%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(file). %% Interface module for the file server and the file io servers. %%% External exports -export([format_error/1]). %% File system and metadata. -export([get_cwd/0, get_cwd/1, set_cwd/1, delete/1, rename/2, make_dir/1, del_dir/1, list_dir/1, list_dir_all/1, read_file_info/1, read_file_info/2, write_file_info/2, write_file_info/3, altname/1, read_link_info/1, read_link_info/2, read_link/1, read_link_all/1, make_link/2, make_symlink/2, read_file/1, write_file/2, write_file/3]). %% Specialized -export([ipread_s32bu_p32bu/3]). %% Generic file contents. -export([open/2, close/1, advise/4, allocate/3, read/2, write/2, pread/2, pread/3, pwrite/2, pwrite/3, read_line/1, position/2, truncate/1, datasync/1, sync/1, copy/2, copy/3]). %% High level operations -export([consult/1, path_consult/2]). -export([eval/1, eval/2, path_eval/2, path_eval/3, path_open/3]). -export([script/1, script/2, path_script/2, path_script/3]). -export([change_owner/2, change_owner/3, change_group/2, change_mode/2, change_time/2, change_time/3]). -export([pid2name/1]). %% Sendfile functions -export([sendfile/2,sendfile/5]). %%% Obsolete exported functions -export([raw_read_file_info/1, raw_write_file_info/2]). %% Internal export to prim_file and ram_file until they implement %% an efficient copy themselves. -export([copy_opened/3]). -export([ipread_s32bu_p32bu_int/3]). %% Types that can be used from other modules -- alphabetically ordered. -export_type([date_time/0, fd/0, file_info/0, filename/0, filename_all/0, io_device/0, name/0, name_all/0, posix/0]). %%% Includes and defines -include("file.hrl"). -define(FILE_IO_SERVER_TABLE, file_io_servers). -define(FILE_SERVER, file_server_2). % Registered name -define(PRIM_FILE, prim_file). % Module -define(RAM_FILE, ram_file). % Module %% data types -type filename() :: string(). -type filename_all() :: string() | binary(). -type file_info() :: #file_info{}. -type fd() :: #file_descriptor{}. -type io_device() :: pid() | fd(). -type location() :: integer() | {'bof', Offset :: integer()} | {'cur', Offset :: integer()} | {'eof', Offset :: integer()} | 'bof' | 'cur' | 'eof'. -type mode() :: 'read' | 'write' | 'append' | 'exclusive' | 'raw' | 'binary' | {'delayed_write', Size :: non_neg_integer(), Delay :: non_neg_integer()} | 'delayed_write' | {'read_ahead', Size :: pos_integer()} | 'read_ahead' | 'compressed' | {'encoding', unicode:encoding()} | sync. -type deep_list() :: [char() | atom() | deep_list()]. -type name() :: string() | atom() | deep_list(). -type name_all() :: string() | atom() | deep_list() | (RawFilename :: binary()). -type posix() :: 'eacces' | 'eagain' | 'ebadf' | 'ebusy' | 'edquot' | 'eexist' | 'efault' | 'efbig' | 'eintr' | 'einval' | 'eio' | 'eisdir' | 'eloop' | 'emfile' | 'emlink' | 'enametoolong' | 'enfile' | 'enodev' | 'enoent' | 'enomem' | 'enospc' | 'enotblk' | 'enotdir' | 'enotsup' | 'enxio' | 'eperm' | 'epipe' | 'erofs' | 'espipe' | 'esrch' | 'estale' | 'exdev'. -type date_time() :: calendar:datetime(). -type posix_file_advise() :: 'normal' | 'sequential' | 'random' | 'no_reuse' | 'will_need' | 'dont_need'. -type sendfile_option() :: {chunk_size, non_neg_integer()} | {use_threads, boolean()}. -type file_info_option() :: {'time', 'local'} | {'time', 'universal'} | {'time', 'posix'} | raw. %%% BIFs -export([native_name_encoding/0]). -spec native_name_encoding() -> latin1 | utf8. native_name_encoding() -> erlang:nif_error(undef). %%% End of BIFs %%%----------------------------------------------------------------- %%% General functions -spec format_error(Reason) -> Chars when Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}, Chars :: string(). format_error({_Line, ?MODULE, undefined_script}) -> "no value returned from script"; format_error({Line, ?MODULE, {Class, Reason, Stacktrace}}) -> io_lib:format("~w: evaluation failed with reason ~w:~w and stacktrace ~w", [Line, Class, Reason, Stacktrace]); format_error({Line, ?MODULE, {Reason, Stacktrace}}) -> io_lib:format("~w: evaluation failed with reason ~w and stacktrace ~w", [Line, Reason, Stacktrace]); format_error({Line, Mod, Reason}) -> io_lib:format("~w: ~ts", [Line, Mod:format_error(Reason)]); format_error(badarg) -> "bad argument"; format_error(system_limit) -> "a system limit was hit, probably not enough ports"; format_error(terminated) -> "the file server process is terminated"; format_error(ErrorId) -> erl_posix_msg:message(ErrorId). -spec pid2name(Pid) -> {ok, Filename} | undefined when Filename :: filename_all(), Pid :: pid(). pid2name(Pid) when is_pid(Pid) -> case whereis(?FILE_SERVER) of undefined -> undefined; _ -> case ets:lookup(?FILE_IO_SERVER_TABLE, Pid) of [{_, Name} | _] -> {ok, Name}; _ -> undefined end end. %%%----------------------------------------------------------------- %%% File server functions. %%% Functions that do not operate on a single open file. %%% Stateless. -spec get_cwd() -> {ok, Dir} | {error, Reason} when Dir :: filename(), Reason :: posix(). get_cwd() -> call(get_cwd, []). -spec get_cwd(Drive) -> {ok, Dir} | {error, Reason} when Drive :: string(), Dir :: filename(), Reason :: posix() | badarg. get_cwd(Drive) -> check_and_call(get_cwd, [file_name(Drive)]). -spec set_cwd(Dir) -> ok | {error, Reason} when Dir :: name() | EncodedBinary, EncodedBinary :: binary(), Reason :: posix() | badarg | no_translation. set_cwd(Dirname) -> check_and_call(set_cwd, [file_name(Dirname)]). -spec delete(Filename) -> ok | {error, Reason} when Filename :: name_all(), Reason :: posix() | badarg. delete(Name) -> check_and_call(delete, [file_name(Name)]). -spec rename(Source, Destination) -> ok | {error, Reason} when Source :: name_all(), Destination :: name_all(), Reason :: posix() | badarg. rename(From, To) -> check_and_call(rename, [file_name(From), file_name(To)]). -spec make_dir(Dir) -> ok | {error, Reason} when Dir :: name_all(), Reason :: posix() | badarg. make_dir(Name) -> check_and_call(make_dir, [file_name(Name)]). -spec del_dir(Dir) -> ok | {error, Reason} when Dir :: name_all(), Reason :: posix() | badarg. del_dir(Name) -> check_and_call(del_dir, [file_name(Name)]). -spec read_file_info(Filename) -> {ok, FileInfo} | {error, Reason} when Filename :: name_all(), FileInfo :: file_info(), Reason :: posix() | badarg. read_file_info(Name) -> check_and_call(read_file_info, [file_name(Name)]). -spec read_file_info(Filename, Opts) -> {ok, FileInfo} | {error, Reason} when Filename :: name_all(), Opts :: [file_info_option()], FileInfo :: file_info(), Reason :: posix() | badarg. read_file_info(Name, Opts) when is_list(Opts) -> Args = [file_name(Name), Opts], case check_args(Args) of ok -> case lists:member(raw, Opts) of true -> [FileName|_] = Args, ?PRIM_FILE:read_file_info(FileName, Opts); false -> call(read_file_info, Args) end; Error -> Error end. -spec altname(Name :: name_all()) -> any(). altname(Name) -> check_and_call(altname, [file_name(Name)]). -spec read_link_info(Name) -> {ok, FileInfo} | {error, Reason} when Name :: name_all(), FileInfo :: file_info(), Reason :: posix() | badarg. read_link_info(Name) -> check_and_call(read_link_info, [file_name(Name)]). -spec read_link_info(Name, Opts) -> {ok, FileInfo} | {error, Reason} when Name :: name_all(), Opts :: [file_info_option()], FileInfo :: file_info(), Reason :: posix() | badarg. read_link_info(Name, Opts) when is_list(Opts) -> Args = [file_name(Name), Opts], case check_args(Args) of ok -> case lists:member(raw, Opts) of true -> [FileName|_] = Args, ?PRIM_FILE:read_link_info(FileName, Opts); false -> call(read_link_info, Args) end; Error -> Error end. -spec read_link(Name) -> {ok, Filename} | {error, Reason} when Name :: name_all(), Filename :: filename(), Reason :: posix() | badarg. read_link(Name) -> check_and_call(read_link, [file_name(Name)]). -spec read_link_all(Name) -> {ok, Filename} | {error, Reason} when Name :: name_all(), Filename :: filename_all(), Reason :: posix() | badarg. read_link_all(Name) -> check_and_call(read_link_all, [file_name(Name)]). -spec write_file_info(Filename, FileInfo) -> ok | {error, Reason} when Filename :: name_all(), FileInfo :: file_info(), Reason :: posix() | badarg. write_file_info(Name, Info = #file_info{}) -> check_and_call(write_file_info, [file_name(Name), Info]). -spec write_file_info(Filename, FileInfo, Opts) -> ok | {error, Reason} when Filename :: name_all(), Opts :: [file_info_option()], FileInfo :: file_info(), Reason :: posix() | badarg. write_file_info(Name, Info = #file_info{}, Opts) when is_list(Opts) -> Args = [file_name(Name), Info, Opts], case check_args(Args) of ok -> case lists:member(raw, Opts) of true -> [FileName|_] = Args, ?PRIM_FILE:write_file_info(FileName, Info, Opts); false -> call(write_file_info, Args) end; Error -> Error end. -spec list_dir(Dir) -> {ok, Filenames} | {error, Reason} when Dir :: name_all(), Filenames :: [filename()], Reason :: posix() | badarg | {no_translation, Filename :: unicode:latin1_binary()}. list_dir(Name) -> check_and_call(list_dir, [file_name(Name)]). -spec list_dir_all(Dir) -> {ok, Filenames} | {error, Reason} when Dir :: name_all(), Filenames :: [filename_all()], Reason :: posix() | badarg. list_dir_all(Name) -> check_and_call(list_dir_all, [file_name(Name)]). -spec read_file(Filename) -> {ok, Binary} | {error, Reason} when Filename :: name_all(), Binary :: binary(), Reason :: posix() | badarg | terminated | system_limit. read_file(Name) -> check_and_call(read_file, [file_name(Name)]). -spec make_link(Existing, New) -> ok | {error, Reason} when Existing :: name_all(), New :: name_all(), Reason :: posix() | badarg. make_link(Old, New) -> check_and_call(make_link, [file_name(Old), file_name(New)]). -spec make_symlink(Existing, New) -> ok | {error, Reason} when Existing :: name_all(), New :: name_all(), Reason :: posix() | badarg. make_symlink(Old, New) -> check_and_call(make_symlink, [file_name(Old), file_name(New)]). -spec write_file(Filename, Bytes) -> ok | {error, Reason} when Filename :: name_all(), Bytes :: iodata(), Reason :: posix() | badarg | terminated | system_limit. write_file(Name, Bin) -> check_and_call(write_file, [file_name(Name), make_binary(Bin)]). %% This whole operation should be moved to the file_server and prim_file %% when it is time to change file server protocol again. %% Meanwhile, it is implemented here, slightly less efficient. -spec write_file(Filename, Bytes, Modes) -> ok | {error, Reason} when Filename :: name_all(), Bytes :: iodata(), Modes :: [mode()], Reason :: posix() | badarg | terminated | system_limit. write_file(Name, Bin, ModeList) when is_list(ModeList) -> case make_binary(Bin) of B when is_binary(B) -> case open(Name, [binary, write | lists:delete(binary, lists:delete(write, ModeList))]) of {ok, Handle} -> case write(Handle, B) of ok -> close(Handle); E1 -> _ = close(Handle), E1 end; E2 -> E2 end; E3 -> E3 end. %% Obsolete, undocumented, local node only, don't use!. %% XXX to be removed. raw_read_file_info(Name) -> read_file_info(Name, [raw]). %% Obsolete, undocumented, local node only, don't use!. %% XXX to be removed. raw_write_file_info(Name, #file_info{} = Info) -> write_file_info(Name, Info, [raw]). %%%----------------------------------------------------------------- %%% File io server functions. %%% They operate on a single open file. %%% Stateful. %% Contemporary mode specification - list of options -spec open(File, Modes) -> {ok, IoDevice} | {error, Reason} when File :: Filename | iodata(), Filename :: name_all(), Modes :: [mode() | ram], IoDevice :: io_device(), Reason :: posix() | badarg | system_limit. open(Item, ModeList) when is_list(ModeList) -> case lists:member(raw, ModeList) of %% Raw file, use ?PRIM_FILE to handle this file true -> Args = [file_name(Item) | ModeList], case check_args(Args) of ok -> [FileName | _] = Args, %% We rely on the returned Handle (in {ok, Handle}) %% being a pid() or a #file_descriptor{} ?PRIM_FILE:open(FileName, ModeList); Error -> Error end; false -> case lists:member(ram, ModeList) of %% RAM file, use ?RAM_FILE to handle this file true -> case check_args(ModeList) of ok -> ?RAM_FILE:open(Item, ModeList); Error -> Error end; %% File server file false -> Args = [file_name(Item) | ModeList], case check_args(Args) of ok -> [FileName | _] = Args, call(open, [FileName, ModeList]); Error -> Error end end end; %% Old obsolete mode specification in atom or 2-tuple format open(Item, Mode) -> open(Item, mode_list(Mode)). %%%----------------------------------------------------------------- %%% The following interface functions operate on open files. %%% The File argument must be either a Pid or a handle %%% returned from ?PRIM_FILE:open. -spec close(IoDevice) -> ok | {error, Reason} when IoDevice :: io_device(), Reason :: posix() | badarg | terminated. close(File) when is_pid(File) -> case file_request(File, close) of {error, terminated} -> ok; Other -> Other end; %% unlink(File), %% exit(File, close), %% ok; close(#file_descriptor{module = Module} = Handle) -> Module:close(Handle); close(_) -> {error, badarg}. -spec advise(IoDevice, Offset, Length, Advise) -> ok | {error, Reason} when IoDevice :: io_device(), Offset :: integer(), Length :: integer(), Advise :: posix_file_advise(), Reason :: posix() | badarg. advise(File, Offset, Length, Advise) when is_pid(File) -> file_request(File, {advise, Offset, Length, Advise}); advise(#file_descriptor{module = Module} = Handle, Offset, Length, Advise) -> Module:advise(Handle, Offset, Length, Advise); advise(_, _, _, _) -> {error, badarg}. -spec allocate(File, Offset, Length) -> 'ok' | {'error', posix()} when File :: io_device(), Offset :: non_neg_integer(), Length :: non_neg_integer(). allocate(File, Offset, Length) when is_pid(File) -> file_request(File, {allocate, Offset, Length}); allocate(#file_descriptor{module = Module} = Handle, Offset, Length) -> Module:allocate(Handle, Offset, Length). -spec read(IoDevice, Number) -> {ok, Data} | eof | {error, Reason} when IoDevice :: io_device() | atom(), Number :: non_neg_integer(), Data :: string() | binary(), Reason :: posix() | badarg | terminated | {no_translation, unicode, latin1}. read(File, Sz) when (is_pid(File) orelse is_atom(File)), is_integer(Sz), Sz >= 0 -> case io:request(File, {get_chars, '', Sz}) of Data when is_list(Data); is_binary(Data) -> {ok, Data}; Other -> Other end; read(#file_descriptor{module = Module} = Handle, Sz) when is_integer(Sz), Sz >= 0 -> Module:read(Handle, Sz); read(_, _) -> {error, badarg}. -spec read_line(IoDevice) -> {ok, Data} | eof | {error, Reason} when IoDevice :: io_device() | atom(), Data :: string() | binary(), Reason :: posix() | badarg | terminated | {no_translation, unicode, latin1}. read_line(File) when (is_pid(File) orelse is_atom(File)) -> case io:request(File, {get_line, ''}) of Data when is_list(Data); is_binary(Data) -> {ok, Data}; Other -> Other end; read_line(#file_descriptor{module = Module} = Handle) -> Module:read_line(Handle); read_line(_) -> {error, badarg}. -spec pread(IoDevice, LocNums) -> {ok, DataL} | eof | {error, Reason} when IoDevice :: io_device(), LocNums :: [{Location :: location(), Number :: non_neg_integer()}], DataL :: [Data], Data :: string() | binary() | eof, Reason :: posix() | badarg | terminated. pread(File, L) when is_pid(File), is_list(L) -> pread_int(File, L, []); pread(#file_descriptor{module = Module} = Handle, L) when is_list(L) -> Module:pread(Handle, L); pread(_, _) -> {error, badarg}. pread_int(_File, [], R) -> {ok, lists:reverse(R)}; pread_int(File, [{At, Sz} | T], R) when is_integer(Sz), Sz >= 0 -> case pread(File, At, Sz) of {ok, Data} -> pread_int(File, T, [Data | R]); eof -> pread_int(File, T, [eof | R]); {error, _} = Error -> Error end; pread_int(_, _, _) -> {error, badarg}. -spec pread(IoDevice, Location, Number) -> {ok, Data} | eof | {error, Reason} when IoDevice :: io_device(), Location :: location(), Number :: non_neg_integer(), Data :: string() | binary(), Reason :: posix() | badarg | terminated. pread(File, At, Sz) when is_pid(File), is_integer(Sz), Sz >= 0 -> file_request(File, {pread, At, Sz}); pread(#file_descriptor{module = Module} = Handle, Offs, Sz) when is_integer(Sz), Sz >= 0 -> Module:pread(Handle, Offs, Sz); pread(_, _, _) -> {error, badarg}. -spec write(IoDevice, Bytes) -> ok | {error, Reason} when IoDevice :: io_device() | atom(), Bytes :: iodata(), Reason :: posix() | badarg | terminated. write(File, Bytes) when (is_pid(File) orelse is_atom(File)) -> case make_binary(Bytes) of Bin when is_binary(Bin) -> io:request(File, {put_chars,latin1,Bin}); Error -> Error end; write(#file_descriptor{module = Module} = Handle, Bytes) -> Module:write(Handle, Bytes); write(_, _) -> {error, badarg}. -spec pwrite(IoDevice, LocBytes) -> ok | {error, {N, Reason}} when IoDevice :: io_device(), LocBytes :: [{Location :: location(), Bytes :: iodata()}], N :: non_neg_integer(), Reason :: posix() | badarg | terminated. pwrite(File, L) when is_pid(File), is_list(L) -> pwrite_int(File, L, 0); pwrite(#file_descriptor{module = Module} = Handle, L) when is_list(L) -> Module:pwrite(Handle, L); pwrite(_, _) -> {error, badarg}. pwrite_int(_File, [], _R) -> ok; pwrite_int(File, [{At, Bytes} | T], R) -> case pwrite(File, At, Bytes) of ok -> pwrite_int(File, T, R+1); {error, Reason} -> {error, {R, Reason}} end; pwrite_int(_, _, _) -> {error, badarg}. -spec pwrite(IoDevice, Location, Bytes) -> ok | {error, Reason} when IoDevice :: io_device(), Location :: location(), Bytes :: iodata(), Reason :: posix() | badarg | terminated. pwrite(File, At, Bytes) when is_pid(File) -> file_request(File, {pwrite, At, Bytes}); pwrite(#file_descriptor{module = Module} = Handle, Offs, Bytes) -> Module:pwrite(Handle, Offs, Bytes); pwrite(_, _, _) -> {error, badarg}. -spec datasync(IoDevice) -> ok | {error, Reason} when IoDevice :: io_device(), Reason :: posix() | badarg | terminated. datasync(File) when is_pid(File) -> file_request(File, datasync); datasync(#file_descriptor{module = Module} = Handle) -> Module:datasync(Handle); datasync(_) -> {error, badarg}. -spec sync(IoDevice) -> ok | {error, Reason} when IoDevice :: io_device(), Reason :: posix() | badarg | terminated. sync(File) when is_pid(File) -> file_request(File, sync); sync(#file_descriptor{module = Module} = Handle) -> Module:sync(Handle); sync(_) -> {error, badarg}. -spec position(IoDevice, Location) -> {ok, NewPosition} | {error, Reason} when IoDevice :: io_device(), Location :: location(), NewPosition :: integer(), Reason :: posix() | badarg | terminated. position(File, At) when is_pid(File) -> file_request(File, {position,At}); position(#file_descriptor{module = Module} = Handle, At) -> Module:position(Handle, At); position(_, _) -> {error, badarg}. -spec truncate(IoDevice) -> ok | {error, Reason} when IoDevice :: io_device(), Reason :: posix() | badarg | terminated. truncate(File) when is_pid(File) -> file_request(File, truncate); truncate(#file_descriptor{module = Module} = Handle) -> Module:truncate(Handle); truncate(_) -> {error, badarg}. -spec copy(Source, Destination) -> {ok, BytesCopied} | {error, Reason} when Source :: io_device() | Filename | {Filename, Modes}, Destination :: io_device() | Filename | {Filename, Modes}, Filename :: name_all(), Modes :: [mode()], BytesCopied :: non_neg_integer(), Reason :: posix() | badarg | terminated. copy(Source, Dest) -> copy_int(Source, Dest, infinity). -spec copy(Source, Destination, ByteCount) -> {ok, BytesCopied} | {error, Reason} when Source :: io_device() | Filename | {Filename, Modes}, Destination :: io_device() | Filename | {Filename, Modes}, Filename :: name_all(), Modes :: [mode()], ByteCount :: non_neg_integer() | infinity, BytesCopied :: non_neg_integer(), Reason :: posix() | badarg | terminated. copy(Source, Dest, Length) when is_integer(Length), Length >= 0; is_atom(Length) -> copy_int(Source, Dest, Length); copy(_, _, _) -> {error, badarg}. %% Here we know that Length is either an atom or an integer >= 0 %% (by the way, atoms > integers) %% %% Copy between open files. copy_int(Source, Dest, Length) when is_pid(Source), is_pid(Dest); is_pid(Source), is_record(Dest, file_descriptor); is_record(Source, file_descriptor), is_pid(Dest) -> copy_opened_int(Source, Dest, Length, 0); %% Copy between open raw files, both handled by the same module copy_int(#file_descriptor{module = Module} = Source, #file_descriptor{module = Module} = Dest, Length) -> Module:copy(Source, Dest, Length); %% Copy between open raw files of different modules copy_int(#file_descriptor{} = Source, #file_descriptor{} = Dest, Length) -> copy_opened_int(Source, Dest, Length, 0); %% Copy between filenames, let the server do the copy copy_int({SourceName, SourceOpts}, {DestName, DestOpts}, Length) when is_list(SourceOpts), is_list(DestOpts) -> check_and_call(copy, [file_name(SourceName), SourceOpts, file_name(DestName), DestOpts, Length]); %% Filename -> open file; must open Source and do client copy copy_int({SourceName, SourceOpts}, Dest, Length) when is_list(SourceOpts), is_pid(Dest); is_list(SourceOpts), is_record(Dest, file_descriptor) -> case file_name(SourceName) of {error, _} = Error -> Error; Source -> case open(Source, [read | SourceOpts]) of {ok, Handle} -> Result = copy_opened_int(Handle, Dest, Length, 0), _ = close(Handle), Result; {error, _} = Error -> Error end end; %% Open file -> filename; must open Dest and do client copy copy_int(Source, {DestName, DestOpts}, Length) when is_pid(Source), is_list(DestOpts); is_record(Source, file_descriptor), is_list(DestOpts) -> case file_name(DestName) of {error, _} = Error -> Error; Dest -> case open(Dest, [write | DestOpts]) of {ok, Handle} -> case copy_opened_int(Source, Handle, Length, 0) of {ok, _} = OK -> case close(Handle) of ok -> OK; Error -> Error end; Error -> _ = close(Handle), Error end; {error, _} = Error -> Error end end; %% %% That was all combinations of {Name, Opts} tuples %% and open files. At least one of Source and Dest has %% to be a bare filename. %% %% If Source is not a bare filename; Dest must be copy_int(Source, Dest, Length) when is_pid(Source); is_record(Source, file_descriptor) -> copy_int(Source, {Dest, []}, Length); copy_int({_SourceName, SourceOpts} = Source, Dest, Length) when is_list(SourceOpts) -> copy_int(Source, {Dest, []}, Length); %% If Dest is not a bare filename; Source must be copy_int(Source, Dest, Length) when is_pid(Dest); is_record(Dest, file_descriptor) -> copy_int({Source, []}, Dest, Length); copy_int(Source, {_DestName, DestOpts} = Dest, Length) when is_list(DestOpts) -> copy_int({Source, []}, Dest, Length); %% Both must be bare filenames. If they are not, %% the filename check in the copy operation will yell. copy_int(Source, Dest, Length) -> copy_int({Source, []}, {Dest, []}, Length). copy_opened(Source, Dest, Length) when is_integer(Length), Length >= 0; is_atom(Length) -> copy_opened_int(Source, Dest, Length); copy_opened(_, _, _) -> {error, badarg}. %% Here we know that Length is either an atom or an integer >= 0 %% (by the way, atoms > integers) copy_opened_int(Source, Dest, Length) when is_pid(Source), is_pid(Dest) -> copy_opened_int(Source, Dest, Length, 0); copy_opened_int(Source, Dest, Length) when is_pid(Source), is_record(Dest, file_descriptor) -> copy_opened_int(Source, Dest, Length, 0); copy_opened_int(Source, Dest, Length) when is_record(Source, file_descriptor), is_pid(Dest) -> copy_opened_int(Source, Dest, Length, 0); copy_opened_int(Source, Dest, Length) when is_record(Source, file_descriptor), is_record(Dest, file_descriptor) -> copy_opened_int(Source, Dest, Length, 0); copy_opened_int(_, _, _) -> {error, badarg}. %% Here we know that Source and Dest are handles to open files, Length is %% as above, and Copied is an integer >= 0 %% Copy loop in client process copy_opened_int(_, _, Length, Copied) when Length =< 0 -> % atom() > integer() {ok, Copied}; copy_opened_int(Source, Dest, Length, Copied) -> N = if Length > 65536 -> 65536; true -> Length end, % atom() > integer() ! case read(Source, N) of {ok, Data} -> M = if is_binary(Data) -> byte_size(Data); is_list(Data) -> length(Data) end, case write(Dest, Data) of ok -> if M < N -> %% Got less than asked for - must be end of file {ok, Copied+M}; true -> %% Decrement Length (might be an atom (infinity)) NewLength = if is_atom(Length) -> Length; true -> Length-M end, copy_opened_int(Source, Dest, NewLength, Copied+M) end; {error, _} = Error -> Error end; eof -> {ok, Copied}; {error, _} = Error -> Error end. %% Special indirect pread function. Introduced for Dets. %% Reads a header from pos 'Pos', the header is first a size encoded as %% 32 bit big endian unsigned and then a position also encoded as %% 32 bit big endian. Finally it preads the data from that pos and size %% in the file. ipread_s32bu_p32bu(File, Pos, MaxSize) when is_pid(File) -> ipread_s32bu_p32bu_int(File, Pos, MaxSize); ipread_s32bu_p32bu(#file_descriptor{module = Module} = Handle, Pos, MaxSize) -> Module:ipread_s32bu_p32bu(Handle, Pos, MaxSize); ipread_s32bu_p32bu(_, _, _) -> {error, badarg}. ipread_s32bu_p32bu_int(File, Pos, Infinity) when is_atom(Infinity) -> ipread_s32bu_p32bu_int(File, Pos, (1 bsl 31)-1); ipread_s32bu_p32bu_int(File, Pos, MaxSize) when is_integer(MaxSize), MaxSize >= 0 -> if MaxSize < (1 bsl 31) -> case pread(File, Pos, 8) of {ok, Header} -> ipread_s32bu_p32bu_2(File, Header, MaxSize); Error -> Error end; true -> {error, einval} end; ipread_s32bu_p32bu_int(_File, _Pos, _MaxSize) -> {error, badarg}. ipread_s32bu_p32bu_2(_File, <<0:32/big-unsigned, Pos:32/big-unsigned>>, _MaxSize) -> {ok, {0, Pos, eof}}; ipread_s32bu_p32bu_2(File, <>, MaxSize) when Size =< MaxSize -> case pread(File, Pos, Size) of {ok, Data} -> {ok, {Size, Pos, Data}}; eof -> {ok, {Size, Pos, eof}}; Error -> Error end; ipread_s32bu_p32bu_2(_File, <<_:8/binary>>, _MaxSize) -> eof; ipread_s32bu_p32bu_2(_File, <<_/binary>>, _MaxSize) -> eof; ipread_s32bu_p32bu_2(File, Header, MaxSize) when is_list(Header) -> ipread_s32bu_p32bu_2(File, list_to_binary(Header), MaxSize). %%%----------------------------------------------------------------- %%% The following functions, built upon the other interface functions, %%% provide a higher-lever interface to files. -spec consult(Filename) -> {ok, Terms} | {error, Reason} when Filename :: name_all(), Terms :: [term()], Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. consult(File) -> case open(File, [read]) of {ok, Fd} -> R = consult_stream(Fd), _ = close(Fd), R; Error -> Error end. -spec path_consult(Path, Filename) -> {ok, Terms, FullName} | {error, Reason} when Path :: [Dir], Dir :: name_all(), Filename :: name_all(), Terms :: [term()], FullName :: filename_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. path_consult(Path, File) -> case path_open(Path, File, [read]) of {ok, Fd, Full} -> case consult_stream(Fd) of {ok, List} -> _ = close(Fd), {ok, List, Full}; E1 -> _ = close(Fd), E1 end; E2 -> E2 end. -spec eval(Filename) -> ok | {error, Reason} when Filename :: name_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. eval(File) -> eval(File, erl_eval:new_bindings()). -spec eval(Filename, Bindings) -> ok | {error, Reason} when Filename :: name_all(), Bindings :: erl_eval:binding_struct(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. eval(File, Bs) -> case open(File, [read]) of {ok, Fd} -> R = eval_stream(Fd, ignore, Bs), _ = close(Fd), R; Error -> Error end. -spec path_eval(Path, Filename) -> {ok, FullName} | {error, Reason} when Path :: [Dir :: name_all()], Filename :: name_all(), FullName :: filename_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. path_eval(Path, File) -> path_eval(Path, File, erl_eval:new_bindings()). -spec path_eval(Path, Filename, Bindings) -> {ok, FullName} | {error, Reason} when Path :: [Dir :: name_all()], Filename :: name_all(), Bindings :: erl_eval:binding_struct(), FullName :: filename_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. path_eval(Path, File, Bs) -> case path_open(Path, File, [read]) of {ok, Fd, Full} -> case eval_stream(Fd, ignore, Bs) of ok -> _ = close(Fd), {ok, Full}; E1 -> _ = close(Fd), E1 end; E2 -> E2 end. -spec script(Filename) -> {ok, Value} | {error, Reason} when Filename :: name_all(), Value :: term(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. script(File) -> script(File, erl_eval:new_bindings()). -spec script(Filename, Bindings) -> {ok, Value} | {error, Reason} when Filename :: name_all(), Bindings :: erl_eval:binding_struct(), Value :: term(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. script(File, Bs) -> case open(File, [read]) of {ok, Fd} -> R = eval_stream(Fd, return, Bs), _ = close(Fd), R; Error -> Error end. -spec path_script(Path, Filename) -> {ok, Value, FullName} | {error, Reason} when Path :: [Dir :: name_all()], Filename :: name_all(), Value :: term(), FullName :: filename_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. path_script(Path, File) -> path_script(Path, File, erl_eval:new_bindings()). -spec path_script(Path, Filename, Bindings) -> {ok, Value, FullName} | {error, Reason} when Path :: [Dir :: name_all()], Filename :: name_all(), Bindings :: erl_eval:binding_struct(), Value :: term(), FullName :: filename_all(), Reason :: posix() | badarg | terminated | system_limit | {Line :: integer(), Mod :: module(), Term :: term()}. path_script(Path, File, Bs) -> case path_open(Path, File, [read]) of {ok,Fd,Full} -> case eval_stream(Fd, return, Bs) of {ok,R} -> _ = close(Fd), {ok, R, Full}; E1 -> _ = close(Fd), E1 end; E2 -> E2 end. %% path_open(Paths, Filename, Mode) -> %% {ok,FileDescriptor,FullName} %% {error,Reason} %% %% Searches the Paths for file Filename which can be opened with Mode. %% The path list is ignored if Filename contains an absolute path. -spec path_open(Path, Filename, Modes) -> {ok, IoDevice, FullName} | {error, Reason} when Path :: [Dir :: name_all()], Filename :: name_all(), Modes :: [mode()], IoDevice :: io_device(), FullName :: filename_all(), Reason :: posix() | badarg | system_limit. path_open(PathList, Name, Mode) -> case file_name(Name) of {error, _} = Error -> Error; FileName -> case filename:pathtype(FileName) of relative -> path_open_first(PathList, FileName, Mode, enoent); _ -> case open(Name, Mode) of {ok, Fd} -> {ok, Fd, Name}; Error -> Error end end end. -spec change_mode(Filename, Mode) -> ok | {error, Reason} when Filename :: name_all(), Mode :: integer(), Reason :: posix() | badarg. change_mode(Name, Mode) when is_integer(Mode) -> write_file_info(Name, #file_info{mode=Mode}). -spec change_owner(Filename, Uid) -> ok | {error, Reason} when Filename :: name_all(), Uid :: integer(), Reason :: posix() | badarg. change_owner(Name, OwnerId) when is_integer(OwnerId) -> write_file_info(Name, #file_info{uid=OwnerId}). -spec change_owner(Filename, Uid, Gid) -> ok | {error, Reason} when Filename :: name_all(), Uid :: integer(), Gid :: integer(), Reason :: posix() | badarg. change_owner(Name, OwnerId, GroupId) when is_integer(OwnerId), is_integer(GroupId) -> write_file_info(Name, #file_info{uid=OwnerId, gid=GroupId}). -spec change_group(Filename, Gid) -> ok | {error, Reason} when Filename :: name_all(), Gid :: integer(), Reason :: posix() | badarg. change_group(Name, GroupId) when is_integer(GroupId) -> write_file_info(Name, #file_info{gid=GroupId}). -spec change_time(Filename, Mtime) -> ok | {error, Reason} when Filename :: name_all(), Mtime :: date_time(), Reason :: posix() | badarg. change_time(Name, {{Y, M, D}, {H, Min, Sec}}=Time) when is_integer(Y), is_integer(M), is_integer(D), is_integer(H), is_integer(Min), is_integer(Sec)-> write_file_info(Name, #file_info{mtime=Time}). -spec change_time(Filename, Atime, Mtime) -> ok | {error, Reason} when Filename :: name_all(), Atime :: date_time(), Mtime :: date_time(), Reason :: posix() | badarg. change_time(Name, {{AY, AM, AD}, {AH, AMin, ASec}}=Atime, {{MY, MM, MD}, {MH, MMin, MSec}}=Mtime) when is_integer(AY), is_integer(AM), is_integer(AD), is_integer(AH), is_integer(AMin), is_integer(ASec), is_integer(MY), is_integer(MM), is_integer(MD), is_integer(MH), is_integer(MMin), is_integer(MSec)-> write_file_info(Name, #file_info{atime=Atime, mtime=Mtime}). %% %% Send data using sendfile %% -define(MAX_CHUNK_SIZE, (1 bsl 20)*20). %% 20 MB, has to fit in primary memory -spec sendfile(RawFile, Socket, Offset, Bytes, Opts) -> {'ok', non_neg_integer()} | {'error', inet:posix() | closed | badarg | not_owner} when RawFile :: fd(), Socket :: inet:socket(), Offset :: non_neg_integer(), Bytes :: non_neg_integer(), Opts :: [sendfile_option()]. sendfile(File, _Sock, _Offet, _Bytes, _Opts) when is_pid(File) -> {error, badarg}; sendfile(File, Sock, Offset, Bytes, []) -> sendfile(File, Sock, Offset, Bytes, ?MAX_CHUNK_SIZE, [], [], []); sendfile(File, Sock, Offset, Bytes, Opts) -> ChunkSize0 = proplists:get_value(chunk_size, Opts, ?MAX_CHUNK_SIZE), ChunkSize = if ChunkSize0 > ?MAX_CHUNK_SIZE -> ?MAX_CHUNK_SIZE; true -> ChunkSize0 end, %% Support for headers, trailers and options has been removed because the %% Darwin and BSD API for using it does not play nice with %% non-blocking sockets. See unix_efile.c for more info. sendfile(File, Sock, Offset, Bytes, ChunkSize, [], [], Opts). %% sendfile/2 -spec sendfile(Filename, Socket) -> {'ok', non_neg_integer()} | {'error', inet:posix() | closed | badarg | not_owner} when Filename :: name_all(), Socket :: inet:socket(). sendfile(Filename, Sock) -> case file:open(Filename, [read, raw, binary]) of {error, Reason} -> {error, Reason}; {ok, Fd} -> Res = sendfile(Fd, Sock, 0, 0, []), _ = file:close(Fd), Res end. %% Internal sendfile functions sendfile(#file_descriptor{ module = Mod } = Fd, Sock, Offset, Bytes, ChunkSize, Headers, Trailers, Opts) when is_port(Sock) -> case Mod:sendfile(Fd, Sock, Offset, Bytes, ChunkSize, Headers, Trailers, Opts) of {error, enotsup} -> sendfile_fallback(Fd, Sock, Offset, Bytes, ChunkSize, Headers, Trailers); Else -> Else end; sendfile(_,_,_,_,_,_,_,_) -> {error, badarg}. %%% %% Sendfile Fallback %%% sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers) when Headers == []; is_integer(Headers) -> case sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) of {ok, BytesSent} when is_list(Trailers), Trailers =/= [], is_integer(Headers) -> sendfile_send(Sock, Trailers, BytesSent+Headers); {ok, BytesSent} when is_list(Trailers), Trailers =/= [] -> sendfile_send(Sock, Trailers, BytesSent); {ok, BytesSent} when is_integer(Headers) -> {ok, BytesSent + Headers}; Else -> Else end; sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers) -> case sendfile_send(Sock, Headers, 0) of {ok, BytesSent} -> sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, BytesSent, Trailers); Else -> Else end. sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) -> {ok, CurrPos} = file:position(File, {cur, 0}), {ok, _NewPos} = file:position(File, {bof, Offset}), Res = sendfile_fallback_int(File, Sock, Bytes, ChunkSize, 0), _ = file:position(File, {bof, CurrPos}), Res. sendfile_fallback_int(File, Sock, Bytes, ChunkSize, BytesSent) when Bytes > BytesSent; Bytes == 0 -> Size = if Bytes == 0 -> ChunkSize; (Bytes - BytesSent) < ChunkSize -> Bytes - BytesSent; true -> ChunkSize end, case file:read(File, Size) of {ok, Data} -> case sendfile_send(Sock, Data, BytesSent) of {ok,NewBytesSent} -> sendfile_fallback_int( File, Sock, Bytes, ChunkSize, NewBytesSent); Error -> Error end; eof -> {ok, BytesSent}; Error -> Error end; sendfile_fallback_int(_File, _Sock, BytesSent, _ChunkSize, BytesSent) -> {ok, BytesSent}. sendfile_send(Sock, Data, Old) -> Len = iolist_size(Data), case gen_tcp:send(Sock, Data) of ok -> {ok, Len+Old}; Else -> Else end. %%%----------------------------------------------------------------- %%% Helpers consult_stream(Fd) -> _ = epp:set_encoding(Fd), consult_stream(Fd, 1, []). consult_stream(Fd, Line, Acc) -> case io:read(Fd, '', Line) of {ok,Term,EndLine} -> consult_stream(Fd, EndLine, [Term|Acc]); {error,Error,_Line} -> {error,Error}; {eof,_Line} -> {ok,lists:reverse(Acc)} end. eval_stream(Fd, Handling, Bs) -> _ = epp:set_encoding(Fd), eval_stream(Fd, Handling, 1, undefined, [], Bs). eval_stream(Fd, H, Line, Last, E, Bs) -> eval_stream2(io:parse_erl_exprs(Fd, '', Line), Fd, H, Last, E, Bs). eval_stream2({ok,Form,EndLine}, Fd, H, Last, E, Bs0) -> try erl_eval:exprs(Form, Bs0) of {value,V,Bs} -> eval_stream(Fd, H, EndLine, {V}, E, Bs) catch Class:Reason -> Error = {EndLine,?MODULE,{Class,Reason,erlang:get_stacktrace()}}, eval_stream(Fd, H, EndLine, Last, [Error|E], Bs0) end; eval_stream2({error,What,EndLine}, Fd, H, Last, E, Bs) -> eval_stream(Fd, H, EndLine, Last, [What | E], Bs); eval_stream2({eof,EndLine}, _Fd, H, Last, E, _Bs) -> case {H, Last, E} of {return, {Val}, []} -> {ok, Val}; {return, undefined, E} -> {error, hd(lists:reverse(E, [{EndLine,?MODULE,undefined_script}]))}; {ignore, _, []} -> ok; {_, _, [_|_] = E} -> {error, hd(lists:reverse(E))} end. path_open_first([Path|Rest], Name, Mode, LastError) -> case file_name(Path) of {error, _} = Error -> Error; FilePath -> FileName = fname_join(FilePath, Name), case open(FileName, Mode) of {ok, Fd} -> {ok, Fd, FileName}; {error, enoent} -> path_open_first(Rest, Name, Mode, LastError); Error -> Error end end; path_open_first([], _Name, _Mode, LastError) -> {error, LastError}. fname_join(".", Name) -> Name; fname_join(Dir, Name) -> filename:join(Dir, Name). %%%----------------------------------------------------------------- %%% Utility functions. %% file_name(FileName) %% Generates a flat file name from a deep list of atoms and %% characters (integers). file_name(N) when is_binary(N) -> N; file_name(N) -> try file_name_1(N,file:native_name_encoding()) catch Reason -> {error, Reason} end. file_name_1([C|T],latin1) when is_integer(C), C < 256-> [C|file_name_1(T,latin1)]; file_name_1([C|T],utf8) when is_integer(C) -> [C|file_name_1(T,utf8)]; file_name_1([H|T],E) -> file_name_1(H,E) ++ file_name_1(T,E); file_name_1([],_) -> []; file_name_1(N,_) when is_atom(N) -> atom_to_list(N); file_name_1(_,_) -> throw(badarg). make_binary(Bin) when is_binary(Bin) -> Bin; make_binary(List) -> %% Convert the list to a binary in order to avoid copying a list %% to the file server. try erlang:iolist_to_binary(List) catch error:Reason -> {error, Reason} end. mode_list(read) -> [read]; mode_list(write) -> [write]; mode_list(read_write) -> [read, write]; mode_list({binary, Mode}) when is_atom(Mode) -> [binary | mode_list(Mode)]; mode_list({character, Mode}) when is_atom(Mode) -> mode_list(Mode); mode_list(_) -> [{error, badarg}]. %%----------------------------------------------------------------- %% Functions for communicating with the file server call(Command, Args) when is_list(Args) -> X = erlang:dt_spread_tag(true), Y = gen_server:call(?FILE_SERVER, list_to_tuple([Command | Args]), infinity), erlang:dt_restore_tag(X), Y. check_and_call(Command, Args) when is_list(Args) -> case check_args(Args) of ok -> call(Command, Args); Error -> Error end. check_args([{error, _}=Error|_Rest]) -> Error; check_args([_Name|Rest]) -> check_args(Rest); check_args([]) -> ok. %%----------------------------------------------------------------- %% Function for communicating with a file io server. %% The messages sent have the following formats: %% %% {file_request,From,ReplyAs,Request} %% {file_reply,ReplyAs,Reply} file_request(Io, Request) -> Ref = erlang:monitor(process, Io), Io ! {file_request,self(),Ref,Request}, receive {file_reply,Ref,Reply} -> erlang:demonitor(Ref, [flush]), Reply; {'DOWN', Ref, _, _, _} -> {error, terminated} end.