1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-20 19:50:24 +02:00

Change `sendfile' to loop until everything has been sent.

* libguile/filesys.c (scm_sendfile)[HAVE_SYS_SENDFILE_H &&
  HAVE_SENDFILE]: Compare RESULT with C_COUNT.  Loop until C_COUNT bytes
  have been sent.
* doc/ref/posix.texi (File System): Update the description.  Explain the
  new semantics.
* test-suite/tests/filesys.test ("sendfile"): Rewrite using
  `pass-if-equal'.  Check the return value for all the tests.
  ["file with offset past the end", "file with offset near the end"]:
  New tests.
This commit is contained in:
Ludovic Courtès 2013-04-07 23:43:21 +02:00
parent 254d313a21
commit e0886e0780
3 changed files with 111 additions and 71 deletions

View file

@ -806,9 +806,10 @@ The return value is unspecified.
@deffn {Scheme Procedure} sendfile out in count [offset] @deffn {Scheme Procedure} sendfile out in count [offset]
@deffnx {C Function} scm_sendfile (out, in, count, offset) @deffnx {C Function} scm_sendfile (out, in, count, offset)
Send @var{count} bytes from @var{in} to @var{out}, both of which Send @var{count} bytes from @var{in} to @var{out}, both of which
are either open file ports or file descriptors. When must be either open file ports or file descriptors. When
@var{offset} is omitted, start reading from @var{in}'s current @var{offset} is omitted, start reading from @var{in}'s current
position; otherwise, start reading at @var{offset}. position; otherwise, start reading at @var{offset}. Return
the number of bytes actually sent.
When @var{in} is a port, it is often preferable to specify @var{offset}, When @var{in} is a port, it is often preferable to specify @var{offset},
because @var{in}'s offset as a port may be different from the offset of because @var{in}'s offset as a port may be different from the offset of
@ -824,6 +825,12 @@ In some cases, the @code{sendfile} libc function may return
@code{EINVAL} or @code{ENOSYS}. In that case, Guile's @code{sendfile} @code{EINVAL} or @code{ENOSYS}. In that case, Guile's @code{sendfile}
procedure automatically falls back to doing a series of @code{read} and procedure automatically falls back to doing a series of @code{read} and
@code{write} calls. @code{write} calls.
In other cases, the libc function may send fewer bytes than
@var{count}---for instance because @var{out} is a slow or limited
device, such as a pipe. When that happens, Guile's @code{sendfile}
automatically retries until exactly @var{count} bytes were sent or an
error occurs.
@end deffn @end deffn
@findex rename @findex rename

View file

@ -1111,9 +1111,10 @@ SCM_DEFINE (scm_copy_file, "copy-file", 2, 0, 0,
SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0, SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
(SCM out, SCM in, SCM count, SCM offset), (SCM out, SCM in, SCM count, SCM offset),
"Send @var{count} bytes from @var{in} to @var{out}, both of which " "Send @var{count} bytes from @var{in} to @var{out}, both of which "
"are either open file ports or file descriptors. When " "must be either open file ports or file descriptors. When "
"@var{offset} is omitted, start reading from @var{in}'s current " "@var{offset} is omitted, start reading from @var{in}'s current "
"position; otherwise, start reading at @var{offset}.") "position; otherwise, start reading at @var{offset}. Return "
"the number of bytes actually sent.")
#define FUNC_NAME s_scm_sendfile #define FUNC_NAME s_scm_sendfile
{ {
#define VALIDATE_FD_OR_PORT(cvar, svar, pos) \ #define VALIDATE_FD_OR_PORT(cvar, svar, pos) \
@ -1126,9 +1127,9 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
cvar = SCM_FPORT_FDES (svar); \ cvar = SCM_FPORT_FDES (svar); \
} }
size_t c_count; ssize_t result SCM_UNUSED;
size_t c_count, total = 0;
scm_t_off c_offset; scm_t_off c_offset;
ssize_t result;
int in_fd, out_fd; int in_fd, out_fd;
VALIDATE_FD_OR_PORT (out_fd, out, 1); VALIDATE_FD_OR_PORT (out_fd, out, 1);
@ -1139,9 +1140,30 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE
/* The Linux-style sendfile(2), which is different from the BSD-style. */ /* The Linux-style sendfile(2), which is different from the BSD-style. */
result = sendfile_or_sendfile64 (out_fd, in_fd, {
SCM_UNBNDP (offset) ? NULL : &c_offset, off_t *offset_ptr;
c_count);
offset_ptr = SCM_UNBNDP (offset) ? NULL : &c_offset;
/* On Linux, when OUT_FD is a file, everything is transferred at once and
RESULT == C_COUNT. However, when OUT_FD is a pipe or other "slow"
device, fewer bytes may be transferred, hence the loop. RESULT == 0
means EOF on IN_FD, so leave the loop in that case. */
do
{
result = sendfile_or_sendfile64 (out_fd, in_fd, offset_ptr,
c_count - total);
if (result > 0)
/* At this point, either OFFSET_PTR is non-NULL and it has been
updated to the current offset in IN_FD, or it is NULL and IN_FD's
offset has been updated. */
total += result;
else if (result < 0 && (errno == EINTR || errno == EAGAIN))
/* Keep going. */
result = 0;
}
while (total < c_count && result > 0);
}
/* Quoting the Linux man page: "In Linux kernels before 2.6.33, out_fd /* Quoting the Linux man page: "In Linux kernels before 2.6.33, out_fd
must refer to a socket. Since Linux 2.6.33 it can be any file." must refer to a socket. Since Linux 2.6.33 it can be any file."
@ -1152,12 +1174,12 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
#endif #endif
{ {
char buf[8192]; char buf[8192];
size_t result, left; size_t left;
if (!SCM_UNBNDP (offset)) if (!SCM_UNBNDP (offset))
{ {
if (SCM_PORTP (in)) if (SCM_PORTP (in))
scm_seek (in, offset, scm_from_int (SEEK_SET)); scm_seek (in, scm_from_off_t (c_offset), scm_from_int (SEEK_SET));
else else
{ {
if (lseek_or_lseek64 (in_fd, c_offset, SEEK_SET) < 0) if (lseek_or_lseek64 (in_fd, c_offset, SEEK_SET) < 0)
@ -1165,7 +1187,7 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
} }
} }
for (result = 0, left = c_count; result < c_count; ) for (total = 0, left = c_count; total < c_count; )
{ {
size_t asked, obtained; size_t asked, obtained;
@ -1180,13 +1202,12 @@ SCM_DEFINE (scm_sendfile, "sendfile", 3, 1, 0,
if (obtained < asked) if (obtained < asked)
SCM_SYSERROR; SCM_SYSERROR;
result += obtained; total += obtained;
} }
return scm_from_size_t (result);
} }
return scm_from_ssize_t (result); return scm_from_size_t (total);
#undef VALIDATE_FD_OR_PORT #undef VALIDATE_FD_OR_PORT
} }

View file

@ -130,70 +130,82 @@
(with-test-prefix "sendfile" (with-test-prefix "sendfile"
(pass-if "file" (let* ((file (search-path %load-path "ice-9/boot-9.scm"))
(let ((file (search-path %load-path "ice-9/boot-9.scm"))) (len (stat:size (stat file)))
(call-with-input-file file (ref (call-with-input-file file get-bytevector-all)))
(lambda (input)
(let ((len (stat:size (stat input))))
(call-with-output-file (test-file)
(lambda (output)
(sendfile output input len 0))))))
(let ((ref (call-with-input-file file get-bytevector-all))
(out (call-with-input-file (test-file) get-bytevector-all)))
(bytevector=? ref out))))
(pass-if "file with offset" (pass-if-equal "file" (cons len ref)
(let ((file (search-path %load-path "ice-9/boot-9.scm"))) (cons (call-with-input-file file
(call-with-input-file file
(lambda (input) (lambda (input)
(let ((len (stat:size (stat input))))
(call-with-output-file (test-file) (call-with-output-file (test-file)
(lambda (output) (lambda (output)
(sendfile output input (- len 777) 777)))))) (sendfile output input len 0)))))
(let ((ref (call-with-input-file file (call-with-input-file (test-file) get-bytevector-all)))
(pass-if-equal "file with offset"
(cons (- len 777) (call-with-input-file file
(lambda (input) (lambda (input)
(seek input 777 SEEK_SET) (seek input 777 SEEK_SET)
(get-bytevector-all input)))) (get-bytevector-all input))))
(out (call-with-input-file (test-file) get-bytevector-all))) (cons (call-with-input-file file
(bytevector=? ref out)))) (lambda (input)
(call-with-output-file (test-file)
(lambda (output)
(sendfile output input (- len 777) 777)))))
(call-with-input-file (test-file) get-bytevector-all)))
(pass-if "pipe" (pass-if-equal "file with offset past the end" (- len 777)
(call-with-input-file file
(lambda (input)
(call-with-output-file (test-file)
(lambda (output)
(sendfile output input len 777))))))
(pass-if-equal "file with offset near the end" 77
(call-with-input-file file
(lambda (input)
(call-with-output-file (test-file)
(lambda (output)
(sendfile output input len (- len 77)))))))
(pass-if-equal "pipe" (cons len ref)
(if (provided? 'threads) (if (provided? 'threads)
(let* ((file (search-path %load-path "ice-9/boot-9.scm")) (let* ((in+out (pipe))
(in+out (pipe))
(child (call-with-new-thread (child (call-with-new-thread
(lambda () (lambda ()
(call-with-input-file file (call-with-input-file file
(lambda (input) (lambda (input)
(let ((len (stat:size (stat input)))) (let ((result (sendfile (cdr in+out)
(sendfile (cdr in+out) (fileno input) len 0) (fileno input)
(close-port (cdr in+out))))))))) len 0)))
(let ((ref (call-with-input-file file get-bytevector-all)) (close-port (cdr in+out))
(out (get-bytevector-all (car in+out)))) result)))))))
(let ((out (get-bytevector-all (car in+out))))
(close-port (car in+out)) (close-port (car in+out))
(bytevector=? ref out))) (cons (join-thread child) out)))
(throw 'unresolved))) (throw 'unresolved)))
(pass-if "pipe with offset" (pass-if-equal "pipe with offset"
(cons (- len 777) (call-with-input-file file
(lambda (input)
(seek input 777 SEEK_SET)
(get-bytevector-all input))))
(if (provided? 'threads) (if (provided? 'threads)
(let* ((file (search-path %load-path "ice-9/boot-9.scm")) (let* ((in+out (pipe))
(in+out (pipe))
(child (call-with-new-thread (child (call-with-new-thread
(lambda () (lambda ()
(call-with-input-file file (call-with-input-file file
(lambda (input) (lambda (input)
(let ((len (stat:size (stat input)))) (let ((result (sendfile (cdr in+out)
(sendfile (cdr in+out) (fileno input) (fileno input)
(- len 777) 777) (- len 777)
(close-port (cdr in+out))))))))) 777)))
(let ((ref (call-with-input-file file (close-port (cdr in+out))
(lambda (input) result)))))))
(seek input 777 SEEK_SET) (let ((out (get-bytevector-all (car in+out))))
(get-bytevector-all input))))
(out (get-bytevector-all (car in+out))))
(close-port (car in+out)) (close-port (car in+out))
(bytevector=? ref out))) (cons (join-thread child) out)))
(throw 'unresolved)))) (throw 'unresolved)))))
(delete-file (test-file)) (delete-file (test-file))
(delete-file (test-symlink)) (delete-file (test-symlink))