Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'ta/sendfile' into pu

* ta/sendfile:
  Implement file:sendfile
  • Loading branch information...
commit 34042f49c7b5f0ac1b91463344db663eddc2e76d 2 parents bae2264 + c652dda
Björn Gustavsson bjorng authored
15 erts/configure.in
View
@@ -1733,6 +1733,21 @@ if test "X$host" = "Xwin32"; then
fi
AC_FUNC_SETVBUF_REVERSED
+dnl TODO: not sure this is enough
+dnl TODO: maybe use AC_CHECK_FUNC instead of AC_CHECK_FUNS([])
+case $host_os in
+ solaris*)
+ AC_CHECK_FUNCS([sendfile])
+ AC_CHECK_LIB(sendfile, sendfile)
+ ;;
+ linux*|darwin*|freebsd*)
+ AC_CHECK_FUNCS([sendfile])
+ ;;
+ *)
+ AC_MSG_NOTICE([not checking for sendfile() on this platform])
+ ;;
+esac
+
disable_vfork=false
if test "x$EMU_THR_LIB_NAME" != "x"; then
AC_MSG_CHECKING([if vfork is known to hang multithreaded applications])
3  erts/emulator/Makefile.in
View
@@ -286,7 +286,8 @@ endif
endif
ifeq ($(TARGET),win32)
-LIBS += -L$(ERL_TOP)/erts/emulator/pcre/obj/$(TARGET)/$(TYPE) -lepcre
+# TODO: is this the right place to add -lmswsock?
+LIBS += -L$(ERL_TOP)/erts/emulator/pcre/obj/$(TARGET)/$(TYPE) -lepcre -lmswsock
else
LIBS += $(ERL_TOP)/erts/emulator/pcre/obj/$(TARGET)/$(TYPE)/$(LIB_PREFIX)epcre$(LIB_SUFFIX)
endif
96 erts/emulator/drivers/common/efile_drv.c
View
@@ -55,6 +55,7 @@
#define FILE_READ_LINE 29
#define FILE_FDATASYNC 30
#define FILE_FADVISE 31
+#define FILE_SENDFILE 32
/* Return codes */
@@ -364,6 +365,11 @@ struct t_data
Sint64 length;
int advise;
} fadvise;
+ struct {
+ Sint destfd;
+ off_t offset;
+ size_t size;
+ } sendfile;
} c;
char b[1];
};
@@ -1665,6 +1671,75 @@ static void invoke_fadvise(void *data)
d->result_ok = efile_fadvise(&d->errInfo, fd, offset, length, advise);
}
+static void invoke_sendfile(void *data)
+{
+ struct t_data *d = (struct t_data *) data;
+ int fd = (int) d->fd;
+ int destfd = (int) d->c.sendfile.destfd;
+ off_t offset = (off_t) d->c.sendfile.offset;
+ size_t count = (size_t) d->c.sendfile.size;
+ d->result_ok = efile_sendfile(&d->errInfo, fd, destfd, &offset, &count);
+
+ if (d->result_ok)
+ d->again=0;
+ else {
+ switch(d->errInfo.posix_errno){
+ /* TODO */
+ /* is this the right way? */
+ case 0: /*ok*/
+#if defined(__linux__)
+ /* case EAGAIN: TODO: is the check in efile_sendfile() enough? */
+ case EBADF:
+ case EFAULT:
+ case EINVAL:
+ case EIO:
+ case ENOMEM:
+#elif defined(__FreeBSD__)
+ /* case EAGAIN: TODO: is the check in efile_sendfile() enough? */
+ case EBADF:
+ case EBUSY:
+ case EFAULT:
+ /* case EINTR: TODO: is the check in efile_sendfile() enough? */
+ case EINVAL:
+ case EIO:
+ case ENOTCONN:
+ case ENOTSOCK:
+ case EOPNOTSUPP:
+ case EPIPE:
+/* TODO: check which to use */
+/* #elif defined(__APPLE__) && defined(__MACH__) */
+#elif defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
+ /* case EAGAIN: TODO: is the check in efile_sendfile() enough? */
+ case EBADF:
+ case ENOTSUP:
+ case ENOTSOCK:
+ case EFAULT:
+ /* case EINTR: TODO: is the check in efile_sendfile() enough? */
+ case EINVAL:
+ case EIO:
+ case ENOTCONN:
+ case EOPNOTSUPP:
+/* TODO: check which to use */
+/* #if defined(__linux__) || defined(__solaris__) */
+#elif defined(__SVR4) && defined(__sun)
+ case EAFNOSUPPORT:
+ /* case EAGAIN: TODO: is the check in efile_sendfile() enough? */
+ case EBADF:
+ case EINVAL:
+ case EIO:
+ case ENOTCONN:
+ case EOPNOTSUPP:
+ case EPIPE:
+ /* case EINTR: TODO: is the check in efile_sendfile() enough? */
+#endif
+ d->again = 0;
+ break;
+ default:
+ d->again = 1;
+ };
+ }
+}
+
static void free_readdir(void *data)
{
struct t_data *d = (struct t_data *) data;
@@ -2077,6 +2152,10 @@ file_async_ready(ErlDrvData e, ErlDrvThreadData data)
}
free_preadv(data);
break;
+ case FILE_SENDFILE:
+ reply_Uint(desc, d->c.sendfile.size);
+ free_data(data);
+ break;
default:
abort();
}
@@ -2389,6 +2468,23 @@ file_output(ErlDrvData e, char* buf, int count)
goto done;
}
+ case FILE_SENDFILE:
+ {
+ d = EF_SAFE_ALLOC(sizeof(struct t_data));
+ d->fd = fd;
+ d->command = command;
+ d->invoke = invoke_sendfile;
+ d->free = free_data;
+ d->level = 2;
+ d->c.sendfile.destfd = get_int32((uchar*) buf);
+ /* TODO: are off_t and size_t 64bit on all platforms?
+ off_t is 32bit on win32 msvc. maybe configurable in msvc. */
+ d->c.sendfile.offset = get_int64(((uchar*) buf) + sizeof(Sint32));
+ d->c.sendfile.size = get_int64(((uchar*) buf) + sizeof(Sint32)
+ + sizeof(Sint64));
+ goto done;
+ }
+
}
/*
2  erts/emulator/drivers/common/erl_efile.h
View
@@ -154,3 +154,5 @@ int efile_symlink(Efile_error* errInfo, char* old, char* new);
int efile_may_openfile(Efile_error* errInfo, char *name);
int efile_fadvise(Efile_error* errInfo, int fd, Sint64 offset, Sint64 length,
int advise);
+int efile_sendfile(Efile_error* errInfo, int in_fd, int out_fd, off_t *offset,
+ size_t *count);
64 erts/emulator/drivers/unix/unix_efile.c
View
@@ -33,6 +33,11 @@
#include <sys/types.h>
#include <sys/uio.h>
#endif
+/* TODO: check which to use */
+/* #if defined(__linux__) || defined(__solaris__) */
+#if defined(__linux__) || (defined(__SVR4) && defined(__sun))
+#include <sys/sendfile.h>
+#endif
#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
#define DARWIN 1
@@ -1462,3 +1467,62 @@ efile_fadvise(Efile_error* errInfo, int fd, Sint64 offset,
return check_error(0, errInfo);
#endif
}
+
+#ifdef HAVE_SENDFILE
+int
+efile_sendfile(Efile_error* errInfo, int in_fd, int out_fd,
+ off_t *offset, size_t *count)
+{
+/* TODO: check which to use */
+/* #if defined(__linux__) || defined(__solaris__) */
+#if defined(__linux__) || (defined(__SVR4) && defined(__sun))
+ off_t cur = *offset;
+ ssize_t retval;
+ /* TODO: do we need a loop limit? */
+ do {
+ retval = sendfile(out_fd, in_fd, offset, *count);
+ } while (retval < 0 && errno == EINTR);
+ if (retval >= 0 && retval != *count) {
+ if (*offset == cur) {
+ *offset += retval;
+ }
+ retval = -1;
+ errno = EAGAIN;
+ }
+ *count = retval;
+ return check_error(retval == -1 ? -1 : 0, errInfo);
+/* TODO: check which to use */
+/* #elif defined(__APPLE__) && defined(__MACH__) */
+#elif defined(DARWIN)
+ off_t len = *count;
+ int retval;
+ do {
+ retval = sendfile(in_fd, out_fd, *offset, &len, NULL, 0);
+ } while (retval < 0 && errno == EINTR);
+ if (retval < 0 && errno == EAGAIN) {
+ *offset += len;
+ }
+ *count = retval == 0 ? len : -1;
+ return check_error(retval == 0 ? 0 : -1, errInfo);
+#elif defined(__FreeBSD__)
+ off_t len = 0;
+ int retval;
+ do {
+ retval = sendfile(in_fd, out_fd, *offset, *count, NULL, &len, 0);
+ } while (retval < 0 && errno == EINTR);
+ if (retval < 0 && errno == EAGAIN) {
+ *offset += len;
+ }
+ *count = retval == 0 ? len : -1;
+ return check_error(retval == 0 ? 0 : -1, errInfo);
+#endif
+}
+#else /* no sendfile() */
+int
+efile_sendfile(Efile_error* errInfo, int in_fd, int out_fd,
+ off_t *offset, size_t *count)
+{
+ errno = ENOTSUP;
+ return check_error(-1, errInfo);
+}
+#endif
23 erts/emulator/drivers/win32/win_efile.c
View
@@ -1446,3 +1446,26 @@ efile_fadvise(Efile_error* errInfo, int fd, Sint64 offset,
errno = ERROR_SUCCESS;
return check_error(0, errInfo);
}
+
+int
+efile_sendfile(Efile_error* errInfo, int in_fd, int out_fd,
+ off_t *offset, size_t *count)
+{
+ /* TODO: Use SetFilePointerEx to support files larger than 4GB?
+ It would require that off_t is 64bit or another type is used.
+ SetFilePointerEx expects LARGE_INTEGER instead of LONG. */
+ if (SetFilePointer((HANDLE) in_fd, *offset, NULL, FILE_BEGIN)
+ != INVALID_SET_FILE_POINTER) {
+ if (TransmitFile((SOCKET) out_fd, (HANDLE) in_fd, *count,
+ 0, NULL, NULL, 0)) {
+ *offset += *count;
+ return check_error(0, errInfo);
+ } else {
+ /* TODO: correct error handling? */
+ return set_error(errInfo);
+ }
+ } else {
+ /* TODO: correct error handling? */
+ return set_error(errInfo);
+ }
+}
8 erts/preloaded/src/prim_file.erl
View
@@ -26,7 +26,8 @@
%% 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]).
+ write/2, pwrite/2, pwrite/3, read/2, read_line/1, pread/2, pread/3,
+ copy/3, sendfile/4]).
%% Specialized file operations
-export([open/1, open/3]).
@@ -98,6 +99,7 @@
-define(FILE_READ_LINE, 29).
-define(FILE_FDATASYNC, 30).
-define(FILE_ADVISE, 31).
+-define(FILE_SENDFILE, 32).
%% Driver responses
-define(FILE_RESP_OK, 0).
@@ -528,6 +530,10 @@ write_file(File, Bin) ->
end.
+%% Returns {error, Reason} | {ok, BytesCopied}
+sendfile(#file_descriptor{module = ?MODULE, data = {Port, _}},
+ DestFD, Offset, Bytes) ->
+ drv_command(Port, <<?FILE_SENDFILE, DestFD:32, Offset:64, Bytes:64>>).
%%%-----------------------------------------------------------------
%%% Functions operating on files without handle to the file. ?DRV.
82 lib/kernel/src/file.erl
View
@@ -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/3, 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]).
@@ -287,6 +287,86 @@ raw_write_file_info(Name, #file_info{} = Info) ->
Error
end.
+%% sendfile/4
+-spec sendfile(File :: io_device(), Sock :: port() | integer(),
+ Offset :: non_neg_integer(), Bytes :: non_neg_integer())
+ -> {'ok', non_neg_integer()} | {'error', posix()}.
+sendfile(_, _, _, 0) ->
+ {ok, 0};
+%% TODO: how to call is_pid(File) variant?
+%% is it OK to call one sendfile() from the other
+%% without documenting that clearer?
+%% specialization on arguments OK?
+sendfile(File, Sock, Offset, Bytes) when is_integer(Sock)
+ andalso is_pid(File) ->
+ R = file_request(File, {sendfile, Sock, Offset, Bytes}),
+ wait_file_reply(File, R);
+sendfile(File, Sock, Offset, Bytes) when is_port(Sock) andalso is_pid(File) ->
+ {ok, SockFD} = prim_inet:getfd(Sock),
+ sendfile(File, SockFD, Offset, Bytes);
+sendfile(#file_descriptor{module = Module} = Handle, Sock, Offset, Bytes)
+ when is_integer(Sock) ->
+ Module:sendfile(Handle, Sock, Offset, Bytes);
+sendfile(#file_descriptor{module = Module} = Handle, Sock, Offset, Bytes)
+ when is_port(Sock) ->
+ {ok, SockFD} = prim_inet:getfd(Sock),
+ sendfile(Handle, SockFD, Offset, Bytes);
+sendfile(_, _, _, _) ->
+ {error, badarg}.
+
+%% sendfile/3
+-spec sendfile(File :: name(), Sock :: port(), ChunkSize :: non_neg_integer())
+ -> {'ok', non_neg_integer()} | {'error', posix()}.
+sendfile(File, Sock, ChunkSize) ->
+ Offset = 0,
+ {ok, #file_info{size = Bytes}} = read_file_info(File),
+ {ok, Fd} = open(File, [read, raw, binary]),
+ {ok, SockFD} = prim_inet:getfd(Sock),
+ Res = sendfile_chunked_loop(Fd, SockFD, Offset, Bytes, Bytes, ChunkSize),
+ %% Res = sendfile_chunked_loop(Fd, Sock, Offset, Bytes, Bytes, ChunkSize),
+ ok = close(Fd),
+ Res.
+
+-spec sendfile_calc_size(Bytes :: non_neg_integer(),
+ ChunkSize :: non_neg_integer()) -> non_neg_integer().
+sendfile_calc_size(Bytes, ChunkSize) when Bytes >= ChunkSize -> ChunkSize;
+sendfile_calc_size(0, _) -> 0;
+sendfile_calc_size(Bytes, _) -> Bytes.
+
+-spec sendfile_chunked_loop(Fd :: io_device(), SockFD :: integer(),
+ Offset0 :: non_neg_integer(),
+ Bytes :: non_neg_integer(),
+ BytesLeft0 :: non_neg_integer(),
+ ChunkSize :: non_neg_integer())
+ -> {'ok', non_neg_integer()} | {'error', posix()}.
+sendfile_chunked_loop(Fd, SockFD, Offset0, Bytes, BytesLeft0, ChunkSize) ->
+ ToWrite = sendfile_calc_size(BytesLeft0, ChunkSize),
+ case sendfile(Fd, SockFD, Offset0, ToWrite) of
+ {ok, 0} ->
+ {ok, Bytes};
+ {ok, Written} ->
+ Offset1 = Offset0 + Written,
+ BytesLeft1 = BytesLeft0 - Written,
+ sendfile_chunked_loop(Fd, SockFD, Offset1, Bytes,
+ BytesLeft1, ChunkSize);
+ {error, Posix} ->
+ {error, Posix}
+ end.
+
+%% TODO: keep this fun when we have chunked send?
+%% for large files this will block the vm in the driver
+%% far too long.
+%% sendfile/2
+-spec sendfile(File :: name(), Sock :: port()) ->
+ {'ok', non_neg_integer()} | {'error', posix()}.
+sendfile(File, Sock) ->
+ Offset = 0,
+ {ok, #file_info{size = Bytes}} = read_file_info(File),
+ {ok, Fd} = open(File, [read, raw, binary]),
+ Res = sendfile(Fd, Sock, Offset, Bytes),
+ ok = close(Fd),
+ Res.
+
%%%-----------------------------------------------------------------
%%% File io server functions.
%%% They operate on a single open file.
8 lib/kernel/src/file_io_server.erl
View
@@ -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},
+ #state{handle=Handle}=State) ->
+ case ?PRIM_FILE:sendfile(Handle, DestFD, Offset, Bytes) 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
76 lib/kernel/test/file_SUITE.erl
View
@@ -86,6 +86,8 @@
-export([standard_io/1,mini_server/1]).
+-export([sendfile/1, sendfile_server/2]).
+
%% Debug exports
-export([create_file_slow/2, create_file/2, create_bin/2]).
-export([verify_file/2, verify_bin/3]).
@@ -106,7 +108,7 @@ all(suite) ->
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],
+ standard_io, sendfile],
fini}.
init(Config) when is_list(Config) ->
@@ -3914,3 +3916,75 @@ flush(Msgs) ->
after 0 ->
lists:reverse(Msgs)
end.
+
+
+%% sendfile/4, sendfile/2
+-define(SENDFILE_TIMEOUT, 5000).
+-define(SENDFILE_CHUNKSIZE, 10*1024).
+
+sendfile(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line Real = filename:join(Data, "realmen.html"),
+ Host = "localhost",
+ Port = 1998,
+ %% TODO: running both test fails with econnrefused for the chunked test
+ %% ?line ok = sendfile_send(Host, Port, Real),
+ ?line ok = sendfile_send_chunked(Host, Port+1, Real).
+
+sendfile_send_chunked(Host, Port, File) ->
+ FileInfo = sendfile_file_info(File),
+ spawn_link(?MODULE, sendfile_server, [self(), Port]),
+ ?line {ok, Sock} = gen_tcp:connect(Host, Port, [binary,{packet,0}]),
+ ?line {ok, _} = file:sendfile(File, Sock, ?SENDFILE_CHUNKSIZE),
+ ?line ok = gen_tcp:close(Sock),
+ Dog = test_server:timetrap(test_server:seconds(5)),
+ receive
+ {ok, Bin} ->
+ %% TODO: is it right to override the default 60s timetrap
+ ?line ok = test_server:timetrap_cancel(Dog),
+ FileInfo = sendfile_bin_info(Bin),
+ ok
+ end.
+
+sendfile_send(Host, Port, File) ->
+ FileInfo = sendfile_file_info(File),
+ spawn_link(?MODULE, sendfile_server, [self(), Port]),
+ ?line {ok, Sock} = gen_tcp:connect(Host, Port, [binary,{packet,0}]),
+ ?line {ok, _} = file:sendfile(File, Sock),
+ ?line ok = gen_tcp:close(Sock),
+ Dog = test_server:timetrap(test_server:seconds(5)),
+ receive
+ {ok, Bin} ->
+ %% TODO: is it right to override the default 60s timetrap
+ ?line ok = test_server:timetrap_cancel(Dog),
+ FileInfo = sendfile_bin_info(Bin),
+ ok
+ end.
+
+sendfile_server(ClientPid, Port) ->
+ ?line {ok, LSock} = gen_tcp:listen(Port, [binary, {packet, 0},
+ {active, false},
+ {reuseaddr, true}]),
+ ?line {ok, Sock} = gen_tcp:accept(LSock),
+ ?line {ok, Bin} = sendfile_do_recv(Sock, []),
+ ?line ok = gen_tcp:close(Sock),
+ ClientPid ! {ok, Bin}.
+
+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}.
Please sign in to comment.
Something went wrong with that request. Please try again.