diff options
-rw-r--r-- | erts/configure.in | 16 | ||||
-rw-r--r-- | erts/emulator/drivers/common/efile_drv.c | 234 | ||||
-rw-r--r-- | erts/emulator/drivers/common/erl_efile.h | 2 | ||||
-rw-r--r-- | erts/emulator/drivers/unix/unix_efile.c | 44 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/win_efile.c | 24 | ||||
-rw-r--r-- | erts/preloaded/src/prim_file.erl | 19 | ||||
-rw-r--r-- | lib/kernel/doc/src/file.xml | 22 | ||||
-rw-r--r-- | lib/kernel/src/file.erl | 58 | ||||
-rw-r--r-- | lib/kernel/src/file_io_server.erl | 8 | ||||
-rw-r--r-- | lib/kernel/test/file_SUITE.erl | 87 |
10 files changed, 509 insertions, 5 deletions
diff --git a/erts/configure.in b/erts/configure.in index e3eb6034e6..548e4cc9d5 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1690,6 +1690,22 @@ dnl fdatasync requires linking against -lrt on SunOS <= 5.10. dnl OpenSolaris 2009.06 is SunOS 5.11 and does not require -lrt. AC_SEARCH_LIBS(fdatasync, [rt]) + +dnl sendfile syscall +case $host_os in + linux*|freebsd*|dragonfly*|darwin*) + AC_CHECK_FUNCS([sendfile]) + ;; + solaris*) + AC_SEARCH_LIBS(sendfile, sendfile, AC_DEFINE(HAVE_SENDFILE, 1)) + ;; + win32) + LIBS="$LIBS -lmswsock" + ;; + *) + ;; +esac + dnl ---------------------------------------------------------------------- dnl Checks for library functions. dnl ---------------------------------------------------------------------- diff --git a/erts/emulator/drivers/common/efile_drv.c b/erts/emulator/drivers/common/efile_drv.c index 901d98c09d..509c4fe48c 100644 --- a/erts/emulator/drivers/common/efile_drv.c +++ b/erts/emulator/drivers/common/efile_drv.c @@ -55,6 +55,7 @@ #define FILE_READ_LINE 29 #define FILE_FDATASYNC 30 #define FILE_FADVISE 31 +#define FILE_SENDFILE 32 /* Return codes */ @@ -217,6 +218,7 @@ typedef unsigned char uchar; static ErlDrvData file_start(ErlDrvPort port, char* command); static int file_init(void); static void file_stop(ErlDrvData); +static void file_ready_output(ErlDrvData data, ErlDrvEvent event); static void file_output(ErlDrvData, char* buf, int len); static int file_control(ErlDrvData, unsigned int command, char* buf, int len, char **rbuf, int rlen); @@ -224,6 +226,7 @@ static void file_timeout(ErlDrvData); static void file_outputv(ErlDrvData, ErlIOVec*); static void file_async_ready(ErlDrvData, ErlDrvThreadData); static void file_flush(ErlDrvData); +static void file_stop_select(ErlDrvEvent event, void* _); @@ -253,6 +256,18 @@ typedef struct { ErlDrvPDL q_mtx; /* Mutex for the driver queue, known by the emulator. Also used for mutual exclusion when accessing field(s) below. */ size_t write_buffered; + ErlDrvTermData caller; /* recipient of sync reply */ + /* sendfile call state to retry/resume on event */ + int command; /* same as d->command. for sendfile. TODO: this seems wrong */ + struct { + int eagain; + int out_fd; + /* TODO: Use Sint64 instead? What about 32-bit off_t linux */ + off_t offset; + size_t count; + size_t chunksize; + ErlDrvSInt64 written; + } sendfile; } file_descriptor; @@ -264,7 +279,7 @@ struct erl_drv_entry efile_driver_entry = { file_stop, file_output, NULL, - NULL, + file_ready_output, "efile", NULL, NULL, @@ -279,7 +294,9 @@ struct erl_drv_entry efile_driver_entry = { ERL_DRV_EXTENDED_MAJOR_VERSION, ERL_DRV_EXTENDED_MINOR_VERSION, ERL_DRV_FLAG_USE_PORT_LOCKING, - NULL + NULL, + NULL, + file_stop_select }; @@ -613,6 +630,111 @@ static struct t_data *cq_deq(file_descriptor *desc) { } +/********************************************************************* + * Command queue functions + */ + +static ErlDrvTermData am_ok; +static ErlDrvTermData am_error; +static ErlDrvTermData am_efile_reply; + +#define INIT_ATOM(NAME) am_ ## NAME = driver_mk_atom(#NAME) + +#define LOAD_ATOM_CNT 2 +#define LOAD_ATOM(vec, i, atom) \ + (((vec)[(i)] = ERL_DRV_ATOM), \ + ((vec)[(i)+1] = (atom)), \ + ((i)+LOAD_ATOM_CNT)) + +#define LOAD_INT_CNT 2 +#define LOAD_INT(vec, i, val) \ + (((vec)[(i)] = ERL_DRV_INT), \ + ((vec)[(i)+1] = (ErlDrvTermData)(val)), \ + ((i)+LOAD_INT_CNT)) + +#define LOAD_INT64_CNT 2 +#define LOAD_INT64(vec, i, val) \ + (((vec)[(i)] = ERL_DRV_INT64), \ + ((vec)[(i)+1] = (ErlDrvTermData)(val)), \ + ((i)+LOAD_INT64_CNT)) + +#define LOAD_PORT_CNT 2 +#define LOAD_PORT(vec, i, port) \ + (((vec)[(i)] = ERL_DRV_PORT), \ + ((vec)[(i)+1] = (port)), \ + ((i)+LOAD_PORT_CNT)) + +#define LOAD_PID_CNT 2 +#define LOAD_PID(vec, i, pid) \ + (((vec)[(i)] = ERL_DRV_PID), \ + ((vec)[(i)+1] = (pid)), \ + ((i)+LOAD_PID_CNT)) + +#define LOAD_TUPLE_CNT 2 +#define LOAD_TUPLE(vec, i, size) \ + (((vec)[(i)] = ERL_DRV_TUPLE), \ + ((vec)[(i)+1] = (size)), \ + ((i)+LOAD_TUPLE_CNT)) + +/* send: +** {efile_reply, Pid, Port, {ok, int64()}} +*/ + +static int ef_send_ok_int64(file_descriptor *desc, ErlDrvTermData caller, + ErlDrvSInt64 *n) +{ + ErlDrvTermData spec[2*LOAD_ATOM_CNT + LOAD_PID_CNT + LOAD_PORT_CNT + + LOAD_INT64_CNT + 2*LOAD_TUPLE_CNT]; + int i = 0; + + i = LOAD_ATOM(spec, i, am_efile_reply); + i = LOAD_PID(spec, i, caller); + i = LOAD_PORT(spec, i, driver_mk_port(desc->port)); + i = LOAD_ATOM(spec, i, am_ok); + i = LOAD_INT64(spec, i, n); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + + return driver_send_term(desc->port, caller, spec, i); +} + +static ErlDrvTermData error_atom(int err) +{ + char errstr[256]; + char* s; + char* t; + + for (s = erl_errno_id(err), t = errstr; *s; s++, t++) + *t = tolower(*s); + *t = '\0'; + return driver_mk_atom(errstr); +} + +/* send: +** {efile_reply, Pid, Port, {error, posix_error()} +*/ + +static int ef_send_posix_error(file_descriptor *desc, ErlDrvTermData caller, + int e) +{ + ErlDrvTermData spec[3*LOAD_ATOM_CNT + LOAD_PID_CNT + LOAD_PORT_CNT + + 2*LOAD_TUPLE_CNT]; + int i = 0; + + i = LOAD_ATOM(spec, i, am_efile_reply); + i = LOAD_PID(spec, i, caller); + i = LOAD_PORT(spec, i, driver_mk_port(desc->port)); + i = LOAD_ATOM(spec, i, am_error); + /* TODO: safe? set of error codes should be limited and safe */ + i = LOAD_ATOM(spec, i, error_atom(e)); + i = LOAD_TUPLE(spec, i, 2); + i = LOAD_TUPLE(spec, i, 4); + ASSERT(i == sizeof(spec)/sizeof(*spec)); + + desc->caller = 0; + return driver_send_term(desc->port, caller, spec, i); +} /********************************************************************* * Driver entry point -> init @@ -628,6 +750,11 @@ file_init(void) ? atoi(buf) : 0); driver_system_info(&sys_info, sizeof(ErlDrvSysInfo)); + + INIT_ATOM(ok); + INIT_ATOM(error); + INIT_ATOM(efile_reply); + return 0; } @@ -1694,6 +1821,74 @@ static void invoke_fadvise(void *data) d->result_ok = efile_fadvise(&d->errInfo, fd, offset, length, advise); } + + +static void do_sendfile(file_descriptor *desc); +static void file_ready_output(ErlDrvData data, ErlDrvEvent event) +{ + file_descriptor* d = (file_descriptor*) data; + + switch (d->command) { + case FILE_SENDFILE: + driver_select(d->port, (ErlDrvEvent)d->sendfile.out_fd, + ERL_DRV_WRITE, 0); + do_sendfile(d); + break; + default: + break; + } +} + +static void file_stop_select(ErlDrvEvent event, void* _) +{ + /* TODO: close socket? */ +} + +static void invoke_sendfile(void *data) +{ + ((struct t_data *)data)->again = 0; +} + +static void do_sendfile(file_descriptor *d) +{ + int fd = d->fd; + int out_fd = d->sendfile.out_fd; + off_t offset = d->sendfile.offset; + size_t count = d->sendfile.count; + size_t chunksize = count < d->sendfile.chunksize + ? count : d->sendfile.chunksize; + int result_ok = 0; + Efile_error errInfo; + + result_ok = efile_sendfile(&errInfo, fd, out_fd, &offset, &chunksize); + + if (result_ok) { + d->sendfile.offset += chunksize; + d->sendfile.written += chunksize; + d->sendfile.count -= chunksize; + if (d->sendfile.count > 0) { + driver_select(d->port, (ErlDrvEvent)d->sendfile.out_fd, + ERL_DRV_USE|ERL_DRV_WRITE, 1); + } else { + printf("==> sendfile DONE eagain=%d\n", d->sendfile.eagain); + ef_send_ok_int64(d, d->caller, &d->sendfile.written); + } + } else if (errInfo.posix_errno == EAGAIN || errInfo.posix_errno == EINTR) { + if (chunksize > 0) { + d->sendfile.offset += chunksize; + d->sendfile.written += chunksize; + d->sendfile.count -= chunksize; + } + d->sendfile.eagain++; + + driver_select(d->port, (ErlDrvEvent)d->sendfile.out_fd, + ERL_DRV_USE|ERL_DRV_WRITE, 1); + } else { + printf("==> sendfile ERROR %s\n", erl_errno_id(errInfo.posix_errno)); + ef_send_posix_error(d, d->caller, errInfo.posix_errno); + } +} + static void free_readdir(void *data) { struct t_data *d = (struct t_data *) data; @@ -2105,6 +2300,13 @@ file_async_ready(ErlDrvData e, ErlDrvThreadData data) } free_preadv(data); break; + case FILE_SENDFILE: + /* Return 'ok' and let prim_file:sendfile wait for message */ + reply_ok(desc); + driver_select(desc->port, (ErlDrvEvent)desc->sendfile.out_fd, + ERL_DRV_USE|ERL_DRV_WRITE, 1); + free_data(data); + break; default: abort(); } @@ -2452,6 +2654,34 @@ 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; + desc->sendfile.out_fd = 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. + Maybe use '#if SIZEOF_SIZE_T == 4'? */ + desc->sendfile.offset = get_int64(((uchar*) buf) + + sizeof(Sint32)); + desc->sendfile.count = get_int64(((uchar*) buf) + + sizeof(Sint32) + + sizeof(Sint64)); + desc->sendfile.chunksize = get_int64(((uchar*) buf) + + sizeof(Sint32) + + 2*sizeof(Sint64)); + desc->sendfile.written = 0; + desc->sendfile.eagain = 0; + /* TODO: shouldn't d->command be enough? */ + desc->command = command; + desc->caller = driver_caller(desc->port); + goto done; + } + } /* diff --git a/erts/emulator/drivers/common/erl_efile.h b/erts/emulator/drivers/common/erl_efile.h index 3097ded3f1..3c6c2ec2db 100644 --- a/erts/emulator/drivers/common/erl_efile.h +++ b/erts/emulator/drivers/common/erl_efile.h @@ -162,3 +162,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); diff --git a/erts/emulator/drivers/unix/unix_efile.c b/erts/emulator/drivers/unix/unix_efile.c index 4b3934657c..5b001b3819 100644 --- a/erts/emulator/drivers/unix/unix_efile.c +++ b/erts/emulator/drivers/unix/unix_efile.c @@ -33,6 +33,9 @@ #include <sys/types.h> #include <sys/uio.h> #endif +#if defined(__linux__) || (defined(__sun) && defined(__SVR4)) +#include <sys/sendfile.h> +#endif #if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) #define DARWIN 1 @@ -1464,3 +1467,44 @@ 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) +{ +#if defined(__linux__) || (defined(__sun) && defined(__SVR4)) + ssize_t retval = sendfile(out_fd, in_fd, offset, *count); + if (retval >= 0) { + if (retval != *count) { + *count = retval; + retval = -1; + errno = EAGAIN; + } else { + *count = retval; + } + } else if (retval == -1 && (errno == EINTR || errno == EAGAIN)) { + *count = 0; + } + return check_error(retval == -1 ? -1 : 0, errInfo); +#elif defined(DARWIN) + off_t len = *count; + int retval = sendfile(in_fd, out_fd, *offset, &len, NULL, 0); + *count = len; + return check_error(retval, errInfo); +#elif defined(__FreeBSD__) || defined(__DragonFly__) + off_t len = 0; + int retval = sendfile(in_fd, out_fd, *offset, *count, NULL, &len, 0); + *count = len; + return check_error(retval, 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 diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c index 931bb196f1..0f41a09bf6 100644 --- a/erts/emulator/drivers/win32/win_efile.c +++ b/erts/emulator/drivers/win32/win_efile.c @@ -1581,3 +1581,27 @@ 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: write proper Windows TransmitFile based implementation */ + /* use overlapped I/O and driver_select on the structure? */ + /* int res = efile_seek(errInfo, in_fd, *offset, EFILE_SEEK_SET, NULL); */ + /* if (res) { */ + /* /\* TODO: could in_fd be shared and require protecting/locking */ + /* efile_seek/SetFilePointerEx? *\/ */ + /* if (TransmitFile((SOCKET) out_fd, (HANDLE) in_fd, *count, */ + /* 0, NULL, NULL, 0)) { */ + /* return check_error(0, errInfo); */ + /* } else { */ + /* /\* TODO: correct error handling? *\/ */ + /* return set_error(errInfo); */ + /* } */ + /* } else { */ + /* return res; */ + /* } */ + errno = ENOTSUP; + return check_error(-1, errInfo); +} diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index 30b7a5246a..f3f977a30b 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -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/5]). %% 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). @@ -539,6 +541,21 @@ write_file(_, _) -> {error, badarg}. +%% Returns {error, Reason} | {ok, BytesCopied} +sendfile(#file_descriptor{module = ?MODULE, data = {Port, _}}, + DestFD, Offset, Bytes, ChunkSize) -> + ok = drv_command(Port, <<?FILE_SENDFILE, DestFD:32, Offset:64, Bytes:64, + ChunkSize:64>>), + Self = self(), + %% Should we use a ref()? + receive + {efile_reply, Self, Port, {ok, _Written}=OKRes}-> + OKRes; + {efile_reply, Self, Port, {error, _PosixError}=Error}-> + Error; + Unexpected -> + Unexpected + end. %%%----------------------------------------------------------------- %%% Functions operating on files without handle to the file. ?DRV. 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}. |