s3 OneFS: Add recvfile implementation
authorTim Prouty <tprouty@samba.org>
Sat, 14 Feb 2009 05:36:42 +0000 (21:36 -0800)
committerTim Prouty <tprouty@samba.org>
Sat, 14 Feb 2009 05:36:42 +0000 (21:36 -0800)
source3/modules/onefs.h
source3/modules/onefs_system.c
source3/modules/vfs_onefs.c

index 57194960fa35104732eafaa0f974ba496114317b..126b75628ae1d5737be0a17bc6e30365bad3573a 100644 (file)
@@ -234,4 +234,7 @@ int onefs_sys_create_file(connection_struct *conn,
                          uint32_t ntfs_flags,
                          int *granted_oplock);
 
+ssize_t onefs_sys_recvfile(int fromfd, int tofd, SMB_OFF_T offset,
+                          size_t count);
+
 #endif /* _ONEFS_H */
index acc38fba30710340357882fdc689140a79bb1f33..b17cfe9b11ee76f47066bdcbfaa5ee7d0030f788 100644 (file)
@@ -163,3 +163,184 @@ int onefs_sys_create_file(connection_struct *conn,
 
        return ret_fd;
 }
+
+/**
+ * Only talloc the spill buffer once (reallocing when necessary).
+ */
+static char *get_spill_buffer(size_t new_count)
+{
+       static int cur_count = 0;
+       static char *spill_buffer = NULL;
+
+       /* If a sufficiently sized buffer exists, just return. */
+       if (new_count <= cur_count) {
+               SMB_ASSERT(spill_buffer);
+               return spill_buffer;
+       }
+
+       /* Allocate the first time. */
+       if (cur_count == 0) {
+               SMB_ASSERT(!spill_buffer);
+               spill_buffer = talloc_array(NULL, char, new_count);
+               if (spill_buffer) {
+                       cur_count = new_count;
+               }
+               return spill_buffer;
+       }
+
+       /* A buffer exists, but it's not big enough, so realloc. */
+       SMB_ASSERT(spill_buffer);
+       spill_buffer = talloc_realloc(NULL, spill_buffer, char, new_count);
+       if (spill_buffer) {
+               cur_count = new_count;
+       }
+       return spill_buffer;
+}
+
+/**
+ * recvfile does zero-copy writes given an fd to write to, and a socket with
+ * some data to write.  If recvfile read more than it was able to write, it
+ * spills the data into a buffer.  After first reading any additional data
+ * from the socket into the buffer, the spill buffer is then written with a
+ * standard pwrite.
+ */
+ssize_t onefs_sys_recvfile(int fromfd, int tofd, SMB_OFF_T offset,
+                          size_t count)
+{
+       char *spill_buffer = NULL;
+       bool socket_drained = false;
+       int ret;
+       off_t total_rbytes = 0;
+       off_t total_wbytes = 0;
+       off_t rbytes;
+       off_t wbytes;
+
+       DEBUG(10,("onefs_recvfile: from = %d, to = %d, offset=%llu, count = "
+                 "%lu\n", fromfd, tofd, offset, count));
+
+       if (count == 0) {
+               return 0;
+       }
+
+       /*
+        * Setup up a buffer for recvfile to spill data that has been read
+        * from the socket but not written.
+        */
+       spill_buffer = get_spill_buffer(count);
+       if (spill_buffer == NULL) {
+               ret = -1;
+               goto out;
+       }
+
+       /*
+        * Keep trying recvfile until:
+        *  - There is no data left to read on the socket, or
+        *  - bytes read != bytes written, or
+        *  - An error is returned that isn't EINTR/EAGAIN
+        */
+       do {
+               /* Keep track of bytes read/written for recvfile */
+               rbytes = 0;
+               wbytes = 0;
+
+               DEBUG(10, ("calling recvfile loop, offset + total_wbytes = "
+                          "%llu, count - total_rbytes = %llu\n",
+                          offset + total_wbytes, count - total_rbytes));
+
+               ret = recvfile(tofd, fromfd, offset + total_wbytes,
+                              count - total_wbytes, &rbytes, &wbytes, 0,
+                              spill_buffer);
+
+               DEBUG(10, ("recvfile ret = %d, errno = %d, rbytes = %llu, "
+                          "wbytes = %llu\n", ret, ret >= 0 ? 0 : errno,
+                          rbytes, wbytes));
+
+               /* Update our progress so far */
+               total_rbytes += rbytes;
+               total_wbytes += wbytes;
+
+       } while ((count - total_rbytes) && (rbytes == wbytes) &&
+                (ret == -1 && (errno == EINTR || errno == EAGAIN)));
+
+       DEBUG(10, ("total_rbytes = %llu, total_wbytes = %llu\n",
+                  total_rbytes, total_wbytes));
+
+       /* Log if recvfile didn't write everything it read. */
+       if (total_rbytes != total_wbytes) {
+               DEBUG(0, ("partial recvfile: total_rbytes=%llu but "
+                         "total_wbytes=%llu, diff = %llu\n", total_rbytes,
+                         total_wbytes, total_rbytes - total_wbytes));
+               SMB_ASSERT(total_rbytes > total_wbytes);
+       }
+
+       /*
+        * If there is still data on the socket, read it off.
+        */
+       while (total_rbytes < count) {
+
+               DEBUG(0, ("shallow recvfile, reading %llu\n",
+                         count - total_rbytes));
+
+               /*
+                * Read the remaining data into the spill buffer.  recvfile
+                * may already have some data in the spill buffer, so start
+                * filling the buffer at total_rbytes - total_wbytes.
+                */
+               ret = sys_read(fromfd,
+                              spill_buffer + (total_rbytes - total_wbytes),
+                              count - total_rbytes);
+
+               if (ret == -1) {
+                       DEBUG(0, ("shallow recvfile read failed: %s\n",
+                                 strerror(errno)));
+                       /* Socket is dead, so treat as if it were drained. */
+                       socket_drained = true;
+                       goto out;
+               }
+
+               /* Data was read so update the rbytes */
+               total_rbytes += ret;
+       }
+
+       if (total_rbytes != count) {
+               smb_panic("Unread recvfile data still on the socket!");
+       }
+
+       /*
+        * Now write any spilled data + the extra data read off the socket.
+        */
+       while (total_wbytes < count) {
+
+               DEBUG(0, ("partial recvfile, writing %llu\n", count - total_wbytes));
+
+               ret = sys_pwrite(tofd, spill_buffer, count - total_wbytes,
+                                offset + total_wbytes);
+
+               if (ret == -1) {
+                       DEBUG(0, ("partial recvfile write failed: %s\n",
+                                 strerror(errno)));
+                       goto out;
+               }
+
+               /* Data was written so update the wbytes */
+               total_wbytes += ret;
+       }
+
+       /* Success! */
+       ret = total_wbytes;
+
+out:
+       /* Make sure we always try to drain the socket. */
+       if (!socket_drained && count - total_rbytes) {
+               int saved_errno = errno;
+
+               if (drain_socket(fromfd, count - total_rbytes) !=
+                   count - total_rbytes) {
+                       /* Socket is dead! */
+                       DEBUG(0, ("drain socket failed: %d\n", errno));
+               }
+               errno = saved_errno;
+       }
+
+       return ret;
+}
index 123139f729a6b1a0c47a4807e6a419425c1b4336..fe0dfc9ea0025b138fe8ae25c4077e8d329d0c91 100644 (file)
@@ -153,6 +153,18 @@ static int onefs_open(vfs_handle_struct *handle, const char *fname,
        return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
 }
 
+static ssize_t onefs_recvfile(vfs_handle_struct *handle, int fromfd,
+                             files_struct *tofsp, SMB_OFF_T offset,
+                             size_t count)
+{
+       ssize_t result;
+
+       START_PROFILE_BYTES(syscall_recvfile, count);
+       result = onefs_sys_recvfile(fromfd, tofsp->fh->fd, offset, count);
+       END_PROFILE(syscall_recvfile);
+       return result;
+}
+
 static uint64_t onefs_get_alloc_size(struct vfs_handle_struct *handle,
                                     files_struct *fsp,
                                     const SMB_STRUCT_STAT *sbuf)
@@ -309,6 +321,8 @@ static vfs_op_tuple onefs_ops[] = {
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_close), SMB_VFS_OP_CLOSE,
         SMB_VFS_LAYER_TRANSPARENT},
+       {SMB_VFS_OP(onefs_recvfile), SMB_VFS_OP_RECVFILE,
+        SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_rename), SMB_VFS_OP_RENAME,
         SMB_VFS_LAYER_TRANSPARENT},
        {SMB_VFS_OP(onefs_stat), SMB_VFS_OP_STAT,