%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(prim_file).
%% Interface module to the file driver.
%%% Interface towards a single file's contents. Uses ?FD_DRV.
%% Generic file contents operations
-export([open/2, close/1, datasync/1, sync/1, advise/4, position/2, truncate/1,
write/2, pwrite/2, pwrite/3, read/2, read_line/1, pread/2, pread/3,
copy/3, sendfile/10]).
%% Specialized file operations
-export([open/1, open/3]).
-export([read_file/1, read_file/2, write_file/2]).
-export([ipread_s32bu_p32bu/3]).
%%% Interface towards file system and metadata. Uses ?DRV.
%% Takes an optional port (opens a ?DRV port per default) as first argument.
-export([get_cwd/0, get_cwd/1, get_cwd/2,
set_cwd/1, set_cwd/2,
delete/1, delete/2,
rename/2, rename/3,
make_dir/1, make_dir/2,
del_dir/1, del_dir/2,
read_file_info/1, read_file_info/2, read_file_info/3,
altname/1, altname/2,
write_file_info/2, write_file_info/3, write_file_info/4,
make_link/2, make_link/3,
make_symlink/2, make_symlink/3,
read_link/1, read_link/2,
read_link_info/1, read_link_info/2, read_link_info/3,
list_dir/1, list_dir/2]).
%% How to start and stop the ?DRV port.
-export([start/0, stop/1]).
%% Debug exports
-export([open_int/4, open_mode/1, open_mode/4]).
%%%-----------------------------------------------------------------
%%% Includes and defines
-include("file.hrl").
-define(DRV, "efile").
-define(FD_DRV, "efile").
-define(LARGEFILESIZE, (1 bsl 63)).
%% Driver commands
-define(FILE_OPEN, 1).
-define(FILE_READ, 2).
-define(FILE_LSEEK, 3).
-define(FILE_WRITE, 4).
-define(FILE_FSTAT, 5).
-define(FILE_PWD, 6).
-define(FILE_READDIR, 7).
-define(FILE_CHDIR, 8).
-define(FILE_FSYNC, 9).
-define(FILE_MKDIR, 10).
-define(FILE_DELETE, 11).
-define(FILE_RENAME, 12).
-define(FILE_RMDIR, 13).
-define(FILE_TRUNCATE, 14).
-define(FILE_READ_FILE, 15).
-define(FILE_WRITE_INFO, 16).
-define(FILE_LSTAT, 19).
-define(FILE_READLINK, 20).
-define(FILE_LINK, 21).
-define(FILE_SYMLINK, 22).
-define(FILE_CLOSE, 23).
-define(FILE_PWRITEV, 24).
-define(FILE_PREADV, 25).
-define(FILE_SETOPT, 26).
-define(FILE_IPREAD, 27).
-define(FILE_ALTNAME, 28).
-define(FILE_READ_LINE, 29).
-define(FILE_FDATASYNC, 30).
-define(FILE_ADVISE, 31).
-define(FILE_SENDFILE, 32).
%% Driver responses
-define(FILE_RESP_OK, 0).
-define(FILE_RESP_ERROR, 1).
-define(FILE_RESP_DATA, 2).
-define(FILE_RESP_NUMBER, 3).
-define(FILE_RESP_INFO, 4).
-define(FILE_RESP_NUMERR, 5).
-define(FILE_RESP_LDATA, 6).
-define(FILE_RESP_N2DATA, 7).
-define(FILE_RESP_EOF, 8).
-define(FILE_RESP_FNAME, 9).
-define(FILE_RESP_ALL_DATA, 10).
-define(FILE_RESP_LFNAME, 11).
%% Open modes for the driver's open function.
-define(EFILE_MODE_READ, 1).
-define(EFILE_MODE_WRITE, 2).
-define(EFILE_MODE_READ_WRITE, 3).
-define(EFILE_MODE_APPEND, 4).
-define(EFILE_COMPRESSED, 8).
-define(EFILE_MODE_EXCL, 16).
%% Use this mask to get just the mode bits to be passed to the driver.
-define(EFILE_MODE_MASK, 31).
%% Seek modes for the driver's seek function.
-define(EFILE_SEEK_SET, 0).
-define(EFILE_SEEK_CUR, 1).
-define(EFILE_SEEK_END, 2).
%% Options
-define(FILE_OPT_DELAYED_WRITE, 0).
-define(FILE_OPT_READ_AHEAD, 1).
%% IPREAD variants
-define(IPREAD_S32BU_P32BU, 0).
%% POSIX file advises
-define(POSIX_FADV_NORMAL, 0).
-define(POSIX_FADV_RANDOM, 1).
-define(POSIX_FADV_SEQUENTIAL, 2).
-define(POSIX_FADV_WILLNEED, 3).
-define(POSIX_FADV_DONTNEED, 4).
-define(POSIX_FADV_NOREUSE, 5).
%%% BIFs
-export([internal_name2native/1,
internal_native2name/1,
internal_normalize_utf8/1]).
-type unicode_string() :: [unicode:unicode_char()].
-type prim_file_name() :: unicode_string() | unicode:unicode_binary().
-spec internal_name2native(prim_file_name()) -> binary().
internal_name2native(_) ->
erlang:nif_error(undefined).
-spec internal_native2name(binary()) -> prim_file_name().
internal_native2name(_) ->
erlang:nif_error(undefined).
-spec internal_normalize_utf8(unicode:unicode_binary()) -> unicode_string().
internal_normalize_utf8(_) ->
erlang:nif_error(undefined).
%%% End of BIFs
%%%-----------------------------------------------------------------
%%% Functions operating on a file through a handle. ?FD_DRV.
%%%
%%% Generic file contents operations.
%%%
%%% Supposed to be called by applications through module file.
%% Opens a file using the driver port Port. Returns {error, Reason}
%% | {ok, FileDescriptor}
open(Port, File, ModeList) when is_port(Port),
(is_list(File) orelse is_binary(File)),
is_list(ModeList) ->
case open_mode(ModeList) of
{Mode, _Portopts, _Setopts} ->
open_int(Port, File, Mode, []);
Reason ->
{error, Reason}
end;
open(_,_,_) ->
{error, badarg}.
%% Opens a file. Returns {error, Reason} | {ok, FileDescriptor}.
open(File, ModeList) when (is_list(File) orelse is_binary(File)),
is_list(ModeList) ->
case open_mode(ModeList) of
{Mode, Portopts, Setopts} ->
open_int({?FD_DRV, Portopts},File, Mode, Setopts);
Reason ->
{error, Reason}
end;
open(_, _) ->
{error, badarg}.
%% Opens a port that can be used for open/3 or read_file/2.
%% Returns {ok, Port} | {error, Reason}.
open(Portopts) when is_list(Portopts) ->
case drv_open(?FD_DRV, Portopts) of
{error, _} = Error ->
Error;
Other ->
Other
end;
open(_) ->
{error, badarg}.
open_int({Driver, Portopts}, File, Mode, Setopts) ->
case drv_open(Driver, Portopts) of
{ok, Port} ->
open_int(Port, File, Mode, Setopts);
{error, _} = Error ->
Error
end;
open_int(Port, File, Mode, Setopts) ->
M = Mode band ?EFILE_MODE_MASK,
case drv_command(Port, [<<?FILE_OPEN, M:32>>, pathname(File)]) of
{ok, Number} ->
open_int_setopts(Port, Number, Setopts);
Error ->
drv_close(Port),
Error
end.
open_int_setopts(Port, Number, []) ->
{ok, #file_descriptor{module = ?MODULE, data = {Port, Number}}};
open_int_setopts(Port, Number, [Cmd | Tail]) ->
case drv_command(Port, Cmd) of
ok ->
open_int_setopts(Port, Number, Tail);
Error ->
drv_close(Port),
Error
end.
%% Returns ok.
close(#file_descriptor{module = ?MODULE, data = {Port, _}}) ->
case drv_command(Port, <<?FILE_CLOSE>>) of
ok ->
drv_close(Port);
Error ->
Error
end;
%% Closes a port opened with open/1.
close(Port) when is_port(Port) ->
drv_close(Port).
-define(ADVISE(Offs, Len, Adv),
<<?FILE_ADVISE, Offs:64/signed, Len:64/signed,
Adv:32/signed>>).
%% Returns {error, Reason} | ok.
advise(#file_descriptor{module = ?MODULE, data = {Port, _}},
Offset, Length, Advise) ->
case Advise of
normal ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_NORMAL),
drv_command(Port, Cmd);
random ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_RANDOM),
drv_command(Port, Cmd);
sequential ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_SEQUENTIAL),
drv_command(Port, Cmd);
will_need ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_WILLNEED),
drv_command(Port, Cmd);
dont_need ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_DONTNEED),
drv_command(Port, Cmd);
no_reuse ->
Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_NOREUSE),
drv_command(Port, Cmd);
_ ->
{error, einval}
end.
%% Returns {error, Reason} | ok.
write(#file_descriptor{module = ?MODULE, data = {Port, _}}, Bytes) ->
case drv_command_nt(Port, [?FILE_WRITE,erlang:dt_prepend_vm_tag_data(Bytes)],undefined) of
{ok, _Size} ->
ok;
Error ->
Error
end.
%% Returns ok | {error, {WrittenCount, Reason}}
pwrite(#file_descriptor{module = ?MODULE, data = {Port, _}}, L)
when is_list(L) ->
pwrite_int(Port, L, 0, [], []).
pwrite_int(_, [], 0, [], []) ->
ok;
pwrite_int(Port, [], N, Spec, Data) ->
Header = list_to_binary([?FILE_PWRITEV, erlang:dt_prepend_vm_tag_data(<<N:32>>) | reverse(Spec)]),
case drv_command_nt(Port, [Header | reverse(Data)], undefined) of
{ok, _Size} ->
ok;
Error ->
Error
end;
pwrite_int(Port, [{Offs, Bytes} | T], N, Spec, Data)
when is_integer(Offs) ->
if
-(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE ->
pwrite_int(Port, T, N, Spec, Data, Offs, Bytes);
true ->
{error, einval}
end;
pwrite_int(_, [_|_], _N, _Spec, _Data) ->
{error, badarg}.
pwrite_int(Port, T, N, Spec, Data, Offs, Bin)
when is_binary(Bin) ->
Size = byte_size(Bin),
pwrite_int(Port, T, N+1,
[<<Offs:64/signed, Size:64>> | Spec],
[Bin | Data]);
pwrite_int(Port, T, N, Spec, Data, Offs, Bytes) ->
try list_to_binary(Bytes) of
Bin ->
pwrite_int(Port, T, N, Spec, Data, Offs, Bin)
catch
error:Reason ->
{error, Reason}
end.
%% Returns {error, Reason} | ok.
pwrite(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offs, Bytes)
when is_integer(Offs) ->
if
-(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE ->
case pwrite_int(Port, [], 0, [], [], Offs, Bytes) of
{error, {_, Reason}} ->
{error, Reason};
Result ->
Result
end;
true ->
{error, einval}
end;
pwrite(#file_descriptor{module = ?MODULE}, _, _) ->
{error, badarg}.
%% Returns {error, Reason} | ok.
datasync(#file_descriptor{module = ?MODULE, data = {Port, _}}) ->
drv_command(Port, [?FILE_FDATASYNC]).
%% Returns {error, Reason} | ok.
sync(#file_descriptor{module = ?MODULE, data = {Port, _}}) ->
drv_command(Port, [?FILE_FSYNC]).
%% Returns {ok, Data} | eof | {error, Reason}.
read_line(#file_descriptor{module = ?MODULE, data = {Port, _}}) ->
case drv_command(Port, <<?FILE_READ_LINE>>) of
{ok, {0, _Data}} ->
eof;
{ok, {_Size, Data}} ->
{ok, Data};
{error, enomem} ->
erlang:garbage_collect(),
case drv_command(Port, <<?FILE_READ_LINE>>) of
{ok, {0, _Data}} ->
eof;
{ok, {_Size, Data}} ->
{ok, Data};
Other ->
Other
end;
Error ->
Error
end.
%% Returns {ok, Data} | eof | {error, Reason}.
read(#file_descriptor{module = ?MODULE, data = {Port, _}}, Size)
when is_integer(Size), 0 =< Size ->
if
Size < ?LARGEFILESIZE ->
case drv_command(Port, <<?FILE_READ, Size:64>>) of
{ok, {0, _Data}} when Size =/= 0 ->
eof;
{ok, {_Size, Data}} ->
{ok, Data};
{error, enomem} ->
%% Garbage collecting here might help if
%% the current processes have some old binaries left.
erlang:garbage_collect(),
case drv_command(Port, <<?FILE_READ, Size:64>>) of
{ok, {0, _Data}} when Size =/= 0 ->
eof;
{ok, {_Size, Data}} ->
{ok, Data};
Other ->
Other
end;
Error ->
Error
end;
true ->
{error, einval}
end.
%% Returns {ok, [Data|eof, ...]} | {error, Reason}
pread(#file_descriptor{module = ?MODULE, data = {Port, _}}, L)
when is_list(L) ->
pread_int(Port, L, 0, []).
pread_int(_, [], 0, []) ->
{ok, []};
pread_int(Port, [], N, Spec) ->
drv_command_nt(Port, [?FILE_PREADV, erlang:dt_prepend_vm_tag_data(<<0:32, N:32>>) | reverse(Spec)],undefined);
pread_int(Port, [{Offs, Size} | T], N, Spec)
when is_integer(Offs), is_integer(Size), 0 =< Size ->
if
-(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE,
Size < ?LARGEFILESIZE ->
pread_int(Port, T, N+1, [<<Offs:64/signed, Size:64>> | Spec]);
true ->
{error, einval}
end;
pread_int(_, [_|_], _N, _Spec) ->
{error, badarg}.
%% Returns {ok, Data} | eof | {error, Reason}.
pread(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offs, Size)
when is_integer(Offs), is_integer(Size), 0 =< Size ->
if
-(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE,
Size < ?LARGEFILESIZE ->
case drv_command_nt(Port,
[?FILE_PREADV, erlang:dt_prepend_vm_tag_data(<<0:32, 1:32,
Offs:64/signed, Size:64>>)], undefined) of
{ok, [eof]} ->
eof;
{ok, [Data]} ->
{ok, Data};
Error ->
Error
end;
true ->
{error, einval}
end;
pread(#file_descriptor{module = ?MODULE, data = {_, _}}, _, _) ->
{error, badarg}.
%% Returns {ok, Position} | {error, Reason}.
position(#file_descriptor{module = ?MODULE, data = {Port, _}}, At) ->
case lseek_position(At) of
{Offs, Whence}
when -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE ->
drv_command(Port, <<?FILE_LSEEK, Offs:64/signed, Whence:32>>);
{_, _} ->
{error, einval};
Reason ->
{error, Reason}
end.
%% Returns {error, Reaseon} | ok.
truncate(#file_descriptor{module = ?MODULE, data = {Port, _}}) ->
drv_command(Port, <<?FILE_TRUNCATE>>).
%% Returns {error, Reason} | {ok, BytesCopied}
copy(#file_descriptor{module = ?MODULE} = Source,
#file_descriptor{module = ?MODULE} = Dest,
Length)
when is_integer(Length), Length >= 0;
is_atom(Length) ->
%% XXX Should be moved down to the driver for optimization.
file:copy_opened(Source, Dest, Length).
ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE,
data = {_, _}} = Handle,
Offs,
Infinity) when is_atom(Infinity) ->
ipread_s32bu_p32bu(Handle, Offs, (1 bsl 31)-1);
ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE, data = {Port, _}},
Offs,
MaxSize)
when is_integer(Offs), is_integer(MaxSize) ->
if
-(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE,
0 =< MaxSize, MaxSize < (1 bsl 31) ->
drv_command(Port, <<?FILE_IPREAD, ?IPREAD_S32BU_P32BU,
Offs:64, MaxSize:32>>);
true ->
{error, einval}
end;
ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE, data = {_, _}},
_Offs,
_MaxSize) ->
{error, badarg}.
%% Returns {ok, Contents} | {error, Reason}
read_file(File) when (is_list(File) orelse is_binary(File)) ->
case drv_open(?FD_DRV, [binary]) of
{ok, Port} ->
Result = read_file(Port, File),
close(Port),
Result;
{error, _} = Error ->
Error
end;
read_file(_) ->
{error, badarg}.
%% Takes a Port opened with open/1.
read_file(Port, File) when is_port(Port),
(is_list(File) orelse is_binary(File)) ->
Cmd = [?FILE_READ_FILE | pathname(File)],
case drv_command(Port, Cmd) of
{error, enomem} ->
%% It could possibly help to do a
%% garbage collection here,
%% if the file server has some references
%% to binaries read earlier.
erlang:garbage_collect(),
drv_command(Port, Cmd);
Result ->
Result
end;
read_file(_,_) ->
{error, badarg}.
%% Returns {error, Reason} | ok.
write_file(File, Bin) when (is_list(File) orelse is_binary(File)) ->
case open(File, [binary, write]) of
{ok, Handle} ->
Result = write(Handle, Bin),
close(Handle),
Result;
Error ->
Error
end;
write_file(_, _) ->
{error, badarg}.
%% Returns {error, Reason} | {ok, BytesCopied}
%sendfile(_,_,_,_,_,_,_,_,_,_) ->
% {error, enotsup};
sendfile(#file_descriptor{module = ?MODULE, data = {Port, _}},
Dest, Offset, Bytes, _ChunkSize, Headers, Trailers,
_Nodiskio, _MNowait, _Sync) ->
case erlang:port_get_data(Dest) of
Data when Data == inet_tcp; Data == inet6_tcp ->
ok = inet:lock_socket(Dest,true),
{ok, DestFD} = prim_inet:getfd(Dest),
try drv_command(Port, [<<?FILE_SENDFILE, DestFD:32,
0:8,
Offset:64/unsigned,
Bytes:64/unsigned,
(iolist_size(Headers)):32/unsigned,
(iolist_size(Trailers)):32/unsigned>>,
Headers,Trailers])
after
ok = inet:lock_socket(Dest,false)
end;
_Else ->
{error,badarg}
end.
%%%-----------------------------------------------------------------
%%% Functions operating on files without handle to the file. ?DRV.
%%%
%%% Supposed to be called by applications through module file.
%% Returns {ok, Port}, the Port should be used as first argument in all
%% the following functions. Returns {error, Reason} upon failure.
start() ->
try erlang:open_port({spawn, ?DRV}, [binary]) of
Port ->
{ok, Port}
catch
error:Reason ->
{error, Reason}
end.
stop(Port) when is_port(Port) ->
try erlang:port_close(Port) of
_ ->
ok
catch
_:_ ->
ok
end.
%%% The following functions take an optional Port as first argument.
%%% If the port is not supplied, a temporary one is opened and then
%%% closed after the request has been performed.
%% get_cwd/{0,1,2}
get_cwd() ->
get_cwd_int(0).
get_cwd(Port) when is_port(Port) ->
get_cwd_int(Port, 0);
get_cwd([]) ->
get_cwd_int(0);
get_cwd([Letter, $: | _]) when $a =< Letter, Letter =< $z ->
get_cwd_int(Letter - $a + 1);
get_cwd([Letter, $: | _]) when $A =< Letter, Letter =< $Z ->
get_cwd_int(Letter - $A + 1);
get_cwd([_|_]) ->
{error, einval};
get_cwd(_) ->
{error, badarg}.
get_cwd(Port, []) when is_port(Port) ->
get_cwd_int(Port, 0);
get_cwd(Port, [Letter, $: | _])
when is_port(Port), $a =< Letter, Letter =< $z ->
get_cwd_int(Port, Letter - $a + 1);
get_cwd(Port, [Letter, $: | _])
when is_port(Port), $A =< Letter, Letter =< $Z ->
get_cwd_int(Port, Letter - $A + 1);
get_cwd(Port, [_|_]) when is_port(Port) ->
{error, einval};
get_cwd(_, _) ->
{error, badarg}.
get_cwd_int(Drive) ->
get_cwd_int({?DRV, [binary]}, Drive).
get_cwd_int(Port, Drive) ->
drv_command(Port, <<?FILE_PWD, Drive>>).
%% set_cwd/{1,2}
set_cwd(Dir) ->
set_cwd_int({?DRV, [binary]}, Dir).
set_cwd(Port, Dir) when is_port(Port) ->
set_cwd_int(Port, Dir).
set_cwd_int(Port, Dir) ->
%% Dir is now either a string or an EXIT tuple.
%% An EXIT tuple will fail in the following catch.
drv_command(Port, [?FILE_CHDIR, pathname(Dir)]).
%% delete/{1,2}
delete(File) ->
delete_int({?DRV, [binary]}, File).
delete(Port, File) when is_port(Port) ->
delete_int(Port, File).
delete_int(Port, File) ->
drv_command(Port, [?FILE_DELETE, pathname(File)]).
%% rename/{2,3}
rename(From, To) ->
rename_int({?DRV, [binary]}, From, To).
rename(Port, From, To) when is_port(Port) ->
rename_int(Port, From, To).
rename_int(Port, From, To) ->
drv_command(Port, [?FILE_RENAME, pathname(From), pathname(To)]).
%% make_dir/{1,2}
make_dir(Dir) ->
make_dir_int({?DRV, [binary]}, Dir).
make_dir(Port, Dir) when is_port(Port) ->
make_dir_int(Port, Dir).
make_dir_int(Port, Dir) ->
drv_command(Port, [?FILE_MKDIR, pathname(Dir)]).
%% del_dir/{1,2}
del_dir(Dir) ->
del_dir_int({?DRV, [binary]}, Dir).
del_dir(Port, Dir) when is_port(Port) ->
del_dir_int(Port, Dir).
del_dir_int(Port, Dir) ->
drv_command(Port, [?FILE_RMDIR, pathname(Dir)]).
%% read_file_info/{1,2,3}
read_file_info(File) ->
read_file_info_int({?DRV, [binary]}, File, local).
read_file_info(Port, File) when is_port(Port) ->
read_file_info_int(Port, File, local);
read_file_info(File, Opts) ->
read_file_info_int({?DRV, [binary]}, File, plgv(time, Opts, local)).
read_file_info(Port, File, Opts) when is_port(Port) ->
read_file_info_int(Port, File, plgv(time, Opts, local)).
read_file_info_int(Port, File, TimeType) ->
try
case drv_command(Port, [?FILE_FSTAT, pathname(File)]) of
{ok, FI} -> {ok, FI#file_info{
ctime = from_seconds(FI#file_info.ctime, TimeType),
mtime = from_seconds(FI#file_info.mtime, TimeType),
atime = from_seconds(FI#file_info.atime, TimeType)
}};
Error -> Error
end
catch
error:_ -> {error, badarg}
end.
%% altname/{1,2}
altname(File) ->
altname_int({?DRV, [binary]}, File).
altname(Port, File) when is_port(Port) ->
altname_int(Port, File).
altname_int(Port, File) ->
drv_command(Port, [?FILE_ALTNAME, pathname(File)]).
%% write_file_info/{2,3,4}
write_file_info(File, Info) ->
write_file_info_int({?DRV, [binary]}, File, Info, local).
write_file_info(Port, File, Info) when is_port(Port) ->
write_file_info_int(Port, File, Info, local);
write_file_info(File, Info, Opts) ->
write_file_info_int({?DRV, [binary]}, File, Info, plgv(time, Opts, local)).
write_file_info(Port, File, Info, Opts) when is_port(Port) ->
write_file_info_int(Port, File, Info, plgv(time, Opts, local)).
write_file_info_int(Port, File,
#file_info{mode=Mode,
uid=Uid,
gid=Gid,
atime=Atime0,
mtime=Mtime0,
ctime=Ctime0},
TimeType) ->
% Atime and/or Mtime might be undefined
% - use localtime() for atime, if atime is undefined
% - use atime as mtime if mtime is undefined
% - use mtime as ctime if ctime is undefined
try
Atime = file_info_validate_atime(Atime0, TimeType),
Mtime = file_info_validate_mtime(Mtime0, Atime),
Ctime = file_info_validate_ctime(Ctime0, Mtime),
drv_command(Port, [?FILE_WRITE_INFO,
int_to_int32bytes(Mode),
int_to_int32bytes(Uid),
int_to_int32bytes(Gid),
int_to_int64bytes(to_seconds(Atime, TimeType)),
int_to_int64bytes(to_seconds(Mtime, TimeType)),
int_to_int64bytes(to_seconds(Ctime, TimeType)),
pathname(File)])
catch
error:_ -> {error, badarg}
end.
file_info_validate_atime(Atime, _) when Atime =/= undefined -> Atime;
file_info_validate_atime(undefined, local) -> erlang:localtime();
file_info_validate_atime(undefined, universal) -> erlang:universaltime();
file_info_validate_atime(undefined, posix) -> erlang:universaltime_to_posixtime(erlang:universaltime()).
file_info_validate_mtime(undefined, Atime) -> Atime;
file_info_validate_mtime(Mtime, _) -> Mtime.
file_info_validate_ctime(undefined, Mtime) -> Mtime;
file_info_validate_ctime(Ctime, _) -> Ctime.
%% make_link/{2,3}
make_link(Old, New) ->
make_link_int({?DRV, [binary]}, Old, New).
make_link(Port, Old, New) when is_port(Port) ->
make_link_int(Port, Old, New).
make_link_int(Port, Old, New) ->
drv_command(Port, [?FILE_LINK, pathname(Old), pathname(New)]).
%% make_symlink/{2,3}
make_symlink(Old, New) ->
make_symlink_int({?DRV, [binary]}, Old, New).
make_symlink(Port, Old, New) when is_port(Port) ->
make_symlink_int(Port, Old, New).
make_symlink_int(Port, Old, New) ->
drv_command(Port, [?FILE_SYMLINK, pathname(Old), pathname(New)]).
%% read_link/{2,3}
read_link(Link) ->
read_link_int({?DRV, [binary]}, Link).
read_link(Port, Link) when is_port(Port) ->
read_link_int(Port, Link).
read_link_int(Port, Link) ->
drv_command(Port, [?FILE_READLINK, pathname(Link)]).
%% read_link_info/{2,3}
read_link_info(Link) ->
read_link_info_int({?DRV, [binary]}, Link, local).
read_link_info(Port, Link) when is_port(Port) ->
read_link_info_int(Port, Link, local);
read_link_info(Link, Opts) ->
read_link_info_int({?DRV, [binary]}, Link, plgv(time, Opts, local)).
read_link_info(Port, Link, Opts) when is_port(Port) ->
read_link_info_int(Port, Link, plgv(time, Opts, local)).
read_link_info_int(Port, Link, TimeType) ->
try
case drv_command(Port, [?FILE_LSTAT, pathname(Link)]) of
{ok, FI} -> {ok, FI#file_info{
ctime = from_seconds(FI#file_info.ctime, TimeType),
mtime = from_seconds(FI#file_info.mtime, TimeType),
atime = from_seconds(FI#file_info.atime, TimeType)
}};
Error -> Error
end
catch
error:_ -> {error, badarg}
end.
%% list_dir/{1,2}
list_dir(Dir) ->
list_dir_int({?DRV, [binary]}, Dir).
list_dir(Port, Dir) when is_port(Port) ->
list_dir_int(Port, Dir).
list_dir_int(Port, Dir) ->
drv_command(Port, [?FILE_READDIR, pathname(Dir)], []).
%%%-----------------------------------------------------------------
%%% Functions to communicate with the driver
%% Opens a driver port and converts any problems into {error, emfile}.
%% Returns {ok, Port} when successful.
drv_open(Driver, Portopts) ->
try erlang:open_port({spawn, Driver}, Portopts) of
Port ->
{ok, Port}
catch
error:Reason ->
{error, Reason}
end.
%% Closes a port in a safe way. Returns ok.
drv_close(Port) ->
Save = erlang:dt_spread_tag(false),
try
try erlang:port_close(Port) catch error:_ -> ok end,
receive %% Ugly workaround in case the caller==owner traps exits
{'EXIT', Port, _Reason} ->
ok
after 0 ->
ok
end
after
erlang:dt_restore_tag(Save)
end.
%% Issues a command to a port and gets the response.
%% If Port is {Driver, Portopts} a port is first opened and
%% then closed after the result has been received.
%% Returns {ok, Result} or {error, Reason}.
drv_command(Port, Command) ->
drv_command(Port, Command, undefined).
drv_command(Port, Command, R) when is_binary(Command) ->
drv_command(Port, Command, true, R);
drv_command(Port, Command, R) ->
try erlang:iolist_size(Command) of
_ ->
drv_command(Port, Command, true, R)
catch
error:Reason ->
{error, Reason}
end.
drv_command(Port, Command, Validated, R) when is_port(Port) ->
Save = erlang:dt_spread_tag(false),
try erlang:port_command(Port, erlang:dt_append_vm_tag_data(Command)) of
true ->
drv_get_response(Port, R)
catch
%% If the Command is valid, knowing that the port is a port,
%% a badarg error must mean it is a dead port, that is:
%% a currently invalid filehandle, -> einval, not badarg.
error:badarg when Validated ->
{error, einval};
error:badarg ->
try erlang:iolist_size(Command) of
_ -> % Valid
{error, einval}
catch
error:_ ->
{error, badarg}
end;
error:Reason ->
{error, Reason}
after
erlang:dt_restore_tag(Save)
end;
drv_command({Driver, Portopts}, Command, Validated, R) ->
case drv_open(Driver, Portopts) of
{ok, Port} ->
Result = drv_command(Port, Command, Validated, R),
drv_close(Port),
Result;
Error ->
Error
end.
drv_command_nt(Port, Command, R) when is_port(Port) ->
Save = erlang:dt_spread_tag(false),
try erlang:port_command(Port, Command) of
true ->
drv_get_response(Port, R)
catch
error:badarg ->
try erlang:iolist_size(Command) of
_ -> % Valid
{error, einval}
catch
error:_ ->
{error, badarg}
end;
error:Reason ->
{error, Reason}
after
erlang:dt_restore_tag(Save)
end.
%% Receives the response from a driver port.
%% Returns: {ok, ListOrBinary}|{error, Reason}
drv_get_response(Port, R) when is_list(R) ->
case drv_get_response(Port) of
ok ->
{ok, R};
{ok, Name} ->
drv_get_response(Port, [Name|R]);
{append, Names} ->
drv_get_response(Port, append(Names, R));
Error ->
Error
end;
drv_get_response(Port, _) ->
drv_get_response(Port).
drv_get_response(Port) ->
erlang:bump_reductions(100),
receive
{Port, {data, [Response|Rest] = Data}} ->
try translate_response(Response, Rest)
catch
error:Reason ->
{error, {bad_response_from_port, Data,
{Reason, erlang:get_stacktrace()}}}
end;
{'EXIT', Port, Reason} ->
{error, {port_died, Reason}}
end.
%%%-----------------------------------------------------------------
%%% Utility functions.
append([I | Is], R) when is_list(R) -> append(Is, [I | R]);
append([], R) -> R.
%% Converts a list of mode atoms into a mode word for the driver.
%% Returns {Mode, Portopts, Setopts} where Portopts is a list of
%% options for erlang:open_port/2 and Setopts is a list of
%% setopt commands to send to the port, or error Reason upon failure.
open_mode(List) when is_list(List) ->
case open_mode(List, 0, [], []) of
{Mode, Portopts, Setopts} when Mode band
(?EFILE_MODE_READ bor ?EFILE_MODE_WRITE)
=:= 0 ->
{Mode bor ?EFILE_MODE_READ, Portopts, Setopts};
Other ->
Other
end.
open_mode([raw|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode, Portopts, Setopts);
open_mode([read|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode bor ?EFILE_MODE_READ, Portopts, Setopts);
open_mode([write|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode bor ?EFILE_MODE_WRITE, Portopts, Setopts);
open_mode([binary|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode, [binary | Portopts], Setopts);
open_mode([compressed|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode bor ?EFILE_COMPRESSED, Portopts, Setopts);
open_mode([append|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode bor ?EFILE_MODE_APPEND bor ?EFILE_MODE_WRITE,
Portopts, Setopts);
open_mode([exclusive|Rest], Mode, Portopts, Setopts) ->
open_mode(Rest, Mode bor ?EFILE_MODE_EXCL, Portopts, Setopts);
open_mode([delayed_write|Rest], Mode, Portopts, Setopts) ->
open_mode([{delayed_write, 64*1024, 2000}|Rest], Mode,
Portopts, Setopts);
open_mode([{delayed_write, Size, Delay}|Rest], Mode, Portopts, Setopts)
when is_integer(Size), 0 =< Size, is_integer(Delay), 0 =< Delay ->
if
Size < ?LARGEFILESIZE, Delay < 1 bsl 64 ->
open_mode(Rest, Mode, Portopts,
[<<?FILE_SETOPT, ?FILE_OPT_DELAYED_WRITE,
Size:64, Delay:64>>
| Setopts]);
true ->
einval
end;
open_mode([read_ahead|Rest], Mode, Portopts, Setopts) ->
open_mode([{read_ahead, 64*1024}|Rest], Mode, Portopts, Setopts);
open_mode([{read_ahead, Size}|Rest], Mode, Portopts, Setopts)
when is_integer(Size), 0 =< Size ->
if
Size < ?LARGEFILESIZE ->
open_mode(Rest, Mode, Portopts,
[<<?FILE_SETOPT, ?FILE_OPT_READ_AHEAD,
Size:64>> | Setopts]);
true ->
einval
end;
open_mode([], Mode, Portopts, Setopts) ->
{Mode, reverse(Portopts), reverse(Setopts)};
open_mode(_, _Mode, _Portopts, _Setopts) ->
badarg.
%% Converts a position tuple {bof, X} | {cur, X} | {eof, X} into
%% {Offset, OriginCode} for the driver.
%% Returns badarg upon failure.
lseek_position(Pos)
when is_integer(Pos) ->
lseek_position({bof, Pos});
lseek_position(bof) ->
lseek_position({bof, 0});
lseek_position(cur) ->
lseek_position({cur, 0});
lseek_position(eof) ->
lseek_position({eof, 0});
lseek_position({bof, Offset})
when is_integer(Offset) ->
{Offset, ?EFILE_SEEK_SET};
lseek_position({cur, Offset})
when is_integer(Offset) ->
{Offset, ?EFILE_SEEK_CUR};
lseek_position({eof, Offset})
when is_integer(Offset) ->
{Offset, ?EFILE_SEEK_END};
lseek_position(_) ->
badarg.
%% Translates the response from the driver into
%% {ok, Result} or {error, Reason}.
translate_response(?FILE_RESP_OK, []) ->
ok;
translate_response(?FILE_RESP_ERROR, List) when is_list(List) ->
{error, list_to_atom(List)};
translate_response(?FILE_RESP_NUMBER, List) ->
{N, []} = get_uint64(List),
{ok, N};
translate_response(?FILE_RESP_DATA, List) ->
{_N, _Data} = ND = get_uint64(List),
{ok, ND};
translate_response(?FILE_RESP_INFO, List) when is_list(List) ->
{ok, transform_info(List)};
translate_response(?FILE_RESP_NUMERR, L0) ->
{N, L1} = get_uint64(L0),
{error, {N, list_to_atom(L1)}};
translate_response(?FILE_RESP_LDATA, List) ->
{ok, transform_ldata(List)};
translate_response(?FILE_RESP_N2DATA,
<<Offset:64, 0:64, Size:64>>) ->
{ok, {Size, Offset, eof}};
translate_response(?FILE_RESP_N2DATA,
[<<Offset:64, 0:64, Size:64>> | <<>>]) ->
{ok, {Size, Offset, eof}};
translate_response(?FILE_RESP_N2DATA = X,
[<<_:64, 0:64, _:64>> | _] = Data) ->
{error, {bad_response_from_port, [X | Data]}};
translate_response(?FILE_RESP_N2DATA = X,
[<<_:64, _:64, _:64>> | <<>>] = Data) ->
{error, {bad_response_from_port, [X | Data]}};
translate_response(?FILE_RESP_N2DATA,
[<<Offset:64, _ReadSize:64, Size:64>> | D]) ->
{ok, {Size, Offset, D}};
translate_response(?FILE_RESP_N2DATA = X, L0) when is_list(L0) ->
{Offset, L1} = get_uint64(L0),
{ReadSize, L2} = get_uint64(L1),
{Size, L3} = get_uint64(L2),
case {ReadSize, L3} of
{0, []} ->
{ok, {Size, Offset, eof}};
{0, _} ->
{error, {bad_response_from_port, [X | L0]}};
{_, []} ->
{error, {bad_response_from_port, [X | L0]}};
_ ->
{ok, {Size, Offset, L3}}
end;
translate_response(?FILE_RESP_EOF, []) ->
eof;
translate_response(?FILE_RESP_FNAME, []) ->
ok;
translate_response(?FILE_RESP_FNAME, Data) when is_binary(Data) ->
{ok, prim_file:internal_native2name(Data)};
translate_response(?FILE_RESP_FNAME, Data) ->
{ok, Data};
translate_response(?FILE_RESP_LFNAME, []) ->
ok;
translate_response(?FILE_RESP_LFNAME, Data) when is_binary(Data) ->
{append, transform_lfname(Data)};
translate_response(?FILE_RESP_LFNAME, Data) ->
{append, transform_lfname(Data)};
translate_response(?FILE_RESP_ALL_DATA, Data) ->
{ok, Data};
translate_response(X, Data) ->
{error, {bad_response_from_port, [X | Data]}}.
transform_info([
Hsize1, Hsize2, Hsize3, Hsize4,
Lsize1, Lsize2, Lsize3, Lsize4,
Type1, Type2, Type3, Type4,
Atime1, Atime2, Atime3, Atime4, Atime5, Atime6, Atime7, Atime8,
Mtime1, Mtime2, Mtime3, Mtime4, Mtime5, Mtime6, Mtime7, Mtime8,
Ctime1, Ctime2, Ctime3, Ctime4, Ctime5, Ctime6, Ctime7, Ctime8,
Mode1, Mode2, Mode3, Mode4,
Links1, Links2, Links3, Links4,
Major1, Major2, Major3, Major4,
Minor1, Minor2, Minor3, Minor4,
Inode1, Inode2, Inode3, Inode4,
Uid1, Uid2, Uid3, Uid4,
Gid1, Gid2, Gid3, Gid4,
Access1,Access2,Access3,Access4]) ->
#file_info {
size = uint32(Hsize1,Hsize2,Hsize3,Hsize4)*16#100000000 + uint32(Lsize1,Lsize2,Lsize3,Lsize4),
type = file_type(uint32(Type1,Type2,Type3,Type4)),
access = file_access(uint32(Access1,Access2,Access3,Access4)),
atime = sint64(Atime1, Atime2, Atime3, Atime4, Atime5, Atime6, Atime7, Atime8),
mtime = sint64(Mtime1, Mtime2, Mtime3, Mtime4, Mtime5, Mtime6, Mtime7, Mtime8),
ctime = sint64(Ctime1, Ctime2, Ctime3, Ctime4, Ctime5, Ctime6, Ctime7, Ctime8),
mode = uint32(Mode1,Mode2,Mode3,Mode4),
links = uint32(Links1,Links2,Links3,Links4),
major_device = uint32(Major1,Major2,Major3,Major4),
minor_device = uint32(Minor1,Minor2,Minor3,Minor4),
inode = uint32(Inode1,Inode2,Inode3,Inode4),
uid = uint32(Uid1,Uid2,Uid3,Uid4),
gid = uint32(Gid1,Gid2,Gid3,Gid4)
}.
file_type(1) -> device;
file_type(2) -> directory;
file_type(3) -> regular;
file_type(4) -> symlink;
file_type(_) -> other.
file_access(0) -> none;
file_access(1) -> write;
file_access(2) -> read;
file_access(3) -> read_write.
int_to_int32bytes(Int) when is_integer(Int) ->
<<Int:32>>;
int_to_int32bytes(undefined) ->
<<-1:32>>.
int_to_int64bytes(Int) when is_integer(Int) ->
<<Int:64/signed>>.
sint64(I1,I2,I3,I4,I5,I6,I7,I8) when I1 > 127 ->
((I1 bsl 56) bor (I2 bsl 48) bor (I3 bsl 40) bor (I4 bsl 32) bor
(I5 bsl 24) bor (I6 bsl 16) bor (I7 bsl 8) bor I8) - (1 bsl 64);
sint64(I1,I2,I3,I4,I5,I6,I7,I8) ->
((I1 bsl 56) bor (I2 bsl 48) bor (I3 bsl 40) bor (I4 bsl 32) bor
(I5 bsl 24) bor (I6 bsl 16) bor (I7 bsl 8) bor I8).
uint32(X1,X2,X3,X4) ->
(X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4.
get_uint64(L0) ->
{X1, L1} = get_uint32(L0),
{X2, L2} = get_uint32(L1),
{(X1 bsl 32) bor X2, L2}.
get_uint32([X1,X2,X3,X4|List]) ->
{(((((X1 bsl 8) bor X2) bsl 8) bor X3) bsl 8) bor X4, List}.
%% Binary mode
transform_ldata(<<0:32, 0:32>>) ->
[];
transform_ldata([<<0:32, N:32, Sizes/binary>> | Datas]) ->
transform_ldata(N, Sizes, Datas, []);
%% List mode
transform_ldata([_,_,_,_,_,_,_,_|_] = L0) ->
{0, L1} = get_uint32(L0),
{N, L2} = get_uint32(L1),
transform_ldata(N, L2, []).
%% List mode
transform_ldata(0, List, Sizes) ->
transform_ldata(0, List, reverse(Sizes), []);
transform_ldata(N, L0, Sizes) ->
{Size, L1} = get_uint64(L0),
transform_ldata(N-1, L1, [Size | Sizes]).
%% Binary mode
transform_ldata(1, <<0:64>>, <<>>, R) ->
reverse(R, [eof]);
transform_ldata(1, <<Size:64>>, Data, R)
when byte_size(Data) =:= Size ->
reverse(R, [Data]);
transform_ldata(N, <<0:64, Sizes/binary>>, [<<>> | Datas], R) ->
transform_ldata(N-1, Sizes, Datas, [eof | R]);
transform_ldata(N, <<Size:64, Sizes/binary>>, [Data | Datas], R)
when byte_size(Data) =:= Size ->
transform_ldata(N-1, Sizes, Datas, [Data | R]);
%% List mode
transform_ldata(0, [], [], R) ->
reverse(R);
transform_ldata(0, List, [0 | Sizes], R) ->
transform_ldata(0, List, Sizes, [eof | R]);
transform_ldata(0, List, [Size | Sizes], R) ->
{Front, Rear} = lists_split(List, Size),
transform_ldata(0, Rear, Sizes, [Front | R]).
transform_lfname(<<>>) -> [];
transform_lfname(<<L:16, Name:L/binary, Names/binary>>) ->
[ prim_file:internal_native2name(Name) | transform_lfname(Names)];
transform_lfname([]) -> [];
transform_lfname([L1,L2|Names]) ->
L = (L1 bsl 8) bor L2,
{Name, Rest} = lists_split(Names, L),
[Name | transform_lfname(Rest)].
lists_split(List, 0) when is_list(List) ->
{[], List};
lists_split(List, N) when is_list(List), is_integer(N), N < 0 ->
erlang:error(badarg, [List, N]);
lists_split(List, N) when is_list(List), is_integer(N) ->
case lists_split(List, N, []) of
premature_end_of_list ->
erlang:error(badarg, [List, N]);
Result ->
Result
end.
lists_split(List, 0, Rev) ->
{reverse(Rev), List};
lists_split([], _, _) ->
premature_end_of_list;
lists_split([Hd | Tl], N, Rev) ->
lists_split(Tl, N-1, [Hd | Rev]).
%% We KNOW that lists:reverse/2 is a BIF.
reverse(X) -> lists:reverse(X, []).
reverse(L, T) -> lists:reverse(L, T).
% Will add zero termination too
% The 'EXIT' tuple from a bad argument will eventually generate an error
% in list_to_binary, which is caught and generates the {error,badarg} return
pathname(File) ->
(catch prim_file:internal_name2native(File)).
%% proplist:get_value/3
plgv(K, [{K, V}|_], _) -> V;
plgv(K, [_|KVs], D) -> plgv(K, KVs, D);
plgv(_, [], D) -> D.
%%
%% We don't actually want this here
%% We want to use posix time in all prim but erl_prim_loader makes that tricky
%% It is probably needed to redo the whole erl_prim_loader
from_seconds(Seconds, posix) when is_integer(Seconds) ->
Seconds;
from_seconds(Seconds, universal) when is_integer(Seconds) ->
erlang:posixtime_to_universaltime(Seconds);
from_seconds(Seconds, local) when is_integer(Seconds) ->
erlang:universaltime_to_localtime(erlang:posixtime_to_universaltime(Seconds)).
to_seconds(Seconds, posix) when is_integer(Seconds) ->
Seconds;
to_seconds({_,_} = Datetime, universal) ->
erlang:universaltime_to_posixtime(Datetime);
to_seconds({_,_} = Datetime, local) ->
erlang:universaltime_to_posixtime(erlang:localtime_to_universaltime(Datetime)).