Initial recvfile VFS change

Jeremy Allison jra at samba.org
Tue Oct 30 23:31:12 GMT 2007


Ok, here is the patchset to add recvfile, including a
userspace implementation.

To turn it on add :

min receivefile size = 8000

(or equivalent). SMBwriteX calls greater
than this will be processed by the recvfile
code.

I'm trying to get an implementation using
splice() up and running on Linux, but
my system header files are f&%ked on
this development box....

Jeremy.
-------------- next part --------------
diff --git a/examples/VFS/skel_opaque.c b/examples/VFS/skel_opaque.c
index 244c02e..fd4b206 100644
--- a/examples/VFS/skel_opaque.c
+++ b/examples/VFS/skel_opaque.c
@@ -155,6 +155,17 @@ static SMB_OFF_T skel_lseek(vfs_handle_struct *handle, files_struct *fsp, int fi
 	return vfswrap_lseek(NULL, fsp, filedes, offset, whence);
 }
 
+static ssize_t skel_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fsp, int fromfd, const DATA_BLOB *hdr,
+		                        SMB_OFF_T offset, size_t n)
+{
+	return vfswrap_sendfile(NULL, tofd, fsp, fromfd, hdr, offset, n);
+}
+
+static ssize_t skel_recvfile(vfs_handle_struct *handle, int fromfd, files_struct *fsp, int tofd, SMB_OFF_T offset, size_t n)
+{
+	return vfswrap_recvfile(NULL, fromfd, fsp, tofd, offset, n);
+}
+
 static int skel_rename(vfs_handle_struct *handle,  const char *oldname, const char *newname)
 {
 	return vfswrap_rename(NULL,  oldname, newname);
@@ -603,6 +614,8 @@ static vfs_op_tuple skel_op_tuples[] = {
 	{SMB_VFS_OP(skel_write),			SMB_VFS_OP_WRITE,		SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(skel_pwrite),			SMB_VFS_OP_PWRITE,		SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(skel_lseek),			SMB_VFS_OP_LSEEK,		SMB_VFS_LAYER_OPAQUE},
+	{SMB_VFS_OP(skel_sendfile),			SMB_VFS_OP_SENDFILE,		SMB_VFS_LAYER_OPAQUE},
+	{SMB_VFS_OP(skel_recvfile),			SMB_VFS_OP_RECVFLE,		SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(skel_rename),			SMB_VFS_OP_RENAME,		SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(skel_fsync),			SMB_VFS_OP_FSYNC,		SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(skel_stat),				SMB_VFS_OP_STAT,		SMB_VFS_LAYER_OPAQUE},
diff --git a/examples/VFS/skel_transparent.c b/examples/VFS/skel_transparent.c
index 66ea112..2512f4d 100644
--- a/examples/VFS/skel_transparent.c
+++ b/examples/VFS/skel_transparent.c
@@ -149,6 +149,16 @@ static SMB_OFF_T skel_lseek(vfs_handle_struct *handle, files_struct *fsp, int fi
 	return SMB_VFS_NEXT_LSEEK(handle, fsp, filedes, offset, whence);
 }
 
+static ssize_t skel_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fsp, int fromfd, const DATA_BLOB *hdr, SMB_OFF_T offset, size_t n)
+{
+	return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, fromfd, hdr, offset, n);
+}
+
+static ssize_t skel_recvfile(vfs_handle_struct *handle, int fromfd, files_struct *fsp, int tofd, SMB_OFF_T offset, size_t n)
+{
+	return SMB_VFS_NEXT_RECVFILE(handle, fromfd, fsp, tofd, offset, n);
+}
+
 static int skel_rename(vfs_handle_struct *handle,  const char *oldname, const char *newname)
 {
 	return SMB_VFS_NEXT_RENAME(handle, oldname, newname);
@@ -561,6 +571,8 @@ static vfs_op_tuple skel_op_tuples[] = {
 	{SMB_VFS_OP(skel_read),				SMB_VFS_OP_READ,		SMB_VFS_LAYER_TRANSPARENT},
 	{SMB_VFS_OP(skel_write),			SMB_VFS_OP_WRITE,		SMB_VFS_LAYER_TRANSPARENT},
 	{SMB_VFS_OP(skel_lseek),			SMB_VFS_OP_LSEEK,		SMB_VFS_LAYER_TRANSPARENT},
+	{SMB_VFS_OP(skel_sendfile),			SMB_VFS_OP_SENDFILE,		SMB_VFS_LAYER_TRANSPARENT},
+	{SMB_VFS_OP(skel_recvfile),			SMB_VFS_OP_RECVFILE,		SMB_VFS_LAYER_TRANSPARENT},
 	{SMB_VFS_OP(skel_rename),			SMB_VFS_OP_RENAME,		SMB_VFS_LAYER_TRANSPARENT},
 	{SMB_VFS_OP(skel_fsync),			SMB_VFS_OP_FSYNC,		SMB_VFS_LAYER_TRANSPARENT},
 	{SMB_VFS_OP(skel_stat),				SMB_VFS_OP_STAT,		SMB_VFS_LAYER_TRANSPARENT},
diff --git a/source/Makefile.in b/source/Makefile.in
index 98fd8f8..a1da65a 100644
--- a/source/Makefile.in
+++ b/source/Makefile.in
@@ -275,7 +275,7 @@ LIB_WITHOUT_PROTO_OBJ = $(LIBREPLACE_OBJ) $(SOCKET_WRAPPER_OBJ) $(TALLOC_OBJ) \
 LIB_WITH_PROTO_OBJ = $(VERSION_OBJ) lib/charcnv.o lib/debug.o lib/fault.o \
 	  lib/interface.o lib/md4.o \
 	  lib/pidfile.o \
-	  lib/signal.o lib/system.o lib/sendfile.o lib/time.o \
+	  lib/signal.o lib/system.o lib/sendfile.o lib/recvfile.o lib/time.o \
 	  lib/ufc.o lib/genrand.o lib/username.o \
 	  lib/util_pw.o lib/access.o lib/smbrun.o \
 	  lib/bitmap.o lib/crc32.o lib/dprintf.o \
diff --git a/source/include/smbprofile.h b/source/include/smbprofile.h
index c898155..acd8460 100644
--- a/source/include/smbprofile.h
+++ b/source/include/smbprofile.h
@@ -103,6 +103,10 @@ enum profile_stats_values
 #define syscall_sendfile_count __profile_stats_value(PR_VALUE_SYSCALL_SENDFILE, count)
 #define syscall_sendfile_time __profile_stats_value(PR_VALUE_SYSCALL_SENDFILE, time)
 
+	PR_VALUE_SYSCALL_RECVFILE,
+#define syscall_recvfile_count __profile_stats_value(PR_VALUE_SYSCALL_RECVFILE, count)
+#define syscall_recvfile_time __profile_stats_value(PR_VALUE_SYSCALL_RECVFILE, time)
+
 	PR_VALUE_SYSCALL_RENAME,
 #define syscall_rename_count __profile_stats_value(PR_VALUE_SYSCALL_RENAME, count)
 #define syscall_rename_time __profile_stats_value(PR_VALUE_SYSCALL_RENAME, time)
diff --git a/source/include/vfs.h b/source/include/vfs.h
index 9dadacb..03af04d 100644
--- a/source/include/vfs.h
+++ b/source/include/vfs.h
@@ -73,6 +73,7 @@
 /* Leave at 22 - not yet released. But change set_nt_acl to return an NTSTATUS. jra. */
 /* Leave at 22 - not yet released. Add file_id_create operation. --metze */
 /* Leave at 22 - not yet released. Change all BOOL parameters (int) to bool. jra. */
+/* Leave at 22 - not yet released. Added recvfile. */
 #define SMB_VFS_INTERFACE_VERSION 22
 
 
@@ -138,6 +139,7 @@ typedef enum _vfs_op_type {
 	SMB_VFS_OP_PWRITE,
 	SMB_VFS_OP_LSEEK,
 	SMB_VFS_OP_SENDFILE,
+	SMB_VFS_OP_RECVFILE,
 	SMB_VFS_OP_RENAME,
 	SMB_VFS_OP_FSYNC,
 	SMB_VFS_OP_STAT,
@@ -266,6 +268,7 @@ struct vfs_ops {
 		ssize_t (*pwrite)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd, const void *data, size_t n, SMB_OFF_T offset);
 		SMB_OFF_T (*lseek)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd, SMB_OFF_T offset, int whence);
 		ssize_t (*sendfile)(struct vfs_handle_struct *handle, int tofd, files_struct *fsp, int fromfd, const DATA_BLOB *header, SMB_OFF_T offset, size_t count);
+		ssize_t (*recvfile)(struct vfs_handle_struct *handle, int fromfd, files_struct *fsp, int tofd, SMB_OFF_T offset, size_t count);
 		int (*rename)(struct vfs_handle_struct *handle, const char *oldname, const char *newname);
 		int (*fsync)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd);
 		int (*stat)(struct vfs_handle_struct *handle, const char *fname, SMB_STRUCT_STAT *sbuf);
@@ -392,6 +395,7 @@ struct vfs_ops {
 		struct vfs_handle_struct *pwrite;
 		struct vfs_handle_struct *lseek;
 		struct vfs_handle_struct *sendfile;
+		struct vfs_handle_struct *recvfile;
 		struct vfs_handle_struct *rename;
 		struct vfs_handle_struct *fsync;
 		struct vfs_handle_struct *stat;
diff --git a/source/include/vfs_macros.h b/source/include/vfs_macros.h
index 7e161c3..cc7780f 100644
--- a/source/include/vfs_macros.h
+++ b/source/include/vfs_macros.h
@@ -54,6 +54,7 @@
 #define SMB_VFS_PWRITE(fsp, fd, data, n, off) ((fsp)->conn->vfs.ops.pwrite((fsp)->conn->vfs.handles.pwrite, (fsp), (fd), (data), (n), (off)))
 #define SMB_VFS_LSEEK(fsp, fd, offset, whence) ((fsp)->conn->vfs.ops.lseek((fsp)->conn->vfs.handles.lseek, (fsp), (fd), (offset), (whence)))
 #define SMB_VFS_SENDFILE(tofd, fsp, fromfd, header, offset, count) ((fsp)->conn->vfs.ops.sendfile((fsp)->conn->vfs.handles.sendfile, (tofd), (fsp), (fromfd), (header), (offset), (count)))
+#define SMB_VFS_RECVFILE(fromfd, fsp, tofd, offset, count) ((fsp)->conn->vfs.ops.recvfile((fsp)->conn->vfs.handles.recvfile, (fromfd), (fsp), (tofd), (offset), (count)))
 #define SMB_VFS_RENAME(conn, old, new) ((conn)->vfs.ops.rename((conn)->vfs.handles.rename, (old), (new)))
 #define SMB_VFS_FSYNC(fsp, fd) ((fsp)->conn->vfs.ops.fsync((fsp)->conn->vfs.handles.fsync, (fsp), (fd)))
 #define SMB_VFS_STAT(conn, fname, sbuf) ((conn)->vfs.ops.stat((conn)->vfs.handles.stat, (fname), (sbuf)))
@@ -172,6 +173,7 @@
 #define SMB_VFS_OPAQUE_PWRITE(fsp, fd, data, n, off) ((fsp)->conn->vfs_opaque.ops.pwrite((fsp)->conn->vfs_opaque.handles.pwrite, (fsp), (fd), (data), (n), (off)))
 #define SMB_VFS_OPAQUE_LSEEK(fsp, fd, offset, whence) ((fsp)->conn->vfs_opaque.ops.lseek((fsp)->conn->vfs_opaque.handles.lseek, (fsp), (fd), (offset), (whence)))
 #define SMB_VFS_OPAQUE_SENDFILE(tofd, fsp, fromfd, header, offset, count) ((fsp)->conn->vfs_opaque.ops.sendfile((fsp)->conn->vfs_opaque.handles.sendfile, (tofd), (fsp), (fromfd), (header), (offset), (count)))
+#define SMB_VFS_OPAQUE_RECVFILE(fromfd, fsp, tofd, offset, count) ((fsp)->conn->vfs_opaque.ops.recvfile((fsp)->conn->vfs_opaque.handles.recvfile, (fromfd), (fsp), (tofd), (offset), (count)))
 #define SMB_VFS_OPAQUE_RENAME(conn, old, new) ((conn)->vfs_opaque.ops.rename((conn)->vfs_opaque.handles.rename, (old), (new)))
 #define SMB_VFS_OPAQUE_FSYNC(fsp, fd) ((fsp)->conn->vfs_opaque.ops.fsync((fsp)->conn->vfs_opaque.handles.fsync, (fsp), (fd)))
 #define SMB_VFS_OPAQUE_STAT(conn, fname, sbuf) ((conn)->vfs_opaque.ops.stat((conn)->vfs_opaque.handles.stat, (fname), (sbuf)))
@@ -291,6 +293,7 @@
 #define SMB_VFS_NEXT_PWRITE(handle, fsp, fd, data, n, off) ((handle)->vfs_next.ops.pwrite((handle)->vfs_next.handles.pwrite, (fsp), (fd), (data), (n), (off)))
 #define SMB_VFS_NEXT_LSEEK(handle, fsp, fd, offset, whence) ((handle)->vfs_next.ops.lseek((handle)->vfs_next.handles.lseek, (fsp), (fd), (offset), (whence)))
 #define SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, fromfd, header, offset, count) ((handle)->vfs_next.ops.sendfile((handle)->vfs_next.handles.sendfile, (tofd), (fsp), (fromfd), (header), (offset), (count)))
+#define SMB_VFS_NEXT_RECVFILE(handle, fromfd, fsp, tofd, offset, count) ((handle)->vfs_next.ops.recvfile((handle)->vfs_next.handles.recvfile, (fromfd), (fsp), (tofd), (offset), (count)))
 #define SMB_VFS_NEXT_RENAME(handle, old, new) ((handle)->vfs_next.ops.rename((handle)->vfs_next.handles.rename, (old), (new)))
 #define SMB_VFS_NEXT_FSYNC(handle, fsp, fd) ((handle)->vfs_next.ops.fsync((handle)->vfs_next.handles.fsync, (fsp), (fd)))
 #define SMB_VFS_NEXT_STAT(handle, fname, sbuf) ((handle)->vfs_next.ops.stat((handle)->vfs_next.handles.stat, (fname), (sbuf)))
diff --git a/source/lib/recvfile.c b/source/lib/recvfile.c
new file mode 100644
index 0000000..cd9fb12
--- /dev/null
+++ b/source/lib/recvfile.c
@@ -0,0 +1,158 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.2.x
+ recvfile implementations.
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file handles the OS dependent recvfile implementations.
+ * The API is such that it returns -1 on error, else returns the
+ * number of bytes written.
+ */
+
+#include "includes.h"
+
+/* Do this on our own in TRANSFER_BUF_SIZE chunks.
+ * It's safe to make direct syscalls to lseek/write here
+ * as we're below the Samba vfs layer.
+ *
+ * If tofd is -1 we just drain the incoming socket of count
+ * bytes without writing to the outgoing fd.
+ * If a write fails we do the same (to cope with disk full)
+ * errors.
+ *
+ * Returns -1 on short reads from fromfd (read error)
+ * and sets errno.
+ *
+ * Returns number of bytes written to 'tofd'
+ * or thrown away if 'tofd == -1'.
+ * eturn != count then sets errno.
+ * Returns count if complete success.
+ */
+
+#ifndef TRANSFER_BUF_SIZE
+#define TRANSFER_BUF_SIZE (128*1024)
+#endif
+
+static ssize_t default_sys_recvfile(int fromfd,
+			int tofd,
+			SMB_OFF_T offset,
+			size_t count)
+{
+	int saved_errno = 0;
+	size_t total = 0;
+	size_t bufsize = MIN(TRANSFER_BUF_SIZE,count);
+	size_t total_written = 0;
+	char *buffer = NULL;
+
+	if (count == 0) {
+		return 0;
+	}
+
+	if (tofd != -1 && offset != (SMB_OFF_T)-1) {
+		if (sys_lseek(tofd, offset, SEEK_SET) == -1) {
+			if (errno != ESPIPE) {
+				return -1;
+			}
+		}
+	}
+
+	buffer = SMB_MALLOC_ARRAY(char, bufsize);
+	if (buffer == NULL) {
+		return -1;
+	}
+
+	while (total < count) {
+		size_t num_written = 0;
+		ssize_t read_ret;
+		size_t toread = MIN(bufsize,count - total);
+
+		/* Read from socket - ignore EINTR. */
+		read_ret = sys_read(fromfd, buffer, toread);
+		if (read_ret <= 0) {
+			/* EOF or socket error. */
+			free(buffer);
+			return -1;
+		}
+
+		num_written = 0;
+
+		while (num_written < read_ret) {
+			ssize_t write_ret;
+
+			if (tofd == -1) {
+				write_ret = read_ret;
+			} else {
+				/* Write to file - ignore EINTR. */
+				write_ret = sys_write(tofd,
+						buffer + num_written,
+						read_ret - num_written);
+
+				if (write_ret <= 0) {
+					/* write error - stop writing. */
+					tofd = -1;
+					saved_errno = errno;
+					continue;
+				}
+			}
+
+			num_written += (size_t)write_ret;
+			total_written += (size_t)write_ret;
+		}
+
+		total += read_ret;
+	}
+
+	free(buffer);
+	if (saved_errno) {
+		/* Return the correct write error. */
+		errno = saved_errno;
+	}
+	return (ssize_t)total_written;
+}
+
+#if defined(HAVE_SPLICE_SYSCALL)
+ssize_t sys_recvfile(int fromfd,
+			int tofd,
+			SMB_OFF_T offset,
+			size_t count)
+{
+	errno = ENOSYS
+	return -1;
+}
+#else
+
+/*****************************************************************
+ No recvfile system call - use the default 128 chunk implementation.
+*****************************************************************/
+
+ssize_t sys_recvfile(int fromfd,
+			int tofd,
+			SMB_OFF_T offset,
+			size_t count)
+{
+	return default_sys_recvfile(fromfd, tofd, offset, count);
+}
+#endif
+
+/*****************************************************************
+ Throw away "count" bytes from the client socket.
+*****************************************************************/
+
+ssize_t drain_socket(int sockfd, size_t count)
+{
+	return default_sys_recvfile(sockfd, -1, (SMB_OFF_T)-1, count);
+}
diff --git a/source/modules/vfs_default.c b/source/modules/vfs_default.c
index a3bb61d..8c2bbfe 100644
--- a/source/modules/vfs_default.c
+++ b/source/modules/vfs_default.c
@@ -348,6 +348,21 @@ static ssize_t vfswrap_sendfile(vfs_handle_struct *handle, int tofd, files_struc
 	return result;
 }
 
+static ssize_t vfswrap_recvfile(vfs_handle_struct *handle,
+			int fromfd,
+			files_struct *fsp,
+			int tofd,
+			SMB_OFF_T offset,
+			size_t n)
+{
+	ssize_t result;
+
+	START_PROFILE_BYTES(syscall_recvfile, n);
+	result = sys_recvfile(fromfd, tofd, offset, n);
+	END_PROFILE(syscall_recvfile);
+	return result;
+}
+
 /*********************************************************
  For rename across filesystems Patch from Warren Birnbaum
  <warrenb at hpcvscdp.cv.hp.com>
@@ -1263,6 +1278,8 @@ static vfs_op_tuple vfs_default_ops[] = {
 	 SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(vfswrap_sendfile),	SMB_VFS_OP_SENDFILE,
 	 SMB_VFS_LAYER_OPAQUE},
+	{SMB_VFS_OP(vfswrap_recvfile),	SMB_VFS_OP_RECVFILE,
+	 SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(vfswrap_rename),	SMB_VFS_OP_RENAME,
 	 SMB_VFS_LAYER_OPAQUE},
 	{SMB_VFS_OP(vfswrap_fsync),	SMB_VFS_OP_FSYNC,
diff --git a/source/modules/vfs_full_audit.c b/source/modules/vfs_full_audit.c
index df49c86..c8a82e3 100644
--- a/source/modules/vfs_full_audit.c
+++ b/source/modules/vfs_full_audit.c
@@ -127,6 +127,10 @@ static ssize_t smb_full_audit_sendfile(vfs_handle_struct *handle, int tofd,
 			      files_struct *fsp, int fromfd,
 			      const DATA_BLOB *hdr, SMB_OFF_T offset,
 			      size_t n);
+static ssize_t smb_full_audit_recvfile(vfs_handle_struct *handle, int fromfd,
+			      files_struct *fsp, int tofd,
+			      SMB_OFF_T offset,
+			      size_t n);
 static int smb_full_audit_rename(vfs_handle_struct *handle,
 			const char *oldname, const char *newname);
 static int smb_full_audit_fsync(vfs_handle_struct *handle, files_struct *fsp, int fd);
@@ -363,6 +367,8 @@ static vfs_op_tuple audit_op_tuples[] = {
 	 SMB_VFS_LAYER_LOGGER},
 	{SMB_VFS_OP(smb_full_audit_sendfile),	SMB_VFS_OP_SENDFILE,
 	 SMB_VFS_LAYER_LOGGER},
+	{SMB_VFS_OP(smb_full_audit_recvfile),	SMB_VFS_OP_RECVFILE,
+	 SMB_VFS_LAYER_LOGGER},
 	{SMB_VFS_OP(smb_full_audit_rename),	SMB_VFS_OP_RENAME,
 	 SMB_VFS_LAYER_LOGGER},
 	{SMB_VFS_OP(smb_full_audit_fsync),	SMB_VFS_OP_FSYNC,
@@ -1145,6 +1151,22 @@ static ssize_t smb_full_audit_sendfile(vfs_handle_struct *handle, int tofd,
 	return result;
 }
 
+static ssize_t smb_full_audit_recvfile(vfs_handle_struct *handle, int fromfd,
+			      files_struct *fsp, int tofd,
+			      SMB_OFF_T offset,
+			      size_t n)
+{
+	ssize_t result;
+
+	result = SMB_VFS_NEXT_RECVFILE(handle, fromfd, fsp, tofd,
+				       offset, n);
+
+	do_log(SMB_VFS_OP_RECVFILE, (result >= 0), handle,
+	       "%s", fsp->fsp_name);
+
+	return result;
+}
+
 static int smb_full_audit_rename(vfs_handle_struct *handle,
 			const char *oldname, const char *newname)
 {
diff --git a/source/profile/profile.c b/source/profile/profile.c
index 50751d5..e9c7c7b 100644
--- a/source/profile/profile.c
+++ b/source/profile/profile.c
@@ -297,6 +297,7 @@ bool profile_setup(struct messaging_context *msg_ctx, bool rdonly)
 	    "syscall_pwrite",		/* PR_VALUE_SYSCALL_PWRITE */
 	    "syscall_lseek",		/* PR_VALUE_SYSCALL_LSEEK */
 	    "syscall_sendfile",		/* PR_VALUE_SYSCALL_SENDFILE */
+	    "syscall_recvfile",		/* PR_VALUE_SYSCALL_RECVFILE */
 	    "syscall_rename",		/* PR_VALUE_SYSCALL_RENAME */
 	    "syscall_fsync",		/* PR_VALUE_SYSCALL_FSYNC */
 	    "syscall_stat",		/* PR_VALUE_SYSCALL_STAT */
-------------- next part --------------
diff --git a/source/include/smb.h b/source/include/smb.h
index 4c51acf..303f760 100644
--- a/source/include/smb.h
+++ b/source/include/smb.h
@@ -675,6 +675,7 @@ struct smb_request {
 	uint8  wct;
 	const uint8 *inbuf;
 	uint8 *outbuf;
+	size_t unread_bytes;
 };
 
 /* Defines for the sent_oplock_break field above. */
diff --git a/source/lib/recvfile.c b/source/lib/recvfile.c
index cd9fb12..9d77f94 100644
--- a/source/lib/recvfile.c
+++ b/source/lib/recvfile.c
@@ -125,13 +125,49 @@ static ssize_t default_sys_recvfile(int fromfd,
 }
 
 #if defined(HAVE_SPLICE_SYSCALL)
+
+#ifdef JRA_SPLICE_TEST
+#include <linux/unistd.h>
+#include <sys/syscall.h>
+
+#define __NR_splice             313
+_syscall6( long, splice,
+		int, fromfd,
+		loff_t *, fromoffset,
+		int, tofd,
+		loff_t *, tooffset,
+		size_t, count,
+		unsigned int, flags);
+#endif
+
 ssize_t sys_recvfile(int fromfd,
 			int tofd,
 			SMB_OFF_T offset,
 			size_t count)
 {
-	errno = ENOSYS
-	return -1;
+	size_t total = 0;
+
+	if (count == 0) {
+		return 0;
+	}
+
+	while (total < count) {
+		ssize_t ret = splice(fromfd,
+					NULL,
+					tofd,
+					&offset,
+					count,
+					0);
+		if (ret == -1) {
+			if (errno != EINTR) {
+				return -1;
+			}
+			continue;
+		}
+		total += ret;
+		count -= ret;
+	}
+	return total;
 }
 #else
 
diff --git a/source/lib/util_sock.c b/source/lib/util_sock.c
index e66bd5f..bbcbcac 100644
--- a/source/lib/util_sock.c
+++ b/source/lib/util_sock.c
@@ -1113,7 +1113,7 @@ bool send_keepalive(int client)
  Timeout is in milliseconds.
 ****************************************************************************/
 
-static ssize_t read_smb_length_return_keepalive(int fd,
+ssize_t read_smb_length_return_keepalive(int fd,
 						char *inbuf,
 						unsigned int timeout)
 {
@@ -1260,86 +1260,6 @@ ssize_t receive_smb_raw(int fd,
 	return len;
 }
 
-static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
-				      char **buffer, unsigned int timeout)
-{
-	char lenbuf[4];
-	ssize_t len,ret;
-
-	smb_read_error = 0;
-
-	len = read_smb_length_return_keepalive(fd, lenbuf, timeout);
-	if (len < 0) {
-		DEBUG(10,("receive_smb_raw: length < 0!\n"));
-
-		/*
-		 * Correct fix. smb_read_error may have already been
-		 * set. Only set it here if not already set. Global
-		 * variables still suck :-). JRA.
-		 */
-
-		if (smb_read_error == 0)
-			smb_read_error = READ_ERROR;
-		return -1;
-	}
-
-	/*
-	 * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
-	 * of header. Don't print the error if this fits.... JRA.
-	 */
-
-	if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
-		DEBUG(0,("Invalid packet length! (%lu bytes).\n",
-					(unsigned long)len));
-		if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) {
-
-			/*
-			 * Correct fix. smb_read_error may have already been
-			 * set. Only set it here if not already set. Global
-			 * variables still suck :-). JRA.
-			 */
-
-			if (smb_read_error == 0)
-				smb_read_error = READ_ERROR;
-			return -1;
-		}
-	}
-
-	/*
-	 * The +4 here can't wrap, we've checked the length above already.
-	 */
-
-	*buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
-
-	if (*buffer == NULL) {
-		DEBUG(0, ("Could not allocate inbuf of length %d\n",
-			  (int)len+4));
-		if (smb_read_error == 0)
-			smb_read_error = READ_ERROR;
-		return -1;
-	}
-
-	memcpy(*buffer, lenbuf, sizeof(lenbuf));
-
-	if(len > 0) {
-		if (timeout > 0) {
-			ret = read_socket_with_timeout(fd,(*buffer)+4, len,
-						       len, timeout);
-		} else {
-			ret = read_data(fd, (*buffer)+4, len);
-		}
-
-		if (ret != len) {
-			if (smb_read_error == 0) {
-				smb_read_error = READ_ERROR;
-			}
-			return -1;
-		}
-	}
-
-	return len + 4;
-}
-
 /****************************************************************************
  Wrapper for receive_smb_raw().
  Checks the MAC on signed packets.
@@ -1364,30 +1284,6 @@ bool receive_smb(int fd, char *buffer, unsigned int timeout)
 	return true;
 }
 
-ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
-			   unsigned int timeout)
-{
-	ssize_t len;
-
-	len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout);
-
-	if (len < 0) {
-		return -1;
-	}
-
-	/* Check the incoming SMB signature. */
-	if (!srv_check_sign_mac(*buffer, true)) {
-		DEBUG(0, ("receive_smb: SMB Signature verification failed on "
-			  "incoming packet!\n"));
-		if (smb_read_error == 0) {
-			smb_read_error = READ_BAD_SIG;
-		}
-		return -1;
-	}
-
-	return len;
-}
-
 /****************************************************************************
  Send an smb to a fd.
 ****************************************************************************/
diff --git a/source/param/loadparm.c b/source/param/loadparm.c
index dcec6bc..a5b2647 100644
--- a/source/param/loadparm.c
+++ b/source/param/loadparm.c
@@ -331,6 +331,7 @@ typedef struct {
 
 	bool bResetOnZeroVC;
 	int iKeepalive;
+	int iminreceivefile;
 	param_opt_struct *param_opt;
 } global;
 
@@ -998,6 +999,7 @@ static struct parm_struct parm_table[] = {
 	{"max protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
 	{"protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
 	{"min protocol", P_ENUM, P_GLOBAL, &Globals.minprotocol, NULL, enum_protocol, FLAG_ADVANCED}, 
+	{"min receivefile size", P_INTEGER, P_GLOBAL, &Globals.iminreceivefile, NULL, NULL, FLAG_ADVANCED}, 
 	{"read raw", P_BOOL, P_GLOBAL, &Globals.bReadRaw, NULL, NULL, FLAG_ADVANCED}, 
 	{"write raw", P_BOOL, P_GLOBAL, &Globals.bWriteRaw, NULL, NULL, FLAG_ADVANCED}, 
 	{"disable netbios", P_BOOL, P_GLOBAL, &Globals.bDisableNetbios, NULL, NULL, FLAG_ADVANCED}, 
@@ -1708,6 +1710,8 @@ static void init_globals(bool first_time_only)
 
 	/* By default no shares out of the registry */
 	Globals.bRegistryShares = False;
+
+	Globals.iminreceivefile = 0;
 }
 
 /*******************************************************************
@@ -2165,6 +2169,7 @@ FN_GLOBAL_INTEGER(lp_algorithmic_rid_base, &Globals.AlgorithmicRidBase)
 FN_GLOBAL_INTEGER(lp_name_cache_timeout, &Globals.name_cache_timeout)
 FN_GLOBAL_INTEGER(lp_client_signing, &Globals.client_signing)
 FN_GLOBAL_INTEGER(lp_server_signing, &Globals.server_signing)
+FN_GLOBAL_INTEGER(lp_min_receive_file_size, &Globals.iminreceivefile);
 FN_GLOBAL_INTEGER(lp_client_ldap_sasl_wrapping, &Globals.client_ldap_sasl_wrapping)
 
 /* local prototypes */
diff --git a/source/smbd/blocking.c b/source/smbd/blocking.c
index 66a6144..0078bb7 100644
--- a/source/smbd/blocking.c
+++ b/source/smbd/blocking.c
@@ -259,7 +259,7 @@ static void reply_lockingX_success(blocking_lock_record *blr)
 		smb_panic("Could not allocate smb_request");
 	}
 
-	init_smb_request(req, (uint8 *)blr->inbuf);
+	init_smb_request(req, (uint8 *)blr->inbuf, 0);
 	reply_outbuf(req, 2, 0);
 
 	/*
@@ -531,7 +531,7 @@ static bool process_trans2(blocking_lock_record *blr)
 		return True;
 	}
 
-	init_smb_request(req, (uint8 *)blr->inbuf);
+	init_smb_request(req, (uint8 *)blr->inbuf, 0);
 
 	SCVAL(req->inbuf, smb_com, SMBtrans2);
 	SSVAL(params,0,0);
diff --git a/source/smbd/fileio.c b/source/smbd/fileio.c
index 14056dd..9e296f2 100644
--- a/source/smbd/fileio.c
+++ b/source/smbd/fileio.c
@@ -116,12 +116,16 @@ static unsigned int allocated_write_caches;
  *Really* write to a file.
 ****************************************************************************/
 
-static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos, size_t n)
+static ssize_t real_write_file(struct smb_request *req,
+				files_struct *fsp,
+				const char *data,
+				SMB_OFF_T pos,
+				size_t n)
 {
 	ssize_t ret;
 
         if (pos == -1) {
-                ret = vfs_write_data(fsp, data, n);
+                ret = vfs_write_data(req, fsp, data, n);
         } else {
 		fsp->fh->pos = pos;
 		if (pos && lp_strict_allocate(SNUM(fsp->conn))) {
@@ -129,7 +133,7 @@ static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos
 				return -1;
 			}
 		}
-                ret = vfs_pwrite_data(fsp, data, n, pos);
+                ret = vfs_pwrite_data(req, fsp, data, n, pos);
 	}
 
 	DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
@@ -191,11 +195,15 @@ static int wcp_file_size_change(files_struct *fsp)
  Write to a file.
 ****************************************************************************/
 
-ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n)
+ssize_t write_file(struct smb_request *req,
+			files_struct *fsp,
+			const char *data,
+			SMB_OFF_T pos,
+			size_t n)
 {
 	write_cache *wcp = fsp->wcp;
 	ssize_t total_written = 0;
-	int write_path = -1; 
+	int write_path = -1;
 
 	if (fsp->print_file) {
 		fstring sharename;
@@ -234,8 +242,8 @@ ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n)
 			if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && !wcp) {
 				setup_write_cache(fsp, st.st_size);
 				wcp = fsp->wcp;
-			} 
-		}  
+			}
+		}
 	}
 
 #ifdef WITH_PROFILE
@@ -280,9 +288,18 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
 	}
 #endif
 
+	if (wcp && req->unread_bytes) {
+		/* If we're using receivefile don't
+		 * deal with a write cache.
+		 */
+		flush_write_cache(fsp, WRITE_FLUSH);
+		delete_write_cache(fsp);
+		wcp = NULL;
+	}
+
 	if(!wcp) {
 		DO_PROFILE_INC(writecache_direct_writes);
-		total_written = real_write_file(fsp, data, pos, n);
+		total_written = real_write_file(req, fsp, data, pos, n);
 		return total_written;
 	}
 
@@ -291,7 +308,7 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
 
 	fsp->fh->pos = pos + n;
 
-	/* 
+	/*
 	 * If we have active cache and it isn't contiguous then we flush.
 	 * NOTE: There is a small problem with running out of disk ....
 	 */
@@ -610,7 +627,7 @@ len = %u\n",fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (uns
 			if ( n <= wcp->alloc_size && n > wcp->data_size) {
 				cache_flush_needed = True;
 			} else {
-				ssize_t ret = real_write_file(fsp, data, pos, n);
+				ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
 
 				/*
 				 * If the write overlaps the entire cache, then
@@ -657,7 +674,7 @@ n = %u, wcp->offset=%.0f, wcp->data_size=%u\n",
 	 */
 
 	if (n > wcp->alloc_size ) {
-		ssize_t ret = real_write_file(fsp, data, pos, n);
+		ssize_t ret = real_write_file(NULL,fsp, data, pos, n);
 		if (ret == -1) {
 			return -1;
 		}
@@ -828,7 +845,7 @@ ssize_t flush_write_cache(files_struct *fsp, enum flush_reason_enum reason)
 	}
 #endif
 
-	ret = real_write_file(fsp, wcp->data, wcp->offset, data_size);
+	ret = real_write_file(NULL, fsp, wcp->data, wcp->offset, data_size);
 
 	/*
 	 * Ensure file size if kept up to date if write extends file.
diff --git a/source/smbd/notify.c b/source/smbd/notify.c
index ecb7d9d..0dd7fbb 100644
--- a/source/smbd/notify.c
+++ b/source/smbd/notify.c
@@ -189,7 +189,7 @@ void change_notify_reply(const uint8 *request_buf, uint32 max_param,
 	smb_setlen((char *)tmp_request, smb_size);
 	SCVAL(tmp_request, smb_wct, 0);
 
-	init_smb_request(req, tmp_request);
+	init_smb_request(req, tmp_request,0);
 
 	send_nt_replies(req, NT_STATUS_OK, prs_data_p(&ps),
 			prs_offset(&ps), NULL, 0);
diff --git a/source/smbd/process.c b/source/smbd/process.c
index ed1bf76..1c8d8a6 100644
--- a/source/smbd/process.c
+++ b/source/smbd/process.c
@@ -25,7 +25,7 @@ extern int smb_echo_count;
 
 const int total_buffer_size = (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE + SAFETY_MARGIN);
 
-/* 
+/*
  * Size of data we can send to client. Set
  *  by the client for all protocols above CORE.
  *  Set by us for CORE protocol.
@@ -48,7 +48,9 @@ extern int max_send;
  * Initialize a struct smb_request from an inbuf
  */
 
-void init_smb_request(struct smb_request *req, const uint8 *inbuf)
+void init_smb_request(struct smb_request *req,
+			const uint8 *inbuf,
+			size_t unread_bytes)
 {
 	size_t req_size = smb_len(inbuf) + 4;
 	/* Ensure we have at least smb_size bytes. */
@@ -63,6 +65,8 @@ void init_smb_request(struct smb_request *req, const uint8 *inbuf)
 	req->vuid   = SVAL(inbuf, smb_uid);
 	req->tid    = SVAL(inbuf, smb_tid);
 	req->wct    = CVAL(inbuf, smb_wct);
+	req->unread_bytes = unread_bytes;
+
 	/* Ensure we have at least wct words and 2 bytes of bcc. */
 	if (smb_size + req->wct*2 > req_size) {
 		DEBUG(0,("init_smb_request: invalid wct number %u (size %u)\n",
@@ -231,6 +235,14 @@ bool push_deferred_smb_message(struct smb_request *req,
 {
 	struct timeval end_time;
 
+	if (req->unread_bytes) {
+		DEBUG(0,("push_deferred_smb_message: logic error ! "
+			"unread_bytes = %u\n",
+			(unsigned int)req->unread_bytes ));
+		smb_panic("push_deferred_smb_message: "
+			"logic error unread_bytes != 0" );
+	}
+
 	end_time = timeval_sum(&request_time, &timeout);
 
 	DEBUG(10,("push_deferred_open_smb_message: pushing message len %u mid %u "
@@ -382,8 +394,11 @@ static int select_on_fd(int fd, int maxfd, fd_set *fds)
 The timeout is in milliseconds
 ****************************************************************************/
 
-static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
-				   size_t *buffer_len, int timeout)
+static bool receive_message_or_smb(TALLOC_CTX *mem_ctx,
+				char **buffer,
+				size_t *buffer_len,
+				int timeout,
+				size_t *p_unread)
 {
 	fd_set r_fds, w_fds;
 	int selrtn;
@@ -391,6 +406,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
 	int maxfd = 0;
 	ssize_t len;
 
+	*p_unread = 0;
 	smb_read_error = 0;
 
  again:
@@ -565,7 +581,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
 		goto again;
 	}
 
-	len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0);
+	len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0, p_unread);
 
 	if (len == -1) {
 		return False;
@@ -1115,7 +1131,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size)
  Construct a reply to the incoming packet.
 ****************************************************************************/
 
-static void construct_reply(char *inbuf, int size)
+static void construct_reply(char *inbuf, int size, size_t unread_bytes)
 {
 	uint8 type = CVAL(inbuf,smb_com);
 	struct smb_request *req;
@@ -1127,10 +1143,19 @@ static void construct_reply(char *inbuf, int size)
 	if (!(req = talloc(talloc_tos(), struct smb_request))) {
 		smb_panic("could not allocate smb_request");
 	}
-	init_smb_request(req, (uint8 *)inbuf);
+	init_smb_request(req, (uint8 *)inbuf, unread_bytes);
 
 	switch_message(type, req, size);
 
+	if (req->unread_bytes) {
+		/* writeX failed. drain socket. */
+		if (drain_socket(smbd_server_fd(), req->unread_bytes) !=
+				req->unread_bytes) {
+			smb_panic("failed to drain pending bytes");
+		}
+		req->unread_bytes = 0;
+	}
+
 	if (req->outbuf == NULL) {
 		return;
 	}
@@ -1152,7 +1177,7 @@ static void construct_reply(char *inbuf, int size)
  Process an smb from the client
 ****************************************************************************/
 
-static void process_smb(char *inbuf, size_t nread)
+static void process_smb(char *inbuf, size_t nread, size_t unread_bytes)
 {
 	static int trans_num;
 	int msg_type = CVAL(inbuf,0);
@@ -1176,7 +1201,9 @@ static void process_smb(char *inbuf, size_t nread)
 
 	DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
 		    smb_len(inbuf) ) );
-	DEBUG( 3, ( "Transaction %d of length %d\n", trans_num, (int)nread ) );
+	DEBUG( 3, ( "Transaction %d of length %d (%u toread)\n", trans_num,
+				(int)nread,
+				(unsigned int)unread_bytes ));
 
 	if (msg_type != 0) {
 		/*
@@ -1188,8 +1215,8 @@ static void process_smb(char *inbuf, size_t nread)
 
 	show_msg(inbuf);
 
-	construct_reply(inbuf,nread);
-      
+	construct_reply(inbuf,nread,unread_bytes);
+
 	trans_num++;
 }
 
@@ -1348,7 +1375,7 @@ void chain_reply(struct smb_request *req)
 	if (!(req2 = talloc(talloc_tos(), struct smb_request))) {
 		smb_panic("could not allocate smb_request");
 	}
-	init_smb_request(req2, (uint8 *)inbuf2);
+	init_smb_request(req2, (uint8 *)inbuf2,0);
 
 	/* process the request */
 	switch_message(smb_com2, req2, new_size);
@@ -1625,6 +1652,7 @@ void smbd_process(void)
 {
 	time_t last_timeout_processing_time = time(NULL);
 	unsigned int num_smbs = 0;
+	size_t unread_bytes = 0;
 
 	max_recv = MIN(lp_maxxmit(),BUFFER_SIZE);
 
@@ -1635,8 +1663,8 @@ void smbd_process(void)
 		size_t inbuf_len;
 		TALLOC_CTX *frame = talloc_stackframe();
 
-		errno = 0;      
-		
+		errno = 0;
+
 		/* Did someone ask for immediate checks on things like blocking locks ? */
 		if (select_timeout == 0) {
 			if(!timeout_processing(&select_timeout,
@@ -1648,7 +1676,7 @@ void smbd_process(void)
 		run_events(smbd_event_context(), 0, NULL, NULL);
 
 		while (!receive_message_or_smb(NULL, &inbuf, &inbuf_len,
-					       select_timeout)) {
+					       select_timeout, &unread_bytes)) {
 			if(!timeout_processing(&select_timeout,
 					       &last_timeout_processing_time))
 				return;
@@ -1664,10 +1692,10 @@ void smbd_process(void)
 		 * faster than the select timeout, thus starving out the
 		 * essential processing (change notify, blocking locks) that
 		 * the timeout code does. JRA.
-		 */ 
+		 */
 		num_echos = smb_echo_count;
 
-		process_smb(inbuf, inbuf_len);
+		process_smb(inbuf, inbuf_len, unread_bytes);
 
 		TALLOC_FREE(inbuf);
 
diff --git a/source/smbd/reply.c b/source/smbd/reply.c
index 38ce797..4c1ed56 100644
--- a/source/smbd/reply.c
+++ b/source/smbd/reply.c
@@ -3494,7 +3494,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req)
 	}
 
 	if (numtowrite>0) {
-		nwritten = write_file(fsp,data,startpos,numtowrite);
+		nwritten = write_file(req,fsp,data,startpos,numtowrite);
 	}
 
 	DEBUG(3,("reply_writebraw: initial write fnum=%d start=%.0f num=%d "
@@ -3572,7 +3572,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req)
 			exit_server_cleanly("secondary writebraw failed");
 		}
 
-		nwritten = write_file(fsp,buf+4,startpos+nwritten,numtowrite);
+		nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite);
 		if (nwritten == -1) {
 			TALLOC_FREE(buf);
 			reply_unixerror(req, ERRHRD, ERRdiskfull);
@@ -3686,7 +3686,7 @@ void reply_writeunlock(connection_struct *conn, struct smb_request *req)
 	if(numtowrite == 0) {
 		nwritten = 0;
 	} else {
-		nwritten = write_file(fsp,data,startpos,numtowrite);
+		nwritten = write_file(req,fsp,data,startpos,numtowrite);
 	}
   
 	status = sync_file(conn, fsp, False /* write through */);
@@ -3808,7 +3808,7 @@ void reply_write(connection_struct *conn, struct smb_request *req)
 			return;
 		}
 	} else
-		nwritten = write_file(fsp,data,startpos,numtowrite);
+		nwritten = write_file(req,fsp,data,startpos,numtowrite);
   
 	status = sync_file(conn, fsp, False);
 	if (!NT_STATUS_IS_OK(status)) {
@@ -3841,6 +3841,64 @@ void reply_write(connection_struct *conn, struct smb_request *req)
 }
 
 /****************************************************************************
+ Ensure a buffer is a valid writeX for recvfile purposes.
+****************************************************************************/
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+						(2*14) + /* word count (including bcc) */ \
+						1 /* pad byte */)
+
+bool is_valid_writeX_buffer(char *inbuf)
+{
+	size_t numtowrite;
+	connection_struct *conn = NULL;
+	unsigned int doff = 0;
+	size_t len = smb_len(inbuf);
+
+	if (CVAL(inbuf,smb_com) != SMBwriteX ||
+			CVAL(inbuf,smb_vwv0) != 0xFF ||
+			CVAL(inbuf,smb_wct) != 14) {
+		return false;
+	}
+	conn = conn_find(SVAL(inbuf, smb_tid));
+	if (conn == NULL) {
+		return false;
+	}
+	if (IS_IPC(conn)) {
+		return false;
+	}
+	numtowrite = SVAL(inbuf,smb_vwv10);
+	numtowrite |= ((((size_t)SVAL(inbuf,smb_vwv9)) & 1 )<<16);
+	if (numtowrite == 0) {
+		return false;
+	}
+	/* Ensure the sizes match up. */
+	doff = SVAL(inbuf,smb_vwv11);
+
+	if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) {
+		/* no pad byte...old smbclient :-( */
+		return false;
+	}
+
+	if (len - doff != numtowrite) {
+		DEBUG(10,("is_valid_writeX_buffer: doff mismatch "
+			"len = %u, doff = %u, numtowrite = %u\n",
+			(unsigned int)len,
+			(unsigned int)doff,
+			(unsigned int)numtowrite ));
+		return false;
+	}
+
+	DEBUG(10,("is_valid_writeX_buffer: true "
+		"len = %u, doff = %u, numtowrite = %u\n",
+		(unsigned int)len,
+		(unsigned int)doff,
+		(unsigned int)numtowrite ));
+
+	return true;
+}
+
+/****************************************************************************
  Reply to a write and X.
 ****************************************************************************/
 
@@ -3875,10 +3933,18 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req)
 		numtowrite |= ((((size_t)SVAL(req->inbuf,smb_vwv9)) & 1 )<<16);
 	}
 
-	if(smb_doff > smblen || (smb_doff + numtowrite > smblen)) {
-		reply_doserror(req, ERRDOS, ERRbadmem);
-		END_PROFILE(SMBwriteX);
-		return;
+	if (req->unread_bytes) {
+	       	if (numtowrite != req->unread_bytes) {
+			reply_doserror(req, ERRDOS, ERRbadmem);
+			END_PROFILE(SMBwriteX);
+			return;
+		}
+	} else {
+		if (smb_doff > smblen || smb_doff + numtowrite > smblen) {
+			reply_doserror(req, ERRDOS, ERRbadmem);
+			END_PROFILE(SMBwriteX);
+			return;
+		}
 	}
 
 	/* If it's an IPC, pass off the pipe handler. */
@@ -3947,15 +4013,16 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req)
 		nwritten = 0;
 	} else {
 
-		if (schedule_aio_write_and_X(conn, req, fsp, data, startpos,
-					     numtowrite)) {
+		if (req->unread_bytes == 0 &&
+				schedule_aio_write_and_X(conn, req, fsp, data,
+							startpos, numtowrite)) {
 			END_PROFILE(SMBwriteX);
 			return;
 		}
 
-		nwritten = write_file(fsp,data,startpos,numtowrite);
+		nwritten = write_file(req,fsp,data,startpos,numtowrite);
 	}
-  
+
 	if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
 		reply_unixerror(req, ERRHRD, ERRdiskfull);
 		END_PROFILE(SMBwriteX);
@@ -4264,7 +4331,7 @@ void reply_writeclose(connection_struct *conn, struct smb_request *req)
 		return;
 	}
   
-	nwritten = write_file(fsp,data,startpos,numtowrite);
+	nwritten = write_file(req,fsp,data,startpos,numtowrite);
 
 	set_filetime(conn, fsp->fsp_name, mtime);
   
@@ -4726,7 +4793,7 @@ void reply_printwrite(connection_struct *conn, struct smb_request *req)
 
 	data = smb_buf(req->inbuf) + 3;
 
-	if (write_file(fsp,data,-1,numtowrite) != numtowrite) {
+	if (write_file(req,fsp,data,-1,numtowrite) != numtowrite) {
 		reply_unixerror(req, ERRHRD, ERRdiskfull);
 		END_PROFILE(SMBsplwr);
 		return;
diff --git a/source/smbd/server.c b/source/smbd/server.c
index b9ab7ef..25e2d2c 100644
--- a/source/smbd/server.c
+++ b/source/smbd/server.c
@@ -1,20 +1,22 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
    Main SMB server routines
    Copyright (C) Andrew Tridgell		1992-1998
    Copyright (C) Martin Pool			2002
    Copyright (C) Jelmer Vernooij		2002-2003
-   
+   Copyright (C) Volker Lendecke		1993-2007
+   Copyright (C) Jeremy Allison			1993-2007
+
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
@@ -37,6 +39,8 @@ extern SIG_ATOMIC_T got_sig_term;
 extern SIG_ATOMIC_T reload_after_sighup;
 static SIG_ATOMIC_T got_sig_cld;
 
+extern int smb_read_error;
+
 #ifdef WITH_DFS
 extern int dcelogin_atmost_once;
 #endif /* WITH_DFS */
@@ -60,6 +64,293 @@ static void smbd_set_server_fd(int fd)
 	client_setfd(fd);
 }
 
+/* Socket functions for smbd packet processing. */
+
+static bool valid_packet_size(len)
+{
+	/*
+	 * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
+	 * of header. Don't print the error if this fits.... JRA.
+	 */
+
+	if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
+		DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+					(unsigned long)len));
+		if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) {
+
+			/*
+			 * Correct fix. smb_read_error may have already been
+			 * set. Only set it here if not already set. Global
+			 * variables still suck :-). JRA.
+			 */
+
+			if (smb_read_error == 0)
+				smb_read_error = READ_ERROR;
+			return false;
+		}
+	}
+	return true;
+}
+
+static ssize_t read_packet_remainder(int fd,
+					char *buffer,
+					unsigned int timeout,
+					ssize_t len)
+{
+	ssize_t ret;
+
+	if(len <= 0) {
+		return len;
+	}
+
+	if (timeout > 0) {
+		ret = read_socket_with_timeout(fd,
+						buffer,
+						len,
+						len,
+						timeout);
+	} else {
+		ret = read_data(fd, buffer, len);
+	}
+
+	if (ret != len) {
+		if (smb_read_error == 0) {
+			smb_read_error = READ_ERROR;
+		}
+		return -1;
+	}
+
+	return len;
+}
+
+/****************************************************************************
+ Attempt a zerocopy writeX read. We know here that len > smb_size-4
+****************************************************************************/
+
+/*
+ * Unfortunately, earlier versions of smbclient/libsmbclient
+ * don't send this "standard" writeX header. I've fixed this
+ * for 3.2 but we'll use the old method with earlier versions.
+ * Windows and CIFSFS at least use this standard size. Not
+ * sure about MacOSX.
+ */
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+				(2*14) + /* word count (including bcc) */ \
+				1 /* pad byte */)
+
+ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
+					const char lenbuf[4],
+					int fd,
+					char **buffer,
+					unsigned int timeout,
+					size_t *p_unread)
+{
+	/* Size of a WRITEX call (+4 byte len). */
+	char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
+	ssize_t len = smb_len(lenbuf);
+	ssize_t toread;
+	ssize_t ret;
+
+	memcpy(writeX_header, lenbuf, sizeof(lenbuf));
+
+	if (timeout > 0) {
+		ret = read_socket_with_timeout(fd,
+					writeX_header + 4,
+					STANDARD_WRITE_AND_X_HEADER_SIZE,
+					STANDARD_WRITE_AND_X_HEADER_SIZE,
+					timeout);
+	} else {
+		ret = read_data(fd,
+				writeX_header+4,
+				STANDARD_WRITE_AND_X_HEADER_SIZE);
+	}
+
+	if (ret != STANDARD_WRITE_AND_X_HEADER_SIZE) {
+		if (smb_read_error == 0) {
+			smb_read_error = READ_ERROR;
+		}
+		return -1;
+	}
+
+	/*
+	 * Ok - now try and see if this is a possible
+	 * valid writeX call.
+	 */
+
+	if (is_valid_writeX_buffer(writeX_header)) {
+		/*
+		 * If the data offset is beyond what
+		 * we've read, drain the extra bytes.
+		 */
+		uint16_t doff = SVAL(writeX_header,smb_vwv11);
+		ssize_t newlen;
+
+		if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) {
+			size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE;
+			if (drain_socket(smbd_server_fd(), drain) != drain) {
+	                        smb_panic("receive_smb_raw_talloc_partial_read:"
+					" failed to drain pending bytes");
+	                }
+		} else {
+			doff = STANDARD_WRITE_AND_X_HEADER_SIZE;
+		}
+
+		/* Spoof down the length and null out the bcc. */
+		set_message_bcc(writeX_header, 0);
+		newlen = smb_len(writeX_header);
+
+		/* Copy the header we've written. */
+
+		*buffer = TALLOC_MEMDUP(mem_ctx,
+				writeX_header,
+				sizeof(writeX_header));
+
+		if (*buffer == NULL) {
+			DEBUG(0, ("Could not allocate inbuf of length %d\n",
+				  (int)sizeof(writeX_header)));
+			if (smb_read_error == 0)
+				smb_read_error = READ_ERROR;
+			return -1;
+		}
+
+		/* Work out the remaining bytes. */
+		*p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+		return newlen + 4;
+	}
+
+	if (!valid_packet_size(len)) {
+		return -1;
+	}
+
+	/*
+	 * Not a valid writeX call. Just do the standard
+	 * talloc and return.
+	 */
+
+	*buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+	if (*buffer == NULL) {
+		DEBUG(0, ("Could not allocate inbuf of length %d\n",
+			  (int)len+4));
+		if (smb_read_error == 0)
+			smb_read_error = READ_ERROR;
+		return -1;
+	}
+
+	/* Copy in what we already read. */
+	memcpy(*buffer,
+		writeX_header,
+		4 + STANDARD_WRITE_AND_X_HEADER_SIZE);
+	toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+	if(toread > 0) {
+		ret = read_packet_remainder(fd,
+			(*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+					timeout,
+					toread);
+		if (ret != toread) {
+			return -1;
+		}
+	}
+
+	return len + 4;
+}
+
+static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
+					int fd,
+					char **buffer,
+					unsigned int timeout,
+					size_t *p_unread)
+{
+	char lenbuf[4];
+	ssize_t len,ret;
+	int min_recv_size = lp_min_receive_file_size();
+
+	smb_read_error = 0;
+	*p_unread = 0;
+
+	len = read_smb_length_return_keepalive(fd, lenbuf, timeout);
+	if (len < 0) {
+		DEBUG(10,("receive_smb_raw: length < 0!\n"));
+
+		/*
+		 * Correct fix. smb_read_error may have already been
+		 * set. Only set it here if not already set. Global
+		 * variables still suck :-). JRA.
+		 */
+
+		if (smb_read_error == 0)
+			smb_read_error = READ_ERROR;
+		return -1;
+	}
+
+	if (CVAL(lenbuf,0) != SMBkeepalive &&
+			min_recv_size &&
+			len > min_recv_size &&
+			!srv_is_signing_active()) {
+
+		return receive_smb_raw_talloc_partial_read(mem_ctx,
+							lenbuf,
+							fd,
+							buffer,
+							timeout,
+							p_unread);
+	}
+
+	if (!valid_packet_size(len)) {
+		return -1;
+	}
+
+	/*
+	 * The +4 here can't wrap, we've checked the length above already.
+	 */
+
+	*buffer = TALLOC_ARRAY(mem_ctx, char, len+4);
+
+	if (*buffer == NULL) {
+		DEBUG(0, ("Could not allocate inbuf of length %d\n",
+			  (int)len+4));
+		if (smb_read_error == 0)
+			smb_read_error = READ_ERROR;
+		return -1;
+	}
+
+	memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+	ret = read_packet_remainder(fd, (*buffer)+4, timeout, len);
+	if (ret != len) {
+		return -1;
+	}
+
+	return len + 4;
+}
+
+ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer,
+			   unsigned int timeout, size_t *p_unread)
+{
+	ssize_t len;
+
+	len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout, p_unread);
+
+	if (len < 0) {
+		return -1;
+	}
+
+	/* Check the incoming SMB signature. */
+	if (!srv_check_sign_mac(*buffer, true)) {
+		DEBUG(0, ("receive_smb: SMB Signature verification failed on "
+			  "incoming packet!\n"));
+		if (smb_read_error == 0) {
+			smb_read_error = READ_BAD_SIG;
+		}
+		return -1;
+	}
+
+	return len;
+}
+
 struct event_context *smbd_event_context(void)
 {
 	static struct event_context *ctx;
diff --git a/source/smbd/vfs.c b/source/smbd/vfs.c
index e862710..c1c1939 100644
--- a/source/smbd/vfs.c
+++ b/source/smbd/vfs.c
@@ -418,11 +418,24 @@ ssize_t vfs_pread_data(files_struct *fsp, char *buf,
  Write data to a fd on the vfs.
 ****************************************************************************/
 
-ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N)
+ssize_t vfs_write_data(struct smb_request *req,
+			files_struct *fsp,
+			const char *buffer,
+			size_t N)
 {
 	size_t total=0;
 	ssize_t ret;
 
+	if (req && req->unread_bytes) {
+		SMB_ASSERT(req->unread_bytes == N);
+		req->unread_bytes = 0;
+		return SMB_VFS_RECVFILE(smbd_server_fd(),
+					fsp,
+					fsp->fh->fd,
+					(SMB_OFF_T)-1,
+					N);
+	}
+
 	while (total < N) {
 		ret = SMB_VFS_WRITE(fsp,fsp->fh->fd,buffer + total,N - total);
 
@@ -436,12 +449,25 @@ ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N)
 	return (ssize_t)total;
 }
 
-ssize_t vfs_pwrite_data(files_struct *fsp,const char *buffer,
-                size_t N, SMB_OFF_T offset)
+ssize_t vfs_pwrite_data(struct smb_request *req,
+			files_struct *fsp,
+			const char *buffer,
+			size_t N,
+			SMB_OFF_T offset)
 {
 	size_t total=0;
 	ssize_t ret;
 
+	if (req && req->unread_bytes) {
+		SMB_ASSERT(req->unread_bytes == N);
+		req->unread_bytes = 0;
+		return SMB_VFS_RECVFILE(smbd_server_fd(),
+					fsp,
+					fsp->fh->fd,
+					offset,
+					N);
+	}
+
 	while (total < N) {
 		ret = SMB_VFS_PWRITE(fsp, fsp->fh->fd, buffer + total,
                                 N - total, offset + total);


More information about the samba-technical mailing list