From 195e1f19b06095f39a4fb0da46dfab2ec5b10e9a Mon Sep 17 00:00:00 2001
From: Tuncer Ayaz
Date: Thu, 13 Jan 2011 12:36:14 +0100
Subject: Implement file:sendfile
Allow Erlang code to use sendfile() where available by wrapping it as
file:sendfile/4 and file:sendfile/2.
sendfile(2) - Linux man page:
"sendfile() copies data between one file descriptor and another.
Because this copying is done within the kernel, sendfile() is more
efficient than the combination of read(2) and write(2), which would
require transferring data to and from user space."
---
lib/kernel/doc/src/file.xml | 22 ++++++++++
lib/kernel/src/file.erl | 58 +++++++++++++++++++++++++-
lib/kernel/src/file_io_server.erl | 8 ++++
lib/kernel/test/file_SUITE.erl | 87 ++++++++++++++++++++++++++++++++++++++-
4 files changed, 173 insertions(+), 2 deletions(-)
(limited to 'lib')
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
@@ -1573,6 +1573,28 @@
otherwise {error, Reason}.
+
+
+ send a file to a socket
+
+ Sends Bytes in from the file
+ referenced by IoDevice beginning at Offset to
+ Socket.
+ Returns {ok, BytesSent} if successful,
+ otherwise {error, Reason}.
+ Available on Linux, FreeBSD, DragonflyBSD, Solaris, Darwin and Windows
+
+
+
+
+ send a file to a socket
+
+ Sends the file Filename to Socket.
+ Returns {ok, BytesSent} if successful,
+ otherwise {error, Reason}.
+ Available on Linux, FreeBSD, DragonflyBSD, Solaris, Darwin and Windows
+
+
Write to a file
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}.
--
cgit v1.2.3