Bugzilla bug 132208: implemented PR_SendFile on Linux with the sendfile NSPR_4_2_RC1
authorwtc%netscape.com
Wed, 20 Mar 2002 21:09:41 +0000
changeset 2306 c3cd1a3e66d3b2b89f738e2f228f8324bb60e9e1
parent 2305 2b71e5da1152030f0b1683041f43cbd03c821327
child 2307 879ed5860b962cff737d1f24f6e0ec770a2e20cd
push idunknown
push userunknown
push dateunknown
bugs132208
Bugzilla bug 132208: implemented PR_SendFile on Linux with the sendfile system call and the TCP_CORK socket option. r=jgmyers. Modified Files: _linux.h unix_errors.c ptio.c
pr/include/md/_linux.h
pr/src/md/unix/unix_errors.c
pr/src/pthreads/ptio.c
--- a/pr/include/md/_linux.h
+++ b/pr/include/md/_linux.h
@@ -465,9 +465,11 @@ extern PRIntervalTime _PR_UNIX_TicksPerS
 extern int __syscall_poll(struct pollfd *ufds, unsigned long int nfds,
 	int timeout);
 #define _MD_POLL __syscall_poll
 #endif
 
 /* For writev() */
 #include <sys/uio.h>
 
+extern void _MD_linux_map_sendfile_error(int err);
+
 #endif /* nspr_linux_defs_h___ */
--- a/pr/src/md/unix/unix_errors.c
+++ b/pr/src/md/unix/unix_errors.c
@@ -842,8 +842,15 @@ void _MD_hpux_map_sendfile_error(int err
 #endif /* HPUX11 */
 
 #ifdef SOLARIS
 void _MD_solaris_map_sendfile_error(int err)
 {
     _MD_unix_map_default_error(err) ;
 }
 #endif /* SOLARIS */
+
+#ifdef LINUX
+void _MD_linux_map_sendfile_error(int err)
+{
+    _MD_unix_map_default_error(err) ;
+}
+#endif /* LINUX */
--- a/pr/src/pthreads/ptio.c
+++ b/pr/src/pthreads/ptio.c
@@ -168,21 +168,31 @@ struct sf_parms {
  */
 static ssize_t (*pt_aix_sendfile_fptr)() = NULL;
 
 #define AIX_SEND_FILE(a, b, c) (*pt_aix_sendfile_fptr)(a, b, c)
 
 #endif /* HAVE_SEND_FILE */
 #endif /* AIX */
 
+#ifdef LINUX
+#include <sys/sendfile.h>
+#endif
+
 #include "primpl.h"
 
 /* On Alpha Linux, these are already defined in sys/socket.h */
 #if !(defined(LINUX) && defined(__alpha))
 #include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
+#ifdef LINUX
+/* TCP_CORK is not defined in <netinet/tcp.h> on Red Hat Linux 6.0 */
+#ifndef TCP_CORK
+#define TCP_CORK 3
+#endif
+#endif
 #endif
 
 #if defined(SOLARIS)
 #define _PRSockOptVal_t char *
 #elif defined(IRIX) || defined(OSF1) || defined(AIX) || defined(HPUX) \
     || defined(LINUX) || defined(FREEBSD) || defined(BSDI) || defined(VMS) \
     || defined(NTO) || defined(OPENBSD) || defined(DARWIN) \
     || defined(UNIXWARE)
@@ -333,16 +343,25 @@ struct pt_Continuation
 #endif  /* HPUX11 */
     
 #ifdef SOLARIS
     /*
      * For sendfilev()
      */
     int nbytes_to_send;                     /* size of header and file */
 #endif  /* SOLARIS */
+
+#ifdef LINUX
+    /*
+     * For sendfile()
+     */
+    int in_fd;                              /* descriptor of file to send */
+    off_t offset;
+    size_t count;
+#endif  /* LINUX */
  
     PRIntervalTime timeout;                 /* client (relative) timeout */
 
     PRInt16 event;                           /* flags for poll()'s events */
 
     /*
     ** The representation and notification of the results of the operation.
     ** These function can either return an int return code or a pointer to
@@ -1054,16 +1073,43 @@ static PRBool pt_solaris_sendfile_cont(p
 
         return PR_FALSE;
     }
 
     return PR_TRUE;
 }
 #endif  /* SOLARIS */
 
+#ifdef LINUX 
+static PRBool pt_linux_sendfile_cont(pt_Continuation *op, PRInt16 revents)
+{
+    ssize_t rv;
+    off_t oldoffset;
+
+    oldoffset = op->offset;
+    rv = sendfile(op->arg1.osfd, op->in_fd, &op->offset, op->count);
+    op->syserrno = errno;
+
+    if (rv == -1) {
+        if (op->syserrno != EWOULDBLOCK && op->syserrno != EAGAIN) {
+            op->result.code = -1;
+            return PR_TRUE;
+        }
+        rv = 0;
+    }
+    PR_ASSERT(rv == op->offset - oldoffset);
+    op->result.code += rv;
+    if (rv < op->count) {
+        op->count -= rv;
+        return PR_FALSE;
+    }
+    return PR_TRUE;
+}
+#endif  /* LINUX */
+
 void _PR_InitIO(void)
 {
 #if defined(DEBUG)
     memset(&pt_debug, 0, sizeof(PTDebug));
     pt_debug.timeStarted = PR_Now();
 #endif
 
     _pr_flock_lock = PR_NewLock();
@@ -2422,16 +2468,136 @@ static PRInt32 pt_SolarisDispatchSendFil
     } else {
         return PR_EmulateSendFile(sd, sfd, flags, timeout);
     }
 }
 #endif /* !HAVE_SENDFILEV */
 
 #endif  /* SOLARIS */
 
+#ifdef LINUX
+/*
+ * pt_LinuxSendFile
+ *
+ *    Send file sfd->fd across socket sd. If specified, header and trailer
+ *    buffers are sent before and after the file, respectively.
+ *
+ *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
+ *    
+ *    return number of bytes sent or -1 on error
+ *
+ *      This implementation takes advantage of the sendfile() system
+ *      call available in Linux kernel 2.2 or higher.
+ */
+
+static PRInt32 pt_LinuxSendFile(PRFileDesc *sd, PRSendFileData *sfd,
+                PRTransmitFileFlags flags, PRIntervalTime timeout)
+{
+    struct stat statbuf;
+    size_t file_nbytes_to_send;	
+    PRInt32 count = 0;
+    ssize_t rv;
+    int syserrno;
+    off_t offset;
+    PRBool tcp_cork_enabled = PR_FALSE;
+    int tcp_cork;
+
+    if (sfd->file_nbytes == 0) {
+        /* Get file size */
+        if (fstat(sfd->fd->secret->md.osfd, &statbuf) == -1) {
+            _PR_MD_MAP_FSTAT_ERROR(errno);
+            return -1;
+        } 		
+        file_nbytes_to_send = statbuf.st_size - sfd->file_offset;
+    } else {
+        file_nbytes_to_send = sfd->file_nbytes;
+    }
+
+    if (sfd->hlen != 0 || sfd->tlen != 0) {
+        tcp_cork = 1;
+        if (setsockopt(sd->secret->md.osfd, SOL_TCP, TCP_CORK,
+                &tcp_cork, sizeof tcp_cork) == -1) {
+            _PR_MD_MAP_SETSOCKOPT_ERROR(errno);
+            return -1;
+        }
+        tcp_cork_enabled = PR_TRUE;
+    }
+
+    if (sfd->hlen != 0) {
+        count = PR_Send(sd, sfd->header, sfd->hlen, 0, timeout);
+        if (count == -1) {
+            goto failed;
+        }
+    }
+
+    if (file_nbytes_to_send != 0) {
+        offset = sfd->file_offset;
+        do {
+            rv = sendfile(sd->secret->md.osfd, sfd->fd->secret->md.osfd,
+                &offset, file_nbytes_to_send);
+        } while (rv == -1 && (syserrno = errno) == EINTR);
+        if (rv == -1) {
+            if (syserrno != EAGAIN && syserrno != EWOULDBLOCK) {
+                _MD_linux_map_sendfile_error(syserrno);
+                count = -1;
+                goto failed;
+            }
+            rv = 0;
+        }
+        PR_ASSERT(rv == offset - sfd->file_offset);
+        count += rv;
+
+        if (rv < file_nbytes_to_send) {
+            pt_Continuation op;
+
+            op.arg1.osfd = sd->secret->md.osfd;
+            op.in_fd = sfd->fd->secret->md.osfd;
+            op.offset = offset;
+            op.count = file_nbytes_to_send - rv;
+            op.result.code = count;
+            op.timeout = timeout;
+            op.function = pt_linux_sendfile_cont;
+            op.event = POLLOUT | POLLPRI;
+            count = pt_Continue(&op);
+            syserrno = op.syserrno;
+            if (count == -1) {
+                _MD_linux_map_sendfile_error(syserrno);
+                goto failed;
+            }
+        }
+    }
+
+    if (sfd->tlen != 0) {
+        rv = PR_Send(sd, sfd->trailer, sfd->tlen, 0, timeout);
+        if (rv == -1) {
+            count = -1;
+            goto failed;
+        }
+        count += rv;
+    }
+
+failed:
+    if (tcp_cork_enabled) {
+        tcp_cork = 0;
+        if (setsockopt(sd->secret->md.osfd, SOL_TCP, TCP_CORK,
+                &tcp_cork, sizeof tcp_cork) == -1 && count != -1) {
+            _PR_MD_MAP_SETSOCKOPT_ERROR(errno);
+            count = -1;
+        }
+    }
+    if (count != -1) {
+        if (flags & PR_TRANSMITFILE_CLOSE_SOCKET) {
+            PR_Close(sd);
+        }
+        PR_ASSERT(count == sfd->hlen + sfd->tlen + file_nbytes_to_send);
+    }
+    return count;
+}
+#endif  /* LINUX */
+
 #ifdef AIX
 extern	int _pr_aix_send_file_use_disabled;
 #endif
 
 static PRInt32 pt_SendFile(
     PRFileDesc *sd, PRSendFileData *sfd,
     PRTransmitFileFlags flags, PRIntervalTime timeout)
 {
@@ -2462,16 +2628,18 @@ static PRInt32 pt_SendFile(
     /* return(pt_AIXDispatchSendFile(sd, sfd, flags, timeout));*/
 #endif /* HAVE_SEND_FILE */
 #elif defined(SOLARIS)
 #ifdef HAVE_SENDFILEV
     	return(pt_SolarisSendFile(sd, sfd, flags, timeout));
 #else
 	return(pt_SolarisDispatchSendFile(sd, sfd, flags, timeout));
 #endif /* HAVE_SENDFILEV */
+#elif defined(LINUX)
+    	return(pt_LinuxSendFile(sd, sfd, flags, timeout));
 #else
 	return(PR_EmulateSendFile(sd, sfd, flags, timeout));
 #endif
 }
 
 static PRInt32 pt_TransmitFile(
     PRFileDesc *sd, PRFileDesc *fd, const void *headers,
     PRInt32 hlen, PRTransmitFileFlags flags, PRIntervalTime timeout)