diff options
author | John Högberg <[email protected]> | 2017-11-03 11:49:27 +0100 |
---|---|---|
committer | John Högberg <[email protected]> | 2017-11-30 15:44:33 +0100 |
commit | ebbd26eeea4115c946d1254d94acd50f150b4455 (patch) | |
tree | ab2a8ba1023aa0684695dc36ff5567ae9e3f59c4 /erts/preloaded | |
parent | 6cb62e44eba1db8d1917ebb0db84298e91582c4e (diff) | |
download | otp-ebbd26eeea4115c946d1254d94acd50f150b4455.tar.gz otp-ebbd26eeea4115c946d1254d94acd50f150b4455.tar.bz2 otp-ebbd26eeea4115c946d1254d94acd50f150b4455.zip |
Reimplement efile_drv as a dirty NIF
This improves the latency of file operations as dirty schedulers
are a bit more eager to run jobs than async threads, and use a
single global queue rather than per-thread queues, eliminating the
risk of a job stalling behind a long-running job on the same thread
while other async threads sit idle.
There's no such thing as a free lunch though; the lowered latency
comes at the cost of increased busy-waiting which may have an
adverse effect on some applications. This behavior can be tweaked
with the +sbwt flag, but unfortunately it affects all types of
schedulers and not just dirty ones. We plan to add type-specific
flags at a later stage.
sendfile has been moved to inet_drv to lessen the effect of a nasty
race; the cooperation between inet_drv and efile has never been
airtight and the socket dying at the wrong time (Regardless of
reason) could result in fd aliasing. Moving it to the inet driver
makes it impossible to trigger this by closing the socket in the
middle of a sendfile operation, while still allowing it to be
aborted -- something that can't be done if it stays in the file
driver.
The race still occurs if the controlling process dies in the short
window between dispatching the sendfile operation and the dup(2)
call in the driver, but it's much less likely to happen now.
A proper fix is in the works.
--
Notable functional differences:
* The use_threads option for file:sendfile/5 no longer has any
effect.
* The file-specific DTrace probes have been removed. The same
effect can be achieved with normal tracing together with the
nif__entry/nif__return probes to track scheduling.
--
OTP-14256
Diffstat (limited to 'erts/preloaded')
-rw-r--r-- | erts/preloaded/ebin/erl_prim_loader.beam | bin | 54704 -> 54268 bytes | |||
-rw-r--r-- | erts/preloaded/ebin/init.beam | bin | 50348 -> 50472 bytes | |||
-rw-r--r-- | erts/preloaded/ebin/prim_file.beam | bin | 43956 -> 27428 bytes | |||
-rw-r--r-- | erts/preloaded/ebin/prim_inet.beam | bin | 75988 -> 77928 bytes | |||
-rw-r--r-- | erts/preloaded/src/Makefile | 6 | ||||
-rw-r--r-- | erts/preloaded/src/erl_prim_loader.erl | 20 | ||||
-rw-r--r-- | erts/preloaded/src/init.erl | 5 | ||||
-rw-r--r-- | erts/preloaded/src/prim_file.erl | 1991 | ||||
-rw-r--r-- | erts/preloaded/src/prim_inet.erl | 51 |
9 files changed, 714 insertions, 1359 deletions
diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex 198032c18d..2d86dd3843 100644 --- a/erts/preloaded/ebin/erl_prim_loader.beam +++ b/erts/preloaded/ebin/erl_prim_loader.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex fb4fc67148..a2dd41b435 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex eb326ac4b6..902b0945c6 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 32b104ae95..a42f45c180 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index bb8d272793..a2872082f2 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -36,6 +36,7 @@ include $(ERL_TOP)/lib/kernel/vsn.mk PRE_LOADED_ERL_MODULES = \ erl_prim_loader \ init \ + prim_buffer \ prim_file \ prim_inet \ zlib \ @@ -46,8 +47,7 @@ PRE_LOADED_ERL_MODULES = \ erts_internal \ erl_tracer \ erts_literal_area_collector \ - erts_dirty_process_code_checker \ - prim_buffer + erts_dirty_process_code_checker PRE_LOADED_BEAM_MODULES = \ prim_eval @@ -117,7 +117,7 @@ prim_eval.beam: prim_eval.S prim_eval.abstr # Include dependencies -- list below added by PaN $(EBIN)/erl_prim_loader.beam: $(KERNEL_SRC)/inet_boot.hrl $(KERNEL_INCLUDE)/file.hrl -$(EBIN)/prim_file.beam: $(KERNEL_INCLUDE)/file.hrl +$(EBIN)/prim_file.beam: $(KERNEL_SRC)/file_int.hrl $(KERNEL_INCLUDE)/file.hrl $(EBIN)/prim_inet.beam: $(KERNEL_SRC)/inet_int.hrl $(KERNEL_INCLUDE)/inet_sctp.hrl $(EBIN)/prim_zip.beam: zip_internal.hrl $(KERNEL_INCLUDE)/file.hrl $(STDLIB_INCLUDE)/zip.hrl $(EBIN)/init.erl: $(KERNEL_INCLUDE)/file.hrl diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl index 1d09aeded9..d16fe5a0bc 100644 --- a/erts/preloaded/src/erl_prim_loader.erl +++ b/erts/preloaded/src/erl_prim_loader.erl @@ -151,9 +151,8 @@ start_inet(Parent) -> loop(State, Parent, []). start_efile(Parent) -> - {ok, Port} = prim_file:start(), %% Check that we started in a valid directory. - case prim_file:get_cwd(Port) of + case prim_file:get_cwd() of {error, _} -> %% At this point in the startup, we have no error_logger at all. Report = "Invalid current directory or invalid filename " @@ -165,7 +164,7 @@ start_efile(Parent) -> end, PS = prim_init(), State = #state {loader = efile, - data = Port, + data = noport, timeout = ?EFILE_IDLE_TIMEOUT, prim_state = PS}, loop(State, Parent, []). @@ -401,12 +400,12 @@ handle_get_cwd(State = #state{loader = inet}, Drive) -> ?SAFE2(inet_get_cwd(State, Drive), State). handle_stop(State = #state{loader = efile}) -> - efile_stop_port(State); + State; handle_stop(State = #state{loader = inet}) -> inet_stop_port(State). -handle_exit(State = #state{loader = efile}, Who, Reason) -> - efile_exit_port(State, Who, Reason); +handle_exit(State = #state{loader = efile}, _Who, _Reason) -> + State; handle_exit(State = #state{loader = inet}, Who, Reason) -> inet_exit_port(State, Who, Reason). @@ -475,15 +474,6 @@ efile_get_cwd(#state{prim_state = PS} = State, Drive) -> {Res, PS2} = prim_get_cwd(PS, Drive), {Res, State#state{prim_state = PS2}}. -efile_stop_port(#state{data=Port}=State) -> - prim_file:close(Port), - State#state{data=noport}. - -efile_exit_port(State, Port, Reason) when State#state.data =:= Port -> - exit({port_died,Reason}); -efile_exit_port(State, _Port, _Reason) -> - State. - efile_timeout_handler(State, _Parent) -> prim_purge_cache(), State. diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index 34a9f6b8b9..679a2241d2 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -200,10 +200,11 @@ boot(BootArgs) -> register(init, self()), process_flag(trap_exit, true), - %% Load the zlib nif + %% Load the static nifs zlib:on_load(), - %% Load the tracer nif erl_tracer:on_load(), + prim_buffer:on_load(), + prim_file:on_load(), {Start0,Flags,Args} = parse_boot_args(BootArgs), %% We don't get to profile parsing of BootArgs diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index ab5359ebbc..35042a7c72 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2017. 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. @@ -19,147 +19,45 @@ %% -module(prim_file). -%% Interface module to the file driver. +-export([on_load/0]). +-export([open/2, close/1, + sync/1, datasync/1, truncate/1, advise/4, allocate/3, + read_line/1, read/2, write/2, position/2, + pread/2, pread/3, pwrite/2, pwrite/3]). +%% OTP internal. +-export([ipread_s32bu_p32bu/3, sendfile/8, altname/1, get_handle/1]). -%%% Interface towards a single file's contents. Uses ?FD_DRV. +-export([read_file/1, write_file/2]). -%% 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/8, allocate/3]). +-export([read_link/1, read_link_all/1, + read_link_info/1, read_link_info/2, + read_file_info/1, read_file_info/2, + write_file_info/2, write_file_info/3]). -%% Specialized file operations --export([open/1, open/3]). --export([read_file/1, read_file/2, write_file/2]). --export([ipread_s32bu_p32bu/3]). +-export([list_dir/1, list_dir_all/1]). +-export([get_cwd/0, get_cwd/1, set_cwd/1, + delete/1, rename/2, + make_dir/1, del_dir/1, + make_link/2, make_symlink/2]). - -%%% 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_all/1, read_link_all/2, - read_link_info/1, read_link_info/2, read_link_info/3, - list_dir/1, list_dir/2, list_dir_all/1, list_dir_all/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(MIN_READLINE_SIZE, 256). -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). --define(FILE_ALLOCATE, 33). - -%% 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). -%% Note: bit 5 (32) is used internally for VxWorks --define(EFILE_MODE_SYNC, 64). - -%% Use this mask to get just the mode bits to be passed to the driver. --define(EFILE_MODE_MASK, 127). - -%% 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). - -%% Sendfile flags --define(EFILE_SENDFILE_USE_THREADS, 1). +-export([copy/3]). +-include("file_int.hrl"). + +-type prim_file_ref() :: term(). %%% BIFs -export([internal_name2native/1, internal_native2name/1, internal_normalize_utf8/1, - is_translatable/1]). + is_translatable/1]). -type prim_file_name() :: string() | unicode:unicode_binary(). -type prim_file_name_error() :: 'error' | 'ignore' | 'warning'. @@ -185,1316 +83,733 @@ internal_normalize_utf8(_) -> is_translatable(_) -> 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) -> - drv_open(?FD_DRV, [binary|Portopts]); -open(_) -> - {error, badarg}. +%% 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). -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. +on_load() -> + ok = erlang:load_nif(atom_to_list(?MODULE), 0). -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 +open(Name, Modes) -> + %% The try/catch pattern seen here is used throughout the file to adhere to + %% the public file interface, which has leaked through for ages because of + %% "raw files." + try open_nif(encode_path(Name), Modes) of + {ok, Ref} -> {ok, make_fd(Ref, Modes)}; + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} end. +make_fd(FRef, Modes) -> + #file_descriptor{module = ?MODULE, data = build_fd_data(FRef, Modes) }. - -%% 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} +close(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + close_nif(FRef) + catch + error:badarg -> {error, badarg} end. -%% Returns {error, Reason} | ok. -allocate(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offset, Length) -> - Cmd = <<?FILE_ALLOCATE, Offset:64/signed, Length:64/signed>>, - drv_command(Port, Cmd). - -%% 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 +read(Fd, Size) -> + try + #{ handle := FRef, + r_ahead_size := RASz, + r_buffer := RBuf } = get_fd_data(Fd), + read_1(FRef, RBuf, prim_buffer:size(RBuf), RASz, Size) + catch + error:badarg -> {error, badarg} 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} +-spec read_1(FRef, RBuf, RBufSz, RASz, RSz) -> Result when + FRef :: prim_file_ref(), + RBuf :: term(), + RBufSz :: non_neg_integer(), + RASz :: non_neg_integer(), + RSz :: non_neg_integer(), + Result :: eof | {ok, binary()} | {error, Reason :: atom()}. +read_1(_FRef, RBuf, RBufSz, _RASz, RSz) when RBufSz >= RSz -> + {ok, prim_buffer:read(RBuf, RSz)}; +read_1(FRef, RBuf, RBufSz, RASz, RSz) when RBufSz > 0 -> + Buffered = prim_buffer:read(RBuf, RBufSz), + case read_1(FRef, RBuf, 0, RASz, RSz - RBufSz) of + {ok, Data} -> + {ok, <<Buffered/binary, Data/binary>>}; + eof -> + {ok, Buffered}; + {error, Reason} -> + {error, Reason} end; -pwrite_int(_, [_|_], _N, _Spec, _Data) -> - {error, badarg}. +read_1(FRef, RBuf, RBufSz, RASz, RSz) when RBufSz =:= 0 -> + case read_nif(FRef, RASz + RSz) of + {ok, Data} when byte_size(Data) > RSz -> + {First, Rest} = split_binary(Data, RSz), + prim_buffer:write(RBuf, [Rest]), + {ok, First}; + {ok, Data} when byte_size(Data) =< RSz -> + {ok, Data}; + eof -> + eof; + {error, Reason} -> + {error, Reason} + end. -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) +read_line(Fd) -> + try + #{ handle := FRef, + r_ahead_size := RASz, + r_buffer := RBuf } = get_fd_data(Fd), + SearchResult = prim_buffer:find_byte_index(RBuf, $\n), + LineSize = max(?MIN_READLINE_SIZE, RASz), + read_line_1(FRef, RBuf, SearchResult, LineSize) catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} 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} +-spec read_line_1(FRef, RBuf, SearchResult, LineSize) -> Result when + FRef :: prim_file_ref(), + RBuf :: term(), + SearchResult :: not_found | {ok, non_neg_integer()}, + LineSize :: non_neg_integer(), + Result :: eof | {ok, binary()} | {error, Reason :: atom()}. +read_line_1(FRef, RBuf, not_found, LineSize) -> + case read_nif(FRef, LineSize) of + {ok, Data} -> + prim_buffer:write(RBuf, [Data]), + SearchResult = prim_buffer:find_byte_index(RBuf, $\n), + read_line_1(FRef, RBuf, SearchResult, LineSize); + eof -> + case prim_buffer:size(RBuf) of + Size when Size > 0 -> {ok, prim_buffer:read(RBuf, Size)}; + Size when Size =:= 0 -> eof + end; + {error, Reason} -> + {error, Reason} end; -pwrite(#file_descriptor{module = ?MODULE}, _, _) -> - {error, badarg}. - +read_line_1(_FRef, RBuf, {ok, LFIndex}, _LineSize) -> + %% Translate CRLF into just LF, completely ignoring which encoding is used. + CRIndex = (LFIndex - 1), + case prim_buffer:read(RBuf, LFIndex + 1) of + <<Line:CRIndex/binary, "\r\n">> -> {ok, <<Line/binary, "\n">>}; + Line -> {ok, Line} + end. -%% 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 +write(Fd, IOData) -> + try + #{ handle := FRef } = get_fd_data(Fd), + reset_write_position(Fd), + write_1(FRef, erlang:iolist_to_iovec(IOData)) + catch + error:badarg -> {error, badarg} 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} +write_1(FRef, IOVec) -> + case write_nif(FRef, IOVec) of + {continue, Remainder} -> + write_1(FRef, Remainder); + ok -> + ok; + {error, Reason} -> + {error, Reason} 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} +truncate(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + reset_write_position(Fd), + truncate_nif(FRef) + catch + error:badarg -> {error, badarg} end. -%% Returns {error, Reason} | 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}. +advise(Fd, Offset, Length, Advise) -> + try + #{ handle := FRef } = get_fd_data(Fd), + advise_nif(FRef, Offset, Length, Advise) + catch + error:badarg -> {error, badarg} + end. +allocate(Fd, Offset, Length) -> + try + #{ handle := FRef } = get_fd_data(Fd), + allocate_nif(FRef, Offset, Length) + catch + error:badarg -> {error, badarg} + end. +sync(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + sync_nif(FRef, 0) + catch + error:badarg -> {error, badarg} + end. -%% 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}. +datasync(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + sync_nif(FRef, 1) + catch + error:badarg -> {error, badarg} + end. -%% 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 +position(Fd, {cur, Offset}) -> + try + %% Adjust our current position according to how much we've read ahead. + #{ r_buffer := RBuf } = get_fd_data(Fd), + position_1(Fd, cur, Offset - prim_buffer:size(RBuf)) + catch + error:badarg -> {error, badarg} 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 +position(Fd, {Mark, Offset}) -> + try + position_1(Fd, Mark, Offset) + catch + error:badarg -> {error, badarg} end; -write_file(_, _) -> - {error, badarg}. +position(Fd, cur) -> position(Fd, {cur, 0}); +position(Fd, bof) -> position(Fd, {bof, 0}); +position(Fd, eof) -> position(Fd, {eof, 0}); +position(Fd, Offset) -> position(Fd, {bof, Offset}). +position_1(Fd, Mark, Offset) -> + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + seek_nif(FRef, Mark, Offset). -%% Returns {error, Reason} | {ok, BytesCopied} -%sendfile(_,_,_,_,_,_,_,_,_,_) -> -% {error, enotsup}; -sendfile(#file_descriptor{module = ?MODULE, data = {Port, _}}, - Dest, Offset, Bytes, _ChunkSize, Headers, Trailers, - Flags) -> - 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), - IntFlags = translate_sendfile_flags(Flags), - try drv_command(Port, [<<?FILE_SENDFILE, DestFD:32, - IntFlags: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} +pread(Fd, Offset, Size) -> + try + #{ handle := FRef } = get_fd_data(Fd), + pread_nif(FRef, Offset, Size) + catch + error:badarg -> {error, badarg} end. -translate_sendfile_flags([{use_threads,true}|T]) -> - ?EFILE_SENDFILE_USE_THREADS bor translate_sendfile_flags(T); -translate_sendfile_flags([_|T]) -> - translate_sendfile_flags(T); -translate_sendfile_flags([]) -> - 0. - - -%%%----------------------------------------------------------------- -%%% 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() -> - drv_open(?DRV, [binary]). - -stop(Port) when is_port(Port) -> - try erlang:port_close(Port) of - _ -> - ok +pread(Fd, LocNums) -> + try + #{ handle := FRef } = get_fd_data(Fd), + pread_list(FRef, LocNums, []) catch - _:_ -> - ok + error:badarg -> {error, badarg} end. +-spec pread_list(FRef, LocNums, ResultList) -> Result when + FRef :: prim_file_ref(), + LocNums :: list({Offset :: non_neg_integer(), + Size :: non_neg_integer()}), + ResultList :: list(eof | binary()), + Result :: {ok, ResultList} | {error, Reason :: atom()}. +pread_list(_FRef, [], ResultList) -> + {ok, reverse_list(ResultList)}; +pread_list(FRef, [{Offset, Size} | Rest], ResultList) -> + case pread_nif(FRef, Offset, Size) of + {ok, Data} -> + pread_list(FRef, Rest, [Data | ResultList]); + eof -> + pread_list(FRef, Rest, [eof | ResultList]); + {error, Reason} -> + {error, Reason} + end. +pwrite(Fd, Offset, IOData) -> + try + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + pwrite_plain(FRef, Offset, erlang:iolist_to_iovec(IOData)) + catch + error:badarg -> {error, badarg} + end. +pwrite_plain(FRef, Offset, IOVec) -> + case pwrite_nif(FRef, Offset, IOVec) of + {continue, BytesWritten, Remainder} -> + pwrite_plain(FRef, Offset + BytesWritten, Remainder); + ok -> + ok; + {error, Reason} -> + {error, Reason} + 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>>, - fun handle_fname_response/1). - - - -%% set_cwd/{1,2} +pwrite(Fd, LocBytes) -> + try + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + pwrite_list(FRef, LocBytes, 0) + catch + error:badarg -> {error, badarg} + end. -set_cwd(Dir) -> - set_cwd_int({?DRV, [binary]}, Dir). +-spec pwrite_list(FRef, LocBytes, Successes) -> Result when + FRef :: prim_file_ref(), + LocBytes :: list({Offset :: non_neg_integer(), + IOData :: iodata()}), + Successes :: non_neg_integer(), + Result :: ok | {error, {Successes, Reason :: atom()}}. +pwrite_list(_FRef, [], _Successes) -> + ok; +pwrite_list(FRef, [{Offset, IOData} | Rest], Successes) -> + case pwrite_plain(FRef, Offset, erlang:iolist_to_iovec(IOData)) of + {error, Reason} -> {error, {Successes, Reason}}; + ok -> pwrite_list(FRef, Rest, Successes + 1) + end. -set_cwd(Port, Dir) when is_port(Port) -> - set_cwd_int(Port, Dir). +sendfile(Fd, Socket, Offset, Bytes, _ChunkSize, [], [], _Flags) -> + %% There's a very nasty race in here; if we die just prior to duplicating + %% the handle down in the sendfile call, it might get reused by something + %% entirely different and we'll leak unknown data to the socket until it + %% dies soon after. + %% + %% This bug was inherited from the old driver, except it was vulnerable to + %% the bug at any point and not just during setup. + %% + %% We'll have to live with this until we have a way to unambiguously + %% transfer things between drivers or NIFs. Current ideas all fall afoul + %% of the Two Generals problem. + try + advise(Fd, Offset, Bytes, sequential), + prim_inet:sendfile(Socket, get_handle(Fd), Offset, Bytes) + catch + error:badarg -> {error, badarg} + end; +sendfile(_Fd, _Socket, _Offset, _Bytes, _ChunkSize, _Headers, _Trailers, _Flags) -> + {error, enotsup}. -set_cwd_int(Port, Dir) when is_binary(Dir) -> - case prim_file:is_translatable(Dir) of - false -> - {error, no_translation}; - true -> - drv_command(Port, [?FILE_CHDIR, pathname(Dir)]) +%% Undocumented internal function that reads a data block with indirection. +%% +%% This is only used once in DETS and can easily be emulated with pread/2, but +%% it's pretty performance-sensitive so we've implemented it down in the NIF to +%% avoid excessive rescheduling. +-spec ipread_s32bu_p32bu(Fd, Offset, MaxSize) -> Result when + Fd :: #file_descriptor{}, + Offset :: non_neg_integer(), + MaxSize :: non_neg_integer() | infinity, + Result :: {ok, Size :: non_neg_integer(), + Pointer :: non_neg_integer(), + Data :: iodata() | eof} | + eof | + {error, Reason :: atom()}. +ipread_s32bu_p32bu(Fd, Offset, Infinity) when is_atom(Infinity) -> + ipread_s32bu_p32bu(Fd, Offset, (1 bsl 31) - 1); +ipread_s32bu_p32bu(Fd, Offset, MaxSize) + when is_integer(Offset), is_integer(MaxSize) -> + try + #{ handle := FRef } = get_fd_data(Fd), + ipread_s32bu_p32bu_nif(FRef, Offset, MaxSize) + catch + error:badarg -> {error, badarg} end; -set_cwd_int(Port, Dir) when is_list(Dir) -> - drv_command(Port, [?FILE_CHDIR, pathname(Dir)]); -set_cwd_int(_, _) -> +ipread_s32bu_p32bu(_Fd, _Offset, _MaxSize) -> {error, badarg}. - - - -%% 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) -> +ipread_s32bu_p32bu_nif(_FRef, _Offset, _MaxSize) -> + erlang:nif_error(undef). + +%% Returns the binary representation of the underlying handle, for use in +%% tricky operations like sendfile/8. +-spec get_handle(Fd) -> Result when + Fd :: #file_descriptor{}, + Result :: binary() | {error, Reason :: atom()}. +get_handle(Fd) -> 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 + #{ handle := FRef } = get_fd_data(Fd), + get_handle_nif(FRef) catch - error:_ -> {error, badarg} + error:badarg -> {error, badarg} end. +%% Resets the write head to the position the user believes we're at, which may +%% not be the same as the real one when read caching is in effect. +reset_write_position(Fd) -> + #{ r_buffer := RBuf } = Fd#file_descriptor.data, + case prim_buffer:size(RBuf) of + Size when Size > 0 -> position(Fd, cur); + Size when Size =:= 0 -> ok + 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)], - fun handle_fname_response/1). - -%% 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)). +get_fd_data(#file_descriptor{ data = Data }) -> + #{ owner := Owner } = Data, + case self() of + Owner -> Data; + _ -> error(not_on_controlling_process) + end. -write_file_info_int(Port, File, - #file_info{mode=Mode, - uid=Uid, - gid=Gid, - atime=Atime0, - mtime=Mtime0, - ctime=Ctime0}, - TimeType) -> +build_fd_data(FRef, Modes) -> + Defaults = + #{ owner => self(), + handle => FRef, + r_ahead_size => 0, + r_buffer => prim_buffer:new() }, + fill_fd_option_map(Modes, Defaults). + +fill_fd_option_map([], Map) -> + Map; + +fill_fd_option_map([read_ahead | Modes], Map) -> + fill_fd_option_map([{read_ahead, 64 bsl 10} | Modes], Map); +fill_fd_option_map([{read_ahead, Size} | Modes], Map) -> + fill_fd_option_map(Modes, Map#{ r_ahead_size => Size }); + +fill_fd_option_map([_Ignored | Modes], Map) -> + fill_fd_option_map(Modes, Map). + +open_nif(_Name, _Modes) -> + erlang:nif_error(undef). +close_nif(_FileRef) -> + erlang:nif_error(undef). +read_nif(_FileRef, _Size) -> + erlang:nif_error(undef). +write_nif(_FileRef, _IOVec) -> + erlang:nif_error(undef). +pread_nif(_FileRef, _Offset, _Size) -> + erlang:nif_error(undef). +pwrite_nif(_FileRef, _Offset, _IOVec) -> + erlang:nif_error(undef). +seek_nif(_FileRef, _Mark, _Offset) -> + erlang:nif_error(undef). +sync_nif(_FileRef, _DataOnly) -> + erlang:nif_error(undef). +advise_nif(_FileRef, _Offset, _Length, _Advise) -> + erlang:nif_error(undef). +allocate_nif(_FileRef, _Offset, _Length) -> + erlang:nif_error(undef). +truncate_nif(_FileRef) -> + erlang:nif_error(undef). +get_handle_nif(_FileRef) -> + erlang:nif_error(undef). - % 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 +%% +%% Quality-of-life helpers +%% +read_file(Filename) -> + %% We're doing this operation in the NIF to avoid excessive rescheduling. 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)]) + read_file_nif(encode_path(Filename)) catch - error:_ -> {error, badarg} + error:badarg -> {error, badarg} + end. +read_file_nif(_Filename) -> + erlang:nif_error(undef). + +write_file(Filename, Bytes) -> + write_file(Filename, Bytes, []). +write_file(Filename, Bytes, Modes) -> + case open(Filename, [write, binary | Modes]) of + {ok, Fd} -> + Result = write(Fd, Bytes), + close(Fd), + Result; + {error, Reason} -> + {error, Reason} end. +%% +%% Filesystem operations +%% -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)], - fun handle_fname_response/1). - -%% read_link_all/{2,3} - -read_link_all(Link) -> - read_link_all_int({?DRV, [binary]}, Link). - -read_link_all(Port, Link) when is_port(Port) -> - read_link_all_int(Port, Link). - -read_link_all_int(Port, Link) -> - drv_command(Port, [?FILE_READLINK, pathname(Link)], - fun handle_fname_response_all/1). - - - -%% 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(Name) -> read_link_1(Name, false). +read_link_all(Name) -> read_link_1(Name, true). -read_link_info(Link, Opts) -> - read_link_info_int({?DRV, [binary]}, Link, plgv(time, Opts, local)). +read_link_1(Name, AcceptRawNames) -> + try read_link_nif(encode_path(Name)) of + {ok, RawName} -> translate_raw_name(RawName, AcceptRawNames); + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} + end. -read_link_info(Port, Link, Opts) when is_port(Port) -> - read_link_info_int(Port, Link, plgv(time, Opts, local)). +translate_raw_name(RawName, SilentFailure) -> + case decode_path(RawName) of + Converted when is_list(Converted) -> {ok, Converted}; + {error, _Reason} when SilentFailure =:= false -> {error, einval}; + {error, _Reason} when SilentFailure =:= true -> {ok, RawName} + end. +list_dir(Name) -> list_dir_1(Name, true). +list_dir_all(Name) -> list_dir_1(Name, false). -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 +list_dir_1(Name, SkipInvalid) -> + try list_dir_nif(encode_path(Name)) of + {ok, RawNames} -> list_dir_convert(RawNames, SkipInvalid, []); + {error, Reason} -> {error, Reason} catch - error:_ -> {error, badarg} + error:badarg -> {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)], - fun(P) -> - case list_dir_response(P, []) of - {ok, RawNames} -> - try - {ok, list_dir_convert(RawNames)} - catch - throw:Reason -> - Reason - end; - Error -> - Error - end - end). - -list_dir_all(Dir) -> - list_dir_all_int({?DRV, [binary]}, Dir). - -list_dir_all(Port, Dir) when is_port(Port) -> - list_dir_all_int(Port, Dir). - -list_dir_all_int(Port, Dir) -> - drv_command(Port, [?FILE_READDIR, pathname(Dir)], - fun(P) -> - case list_dir_response(P, []) of - {ok, RawNames} -> - {ok, list_dir_convert_all(RawNames)}; - Error -> - Error - end - end). - -list_dir_response(Port, Acc0) -> - case drv_get_response(Port) of - {lfname, []} -> - {ok, Acc0}; - {lfname, Names} -> - Acc = [Name || <<L:16,Name:L/binary>> <= Names] ++ Acc0, - list_dir_response(Port, Acc); - Error -> - Error +list_dir_convert([], _SkipInvalid, Result) -> + {ok, Result}; +list_dir_convert([RawName | Rest], SkipInvalid, Result) -> + case decode_path(RawName) of + Converted when is_list(Converted) -> + list_dir_convert(Rest, SkipInvalid, [Converted | Result]); + {error, _} when SkipInvalid =:= false -> + list_dir_convert(Rest, SkipInvalid, [RawName | Result]); + + %% If the filename cannot be converted, return error or ignore with + %% optional error logger warning depending on +fn{u|a}{i|e|w} emulator + %% switches. + {error, ignore} -> + list_dir_convert(Rest, SkipInvalid, Result); + {error, warning} -> + error_logger:warning_msg( + "Non-unicode filename ~p ignored\n", [RawName]), + list_dir_convert(Rest, SkipInvalid, Result); + {error, _} -> + {error, {no_translation, RawName}} end. -list_dir_convert([Name|Names]) -> - %% If the filename cannot be converted, return error or ignore - %% with optional error logger warning, depending on +fn{u|a}{i|e|w} - %% emulator switches. - case prim_file:internal_native2name(Name) of - {error, warning} -> - error_logger:warning_msg("Non-unicode filename ~p ignored\n", - [Name]), - list_dir_convert(Names); - {error, ignore} -> - list_dir_convert(Names); - {error, error} -> - throw({error, {no_translation, Name}}); - Converted when is_list(Converted) -> - [Converted|list_dir_convert(Names)] - end; -list_dir_convert([]) -> []. - -list_dir_convert_all([Name|Names]) -> - %% If the filename cannot be converted, retain the filename as - %% a binary. - case prim_file:internal_native2name(Name) of - {error, _} -> - [Name|list_dir_convert_all(Names)]; - Converted when is_list(Converted) -> - [Converted|list_dir_convert_all(Names)] - end; -list_dir_convert_all([]) -> []. - -%%%----------------------------------------------------------------- -%%% Functions to communicate with the driver - -handle_fname_response(Port) -> - case drv_get_response(Port) of - {fname, Name} -> - case prim_file:internal_native2name(Name) of - {error, warning} -> - error_logger:warning_msg("Non-unicode filename ~p " - "ignored when reading link\n", - [Name]), - {error, einval}; - {error, _} -> - {error, einval}; - Converted when is_list(Converted) -> - {ok, Converted} - end; - Error -> - Error +read_file_info(Filename) -> + read_info_1(Filename, 1, local). +read_file_info(Filename, Opts) -> + read_info_1(Filename, 1, proplist_get_value(time, Opts, local)). + +read_link_info(Name) -> + read_info_1(Name, 0, local). +read_link_info(Name, Opts) -> + read_info_1(Name, 0, proplist_get_value(time, Opts, local)). + +read_info_1(Name, FollowLinks, TimeType) -> + try read_info_nif(encode_path(Name), FollowLinks) of + {error, Reason} -> {error, Reason}; + FileInfo -> + CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType), + MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType), + ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType), + {ok, FileInfo#file_info{ ctime = CTime, mtime = MTime, atime = ATime }} + catch + error:badarg -> {error, badarg} end. -handle_fname_response_all(Port) -> - case drv_get_response(Port) of - {fname, Name} -> - case prim_file:internal_native2name(Name) of - {error, _} -> - {ok, Name}; - Converted when is_list(Converted) -> - {ok, Converted} - end; - Error -> - Error +write_file_info(Filename, Info) -> + write_file_info_1(Filename, Info, local). +write_file_info(Filename, Info, Opts) -> + write_file_info_1(Filename, Info, proplist_get_value(time, Opts, local)). + +write_file_info_1(Filename, Info, TimeType) -> + #file_info{ mode = Modes, + uid = Uid, + gid = Gid, + atime = ATime0, + mtime = MTime0, + ctime = CTime0} = Info, + try + % 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 + ATime = file_info_convert_atime(ATime0, TimeType), + MTime = file_info_convert_mtime(MTime0, ATime, TimeType), + CTime = file_info_convert_ctime(CTime0, MTime, TimeType), + EncodedName = encode_path(Filename), + + %% This is a bit ugly but we need to handle partial failures the same + %% way the old driver did. + throw_on_error(set_owner(EncodedName, Uid, Gid)), + throw_on_error(set_permissions(EncodedName, Modes)), + throw_on_error(set_time(EncodedName, ATime, MTime, CTime)) + catch + throw:Reason -> {error, Reason}; + error:_ -> {error, badarg} end. -%% Opens a driver port and converts any problems into {error, emfile}. -%% Returns {ok, Port} when successful. +set_owner(_EncodedName, undefined, undefined) -> + ok; +set_owner(EncodedName, Uid, Gid) -> + set_owner_nif(EncodedName, Uid, Gid). +set_owner_nif(_Path, _Uid, _Gid) -> + erlang:nif_error(undef). -drv_open(Driver, Portopts) -> - try erlang:open_port({spawn_driver, Driver}, Portopts) of - Port -> - {ok, Port} +set_permissions(_EncodedName, undefined) -> + ok; +set_permissions(EncodedName, Permissions) -> + set_permissions_nif(EncodedName, Permissions). +set_permissions_nif(_Path, _Permissions) -> + erlang:nif_error(undef). + +set_time(EncodedName, ATime, MTime, CTime) -> + set_time_nif(EncodedName, ATime, MTime, CTime). +set_time_nif(_Path, _ATime, _MTime, _CTime) -> + erlang:nif_error(undef). + +throw_on_error(ok) -> ok; +throw_on_error({error, enotsup}) -> ok; +throw_on_error({error, Reason}) -> throw(Reason). + +file_info_convert_atime(ATime, TimeType) when ATime =/= undefined -> + to_posix_seconds(ATime, TimeType); +file_info_convert_atime(undefined, local) -> + to_posix_seconds(erlang:localtime(), local); +file_info_convert_atime(undefined, universal) -> + to_posix_seconds(erlang:universaltime(), universal); +file_info_convert_atime(undefined, posix) -> + erlang:universaltime_to_posixtime(erlang:universaltime()). + +file_info_convert_mtime(undefined, ATime, _TimeType) -> + ATime; +file_info_convert_mtime(MTime, _ATime, TimeType) -> + to_posix_seconds(MTime, TimeType). + +file_info_convert_ctime(undefined, MTime, _TimeType) -> + MTime; +file_info_convert_ctime(CTime, _MTime, TimeType) -> + to_posix_seconds(CTime, TimeType). + +%% This is only relevant on Windows, so we assume that format to simplify the +%% internals. +get_cwd([Letter, $:]) when Letter >= $A, Letter =< $Z -> + get_dcwd(Letter - $A + 1); +get_cwd([Letter, $:]) when Letter >= $a, Letter =< $z -> + get_dcwd(Letter - $a + 1); +get_cwd([_|_]) -> + {error, einval}; +get_cwd(_) -> + {error, badarg}. +get_dcwd(Index) -> + try get_device_cwd_nif(Index) of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + {error, Reason} -> {error, Reason} catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} end. - - -%% Closes a port in a safe way. Returns ok. - -drv_close(Port) -> - Save = erlang:dt_spread_tag(false), +get_cwd() -> + try get_cwd_nif() of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} + end. +set_cwd(Path) -> 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) + case is_path_translatable(Path) of + true -> set_cwd_nif(encode_path(Path)); + false -> {error, no_translation} + end + catch + error:badarg -> {error, badarg} 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) +delete(Path) -> + try + del_file_nif(encode_path(Path)) catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} 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) +rename(Source, Destination) -> + try + rename_nif(encode_path(Source), encode_path(Destination)) 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 + error:badarg -> {error, badarg} 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) +make_dir(Path) -> + try + make_dir_nif(encode_path(Path)) 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) + error:badarg -> {error, badarg} end. - - - -%% Receives the response from a driver port. -%% Returns: {ok, ListOrBinary}|{error, Reason} - -drv_get_response(Port, undefined) -> - drv_get_response(Port); -drv_get_response(Port, Fun) when is_function(Fun, 1) -> - Fun(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}} +del_dir(Path) -> + try + del_dir_nif(encode_path(Path)) + catch + error:badarg -> {error, badarg} end. - - -%%%----------------------------------------------------------------- -%%% Utility functions. - -%% 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 +make_link(Existing, New) -> + try + make_hard_link_nif(encode_path(Existing), encode_path(New)) + catch + error:badarg -> {error, badarg} 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([sync|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_SYNC, 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}. - --dialyzer({no_improper_lists, translate_response/2}). -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, Data) -> - {fname, Data}; -translate_response(?FILE_RESP_LFNAME, Data) -> - {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]). - -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 +make_symlink(Existing, New) -> + try + make_soft_link_nif(encode_path(Existing), encode_path(New)) + catch + error:badarg -> {error, badarg} 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). +altname(Path) -> + try altname_nif(encode_path(Path)) of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + Other -> Other + catch + error:badarg -> {error, badarg} + end. -% 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)). +list_dir_nif(_Path) -> + erlang:nif_error(undef). +read_link_nif(_Path) -> + erlang:nif_error(undef). +read_info_nif(_Path, _FollowLinks) -> + erlang:nif_error(undef). +make_hard_link_nif(_Existing, _New) -> + erlang:nif_error(undef). +make_soft_link_nif(_Existing, _New) -> + erlang:nif_error(undef). +rename_nif(_Source, _Destination) -> + erlang:nif_error(undef). +make_dir_nif(_Path) -> + erlang:nif_error(undef). +del_file_nif(_Path) -> + erlang:nif_error(undef). +del_dir_nif(_Path) -> + erlang:nif_error(undef). +get_device_cwd_nif(_DevicePath) -> + erlang:nif_error(undef). +set_cwd_nif(_Path) -> + erlang:nif_error(undef). +get_cwd_nif() -> + erlang:nif_error(undef). +altname_nif(_Path) -> + erlang:nif_error(undef). +%% +%% General helper functions. +%% -%% proplist:get_value/3 -plgv(K, [{K, V}|_], _) -> V; -plgv(K, [_|KVs], D) -> plgv(K, KVs, D); -plgv(_, [], D) -> D. +%% We know for certain that lists:reverse/2 is a BIF, so it's safe to use it +%% even though this module is preloaded. +reverse_list(List) -> lists:reverse(List). + +proplist_get_value(_Key, [], Default) -> + Default; +proplist_get_value(Key, [{Key, Value} | _Rest], _Default) -> + Value; +proplist_get_value(Key, [Key | _Rest], _Default) -> + true; +proplist_get_value(Key, [_Other | Rest], Default) -> + proplist_get_value(Key, Rest, Default). + +encode_path(Path) -> + prim_file:internal_name2native(Path). +decode_path(NativePath) when is_binary(NativePath) -> + prim_file:internal_native2name(NativePath). + +is_path_translatable(Path) when is_list(Path) -> + true; +is_path_translatable(Path) -> + prim_file:is_translatable(Path). -%% %% 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) -> +from_posix_seconds(Seconds, posix) when is_integer(Seconds) -> Seconds; -from_seconds(Seconds, universal) when is_integer(Seconds) -> +from_posix_seconds(Seconds, universal) when is_integer(Seconds) -> erlang:posixtime_to_universaltime(Seconds); -from_seconds(Seconds, local) when is_integer(Seconds) -> +from_posix_seconds(Seconds, local) when is_integer(Seconds) -> erlang:universaltime_to_localtime(erlang:posixtime_to_universaltime(Seconds)). -to_seconds(Seconds, posix) when is_integer(Seconds) -> +to_posix_seconds(Seconds, posix) when is_integer(Seconds) -> Seconds; -to_seconds({_,_} = Datetime, universal) -> +to_posix_seconds({_,_} = Datetime, universal) -> erlang:universaltime_to_posixtime(Datetime); -to_seconds({_,_} = Datetime, local) -> +to_posix_seconds({_,_} = Datetime, local) -> erlang:universaltime_to_posixtime(erlang:localtime_to_universaltime(Datetime)). diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 017a706a8b..2a3605260d 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -31,7 +31,7 @@ -export([connect/3, connect/4, async_connect/4]). -export([accept/1, accept/2, async_accept/2]). -export([shutdown/2]). --export([send/2, send/3, sendto/4, sendmsg/3]). +-export([send/2, send/3, sendto/4, sendmsg/3, sendfile/4]). -export([recv/2, recv/3, async_recv/3]). -export([unrecv/2]). -export([recvfrom/2, recvfrom/3]). @@ -500,6 +500,55 @@ sendmsg(S, #sctp_sndrcvinfo{}=SRI, Data) when is_port(S) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% +%% SENDFILE(outsock(), Fd, Offset, Length) -> {ok,BytesSent} | {error, Reason} +%% +%% send Length data bytes from a file handle, to a socket, starting at Offset +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% "sendfile" is for TCP: + +sendfile(S, FileHandle, Offset, Length) + when not is_port(S); + not is_binary(FileHandle); + not is_integer(Offset); + not is_integer(Length) -> + {error, badarg}; +sendfile(S, FileHandle, Offset, Length) -> + case erlang:port_info(S, connected) of + {connected, Pid} when Pid =:= self() -> + sendfile_1(S, FileHandle, Offset, Length); + {connected, Pid} when Pid =/= self() -> + {error, not_owner}; + _Other -> + {error, einval} + end. + +sendfile_1(S, FileHandle, Offset, 0) -> + sendfile_1(S, FileHandle, Offset, (1 bsl 63) - 1); +sendfile_1(_S, _FileHandle, Offset, Length) when + Offset < 0; Offset > ((1 bsl 63) - 1); + Length < 0; Length > ((1 bsl 63) - 1) -> + {error, einval}; +sendfile_1(S, FileHandle, Offset, Length) -> + Args = [FileHandle, + ?int64(Offset), + ?int64(Length)], + case ctl_cmd(S, ?TCP_REQ_SENDFILE, Args) of + {ok, []} -> + receive + {sendfile, S, {ok, SentLow, SentHigh}} -> + {ok, SentLow bor (SentHigh bsl 32)}; + {sendfile, S, {error, Reason}} -> + {error, Reason}; + {'EXIT', S, _Reason} -> + {error, closed} + end; + {error, Reason} -> + {error, Reason} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% RECV(insock(), Length, [Timeout]) -> {ok,Data} | {error, Reason} %% %% receive Length data bytes from a socket |