patch-2.4.0-test5 linux/fs/smbfs/proc.c

Next file: linux/fs/smbfs/smb_debug.h
Previous file: linux/fs/smbfs/ioctl.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test4/linux/fs/smbfs/proc.c linux/fs/smbfs/proc.c
@@ -4,9 +4,7 @@
  *  Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke
  *  Copyright (C) 1997 by Volker Lendecke
  *
- *  28/06/96 - Fixed long file name support (smb_proc_readdir_long) by Yuri Per
- *  28/09/97 - Fixed smb_d_path [now smb_build_path()] to be non-recursive
- *             by Riccardo Facchetti
+ *  Please add a note about your changes to smbfs in the ChangeLog file.
  */
 
 #include <linux/types.h>
@@ -25,10 +23,11 @@
 
 #include <asm/string.h>
 
-#define SMBFS_PARANOIA 1
-/* #define SMBFS_DEBUG_TIMESTAMP 1 */
-/* #define SMBFS_DEBUG_VERBOSE 1 */
-/* #define pr_debug printk */
+#include "smb_debug.h"
+
+/* Features. Undefine if they cause problems, this should perhaps be a
+   config option. */
+#define SMBFS_POSIX_UNLINK 1
 
 #define SMB_VWV(packet)  ((packet) + SMB_HEADER_LEN)
 #define SMB_CMD(packet)  (*(packet+8))
@@ -39,16 +38,15 @@
 #define SMB_DIRINFO_SIZE 43
 #define SMB_STATUS_SIZE  21
 
-/* yes, this deliberately has two parts */
-#define DENTRY_PATH(dentry) (dentry)->d_parent->d_name.name,(dentry)->d_name.name
-
-static int smb_proc_setattr_ext(struct smb_sb_info *, struct inode *,
-				struct smb_fattr *);
-static inline int
-min(int a, int b)
-{
-	return a < b ? a : b;
-}
+static int
+smb_proc_setattr_ext(struct smb_sb_info *, struct inode *,
+		     struct smb_fattr *);
+static int
+smb_proc_setattr_core(struct smb_sb_info *server, struct dentry *dentry,
+                      __u16 attr);
+static int
+smb_proc_do_getattr(struct smb_sb_info *server, struct dentry *dir,
+		    struct smb_fattr *fattr);
 
 static void
 str_upper(char *name, int len)
@@ -72,7 +70,9 @@
 	}
 }
 
-static void reverse_string(char *buf, int len) {
+/* reverse a string inline. This is used by the dircache walking routines */
+static void reverse_string(char *buf, int len)
+{
 	char c;
 	char *end = buf+len-1;
 
@@ -225,12 +225,10 @@
 	if ((year + 3) / 4 + 365 * year > day)
 		year--;
 	day -= (year + 3) / 4 + 365 * year;
-	if (day == 59 && !(year & 3))
-	{
+	if (day == 59 && !(year & 3)) {
 		nl_day = day;
 		month = 2;
-	} else
-	{
+	} else {
 		nl_day = (year & 3) || day <= 59 ? day : day - 1;
 		for (month = 0; month < 12; month++)
 			if (day_n[month] > nl_day)
@@ -287,16 +285,16 @@
 	return 0;
 
 bad_command:
-	printk("smb_verify: command=%x, SMB_CMD=%x??\n",
-		command, SMB_CMD(packet));
+	printk(KERN_ERR "smb_verify: command=%x, SMB_CMD=%x??\n",
+	       command, SMB_CMD(packet));
 	goto fail;
 bad_wct:
-	printk("smb_verify: command=%x, wct=%d, SMB_WCT=%d??\n",
-		command, wct, SMB_WCT(packet));
+	printk(KERN_ERR "smb_verify: command=%x, wct=%d, SMB_WCT=%d??\n",
+	       command, wct, SMB_WCT(packet));
 	goto fail;
 bad_bcc:
-	printk("smb_verify: command=%x, bcc=%d, SMB_BCC=%d??\n",
-		command, bcc, SMB_BCC(packet));
+	printk(KERN_ERR "smb_verify: command=%x, bcc=%d, SMB_BCC=%d??\n",
+	       command, bcc, SMB_BCC(packet));
 fail:
 	return -EIO;
 }
@@ -328,10 +326,10 @@
 {
 	int overhead = SMB_HEADER_LEN + 5 * sizeof(__u16) + 2 + 1 + 2;
 	int size = smb_get_xmitsize(server, overhead);
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_get_rsize: packet=%d, xmit=%d, size=%d\n",
-server->packet_size, server->opt.max_xmit, size);
-#endif
+
+	VERBOSE("packet=%d, xmit=%d, size=%d\n",
+		server->packet_size, server->opt.max_xmit, size);
+
 	return size;
 }
 
@@ -343,10 +341,10 @@
 {
 	int overhead = SMB_HEADER_LEN + 5 * sizeof(__u16) + 2 + 1 + 2;
 	int size = smb_get_xmitsize(server, overhead);
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_get_wsize: packet=%d, xmit=%d, size=%d\n",
-server->packet_size, server->opt.max_xmit, size);
-#endif
+
+	VERBOSE("packet=%d, xmit=%d, size=%d\n",
+		server->packet_size, server->opt.max_xmit, size);
+
 	return size;
 }
 
@@ -357,10 +355,8 @@
 	int error  = server->err;
 	char *class = "Unknown";
 
-#ifdef SMBFS_DEBUG_VERBOSE
-	printk("smb_errno: errcls %d  code %d  from command 0x%x\n",
+	VERBOSE("errcls %d  code %d  from command 0x%x\n",
 		errcls, error, SMB_CMD(server->packet));
-#endif
 
 	if (errcls == ERRDOS)
 		switch (error)
@@ -463,8 +459,8 @@
 		class = "ERRCMD";
 
 err_unknown:
-	printk("smb_errno: class %s, code %d from command 0x%x\n",
-		class, error, SMB_CMD(server->packet));
+	printk(KERN_ERR "smb_errno: class %s, code %d from command 0x%x\n",
+	       class, error, SMB_CMD(server->packet));
 	return EIO;
 }
 
@@ -482,10 +478,10 @@
 
 /*
  * smb_retry: This function should be called when smb_request_ok has
-   indicated an error. If the error was indicated because the
-   connection was killed, we try to reconnect. If smb_retry returns 0,
-   the error was indicated for another reason, so a retry would not be
-   of any use.
+ * indicated an error. If the error was indicated because the
+ * connection was killed, we try to reconnect. If smb_retry returns 0,
+ * the error was indicated for another reason, so a retry would not be
+ * of any use.
  * N.B. The server must be locked for this call.
  */
 static int
@@ -501,7 +497,7 @@
 
 	if (pid == 0)
 	{
-		printk("smb_retry: no connection process\n");
+		printk(KERN_ERR "smb_retry: no connection process\n");
 		server->state = CONN_RETRIED;
 		goto out;
 	}
@@ -517,29 +513,28 @@
 	error = kill_proc(pid, SIGUSR1, 1);
 	if (error)
 	{
-		printk("smb_retry: signal failed, error=%d\n", error);
+		printk(KERN_ERR "smb_retry: signal failed, error=%d\n", error);
 		goto out_restore;
 	}
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_retry: signalled pid %d, waiting for new connection\n",
-server->conn_pid);
-#endif
+	VERBOSE("signalled pid %d, waiting for new connection\n",
+		server->conn_pid);
+
 	/*
 	 * Wait for the new connection.
 	 */
 	interruptible_sleep_on_timeout(&server->wait,  5*HZ);
 	if (signal_pending(current))
-		printk("smb_retry: caught signal\n");
+		printk(KERN_INFO "smb_retry: caught signal\n");
 
 	/*
 	 * Check for a valid connection.
 	 */
 	if (server->state == CONN_VALID)
 	{
-#ifdef SMBFS_PARANOIA
-printk("smb_retry: new pid=%d, generation=%d\n",
-server->conn_pid, server->generation);
-#endif
+		/* This should be changed to VERBOSE, except many smbfs
+		   problems is with the userspace daemon not reconnecting. */
+		PARANOIA("sucessful, new pid=%d, generation=%d\n",
+			 server->conn_pid, server->generation);
 		result = 1;
 	}
 
@@ -570,20 +565,18 @@
 	/* Make sure we have a connection */
 	if (s->state != CONN_VALID)
 	{
- 		if (!smb_retry(s))
+		if (!smb_retry(s))
 			goto out;
 	}
 
 	if (smb_request(s) < 0)
 	{
-		pr_debug("smb_request failed\n");
+		DEBUG1("smb_request failed\n");
 		goto out;
 	}
 	if (smb_valid_packet(s->packet) != 0)
 	{
-#ifdef SMBFS_PARANOIA
-printk("smb_request_ok: invalid packet!\n");
-#endif
+		PARANOIA("invalid packet!\n");
 		goto out;
 	}
 
@@ -596,7 +589,7 @@
 	{
 		result = -smb_errno(s);
 		if (!result)
-			printk("smb_request_ok: rcls=%d, err=%d mapped to 0\n",
+			printk(KERN_DEBUG "smb_request_ok: rcls=%d, err=%d mapped to 0\n",
 				s->rcls, s->err);
 		/*
 		 * Exit now even if the error was squashed ...
@@ -624,9 +617,8 @@
 	struct file *filp;
 	int error;
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_newconn: fd=%d, pid=%d\n", opt->fd, current->pid);
-#endif
+	VERBOSE("fd=%d, pid=%d\n", opt->fd, current->pid);
+
 	/*
 	 * Make sure we don't already have a pid ...
 	 */
@@ -666,15 +658,13 @@
 	    !(server->opt.capabilities & SMB_CAP_NT_SMBS)) {
 		server->mnt->version |= SMB_FIX_WIN95;
 #ifdef SMBFS_DEBUG_VERBOSE
-		printk("smb_newconn: detected WIN95 server\n");
+		printk(KERN_NOTICE "smb_newconn: detected WIN95 server\n");
 #endif
 	}
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_newconn: protocol=%d, max_xmit=%d, pid=%d capabilities=0x%x\n",
-       server->opt.protocol, server->opt.max_xmit, server->conn_pid,
-       server->opt.capabilities);
-#endif
+	VERBOSE("protocol=%d, max_xmit=%d, pid=%d capabilities=0x%x\n",
+		server->opt.protocol, server->opt.max_xmit, server->conn_pid,
+		server->opt.capabilities);
 
 out:
 	wake_up_interruptible(&server->wait);
@@ -695,9 +685,10 @@
 	__u8 *p = server->packet;
 	__u8 *buf = server->packet;
 
-if (xmit_len > server->packet_size)
-printk("smb_setup_header: Aieee, xmit len > packet! len=%d, size=%d\n",
-xmit_len, server->packet_size);
+	if (xmit_len > server->packet_size)
+		printk(KERN_DEBUG "smb_setup_header: "
+		       "Aieee, xmit len > packet! len=%d, size=%d\n",
+		       xmit_len, server->packet_size);
 
 	p = smb_encode_smb_length(p, xmit_len - 4);
 
@@ -778,10 +769,8 @@
 		if (mode == read_write &&
 		    (error == -EACCES || error == -ETXTBSY || error == -EROFS))
 		{
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_open: %s/%s R/W failed, error=%d, retrying R/O\n",
-       DENTRY_PATH(dentry), error);
-#endif
+			VERBOSE("%s/%s R/W failed, error=%d, retrying R/O\n",
+				DENTRY_PATH(dentry), error);
 			mode = read_only;
 			goto retry;
 		}
@@ -813,7 +802,7 @@
 	result = -ENOENT;
 	if (!inode)
 	{
-		printk("smb_open: no inode for dentry %s/%s\n",
+		printk(KERN_ERR "smb_open: no inode for dentry %s/%s\n",
 		       DENTRY_PATH(dentry));
 		goto out;
 	}
@@ -828,10 +817,8 @@
 		smb_unlock_server(server);
 		if (result)
 		{
-#ifdef SMBFS_PARANOIA
-printk("smb_open: %s/%s open failed, result=%d\n",
-       DENTRY_PATH(dentry), result);
-#endif
+			PARANOIA("%s/%s open failed, result=%d\n",
+				 DENTRY_PATH(dentry), result);
 			goto out;
 		}
 		/*
@@ -847,10 +834,8 @@
 	if (inode->u.smbfs_i.access != wish && 
 	    inode->u.smbfs_i.access != SMB_O_RDWR)
 	{
-#ifdef SMBFS_PARANOIA
-printk("smb_open: %s/%s access denied, access=%x, wish=%x\n",
-       DENTRY_PATH(dentry), inode->u.smbfs_i.access, wish);
-#endif
+		PARANOIA("%s/%s access denied, access=%x, wish=%x\n",
+			 DENTRY_PATH(dentry), inode->u.smbfs_i.access, wish);
 		result = -EACCES;
 	}
 out:
@@ -962,7 +947,7 @@
 {
 	struct smb_sb_info *server = server_from_dentry(dentry);
 	__u16 returned_count, data_len;
-	char *buf;
+	unsigned char *buf;
 	int result;
 
 	smb_lock_server(server);
@@ -980,10 +965,19 @@
 
 	buf = SMB_BUF(server->packet);
 	data_len = WVAL(buf, 1);
+
+	/* we can NOT simply trust the data_len given by the server ... */
+	if (data_len > server->packet_size - (buf+3 - server->packet)) {
+		printk(KERN_ERR "smb_proc_read: invalid data length!! "
+		       "%d > %d - (%p - %p)\n",
+		       data_len, server->packet_size, buf+3, server->packet);
+		result = -EIO;
+		goto out;
+	}
+
 	memcpy(data, buf+3, data_len);
 
-	if (returned_count != data_len)
-	{
+	if (returned_count != data_len) {
 		printk(KERN_NOTICE "smb_proc_read: returned != data_len\n");
 		printk(KERN_NOTICE "smb_proc_read: ret_c=%d, data_len=%d\n",
 		       returned_count, data_len);
@@ -991,10 +985,8 @@
 	result = data_len;
 
 out:
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_read: file %s/%s, count=%d, result=%d\n",
-       DENTRY_PATH(dentry), count, result);
-#endif
+	VERBOSE("file %s/%s, count=%d, result=%d\n",
+		DENTRY_PATH(dentry), count, result);
 	smb_unlock_server(server);
 	return result;
 }
@@ -1005,11 +997,10 @@
 	struct smb_sb_info *server = server_from_dentry(dentry);
 	int result;
 	__u8 *p;
+	
+	VERBOSE("file %s/%s, count=%d@%ld, packet_size=%d\n",
+		DENTRY_PATH(dentry), count, offset, server->packet_size);
 
-#if SMBFS_DEBUG_VERBOSE
-printk("smb_proc_write: file %s/%s, count=%d@%ld, packet_size=%d\n",
-       DENTRY_PATH(dentry), count, offset, server->packet_size);
-#endif
 	smb_lock_server(server);
 	p = smb_setup_header(server, SMBwrite, 5, count + 3);
 	WSET(server->packet, smb_vwv0, dentry->d_inode->u.smbfs_i.fileid);
@@ -1072,7 +1063,7 @@
 
       retry:
 	p = smb_setup_header(server, SMBmv, 1, 0);
-	WSET(server->packet, smb_vwv0, aSYSTEM | aHIDDEN);
+	WSET(server->packet, smb_vwv0, aSYSTEM | aHIDDEN | aDIR);
 	*p++ = 4;
 	p = smb_encode_path(server, p, old_dentry, NULL);
 	*p++ = 4;
@@ -1134,10 +1125,37 @@
 	return smb_proc_generic_command(dentry, SMBrmdir);
 }
 
+#if SMBFS_POSIX_UNLINK
+/*
+ * Removes readonly attribute from a file. Used by unlink to give posix
+ * semantics.
+ * Note: called with the server locked.
+ */
+static int
+smb_set_rw(struct dentry *dentry,struct smb_sb_info *server)
+{
+	int result;
+	struct smb_fattr fattr;
+
+	/* first get current attribute */
+	result = smb_proc_do_getattr(server, dentry, &fattr);
+	if (result < 0)
+		return result;
+
+	/* if RONLY attribute is set, remove it */
+	if (fattr.attr & aRONLY) {  /* read only attribute is set */
+		fattr.attr &= ~aRONLY;
+		result = smb_proc_setattr_core(server, dentry, fattr.attr);
+	}
+	return result;
+}
+#endif
+
 int
 smb_proc_unlink(struct dentry *dentry)
 {
 	struct smb_sb_info *server = server_from_dentry(dentry);
+	int flag = 0;
 	char *p;
 	int result;
 
@@ -1152,6 +1170,28 @@
 
 	if ((result = smb_request_ok(server, SMBunlink, 0, 0)) < 0)
 	{
+#if SMBFS_POSIX_UNLINK
+		if (result == -EACCES && !flag) {
+			/* Posix semantics is for the read-only state
+			   of a file to be ignored in unlink(). In the
+			   SMB world a unlink() is refused on a
+			   read-only file. To make things easier for
+			   unix users we try to override the files
+			   permission if the unlink fails with the
+			   right error.
+			   This introduces a race condition that could
+			   lead to a file being written by someone who
+			   shouldn't have access, but as far as I can
+			   tell that is unavoidable */
+
+			/* remove RONLY attribute and try again */
+			result = smb_set_rw(dentry,server);
+			if (result == 0) {
+				flag = 1;
+				goto retry;
+			}
+		}
+#endif
 		if (smb_retry(server))
 			goto retry;
 		goto out;
@@ -1257,9 +1297,8 @@
 	entry->name = p + 9;
 	len = strlen(entry->name);
 	if (len > 12)
-	{
 		len = 12;
-	}
+
 	/*
 	 * Trim trailing blanks for Pathworks servers
 	 */
@@ -1267,8 +1306,7 @@
 		len--;
 	entry->len = len;
 
-	switch (server->opt.case_handling)
-	{
+	switch (server->opt.case_handling) {
 	case SMB_CASE_UPPER:
 		str_upper(entry->name, len);
 		break;
@@ -1278,7 +1316,7 @@
 	default:
 		break;
 	}
-	pr_debug("smb_decode_dirent: len=%d, name=%s\n", len, entry->name);
+	DEBUG1("len=%d, name=%.*s\n", len, len, entry->name);
 	return p + 22;
 }
 
@@ -1290,7 +1328,7 @@
 smb_proc_readdir_short(struct smb_sb_info *server, struct dentry *dir, int fpos,
 		       void *cachep)
 {
-	char *p;
+	unsigned char *p;
 	int result;
 	int i, first, entries_seen, entries;
 	int entries_asked = (server->opt.max_xmit - 100) / SMB_DIRINFO_SIZE;
@@ -1298,36 +1336,31 @@
 	__u16 count;
 	char status[SMB_STATUS_SIZE];
 	static struct qstr mask = { "*.*", 3, 0 };
+	unsigned char *last_status;
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir_short: %s/%s, pos=%d\n",
-       DENTRY_PATH(dir), fpos);
-#endif
+	VERBOSE("%s/%s, pos=%d\n", DENTRY_PATH(dir), fpos);
 
 	smb_lock_server(server);
 
 	/* N.B. We need to reinitialize the cache to restart */
-      retry:
+retry:
 	smb_init_dircache(cachep);
 	first = 1;
 	entries = 0;
 	entries_seen = 2; /* implicit . and .. */
 
-	while (1)
-	{
+	while (1) {
 		p = smb_setup_header(server, SMBsearch, 2, 0);
 		WSET(server->packet, smb_vwv0, entries_asked);
 		WSET(server->packet, smb_vwv1, aDIR);
 		*p++ = 4;
-		if (first == 1)
-		{
+		if (first == 1) {
 			p = smb_encode_path(server, p, dir, &mask);
 			*p++ = 5;
 			WSET(p, 0, 0);
 			p += 2;
 			first = 0;
-		} else
-		{
+		} else {
 			*p++ = 0;
 			*p++ = 5;
 			WSET(p, 0, SMB_STATUS_SIZE);
@@ -1339,8 +1372,7 @@
 		smb_setup_bcc(server, p);
 
 		result = smb_request_ok(server, SMBsearch, 1, -1);
-		if (result < 0)
-		{
+		if (result < 0) {
 			if ((server->rcls == ERRDOS) && 
 			    (server->err  == ERRnofiles))
 				break;
@@ -1359,57 +1391,63 @@
 			goto unlock_return;
 		p += 7;
 
+
+		/* Make sure the response fits in the buffer. Fixed sized 
+		   entries means we don't have to check in the decode loop. */
+
+		last_status = SMB_BUF(server->packet) + 3 + (count - 1) *
+			SMB_DIRINFO_SIZE;
+
+		if (last_status + SMB_DIRINFO_SIZE >=
+		    server->packet + server->packet_size) {
+			printk(KERN_ERR "smb_proc_readdir_short: "
+			       "last dir entry outside buffer! "
+			       "%d@%p  %d@%p\n", SMB_DIRINFO_SIZE, last_status,
+			       server->packet_size, server->packet);
+			goto unlock_return;
+		}
+
 		/* Read the last entry into the status field. */
-		memcpy(status,
-		       SMB_BUF(server->packet) + 3 +
-		       (count - 1) * SMB_DIRINFO_SIZE,
-		       SMB_STATUS_SIZE);
+		memcpy(status, last_status, SMB_STATUS_SIZE);
+
 
 		/* Now we are ready to parse smb directory entries. */
 
-		for (i = 0; i < count; i++)
-		{
+		for (i = 0; i < count; i++) {
 			struct cache_dirent this_ent, *entry = &this_ent;
 
 			p = smb_decode_dirent(server, p, entry);
-			if (entries_seen == 2 && entry->name[0] == '.')
-			{
+			if (entries_seen == 2 && entry->name[0] == '.') {
 				if (entry->len == 1)
 					continue;
 				if (entry->name[1] == '.' && entry->len == 2)
 					continue;
 			}
-			if (entries_seen >= fpos)
-			{
-				pr_debug("smb_proc_readdir: fpos=%u\n", 
-					entries_seen);
+			if (entries_seen >= fpos) {
+				DEBUG1("fpos=%u\n", entries_seen);
 				smb_add_to_cache(cachep, entry, entries_seen);
 				entries++;
-			} else
-			{
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir: skipped, seen=%d, i=%d, fpos=%d\n",
-entries_seen, i, fpos);
-#endif
+			} else {
+				VERBOSE("skipped, seen=%d, i=%d, fpos=%d\n",
+					entries_seen, i, fpos);
 			}
 			entries_seen++;
 		}
 	}
 	result = entries;
 
-    unlock_return:
+unlock_return:
 	smb_unlock_server(server);
 	return result;
 }
 
 /*
  * Interpret a long filename structure using the specified info level:
- *   level 1   -- Win NT, Win 95, OS/2
- *   level 259 -- File name and length only, Win NT, Win 95
+ *   level 1 for anything below NT1 protocol
+ *   level 260 for NT1 protocol
  *
  * We return a reference to the name string to avoid copying, and perform
- * any needed upper/lower casing in place.  Note!! Level 259 entries may
- * not have any space beyond the name, so don't try to write a null byte!
+ * any needed upper/lower casing in place.
  *
  * Bugs Noted:
  * (1) Win NT 4.0 appends a null byte to names and counts it in the length!
@@ -1426,43 +1464,35 @@
 	 */
 	entry->ino = 0;
 
-	switch (level)
-	{
+	switch (level) {
 	case 1:
-		len = *((unsigned char *) p + 26);
+		len = *((unsigned char *) p + 22);
 		entry->len = len;
-		entry->name = p + 27;
-		result = p + 28 + len;
-		break;
+		entry->name = p + 23;
+		result = p + 24 + len;
 
-	case 259: /* SMB_FIND_FILE_NAMES_INFO = 0x103 */
-		result = p + DVAL(p, 0);
-		/* DVAL(p, 4) should be resume key? Seems to be 0 .. */
-		len = DVAL(p, 8);
-		if (len > 255)
-			len = 255;
-		entry->name = p + 12;
-		/*
-		 * Kludge alert: Win NT 4.0 adds a trailing null byte and
-		 * counts it in the name length, but Win 95 doesn't.  Hence
-		 * we test for a trailing null and decrement the length ...
-		 */
+		VERBOSE("info 1 at %p, len=%d, name=%.*s\n",
+			p, entry->len, entry->len, entry->name);
+		break;
+	case 260:
+		result = p + WVAL(p, 0);
+		len = DVAL(p, 60);
+		if (len > 255) len = 255;
+		/* NT4 null terminates */
+		entry->name = p + 94;
 		if (len && entry->name[len-1] == '\0')
 			len--;
 		entry->len = len;
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_decode_long_dirent: info 259 at %p, len=%d, name=%s\n",
-p, len, entry->name);
-#endif
-		break;
 
+		VERBOSE("info 260 at %p, len=%d, name=%.*s\n",
+			p, entry->len, entry->len, entry->name);
+		break;
 	default:
-		printk("smb_decode_long_dirent: Unknown level %d\n", level);
+		PARANOIA("Unknown info level %d\n", level);
 		result = p + WVAL(p, 0);
 	}
 
-	switch (server->opt.case_handling)
-	{
+	switch (server->opt.case_handling) {
 	case SMB_CASE_UPPER:
 		str_upper(entry->name, len);
 		break;
@@ -1476,7 +1506,17 @@
 	return result;
 }
 
+/* findfirst/findnext flags */
+#define SMB_CLOSE_AFTER_FIRST (1<<0)
+#define SMB_CLOSE_IF_END (1<<1)
+#define SMB_REQUIRE_RESUME_KEY (1<<2)
+#define SMB_CONTINUE_BIT (1<<3)
+
 /*
+ * Note: samba-2.0.7 (at least) has a very similar routine, cli_list, in
+ * source/libsmb/clilist.c. When looking for smb bugs in the readdir code,
+ * go there for advise.
+ *
  * Bugs Noted:
  * (1) When using Info Level 1 Win NT 4.0 truncates directory listings 
  * for certain patterns of names and/or lengths. The breakage pattern
@@ -1487,19 +1527,19 @@
 smb_proc_readdir_long(struct smb_sb_info *server, struct dentry *dir, int fpos,
 		      void *cachep)
 {
-	char *p, *mask, *lastname, *param = server->temp_buf;
+	unsigned char *p;
+	char *mask, *lastname, *param = server->temp_buf;
 	__u16 command;
 	int first, entries, entries_seen;
 
 	/* Both NT and OS/2 accept info level 1 (but see note below). */
-	int info_level = 1;
+	int info_level = 260;
 	const int max_matches = 512;
 
 	unsigned char *resp_data = NULL;
 	unsigned char *resp_param = NULL;
 	int resp_data_len = 0;
 	int resp_param_len = 0;
-	int ff_resume_key = 0; /* this isn't being used */
 	int ff_searchcount = 0;
 	int ff_eos = 0;
 	int ff_lastname = 0;
@@ -1509,27 +1549,22 @@
 	static struct qstr star = { "*", 1, 0 };
 
 	/*
-	 * Check whether to change the info level.  There appears to be
-	 * a bug in Win NT 4.0's handling of info level 1, whereby it
-	 * truncates the directory scan for certain patterns of files.
-	 * Hence we use level 259 for NT.
+	 * use info level 1 for older servers that don't do 260
 	 */
-	if (server->opt.protocol >= SMB_PROTOCOL_NT1 &&
-	    !(server->mnt->version & SMB_FIX_WIN95))
-		info_level = 259;
+	if (server->opt.protocol < SMB_PROTOCOL_NT1)
+		info_level = 1;
 
 	smb_lock_server(server);
 
-      retry:
+retry:
 	/*
 	 * Encode the initial path
 	 */
 	mask = param + 12;
 	mask_len = smb_encode_path(server, mask, dir, &star) - mask;
 	first = 1;
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir_long: starting fpos=%d, mask=%s\n", fpos, mask);
-#endif
+	VERBOSE("starting fpos=%d, mask=%s\n", fpos, mask);
+
 	/*
 	 * We must reinitialize the dircache when retrying.
 	 */
@@ -1538,49 +1573,33 @@
 	entries_seen = 2;
 	ff_eos = 0;
 
-	while (ff_eos == 0)
-	{
+	while (ff_eos == 0) {
 		loop_count += 1;
-		if (loop_count > 200)
-		{
+		if (loop_count > 10) {
 			printk(KERN_WARNING "smb_proc_readdir_long: "
 			       "Looping in FIND_NEXT??\n");
 			entries = -EIO;
 			break;
 		}
 
-		if (first != 0)
-		{
+		if (first != 0) {
 			command = TRANSACT2_FINDFIRST;
 			WSET(param, 0, aSYSTEM | aHIDDEN | aDIR);
 			WSET(param, 2, max_matches);	/* max count */
-			WSET(param, 4, 8 + 4 + 2);	/* resume required +
-							   close on end +
-							   continue */
+			WSET(param, 4, SMB_CLOSE_IF_END);
 			WSET(param, 6, info_level);
 			DSET(param, 8, 0);
-		} else
-		{
+		} else {
 			command = TRANSACT2_FINDNEXT;
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir_long: handle=0x%X, resume=%d, lastname=%d, mask=%s\n",
-ff_dir_handle, ff_resume_key, ff_lastname, mask);
-#endif
+
+			VERBOSE("handle=0x%X, lastname=%d, mask=%s\n",
+				ff_dir_handle, ff_lastname, mask);
+
 			WSET(param, 0, ff_dir_handle);	/* search handle */
 			WSET(param, 2, max_matches);	/* max count */
 			WSET(param, 4, info_level);
-			DSET(param, 6, ff_resume_key);	/* ff_resume_key */
-			WSET(param, 10, 8 + 4 + 2);	/* resume required +
-							   close on end +
-							   continue */
-			if (server->mnt->version & SMB_FIX_WIN95)
-			{
-				/* Windows 95 is not able to deliver answers
-				 * to FIND_NEXT fast enough, so sleep 0.2 sec
-				 */
-				current->state = TASK_INTERRUPTIBLE;
-				schedule_timeout(HZ/5);
-			}
+			DSET(param, 6, 0);
+			WSET(param, 10, SMB_CONTINUE_BIT|SMB_CLOSE_IF_END);
 		}
 
 		result = smb_trans2_request(server, command,
@@ -1588,65 +1607,59 @@
 					    &resp_data_len, &resp_data,
 					    &resp_param_len, &resp_param);
 
-		if (result < 0)
-		{
-			if (smb_retry(server))
-			{
-#ifdef SMBFS_PARANOIA
-printk("smb_proc_readdir_long: error=%d, retrying\n", result);
-#endif
+		if (result < 0) {
+			if (smb_retry(server)) {
+				PARANOIA("error=%d, retrying\n", result);
 				goto retry;
 			}
-#ifdef SMBFS_PARANOIA
-printk("smb_proc_readdir_long: error=%d, breaking\n", result);
-#endif
+			PARANOIA("error=%d, breaking\n", result);
 			entries = result;
 			break;
 		}
-		if (server->rcls != 0)
-		{ 
-#ifdef SMBFS_PARANOIA
-printk("smb_proc_readdir_long: name=%s, entries=%d, rcls=%d, err=%d\n",
-mask, entries, server->rcls, server->err);
-#endif
+
+		if (server->rcls == ERRSRV && server->err == ERRerror) {
+			/* a damn Win95 bug - sometimes it clags if you 
+			   ask it too fast */
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(HZ/5);
+			continue;
+                }
+
+		if (server->rcls != 0) { 
+			PARANOIA("name=%s, entries=%d, rcls=%d, err=%d\n",
+				 mask, entries, server->rcls, server->err);
 			entries = -smb_errno(server);
 			break;
 		}
 
 		/* parse out some important return info */
-		if (first != 0)
-		{
+		if (first != 0) {
 			ff_dir_handle = WVAL(resp_param, 0);
 			ff_searchcount = WVAL(resp_param, 2);
 			ff_eos = WVAL(resp_param, 4);
 			ff_lastname = WVAL(resp_param, 8);
-		} else
-		{
+		} else {
 			ff_searchcount = WVAL(resp_param, 0);
 			ff_eos = WVAL(resp_param, 2);
 			ff_lastname = WVAL(resp_param, 6);
 		}
 
 		if (ff_searchcount == 0)
-		{
 			break;
-		}
 
 		/* we might need the lastname for continuations */
 		mask_len = 0;
-		if (ff_lastname > 0)
-		{
+		if (ff_lastname > 0) {
 			lastname = resp_data + ff_lastname;
-			switch (info_level)
-			{
-			case 259:
- 				if (ff_lastname < resp_data_len)
+			switch (info_level) {
+			case 260:
+				if (ff_lastname < resp_data_len)
 					mask_len = resp_data_len - ff_lastname;
 				break;
 			case 1:
 				/* Win NT 4.0 doesn't set the length byte */
 				lastname++;
- 				if (ff_lastname + 2 < resp_data_len)
+				if (ff_lastname + 2 < resp_data_len)
 					mask_len = strlen(lastname);
 				break;
 			}
@@ -1657,49 +1670,53 @@
 				mask_len = 255;
 			if (mask_len)
 				strncpy(mask, lastname, mask_len);
-			ff_resume_key = 0;
 		}
 		mask[mask_len] = 0;
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir_long: new mask, len=%d@%d, mask=%s\n",
-mask_len, ff_lastname, mask);
-#endif
+		VERBOSE("new mask, len=%d@%d, mask=%s\n",
+			mask_len, ff_lastname, mask);
+
 		/* Now we are ready to parse smb directory entries. */
 
 		/* point to the data bytes */
 		p = resp_data;
-		for (i = 0; i < ff_searchcount; i++)
-		{
+		for (i = 0; i < ff_searchcount; i++) {
 			struct cache_dirent this_ent, *entry = &this_ent;
 
+			/* make sure we stay within the buffer */
+			if (p >= resp_data + resp_data_len) {
+				printk(KERN_ERR "smb_proc_readdir_long: "
+				       "dirent pointer outside buffer! "
+				       "%p  %d@%p  %d@%p\n",
+				       p, resp_data_len, resp_data,
+				       server->packet_size, server->packet);
+				result = -EIO; /* always a comm. error? */
+				goto unlock_return;
+			}
+
 			p = smb_decode_long_dirent(server, p, entry,
 							info_level);
 
-			pr_debug("smb_readdir_long: got %s\n", entry->name);
-
 			/* ignore . and .. from the server */
-			if (entries_seen == 2 && entry->name[0] == '.')
-			{
+			if (entries_seen == 2 && entry->name[0] == '.') {
 				if (entry->len == 1)
 					continue;
 				if (entry->name[1] == '.' && entry->len == 2)
 					continue;
 			}
-			if (entries_seen >= fpos)
-			{
+			if (entries_seen >= fpos) {
 				smb_add_to_cache(cachep, entry, entries_seen);
 				entries += 1;
 			}
  			entries_seen++;
 		}
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_readdir_long: received %d entries, eos=%d, resume=%d\n",
-ff_searchcount, ff_eos, ff_resume_key);
-#endif
+		VERBOSE("received %d entries, eos=%d\n", ff_searchcount,ff_eos);
+
 		first = 0;
+		loop_count = 0;
 	}
 
+unlock_return:
 	smb_unlock_server(server);
 	return entries;
 }
@@ -1737,9 +1754,7 @@
 
 retry:
 	mask_len = smb_encode_path(server, mask, dentry, NULL) - mask;
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_getattr_ff: name=%s, len=%d\n", mask, mask_len);
-#endif
+	VERBOSE("name=%s, len=%d\n", mask, mask_len);
 	WSET(param, 0, aSYSTEM | aHIDDEN | aDIR);
 	WSET(param, 2, 1);	/* max count */
 	WSET(param, 4, 1);	/* close after this call */
@@ -1760,9 +1775,9 @@
 	{ 
 		result = -smb_errno(server);
 #ifdef SMBFS_PARANOIA
-if (result != -ENOENT)
-printk("smb_proc_getattr_ff: error for %s, rcls=%d, err=%d\n",
-mask, server->rcls, server->err);
+		if (result != -ENOENT)
+			PARANOIA("error for %s, rcls=%d, err=%d\n",
+				 mask, server->rcls, server->err);
 #endif
 		goto out;
 	}
@@ -1770,10 +1785,8 @@
 	result = -EINVAL;
 	if (resp_data_len < 22 || WVAL(resp_param, 2) != 1)
 	{
-#ifdef SMBFS_PARANOIA
-printk("smb_proc_getattr_ff: bad result for %s, len=%d, count=%d\n",
-mask, resp_data_len, WVAL(resp_param, 2));
-#endif
+		PARANOIA("bad result for %s, len=%d, count=%d\n",
+			 mask, resp_data_len, WVAL(resp_param, 2));
 		goto out;
 	}
 
@@ -1791,10 +1804,8 @@
 	date = WVAL(resp_data, 8);
 	time = WVAL(resp_data, 10);
 	fattr->f_mtime = date_dos2unix(server, date, time);
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_getattr_ff: name=%s, date=%x, time=%x, mtime=%ld\n",
-mask, date, time, fattr->f_mtime);
-#endif
+	VERBOSE("name=%s, date=%x, time=%x, mtime=%ld\n",
+		mask, date, time, fattr->f_mtime);
 	fattr->f_size = DVAL(resp_data, 12);
 	/* ULONG allocation size */
 	fattr->attr = WVAL(resp_data, 20);
@@ -1832,8 +1843,8 @@
 	fattr->f_ctime = fattr->f_mtime; 
 	fattr->f_atime = fattr->f_mtime; 
 #ifdef SMBFS_DEBUG_TIMESTAMP
-printk("getattr_core: %s/%s, mtime=%ld\n",
-       DENTRY_PATH(dir), fattr->f_mtime);
+	printk("getattr_core: %s/%s, mtime=%ld\n",
+	       DENTRY_PATH(dir), fattr->f_mtime);
 #endif
 	result = 0;
 
@@ -1877,20 +1888,16 @@
 	}
 	if (server->rcls != 0)
 	{
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_getattr_trans2: for %s: result=%d, rcls=%d, err=%d\n",
-&param[6], result, server->rcls, server->err);
-#endif
+		VERBOSE("for %s: result=%d, rcls=%d, err=%d\n",
+			&param[6], result, server->rcls, server->err);
 		result = -smb_errno(server);
 		goto out;
 	}
 	result = -ENOENT;
 	if (resp_data_len < 22)
 	{
-#ifdef SMBFS_PARANOIA
-printk("smb_proc_getattr_trans2: not enough data for %s, len=%d\n",
-&param[6], resp_data_len);
-#endif
+		PARANOIA("not enough data for %s, len=%d\n",
+			 &param[6], resp_data_len);
 		goto out;
 	}
 
@@ -1914,8 +1921,8 @@
 	time = WVAL(resp_data, 8 + off_time);
 	attr->f_mtime = date_dos2unix(server, date, time);
 #ifdef SMBFS_DEBUG_TIMESTAMP
-printk("getattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n",
-       DENTRY_PATH(dir), date, time, attr->f_mtime);
+	printk(KERN_DEBUG "getattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n",
+	       DENTRY_PATH(dir), date, time, attr->f_mtime);
 #endif
 	attr->f_size = DVAL(resp_data, 12);
 	attr->attr = WVAL(resp_data, 20);
@@ -1925,13 +1932,15 @@
 	return result;
 }
 
-int
-smb_proc_getattr(struct dentry *dir, struct smb_fattr *fattr)
+/*
+ * Note: called with the server locked
+ */
+static int
+smb_proc_do_getattr(struct smb_sb_info *server, struct dentry *dir,
+		    struct smb_fattr *fattr)
 {
-	struct smb_sb_info *server = server_from_dentry(dir);
 	int result;
 
-	smb_lock_server(server);
 	smb_init_dirent(server, fattr);
 
 	/*
@@ -1940,7 +1949,8 @@
 	if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2) {
 		/*
 		 * Win 95 appears to break with the trans2 getattr.
- 	 	 */
+		 * Note: mnt->version options are set at mount time (inode.c)
+		 */
 		if (server->mnt->version & (SMB_FIX_OLDATTR|SMB_FIX_WIN95))
 			goto core_attr;
 		if (server->mnt->version & SMB_FIX_DIRATTR)
@@ -1953,7 +1963,17 @@
 	}
 
 	smb_finish_dirent(server, fattr);
+	return result;
+}
+
+int
+smb_proc_getattr(struct dentry *dir, struct smb_fattr *fattr)
+{
+	struct smb_sb_info *server = server_from_dentry(dir);
+	int result;
 
+	smb_lock_server(server);
+	result = smb_proc_do_getattr(server, dir, fattr);
 	smb_unlock_server(server);
 	return result;
 }
@@ -2016,10 +2036,8 @@
 	struct smb_sb_info *server = server_from_dentry(dir);
 	int result;
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_setattr: setting %s/%s, open=%d\n", 
-       DENTRY_PATH(dir), smb_is_open(dir->d_inode));
-#endif
+	VERBOSE("setting %s/%s, open=%d\n", 
+		DENTRY_PATH(dir), smb_is_open(dir->d_inode));
 	smb_lock_server(server);
 	result = smb_proc_setattr_core(server, dir, fattr->attr);
 	smb_unlock_server(server);
@@ -2050,8 +2068,8 @@
 	WSET(server->packet, smb_vwv5, date);
 	WSET(server->packet, smb_vwv6, time);
 #ifdef SMBFS_DEBUG_TIMESTAMP
-printk("smb_proc_setattr_ext: date=%d, time=%d, mtime=%ld\n", 
-date, time, fattr->f_mtime);
+	printk(KERN_DEBUG "smb_proc_setattr_ext: date=%d, time=%d, mtime=%ld\n",
+	       date, time, fattr->f_mtime);
 #endif
 
 	result = smb_request_ok(server, SMBsetattrE, 0, 0);
@@ -2100,8 +2118,8 @@
 	WSET(data, 8, date);
 	WSET(data, 10, time);
 #ifdef SMBFS_DEBUG_TIMESTAMP
-printk("setattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n", 
-       DENTRY_PATH(dir), date, time, fattr->f_mtime);
+	printk(KERN_DEBUG "setattr_trans2: %s/%s, date=%x, time=%x, mtime=%ld\n", 
+	       DENTRY_PATH(dir), date, time, fattr->f_mtime);
 #endif
 	DSET(data, 12, 0); /* size */
 	DSET(data, 16, 0); /* blksize */
@@ -2146,10 +2164,9 @@
 	struct inode *inode = dentry->d_inode;
 	int result;
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_proc_settime: setting %s/%s, open=%d\n", 
-       DENTRY_PATH(dentry), smb_is_open(inode));
-#endif
+	VERBOSE("setting %s/%s, open=%d\n",
+		DENTRY_PATH(dentry), smb_is_open(inode));
+
 	smb_lock_server(server);
 	/* setting the time on a Win95 server fails (tridge) */
 	if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2 && 
@@ -2160,8 +2177,7 @@
 			result = smb_proc_setattr_ext(server, inode, fattr);
 		else
 			result = smb_proc_setattr_trans2(server, dentry, fattr);
-	} else
-	{
+	} else {
 		/*
 		 * Fail silently on directories ... timestamp can't be set?
 		 */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)