apr-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Samuelson <pe...@p12n.org>
Subject [PATCH] Linux 2.4 may or may not implement sendfile64()
Date Fri, 05 Jan 2007 11:19:44 GMT

apr incorrectly detects the sendfile64() syscall on Linux 2.4 on
non-i386 architectures.  The problem is that on Linux, libc is not
tightly coupled to your installed kernel, so libc exposes syscalls
which may or may not be implemented kernel-side, requiring runtime
detection of errno==ENOSYS.  I wrote this patch in response to
http://bugs.debian.org/396631 - the symptom is that Apache sends empty
files without realising that anything is wrong.  Multiple Debian users
(not me) have tested the patch, and it will be in Debian 4.0.

  (sendfile64 actually does exist on Linux 2.4, but unfortunately
  nobody bothered to add the entry points to the syscall tables on most
  non-i386 architectures!)

Unlike the epoll case [another situation where runtime detection might
be used], disabling sendfile64 where it actually is available would
sacrifice real functionality: ability to use apr_socket_sendfile for
large files.  So I think the case for runtime detection is stronger
here than for epoll.

Peter

--- network_io/unix/sendrecv.c
+++ network_io/unix/sendrecv.c
@@ -240,31 +240,77 @@
 
 #if defined(__linux__) && defined(HAVE_WRITEV)
 
+/* Helper function for apr_socket_sendfile.
+ * Takes care of sendfile vs. sendfile64 (must be detected at runtime),
+ * EINTR restarting, and other details.  NOTE: does not necessarily
+ * update 'off', as callers don't need this.
+ */
+static
+ssize_t do_sendfile(int out, int in, apr_off_t *off, apr_size_t len)
+{
+#if !APR_HAS_LARGE_FILES
+    ssize_t ret;
+    do
+	ret = sendfile(out, in, off, len);
+    while (ret == -1 && errno == EINTR);
+    return ret;
+#else
+
+#ifdef HAVE_SENDFILE64
+    static int sendfile64_enosys;  /* sendfile64() syscall not found */
+#endif
+    off_t offtmp;
+    ssize_t ret;
+
+    /* Multiple reports have shown sendfile failing with EINVAL if
+     * passed a >=2Gb count value on some 64-bit kernels.  It won't
+     * noticably hurt performance to limit each call to <2Gb at a time,
+     * so avoid that issue here.  (Round down to a common page size.) */
+    if (sizeof(off_t) == 8 && len > INT_MAX)
+        len = INT_MAX - 8191;
+
+    /* The simple and common case: we don't cross the LFS barrier */
+    if (sizeof(off_t) == 8 || (apr_int64_t)*off + len <= INT_MAX) {
+        offtmp = *off;
+        do
+            ret = sendfile(out, in, &offtmp, len);
+        while (ret == -1 && errno == EINTR);
+        return ret;
+    }
+
+    /* From here down we know it's a 32-bit runtime */
+#ifdef HAVE_SENDFILE64
+    if (!sendfile64_enosys) {
+        do
+            ret = sendfile64(out, in, off, len);
+        while (ret == -1 && errno == EINTR);
+
+        if (ret != -1 || errno != ENOSYS)
+            return ret;
+
+        sendfile64_enosys = 1;
+    }
+#endif
+    if (*off > INT_MAX) {
+        errno = EINVAL;
+        return -1;
+    }
+    offtmp = *off;
+    do
+	ret = sendfile(out, in, &offtmp, len);
+    while (ret == -1 && errno == EINTR);
+    return ret;
+#endif /* APR_HAS_LARGE_FILES */
+}
+
+
 apr_status_t apr_socket_sendfile(apr_socket_t *sock, apr_file_t *file,
                                  apr_hdtr_t *hdtr, apr_off_t *offset,
                                  apr_size_t *len, apr_int32_t flags)
 {
     int rv, nbytes = 0, total_hdrbytes, i;
     apr_status_t arv;
-
-#if APR_HAS_LARGE_FILES && defined(HAVE_SENDFILE64)
     apr_off_t off = *offset;
-#define sendfile sendfile64
-
-#elif APR_HAS_LARGE_FILES && SIZEOF_OFF_T == 4
-    /* 64-bit apr_off_t but no sendfile64(): fail if trying to send
-     * past the 2Gb limit. */
-    off_t off;
-    
-    if ((apr_int64_t)*offset + *len > INT_MAX) {
-        return EINVAL;
-    }
-    
-    off = *offset;
-
-#else
-    off_t off = *offset;
-#endif
 
     if (!hdtr) {
         hdtr = &no_hdtr;
@@ -310,12 +356,10 @@
         goto do_select;
     }
 
-    do {
-        rv = sendfile(sock->socketdes,    /* socket */
+    rv = do_sendfile(sock->socketdes,    /* socket */
                       file->filedes, /* open file descriptor of the file to be sent */
                       &off,    /* where in the file to start */
                       *len);   /* number of bytes to send */
-    } while (rv == -1 && errno == EINTR);
 
     while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK) 
                       && (sock->timeout > 0)) {
@@ -326,12 +370,10 @@
             return arv;
         }
         else {
-            do {
-                rv = sendfile(sock->socketdes,    /* socket */
+	    rv = do_sendfile(sock->socketdes,    /* socket */
                               file->filedes, /* open file descriptor of the file to be
sent */
                               &off,    /* where in the file to start */
                               *len);    /* number of bytes to send */
-            } while (rv == -1 && errno == EINTR);
         }
     }
 

Mime
View raw message