aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/doc/src/file.xml22
-rw-r--r--lib/kernel/src/file.erl58
-rw-r--r--lib/kernel/src/file_io_server.erl8
-rw-r--r--lib/kernel/test/file_SUITE.erl87
4 files changed, 173 insertions, 2 deletions
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 7db20e6343..78bf0aff45 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -1574,6 +1574,28 @@
</desc>
</func>
<func>
+ <name name="sendfile" arity="4"/>
+ <fsummary>send a file to a socket</fsummary>
+ <desc>
+ <p>Sends <c>Bytes</c> in from the file
+ referenced by <c>IoDevice</c> beginning at <c>Offset</c> to
+ <c>Socket</c>.
+ Returns <c>{ok, BytesSent}</c> if successful,
+ otherwise <c>{error, Reason}</c>.</p>
+ <p>Available on Linux, FreeBSD, DragonflyBSD, Solaris, Darwin and Windows</p>
+ </desc>
+ </func>
+ <func>
+ <name name="sendfile" arity="2"/>
+ <fsummary>send a file to a socket</fsummary>
+ <desc>
+ <p>Sends the file <c>Filename</c> to <c>Socket</c>.
+ Returns <c>{ok, BytesSent}</c> if successful,
+ otherwise <c>{error, Reason}</c>.</p>
+ <p>Available on Linux, FreeBSD, DragonflyBSD, Solaris, Darwin and Windows</p>
+ </desc>
+ </func>
+ <func>
<name name="write" arity="2"/>
<fsummary>Write to a file</fsummary>
<desc>
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 706c60caaf..fba9a86e95 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -41,7 +41,7 @@
pread/2, pread/3, pwrite/2, pwrite/3,
read_line/1,
position/2, truncate/1, datasync/1, sync/1,
- copy/2, copy/3]).
+ copy/2, copy/3, sendfile/4, sendfile/2]).
%% High level operations
-export([consult/1, path_consult/2]).
-export([eval/1, eval/2, path_eval/2, path_eval/3, path_open/3]).
@@ -335,6 +335,62 @@ raw_write_file_info(Name, #file_info{} = Info) ->
Error
end.
+%% sendfile/5
+%% TODO: add more guards? export sendfile/5?
+-spec sendfile(File, Sock, Offset, Bytes, ChunkSize)
+ -> {'ok', non_neg_integer()} | {'error', posix()} when
+ File::io_device(), Sock::port() | integer(),
+ Offset::non_neg_integer(), Bytes::non_neg_integer(),
+ ChunkSize::non_neg_integer().
+sendfile(File, Sock, Offset, Bytes, ChunkSize) when is_integer(Sock)
+ andalso is_pid(File) ->
+ R = file_request(File, {sendfile, Sock, Offset, Bytes, ChunkSize}),
+ wait_file_reply(File, R);
+sendfile(File, Sock, Offset, Bytes, ChunkSize) when is_port(Sock)
+ andalso is_pid(File) ->
+ {ok, SockFD} = prim_inet:getfd(Sock),
+ sendfile(File, SockFD, Offset, Bytes, ChunkSize);
+sendfile(#file_descriptor{module = Module} = Handle, Sock,
+ Offset, Bytes, ChunkSize) when is_integer(Sock) ->
+ Module:sendfile(Handle, Sock, Offset, Bytes, ChunkSize);
+sendfile(#file_descriptor{module = _Module} = Handle, Sock,
+ Offset, Bytes, ChunkSize) when is_port(Sock) ->
+ {ok, SockFD} = prim_inet:getfd(Sock),
+ sendfile(Handle, SockFD, Offset, Bytes, ChunkSize);
+sendfile(_, _, _, _, _) ->
+ {error, badarg}.
+
+-define(SENDFILE_CHUNK_LIMIT, 2147483648). % 2GB
+
+%% Limit chunksize to work around 4 byte off_t/size_t limits
+sendfile_chunksize(Bytes, Limit) ->
+ case Bytes >= Limit of
+ true -> Limit - 1;
+ false -> Bytes
+ end.
+
+-spec sendfile(File, Sock, Offset, Bytes)
+ -> {'ok', non_neg_integer()} | {'error', posix()} when
+ File::io_device(), Sock::port() | integer(),
+ Offset::non_neg_integer(), Bytes::non_neg_integer().
+sendfile(File, Sock, Offset, Bytes) ->
+ ChunkSize = sendfile_chunksize(Bytes, ?SENDFILE_CHUNK_LIMIT),
+ sendfile(File, Sock, Offset, Bytes, ChunkSize).
+
+%% sendfile/2
+%% TODO: add guards?
+-spec sendfile(File, Sock) -> {'ok', non_neg_integer()} | {'error', posix()}
+ when File::name(), Sock::port().
+sendfile(File, Sock) ->
+ Offset = 0,
+ {ok, #file_info{size = Bytes}} = read_file_info(File),
+ %% TODO: use file:open/2 and file:read_file_info/1 instead of local calls?
+ {ok, Fd} = open(File, [read, raw, binary]),
+ ChunkSize = sendfile_chunksize(Bytes, ?SENDFILE_CHUNK_LIMIT),
+ Res = sendfile(Fd, Sock, Offset, Bytes, ChunkSize),
+ ok = close(Fd),
+ Res.
+
%%%-----------------------------------------------------------------
%%% File io server functions.
%%% They operate on a single open file.
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index 14da9c1a55..78d3d24394 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -249,6 +249,14 @@ file_request(close,
file_request({position,At},
#state{handle=Handle,buf=Buf}=State) ->
std_reply(position(Handle, At, Buf), State);
+file_request({sendfile,DestFD,Offset,Bytes,ChunkSize},
+ #state{handle=Handle}=State) ->
+ case ?PRIM_FILE:sendfile(Handle, DestFD, Offset, Bytes, ChunkSize) of
+ {error,_}=Reply ->
+ {stop,normal,Reply,State};
+ Reply ->
+ {reply,Reply,State}
+ end;
file_request(truncate,
#state{handle=Handle}=State) ->
case ?PRIM_FILE:truncate(Handle) of
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index 77fc7e73f9..67458ae77d 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -86,6 +86,8 @@
-export([standard_io/1,mini_server/1]).
+-export([sendfile/0, sendfile/1, sendfile_server/1]).
+
%% Debug exports
-export([create_file_slow/2, create_file/2, create_bin/2]).
-export([verify_file/2, verify_bin/3]).
@@ -107,7 +109,7 @@ all() ->
delayed_write, read_ahead, segment_read, segment_write,
ipread, pid2name, interleaved_read_write, otp_5814,
large_file, read_line_1, read_line_2, read_line_3,
- read_line_4, standard_io].
+ read_line_4, standard_io, sendfile].
groups() ->
[{dirs, [], [make_del_dir, cur_dir_0, cur_dir_1]},
@@ -3950,3 +3952,86 @@ flush(Msgs) ->
after 0 ->
lists:reverse(Msgs)
end.
+
+
+sendfile() ->
+ [{timetrap, {seconds, 5}}].
+
+sendfile_supported({unix,linux}) -> true;
+sendfile_supported({unix,sunos}) -> true;
+sendfile_supported({unix,freebsd}) -> true;
+sendfile_supported({unix,dragonfly}) -> true;
+sendfile_supported({unix,darwin}) -> true;
+%% TODO: enable win32 once TransmitFile based implemenation written properly
+%% sendfile_supported({win32,_}) -> true;
+sendfile_supported(_) -> false.
+
+sendfile(Config) when is_list(Config) ->
+ case sendfile_supported(os:type()) of
+ true ->
+ ?line Data = ?config(data_dir, Config),
+ ?line Real = filename:join(Data, "realmen.html"),
+ Host = "localhost",
+
+ %% TODO: find another way to test for {error, posix_error()}?
+ %% Disabled because with driver_select I cannot test for
+ %% invalid out_fd
+ %% ?line {error, Error} = file:sendfile(Real, -1),
+ %% ?line test_server:format("sendfile error = ~p", [Error]),
+ %% %% Unix ebadf, Windows eio
+ %% ?line true = Error =:= ebadf orelse Error =:= eio,
+
+ ?line ok = sendfile_send(Host, Real);
+ false ->
+ {skip, "sendfile not supported on this platform"}
+ end.
+
+%% TODO: consolidate tests and reduce code
+
+sendfile_send(Host, File) ->
+ {Size, _Md5} = FileInfo = sendfile_file_info(File),
+ spawn_link(?MODULE, sendfile_server, [self()]),
+ receive
+ {server, Port} ->
+ ?line {ok, Sock} = gen_tcp:connect(Host, Port,
+ [binary,{packet,0}]),
+ ?line {ok, Size} = file:sendfile(File, Sock),
+ ?line ok = gen_tcp:close(Sock),
+ receive
+ {ok, Bin} ->
+ ?line FileInfo = sendfile_bin_info(Bin),
+ ok
+ end
+ end.
+
+sendfile_server(ClientPid) ->
+ ?line {ok, LSock} = gen_tcp:listen(0, [binary, {packet, 0},
+ {active, false},
+ {reuseaddr, true}]),
+ ?line {ok, Port} = inet:port(LSock),
+ ClientPid ! {server, Port},
+ ?line {ok, Sock} = gen_tcp:accept(LSock),
+ ?line {ok, Bin} = sendfile_do_recv(Sock, []),
+ ?line ok = gen_tcp:close(Sock),
+ ClientPid ! {ok, Bin}.
+
+-define(SENDFILE_TIMEOUT, 5000).
+
+sendfile_do_recv(Sock, Bs) ->
+ case gen_tcp:recv(Sock, 0, ?SENDFILE_TIMEOUT) of
+ {ok, B} ->
+ sendfile_do_recv(Sock, [B|Bs]);
+ {error, closed} ->
+ {ok, lists:reverse(Bs)}
+ end.
+
+sendfile_file_info(File) ->
+ {ok, #file_info{size = Size}} = file:read_file_info(File),
+ {ok, Data} = file:read_file(File),
+ Md5 = erlang:md5(Data),
+ {Size, Md5}.
+
+sendfile_bin_info(Data) ->
+ Size = lists:foldl(fun(E,Sum) -> size(E) + Sum end, 0, Data),
+ Md5 = erlang:md5(Data),
+ {Size, Md5}.