diff -u --recursive --new-file v2.1.57/linux/CREDITS linux/CREDITS
--- v2.1.57/linux/CREDITS	Wed Sep 24 20:05:45 1997
+++ linux/CREDITS	Mon Oct 13 11:46:49 1997
@@ -632,8 +632,9 @@
 N: Nick Holloway
 E: Nick.Holloway@alfie.demon.co.uk
 E: Nick.Holloway@parallax.co.uk
-D: Small patches for kernel, libc
-D: MAKEDEV
+W: http://www.alfie.demon.co.uk/
+P: 1024/75C49395 3A F0 E3 4E B7 9F E0 7E  47 A3 B0 D5 68 6A C2 FB
+D: Occasional Linux hacker...
 S: 15 Duke Street
 S: Chapelfields
 S: Coventry
diff -u --recursive --new-file v2.1.57/linux/Makefile linux/Makefile
--- v2.1.57/linux/Makefile	Wed Sep 24 20:05:45 1997
+++ linux/Makefile	Mon Oct 13 16:50:49 1997
@@ -1,6 +1,6 @@
 VERSION = 2
 PATCHLEVEL = 1
-SUBLEVEL = 57
+SUBLEVEL = 58
 
 ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/)
 
diff -u --recursive --new-file v2.1.57/linux/arch/i386/mm/fault.c linux/arch/i386/mm/fault.c
--- v2.1.57/linux/arch/i386/mm/fault.c	Mon Aug 18 18:19:42 1997
+++ linux/arch/i386/mm/fault.c	Tue Oct 14 18:11:47 1997
@@ -172,9 +172,10 @@
 
 	/* Are we prepared to handle this kernel fault?  */
 	if ((fixup = search_exception_table(regs->eip)) != 0) {
-		printk(KERN_DEBUG "%s: Exception at [<%lx>] (%lx)\n",
+		printk(KERN_DEBUG "%s: Exception at [<%lx>] cr2=%lx (fixup: %lx)\n",
 			tsk->comm,
 			regs->eip,
+			address,
 			fixup);
 		regs->eip = fixup;
 		goto out;
diff -u --recursive --new-file v2.1.57/linux/fs/bad_inode.c linux/fs/bad_inode.c
--- v2.1.57/linux/fs/bad_inode.c	Tue Sep 23 16:48:48 1997
+++ linux/fs/bad_inode.c	Sun Oct 12 09:45:12 1997
@@ -19,7 +19,7 @@
 	return ERR_PTR(-EIO);
 }
 
-static int return_EIO()
+static int return_EIO(void)
 {
 	return -EIO;
 }
@@ -81,3 +81,12 @@
 	inode->i_op = &bad_inode_ops;	
 }
 
+/*
+ * This tests whether an inode has been flagged as bad. The test uses
+ * &bad_inode_ops to cover the case of invalidated inodes as well as
+ * those created by make_bad_inode() above.
+ */
+int is_bad_inode(struct inode * inode) 
+{
+	return (inode->i_op == &bad_inode_ops);	
+}
diff -u --recursive --new-file v2.1.57/linux/fs/binfmt_misc.c linux/fs/binfmt_misc.c
--- v2.1.57/linux/fs/binfmt_misc.c	Wed Sep  3 20:52:43 1997
+++ linux/fs/binfmt_misc.c	Sun Oct 12 10:16:38 1997
@@ -281,21 +281,19 @@
 	const char *sp;
 	char del, *dp;
 	struct binfmt_entry *e;
-	int memsize, cnt = count - 1, err = 0;
+	int memsize, cnt = count - 1, err;
 
-	MOD_INC_USE_COUNT;
 	/* some sanity checks */
-	if ((count < 11) || (count > 256)) {
-		err = -EINVAL;
+	err = -EINVAL;
+	if ((count < 11) || (count > 256))
 		goto _err;
-	}
 
+	err = -ENOMEM;
 	memsize = sizeof(struct binfmt_entry) + count;
-	if (!(e = (struct binfmt_entry *) kmalloc(memsize, GFP_USER))) {
-		err = -ENOMEM;
+	if (!(e = (struct binfmt_entry *) kmalloc(memsize, GFP_USER)))
 		goto _err;
-	}
 
+	err = 0;
 	sp = buffer + 1;
 	del = buffer[0];
 	dp = (char *)e + sizeof(struct binfmt_entry);
@@ -327,12 +325,8 @@
 	/* more sanity checks */
 	if (err || !(!cnt || (!(--cnt) && (*sp == '\n'))) ||
 	    (e->size < 1) || ((e->size + e->offset) > 127) ||
-	    !(e->proc_name) || !(e->interpreter) ||
-	    entry_proc_setup(e)) {
-		kfree(e);
-		err = -EINVAL;
-		goto _err;
-	}
+	    !(e->proc_name) || !(e->interpreter) || entry_proc_setup(e))
+		goto free_err;
 
 	write_lock(&entries_lock);
 	e->next = entries;
@@ -341,8 +335,11 @@
 
 	err = count;
 _err:
-	MOD_DEC_USE_COUNT;
 	return err;
+free_err:
+	kfree(e);
+	err = -EINVAL;
+	goto _err;
 }
 
 /*
@@ -357,7 +354,6 @@
 	char *dp;
 	int elen, i, err;
 
-	MOD_INC_USE_COUNT;
 #ifndef VERBOSE_STATUS
 	if (data) {
 		if (!(e = get_entry((int) data))) {
@@ -415,7 +411,6 @@
 	err = elen;
 
 _err:
-	MOD_DEC_USE_COUNT;
 	return err;
 }
 
@@ -429,7 +424,6 @@
 	struct binfmt_entry *e;
 	int res = count;
 
-	MOD_INC_USE_COUNT;
 	if (buffer[count-1] == '\n')
 		count--;
 	if ((count == 1) && !(buffer[0] & ~('0' | '1'))) {
@@ -449,7 +443,6 @@
 	} else {
 		res = -EINVAL;
 	}
-	MOD_DEC_USE_COUNT;
 	return res;
 }
 
@@ -477,29 +470,57 @@
 	return 0;
 }
 
+#ifdef MODULE
+/*
+ * This is called as the fill_inode function when an inode
+ * is going into (fill = 1) or out of service (fill = 0).
+ * We use it here to manage the module use counts.
+ *
+ * Note: only the top-level directory needs to do this; if
+ * a lower level is referenced, the parent will be as well.
+ */
+static void bm_modcount(struct inode *inode, int fill)
+{
+	if (fill)
+		MOD_INC_USE_COUNT;
+	else
+		MOD_DEC_USE_COUNT;
+}
+#endif
 
 __initfunc(int init_misc_binfmt(void))
 {
 	struct proc_dir_entry *status = NULL, *reg;
+	int error = -ENOMEM;
 
-	if (!(bm_dir = create_proc_entry("sys/fs/binfmt_misc", S_IFDIR,
-					 NULL)) ||
-	    !(status = create_proc_entry("status", S_IFREG | S_IRUGO | S_IWUSR,
-					 bm_dir)) ||
-	    !(reg = create_proc_entry("register", S_IFREG | S_IWUSR,
-				      bm_dir))) {
-		if (status)
-			remove_proc_entry("status", bm_dir);
-		if (bm_dir)
-			remove_proc_entry("sys/fs/binfmt_misc", NULL);
-		return -ENOMEM;
-	}
+	bm_dir = create_proc_entry("sys/fs/binfmt_misc", S_IFDIR, NULL);
+	if (!bm_dir)
+		goto out;
+#ifdef MODULE
+	bm_dir->fill_inode = bm_modcount;
+#endif
+
+	status = create_proc_entry("status", S_IFREG | S_IRUGO | S_IWUSR,
+					bm_dir);
+	if (!status)
+		goto cleanup_bm;
 	status->read_proc = proc_read_status;
 	status->write_proc = proc_write_status;
 
+	reg = create_proc_entry("register", S_IFREG | S_IWUSR, bm_dir);
+	if (!reg)
+		goto cleanup_status;
 	reg->write_proc = proc_write_register;
 
-	return register_binfmt(&misc_format);
+	error = register_binfmt(&misc_format);
+out:
+	return error;
+
+cleanup_status:
+	remove_proc_entry("status", bm_dir);
+cleanup_bm:
+	remove_proc_entry("sys/fs/binfmt_misc", NULL);
+	goto out;
 }
 
 #ifdef MODULE
diff -u --recursive --new-file v2.1.57/linux/fs/dcache.c linux/fs/dcache.c
--- v2.1.57/linux/fs/dcache.c	Tue Sep 23 16:48:48 1997
+++ linux/fs/dcache.c	Sun Oct 12 11:31:24 1997
@@ -61,37 +61,54 @@
  */
 void dput(struct dentry *dentry)
 {
-	if (dentry) {
-		int count;
+	int count;
+
+	if (!dentry)
+		return;
+
 repeat:
-		count = dentry->d_count-1;
-		if (count < 0) {
-			printk("Negative d_count (%d) for %s/%s\n",
-				count,
-				dentry->d_parent->d_name.name,
-				dentry->d_name.name);
-			*(int *)0 = 0;
-		}
+	count = dentry->d_count - 1;
+	if (count != 0)
+		goto out;
+
+	/*
+	 * Note that if d_op->d_delete blocks,
+	 * the dentry could go back in use.
+	 * Each fs will have to watch for this.
+	 */
+	if (dentry->d_op && dentry->d_op->d_delete) {
+		dentry->d_op->d_delete(dentry);
+
+		count = dentry->d_count - 1;
+		if (count != 0)
+			goto out;
+	}
+
+	list_del(&dentry->d_lru);
+	if (list_empty(&dentry->d_hash)) {
+		struct inode *inode = dentry->d_inode;
+		struct dentry * parent;
+		if (inode)
+			iput(inode);
+		parent = dentry->d_parent;
+		d_free(dentry);
+		if (dentry == parent)
+			return;
+		dentry = parent;
+		goto repeat;
+	}
+	list_add(&dentry->d_lru, &dentry_unused);
+out:
+	if (count >= 0) {
 		dentry->d_count = count;
-		if (!count) {
-			list_del(&dentry->d_lru);
-			if (dentry->d_op && dentry->d_op->d_delete)
-				dentry->d_op->d_delete(dentry);
-			if (list_empty(&dentry->d_hash)) {
-				struct inode *inode = dentry->d_inode;
-				struct dentry * parent;
-				if (inode)
-					iput(inode);
-				parent = dentry->d_parent;
-				d_free(dentry);
-				if (dentry == parent)
-					return;
-				dentry = parent;
-				goto repeat;
-			}
-			list_add(&dentry->d_lru, &dentry_unused);
-		}
+		return;
 	}
+
+	printk("Negative d_count (%d) for %s/%s\n",
+		count,
+		dentry->d_parent->d_name.name,
+		dentry->d_name.name);
+	*(int *)0 = 0;	
 }
 
 /*
@@ -99,13 +116,14 @@
  * possible. If there are other users of the dentry we
  * can't invalidate it.
  *
- * This is currently incorrect. We should try to see if
- * we can invalidate any unused children - right now we
- * refuse to invalidate way too much.
+ * We should probably try to see if we can invalidate
+ * any unused children - right now we refuse to invalidate
+ * too much. That would require a better child list
+ * data structure, though.
  */
 int d_invalidate(struct dentry * dentry)
 {
-	/* We should do a partial shrink_dcache here */
+	/* We might want to do a partial shrink_dcache here */
 	if (dentry->d_count != 1)
 		return -EBUSY;
 
@@ -114,6 +132,27 @@
 }
 
 /*
+ * Throw away a dentry - free the inode, dput the parent.
+ * This requires that the LRU list has already been
+ * removed.
+ */
+static inline void prune_one_dentry(struct dentry * dentry)
+{
+	struct dentry * parent;
+
+	list_del(&dentry->d_hash);
+	if (dentry->d_inode) {
+		struct inode * inode = dentry->d_inode;
+
+		dentry->d_inode = NULL;
+		iput(inode);
+	}
+	parent = dentry->d_parent;
+	d_free(dentry);
+	dput(parent);
+}
+
+/*
  * Shrink the dcache. This is done when we need
  * more memory, or simply when we need to unmount
  * something (at which point we need to unuse
@@ -131,21 +170,62 @@
 		INIT_LIST_HEAD(tmp);
 		dentry = list_entry(tmp, struct dentry, d_lru);
 		if (!dentry->d_count) {
-			struct dentry * parent;
-
-			list_del(&dentry->d_hash);
-			if (dentry->d_inode) {
-				struct inode * inode = dentry->d_inode;
-
-				dentry->d_inode = NULL;
-				iput(inode);
-			}
-			parent = dentry->d_parent;
-			d_free(dentry);
-			dput(parent);
+			prune_one_dentry(dentry);
 			if (!--count)
 				break;
 		}
+	}
+}
+
+/*
+ * Shrink the dcache for the specified super block.
+ * This allows us to unmount a device without disturbing
+ * the dcache for the other devices.
+ *
+ * This implementation makes just two traversals of the
+ * unused list.  On the first pass we move the selected
+ * dentries to the most recent end, and on the second
+ * pass we free them.  The second pass must restart after
+ * each dput(), but since the target dentries are all at
+ * the end, it's really just a single traversal.
+ */
+void shrink_dcache_sb(struct super_block * sb)
+{
+	struct list_head *tmp, *next;
+	struct dentry *dentry;
+
+	/*
+	 * Pass one ... move the dentries for the specified
+	 * superblock to the most recent end of the unused list.
+	 */
+	next = dentry_unused.next;
+	while (next != &dentry_unused) {
+		tmp = next;
+		next = tmp->next;
+		dentry = list_entry(tmp, struct dentry, d_lru);
+		if (dentry->d_sb != sb)
+			continue;
+		list_del(tmp);
+		list_add(tmp, &dentry_unused);
+	}
+
+	/*
+	 * Pass two ... free the dentries for this superblock.
+	 */
+repeat:
+	next = dentry_unused.next;
+	while (next != &dentry_unused) {
+		tmp = next;
+		next = tmp->next;
+		dentry = list_entry(tmp, struct dentry, d_lru);
+		if (dentry->d_sb != sb)
+			continue;
+		if (dentry->d_count)
+			continue;
+		list_del(tmp);
+		INIT_LIST_HEAD(tmp);
+		prune_one_dentry(dentry);
+		goto repeat;
 	}
 }
 
diff -u --recursive --new-file v2.1.57/linux/fs/file_table.c linux/fs/file_table.c
--- v2.1.57/linux/fs/file_table.c	Wed Sep  3 20:52:43 1997
+++ linux/fs/file_table.c	Sun Oct 12 10:10:40 1997
@@ -18,8 +18,9 @@
 static kmem_cache_t *filp_cache;
 
 /* sysctl tunables... */
-int nr_files = 0;
-int max_files = NR_FILE;
+int nr_files = 0;	/* read only */
+int nr_free_files = 0;	/* read only */
+int max_files = NR_FILE;/* tunable */
 
 /* Free list management, if you are here you must have f_count == 0 */
 static struct file * free_filps = NULL;
@@ -30,6 +31,7 @@
 		free_filps->f_pprev = &file->f_next;
 	free_filps = file;
 	file->f_pprev = &free_filps;
+	nr_free_files++;
 }
 
 /* The list of in-use filp's must be exported (ugh...) */
@@ -43,6 +45,7 @@
 	file->f_pprev = &inuse_filps;
 }
 
+/* N.B. This should be an __initfunc ... */
 void file_table_init(void)
 {
 	filp_cache = kmem_cache_create("filp", sizeof(struct file),
@@ -50,6 +53,11 @@
 				       SLAB_HWCACHE_ALIGN, NULL, NULL);
 	if(!filp_cache)
 		panic("VFS: Cannot alloc filp SLAB cache.");
+	/*
+	 * We could allocate the reserved files here, but really
+	 * shouldn't need to: the normal boot process will create
+	 * plenty of free files.
+	 */
 }
 
 /* Find an unused file structure and return a pointer to it.
@@ -61,24 +69,31 @@
 	static int old_max = 0;
 	struct file * f;
 
-	f = free_filps;
-	if (!f) 
-		goto get_more;
-	remove_filp(f);
-got_one:
-	memset(f, 0, sizeof(*f));
-	f->f_count = 1;
-	f->f_version = ++event;
-	put_inuse(f);
-	return f;
-
-get_more:
-	/* Reserve a few files for the super-user.. */
-	if (nr_files < (current->euid ? max_files - 10 : max_files)) {
+	if (nr_free_files > NR_RESERVED_FILES) {
+	used_one:
+		f = free_filps;
+		remove_filp(f);
+		nr_free_files--;
+	new_one:
+		memset(f, 0, sizeof(*f));
+		f->f_count = 1;
+		f->f_version = ++event;
+		put_inuse(f);
+		return f;
+	}
+	/*
+	 * Use a reserved one if we're the superuser
+	 */
+	if (nr_free_files && !current->euid)
+		goto used_one;
+	/*
+	 * Allocate a new one if we're below the limit.
+	 */
+	if (nr_files < max_files) {
 		f = kmem_cache_alloc(filp_cache, SLAB_KERNEL);
 		if (f) {
 			nr_files++;
-			goto got_one;
+			goto new_one;
 		}
 		/* Big problems... */
 		printk("VFS: filp allocation failed\n");
diff -u --recursive --new-file v2.1.57/linux/fs/lockd/clntproc.c linux/fs/lockd/clntproc.c
--- v2.1.57/linux/fs/lockd/clntproc.c	Thu Jul 17 10:06:07 1997
+++ linux/fs/lockd/clntproc.c	Sun Oct 12 10:17:46 1997
@@ -160,8 +160,9 @@
 nlmclnt_grace_wait(struct nlm_host *host)
 {
 	if (!host->h_reclaiming)
-		current->timeout = 10 * HZ;
+		current->timeout = jiffies + 10 * HZ;
 	interruptible_sleep_on(&host->h_gracewait);
+	current->timeout = 0;
 	return signalled()? -ERESTARTSYS : 0;
 }
 
@@ -178,9 +179,11 @@
 					sizeof(struct nlm_rqst));
 		if (call)
 			return call;
-		current->timeout = 5 * HZ;
+		printk("nlmclnt_alloc_call: failed, waiting for memory\n");
+		current->timeout = jiffies + 5 * HZ;
 		current->state = TASK_INTERRUPTIBLE;
 		schedule();
+		current->timeout = 0;
 	}
 	return NULL;
 }
@@ -232,6 +235,7 @@
 		/* Back off a little and try again */
 		current->timeout = jiffies + 15 * HZ;
 		interruptible_sleep_on(&host->h_gracewait);
+		current->timeout = 0;
 	} while (!signalled());
 
 	return -ERESTARTSYS;
diff -u --recursive --new-file v2.1.57/linux/fs/lockd/svc.c linux/fs/lockd/svc.c
--- v2.1.57/linux/fs/lockd/svc.c	Wed Apr 23 19:01:26 1997
+++ linux/fs/lockd/svc.c	Sun Oct 12 10:17:46 1997
@@ -42,11 +42,15 @@
 
 extern struct svc_program	nlmsvc_program;
 struct nlmsvc_binding *		nlmsvc_ops = NULL;
-static int			nlmsvc_sema = 0;
-static int			nlmsvc_pid = 0;
+static struct semaphore 	nlmsvc_sema = MUTEX;
+static unsigned int		nlmsvc_users = 0;
+static pid_t			nlmsvc_pid = 0;
 unsigned long			nlmsvc_grace_period = 0;
 unsigned long			nlmsvc_timeout = 0;
 
+static struct wait_queue *	lockd_start = NULL;
+static struct wait_queue *	lockd_exit = NULL;
+
 /*
  * Currently the following can be set only at insmod time.
  * Ideally, they would be accessible through the sysctl interface.
@@ -64,10 +68,16 @@
 	sigset_t	oldsigmask;
 	int		err = 0;
 
-	lock_kernel();
 	/* Lock module and set up kernel thread */
 	MOD_INC_USE_COUNT;
-	/* exit_files(current); */
+	lock_kernel();
+
+	/*
+	 * Let our maker know we're running.
+	 */
+	nlmsvc_pid = current->pid;
+	wake_up(&lockd_start);
+
 	exit_mm(current);
 	current->session = 1;
 	current->pgrp = 1;
@@ -76,6 +86,12 @@
 	/* kick rpciod */
 	rpciod_up();
 
+	/*
+	 * N.B. current do_fork() doesn't like NULL task->files,
+	 * so we defer closing files until forking rpciod.
+	 */
+	exit_files(current);
+
 	dprintk("NFS locking service started (ver " LOCKD_VERSION ").\n");
 
 	if (!nlm_timeout)
@@ -94,14 +110,14 @@
 
 	nlmsvc_grace_period += jiffies;
 	nlmsvc_timeout = nlm_timeout * HZ;
-	nlmsvc_pid = current->pid;
 
 	/*
 	 * The main request loop. We don't terminate until the last
 	 * NFS mount or NFS daemon has gone away, and we've been sent a
-	 * signal.
+	 * signal, or else another process has taken over our job.
 	 */
-	while (nlmsvc_sema || !signalled()) {
+	while ((nlmsvc_users || !signalled()) && nlmsvc_pid == current->pid)
+	{
 		if (signalled())
 			current->signal = 0;
 
@@ -156,8 +172,17 @@
 			nlmsvc_ops->exp_unlock();
 	}
 
-	nlm_shutdown_hosts();
-
+	/*
+	 * Check whether there's a new lockd process before
+	 * shutting down the hosts and clearing the slot.
+	 */
+	if (!nlmsvc_pid || current->pid == nlmsvc_pid) {
+		nlm_shutdown_hosts();
+		nlmsvc_pid = 0;
+	} else
+		printk("lockd: new process, skipping host shutdown\n");
+	wake_up(&lockd_exit);
+		
 	/* Exit the RPC thread */
 	svc_exit_thread(rqstp);
 
@@ -166,7 +191,6 @@
 
 	/* Release module */
 	MOD_DEC_USE_COUNT;
-	nlmsvc_pid = 0;
 }
 
 /*
@@ -185,42 +209,100 @@
 	return svc_create_socket(serv, protocol, &sin);
 }
 
+/*
+ * Bring up the lockd process if it's not already up.
+ */
 int
 lockd_up(void)
 {
 	struct svc_serv *	serv;
-	int			error;
+	int			error = 0;
 
-	if (nlmsvc_pid || nlmsvc_sema++)
-		return 0;
+	down(&nlmsvc_sema);
+	/*
+	 * Unconditionally increment the user count ... this is
+	 * the number of clients who _want_ a lockd process.
+	 */
+	nlmsvc_users++; 
+	/*
+	 * Check whether we're already up and running.
+	 */
+	if (nlmsvc_pid)
+		goto out;
+
+	/*
+	 * Sanity check: if there's no pid,
+	 * we should be the first user ...
+	 */
+	if (nlmsvc_users > 1)
+		printk("lockd_up: no pid, %d users??\n", nlmsvc_users);
 
-	dprintk("lockd: creating service\n");
-	if ((serv = svc_create(&nlmsvc_program, 0, NLMSVC_XDRSIZE)) == NULL)
-		return -ENOMEM;
+	error = -ENOMEM;
+	serv = svc_create(&nlmsvc_program, 0, NLMSVC_XDRSIZE);
+	if (!serv) {
+		printk("lockd_up: create service failed\n");
+		goto out;
+	}
 
-	if ((error = lockd_makesock(serv, IPPROTO_UDP, 0)) < 0
+	if ((error = lockd_makesock(serv, IPPROTO_UDP, 0)) < 0 
 	 || (error = lockd_makesock(serv, IPPROTO_TCP, 0)) < 0) {
-		svc_destroy(serv);
-		return error;
+		printk("lockd_up: makesock failed, error=%d\n", error);
+		goto destroy_and_out;
 	}
 
-	if ((error = svc_create_thread(lockd, serv)) < 0)
-		nlmsvc_sema--;
+	/*
+	 * Create the kernel thread and wait for it to start.
+	 */
+	error = svc_create_thread(lockd, serv);
+	if (error) {
+		printk("lockd_up: create thread failed, error=%d\n", error);
+		goto destroy_and_out;
+	}
+	sleep_on(&lockd_start);
 
-	/* Release server */
+	/*
+	 * Note: svc_serv structures have an initial use count of 1,
+	 * so we exit through here on both success and failure.
+	 */
+destroy_and_out:
 	svc_destroy(serv);
-	return 0;
+out:
+	up(&nlmsvc_sema);
+	return error;
 }
 
+/*
+ * Decrement the user count and bring down lockd if we're the last.
+ */
 void
 lockd_down(void)
 {
-	if (!nlmsvc_pid || --nlmsvc_sema > 0)
-		return;
+	down(&nlmsvc_sema);
+	if (nlmsvc_users) {
+		if (--nlmsvc_users)
+			goto out;
+	} else
+		printk("lockd_down: no users! pid=%d\n", nlmsvc_pid);
+
+	if (!nlmsvc_pid) {
+		printk("lockd_down: nothing to do!\n"); 
+		goto out;
+	}
 
 	kill_proc(nlmsvc_pid, SIGKILL, 1);
-	nlmsvc_sema = 0;
-	nlmsvc_pid = 0;
+	/*
+	 * Wait for the lockd process to exit, but since we're holding
+	 * the lockd semaphore, we can't wait around forever ...
+	 */
+	current->timeout = jiffies + 5 * HZ;
+	interruptible_sleep_on(&lockd_exit);
+	current->timeout = 0;
+	if (nlmsvc_pid) {
+		printk("lockd_down: lockd failed to exit, clearing pid\n");
+		nlmsvc_pid = 0;
+	}
+out:
+	up(&nlmsvc_sema);
 }
 
 #ifdef MODULE
@@ -235,6 +317,11 @@
 int
 init_module(void)
 {
+	/* Init the static variables */
+	nlmsvc_sema = MUTEX;
+	nlmsvc_users = 0;
+	nlmsvc_pid = 0;
+	lockd_exit = NULL;
 	nlmxdr_init();
 	return 0;
 }
diff -u --recursive --new-file v2.1.57/linux/fs/locks.c linux/fs/locks.c
--- v2.1.57/linux/fs/locks.c	Wed Sep 24 20:05:48 1997
+++ linux/fs/locks.c	Tue Oct 14 11:33:41 1997
@@ -881,9 +881,6 @@
 
 	if (caller->fl_type != F_UNLCK) {
   repeat:
-		error = -ERESTARTSYS;
-		if (signal_pending(current))
-			goto out;
 		for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {
 			if (!(fl->fl_flags & FL_POSIX))
 				continue;
@@ -894,6 +891,9 @@
 				goto out;
 			error = -EDEADLK;
 			if (posix_locks_deadlock(caller, fl))
+				goto out;
+			error = -ERESTARTSYS;
+			if (signal_pending(current))
 				goto out;
 			locks_insert_block(fl, caller);
 			interruptible_sleep_on(&caller->fl_wait);
diff -u --recursive --new-file v2.1.57/linux/fs/namei.c linux/fs/namei.c
--- v2.1.57/linux/fs/namei.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/namei.c	Sun Oct 12 10:13:51 1997
@@ -232,11 +232,14 @@
 	result = d_lookup(parent, name);
 	if (!result) {
 		struct dentry * dentry = d_alloc(parent, name);
-		int error = dir->i_op->lookup(dir, dentry);
-		result = ERR_PTR(error);
-		if (!error)
-			result = dget(dentry->d_mounts);
-		dput(dentry);
+		result = ERR_PTR(-ENOMEM);
+		if (dentry) {
+			int error = dir->i_op->lookup(dir, dentry);
+			result = ERR_PTR(error);
+			if (!error)
+				result = dget(dentry->d_mounts);
+			dput(dentry);
+		}
 	}
 	up(&dir->i_sem);
 	return result;
@@ -1170,7 +1173,8 @@
 
 	if (new_dir->d_inode->i_sb && new_dir->d_inode->i_sb->dq_op)
 		new_dir->d_inode->i_sb->dq_op->initialize(new_dir->d_inode, -1);
-	error = old_dir->d_inode->i_op->rename(old_dir->d_inode, old_dentry, new_dir->d_inode, new_dentry);
+	error = old_dir->d_inode->i_op->rename(old_dir->d_inode, old_dentry,
+					       new_dir->d_inode, new_dentry);
 
 exit_lock:
 	double_unlock(new_dir, old_dir);
diff -u --recursive --new-file v2.1.57/linux/fs/nfs/dir.c linux/fs/nfs/dir.c
--- v2.1.57/linux/fs/nfs/dir.c	Wed Sep 24 20:05:48 1997
+++ linux/fs/nfs/dir.c	Sun Oct 12 10:15:56 1997
@@ -29,6 +29,11 @@
 
 #include <asm/segment.h>	/* for fs functions */
 
+/* needed by smbfs as well ... move to dcache? */
+extern void nfs_renew_times(struct dentry *);
+extern void nfs_invalidate_dircache_sb(struct super_block *);
+#define NFS_PARANOIA 1
+
 /*
  * Head for a dircache entry. Currently still very simple; when
  * the cache grows larger, we will need a LRU list.
@@ -36,14 +41,14 @@
 struct nfs_dirent {
 	dev_t			dev;		/* device number */
 	ino_t			ino;		/* inode number */
-	u32			cookie;		/* cooke of first entry */
+	u32			cookie;		/* cookie of first entry */
 	unsigned short		valid  : 1,	/* data is valid */
 				locked : 1;	/* entry locked */
 	unsigned int		size;		/* # of entries */
 	unsigned long		age;		/* last used */
 	unsigned long		mtime;		/* last attr stamp */
 	struct wait_queue *	wait;
-	struct nfs_entry *	entry;
+	__u32 *			entry;		/* three __u32's per entry */
 };
 
 static int nfs_dir_open(struct inode * inode, struct file * file);
@@ -123,15 +128,16 @@
 
 static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
 {
+	struct inode 		*inode = filp->f_dentry->d_inode;
 	static struct wait_queue *readdir_wait = NULL;
 	struct wait_queue	**waitp = NULL;
 	struct nfs_dirent	*cache, *free;
-	struct nfs_entry	*entry;
 	unsigned long		age, dead;
 	u32			cookie;
 	int			ismydir, result;
 	int			i, j, index = 0;
-	struct inode 		*inode = filp->f_dentry->d_inode;
+	__u32			*entry;
+	char			*name, *start;
 
 	dfprintk(VFS, "NFS: nfs_readdir(%x/%ld)\n", inode->i_dev, inode->i_ino);
 	if (!inode || !S_ISDIR(inode->i_mode)) {
@@ -194,17 +200,15 @@
 			break;
 		}
 		for (j = 0; j < cache->size; j++) {
-			/*
-			dprintk("NFS: examing entry %.*s @%d\n",
-				(int) cache->entry[j].length,
-				cache->entry[j].name,
-				cache->entry[j].cookie);
-			 */
-			if (cache->entry[j].cookie != cookie)
+			__u32 *this_ent = cache->entry + j*3;
+
+			if (*(this_ent+1) != cookie)
 				continue;
 			if (j < cache->size - 1) {
-				entry = cache->entry + (index = j + 1);
-			} else if (cache->entry[j].eof) {
+				index = j + 1;
+				entry = this_ent + 3;
+			} else if (*(this_ent+2) >> 16) {
+				/* eof */
 				return 0;
 			}
 			break;
@@ -235,12 +239,10 @@
 		cache->dev    = inode->i_dev;
 		cache->ino    = inode->i_ino;
 		if (!cache->entry) {
-			cache->entry = (struct nfs_entry *)
-						get_free_page(GFP_KERNEL);
-			if (!cache->entry) {
-				result = -ENOMEM;
+			result = -ENOMEM;
+			cache->entry = (__u32 *) get_free_page(GFP_KERNEL);
+			if (!cache->entry)
 				goto done;
-			}
 		}
 
 		result = nfs_proc_readdir(NFS_SERVER(inode), NFS_FH(inode),
@@ -257,25 +259,29 @@
 	/*
 	 * Yowza! We have a cache entry...
 	 */
+	start = (char *) cache->entry;
 	while (index < cache->size) {
-		int	nextpos = entry->cookie;
+		__u32	fileid  = *entry++;
+		__u32	nextpos = *entry++; /* cookie */
+		__u32	length  = *entry++;
 
 		/*
+		 * Unpack the eof flag, offset, and length
+		 */
+		result = length & (1 << 15); /* eof flag */
+		name = start + ((length >> 16) & 0xFFFF);
+		length &= 0x7FFF;
+		/*
 		dprintk("NFS: filldir(%p, %.*s, %d, %d, %x, eof %x)\n", entry,
-				(int) entry->length, entry->name, entry->length,
+				(int) length, name, length,
 				(unsigned int) filp->f_pos,
-				entry->fileid, entry->eof);
+				fileid, result);
 		 */
 
-		if (filldir(dirent, entry->name, entry->length, cookie, entry->fileid) < 0)
+		if (filldir(dirent, name, length, cookie, fileid) < 0)
 			break;
 		cookie = nextpos;
-		if (nextpos != entry->cookie) {
-			printk("nfs_readdir: shouldn't happen!\n");
-			break;
-		}
 		index++;
-		entry++;
 	}
 	filp->f_pos = cookie;
 	result = 0;
@@ -293,44 +299,73 @@
 }
 
 /*
- * Invalidate dircache entries for inode
+ * Invalidate dircache entries for an inode.
  */
 void
 nfs_invalidate_dircache(struct inode *inode)
 {
-	struct nfs_dirent *cache;
+	struct nfs_dirent *cache = dircache;
 	dev_t		dev = inode->i_dev;
 	ino_t		ino = inode->i_ino;
 	int		i;
 
 	dfprintk(DIRCACHE, "NFS: invalidate dircache for %x/%ld\n", dev, (long)ino);
-	for (i = 0, cache = dircache; i < NFS_MAX_DIRCACHE; i++, cache++) {
-		if (!cache->locked && cache->dev == dev && cache->ino == ino)
-			cache->valid = 0;	/* brute force */
+	for (i = NFS_MAX_DIRCACHE; i--; cache++) {
+		if (cache->ino != ino)
+			continue;
+		if (cache->dev != dev)
+			continue;
+		if (cache->locked) {
+			printk("NFS: cache locked for %s/%ld\n",
+				kdevname(dev), ino);
+			continue;
+		}
+		cache->valid = 0;	/* brute force */
 	}
 }
 
 /*
- * Free directory cache memory
- * Called from cleanup_module
+ * Invalidate the dircache for a super block (or all caches),
+ * and release the cache memory.
  */
 void
-nfs_free_dircache(void)
+nfs_invalidate_dircache_sb(struct super_block *sb)
 {
-	struct nfs_dirent *cache;
+	struct nfs_dirent *cache = dircache;
 	int		i;
+	int		freed = 0;
 
-	dfprintk(DIRCACHE, "NFS: freeing dircache\n");
-	for (i = 0, cache = dircache; i < NFS_MAX_DIRCACHE; i++, cache++) {
-		cache->valid = 0;
+	for (i = NFS_MAX_DIRCACHE; i--; cache++) {
+		if (sb && sb->s_dev != cache->dev)
+			continue;
 		if (cache->locked) {
-			printk("nfs_kfree_cache: locked entry in dircache!\n");
+			printk("NFS: cache locked at umount %s\n",
+				(cache->entry ? "(lost a page!)" : ""));
 			continue;
 		}
-		if (cache->entry)
+		cache->valid = 0;	/* brute force */
+		if (cache->entry) {
 			free_page((unsigned long) cache->entry);
-		cache->entry = NULL;
+			cache->entry = NULL;
+			freed++;
+		}
 	}
+#ifdef NFS_PARANOIA
+if (freed)
+printk("nfs_invalidate_dircache_sb: freed %d pages from %s\n", 
+freed, kdevname(sb->s_dev));
+#endif
+}
+
+/*
+ * Free directory cache memory
+ * Called from cleanup_module
+ */
+void
+nfs_free_dircache(void)
+{
+	dfprintk(DIRCACHE, "NFS: freeing dircache\n");
+	nfs_invalidate_dircache_sb(NULL);
 }
 
 /*
@@ -350,20 +385,46 @@
 	unsigned long time = jiffies - dentry->d_time;
 	unsigned long max = 5*HZ;
 
-	if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
-		max = 10*HZ;
-	return time < max;
+	if (dentry->d_inode) {
+		if (is_bad_inode(dentry->d_inode)) {
+#ifdef NFS_PARANOIA
+printk("nfs_lookup_validate: %s/%s has dud inode\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+			goto bad;
+		}
+		if (S_ISDIR(dentry->d_inode->i_mode))
+			max = 10*HZ;
+	}
+
+	return (time < max) || IS_ROOT(dentry);
+bad:
+	return 0;
 }
 
 static void nfs_silly_delete(struct dentry *);
 
 static struct dentry_operations nfs_dentry_operations = {
-	nfs_lookup_revalidate,
+	nfs_lookup_revalidate,	/* d_validate(struct dentry *) */
 	0,			/* d_hash */
 	0,			/* d_compare */
-	nfs_silly_delete,
+	nfs_silly_delete,	/* d_delete(struct dentry *) */
 };
 
+/*
+ * Whenever a lookup succeeds, we know the parent directories
+ * are all valid, so we want to update the dentry timestamps.
+ */
+void nfs_renew_times(struct dentry * dentry)
+{
+	for (;;) {
+		dentry->d_time = jiffies;
+		if (dentry == dentry->d_parent)
+			break;
+		dentry = dentry->d_parent;
+	}
+}
+
 static int nfs_lookup(struct inode *dir, struct dentry * dentry)
 {
 	struct inode *inode;
@@ -383,20 +444,42 @@
 	if (len > NFS_MAXNAMLEN)
 		return -ENAMETOOLONG;
 
-	error = nfs_proc_lookup(NFS_SERVER(dir), NFS_FH(dir), dentry->d_name.name, &fhandle, &fattr);
-
+	error = nfs_proc_lookup(NFS_SERVER(dir), NFS_FH(dir), 
+				dentry->d_name.name, &fhandle, &fattr);
 	inode = NULL;
+	if (error == -ENOENT)
+		goto no_entry;
 	if (!error) {
+		error = -EACCES;
 		inode = nfs_fhget(dir->i_sb, &fhandle, &fattr);
-		if (!inode)
-			return -EACCES;
-	} else if (error != -ENOENT)
-		return error;
-
-	dentry->d_time = jiffies;
-	dentry->d_op = &nfs_dentry_operations;
-	d_add(dentry, inode);
-	return 0;
+		if (inode) {
+	    no_entry:
+			dentry->d_op = &nfs_dentry_operations;
+			d_add(dentry, inode);
+			nfs_renew_times(dentry);
+			error = 0;
+		}
+	}
+	return error;
+}
+
+/*
+ * Code common to create, mkdir, and mknod.
+ */
+static int nfs_instantiate(struct inode *dir, struct dentry *dentry,
+			struct nfs_fattr *fattr, struct nfs_fh *fhandle)
+{
+	struct inode *inode;
+	int error = -EACCES;
+
+	inode = nfs_fhget(dir->i_sb, fhandle, fattr);
+	if (inode) {
+		nfs_invalidate_dircache(dir);
+		d_instantiate(dentry, inode);
+		nfs_renew_times(dentry);
+		error = 0;
+	}
+	return error;
 }
 
 static int nfs_create(struct inode *dir, struct dentry * dentry, int mode)
@@ -404,7 +487,6 @@
 	struct nfs_sattr sattr;
 	struct nfs_fattr fattr;
 	struct nfs_fh fhandle;
-	struct inode *inode;
 	int error;
 
 	dfprintk(VFS, "NFS: create(%x/%ld, %s\n",
@@ -422,18 +504,10 @@
 	sattr.uid = sattr.gid = sattr.size = (unsigned) -1;
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 	error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir),
-		dentry->d_name.name, &sattr, &fhandle, &fattr);
-
-	if (error)
-		return error;
-
-	inode = nfs_fhget(dir->i_sb, &fhandle, &fattr);
-	if (!inode)
-		return -EACCES;
-
-	nfs_invalidate_dircache(dir);
-	d_instantiate(dentry, inode);
-	return 0;
+			dentry->d_name.name, &sattr, &fhandle, &fattr);
+	if (!error)
+		error = nfs_instantiate(dir, dentry, &fattr, &fhandle);
+	return error;
 }
 
 static int nfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rdev)
@@ -441,7 +515,6 @@
 	struct nfs_sattr sattr;
 	struct nfs_fattr fattr;
 	struct nfs_fh fhandle;
-	struct inode *inode;
 	int error;
 
 	dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n",
@@ -462,18 +535,10 @@
 
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 	error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir),
-		dentry->d_name.name, &sattr, &fhandle, &fattr);
-
-	if (error)
-		return error;
-
-	inode = nfs_fhget(dir->i_sb, &fhandle, &fattr);
-	if (!inode)
-		return -EACCES;
-
-	nfs_invalidate_dircache(dir);
-	d_instantiate(dentry, inode);
-	return 0;
+				dentry->d_name.name, &sattr, &fhandle, &fattr);
+	if (!error)
+		error = nfs_instantiate(dir, dentry, &fattr, &fhandle);
+	return error;
 }
 
 static int nfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
@@ -481,7 +546,6 @@
 	struct nfs_sattr sattr;
 	struct nfs_fattr fattr;
 	struct nfs_fh fhandle;
-	struct inode * inode;
 	int error;
 
 	dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n",
@@ -500,20 +564,16 @@
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 
 	error = nfs_proc_mkdir(NFS_SERVER(dir), NFS_FH(dir),
-		dentry->d_name.name, &sattr, &fhandle, &fattr);
-
-	if (error)
-		return error;
-
-	inode = nfs_fhget(dir->i_sb, &fhandle, &fattr);
-	if (!inode)
-		return -EACCES;
-
-	nfs_invalidate_dircache(dir);
-	d_instantiate(dentry, inode);
-	return 0;
+				dentry->d_name.name, &sattr, &fhandle, &fattr);
+	if (!error)
+		error = nfs_instantiate(dir, dentry, &fattr, &fhandle);
+	return error;
 }
 
+/*
+ *  Update inode->i_nlink immediately after a successful operation.
+ *  (See comments for nfs_unlink.)
+ */
 static int nfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
 	int error;
@@ -530,12 +590,14 @@
 		return -ENAMETOOLONG;
 
 	error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dir), dentry->d_name.name);
-	if (error)
-		return error;
-
-	nfs_invalidate_dircache(dir);
-	d_delete(dentry);
-	return 0;
+	if (!error) {
+		if (dentry->d_inode->i_nlink)
+			dentry->d_inode->i_nlink --;
+		nfs_invalidate_dircache(dir);
+		nfs_renew_times(dentry);
+		d_delete(dentry);
+	}
+	return error;
 }
 
 
@@ -642,16 +704,15 @@
 	error = nfs_proc_rename(NFS_SERVER(dir),
 				NFS_FH(dir), dentry->d_name.name,
 				NFS_FH(dir), silly);
-	if (error) {
-		dput(sdentry);
-		return error;
+	if (!error) {
+		nfs_invalidate_dircache(dir);
+		nfs_renew_times(dentry);
+		d_move(dentry, sdentry);
+		dentry->d_flags |= DCACHE_NFSFS_RENAMED;
+ 		/* If we return 0 we don't unlink */
 	}
-	nfs_invalidate_dircache(dir);
-	d_move(dentry, sdentry);
 	dput(sdentry);
-	dentry->d_flags |= DCACHE_NFSFS_RENAMED;
-
-	return 0; /* don't unlink */
+	return error;
 }
 
 static void nfs_silly_delete(struct dentry *dentry)
@@ -670,8 +731,18 @@
 		if (error < 0)
 			printk("NFS " __FUNCTION__ " failed (err = %d)\n",
 			       -error);
-		dentry->d_inode->i_nlink --;
+		if (dentry->d_inode) {
+			if (dentry->d_inode->i_nlink)
+				dentry->d_inode->i_nlink --;
+		} else
+			printk("nfs_silly_delete: negative dentry %s/%s\n",
+				dentry->d_parent->d_name.name,
+				dentry->d_name.name);
 		nfs_invalidate_dircache(dir);
+		/*
+		 * The dentry is unhashed, but we want to make it negative.
+		 */
+		d_delete(dentry);
 	}
 }
 
@@ -680,8 +751,11 @@
  *
  *  If sillyrename() returns 0, we do nothing, otherwise we unlink.
  * 
- *  inode->i_nlink is updated here rather than waiting for the next
- *  nfs_refresh_inode() for cosmetic reasons only.
+ *  Updating inode->i_nlink here rather than waiting for the next
+ *  nfs_refresh_inode() is not merely cosmetic; once an object has
+ *  been deleted, we want to get rid of the inode locally.  The NFS
+ *  server may reuse the fileid for a new inode, and we don't want
+ *  that to be confused with this inode.
  */
 static int nfs_unlink(struct inode *dir, struct dentry *dentry)
 {
@@ -700,20 +774,19 @@
 
 	error = nfs_sillyrename(dir, dentry);
 
-	if (error == -EBUSY) {
-		return -EBUSY;
-	} else if (error < 0) {
+	if (error && error != -EBUSY) {
 		error = nfs_proc_remove(NFS_SERVER(dir),
 					NFS_FH(dir), dentry->d_name.name);
-		if (error < 0)
-			return error;
-		
-		dentry->d_inode->i_nlink --;
-		nfs_invalidate_dircache(dir);
-		d_delete(dentry);
+		if (!error) {
+			if (dentry->d_inode->i_nlink)
+				dentry->d_inode->i_nlink --;
+			nfs_invalidate_dircache(dir);
+			nfs_renew_times(dentry);
+			d_delete(dentry);
+		}
 	}
 
-	return 0;
+	return error;
 }
 
 static int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
@@ -740,22 +813,21 @@
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 
 	error = nfs_proc_symlink(NFS_SERVER(dir), NFS_FH(dir),
-		dentry->d_name.name, symname, &sattr);
-
-	if (error)
-		return error;
-
-	nfs_invalidate_dircache(dir);
-	/*  this looks _funny_ doesn't it? But: nfs_proc_symlink()
-	 *  only fills in sattr, not fattr. Thus nfs_fhget() cannot be
-	 *  called, it would be pointless, without a valid fattr
-	 *  argument. Other possibility: call nfs_proc_lookup()
-	 *  HERE. But why? If somebody wants to reference this
-	 *  symlink, the cached_lookup() will fail, and
-	 *  nfs_proc_symlink() will be called anyway.
-	 */
-	d_drop(dentry);
-	return 0;
+				dentry->d_name.name, symname, &sattr);
+	if (!error) {
+		nfs_invalidate_dircache(dir);
+		nfs_renew_times(dentry->d_parent);
+		/*  this looks _funny_ doesn't it? But: nfs_proc_symlink()
+		 *  only fills in sattr, not fattr. Thus nfs_fhget() cannot be
+		 *  called, it would be pointless, without a valid fattr
+		 *  argument. Other possibility: call nfs_proc_lookup()
+		 *  HERE. But why? If somebody wants to reference this
+		 *  symlink, the cached_lookup() will fail, and
+		 *  nfs_proc_symlink() will be called anyway.
+		 */
+		d_drop(dentry);
+	}
+	return error;
 }
 
 static int nfs_link(struct inode *inode, struct inode *dir, struct dentry *dentry)
@@ -774,17 +846,16 @@
 	if (dentry->d_name.len > NFS_MAXNAMLEN)
 		return -ENAMETOOLONG;
 
-	error = nfs_proc_link(NFS_SERVER(inode), NFS_FH(inode),
-		NFS_FH(dir), dentry->d_name.name);
-
-	if (error)
-		return error;
-
-	nfs_invalidate_dircache(dir);
-	inode->i_count ++;
-	inode->i_nlink ++; /* no need to wait for nfs_refresh_inode() */
-	d_instantiate(dentry, inode);
-	return 0;
+	error = nfs_proc_link(NFS_SERVER(inode), NFS_FH(inode), NFS_FH(dir),
+				dentry->d_name.name);
+	if (!error) {
+		nfs_invalidate_dircache(dir);
+		inode->i_count ++;
+		inode->i_nlink ++; /* no need to wait for nfs_refresh_inode() */
+		d_instantiate(dentry, inode);
+		error = 0;
+	}
+	return error;
 }
 
 /*
@@ -843,15 +914,19 @@
 	error = nfs_proc_rename(NFS_SERVER(old_dir),
 				NFS_FH(old_dir), old_dentry->d_name.name,
 				NFS_FH(new_dir), new_dentry->d_name.name);
-	if (error)
-		return error;
-
-	nfs_invalidate_dircache(old_dir);
-	nfs_invalidate_dircache(new_dir);
+	if (!error) {
+		nfs_invalidate_dircache(old_dir);
+		nfs_invalidate_dircache(new_dir);
+		/*
+		 * We know these paths are still valid ...
+		 */
+		nfs_renew_times(old_dentry);
+		nfs_renew_times(new_dentry->d_parent);
 
-	/* Update the dcache */
-	d_move(old_dentry, new_dentry);
-	return 0;
+		/* Update the dcache */
+		d_move(old_dentry, new_dentry);
+	}
+	return error;
 }
 
 /*
@@ -860,23 +935,68 @@
  * of the server's inode.
  */
 
-void nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
+int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
-	int was_empty;
+	int error = -EIO;
 
 	dfprintk(VFS, "NFS: refresh_inode(%x/%ld ct=%d)\n",
 		 inode->i_dev, inode->i_ino, inode->i_count);
 
 	if (!inode || !fattr) {
 		printk("nfs_refresh_inode: inode or fattr is NULL\n");
-		return;
+		goto out;
 	}
 	if (inode->i_ino != fattr->fileid) {
 		printk("nfs_refresh_inode: inode number mismatch\n");
-		return;
+		goto out;
 	}
-	was_empty = (inode->i_mode == 0);
-	inode->i_mode = fattr->mode;
+	/*
+	 * Check whether the mode has been set, as we only want to
+	 * do this once. (We don't allow inodes to change types.)
+	 */
+	if (inode->i_mode == 0) {
+		inode->i_mode = fattr->mode;
+		if (S_ISREG(inode->i_mode))
+			inode->i_op = &nfs_file_inode_operations;
+		else if (S_ISDIR(inode->i_mode))
+			inode->i_op = &nfs_dir_inode_operations;
+		else if (S_ISLNK(inode->i_mode))
+			inode->i_op = &nfs_symlink_inode_operations;
+		else if (S_ISCHR(inode->i_mode)) {
+			inode->i_op = &chrdev_inode_operations;
+			inode->i_rdev = to_kdev_t(fattr->rdev);
+		} else if (S_ISBLK(inode->i_mode)) {
+			inode->i_op = &blkdev_inode_operations;
+			inode->i_rdev = to_kdev_t(fattr->rdev);
+		} else if (S_ISFIFO(inode->i_mode))
+			init_fifo(inode);
+		else
+			inode->i_op = NULL;
+	} else if ((inode->i_mode & S_IFMT) == (fattr->mode & S_IFMT)) {
+		inode->i_mode = fattr->mode;
+	} else {
+		mode_t old_mode;
+		/*
+		 * Big trouble! The inode has become a different object.
+		 */
+#if 1
+printk("nfs_refresh_inode: mode changed, %07o to %07o\n",
+inode->i_mode, fattr->mode);
+#endif
+		old_mode = inode->i_mode; /* save mode */
+		make_bad_inode(inode);
+		inode->i_mode = old_mode; /* restore mode */
+		inode->i_nlink = 0;
+		/*
+		 * No need to worry about unhashing the dentry, as the
+		 * lookup validation will know that the inode is bad.
+		 * (But we do want to invalidate the caches.)
+		 */
+		invalidate_inode_pages(inode);
+		nfs_invalidate_dircache(inode);
+		goto out;
+	}
+
 	inode->i_nlink = fattr->nlink;
 	inode->i_uid = fattr->uid;
 	inode->i_gid = fattr->gid;
@@ -893,29 +1013,13 @@
 		NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
 	}
 	inode->i_size = fattr->size;
-	if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
-		inode->i_rdev = to_kdev_t(fattr->rdev);
-	else
-		inode->i_rdev = 0;
 	inode->i_blocks = fattr->blocks;
 	inode->i_atime = fattr->atime.seconds;
 	inode->i_mtime = fattr->mtime.seconds;
 	inode->i_ctime = fattr->ctime.seconds;
-	if (S_ISREG(inode->i_mode))
-		inode->i_op = &nfs_file_inode_operations;
-	else if (S_ISDIR(inode->i_mode))
-		inode->i_op = &nfs_dir_inode_operations;
-	else if (S_ISLNK(inode->i_mode))
-		inode->i_op = &nfs_symlink_inode_operations;
-	else if (S_ISCHR(inode->i_mode))
-		inode->i_op = &chrdev_inode_operations;
-	else if (S_ISBLK(inode->i_mode))
-		inode->i_op = &blkdev_inode_operations;
-	else if (S_ISFIFO(inode->i_mode)) {
-		if (was_empty)
-			init_fifo(inode);
-	} else
-		inode->i_op = NULL;
+	error = 0;
+out:
+	return error;
 }
 
 /*
diff -u --recursive --new-file v2.1.57/linux/fs/nfs/inode.c linux/fs/nfs/inode.c
--- v2.1.57/linux/fs/nfs/inode.c	Sun Sep  7 13:10:43 1997
+++ linux/fs/nfs/inode.c	Sun Oct 12 10:15:56 1997
@@ -34,6 +34,8 @@
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
 
+extern void nfs_invalidate_dircache_sb(struct super_block *);
+
 static int nfs_notify_change(struct inode *, struct iattr *);
 static void nfs_put_inode(struct inode *);
 static void nfs_delete_inode(struct inode *);
@@ -67,6 +69,7 @@
 {
 	inode->i_blksize = inode->i_sb->s_blocksize;
 	inode->i_mode = 0;
+	inode->i_rdev = 0;
 	inode->i_op = NULL;
 	NFS_CACHEINV(inode);
 }
@@ -75,6 +78,11 @@
 nfs_put_inode(struct inode * inode)
 {
 	dprintk("NFS: put_inode(%x/%ld)\n", inode->i_dev, inode->i_ino);
+	/*
+	 * We want to get rid of unused inodes ...
+	 */
+	if (inode->i_count == 1)
+		inode->i_nlink = 0;
 }
 
 static void
@@ -90,13 +98,21 @@
 	struct nfs_server *server = &sb->u.nfs_sb.s_server;
 	struct rpc_clnt	*rpc;
 
+	/*
+	 * Lock the super block while we bring down the daemons.
+	 */
+	lock_super(sb);
 	if ((rpc = server->client) != NULL)
 		rpc_shutdown_client(rpc);
 
 	if (!(server->flags & NFS_MOUNT_NONLM))
 		lockd_down();	/* release rpc.lockd */
 	rpciod_down();		/* release rpciod */
-	lock_super(sb);
+	/*
+	 * Invalidate the dircache for this superblock.
+	 */
+	nfs_invalidate_dircache_sb(sb);
+
 	sb->s_dev = 0;
 	unlock_super(sb);
 	MOD_DEC_USE_COUNT;
@@ -147,14 +163,12 @@
 	unsigned int		authflavor;
 	int			tcp;
 	kdev_t			dev = sb->s_dev;
+	struct inode		*root_inode;
 
 	MOD_INC_USE_COUNT;
-	if (!data) {
-		printk("nfs_read_super: missing data argument\n");
-		sb->s_dev = 0;
-		MOD_DEC_USE_COUNT;
-		return NULL;
-	}
+	if (!data)
+		goto out_miss_args;
+
 	if (data->version != NFS_MOUNT_VERSION) {
 		printk("nfs warning: mount version %s than kernel\n",
 			data->version < NFS_MOUNT_VERSION ? "older" : "newer");
@@ -164,13 +178,19 @@
 			data->bsize  = 0;
 	}
 
+	/* We now require that the mount process passes the remote address */
+	memcpy(&srvaddr, &data->addr, sizeof(srvaddr));
+	if (srvaddr.sin_addr.s_addr == INADDR_ANY)
+		goto out_no_remote;
+
 	lock_super(sb);
 
-	server           = &sb->u.nfs_sb.s_server;
 	sb->s_magic      = NFS_SUPER_MAGIC;
 	sb->s_dev        = dev;
 	sb->s_op         = &nfs_sops;
 	sb->s_blocksize  = nfs_block_size(data->bsize, &sb->s_blocksize_bits);
+	sb->u.nfs_sb.s_root = data->root;
+	server           = &sb->u.nfs_sb.s_server;
 	server->rsize    = nfs_block_size(data->rsize, NULL);
 	server->wsize    = nfs_block_size(data->wsize, NULL);
 	server->flags    = data->flags;
@@ -179,15 +199,6 @@
 	server->acdirmin = data->acdirmin*HZ;
 	server->acdirmax = data->acdirmax*HZ;
 	strcpy(server->hostname, data->hostname);
-	sb->u.nfs_sb.s_root = data->root;
-
-	/* We now require that the mount process passes the remote address */
-	memcpy(&srvaddr, &data->addr, sizeof(srvaddr));
-	if (srvaddr.sin_addr.s_addr == INADDR_ANY) {
-		printk("NFS: mount program didn't pass remote address!\n");
-		MOD_DEC_USE_COUNT;
-		return NULL;
-	}
 
 	/* Which protocol do we use? */
 	tcp   = (data->flags & NFS_MOUNT_TCP);
@@ -210,18 +221,13 @@
 	/* Now create transport and client */
 	xprt = xprt_create_proto(tcp? IPPROTO_TCP : IPPROTO_UDP,
 						&srvaddr, &timeparms);
-	if (xprt == NULL) {
-		printk("NFS: cannot create RPC transport.\n");
-		goto failure;
-	}
+	if (xprt == NULL)
+		goto out_no_xprt;
 
 	clnt = rpc_create_client(xprt, server->hostname, &nfs_program,
 						NFS_VERSION, authflavor);
-	if (clnt == NULL) {
-		printk("NFS: cannot create RPC client.\n");
-		xprt_destroy(xprt);
-		goto failure;
-	}
+	if (clnt == NULL)
+		goto out_no_client;
 
 	clnt->cl_intr     = (data->flags & NFS_MOUNT_INTR)? 1 : 0;
 	clnt->cl_softrtry = (data->flags & NFS_MOUNT_SOFT)? 1 : 0;
@@ -229,29 +235,67 @@
 	server->client    = clnt;
 
 	/* Fire up rpciod if not yet running */
+#ifdef RPCIOD_RESULT
+	if (rpciod_up())
+		goto out_no_iod;
+#else
 	rpciod_up();
+#endif
 
-	/* Unlock super block and try to get root fh attributes */
+	/*
+	 * Keep the super block locked while we try to get 
+	 * the root fh attributes.
+	 */
+	root_inode = nfs_fhget(sb, &data->root, NULL);
+	if (!root_inode)
+		goto out_no_root;
+	sb->s_root = d_alloc_root(root_inode, NULL);
+	if (!sb->s_root)
+		goto out_no_root;
+	/* We're airborne */
 	unlock_super(sb);
 
-	sb->s_root = d_alloc_root(nfs_fhget(sb, &data->root, NULL), NULL);
-	if (sb->s_root != NULL) {
-		/* We're airborne */
-		if (!(server->flags & NFS_MOUNT_NONLM))
-			lockd_up();
-		return sb;
-	}
+	/* Check whether to start the lockd process */
+	if (!(server->flags & NFS_MOUNT_NONLM))
+		lockd_up();
+	return sb;
 
 	/* Yargs. It didn't work out. */
+out_no_root:
 	printk("nfs_read_super: get root inode failed\n");
-	rpc_shutdown_client(server->client);
+	iput(root_inode);
 	rpciod_down();
+#ifdef RPCIOD_RESULT
+	goto out_shutdown;
 
-failure:
-	MOD_DEC_USE_COUNT;
-	if (sb->s_lock)
-		unlock_super(sb);
+out_no_iod:
+	printk("nfs_read_super: couldn't start rpciod!\n");
+out_shutdown:
+#endif
+	rpc_shutdown_client(server->client);
+	goto out_unlock;
+
+out_no_client:
+	printk("NFS: cannot create RPC client.\n");
+	xprt_destroy(xprt);
+	goto out_unlock;
+
+out_no_xprt:
+	printk("NFS: cannot create RPC transport.\n");
+out_unlock:
+	unlock_super(sb);
+	goto out_fail;
+
+out_no_remote:
+	printk("NFS: mount program didn't pass remote address!\n");
+	goto out_fail;
+
+out_miss_args:
+	printk("nfs_read_super: missing data argument\n");
+
+out_fail:
 	sb->s_dev = 0;
+	MOD_DEC_USE_COUNT;
 	return NULL;
 }
 
diff -u --recursive --new-file v2.1.57/linux/fs/nfs/nfs2xdr.c linux/fs/nfs/nfs2xdr.c
--- v2.1.57/linux/fs/nfs/nfs2xdr.c	Wed Apr 23 19:01:26 1997
+++ linux/fs/nfs/nfs2xdr.c	Sun Oct 12 10:15:56 1997
@@ -23,6 +23,7 @@
 #include <linux/nfs_fs.h>
 
 #define NFSDBG_FACILITY		NFSDBG_XDR
+/* #define NFS_PARANOIA 1 */
 
 #define QUADLEN(len)		(((len) + 3) >> 2)
 static int			nfs_stat_to_errno(int stat);
@@ -371,17 +372,18 @@
  * to avoid a malloc of NFS_MAXNAMLEN+1 for each file name.
  * After decoding, the layout in memory looks like this:
  *	entry1 entry2 ... entryN <space> stringN ... string2 string1
+ * Each entry consists of three __u32 values, the same space as NFS uses.
  * Note that the strings are not null-terminated so that the entire number
  * of entries returned by the server should fit into the buffer.
  */
 static int
 nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs_readdirres *res)
 {
-	struct nfs_entry	*entry;
 	struct iovec		*iov = req->rq_rvec;
 	int			status, nr, len;
-	char			*string;
+	char			*string, *start;
 	u32			*end;
+	__u32			fileid, cookie, *entry;
 
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
@@ -396,10 +398,11 @@
 	end = (u32 *) ((u8 *) p + iov[1].iov_len);
 
 	/* Get start and end of dirent buffer */
-	entry  = (struct nfs_entry *) res->buffer;
+	entry  = (__u32 *) res->buffer;
+	start  = (char *) res->buffer;
 	string = (char *) res->buffer + res->bufsiz;
-	for (nr = 0; *p++; nr++, entry++) {
-		entry->fileid = ntohl(*p++);
+	for (nr = 0; *p++; nr++) {
+		fileid = ntohl(*p++);
 
 		len = ntohl(*p++);
 		if ((p + QUADLEN(len) + 3) > end) {
@@ -413,27 +416,36 @@
 			return -errno_NFSERR_IO;
 		}
 		string -= len;
-		if ((void *) (entry+1) > (void *) string) {
-			/* This may actually happen because an nfs_entry
-			 * will take up more space than the XDR data. On
-			 * 32bit machines that's due to 8byte alignment,
-			 * on 64bit machines that's because the char * takes
-			 * up 2 longs.
-			 *
-			 * THIS IS BAD!
+		if ((void *) (entry+3) > (void *) string) {
+			/* 
+			 * This error is impossible as long as the temp
+			 * buffer is no larger than the user buffer. The 
+			 * current packing algorithm uses the same amount
+			 * of space in the user buffer as in the XDR data,
+			 * so it's guaranteed to fit.
 			 */
-			printk(KERN_NOTICE "NFS: should not happen in %s!\n",
+			printk("NFS: incorrect buffer size in %s!\n",
 				__FUNCTION__);
 			break;
 		}
 
-		entry->name = string;
-		entry->length = len;
 		memmove(string, p, len);
 		p += QUADLEN(len);
-		entry->cookie = ntohl(*p++);
-		entry->eof = !p[0] && p[1];
+		cookie = ntohl(*p++);
+		/*
+		 * To make everything fit, we encode the length, offset,
+		 * and eof flag into 32 bits. This works for filenames
+		 * up to 32K and PAGE_SIZE up to 64K.
+		 */
+		status = !p[0] && p[1] ? (1 << 15) : 0; /* eof flag */
+		*entry++ = fileid;
+		*entry++ = cookie;
+		*entry++ = ((string - start) << 16) | status | (len & 0x7FFF);
 	}
+#ifdef NFS_PARANOIA
+printk("nfs_xdr_readdirres: %d entries, ent sp=%d, str sp=%d\n",
+nr, ((char *) entry - start), (start + res->bufsiz - string));
+#endif
 	return nr;
 }
 
diff -u --recursive --new-file v2.1.57/linux/fs/nfs/nfs3xdr.c linux/fs/nfs/nfs3xdr.c
--- v2.1.57/linux/fs/nfs/nfs3xdr.c	Mon Apr  7 11:35:30 1997
+++ linux/fs/nfs/nfs3xdr.c	Sun Oct 12 10:15:56 1997
@@ -384,17 +384,18 @@
  * to avoid a malloc of NFS_MAXNAMLEN+1 for each file name.
  * After decoding, the layout in memory looks like this:
  *	entry1 entry2 ... entryN <space> stringN ... string2 string1
+ * Each entry consists of three __u32 values, the same space as NFS uses.
  * Note that the strings are not null-terminated so that the entire number
  * of entries returned by the server should fit into the buffer.
  */
 static int
 nfs_xdr_readdirres(struct rpc_rqst *req, u32 *p, struct nfs_readdirres *res)
 {
-	struct nfs_entry	*entry;
 	struct iovec		*iov = req->rq_rvec;
 	int			status, nr, len;
-	char			*string;
+	char			*string, *start;
 	u32			*end;
+	__u32			fileid, cookie, *entry;
 
 	if ((status = ntohl(*p++)))
 		return -nfs_stat_to_errno(status);
@@ -413,10 +414,11 @@
 		return -errno_NFSERR_IO;
 	}
 
-	string = (char *) res->buffer + res->bufsiz;
-	entry = (struct nfs_entry *) res->buffer;
-	for (nr = 0; *p++; nr++, entry++) {
-		entry->fileid = ntohl(*p++);
+	entry  = (__u32 *) res->buffer;
+	start  = (char *) res->buffer;
+	string = start + res->bufsiz;
+	for (nr = 0; *p++; nr++) {
+		fileid = ntohl(*p++);
 
 		len = ntohl(*p++);
 		if ((p + QUADLEN(len) + 3) > end) {
@@ -430,22 +432,40 @@
 			return -errno_NFSERR_IO;
 		}
 		string -= len;
-		if ((void *) (entry+1) > (void *) string) {
-			dprintk("NFS: shouldnothappen in readdirres_decode!\n");
-			break;	/* should not happen */
+		if ((void *) (entry+3) > (void *) string) {
+			/* 
+			 * This error is impossible as long as the temp
+			 * buffer is no larger than the user buffer. The 
+			 * current packing algorithm uses the same amount
+			 * of space in the user buffer as in the XDR data,
+			 * so it's guaranteed to fit.
+			 */
+			printk("NFS: incorrect buffer size in %s!\n",
+				__FUNCTION__);
+			break;
 		}
 
-		entry->name = string;
-		entry->length = len;
 		memmove(string, p, len);
 		p += QUADLEN(len);
-		entry->cookie = ntohl(*p++);
-		entry->eof = !p[0] && p[1];
+		cookie = ntohl(*p++);
+		/*
+		 * To make everything fit, we encode the length, offset,
+		 * and eof flag into 32 bits. This works for filenames
+		 * up to 32K and PAGE_SIZE up to 64K.
+		 */
+		status = !p[0] && p[1] ? (1 << 15) : 0; /* eof flag */
+		*entry++ = fileid;
+		*entry++ = cookie;
+		*entry++ = ((string - start) << 16) | status | (len & 0x7FFF);
 		/*
 		dprintk("NFS: decoded dirent %.*s cookie %d eof %d\n",
-			len, string, entry->cookie, entry->eof);
+			len, string, cookie, status);
 		 */
 	}
+#ifdef NFS_PARANOIA
+printk("nfs_xdr_readdirres: %d entries, ent sp=%d, str sp=%d\n",
+nr, ((char *) entry - start), (start + res->bufsiz - string));
+#endif
 	return nr;
 }
 
diff -u --recursive --new-file v2.1.57/linux/fs/nfs/proc.c linux/fs/nfs/proc.c
--- v2.1.57/linux/fs/nfs/proc.c	Mon Jun 16 16:35:58 1997
+++ linux/fs/nfs/proc.c	Sun Oct 12 10:15:56 1997
@@ -250,25 +250,43 @@
  */
 int
 nfs_proc_readdir(struct nfs_server *server, struct nfs_fh *fhandle,
-			u32 cookie, unsigned int size, struct nfs_entry *entry)
+			u32 cookie, unsigned int size, __u32 *entry)
 {
 	struct nfs_readdirargs	arg;
 	struct nfs_readdirres	res;
 	void *			buffer;
+	unsigned int		buf_size = PAGE_SIZE;
 	int			status;
 
 	/* First get a temp buffer for the readdir reply */
-	while (!(buffer = (void *) get_free_page(GFP_USER))) {
-		need_resched = 1;
-		schedule();
-		if (signalled())
-			return -ERESTARTSYS;
-	}
+	/* N.B. does this really need to be cleared? */
+	status = -ENOMEM;
+	buffer = (void *) get_free_page(GFP_KERNEL);
+	if (!buffer)
+		goto out;
+
+	/*
+	 * Calculate the effective size the buffer.  To make sure
+	 * that the returned data will fit into the user's buffer,
+	 * we decrease the buffer size as necessary.
+	 *
+	 * Note: NFS returns three __u32 values for each entry,
+	 * and we assume that the data is packed into the user
+	 * buffer with the same efficiency. 
+	 */
+	if (size < buf_size)
+		buf_size = size;
+	if (server->rsize < buf_size)
+		buf_size = server->rsize;
+#if 0
+printk("nfs_proc_readdir: user size=%d, rsize=%d, buf_size=%d\n",
+size, server->rsize, buf_size);
+#endif
 
 	arg.fh = fhandle;
 	arg.cookie = cookie;
 	arg.buffer = buffer;
-	arg.bufsiz = server->rsize < PAGE_SIZE? server->rsize : PAGE_SIZE;
+	arg.bufsiz = buf_size;
 	res.buffer = entry;
 	res.bufsiz = size;
 
@@ -276,6 +294,7 @@
 	status = rpc_call(server->client, NFSPROC_READDIR, &arg, &res, 0);
 	dprintk("NFS reply readdir: %d\n", status);
 	free_page((unsigned long) buffer);
+out:
 	return status;
 }
 
diff -u --recursive --new-file v2.1.57/linux/fs/proc/array.c linux/fs/proc/array.c
--- v2.1.57/linux/fs/proc/array.c	Wed Sep 24 20:05:48 1997
+++ linux/fs/proc/array.c	Sun Oct 12 10:16:38 1997
@@ -348,6 +348,12 @@
 
 	if (!p || !p->mm || ptr >= TASK_SIZE)
 		return 0;
+	/* Check for NULL pgd .. shouldn't happen! */
+	if (!p->mm->pgd) {
+		printk("get_phys_addr: pid %d has NULL pgd!\n", p->pid);
+		return 0;
+	}
+
 	page_dir = pgd_offset(p->mm,ptr);
 	if (pgd_none(*page_dir))
 		return 0;
@@ -917,24 +923,34 @@
 #define MAPS_LINE_MAX	MAPS_LINE_MAX8
 
 
-static long read_maps (int pid, struct file * file,
-	char * buf, unsigned long count)
+static long read_maps (int pid, struct file * file, char * buf,
+			unsigned long count)
 {
-	struct task_struct *p = find_task_by_pid(pid);
-	char * destptr;
+	struct task_struct *p;
+	struct vm_area_struct * map, * next;
+	char * destptr = buf, * buffer;
 	loff_t lineno;
-	int column;
-	struct vm_area_struct * map;
-	int i;
-	char * buffer;
+	int column, i, volatile_task;
+	long retval;
 
+	/*
+	 * We might sleep getting the page, so get it first.
+	 */
+	retval = -ENOMEM;
+	buffer = (char*)__get_free_page(GFP_KERNEL);
+	if (!buffer)
+		goto out;
+
+	retval = -EINVAL;
+	p = find_task_by_pid(pid);
 	if (!p)
-		return -EINVAL;
+		goto freepage_out;
 
 	if (!p->mm || p->mm == &init_mm || count == 0)
-		return 0;
+		goto getlen_out;
 
-	buffer = (char*)__get_free_page(GFP_KERNEL);
+	/* Check whether the mmaps could change if we sleep */
+	volatile_task = (p != current || p->mm->count > 1);
 
 	/* decode f_pos */
 	lineno = file->f_pos >> MAPS_LINE_SHIFT;
@@ -944,9 +960,7 @@
 	for (map = p->mm->mmap, i = 0; map && (i < lineno); map = map->vm_next, i++)
 		continue;
 
-	destptr = buf;
-
-	for ( ; map ; ) {
+	for ( ; map ; map = next ) {
 		/* produce the next line */
 		char *line;
 		char str[5], *cp = str;
@@ -957,6 +971,10 @@
 			MAPS_LINE_MAX4 :  MAPS_LINE_MAX8;
 		int len;
 
+		/*
+		 * Get the next vma now (but it won't be used if we sleep).
+		 */
+		next = map->vm_next;
 		flags = map->vm_flags;
 
 		*cp++ = flags & VM_READ ? 'r' : '-';
@@ -993,20 +1011,19 @@
 		if (column >= len) {
 			column = 0; /* continue with next line at column 0 */
 			lineno++;
-			map = map->vm_next;
-			continue;
+			continue; /* we haven't slept */
 		}
 
 		i = len-column;
 		if (i > count)
 			i = count;
-		copy_to_user(destptr, line+column, i);
-		destptr += i; count -= i;
-		column += i;
+		copy_to_user(destptr, line+column, i); /* may have slept */
+		destptr += i;
+		count   -= i;
+		column  += i;
 		if (column >= len) {
 			column = 0; /* next time: next line at column 0 */
 			lineno++;
-			map = map->vm_next;
 		}
 
 		/* done? */
@@ -1016,15 +1033,20 @@
 		/* By writing to user space, we might have slept.
 		 * Stop the loop, to avoid a race condition.
 		 */
-		if (p != current)
+		if (volatile_task)
 			break;
 	}
 
 	/* encode f_pos */
 	file->f_pos = (lineno << MAPS_LINE_SHIFT) + column;
 
+getlen_out:
+	retval = destptr - buf;
+
+freepage_out:
 	free_page((unsigned long)buffer);
-	return destptr-buf;
+out:
+	return retval;
 }
 
 #ifdef CONFIG_MODULES
diff -u --recursive --new-file v2.1.57/linux/fs/proc/base.c linux/fs/proc/base.c
--- v2.1.57/linux/fs/proc/base.c	Sat Jul  5 20:53:22 1997
+++ linux/fs/proc/base.c	Sun Oct 12 10:16:38 1997
@@ -50,13 +50,17 @@
 	NULL			/* permission */
 };
 
-static void proc_pid_fill_inode(struct inode * inode)
+/*
+ * The fill argument is non-zero when the inode is being filled ...
+ * we don't need to do anything when it's being deleted.
+ */
+static void proc_pid_fill_inode(struct inode * inode, int fill)
 {
 	struct task_struct *p;
 	int pid = inode->i_ino >> 16;
 	int ino = inode->i_ino & 0xffff;
 
-	if ((p = find_task_by_pid(pid)) != NULL) {
+	if (fill && (p = find_task_by_pid(pid)) != NULL) {
 		if (p->dumpable || ino == PROC_PID_INO) {
 			inode->i_uid = p->euid;
 			inode->i_gid = p->gid;
diff -u --recursive --new-file v2.1.57/linux/fs/proc/generic.c linux/fs/proc/generic.c
--- v2.1.57/linux/fs/proc/generic.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/proc/generic.c	Sun Oct 12 10:16:38 1997
@@ -16,11 +16,13 @@
 #include <linux/stat.h>
 #include <asm/bitops.h>
 
+extern struct inode_operations proc_dyna_dir_inode_operations;
+
 static long proc_file_read(struct inode * inode, struct file * file,
 			   char * buf, unsigned long nbytes);
 static long proc_file_write(struct inode * inode, struct file * file,
 			    const char * buffer, unsigned long count);
-static long long proc_file_lseek(struct file * file, long long offset, int orig);
+static long long proc_file_lseek(struct file *, long long, int);
 
 int proc_match(int len, const char *name,struct proc_dir_entry * de)
 {
@@ -44,17 +46,14 @@
     NULL		/* can't fsync */
 };
 
-/*
- * proc files can do almost nothing..
- */
 struct inode_operations proc_file_inode_operations = {
-    &proc_file_operations,  /* default proc file-ops */
-    NULL,	    /* create	   */
-    NULL,	    /* lookup	   */
-    NULL,	    /* link	   */
-    NULL,	    /* unlink	   */
-    NULL,	    /* symlink	   */
-    NULL,	    /* mkdir	   */
+	&proc_file_operations,  /* default proc file-ops */
+	NULL,		/* create	*/
+	NULL,		/* lookup	*/
+    NULL,		/* link		*/
+    NULL,		/* unlink	*/
+    NULL,		/* symlink	*/
+    NULL,		/* mkdir	*/
     NULL,	    /* rmdir	   */
     NULL,	    /* mknod	   */
     NULL,	    /* rename	   */
@@ -240,57 +239,77 @@
 struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
 					 struct proc_dir_entry *parent)
 {
-	struct proc_dir_entry *ent;
-	const char *fn;
+	struct proc_dir_entry *ent = NULL;
+	const char *fn = name;
+	int len;
 
-	if (parent)
-		fn = name;
-	else {
-		if (xlate_proc_name(name, &parent, &fn))
-			return NULL;
-	}
+	if (!parent && xlate_proc_name(name, &parent, &fn) != 0)
+		goto out;
+	len = strlen(fn);
 
-	ent = kmalloc(sizeof(struct proc_dir_entry), GFP_KERNEL);
+	ent = kmalloc(sizeof(struct proc_dir_entry) + len + 1, GFP_KERNEL);
 	if (!ent)
-		return NULL;
+		goto out;
 	memset(ent, 0, sizeof(struct proc_dir_entry));
+	memcpy(((char *) ent) + sizeof(*ent), fn, len + 1);
+	ent->name = ((char *) ent) + sizeof(*ent);
+	ent->namelen = len;
 
-	if (mode == S_IFDIR)
+	if (mode == S_IFDIR) {
 		mode |= S_IRUGO | S_IXUGO;
-	else if (mode == 0)
-		mode = S_IFREG | S_IRUGO;
-	
-	ent->name = fn;
-	ent->namelen = strlen(fn);
-	ent->mode = mode;
-	if (S_ISDIR(mode)) 
+		ent->ops = &proc_dyna_dir_inode_operations;
 		ent->nlink = 2;
-	else
+	}
+	else if (mode == 0) {
+		mode = S_IFREG | S_IRUGO;
 		ent->nlink = 1;
+	}
+	ent->mode = mode;
 
 	proc_register(parent, ent);
 	
+out:
 	return ent;
 }
 
+extern void free_proc_entry(struct proc_dir_entry *);
+void free_proc_entry(struct proc_dir_entry *de)
+{
+	kfree(de);
+}
+
+/*
+ * Remove a /proc entry and free it if it's not currently in use.
+ * If it is in use, we set the 'deleted' flag.
+ */
 void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
 {
 	struct proc_dir_entry *de;
-	const char *fn;
+	const char *fn = name;
 	int len;
 
-	if (parent)
-		fn = name;
-	else 
-		if (xlate_proc_name(name, &parent, &fn))
-			return;
+	if (!parent && xlate_proc_name(name, &parent, &fn) != 0)
+		goto out;
 	len = strlen(fn);
 
 	for (de = parent->subdir; de ; de = de->next) {
 		if (proc_match(len, fn, de))
 			break;
 	}
-	if (de)
+
+	if (de) {
+printk("remove_proc_entry: parent nlink=%d, file nlink=%d\n",
+parent->nlink, de->nlink);
 		proc_unregister(parent, de->low_ino);
-	kfree(de);
+		de->nlink = 0;
+		de->deleted = 1;
+		if (!de->count)
+			free_proc_entry(de);
+		else {
+			printk("remove_proc_entry: %s/%s busy, count=%d\n",
+				parent->name, de->name, de->count);
+		}
+	}
+out:
+	return;
 }
diff -u --recursive --new-file v2.1.57/linux/fs/proc/inode.c linux/fs/proc/inode.c
--- v2.1.57/linux/fs/proc/inode.c	Thu Jul 17 10:06:07 1997
+++ linux/fs/proc/inode.c	Sun Oct 12 10:16:38 1997
@@ -17,23 +17,66 @@
 #include <asm/system.h>
 #include <asm/uaccess.h>
 
+extern void free_proc_entry(struct proc_dir_entry *);
+
+struct proc_dir_entry * de_get(struct proc_dir_entry *de)
+{
+	if (de)
+		de->count++;
+	return de;
+}
+
+/*
+ * Decrements the use count and checks for deferred deletion.
+ */
+void de_put(struct proc_dir_entry *de)
+{
+	if (de) {
+		if (!de->count) {
+			printk("de_put: entry %s already free!\n", de->name);
+			return;
+		}
+
+		if (!--de->count) {
+			if (de->deleted) {
+				printk("de_put: deferred delete of %s\n",
+					de->name);
+				free_proc_entry(de);
+			}
+		}
+	}
+}
+
 static void proc_put_inode(struct inode *inode)
 {
 #ifdef CONFIG_SUN_OPENPROMFS_MODULE
-	if ((inode->i_ino >= PROC_OPENPROM_FIRST)
-	    && (inode->i_ino < PROC_OPENPROM_FIRST + PROC_NOPENPROM)
-	    && proc_openprom_use)
+	if ((inode->i_ino >= PROC_OPENPROM_FIRST) &&
+	    (inode->i_ino <  PROC_OPENPROM_FIRST + PROC_NOPENPROM) &&
+	    proc_openprom_use)
 		(*proc_openprom_use)(inode, 0);
 #endif	
+	/*
+	 * Kill off unused inodes ... VFS will unhash and
+	 * delete the inode if we set i_nlink to zero.
+	 */
+	if (inode->i_count == 1)
+		inode->i_nlink = 0;
 }
 
 /*
- * Does this ever happen?
+ * Decrement the use count of the proc_dir_entry.
  */
 static void proc_delete_inode(struct inode *inode)
 {
-	printk("proc_delete_inode()?\n");
-	inode->i_size = 0;
+	struct proc_dir_entry *de = inode->u.generic_ip;
+	if (de) {
+		/*
+		 * Call the fill_inode hook to release module counts.
+		 */
+		if (de->fill_inode)
+			de->fill_inode(inode, 0);
+		de_put(de);
+	}
 }
 
 static void proc_put_super(struct super_block *sb)
@@ -47,7 +90,7 @@
 	proc_read_inode,
 	proc_write_inode,
 	proc_put_inode,
-	proc_delete_inode,
+	proc_delete_inode,	/* delete_inode(struct inode *) */
 	NULL,
 	proc_put_super,
 	NULL,
@@ -85,9 +128,24 @@
 	return 1;
 }
 
-struct inode * proc_get_inode(struct super_block * s, int ino, struct proc_dir_entry * de)
+struct inode * proc_get_inode(struct super_block * sb, int ino,
+				struct proc_dir_entry * de)
 {
-	struct inode * inode = iget(s, ino);
+	struct inode * inode;
+
+	/*
+	 * Increment the use count so the dir entry can't disappear.
+	 */
+	de_get(de);
+#if 1
+/* shouldn't ever happen */
+if (de && de->deleted)
+printk("proc_iget: using deleted entry %s, count=%d\n", de->name, de->count);
+#endif
+
+	inode = iget(sb, ino);
+	if (!inode)
+		goto out_fail;
 	
 #ifdef CONFIG_SUN_OPENPROMFS_MODULE
 	if ((inode->i_ino >= PROC_OPENPROM_FIRST)
@@ -95,23 +153,29 @@
 	    && proc_openprom_use)
 		(*proc_openprom_use)(inode, 1);
 #endif	
-	if (inode && inode->i_sb == s) {
-		inode->u.generic_ip = (void *) de;
-		if (de) {
-			if (de->mode) {
-				inode->i_mode = de->mode;
-				inode->i_uid = de->uid;
-				inode->i_gid = de->gid;
-			}
-			if (de->size)
-				inode->i_size = de->size;
-			if (de->ops)
-				inode->i_op = de->ops;
-			if (de->nlink)
-				inode->i_nlink = de->nlink;
-			if (de->fill_inode)
-				de->fill_inode(inode);
+	/* N.B. How can this test ever fail?? */
+	if (inode->i_sb != sb)
+		printk("proc_get_inode: inode fubar\n");
+
+	inode->u.generic_ip = (void *) de;
+	if (de) {
+		if (de->mode) {
+			inode->i_mode = de->mode;
+			inode->i_uid = de->uid;
+			inode->i_gid = de->gid;
 		}
+		if (de->size)
+			inode->i_size = de->size;
+		if (de->ops)
+			inode->i_op = de->ops;
+		if (de->nlink)
+			inode->i_nlink = de->nlink;
+		/*
+		 * The fill_inode routine should use this call 
+		 * to increment module counts, if necessary.
+		 */
+		if (de->fill_inode)
+			de->fill_inode(inode, 1);
 	}
 	/*
 	 * Fixup the root inode's nlink value
@@ -126,26 +190,40 @@
 		}
 		read_unlock(&tasklist_lock);
 	}
+out:
 	return inode;
+
+out_fail:
+	de_put(de);
+	goto out;
 }			
 
 struct super_block *proc_read_super(struct super_block *s,void *data, 
 				    int silent)
 {
+	struct inode * root_inode;
+
 	lock_super(s);
 	s->s_blocksize = 1024;
 	s->s_blocksize_bits = 10;
 	s->s_magic = PROC_SUPER_MAGIC;
 	s->s_op = &proc_sops;
+	root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);
+	if (!root_inode)
+		goto out_no_root;
+	s->s_root = d_alloc_root(root_inode, NULL);
+	if (!s->s_root)
+		goto out_no_root;
+	parse_options(data, &root_inode->i_uid, &root_inode->i_gid);
 	unlock_super(s);
-	s->s_root = d_alloc_root(proc_get_inode(s, PROC_ROOT_INO, &proc_root), NULL);
-	if (!s->s_root) {
-		s->s_dev = 0;
-		printk("get root inode failed\n");
-		return NULL;
-	}
-	parse_options(data, &s->s_root->d_inode->i_uid, &s->s_root->d_inode->i_gid);
 	return s;
+
+out_no_root:
+	printk("proc_read_super: get root inode failed\n");
+	iput(root_inode);
+	s->s_dev = 0;
+	unlock_super(s);
+	return NULL;
 }
 
 int proc_statfs(struct super_block *sb, struct statfs *buf, int bufsiz)
diff -u --recursive --new-file v2.1.57/linux/fs/proc/root.c linux/fs/proc/root.c
--- v2.1.57/linux/fs/proc/root.c	Thu Sep 11 09:02:24 1997
+++ linux/fs/proc/root.c	Sun Oct 12 10:16:37 1997
@@ -25,6 +25,7 @@
 
 static int proc_root_readdir(struct file *, void *, filldir_t);
 static int proc_root_lookup(struct inode *,struct dentry *);
+static int proc_unlink(struct inode *, struct dentry *);
 
 static unsigned char proc_alloc_map[PROC_NDYNAMIC / 8] = {0};
 
@@ -73,6 +74,29 @@
 };
 
 /*
+ * /proc dynamic directories now support unlinking
+ */
+struct inode_operations proc_dyna_dir_inode_operations = {
+	&proc_dir_operations,	/* default proc dir ops */
+	NULL,			/* create */
+	proc_lookup,		/* lookup */
+	NULL,			/* link	*/
+	proc_unlink,		/* unlink(struct inode *, struct dentry *) */
+	NULL,			/* symlink	*/
+	NULL,			/* mkdir */
+	NULL,			/* rmdir */
+	NULL,			/* mknod */
+	NULL,			/* rename */
+	NULL,			/* readlink */
+	NULL,			/* follow_link */
+	NULL,			/* readpage */
+	NULL,			/* writepage */
+	NULL,			/* bmap */
+	NULL,			/* truncate */
+	NULL			/* permission */
+};
+
+/*
  * The root /proc directory is special, as it has the
  * <pid> directories. Thus we don't use the generic
  * directory handling functions for that..
@@ -173,7 +197,8 @@
 
 int proc_openprom_regdev(struct openpromfs_dev *d)
 {
-	if (proc_openpromdev_ino == PROC_OPENPROMD_FIRST + PROC_NOPENPROMD) return -1;
+	if (proc_openpromdev_ino == PROC_OPENPROMD_FIRST + PROC_NOPENPROMD)
+		return -1;
 	d->next = proc_openprom_devices;
 	d->inode = proc_openpromdev_ino++;
 	proc_openprom_devices = d;
@@ -218,6 +243,7 @@
 				(inode, filp, dirent, filldir);
 	return -EINVAL;
 }
+#define OPENPROM_DEFREADDIR proc_openprom_defreaddir
 
 static int 
 proc_openprom_deflookup(struct inode * dir, struct dentry *dentry)
@@ -229,17 +255,17 @@
 				(dir, dentry);
 	return -ENOENT;
 }
+#define OPENPROM_DEFLOOKUP proc_openprom_deflookup
+#else
+#define OPENPROM_DEFREADDIR NULL
+#define OPENPROM_DEFLOOKUP NULL
 #endif
 
 static struct file_operations proc_openprom_operations = {
 	NULL,			/* lseek - default */
 	NULL,			/* read - bad */
 	NULL,			/* write - bad */
-#if defined(CONFIG_SUN_OPENPROMFS_MODULE) && defined(CONFIG_KERNELD)
-	proc_openprom_defreaddir,/* readdir */
-#else
-	NULL,			/* readdir */
-#endif	
+	OPENPROM_DEFREADDIR,	/* readdir */
 	NULL,			/* poll - default */
 	NULL,			/* ioctl - default */
 	NULL,			/* mmap */
@@ -251,11 +277,7 @@
 struct inode_operations proc_openprom_inode_operations = {
 	&proc_openprom_operations,/* default net directory file-ops */
 	NULL,			/* create */
-#if defined(CONFIG_SUN_OPENPROMFS_MODULE) && defined(CONFIG_KERNELD)
-	proc_openprom_deflookup,/* lookup */
-#else
-	NULL,			/* lookup */
-#endif	
+	OPENPROM_DEFLOOKUP,	/* lookup */
 	NULL,			/* link */
 	NULL,			/* unlink */
 	NULL,			/* symlink */
@@ -639,6 +661,26 @@
 }
 
 /*
+ * As some entries in /proc are volatile, we want to 
+ * get rid of unused dentries.  This could be made 
+ * smarter: we could keep a "volatile" flag in the 
+ * inode to indicate which ones to keep.
+ */
+static void
+proc_delete_dentry(struct dentry * dentry)
+{
+	d_drop(dentry);
+}
+
+static struct dentry_operations proc_dentry_operations =
+{
+	NULL,			/* revalidate */
+	NULL,			/* d_hash */
+	NULL,			/* d_compare */
+	proc_delete_dentry	/* d_delete(struct dentry *) */
+};
+
+/*
  * Don't create negative dentries here, return -ENOENT by hand
  * instead.
  */
@@ -646,12 +688,15 @@
 {
 	struct inode *inode;
 	struct proc_dir_entry * de;
+	int error;
 
+	error = -ENOTDIR;
 	if (!dir || !S_ISDIR(dir->i_mode))
-		return -ENOTDIR;
+		goto out;
 
-	de = (struct proc_dir_entry *) dir->u.generic_ip;
+	error = -ENOENT;
 	inode = NULL;
+	de = (struct proc_dir_entry *) dir->u.generic_ip;
 	if (de) {
 		for (de = de->subdir; de ; de = de->next) {
 			if (!de || !de->low_ino)
@@ -660,18 +705,20 @@
 				continue;
 			if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
 				int ino = de->low_ino | (dir->i_ino & ~(0xffff));
+				error = -EINVAL;
 				inode = proc_get_inode(dir->i_sb, ino, de);
-				if (!inode)
-					return -EINVAL;
 				break;
 			}
 		}
 	}
-	if (!inode)
-		return -ENOENT;
 
-	d_add(dentry, inode);
-	return 0;
+	if (inode) {
+		dentry->d_op = &proc_dentry_operations;
+		d_add(dentry, inode);
+		error = 0;
+	}
+out:
+	return error;
 }
 
 static int proc_root_lookup(struct inode * dir, struct dentry * dentry)
@@ -721,6 +768,8 @@
 		if (!inode)
 			return -EINVAL;
 	}
+
+	dentry->d_op = &proc_dentry_operations;
 	d_add(dentry, inode);
 	return 0;
 }
@@ -825,5 +874,17 @@
 		filp->f_pos++;
 	}
 	read_unlock(&tasklist_lock);
+	return 0;
+}
+
+static int proc_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct proc_dir_entry * dp = dir->u.generic_ip;
+
+printk("proc_file_unlink: deleting %s/%s\n", dp->name, dentry->d_name.name);
+
+	remove_proc_entry(dentry->d_name.name, dp);
+	dentry->d_inode->i_nlink = 0;
+	d_delete(dentry);
 	return 0;
 }
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/Makefile linux/fs/smbfs/Makefile
--- v2.1.57/linux/fs/smbfs/Makefile	Wed Sep  3 20:52:43 1997
+++ linux/fs/smbfs/Makefile	Sun Oct 12 10:17:05 1997
@@ -8,7 +8,7 @@
 # Note 2! The CFLAGS definitions are now in the main makefile...
 
 O_TARGET := smbfs.o
-O_OBJS   := proc.o dir.o sock.o inode.o file.o ioctl.o 
+O_OBJS   := proc.o dir.o cache.o sock.o inode.o file.o ioctl.o 
 M_OBJS   := $(O_TARGET)
 
 # If you want debugging output, please uncomment the following line
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/cache.c linux/fs/smbfs/cache.c
--- v2.1.57/linux/fs/smbfs/cache.c	Wed Dec 31 16:00:00 1969
+++ linux/fs/smbfs/cache.c	Sun Oct 12 10:17:05 1997
@@ -0,0 +1,314 @@
+/*
+ *  cache.c
+ *
+ * Copyright (C) 1997 by Bill Hawes
+ *
+ * Routines to support directory cacheing using the page cache.
+ * Right now this only works for smbfs, but will be generalized
+ * for use with other filesystems.
+ */
+
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/dirent.h>
+#include <linux/smb_fs.h>
+
+#include <asm/page.h>
+
+#define SMBFS_PARANOIA 1
+/* #define SMBFS_DEBUG_VERBOSE 1 */
+
+static inline struct inode * 
+get_cache_inode(struct cache_head *cachep)
+{
+	return (mem_map + MAP_NR((unsigned long) cachep))->inode;
+}
+
+/*
+ * Get a pointer to the cache_head structure,
+ * mapped as the page at offset 0. The page is
+ * kept locked while we're using the cache.
+ */
+struct cache_head *
+smb_get_dircache(struct dentry * dentry)
+{
+	struct inode * inode = dentry->d_inode;
+	struct cache_head * cachep;
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_get_dircache: finding cache for %s/%s\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+	cachep = (struct cache_head *) get_cached_page(inode, 0, 1);
+	if (!cachep)
+		goto out;
+	if (cachep->valid)
+	{
+		struct cache_index * index = cachep->index;
+		struct cache_block * block;
+		unsigned long offset;
+		int i;
+
+		cachep->valid = 0;
+		/*
+		 * Here we only want to find existing cache blocks,
+		 * not add new ones.
+		 */
+		for (i = 0; i < cachep->pages; i++, index++) {
+#ifdef SMBFS_PARANOIA
+if (index->block)
+printk("smb_get_dircache: cache %s/%s has existing block!\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+			offset = PAGE_SIZE + (i << PAGE_SHIFT);
+			block = (struct cache_block *) get_cached_page(inode,
+								offset, 0);
+			if (!block)
+				goto out;
+			index->block = block;
+		}
+		cachep->valid = 1;
+	}
+out:
+	return cachep;
+}
+
+/*
+ * Unlock and release the data blocks.
+ */
+static void
+smb_free_cache_blocks(struct cache_head * cachep)
+{
+	struct cache_index * index = cachep->index;
+	int i;
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_free_cache_blocks: freeing %d blocks\n", cachep->pages);
+#endif
+	for (i = 0; i < cachep->pages; i++, index++)
+	{
+		if (index->block)
+		{
+			put_cached_page((unsigned long) index->block);
+			index->block = NULL;
+		}
+	}
+}
+
+/*
+ * Unlocks and releases the dircache.
+ */
+void
+smb_free_dircache(struct cache_head * cachep)
+{
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_free_dircache: freeing cache\n");
+#endif
+	smb_free_cache_blocks(cachep);
+	put_cached_page((unsigned long) cachep);
+}
+
+/*
+ * Initializes the dircache. We release any existing data blocks,
+ * and then clear the cache_head structure.
+ */
+void
+smb_init_dircache(struct cache_head * cachep)
+{
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_init_dircache: initializing cache, %d blocks\n", cachep->pages);
+#endif
+	smb_free_cache_blocks(cachep);
+	memset(cachep, 0, sizeof(struct cache_head));
+}
+
+/*
+ * Add a new entry to the cache.  This assumes that the
+ * entries are coming in order and are added to the end.
+ */
+void
+smb_add_to_cache(struct cache_head * cachep, struct dirent *entry, off_t fpos)
+{
+	struct inode * inode = get_cache_inode(cachep);
+	struct cache_index * index;
+	struct cache_block * block;
+	unsigned long page_off;
+	unsigned int nent, offset, len = entry->d_reclen;
+	unsigned int needed = len + sizeof(struct cache_entry);
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_add_to_cache: cache inode %p, status %d, adding %s at %ld\n",
+inode, cachep->status, entry->d_name, fpos);
+#endif
+	/*
+	 * Don't do anything if we've had an error ...
+	 */
+	if (cachep->status)
+		goto out;
+
+	index = &cachep->index[cachep->idx];
+	if (!index->block)
+		goto get_block;
+
+	/* space available? */
+	if (needed < index->space)
+	{
+	add_entry:
+		nent = index->num_entries;
+		index->num_entries++;
+		index->space -= needed;
+		offset = index->space + 
+			 index->num_entries * sizeof(struct cache_entry);
+		block = index->block;
+		memcpy(&block->cb_data.names[offset], entry->d_name, len);
+		block->cb_data.table[nent].namelen = len;
+		block->cb_data.table[nent].offset = offset;
+		block->cb_data.table[nent].ino = entry->d_ino;
+		cachep->entries++;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_add_to_cache: added entry %s, len=%d, pos=%ld, entries=%d\n",
+entry->d_name, len, fpos, cachep->entries);
+#endif
+		return;
+	}
+	/*
+	 * This block is full ... advance the index.
+	 */
+	cachep->idx++;
+	if (cachep->idx > NINDEX) /* not likely */
+		goto out_full;
+	index++;
+#ifdef SMBFS_PARANOIA
+if (index->block)
+printk("smb_add_to_cache: new index already has block!\n");
+#endif
+
+	/*
+	 * Get the next cache block
+	 */
+get_block:
+	cachep->pages++;
+	page_off = PAGE_SIZE + (cachep->idx << PAGE_SHIFT);
+	block = (struct cache_block *) get_cached_page(inode, page_off, 1);
+	if (block)
+	{
+		index->block = block;
+		index->space = PAGE_SIZE;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_add_to_cache: inode=%p, pages=%d, block at %ld\n",
+inode, cachep->pages, page_off);
+#endif
+		goto add_entry;
+	}
+	/*
+	 * On failure, just set the return status ...
+	 */
+out_full:
+	cachep->status = -ENOMEM;
+out:
+	return;
+}
+
+int
+smb_find_in_cache(struct cache_head * cachep, off_t pos, 
+		struct cache_dirent *entry)
+{
+	struct cache_index * index = cachep->index;
+	struct cache_block * block;
+	unsigned int i, nent, offset = 0;
+	off_t next_pos = 2;
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_find_in_cache: cache %p, looking for pos=%ld\n", cachep, pos);
+#endif
+	for (i = 0; i < cachep->pages; i++, index++)
+	{
+		if (pos < next_pos)
+			break;
+		nent = pos - next_pos;
+		next_pos += index->num_entries;
+		if (pos >= next_pos)
+			continue; 
+		/*
+		 * The entry is in this block. Note: we return
+		 * then name as a reference with _no_ null byte.
+		 */
+		block = index->block;
+		entry->ino = block->cb_data.table[nent].ino;
+		entry->len = block->cb_data.table[nent].namelen;
+		offset = block->cb_data.table[nent].offset;
+		entry->name = &block->cb_data.names[offset];
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_find_in_cache: found %s, len=%d, pos=%ld\n",
+entry->name, entry->len, pos);
+#endif
+		break;
+	}
+	return offset;
+}
+
+int
+smb_refill_dircache(struct cache_head * cachep, struct dentry *dentry)
+{
+	struct inode * inode = dentry->d_inode;
+	int result;
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_refill_dircache: cache %s/%s, blocks=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, cachep->pages);
+#endif
+	/*
+	 * Fill the cache, starting at position 2.
+	 */
+retry:
+	inode->u.smbfs_i.cache_valid = 1;
+	result = smb_proc_readdir(dentry, 2, cachep);
+	if (result < 0)
+	{
+#ifdef SMBFS_PARANOIA
+printk("smb_refill_dircache: readdir failed, result=%d\n", result);
+#endif
+		goto out;
+	}
+
+	/*
+	 * Check whether the cache was invalidated while
+	 * we were doing the scan ...
+	 */
+	if (!inode->u.smbfs_i.cache_valid)
+	{
+#ifdef SMBFS_PARANOIA
+printk("smb_refill_dircache: cache invalidated, retrying\n");
+#endif
+		goto retry;
+	}
+
+	result = cachep->status;
+	if (!result)
+	{
+		cachep->valid = 1;
+	}
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_refill_cache: cache %s/%s status=%d, entries=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name,
+cachep->status, cachep->entries);
+#endif
+
+out:
+	return result;
+}
+
+void
+smb_invalid_dir_cache(struct inode * dir)
+{
+	/*
+	 * Get rid of any unlocked pages, and clear the
+	 * 'valid' flag in case a scan is in progress.
+	 */
+	invalidate_inode_pages(dir);
+	dir->u.smbfs_i.cache_valid = 0;
+}
+
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/dir.c linux/fs/smbfs/dir.c
--- v2.1.57/linux/fs/smbfs/dir.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/smbfs/dir.c	Sun Oct 12 10:17:05 1997
@@ -23,21 +23,17 @@
 /* #define SMBFS_DEBUG_VERBOSE 1 */
 /* #define pr_debug printk */
 
-#define this_dir_cached(dir) ((dir->i_sb == c_sb) && (dir->i_ino == c_ino))
-
-static long
-smb_dir_read(struct inode *inode, struct file *filp,
-	     char *buf, unsigned long count);
-
-static int
-smb_readdir(struct file *filp, void *dirent, filldir_t filldir);
+static long smb_dir_read(struct inode *, struct file *, char *, unsigned long);
+static int smb_readdir(struct file *, void *, filldir_t);
+static int smb_dir_open(struct inode *, struct file *);
 
 static int smb_lookup(struct inode *, struct dentry *);
 static int smb_create(struct inode *, struct dentry *, int);
 static int smb_mkdir(struct inode *, struct dentry *, int);
 static int smb_rmdir(struct inode *, struct dentry *);
 static int smb_unlink(struct inode *, struct dentry *);
-static int smb_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
+static int smb_rename(struct inode *, struct dentry *,
+		      struct inode *, struct dentry *);
 
 static struct file_operations smb_dir_operations =
 {
@@ -48,7 +44,7 @@
 	NULL,			/* poll - default */
 	smb_ioctl,		/* ioctl */
 	NULL,			/* mmap */
-	NULL,			/* no special open code */
+	smb_dir_open,		/* open(struct inode *, struct file *) */
 	NULL,			/* no special release code */
 	NULL			/* fsync */
 };
@@ -75,15 +71,6 @@
 	NULL			/* smap */
 };
 
-static void smb_put_dentry(struct dentry *);
-static struct dentry_operations smbfs_dentry_operations =
-{
-	NULL,			/* revalidate */
-	NULL,			/* d_hash */
-	NULL,			/* d_compare */
-	smb_put_dentry		/* d_delete */
-};
-
 static long
 smb_dir_read(struct inode *inode, struct file *filp, char *buf,
 	     unsigned long count)
@@ -92,120 +79,43 @@
 }
 
 /*
- * This is the callback from dput().  We close the file so that
- * cached dentries don't keep the file open.
+ * Compute the hash for a qstr.
+ * N.B. Move to include/linux/dcache.h?
  */
-void
-smb_put_dentry(struct dentry *dentry)
-{
-	struct inode *ino = dentry->d_inode;
-	if (ino)
-		smb_close(ino);
-}
-
-/* Static variables for the dir cache */
-static struct smb_dirent *c_entry = NULL;
-static struct super_block * c_sb = NULL;
-static unsigned long c_ino = 0;
-static int c_seen_eof;
-static int c_size;
-static int c_last_returned_index;
-
-static struct smb_dirent *
-smb_search_in_cache(struct inode *dir, unsigned long f_pos)
+static unsigned int 
+hash_it(const char * name, unsigned int len)
 {
-	int i;
-
-	if (this_dir_cached(dir))
-		for (i = 0; i < c_size; i++)
-		{
-			if (c_entry[i].f_pos < f_pos)
-				continue;
-			if (c_entry[i].f_pos == f_pos)
-			{
-				c_last_returned_index = i;
-				return &(c_entry[i]);
-			}
-			break;
-		}
-	return NULL;
-}
-
-/*
- * Compute the hash for a qstr ... move to include/linux/dcache.h?
- */
-static unsigned int hash_it(const char * name, unsigned int len)
-{
-	unsigned long hash;
-	hash = init_name_hash();
+	unsigned long hash = init_name_hash();
 	while (len--)
 		hash = partial_name_hash(*name++, hash);
 	return end_name_hash(hash);
 }
 
-static struct semaphore refill_cache_sem = MUTEX;
 /*
- * Called with the refill semaphore held.
+ * If a dentry already exists, we have to give the cache entry
+ * the correct inode number.  This is needed for getcwd().
  */
-static int
-smb_refill_dir_cache(struct dentry *dentry, unsigned long f_pos)
+static unsigned long
+smb_find_ino(struct dentry *dentry, struct cache_dirent *entry)
 {
-	struct inode *dir = dentry->d_inode;
-	ino_t ino_start;
-	int i, result;
-
-	result = smb_proc_readdir(dentry, f_pos,
-					SMB_READDIR_CACHE_SIZE, c_entry);
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_refill_dir_cache: dir=%s/%s, pos=%lu, result=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, f_pos, result);
-#endif
-
-	if (result <= 0)
+	struct dentry * new_dentry;
+	struct qstr qname;
+	unsigned long ino = 0;
+
+	qname.name = entry->name;
+	qname.len  = entry->len;
+	qname.hash = hash_it(qname.name, qname.len);
+	new_dentry = d_lookup(dentry, &qname);
+	if (new_dentry)
 	{
-		/*
-		 * If an error occurred, the cache may have been partially
-		 * filled prior to failing, so we must invalidate.
-		 * N.B. Might not need to for 0 return ... save cache?
-		 */
-		c_sb = NULL;
-		c_ino = 0;
-		c_seen_eof = 0;
-		goto out;
-	}
-
-	/* Suppose there are a multiple of cache entries? */
-	c_seen_eof = (result < SMB_READDIR_CACHE_SIZE);
-	c_sb = dir->i_sb;
-	c_ino = dir->i_ino;
-	c_size = result;
-	c_last_returned_index = 0; /* is this used? */
-
-	ino_start = smb_invent_inos(c_size);
-	/*
-	 * If a dentry already exists, we have to give the cache entry
-	 * the correct inode number.  This is needed for getcwd().
-	 */
-	for (i = 0; i < c_size; i++)
-	{
-		struct dentry * new_dentry;
-		struct qstr qname;
-
-		c_entry[i].attr.f_ino = ino_start++;
-		qname.name = c_entry[i].name;
-		qname.len  = c_entry[i].len;
-		qname.hash = hash_it(qname.name, qname.len);
-		new_dentry = d_lookup(dentry, &qname);
-		if (new_dentry)
-		{
-			struct inode * inode = new_dentry->d_inode;
-			if (inode)
-				c_entry[i].attr.f_ino = inode->i_ino;
-			dput(new_dentry);
-		}
+		struct inode * inode = new_dentry->d_inode;
+		if (inode)
+			ino = inode->i_ino;
+		dput(new_dentry);
 	}
-out:
-	return result;
+	if (!ino)
+		ino = smb_invent_inos(1);
+	return ino;
 }
 
 static int 
@@ -213,150 +123,195 @@
 {
 	struct dentry *dentry = filp->f_dentry;
 	struct inode *dir = dentry->d_inode;
-	struct smb_dirent *entry;
+	struct cache_head *cachep;
 	int result;
 
 	pr_debug("smb_readdir: filp->f_pos = %d\n", (int) filp->f_pos);
 	pr_debug("smb_readdir: dir->i_ino = %ld, c_ino = %ld\n",
 		 dir->i_ino, c_ino);
-
-	result = -EBADF;
-	if ((dir == NULL) || !S_ISDIR(dir->i_mode))
+	/*
+	 * Make sure our inode is up-to-date.
+	 */
+	result = smb_revalidate_inode(dir);
+	if (result)
+		goto out;
+	/*
+	 * Get the cache pointer ...
+	 */
+	cachep = smb_get_dircache(dentry);
+	if (!cachep)
 		goto out;
-
 	/*
-	 * Check whether the directory cache exists yet
+	 * Make sure the cache is up-to-date.
 	 */
-	if (c_entry == NULL)
+	if (!cachep->valid)
 	{
-		int size = sizeof(struct smb_dirent) * SMB_READDIR_CACHE_SIZE;
-		result = -ENOMEM;
-		entry = (struct smb_dirent *) smb_vmalloc(size);
-		/*
-		 * Somebody else may have allocated the cache,
-		 * so we check again to avoid a memory leak.
-		 */
-		if (!c_entry)
-		{
-			if (!entry)
-				goto out;
-			c_entry = entry;
-		} else if (entry) {
-			printk("smb_readdir: cache already alloced!\n");
-			smb_vfree(entry);
-		}
+		result = smb_refill_dircache(cachep, dentry);
+		if (result)
+			goto up_and_out;
 	}
 
-	result = 0;
 	switch ((unsigned int) filp->f_pos)
 	{
 	case 0:
 		if (filldir(dirent, ".", 1, 0, dir->i_ino) < 0)
-			goto out;
+			goto up_and_out;
 		filp->f_pos = 1;
 	case 1:
 		if (filldir(dirent, "..", 2, 1,
 				dentry->d_parent->d_inode->i_ino) < 0)
-			goto out;
+			goto up_and_out;
 		filp->f_pos = 2;
 	}
 
-	/*
-	 * Since filldir() could block if dirent is paged out,
-	 * we hold the refill semaphore while using the cache.
-	 * N.B. It's possible that the directory could change
-	 * between calls to readdir ... what to do??
-	 */
-	down(&refill_cache_sem);
-	entry = smb_search_in_cache(dir, filp->f_pos);
-	if (entry == NULL)
-	{
-		/* Past the end of _this_ directory? */
-		if (this_dir_cached(dir) && c_seen_eof &&
-		    filp->f_pos == c_entry[c_size-1].f_pos + 1)
+	while (1)
+	{
+		struct cache_dirent this_dirent, *entry = &this_dirent;
+
+		if (!smb_find_in_cache(cachep, filp->f_pos, entry))
+			break;
+		/*
+		 * Check whether to look up the inode number.
+		 */
+		if (!entry->ino)
 		{
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_readdir: eof reached for %s/%s, c_size=%d, pos=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, c_size, (int) filp->f_pos);
-#endif
-			goto up_and_out;
+			entry->ino = smb_find_ino(dentry, entry);
 		}
-		result = smb_refill_dir_cache(dentry, filp->f_pos);
-		if (result <= 0)
-			goto up_and_out;
-		entry = c_entry;
-	}
-
-	while (entry < &(c_entry[c_size]))
-	{
-		pr_debug("smb_readdir: entry->name = %s\n", entry->name);
 
 		if (filldir(dirent, entry->name, entry->len, 
-				    entry->f_pos, entry->attr.f_ino) < 0)
+				    filp->f_pos, entry->ino) < 0)
 			break;
-#if SMBFS_PARANOIA
-/* should never happen */
-if (!this_dir_cached(dir) || (entry->f_pos != filp->f_pos))
-printk("smb_readdir: cache changed!\n");
-#endif
 		filp->f_pos += 1;
-		entry += 1;
 	}
 	result = 0;
 
+	/*
+	 * Release the dircache.
+	 */
 up_and_out:
-	up(&refill_cache_sem);
+	smb_free_dircache(cachep);
 out:
 	return result;
 }
 
-void
-smb_init_dir_cache(void)
+static int
+smb_dir_open(struct inode *dir, struct file *file)
 {
-	c_entry = NULL;
-	c_sb = NULL;
-	c_ino = 0;
-	c_seen_eof = 0;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_dir_open: (%s/%s)\n", file->f_dentry->d_parent->d_name.name, 
+file->f_dentry->d_name.name);
+#endif
+	return smb_revalidate_inode(dir);
 }
 
-void
-smb_invalid_dir_cache(struct inode * dir)
+/*
+ * Dentry operations routines
+ */
+static int smb_lookup_validate(struct dentry *);
+static void smb_delete_dentry(struct dentry *);
+
+static struct dentry_operations smbfs_dentry_operations =
+{
+	smb_lookup_validate,	/* d_validate(struct dentry *) */
+	NULL,			/* d_hash */
+	NULL,			/* d_compare */
+	smb_delete_dentry	/* d_delete(struct dentry *) */
+};
+
+/*
+ * This is the callback when the dcache has a lookup hit.
+ */
+static int smb_lookup_validate(struct dentry * dentry)
 {
-	if (this_dir_cached(dir))
+	struct inode * inode = dentry->d_inode;
+	unsigned long age = jiffies - dentry->d_time;
+	int valid;
+
+	/*
+	 * The default validation is based on dentry age:
+	 * we believe in dentries for 5 seconds.  (But each
+	 * successful server lookup renews the timestamp.)
+	 */
+	valid = age < 5 * HZ || IS_ROOT(dentry);
+#ifdef SMBFS_DEBUG_VERBOSE
+if (!valid)
+printk("smb_lookup_validate: %s/%s not valid, age=%d\n", 
+dentry->d_parent->d_name.name, dentry->d_name.name, age)
+#endif
+
+	if (inode)
 	{
-		c_sb = NULL;
-		c_ino = 0;
-		c_seen_eof = 0;
+		if (is_bad_inode(inode))
+		{
+#ifdef SMBFS_PARANOIA
+printk("smb_lookup_validate: %s/%s has dud inode\n", 
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+			valid = 0;
+		}
+	} else
+	{
+	/*
+	 * What should we do for negative dentries?
+	 */
 	}
+	return valid;
 }
 
-void
-smb_free_dir_cache(void)
+/*
+ * This is the callback from dput() when d_count is going to 0.
+ * We use this to close files and unhash dentries with bad inodes.
+ */
+static void smb_delete_dentry(struct dentry * dentry)
 {
-	if (c_entry != NULL)
+	if (dentry->d_inode)
+	{
+		if (is_bad_inode(dentry->d_inode))
+		{
+#ifdef SMBFS_PARANOIA
+printk("smb_delete_dentry: bad inode, unhashing %s/%s\n", 
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+			d_drop(dentry);
+		}
+		smb_close_dentry(dentry);
+	} else
 	{
-		/* N.B. can this block?? */
-		smb_vfree(c_entry);
+	/* N.B. Unhash negative dentries? */
+	}
+}
+
+/*
+ * Whenever a lookup succeeds, we know the parent directories
+ * are all valid, so we want to update the dentry timestamps.
+ * N.B. Move this to dcache?
+ */
+void smb_renew_times(struct dentry * dentry)
+{
+	for (;;) {
+		dentry->d_time = jiffies;
+		if (dentry == dentry->d_parent)
+			break;
+		dentry = dentry->d_parent;
 	}
-	c_entry = NULL;
 }
 
 static int
-smb_lookup(struct inode *dir, struct dentry *d_entry)
+smb_lookup(struct inode *dir, struct dentry *dentry)
 {
 	struct smb_fattr finfo;
 	struct inode *inode;
 	int error;
 
 	error = -ENAMETOOLONG;
-	if (d_entry->d_name.len > SMB_MAXNAMELEN)
+	if (dentry->d_name.len > SMB_MAXNAMELEN)
 		goto out;
 
-	error = smb_proc_getattr(d_entry->d_parent, &(d_entry->d_name), &finfo);
-#if SMBFS_PARANOIA
+	error = smb_proc_getattr(dentry->d_parent, &(dentry->d_name), &finfo);
+#ifdef SMBFS_PARANOIA
 if (error && error != -ENOENT)
 printk("smb_lookup: find %s/%s failed, error=%d\n",
-d_entry->d_parent->d_name.name, d_entry->d_name.name, error);
+dentry->d_parent->d_name.name, dentry->d_name.name, error);
 #endif
 
 	inode = NULL;
@@ -370,10 +325,11 @@
 		if (inode)
 		{
 			/* cache the dentry pointer */
-			inode->u.smbfs_i.dentry = d_entry;
+			inode->u.smbfs_i.dentry = dentry;
 	add_entry:
-			d_entry->d_op = &smbfs_dentry_operations;
-			d_add(d_entry, inode);
+			dentry->d_op = &smbfs_dentry_operations;
+			d_add(dentry, inode);
+			smb_renew_times(dentry);
 			error = 0;
 		}
 	}
@@ -385,25 +341,24 @@
  * This code is common to all routines creating a new inode.
  */
 static int
-smb_instantiate(struct inode *dir, struct dentry *dentry)
+smb_instantiate(struct dentry *dentry)
 {
 	struct smb_fattr fattr;
 	int error;
 
-	smb_invalid_dir_cache(dir);
-
 	error = smb_proc_getattr(dentry->d_parent, &(dentry->d_name), &fattr);
 	if (!error)
 	{
 		struct inode *inode;
 		error = -EACCES;
 		fattr.f_ino = smb_invent_inos(1);
-		inode = smb_iget(dir->i_sb, &fattr);
+		inode = smb_iget(dentry->d_sb, &fattr);
 		if (inode)
 		{
 			/* cache the dentry pointer */
 			inode->u.smbfs_i.dentry = dentry;
 			d_instantiate(dentry, inode);
+			smb_renew_times(dentry);
 			error = 0;
 		}
 	}
@@ -424,11 +379,12 @@
          * state. Currently we close it directly again, although this
 	 * is not necessary anymore. */
 
+	smb_invalid_dir_cache(dir);
 	error = smb_proc_create(dentry->d_parent, &(dentry->d_name), 
 				0, CURRENT_TIME);
 	if (!error)
 	{
-		error = smb_instantiate(dir, dentry);
+		error = smb_instantiate(dentry);
 	}
 out:
 	return error;
@@ -444,10 +400,11 @@
 	if (dentry->d_name.len > SMB_MAXNAMELEN)
 		goto out;
 
+	smb_invalid_dir_cache(dir);
 	error = smb_proc_mkdir(dentry->d_parent, &(dentry->d_name));
 	if (!error)
 	{
-		error = smb_instantiate(dir, dentry);
+		error = smb_instantiate(dentry);
 	}
 out:
 	return error;
@@ -459,7 +416,7 @@
 	int error;
 
 	error = -ENAMETOOLONG;
-	if (dentry->d_name.len > NFS_MAXNAMLEN)
+	if (dentry->d_name.len > SMB_MAXNAMELEN)
 		goto out;
 
 	/*
@@ -468,11 +425,12 @@
 	 */
 	if (dentry->d_inode)
 		smb_close(dentry->d_inode);
-	smb_invalid_dir_cache(dir);
 
+	smb_invalid_dir_cache(dir);
 	error = smb_proc_rmdir(dentry->d_parent, &(dentry->d_name));
 	if (!error)
 	{
+		smb_renew_times(dentry);
 		d_delete(dentry);
 	}
 out:
@@ -494,11 +452,12 @@
 	 */
 	if (dentry->d_inode)
 		smb_close(dentry->d_inode);
-	smb_invalid_dir_cache(dir);
 
+	smb_invalid_dir_cache(dir);
 	error = smb_proc_unlink(dentry->d_parent, &(dentry->d_name));
 	if (!error)
 	{
+		smb_renew_times(dentry);
 		d_delete(dentry);
 	}
 out:
@@ -511,19 +470,6 @@
 {
 	int error;
 
-	error = -ENOTDIR;
-	if (!old_dir || !S_ISDIR(old_dir->i_mode))
-	{
-		printk("smb_rename: old inode is NULL or not a directory\n");
-		goto out;
-	}
-
-	if (!new_dir || !S_ISDIR(new_dir->i_mode))
-	{
-		printk("smb_rename: new inode is NULL or not a directory\n");
-		goto out;
-	}
-
 	error = -ENAMETOOLONG;
 	if (old_dentry->d_name.len > SMB_MAXNAMELEN ||
 	    new_dentry->d_name.len > SMB_MAXNAMELEN)
@@ -538,10 +484,8 @@
 	if (new_dentry->d_inode)
 		smb_close(new_dentry->d_inode);
 
-	/* Assume success and invalidate now */
 	smb_invalid_dir_cache(old_dir);
 	smb_invalid_dir_cache(new_dir);
-
 	error = smb_proc_mv(old_dentry->d_parent, &(old_dentry->d_name),
 			    new_dentry->d_parent, &(new_dentry->d_name));
 	/*
@@ -549,22 +493,24 @@
 	 */
 	if (error == -EEXIST)
 	{
-#ifdef SMBFS_PARANOIA
+#ifdef SMBFS_DEBUG_VERBOSE
 printk("smb_rename: existing file %s/%s, d_count=%d\n",
 new_dentry->d_parent->d_name.name, new_dentry->d_name.name, 
 new_dentry->d_count);
 #endif
 		error = smb_proc_unlink(new_dentry->d_parent, 
 					&(new_dentry->d_name));
-#ifdef SMBFS_PARANOIA
+#ifdef SMBFS_DEBUG_VERBOSE
 printk("smb_rename: after unlink error=%d\n", error);
 #endif
-		if (error)
-			goto out;
-		d_delete(new_dentry);
-
-		error = smb_proc_mv(old_dentry->d_parent, &(old_dentry->d_name),
-				    new_dentry->d_parent, &(new_dentry->d_name));
+		if (!error)
+		{
+			d_delete(new_dentry);
+			error = smb_proc_mv(old_dentry->d_parent, 
+						&(old_dentry->d_name),
+				   	    new_dentry->d_parent, 
+						&(new_dentry->d_name));
+		}
 	}
 
 	/*
@@ -572,6 +518,8 @@
 	 */
 	if (!error)
 	{
+		smb_renew_times(old_dentry);
+		smb_renew_times(new_dentry->d_parent);
 		d_move(old_dentry, new_dentry);
 	}
 out:
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/file.c linux/fs/smbfs/file.c
--- v2.1.57/linux/fs/smbfs/file.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/smbfs/file.c	Sun Oct 12 10:17:05 1997
@@ -23,17 +23,29 @@
 /* #define SMBFS_DEBUG_VERBOSE 1 */
 /* #define pr_debug printk */
 
+extern int smb_get_rsize(struct smb_sb_info *);
+extern int smb_get_wsize(struct smb_sb_info *);
+
 static inline int
 min(int a, int b)
 {
 	return a < b ? a : b;
 }
 
+static inline void
+smb_unlock_page(struct page *page)
+{
+	clear_bit(PG_locked, &page->flags);
+	wake_up(&page->wait);
+}
+
 static int
 smb_fsync(struct file *file, struct dentry * dentry)
 {
-	printk("smb_fsync: sync file %s/%s\n", 
-		dentry->d_parent->d_name.name, dentry->d_name.name);
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_fsync: sync file %s/%s\n", 
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
 	return 0;
 }
 
@@ -43,14 +55,13 @@
 static int
 smb_readpage_sync(struct inode *inode, struct page *page)
 {
-	unsigned long offset = page->offset;
 	char *buffer = (char *) page_address(page);
+	unsigned long offset = page->offset;
 	struct dentry * dentry = inode->u.smbfs_i.dentry;
-	int rsize = SMB_SERVER(inode)->opt.max_xmit - (SMB_HEADER_LEN+15);
-	int result, refresh = 0;
+	int rsize = smb_get_rsize(SMB_SERVER(inode));
 	int count = PAGE_SIZE;
+	int result;
 
-	pr_debug("SMB: smb_readpage_sync(%p)\n", page);
 	clear_bit(PG_error, &page->flags);
 
 	result = -EIO;
@@ -60,24 +71,22 @@
 		goto io_error;
 	}
  
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_readpage_sync: file %s/%s, count=%d@%ld, rsize=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, count, offset, rsize);
+#endif
 	result = smb_open(dentry, O_RDONLY);
 	if (result < 0)
 		goto io_error;
-	/* Should revalidate inode ... */
 
 	do {
 		if (count < rsize)
 			rsize = count;
 
-#ifdef SMBFS_DEBUG_VERBOSE
-printk("smb_readpage: reading %s/%s, offset=%ld, buffer=%p, size=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, offset, buffer, rsize);
-#endif
 		result = smb_proc_read(inode, offset, rsize, buffer);
 		if (result < 0)
 			goto io_error;
 
-		refresh = 1;
 		count -= result;
 		offset += result;
 		buffer += result;
@@ -90,10 +99,7 @@
 	result = 0;
 
 io_error:
-	if (refresh)
-		smb_refresh_inode(inode);
-	clear_bit(PG_locked, &page->flags);
-	wake_up(&page->wait);
+	smb_unlock_page(page);
 	return result;
 }
 
@@ -110,7 +116,7 @@
 	set_bit(PG_locked, &page->flags);
 	atomic_inc(&page->count);
 	error = smb_readpage_sync(inode, page);
-	__free_page(page);
+	free_page(page_address(page));
 	return error;
 }
 
@@ -122,24 +128,24 @@
 smb_writepage_sync(struct inode *inode, struct page *page,
 		   unsigned long offset, unsigned int count)
 {
-	int wsize = SMB_SERVER(inode)->opt.max_xmit - (SMB_HEADER_LEN+15);
+	u8 *buffer = (u8 *) page_address(page) + offset;
+	int wsize = smb_get_wsize(SMB_SERVER(inode));
 	int result, refresh = 0, written = 0;
-	u8 *buffer;
 
-	pr_debug("SMB:      smb_writepage_sync(%x/%ld %d@%ld)\n",
-		 inode->i_dev, inode->i_ino,
-		 count, page->offset + offset);
-
-	buffer = (u8 *) page_address(page) + offset;
 	offset += page->offset;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_writepage_sync: file %s/%s, count=%d@%ld, wsize=%d\n",
+((struct dentry *) inode->u.smbfs_i.dentry)->d_parent->d_name.name, 
+((struct dentry *) inode->u.smbfs_i.dentry)->d_name.name, count, offset, wsize);
+#endif
 
 	do {
 		if (count < wsize)
 			wsize = count;
 
 		result = smb_proc_write(inode, offset, wsize, buffer);
-
-		if (result < 0) {
+		if (result < 0)
+		{
 			/* Must mark the page invalid after I/O error */
 			clear_bit(PG_uptodate, &page->flags);
 			goto io_error;
@@ -157,11 +163,11 @@
 	} while (count);
 
 io_error:
+#if 0
 	if (refresh)
 		smb_refresh_inode(inode);
-
-	clear_bit(PG_locked, &page->flags);
-	wake_up(&page->wait);
+#endif
+	smb_unlock_page(page);
 	return written ? written : result;
 }
 
@@ -181,7 +187,7 @@
 	set_bit(PG_locked, &page->flags);
 	atomic_inc(&page->count);
 	result = smb_writepage_sync(inode, page, 0, PAGE_SIZE);
-	__free_page(page);
+	free_page(page_address(page));
 	return result;
 }
 
@@ -189,8 +195,8 @@
 smb_updatepage(struct inode *inode, struct page *page, const char *buffer,
 	       unsigned long offset, unsigned int count, int sync)
 {
-	u8		*page_addr;
-	int 	result;
+	unsigned long page_addr = page_address(page);
+	int result;
 
 	pr_debug("SMB:      smb_updatepage(%x/%ld %d@%ld, sync=%d)\n",
 		 inode->i_dev, inode->i_ino,
@@ -203,21 +209,21 @@
 	set_bit(PG_locked, &page->flags);
 	atomic_inc(&page->count);
 
-	page_addr = (u8 *) page_address(page);
-
-	if (copy_from_user(page_addr + offset, buffer, count))
+	if (copy_from_user((char *) page_addr + offset, buffer, count))
 		goto bad_fault;
 	result = smb_writepage_sync(inode, page, offset, count);
 out:
-	__free_page(page);
+	free_page(page_addr);
 	return result;
 
 bad_fault:
-	printk("smb_updatepage: fault at page=%p buffer=%p\n", page, buffer);
+#ifdef SMBFS_PARANOIA
+printk("smb_updatepage: fault at addr=%lu, offset=%lu, buffer=%p\n", 
+page_addr, offset, buffer);
+#endif
 	result = -EFAULT;
 	clear_bit(PG_uptodate, &page->flags);
-	clear_bit(PG_locked, &page->flags);
-	wake_up(&page->wait);
+	smb_unlock_page(page);
 	goto out;
 }
 
@@ -227,9 +233,11 @@
 {
 	int	status;
 
-	pr_debug("SMB: read(%x/%ld (%d), %lu@%lu)\n",
-		 inode->i_dev, inode->i_ino, inode->i_count,
-		 count, (unsigned long) file->f_pos);
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_file_read: file %s/%s, count=%lu@%lu\n",
+file->f_dentry->d_parent->d_name.name, file->f_dentry->d_name.name,
+count, (unsigned long) file->f_pos);
+#endif
 
 	status = smb_revalidate_inode(inode);
 	if (status >= 0)
@@ -246,6 +254,11 @@
 	struct inode * inode = dentry->d_inode;
 	int	status;
 
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_file_mmap: file %s/%s, address %lu - %lu\n",
+file->f_dentry->d_parent->d_name.name, file->f_dentry->d_name.name,
+vma->vm_start, vma->vm_end);
+#endif
 	status = smb_revalidate_inode(inode);
 	if (status >= 0)
 	{
@@ -263,40 +276,67 @@
 {
 	int	result;
 
-	pr_debug("SMB: write(%x/%ld (%d), %lu@%lu)\n",
-		 inode->i_dev, inode->i_ino, inode->i_count,
-		 count, (unsigned long) file->f_pos);
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_file_write: file %s/%s, count=%lu@%lu\n",
+file->f_dentry->d_parent->d_name.name, file->f_dentry->d_name.name,
+count, (unsigned long) file->f_pos);
+#endif
 
+#ifdef SMBFS_PARANOIA
+	/* Should be impossible now that inodes can't change mode */
 	result = -EINVAL;
-	if (!inode) {
-		printk("smb_file_write: inode = NULL\n");
+	if (!S_ISREG(inode->i_mode))
+	{
+		printk("smb_file_write: write to non-file, mode %07o\n",
+			inode->i_mode);
 		goto out;
 	}
+#endif
 
 	result = smb_revalidate_inode(inode);
-	if (result < 0)
+	if (result)
 		goto out;
 
 	result = smb_open(file->f_dentry, O_WRONLY);
-	if (result < 0)
+	if (result)
 		goto out;
 
-	result = -EINVAL;
-	if (!S_ISREG(inode->i_mode)) {
-		printk("smb_file_write: write to non-file, mode %07o\n",
-			inode->i_mode);
-		goto out;
-	}
-
-	result = 0;
 	if (count > 0)
 	{
 		result = generic_file_write(inode, file, buf, count);
+		if (result > 0)
+			smb_refresh_inode(inode);
 	}
 out:
 	return result;
 }
 
+static int
+smb_file_open(struct inode *inode, struct file * file)
+{
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_file_open: inode=%p, file=%p\n", inode, file);
+#endif
+	return 0;
+}
+
+static int
+smb_file_release(struct inode *inode, struct file * file)
+{
+	struct dentry * dentry = file->f_dentry;
+
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_file_release: closing file %s/%s, d_count=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
+#endif
+	
+	if (dentry->d_count == 1)
+	{
+		smb_close(inode);
+	}
+	return 0;
+}
+
 static struct file_operations smb_file_operations =
 {
 	NULL,			/* lseek - default */
@@ -305,10 +345,14 @@
 	NULL,			/* readdir - bad */
 	NULL,			/* poll - default */
 	smb_ioctl,		/* ioctl */
-	smb_file_mmap,		/* mmap */
-	NULL,			/* open */
-	NULL,			/* release */
-	smb_fsync,		/* fsync */
+	smb_file_mmap,		/* mmap(struct file*, struct vm_area_struct*) */
+	smb_file_open,		/* open(struct inode*, struct file*) */
+	smb_file_release,	/* release(struct inode*, struct file*) */
+	smb_fsync,		/* fsync(struct file*, struct dentry*) */
+	NULL,			/* fasync(struct file*, int) */
+	NULL,			/* check_media_change(kdev_t dev) */
+	NULL,			/* revalidate(kdev_t dev) */
+	NULL			/* lock(struct file*, int, struct file_lock*) */
 };
 
 struct inode_operations smb_file_inode_operations =
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/inode.c linux/fs/smbfs/inode.c
--- v2.1.57/linux/fs/smbfs/inode.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/smbfs/inode.c	Sun Oct 12 10:17:05 1997
@@ -6,6 +6,8 @@
  *
  */
 
+#define SMBFS_DCACHE_EXT 1
+
 #include <linux/config.h>
 #include <linux/module.h>
 
@@ -18,17 +20,20 @@
 #include <linux/stat.h>
 #include <linux/errno.h>
 #include <linux/locks.h>
-#include <linux/file.h>
-#include <linux/fcntl.h>
 #include <linux/malloc.h>
 #include <linux/init.h>
+#include <linux/dcache.h>
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
 
-#define SB_of(server) ((struct super_block *) ((char *)(server) - \
-	(unsigned long)(&((struct super_block *)0)->u.smbfs_sb)))
+#define SMBFS_PARANOIA 1
+/* #define SMBFS_DEBUG_VERBOSE 1 */
 
+#ifndef SMBFS_DCACHE_EXT
+#define shrink_dcache_sb(sb) shrink_dcache()
+#endif
+extern void smb_renew_times(struct dentry *);
 extern int close_fp(struct file *filp);
 
 static void smb_put_inode(struct inode *);
@@ -67,7 +72,7 @@
 	return ino;
 }
 
-static struct smb_fattr *read_fattr;
+static struct smb_fattr *read_fattr = NULL;
 static struct semaphore read_semaphore = MUTEX;
 
 struct inode *
@@ -80,6 +85,7 @@
 	down(&read_semaphore);
 	read_fattr = fattr;
 	result = iget(sb, fattr->f_ino);
+	read_fattr = NULL;
 	up(&read_semaphore);
 	return result;
 }
@@ -87,7 +93,6 @@
 static void
 smb_set_inode_attr(struct inode *inode, struct smb_fattr *fattr)
 {
-	inode->i_dev	= inode->i_sb->s_dev;
 	inode->i_mode	= fattr->f_mode;
 	inode->i_nlink	= fattr->f_nlink;
 	inode->i_uid	= fattr->f_uid;
@@ -99,6 +104,10 @@
 	inode->i_atime	= fattr->f_atime;
 	inode->i_blksize= fattr->f_blksize;
 	inode->i_blocks = fattr->f_blocks;
+	/*
+	 * Update the "last time refreshed" field for revalidation.
+	 */
+	inode->u.smbfs_i.oldmtime = jiffies;
 }
 
 static void
@@ -106,15 +115,15 @@
 {
 	pr_debug("smb_iget: %p\n", read_fattr);
 
-	if ((atomic_read(&read_semaphore.count) == 1) ||
-	    (inode->i_ino != read_fattr->f_ino))
+	if (!read_fattr || inode->i_ino != read_fattr->f_ino)
 	{
 		printk("smb_read_inode called from invalid point\n");
 		return;
 	}
 
-	smb_set_inode_attr(inode, read_fattr);
+	inode->i_dev = inode->i_sb->s_dev;
 	memset(&(inode->u.smbfs_i), 0, sizeof(inode->u.smbfs_i));
+	smb_set_inode_attr(inode, read_fattr);
 
 	if (S_ISREG(inode->i_mode))
 		inode->i_op = &smb_file_inode_operations;
@@ -131,59 +140,129 @@
 void
 smb_invalidate_inodes(struct smb_sb_info *server)
 {
-	printk("smb_invalidate_inodes\n");
-	shrink_dcache(); /* should shrink only this sb */
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_invalidate_inodes\n");
+#endif
+	shrink_dcache_sb(SB_of(server));
 	invalidate_inodes(SB_of(server));
 }
 
 int
-smb_revalidate_inode(struct inode *ino)
+smb_revalidate_inode(struct inode *inode)
 {
-	struct dentry * dentry = ino->u.smbfs_i.dentry;
+	time_t last_time;
 	int error = 0;
 
 	pr_debug("smb_revalidate_inode\n");
-	if (!ino)
-		goto bad_no_inode;
-	dentry = ino->u.smbfs_i.dentry;
-#if 0
-	if (dentry)
+	/*
+	 * Check whether we've recently refreshed the inode.
+	 */
+	if (jiffies < inode->u.smbfs_i.oldmtime + HZ/10)
 	{
-		printk("smb_revalidate: checking %s/%s\n",
-			dentry->d_parent->d_name.name, dentry->d_name.name);
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_revalidate_inode: up-to-date, jiffies=%lu, oldtime=%lu\n",
+jiffies, inode->u.smbfs_i.oldmtime);
+#endif
+		goto out;
 	}
+
+	/*
+	 * Save the last modified time, then refresh the inode
+	 */
+	last_time = inode->i_mtime;
+	error = smb_refresh_inode(inode);
+	if (!error)
+	{
+		if (inode->i_mtime != last_time)
+		{
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_revalidate: %s/%s changed, old=%ld, new=%ld\n",
+((struct dentry *)inode->u.smbfs_i.dentry)->d_parent->d_name.name,
+((struct dentry *)inode->u.smbfs_i.dentry)->d_name.name,
+(long) last_time, (long) inode->i_mtime);
 #endif
+			if (!S_ISDIR(inode->i_mode))
+				invalidate_inode_pages(inode);
+			else
+				smb_invalid_dir_cache(inode);
+		}
+	}
 out:
 	return error;
-
-bad_no_inode:
-	printk("smb_revalidate: no inode!\n");
-	error = -EINVAL;
-	goto out;
 }
 
+/*
+ * This is called to update the inode attributes after
+ * we've made changes to a file or directory.
+ */
 int
-smb_refresh_inode(struct inode *ino)
+smb_refresh_inode(struct inode *inode)
 {
-	struct dentry * dentry = ino->u.smbfs_i.dentry;
+	struct dentry * dentry = inode->u.smbfs_i.dentry;
 	struct smb_fattr fattr;
 	int error;
 
 	pr_debug("smb_refresh_inode\n");
-	error = -EIO;
-	if (!dentry) {
+	if (!dentry)
+	{
 		printk("smb_refresh_inode: no dentry, can't refresh\n");
+		error = -EIO;
 		goto out;
 	}
 
-	/* N.B. Should check for changes of important fields! cf. NFS */
+	/*
+	 * Kludge alert ... for some reason we can't get attributes
+	 * for the root directory, so just return success.
+	 */
+	error = 0;
+	if (IS_ROOT(dentry))
+		goto out;
+
 	error = smb_proc_getattr(dentry->d_parent, &(dentry->d_name), &fattr);
 	if (!error)
 	{
-		smb_set_inode_attr(ino, &fattr);
+		smb_renew_times(dentry);
+		/*
+		 * Check whether the type part of the mode changed,
+		 * and don't update the attributes if it did.
+		 */
+		if ((inode->i_mode & S_IFMT) == (fattr.f_mode & S_IFMT))
+			smb_set_inode_attr(inode, &fattr);
+		else
+		{
+			/*
+			 * Big trouble! The inode has become a new object,
+			 * so any operations attempted on it are invalid.
+			 *
+			 * Take a couple of steps to limit damage:
+			 * (1) Mark the inode as bad so that subsequent
+			 *     lookup validations will fail.
+			 * (2) Clear i_nlink so the inode will be released
+			 *     at iput() time. (Unhash it as well?)
+			 * We also invalidate the caches for good measure.
+			 */
+#ifdef SMBFS_PARANOIA
+printk("smb_refresh_inode: %s/%s changed mode, %07o to %07o\n",
+dentry->d_parent->d_name.name, dentry->d_name.name,
+inode->i_mode, fattr.f_mode);
+#endif
+			fattr.f_mode = inode->i_mode; /* save mode */
+			make_bad_inode(inode);
+			inode->i_mode = fattr.f_mode; /* restore mode */
+			inode->i_nlink = 0;
+			/*
+			 * No need to worry about unhashing the dentry: the
+			 * lookup validation will see that the inode is bad.
+			 * But we may need to invalidate the caches ...
+			 */
+			invalidate_inode_pages(inode);
+			smb_invalid_dir_cache(inode);
+			error = -EIO;
+		}
 	}
 out:
 	return error;
+
 }
 
 /*
@@ -192,19 +271,20 @@
 static void
 smb_put_inode(struct inode *ino)
 {
-	struct dentry * dentry;
 	pr_debug("smb_put_inode: count = %d\n", ino->i_count);
 
 	if (ino->i_count > 1) {
+		struct dentry * dentry;
 		/*
 		 * Check whether the dentry still holds this inode. 
-		 * This looks scary, but should work ... d_inode is 
-		 * cleared before iput() in the dcache. 
+		 * This looks scary, but should work ... if this is
+		 * the last use, d_inode == NULL or d_count == 0. 
 		 */
 		dentry = (struct dentry *) ino->u.smbfs_i.dentry;
-		if (dentry && dentry->d_inode != ino) {
+		if (dentry && (dentry->d_inode != ino || dentry->d_count == 0))
+		{
 			ino->u.smbfs_i.dentry = NULL;
-#if 1
+#ifdef SMBFS_DEBUG_VERBOSE
 printk("smb_put_inode: cleared dentry for %s/%s (%ld), count=%d\n",
 dentry->d_parent->d_name.name, dentry->d_name.name, ino->i_ino, ino->i_count);
 #endif
@@ -263,7 +343,6 @@
 	struct smb_mount_data *data = (struct smb_mount_data *)raw_data;
 	struct smb_fattr root;
 	kdev_t dev = sb->s_dev;
-	unsigned char *packet;
 	struct inode *root_inode;
 	struct dentry *dentry;
 
@@ -275,62 +354,65 @@
 	if (data->version != SMB_MOUNT_VERSION)
 		goto out_wrong_data;
 
-	packet = smb_vmalloc(SMB_INITIAL_PACKET_SIZE);	
-	if (!packet)
-		goto out_no_mem;
-
 	lock_super(sb);
 
 	sb->s_blocksize = 1024;	/* Eh...  Is this correct? */
 	sb->s_blocksize_bits = 10;
 	sb->s_magic = SMB_SUPER_MAGIC;
-	sb->s_dev = dev;
+	sb->s_dev = dev; /* shouldn't need this ... */
 	sb->s_op = &smb_sops;
 
 	sb->u.smbfs_sb.sock_file = NULL;
 	sb->u.smbfs_sb.sem = MUTEX;
+	sb->u.smbfs_sb.wait = NULL;
 	sb->u.smbfs_sb.conn_pid = 0;
-	sb->u.smbfs_sb.packet = packet;
-	sb->u.smbfs_sb.packet_size = SMB_INITIAL_PACKET_SIZE;
+	sb->u.smbfs_sb.state = CONN_INVALID; /* no connection yet */
 	sb->u.smbfs_sb.generation = 0;
+	sb->u.smbfs_sb.packet_size = SMB_INITIAL_PACKET_SIZE;	
+	sb->u.smbfs_sb.packet = smb_vmalloc(SMB_INITIAL_PACKET_SIZE);	
+	if (!sb->u.smbfs_sb.packet)
+		goto out_no_mem;
 
 	sb->u.smbfs_sb.m = *data;
 	sb->u.smbfs_sb.m.file_mode = (sb->u.smbfs_sb.m.file_mode &
 				      (S_IRWXU | S_IRWXG | S_IRWXO)) | S_IFREG;
 	sb->u.smbfs_sb.m.dir_mode = (sb->u.smbfs_sb.m.dir_mode &
 				     (S_IRWXU | S_IRWXG | S_IRWXO)) | S_IFDIR;
-
+	/*
+	 * Keep the super block locked while we get the root inode.
+	 */
 	smb_init_root_dirent(&(sb->u.smbfs_sb), &root);
-
-	sb->s_root = NULL;
-	unlock_super(sb);
-
 	root_inode = smb_iget(sb, &root);
 	if (!root_inode)
 		goto out_no_root;
+
 	dentry = d_alloc_root(root_inode, NULL);
 	if (!dentry)
 		goto out_no_root;
 	root_inode->u.smbfs_i.dentry = dentry;
 	sb->s_root = dentry;
+
+	unlock_super(sb);
 	return sb;
 
-out_no_data:
-	printk("smb_read_super: missing data argument\n");
-	goto out;
-out_wrong_data:
-	printk(KERN_ERR "smb_read_super: wrong data argument."
-	       " Recompile smbmount.\n");
-	goto out;
-out_no_mem:
-	pr_debug("smb_read_super: could not alloc packet\n");
-	goto out;
 out_no_root:
-	sb->s_dev = 0; /* iput() might block */
 	printk(KERN_ERR "smb_read_super: get root inode failed\n");
 	iput(root_inode);
-	smb_vfree(packet);
-out:
+	smb_vfree(sb->u.smbfs_sb.packet);
+	goto out_unlock;
+out_no_mem:
+	printk("smb_read_super: could not alloc packet\n");
+	goto out_unlock;
+out_wrong_data:
+	printk(KERN_ERR "smb_read_super: wrong data argument."
+	       " Recompile smbmount.\n");
+	goto out_fail;
+out_no_data:
+	printk("smb_read_super: missing data argument\n");
+	goto out_fail;
+out_unlock:
+	unlock_super(sb);
+out_fail:
 	sb->s_dev = 0;
 	MOD_DEC_USE_COUNT;
 	return NULL;
@@ -355,8 +437,9 @@
 int
 smb_notify_change(struct inode *inode, struct iattr *attr)
 {
+	struct smb_sb_info *server = SMB_SERVER(inode);
 	struct dentry *dentry = inode->u.smbfs_i.dentry;
-	int error;
+	int error, refresh = 0;
 
 	error = -EIO;
 	if (!dentry)
@@ -365,40 +448,54 @@
 		goto out;
 	}
 
+	/*
+	 * Make sure our inode is up-to-date ...
+	 */
+	error = smb_revalidate_inode(inode);
+	if (error)
+		goto out;
+
 	if ((error = inode_change_ok(inode, attr)) < 0)
 		goto out;
 
 	error = -EPERM;
-	if (((attr->ia_valid & ATTR_UID) &&
-	     (attr->ia_uid != SMB_SERVER(inode)->m.uid)))
+	if (((attr->ia_valid & ATTR_UID) && (attr->ia_uid != server->m.uid)))
 		goto out;
 
-	if (((attr->ia_valid & ATTR_GID) &&
-	     (attr->ia_uid != SMB_SERVER(inode)->m.gid)))
+	if (((attr->ia_valid & ATTR_GID) && (attr->ia_uid != server->m.gid)))
 		goto out;
 
 	if (((attr->ia_valid & ATTR_MODE) &&
 	(attr->ia_mode & ~(S_IFREG | S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO))))
 		goto out;
 
-	/*
-	 * Assume success and invalidate the parent's dir cache
-	 */ 
-	smb_invalid_dir_cache(dentry->d_parent->d_inode);
-
 	if ((attr->ia_valid & ATTR_SIZE) != 0)
 	{
-		if ((error = smb_open(dentry, O_WRONLY)) < 0)
+		error = smb_open(dentry, O_WRONLY);
+		if (error)
 			goto out;
-
-		if ((error = smb_proc_trunc(SMB_SERVER(inode),
-					    inode->u.smbfs_i.fileid,
-					    attr->ia_size)) < 0)
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_notify_change: changing %s/%s, old size=%ld, new size=%ld\n",
+dentry->d_parent->d_name.name, dentry->d_name.name,
+(long) inode->i_size, (long) attr->ia_size);
+#endif
+		error = smb_proc_trunc(server, inode->u.smbfs_i.fileid,
+					 attr->ia_size);
+		if (error)
 			goto out;
+		/*
+		 * We don't implement an i_op->truncate operation,
+		 * so we have to update the page cache here.
+		 */
+		if (attr->ia_size < inode->i_size) {
+			truncate_inode_pages(inode, attr->ia_size);
+			inode->i_size = attr->ia_size;
+		}
+		refresh = 1;
 	}
+
 	if ((attr->ia_valid & (ATTR_CTIME | ATTR_MTIME | ATTR_ATIME)) != 0)
 	{
-
 		struct smb_fattr fattr;
 
 		fattr.attr = 0;
@@ -417,15 +514,25 @@
 		if ((attr->ia_valid & ATTR_ATIME) != 0)
 			fattr.f_atime = attr->ia_atime;
 
-		error = smb_proc_setattr(SMB_SERVER(inode), dentry, &fattr);
-		if (error >= 0)
-		{
-			inode->i_ctime = fattr.f_ctime;
-			inode->i_mtime = fattr.f_mtime;
-			inode->i_atime = fattr.f_atime;
-		}
+		error = smb_proc_setattr(server, dentry, &fattr);
+		if (error)
+			goto out;
+		refresh = 1;
 	}
+	error = 0;
+
 out:
+	if (refresh)
+	{
+		/*
+		 * N.B. Currently we're only using the dir cache for
+		 * file names, so we don't need to invalidate here.
+		 */
+#if 0
+		smb_invalid_dir_cache(dentry->d_parent->d_inode);
+#endif
+		smb_refresh_inode(inode);
+	}
 	return error;
 }
 
@@ -461,7 +568,6 @@
 	smb_current_vmalloced = 0;
 #endif
 
-	smb_init_dir_cache();
 	read_semaphore = MUTEX;
 
 	return init_smb_fs();
@@ -471,7 +577,6 @@
 cleanup_module(void)
 {
 	pr_debug("smbfs: cleanup_module called\n");
-	smb_free_dir_cache();
 	unregister_filesystem(&smb_fs_type);
 #ifdef DEBUG_SMB_MALLOC
 	printk(KERN_DEBUG "smb_malloced: %d\n", smb_malloced);
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/proc.c linux/fs/smbfs/proc.c
--- v2.1.57/linux/fs/smbfs/proc.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/smbfs/proc.c	Sun Oct 12 10:17:05 1997
@@ -5,6 +5,8 @@
  *  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
  */
 
 #include <linux/config.h>
@@ -16,6 +18,8 @@
 #include <linux/malloc.h>
 #include <linux/stat.h>
 #include <linux/fcntl.h>
+#include <linux/dcache.h>
+#include <linux/dirent.h>
 
 #include <asm/uaccess.h>
 #include <asm/string.h>
@@ -33,8 +37,7 @@
 /* #define SMBFS_DEBUG_VERBOSE 1 */
 /* #define pr_debug printk */
 
-static int smb_request_ok(struct smb_sb_info *s, int command, int wct, int bcc);
-void smb_close_socket(struct smb_sb_info *);
+extern void smb_renew_times(struct dentry *);
 
 static inline int
 min(int a, int b)
@@ -64,6 +67,17 @@
 	}
 }
 
+static void reverse_string(char *buf, int len) {
+	char c;
+	char *end = buf+len-1;
+
+	while(buf < end) {
+		c = *buf;
+		*(buf++) = *end;
+		*(end--) = c;
+	}
+}
+
 /*****************************************************************************/
 /*                                                                           */
 /*  Encoding/Decoding section                                                */
@@ -85,30 +99,55 @@
 }
 
 /*
- * Return the server for the specified dentry
- * N.B. Make this a #define in the smb header
+ * smb_build_path: build the path to entry and name storing it in buf.
+ * The path returned will have the trailing '\0'.
  */
-static struct smb_sb_info * server_from_dentry(struct dentry * dentry)
+static int smb_build_path(struct dentry * entry, struct qstr * name, char * buf)
 {
-	return &dentry->d_sb->u.smbfs_sb;
-}
+	char *path = buf;
 
-static int smb_d_path(struct dentry * entry, char * buf)
-{
+	if (entry == NULL)
+		goto test_name_and_out;
+
+	/*
+	 * If IS_ROOT, we have to do no walking at all.
+	 */
 	if (IS_ROOT(entry)) {
-		*buf = '\\';
-		return 1;
-	} else {
-		int len = smb_d_path(entry->d_parent, buf);
-
-		buf += len;
-		if (len > 1) {
-			*buf++ = '\\';
-			len++;
-		}
-		memcpy(buf, entry->d_name.name, entry->d_name.len);
-		return len + entry->d_name.len;
+		*(path++) = '\\';
+		if (name != NULL)
+			goto name_and_out;
+		goto out;
 	}
+
+	/*
+	 * Build the path string walking the tree backward from end to ROOT
+	 * and store it in reversed order [see reverse_string()]
+	 */
+	for (;;) {
+		memcpy(path, entry->d_name.name, entry->d_name.len);
+		reverse_string(path, entry->d_name.len);
+		path += entry->d_name.len;
+
+		*(path++) = '\\';
+
+		entry = entry->d_parent;
+
+		if (IS_ROOT(entry))
+			break;
+	}
+
+	reverse_string(buf, path-buf);
+
+test_name_and_out:
+	if (name != NULL) {
+		*(path++) = '\\';
+name_and_out:
+		memcpy(path, name->name, name->len);
+		path += name->len;
+	}
+out:
+	*(path++) = '\0';
+	return (path-buf);
 }
 
 static char *smb_encode_path(struct smb_sb_info *server, char *buf,
@@ -116,15 +155,7 @@
 {
 	char *start = buf;
 
-	if (dir != NULL)
-		buf += smb_d_path(dir, buf);
-
-	if (name != NULL) {
-		*buf++ = '\\';
-		memcpy(buf, name->name, name->len);
-		buf += name->len;
-		*buf++ = 0;
-	}
+	buf += smb_build_path(dir, name, buf);
 
 	if (server->opt.protocol <= SMB_PROTOCOL_COREPLUS)
 		str_upper(start);
@@ -249,6 +280,55 @@
 		(bcc == -1 || SMB_BCC(packet) >= bcc)) ? 0 : -EIO;
 }
 
+/*
+ * Returns the maximum read or write size for the current packet size
+ * and max_xmit value.
+ * N.B. Since this value is usually computed before locking the server,
+ * the server's packet size must never be decreased!
+ */
+static int
+smb_get_xmitsize(struct smb_sb_info *server, int overhead)
+{
+	int size = server->packet_size;
+
+	/*
+	 * Start with the smaller of packet size and max_xmit ...
+	 */
+	if (size > server->opt.max_xmit)
+		size = server->opt.max_xmit;
+	return size - overhead;
+}
+
+/*
+ * Calculate the maximum read size
+ */
+int
+smb_get_rsize(struct smb_sb_info *server)
+{
+	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
+	return size;
+}
+
+/*
+ * Calculate the maximum write size
+ */
+int
+smb_get_wsize(struct smb_sb_info *server)
+{
+	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
+	return size;
+}
+
 static int
 smb_errno(int errcls, int error)
 {
@@ -365,38 +445,6 @@
 	up(&(server->sem));
 }
 
-/* smb_request_ok: We expect the server to be locked. Then we do the
-   request and check the answer completely. When smb_request_ok
-   returns 0, you can be quite sure that everything went well. When
-   the answer is <=0, the returned number is a valid unix errno. */
-
-static int
-smb_request_ok(struct smb_sb_info *s, int command, int wct, int bcc)
-{
-	int result = 0;
-
-	s->rcls = 0;
-	s->err = 0;
-
-	if (smb_request(s) < 0)
-	{
-		pr_debug("smb_request failed\n");
-		result = -EIO;
-	} else if (smb_valid_packet(s->packet) != 0)
-	{
-		pr_debug("not a valid packet!\n");
-		result = -EIO;
-	} else if (s->rcls != 0)
-	{
-		result = -smb_errno(s->rcls, s->err);
-	} else if (smb_verify(s->packet, command, wct, bcc) != 0)
-	{
-		pr_debug("smb_verify failed\n");
-		result = -EIO;
-	}
-	return result;
-}
-
 /*
  * smb_retry: This function should be called when smb_request_ok has
    indicated an error. If the error was indicated because the
@@ -409,6 +457,8 @@
 static int
 smb_retry(struct smb_sb_info *server)
 {
+	struct wait_queue wait = { current, NULL };
+	unsigned long timeout;
 	int result = 0;
 
 	if (server->state != CONN_INVALID)
@@ -423,31 +473,94 @@
 		goto out;
 	}
 
-	printk("smb_retry: signalling process %d\n", server->conn_pid);
 	kill_proc(server->conn_pid, SIGUSR1, 0);
+#if 0
 	server->conn_pid = 0;
+#endif
 
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_retry: signalled pid %d, waiting for new connection\n",
+server->conn_pid);
+#endif
 	/*
-	 * Block here until we get a new connection.
-	 * N.B. This needs to be fixed ... we should wait in an
-	 * interruptible sleep for CONN_VALID.
+	 * Wait here for a new connection.
 	 */
-	printk("smb_retry: blocking for new connection\n");
-	smb_lock_server(server);
+	timeout = jiffies + 10*HZ;
+	add_wait_queue(&server->wait, &wait);
+	while (1)
+	{
+		current->state = TASK_INTERRUPTIBLE;
+		current->timeout = jiffies + HZ;
+		if (server->state != CONN_INVALID)
+			break;
+		if (jiffies > timeout)
+		{
+			printk("smb_retry: timed out, try again later\n");
+			break;
+		}
+		if (signal_pending(current))
+		{
+			printk("smb_retry: caught signal\n");
+			break;
+		}
+		schedule();
+	}
+	remove_wait_queue(&server->wait, &wait);
+	current->timeout = 0;
+	current->state = TASK_RUNNING;
 
 	if (server->state == CONN_VALID)
 	{
-		printk("smb_retry: new connection pid=%d\n", server->conn_pid);
+#ifdef SMBFS_PARANOIA
+printk("smb_retry: new connection pid=%d\n", server->conn_pid);
+#endif
 		result = 1;
 	}
+
 out:
 	return result;
 }
 
+/* smb_request_ok: We expect the server to be locked. Then we do the
+   request and check the answer completely. When smb_request_ok
+   returns 0, you can be quite sure that everything went well. When
+   the answer is <=0, the returned number is a valid unix errno. */
+
+static int
+smb_request_ok(struct smb_sb_info *s, int command, int wct, int bcc)
+{
+	int result = 0;
+
+	s->rcls = 0;
+	s->err = 0;
+
+	/* Make sure we have a connection */
+	if (s->state != CONN_VALID && !smb_retry(s))
+	{
+		result = -EIO;
+	} else if (smb_request(s) < 0)
+	{
+		pr_debug("smb_request failed\n");
+		result = -EIO;
+	} else if (smb_valid_packet(s->packet) != 0)
+	{
+		pr_debug("not a valid packet!\n");
+		result = -EIO;
+	} else if (s->rcls != 0)
+	{
+		result = -smb_errno(s->rcls, s->err);
+	} else if (smb_verify(s->packet, command, wct, bcc) != 0)
+	{
+		pr_debug("smb_verify failed\n");
+		result = -EIO;
+	}
+	return result;
+}
+
 /*
  * This is called with the server locked after a successful smb_newconn().
  * It installs the new connection pid, sets server->state to CONN_VALID,
- * and unlocks the server.
+ * and wakes up the process waiting for the new connection.
  * N.B. The first call is made without locking the server -- need to fix!
  */
 int
@@ -458,21 +571,24 @@
 	error = -EACCES;
 	if (!suser() && (current->uid != server->m.mounted_uid))
 		goto out;
+	if (atomic_read(&server->sem.count) == 1)
+	{
+		printk("smb_offerconn: server not locked, count=%d\n",
+			atomic_read(&server->sem.count));
+#if 0
+		goto out;
+#endif
+	}
 
 	server->conn_pid = current->pid;
 	server->state = CONN_VALID;
-	printk("smb_offerconn: state valid, pid=%d\n", server->conn_pid);
+	wake_up_interruptible(&server->wait);
+#ifdef SMBFS_PARANOIA
+printk("smb_offerconn: state valid, pid=%d\n", server->conn_pid);
+#endif
 	error = 0;
 
-	/*
-	 * The initial call may be made without the server locked?
-	 */
 out:
-	if (atomic_read(&server->sem.count) != 1)
-		smb_unlock_server(server);
-	else
-		printk("smb_offerconn: server not locked, count=%d\n",
-			atomic_read(&server->sem.count));
 	return error;
 }
 
@@ -488,15 +604,21 @@
 
 	error = -EBADF;
 	if (opt->fd >= NR_OPEN || !(filp = current->files->fd[opt->fd]))
-		goto out_unlock;
-	if (!S_ISSOCK(filp->f_dentry->d_inode->i_mode))
-		goto out_unlock;
-	if (!S_ISSOCK(filp->f_dentry->d_inode->i_mode))
-		goto out_unlock;
+		goto out;
+	if (!smb_valid_socket(filp->f_dentry->d_inode))
+		goto out;
 
 	error = -EACCES;
 	if (!suser() && (current->uid != server->m.mounted_uid))
-		goto out_unlock;
+		goto out;
+	if (atomic_read(&server->sem.count) == 1)
+	{
+		printk("smb_newconn: server not locked, count=%d\n",
+			atomic_read(&server->sem.count));
+#if 0
+		goto out;
+#endif
+	}
 
 	/*
 	 * Make sure the old socket is closed
@@ -507,23 +629,15 @@
 	server->sock_file = filp;
 	smb_catch_keepalive(server);
 	server->opt = *opt;
-	pr_debug("smb_newconn: protocol = %d\n", server->opt.protocol);
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_newconn: protocol=%d, max_xmit=%d\n",
+server->opt.protocol, server->opt.max_xmit);
+#endif
 	server->generation += 1;
 	error = 0;
 
 out:
 	return error;
-
-	/*
-	 * Unlock now if an error occurred.
-	 */
-out_unlock:
-	if (atomic_read(&server->sem.count) != 1)
-		smb_unlock_server(server);
-	else
-		printk("smb_newconn: server not locked, count=%d\n",
-			atomic_read(&server->sem.count));
-	goto out;
 }
 
 /* smb_setup_header: We completely set up the packet. You only have to
@@ -536,6 +650,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);
+
 	p = smb_encode_smb_length(p, xmit_len - 4);
 
 	*p++ = 0xff;
@@ -577,92 +695,106 @@
 }
 
 /*
- * We're called with the server locked, and we leave it that way. We
- * try maximum permissions.
+ * We're called with the server locked, and we leave it that way.
+ * Set the permissions to be consistent with the desired access.
  */
 
 static int
-smb_proc_open(struct smb_sb_info *server, struct dentry *dir)
+smb_proc_open(struct smb_sb_info *server, struct dentry *dir, int wish)
 {
 	struct inode *ino = dir->d_inode;
+	int mode, read_write = 0x42, read_only = 0x40;
 	int error;
 	char *p;
 
+	mode = read_write;
+#if 0
+	if (!(wish & (O_WRONLY | O_RDWR)))
+		mode = read_only;
+#endif
+
       retry:
 	p = smb_setup_header(server, SMBopen, 2, 0);
-	WSET(server->packet, smb_vwv0, 0x42);	/* read/write */
+	WSET(server->packet, smb_vwv0, mode);
 	WSET(server->packet, smb_vwv1, aSYSTEM | aHIDDEN | aDIR);
 	*p++ = 4;
 	p = smb_encode_path(server, p, dir, NULL);
 	smb_setup_bcc(server, p);
 
-	if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0)
+	error = smb_request_ok(server, SMBopen, 7, 0);
+	if (error != 0)
 	{
 		if (smb_retry(server))
 			goto retry;
 
-		if ((error != -EACCES) && (error != -ETXTBSY)
-		    && (error != -EROFS))
-			goto out;
-
-		p = smb_setup_header(server, SMBopen, 2, 0);
-		WSET(server->packet, smb_vwv0, 0x40);	/* read only */
-		WSET(server->packet, smb_vwv1, aSYSTEM | aHIDDEN | aDIR);
-		*p++ = 4;
-		p = smb_encode_path(server, p, dir, NULL);
-		smb_setup_bcc(server, p);
-
-		if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0)
+		if (mode == read_write &&
+		    (error == -EACCES || error == -ETXTBSY || error == -EROFS))
 		{
-			if (smb_retry(server))
-				goto retry;
-			goto out;
+#ifdef SMBFS_PARANOIA
+printk("smb_proc_open: %s/%s open failed, error=%d, retrying R/O\n",
+dir->d_parent->d_name.name, dir->d_name.name, error);
+#endif
+			mode = read_only;
+			goto retry;
 		}
 	}
 	/* We should now have data in vwv[0..6]. */
 
 	ino->u.smbfs_i.fileid = WVAL(server->packet, smb_vwv0);
 	ino->u.smbfs_i.attr   = WVAL(server->packet, smb_vwv1);
+	/* smb_vwv2 has mtime */
+	/* smb_vwv4 has size  */
 	ino->u.smbfs_i.access = WVAL(server->packet, smb_vwv6);
 	ino->u.smbfs_i.access &= 3;
 
+	/* N.B. Suppose the open failed?? */
 	ino->u.smbfs_i.open = server->generation;
 
-	pr_debug("smb_proc_open: entry->access = %d\n", ino->u.smbfs_i.access);
-out:
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_proc_open: error=%d, access=%d\n", error, ino->u.smbfs_i.access);
+#endif
 	return error;
 }
 
 int
-smb_open(struct dentry *dir, int wish)
+smb_open(struct dentry *dentry, int wish)
 {
-	struct inode *i = dir->d_inode;
+	struct inode *i = dentry->d_inode;
 	int result;
 
-	result = -EIO;
+	result = -ENOENT;
 	if (!i)
 	{
 		printk("smb_open: no inode for dentry %s/%s\n",
-			dir->d_parent->d_name.name, dir->d_name.name);
+			dentry->d_parent->d_name.name, dentry->d_name.name);
 		goto out;
 	}
 
 	/*
-	 * If the inode is already open, we don't need to lock the server.
+	 * Note: If the caller holds an active dentry and the file is
+	 * currently open, we can be sure that the file isn't about
+	 * to be closed. (See smb_close_dentry() below.)
 	 */
 	if (!smb_is_open(i))
 	{
 		struct smb_sb_info *server = SMB_SERVER(i);
 		smb_lock_server(server);
-		result = smb_proc_open(server, dir);
+		result = 0;
+		if (!smb_is_open(i))
+			result = smb_proc_open(server, dentry, wish);
 		smb_unlock_server(server);
 		if (result)
 		{
-			printk("smb_open: %s/%s open failed, result=%d\n",
-				dir->d_parent->d_name.name, dir->d_name.name,
-				result);
+#ifdef SMBFS_PARANOIA
+printk("smb_open: %s/%s open failed, result=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, result);
+#endif
 			goto out;
 		}
+		/*
+		 * A successful open means the path is still valid ...
+		 */
+		smb_renew_times(dentry);
 	}
 
 	result = -EACCES;
@@ -679,15 +811,35 @@
 
 /* We're called with the server locked */
 
-static int smb_proc_close(struct smb_sb_info *server,
-			  __u16 fileid, __u32 mtime)
+static int 
+smb_proc_close(struct smb_sb_info *server, __u16 fileid, __u32 mtime)
 {
 	smb_setup_header(server, SMBclose, 3, 0);
 	WSET(server->packet, smb_vwv0, fileid);
-	DSET(server->packet, smb_vwv1, mtime);
+	DSET(server->packet, smb_vwv1, utc2local(mtime));
 	return smb_request_ok(server, SMBclose, 0, 0);
 }
 
+/*
+ * Called with the server locked
+ */
+static int 
+smb_proc_close_inode(struct smb_sb_info *server, struct inode * ino)
+{
+	int result = 0;
+	if (smb_is_open(ino))
+	{
+		/*
+		 * We clear the open flag in advance, in case another
+ 		 * process observes the value while we block below.
+		 */
+		ino->u.smbfs_i.open = 0;
+		result = smb_proc_close(server, ino->u.smbfs_i.fileid,
+						ino->i_mtime);
+	}
+	return result;
+}
+
 int
 smb_close(struct inode *ino)
 {
@@ -697,39 +849,66 @@
 	{
 		struct smb_sb_info *server = SMB_SERVER(ino);
 		smb_lock_server(server);
-		result = smb_proc_close(server, ino->u.smbfs_i.fileid, 
-						ino->i_mtime);
+		result = smb_proc_close_inode(server, ino);
 		smb_unlock_server(server);
-		ino->u.smbfs_i.open = 0;
 	}
 	return result;
 }
 
+/*
+ * This routine is called from dput() when d_count is going to 0.
+ * We use this to close the file so that cached dentries don't
+ * keep too many files open.
+ *
+ * There are some tricky race conditions here: the dentry may go
+ * back into use while we're closing the file, and we don't want
+ * the new user to be confused as to the open status.
+ */
+void
+smb_close_dentry(struct dentry * dentry)
+{
+	struct inode *ino = dentry->d_inode;
+
+	if (ino)
+	{
+		if (smb_is_open(ino))
+		{
+			struct smb_sb_info *server = SMB_SERVER(ino);
+			smb_lock_server(server);
+			/*
+			 * Check whether the dentry is back in use.
+			 */
+			if (dentry->d_count <= 1)
+			{
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_close_dentry: closing %s/%s, count=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
+#endif
+				smb_proc_close_inode(server, ino);
+			}
+			smb_unlock_server(server);
+		}
+	}
+	/* Consider dropping negative dentries? */
+#if 0
+	else
+		d_drop(dentry);
+#endif
+}
+
 /* In smb_proc_read and smb_proc_write we do not retry, because the
    file-id would not be valid after a reconnection. */
 
-/* smb_proc_read: fs indicates if it should be copied with
-   copy_to_user. */
-
 int
-smb_proc_read(struct inode *ino, off_t offset, long count, char *data)
+smb_proc_read(struct inode *ino, off_t offset, int count, char *data)
 {
 	struct smb_sb_info *server = SMB_SERVER(ino);
 	__u16 returned_count, data_len;
 	char *buf;
 	int result;
-	struct dentry * dentry;
-
-	if (!ino || !(dentry = ino->u.smbfs_i.dentry))
-	{
-		printk("smb_proc_read: no inode!\n");
-		return -EIO;
-	}
 
 	smb_lock_server(server);
 	smb_setup_header(server, SMBread, 5, 0);
-
-	/* Achtung! Do not refer to the cached packet after the request! */
 	buf = server->packet;
 	WSET(buf, smb_vwv0, ino->u.smbfs_i.fileid);
 	WSET(buf, smb_vwv1, count);
@@ -739,10 +918,6 @@
 	result = smb_request_ok(server, SMBread, 5, -1);
 	if (result < 0)
 		goto out;
-#if 0
-printk("smb_proc_read: file %s/%s, result=%d, packet=%p\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, result, server->packet);
-#endif
 	returned_count = WVAL(server->packet, smb_vwv0);
 
 	buf = SMB_BUF(server->packet);
@@ -758,6 +933,11 @@
 	result = data_len;
 
 out:
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_proc_read: file %s/%s, count=%d, result=%d\n",
+((struct dentry *) ino->u.smbfs_i.dentry)->d_parent->d_name.name, 
+((struct dentry *) ino->u.smbfs_i.dentry)->d_name.name, count, result);
+#endif
 	smb_unlock_server(server);
 	return result;
 }
@@ -766,10 +946,17 @@
 smb_proc_write(struct inode *ino, off_t offset, int count, const char *data)
 {
 	struct smb_sb_info *server = SMB_SERVER(ino);
-	int res = 0;
+	int result;
 	__u8 *p;
 
 	smb_lock_server(server);
+#if SMBFS_DEBUG_VERBOSE
+{struct dentry * dentry = ino->u.smbfs_i.dentry;
+printk("smb_proc_write: file %s/%s, count=%d@%ld, packet_size=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, 
+count, offset, server->packet_size);
+}
+#endif
 	p = smb_setup_header(server, SMBwrite, 5, count + 3);
 	WSET(server->packet, smb_vwv0, ino->u.smbfs_i.fileid);
 	WSET(server->packet, smb_vwv1, count);
@@ -780,12 +967,12 @@
 	WSET(p, 0, count);
 	memcpy(p+2, data, count);
 
-	if ((res = smb_request_ok(server, SMBwrite, 1, 0)) >= 0)
-		res = WVAL(server->packet, smb_vwv0);
+	if ((result = smb_request_ok(server, SMBwrite, 1, 0)) >= 0)
+		result = WVAL(server->packet, smb_vwv0);
 
 	smb_unlock_server(server);
 
-	return res;
+	return result;
 }
 
 int
@@ -1003,50 +1190,46 @@
 	smb_init_dirent(server, fattr);
 	fattr->attr = aDIR;
 	fattr->f_ino = 1;
+	fattr->f_mtime = CURRENT_TIME;
 	smb_finish_dirent(server, fattr);
 }
 
 
 static __u8 *
-smb_decode_dirent(struct smb_sb_info *server, __u8 *p,
-		  struct smb_dirent *entry)
+smb_decode_dirent(struct smb_sb_info *server, __u8 *p, struct dirent *entry)
 {
-	smb_init_dirent(server, &(entry->attr));
+	int len;
 
 	p += SMB_STATUS_SIZE;	/* reserved (search_status) */
-	entry->attr.attr = *p;
-	entry->attr.f_mtime = entry->attr.f_atime = entry->attr.f_ctime =
-	    date_dos2unix(WVAL(p, 1), WVAL(p, 3));
-	entry->attr.f_size = DVAL(p, 5);
-	entry->len = strlen(p + 9);
-	if (entry->len > 12)
-	{
-		entry->len = 12;
-	}
-	memcpy(entry->name, p + 9, entry->len);
-	entry->name[entry->len] = '\0';
-	while (entry->len > 2)
-	{
-		/* Pathworks fills names with spaces */
-		entry->len -= 1;
-		if (entry->name[entry->len] == ' ')
-		{
-			entry->name[entry->len] = '\0';
-		}
+	len = strlen(p + 9);
+	if (len > 12)
+	{
+		len = 12;
 	}
+	memcpy(entry->d_name, p + 9, len);
+#ifdef SMBFS_TRIM_BLANKS
+	/*
+	 * Trim trailing blanks for Pathworks servers
+	 */
+	while (len > 2 && entry->d_name[len-1] == ' ')
+		len--;
+#endif
+	entry->d_name[len] = '\0';
+	entry->d_reclen = len;
+	entry->d_ino = 0; /* no inode number available */
+
 	switch (server->opt.case_handling)
 	{
 	case SMB_CASE_UPPER:
-		str_upper(entry->name);
+		str_upper(entry->d_name);
 		break;
 	case SMB_CASE_LOWER:
-		str_lower(entry->name);
+		str_lower(entry->d_name);
 		break;
 	default:
 		break;
 	}
 	pr_debug("smb_decode_dirent: name = %s\n", entry->name);
-	smb_finish_dirent(server, &(entry->attr));
 	return p + 22;
 }
 
@@ -1056,50 +1239,43 @@
 
 static int
 smb_proc_readdir_short(struct smb_sb_info *server, struct dentry *dir, int fpos,
-		       int cache_size, struct smb_dirent *entry)
+		       void *cachep)
 {
 	char *p;
-	char *buf;
-	int error;
 	int result;
-	int i;
-	int first, total_count;
-	struct smb_dirent *current_entry;
+	int i, first, entries_seen, entries;
+	int entries_asked = (server->opt.max_xmit - 100) / SMB_DIRINFO_SIZE;
 	__u16 bcc;
 	__u16 count;
 	char status[SMB_STATUS_SIZE];
-	int entries_asked = (server->opt.max_xmit - 100) / SMB_DIRINFO_SIZE;
-
 	static struct qstr mask = { "*.*", 3, 0 };
 
-	pr_debug("SMB call  readdir %d @ %d\n", cache_size, fpos);
+	pr_debug("smb_proc_readdir_short: %d @ %d\n", cache_size, fpos);
 
 	smb_lock_server(server);
 
+	/* N.B. We need to reinitialize the cache to restart */
       retry:
+	smb_init_dircache(cachep);
 	first = 1;
-	total_count = 0;
-	current_entry = entry;
+	entries = 0;
+	entries_seen = 2; /* implicit . and .. */
 
 	while (1)
 	{
-		buf = server->packet;
+		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)
 		{
-			p = smb_setup_header(server, SMBsearch, 2, 0);
-			WSET(buf, smb_vwv0, entries_asked);
-			WSET(buf, smb_vwv1, aDIR);
-			*p++ = 4;
 			p = smb_encode_path(server, p, dir, &mask);
 			*p++ = 5;
 			WSET(p, 0, 0);
 			p += 2;
+			first = 0;
 		} else
 		{
-			p = smb_setup_header(server, SMBsearch, 2, 0);
-			WSET(buf, smb_vwv0, entries_asked);
-			WSET(buf, smb_vwv1, aDIR);
-			*p++ = 4;
 			*p++ = 0;
 			*p++ = 5;
 			WSET(p, 0, SMB_STATUS_SIZE);
@@ -1110,39 +1286,25 @@
 
 		smb_setup_bcc(server, p);
 
-		if ((error = smb_request_ok(server, SMBsearch, 1, -1)) < 0)
+		result = smb_request_ok(server, SMBsearch, 1, -1);
+		if (result < 0)
 		{
-			if ((server->rcls == ERRDOS)
-			    && (server->err == ERRnofiles))
-			{
-				result = total_count - fpos;
-				goto unlock_return;
-			} else
-			{
-				if (smb_retry(server))
-				{
-					goto retry;
-				}
-				result = error;
-				goto unlock_return;
-			}
+			if ((server->rcls == ERRDOS) && 
+			    (server->err  == ERRnofiles))
+				break;
+			if (smb_retry(server))
+				goto retry;
+			goto unlock_return;
 		}
 		p = SMB_VWV(server->packet);
 		count = WVAL(p, 0);
-		bcc = WVAL(p, 2);
-
-		first = 0;
-
 		if (count <= 0)
-		{
-			result = total_count - fpos;
-			goto unlock_return;
-		}
+			break;
+
+		result = -EIO;
+		bcc = WVAL(p, 2);
 		if (bcc != count * SMB_DIRINFO_SIZE + 3)
-		{
-			result = -EIO;
 			goto unlock_return;
-		}
 		p += 7;
 
 		/* Read the last entry into the status field. */
@@ -1155,102 +1317,125 @@
 
 		for (i = 0; i < count; i++)
 		{
-			if (total_count < fpos)
-			{
-				p += SMB_DIRINFO_SIZE;
-				pr_debug("smb_proc_readdir: skipped entry.\n");
-				pr_debug("                  total_count = %d\n"
-					 "                i = %d, fpos = %d\n",
-					 total_count, i, fpos);
-			} else if (total_count >= fpos + cache_size)
+			struct dirent this_ent, *entry = &this_ent;
+
+			p = smb_decode_dirent(server, p, entry);
+			if (entries_seen == 2 && entry->d_name[0] == '.')
 			{
-				result = total_count - fpos;
-				goto unlock_return;
-			} else
+				if (entry->d_reclen == 1)
+					continue;
+				if (entry->d_name[1] == '.' &&
+				    entry->d_reclen == 2)
+					continue;
+			}
+			if (entries_seen >= fpos)
 			{
-				p = smb_decode_dirent(server, p,
-						      current_entry);
-				current_entry->f_pos = total_count;
-				pr_debug("smb_proc_readdir: entry->f_pos = "
-					 "%u\n", entry->f_pos);
-				current_entry += 1;
+				pr_debug("smb_proc_readdir: fpos=%u\n", 
+					entries_seen);
+				smb_add_to_cache(cachep, entry, entries_seen);
+				entries++;
 			}
-			total_count += 1;
+#ifdef SMBFS_DEBUG_VERBOSE
+else
+printk("smb_proc_readdir: skipped, seen=%d, i=%d, fpos=%d\n",
+entries_seen, i, fpos);
+#endif
+			entries_seen++;
 		}
 	}
-      unlock_return:
+	result = entries;
+
+    unlock_return:
 	smb_unlock_server(server);
 	return result;
 }
 
-/* interpret a long filename structure - this is mostly guesses at the
-   moment.  The length of the structure is returned.  The structure of
-   a long filename depends on the info level. 260 is used by NT and 2
-   is used by OS/2. */
-
+/*
+ * Interpret a long filename structure using the specified info level:
+ *   level 1 -- Win NT, Win 95, OS/2
+ *   level 2 -- OS/2
+ *   level 259 -- File name and length only, Win NT, Win 95
+ *   level 260 -- Win NT, Win 95
+ * There seem to be numerous inconsistencies and bugs in implementation.
+ */
 static char *
 smb_decode_long_dirent(struct smb_sb_info *server, char *p,
-		       struct smb_dirent *entry, int level)
+			struct dirent *entry, int level)
 {
 	char *result;
 	unsigned int len;
 
-	smb_init_dirent(server, &(entry->attr));
+	/*
+	 * SMB doesn't have a concept of inode numbers ...
+	 */
+	entry->d_ino = 0;
 
 	switch (level)
 	{
-		/* We might add more levels later... */
 	case 1:
-		entry->attr.f_ctime = date_dos2unix(WVAL(p, 6), WVAL(p, 4));
-		entry->attr.f_atime = date_dos2unix(WVAL(p, 10), WVAL(p, 8));
-		entry->attr.f_mtime = date_dos2unix(WVAL(p, 14), WVAL(p, 12));
-		entry->attr.f_size = DVAL(p, 16);
-		entry->attr.attr = *(p+24);
-		/*
-		 * Achtung, lengths can go up to 255
-		 */
 		len = *((unsigned char *) p + 26);
-		entry->len = len;
-		strncpy(entry->name, p + 27, len);
-		entry->name[len] = '\0';
+		entry->d_reclen = len;
+		strncpy(entry->d_name, p + 27, len);
+		entry->d_name[len] = '\0';
 
 		result = p + 28 + len;
 		break;
 
+	case 259: /* SMB_FIND_FILE_NAMES_INFO = 0x103 */
+		/*
+		 * This info level returns just the file name and length,
+		 * which is all we need right now.
+		 */
+		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;
+		strncpy(entry->d_name, p + 12, len);
+		/*
+		 * 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 ...
+		 */
+		if (len && entry->d_name[len-1] == '\0')
+			len--;
+		entry->d_name[len] = '\0';
+		entry->d_reclen = len;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_decode_long_dirent: info 259, len=%d, name=%s\n",
+len, entry->d_name);
+#endif
+		break;
+
 	default:
-		printk("smb_decode: Unknown long filename format %d\n", level);
+		printk("smb_decode_long_dirent: Unknown level %d\n", level);
 		result = p + WVAL(p, 0);
 	}
 
 	switch (server->opt.case_handling)
 	{
 	case SMB_CASE_UPPER:
-		str_upper(entry->name);
+		str_upper(entry->d_name);
 		break;
 	case SMB_CASE_LOWER:
-		str_lower(entry->name);
+		str_lower(entry->d_name);
 		break;
 	default:
 		break;
 	}
 
-	smb_finish_dirent(server, &(entry->attr));
-
 	return result;
 }
 
 static int
 smb_proc_readdir_long(struct smb_sb_info *server, struct dentry *dir, int fpos,
-		      int cache_size, struct smb_dirent *cache)
+		      void *cachep)
 {
-	/* NT uses 260, OS/2 uses 2. Both accept 1. */
-	const int info_level = 1;
+	/* Both NT and OS/2 accept info level 1 (but see note below). */
+	int info_level = 1;
 	const int max_matches = 512;
 
-	char *p;
-	char *lastname;
-	unsigned lastname_len;
-	int i;
+	char *p, *mask, *lastname;
 	int first, entries, entries_seen;
 
 	unsigned char *resp_data = NULL;
@@ -1260,36 +1445,45 @@
 
 	__u16 command;
 
-	int result;
-
-	int ff_resume_key = 0;
+	int ff_resume_key = 0; /* this isn't being used */
 	int ff_searchcount = 0;
 	int ff_eos = 0;
 	int ff_lastname = 0;
 	int ff_dir_handle = 0;
 	int loop_count = 0;
+	int mask_len, i, result;
 
-	char param[SMB_MAXPATHLEN + 2 + 12]; /* too long for the stack! */
-	int mask_len;
-	char *mask = &(param[12]);
-
+	char param[12 + SMB_MAXPATHLEN + 2]; /* too long for the stack! */
 	static struct qstr star = { "*", 1, 0 };
 
-	mask_len = smb_encode_path(server, mask, dir, &star) - mask;
-
-	mask[mask_len] = 0;
-	mask[mask_len + 1] = 0;
-
-	pr_debug("smb_readdir_long cache=%d, fpos=%d, mask=%s\n",
-		 cache_size, fpos, mask);
+	/*
+	 * 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. (Win 95 uses this too?)
+	 */
+	if (server->opt.protocol >= SMB_PROTOCOL_NT1)
+		info_level = 259;
 
 	smb_lock_server(server);
 
       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
+	/*
+	 * We must reinitialize the dircache when retrying.
+	 */
+	smb_init_dircache(cachep);
 	entries = 0;
 	entries_seen = 2;
+	ff_eos = 0;
 
 	while (ff_eos == 0)
 	{
@@ -1301,6 +1495,7 @@
 			entries = -EIO;
 			break;
 		}
+
 		if (first != 0)
 		{
 			command = TRANSACT2_FINDFIRST;
@@ -1314,10 +1509,11 @@
 		} else
 		{
 			command = TRANSACT2_FINDNEXT;
-			pr_debug("hand=0x%X resume=%d ff_lastnm=%d mask=%s\n",
-				 ff_dir_handle, ff_resume_key, ff_lastname,
-				 mask);
-			WSET(param, 0, ff_dir_handle);
+#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
+			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 */
@@ -1335,7 +1531,7 @@
 		}
 
 		result = smb_trans2_request(server, command,
-					    0, NULL, 12 + mask_len + 2, param,
+					    0, NULL, 12 + mask_len + 1, param,
 					    &resp_data_len, &resp_data,
 					    &resp_param_len, &resp_param);
 
@@ -1343,10 +1539,13 @@
 		{
 			if (smb_retry(server))
 			{
+#ifdef SMBFS_PARANOIA
+printk("smb_proc_readdir_long: error=%d, retrying\n", result);
+#endif
 				goto retry;
 			}
 #ifdef SMBFS_PARANOIA
-printk("smb_proc_readdir_long: trans2_request error=%d\n", result);
+printk("smb_proc_readdir_long: error=%d, breaking\n", result);
 #endif
 			entries = result;
 			break;
@@ -1354,13 +1553,17 @@
 		if (server->rcls != 0)
 		{ 
 #ifdef SMBFS_PARANOIA
-printk("smb_proc_readdir_long: error, rcls=%d, err=%d\n",
+printk("smb_proc_readdir_long: rcls=%d, err=%d, breaking\n",
 server->rcls, server->err);
 #endif
-			/* Why isn't this considered an error? */
-			/* entries = -EIO; */
+			entries = -smb_errno(server->rcls, server->err);
 			break;
 		}
+#ifdef SMBFS_PARANOIA
+if (resp_data + resp_data_len > server->packet + server->packet_size)
+printk("s_p_r_l: data past packet end! data=%p, len=%d, packet=%p\n",
+resp_data + resp_data_len, resp_data_len, server->packet + server->packet_size);
+#endif
 
 		/* parse out some important return info */
 		if (first != 0)
@@ -1380,86 +1583,90 @@
 		{
 			break;
 		}
-		/* point to the data bytes */
-		p = resp_data;
 
 		/* we might need the lastname for continuations */
-		lastname = "";
-		lastname_len = 0;
+		mask_len = 0;
 		if (ff_lastname > 0)
 		{
-			ff_resume_key = 0;
-			lastname = p + ff_lastname;
+			lastname = resp_data + ff_lastname;
 			switch (info_level)
 			{
 			case 260:
-				lastname_len = resp_data_len - ff_lastname;
+ 				if (ff_lastname < resp_data_len)
+					mask_len = resp_data_len - ff_lastname;
 				break;
 			case 1:
-				lastname_len = *((unsigned char *) lastname++);
+				/* Win NT 4.0 doesn't set the length byte */
+				lastname++;
+ 				if (ff_lastname + 2 < resp_data_len)
+					mask_len = strlen(lastname);
 				break;
 			}
+			/*
+			 * Update the mask string for the next message.
+			 */
+			if (mask_len > 255)
+				mask_len = 255;
+			if (mask_len)
+				strncpy(mask, lastname, mask_len);
+			ff_resume_key = 0;
 		}
-		lastname_len = min(lastname_len, 256);
-		strncpy(mask, lastname, lastname_len);
-		mask[lastname_len] = '\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
 		/* Now we are ready to parse smb directory entries. */
 
+		/* point to the data bytes */
+		p = resp_data;
 		for (i = 0; i < ff_searchcount; i++)
 		{
-			struct smb_dirent *entry = &(cache[entries]);
+			struct dirent this_ent, *entry = &this_ent;
 
-			p = smb_decode_long_dirent(server, p,
-						   entry, info_level);
+			p = smb_decode_long_dirent(server, p, entry,
+							info_level);
 
 			pr_debug("smb_readdir_long: got %s\n", entry->name);
 
-			if ((entry->name[0] == '.')
-			    && ((entry->name[1] == '\0')
-				|| ((entry->name[1] == '.')
-				    && (entry->name[2] == '\0'))))
+			/* ignore . and .. from the server */
+			if (entries_seen == 2 && entry->d_name[0] == '.')
 			{
-				/* ignore . and .. from the server */
-				continue;
+				if (entry->d_reclen == 1)
+					continue;
+				if (entry->d_name[1] == '.' && 
+				    entry->d_reclen == 2)
+					continue;
 			}
 			if (entries_seen >= fpos)
 			{
-				entry->f_pos = entries_seen;
+				smb_add_to_cache(cachep, entry, entries_seen);
 				entries += 1;
 			}
-			entries_seen += 1;
-			if (entries < cache_size)
-				continue;
-
-			/* cache is full */
-			goto finished;
+ 			entries_seen++;
 		}
 
-		pr_debug("received %d entries (eos=%d resume=%d)\n",
-			 ff_searchcount, ff_eos, ff_resume_key);
-
+#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
 		first = 0;
 	}
 
-      finished:
 	smb_unlock_server(server);
 	return entries;
 }
 
 int
-smb_proc_readdir(struct dentry *dir, int fpos,
-		 int cache_size, struct smb_dirent *entry)
+smb_proc_readdir(struct dentry *dir, int fpos, void *cachep)
 {
 	struct smb_sb_info *server;
 
 	server = server_from_dentry(dir);
 	if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2)
-		return smb_proc_readdir_long(server, dir, fpos, cache_size,
-					     entry);
+		return smb_proc_readdir_long(server, dir, fpos, cachep);
 	else
-		return smb_proc_readdir_short(server, dir, fpos, cache_size,
-					      entry);
+		return smb_proc_readdir_short(server, dir, fpos, cachep);
 }
 
 static int
@@ -1498,7 +1705,6 @@
 smb_proc_getattr_trans2(struct smb_sb_info *server, struct dentry *dir,
 			struct qstr *name, struct smb_fattr *attr)
 {
-	char param[SMB_MAXPATHLEN + 20];
 	char *p;
 	int result;
 
@@ -1506,29 +1712,34 @@
 	unsigned char *resp_param = NULL;
 	int resp_data_len = 0;
 	int resp_param_len = 0;
+	char param[SMB_MAXPATHLEN + 20]; /* too big for the stack! */
 
+	smb_lock_server(server);
+
+      retry:
 	WSET(param, 0, 1);	/* Info level SMB_INFO_STANDARD */
 	DSET(param, 2, 0);
 	p = smb_encode_path(server, param + 6, dir, name);
 
-	smb_lock_server(server);
-      retry:
 	result = smb_trans2_request(server, TRANSACT2_QPATHINFO,
 				    0, NULL, p - param, param,
 				    &resp_data_len, &resp_data,
 				    &resp_param_len, &resp_param);
- 
-	if (server->rcls != 0)
-	{
-		result = -smb_errno(server->rcls, server->err);
-		goto out;
-	}
 	if (result < 0)
 	{
 		if (smb_retry(server))
 			goto retry;
 		goto out;
 	}
+	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
+		result = -smb_errno(server->rcls, server->err);
+		goto out;
+	}
 	result = -ENOENT;
 	if (resp_data_len < 22)
 		goto out;
@@ -1553,15 +1764,19 @@
 		     struct smb_fattr *fattr)
 {
 	struct smb_sb_info *server;
-	int result = -1;
+	int result;
 
 	server = server_from_dentry(dir);
 	smb_init_dirent(server, fattr);
 
+	/*
+	 * N.B. Why would we want to fall back to xxx_core on error?
+	 * If the file doesn't exist (a very common case), the core
+	 * protocol won't find it either.
+	 */
 	if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2)
 		result = smb_proc_getattr_trans2(server, dir, name, fattr);
-
-	if (result < 0)
+	else
 		result = smb_proc_getattr_core(server, dir, name, fattr);
 
 	smb_finish_dirent(server, fattr);
@@ -1605,8 +1820,6 @@
 smb_proc_setattr_trans2(struct smb_sb_info *server,
 			struct dentry *dir, struct smb_fattr *fattr)
 {
-	char param[SMB_MAXPATHLEN + 20];
-	char data[26];
 	char *p;
 	int result;
 
@@ -1614,7 +1827,12 @@
 	unsigned char *resp_param = NULL;
 	int resp_data_len = 0;
 	int resp_param_len = 0;
+	char param[SMB_MAXPATHLEN + 20]; /* too long for the stack! */
+	char data[26];
 
+	smb_lock_server(server);
+
+      retry:
 	WSET(param, 0, 1);	/* Info level SMB_INFO_STANDARD */
 	DSET(param, 2, 0);
 	p = smb_encode_path(server, param + 6, dir, NULL);
@@ -1627,22 +1845,20 @@
 	WSET(data, 20, fattr->attr);
 	WSET(data, 22, 0);
 
-	smb_lock_server(server);
-      retry:
 	result = smb_trans2_request(server, TRANSACT2_SETPATHINFO,
 				    26, data, p - param, param,
 				    &resp_data_len, &resp_data,
 				    &resp_param_len, &resp_param);
-
-	if (server->rcls != 0)
-	{
-		smb_unlock_server(server);
-		return -smb_errno(server->rcls, server->err);
-	}
 	if (result < 0)
+	{
 		if (smb_retry(server))
 			goto retry;
+		goto out;
+	}
+	if (server->rcls != 0)
+		result = -smb_errno(server->rcls, server->err);
 
+out:
 	smb_unlock_server(server);
 	return 0;
 }
@@ -1651,12 +1867,14 @@
 smb_proc_setattr(struct smb_sb_info *server, struct dentry *dir,
 		 struct smb_fattr *fattr)
 {
-	int result = -1;
+	int result;
 
+	/*
+	 * N.B. Why would we want to fall back to xxx_core on error?
+	 */
 	if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2)
 		result = smb_proc_setattr_trans2(server, dir, fattr);
-
-	if (result < 0)
+	else
 		result = smb_proc_setattr_core(server, dir, fattr);
 
 	return result;
diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/sock.c linux/fs/smbfs/sock.c
--- v2.1.57/linux/fs/smbfs/sock.c	Tue Sep 23 16:48:49 1997
+++ linux/fs/smbfs/sock.c	Sun Oct 12 10:17:05 1997
@@ -126,18 +126,26 @@
 	}
 }
 
+int
+smb_valid_socket(struct inode * inode)
+{
+	return (inode && S_ISSOCK(inode->i_mode) && 
+		inode->u.socket_i.type == SOCK_STREAM);
+}
+
 static struct socket *
 server_sock(struct smb_sb_info *server)
 {
 	struct file *file;
-	struct inode *inode;
 
-	if (server				&& 
-	    (file = server->sock_file)		&&
-	    (inode = file->f_dentry->d_inode)	&& 
-	    S_ISSOCK(inode->i_mode)		&& 
-	    inode->u.socket_i.type == SOCK_STREAM)
-		return &(inode->u.socket_i);
+	if (server && (file = server->sock_file))
+	{
+#ifdef SMBFS_PARANOIA
+		if (!smb_valid_socket(file->f_dentry->d_inode))
+			printk("smb_server_sock: bad socket!\n");
+#endif
+		return &file->f_dentry->d_inode->u.socket_i;
+	}
 	return NULL;
 }
 
@@ -242,15 +250,13 @@
 
 	if (file)
 	{
-		struct socket * socket = server_sock(server);
-
-		printk("smb_close_socket: closing socket %p\n", socket);
-		/*
-		 * We need a way to check for tasks running the callback!
-		 */
-		if (socket->sk->data_ready == smb_data_callback)
-			printk("smb_close_socket: still catching keepalives!\n");
-
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_close_socket: closing socket %p\n", server_sock(server));
+#endif
+#ifdef SMBFS_PARANOIA
+if (server_sock(server)->sk->data_ready == smb_data_callback)
+printk("smb_close_socket: still catching keepalives!\n");
+#endif
 		server->sock_file = NULL;
 		close_fp(file);
 	}
@@ -325,7 +331,9 @@
 
 	if (result < 0)
 	{
-		pr_debug("smb_get_length: recv error = %d\n", -result);
+#ifdef SMBFS_PARANOIA
+printk("smb_get_length: recv error = %d\n", -result);
+#endif
 		return result;
 	}
 	switch (peek_buf[0])
@@ -339,7 +347,9 @@
 		goto re_recv;
 
 	default:
-		pr_debug("smb_get_length: Invalid NBT packet\n");
+#ifdef SMBFS_PARANOIA
+printk("smb_get_length: Invalid NBT packet, code=%x\n", peek_buf[0]);
+#endif
 		return -EIO;
 	}
 
@@ -359,39 +369,39 @@
 smb_receive(struct smb_sb_info *server)
 {
 	struct socket *socket = server_sock(server);
-	int len;
-	int result;
+	int len, result;
 	unsigned char peek_buf[4];
 
-	len = smb_get_length(socket, peek_buf);
-
-	if (len < 0)
-	{
-		return len;
-	}
+	result = smb_get_length(socket, peek_buf);
+	if (result < 0)
+		goto out;
+	len = result;
+	/*
+	 * Some servers do not respect our max_xmit and send
+	 * larger packets.  Try to allocate a new packet,
+	 * but don't free the old one unless we succeed.
+	 */
 	if (len + 4 > server->packet_size)
 	{
-		/* Some servers do not care about our max_xmit. They
-		   send larger packets */
+		char * packet;
 		pr_debug("smb_receive: Increase packet size from %d to %d\n",
 			server->packet_size, len + 4);
+		result = -ENOMEM;
+		packet = smb_vmalloc(len + 4);
+		if (packet == NULL)
+			goto out;
 		smb_vfree(server->packet);
-		server->packet = 0;
-		server->packet_size = 0;
-		server->packet = smb_vmalloc(len + 4);
-		if (server->packet == NULL)
-		{
-			return -ENOMEM;
-		}
+		server->packet = packet;
 		server->packet_size = len + 4;
 	}
 	memcpy(server->packet, peek_buf, 4);
 	result = smb_receive_raw(socket, server->packet + 4, len);
-
 	if (result < 0)
 	{
-		pr_debug("smb_receive: receive error: %d\n", result);
-		return result;
+#ifdef SMBFS_DEBUG_VERBOSE
+printk("smb_receive: receive error: %d\n", result);
+#endif
+		goto out;
 	}
 	server->rcls = *(server->packet+9);
 	server->err = WVAL(server->packet, 11);
@@ -400,9 +410,16 @@
 if (server->rcls != 0)
 printk("smb_receive: rcls=%d, err=%d\n", server->rcls, server->err);
 #endif
+out:
 	return result;
 }
 
+/*
+ * This routine needs a lot of work.  We should check whether the packet
+ * is all one part before allocating a new one, and should try first to
+ * copy to a temp buffer before allocating.
+ * The final server->packet should be the larger of the two.
+ */
 static int
 smb_receive_trans2(struct smb_sb_info *server,
 		   int *ldata, unsigned char **data,
@@ -515,6 +532,11 @@
 	*ldata = data_len;
 	*lparam = param_len;
 
+#ifdef SMBFS_PARANOIA
+if (buf_len < server->packet_size)
+printk("smb_receive_trans2: changing packet, old size=%d, new size=%d\n",
+server->packet_size, buf_len);
+#endif
 	smb_vfree(server->packet);
 	server->packet = rcv_buf;
 	server->packet_size = buf_len;
@@ -537,9 +559,6 @@
 	unsigned char *buffer;
 
 	result = -EBADF;
-	if (!server) /* this can't happen */
-		goto bad_no_server;
-		
 	buffer = server->packet;
 	if (!buffer)
 		goto bad_no_packet;
@@ -586,13 +605,12 @@
 	return result;
 	
 bad_conn:
-	printk("smb_request: result %d, setting invalid\n", result);
+#ifdef SMBFS_PARANOIA
+printk("smb_request: result %d, setting invalid\n", result);
+#endif
 	server->state = CONN_INVALID;
 	smb_invalidate_inodes(server);
 	goto out;		
-bad_no_server:
-	printk("smb_request: no server!\n");
-	goto out;
 bad_no_packet:
 	printk("smb_request: no packet!\n");
 	goto out;
@@ -631,6 +649,7 @@
 	struct iovec iov[4];
 	struct msghdr msg;
 
+	/* N.B. This test isn't valid! packet_size may be < max_xmit */
 	if ((bcc + oparam) > server->opt.max_xmit)
 	{
 		return -ENOMEM;
@@ -639,6 +658,7 @@
 
 	WSET(server->packet, smb_tpscnt, lparam);
 	WSET(server->packet, smb_tdscnt, ldata);
+	/* N.B. these values should reflect out current packet size */
 	WSET(server->packet, smb_mprcnt, TRANS2_MAX_TRANSFER);
 	WSET(server->packet, smb_mdrcnt, TRANS2_MAX_TRANSFER);
 	WSET(server->packet, smb_msrcnt, 0);
@@ -745,7 +765,9 @@
 	return result;
 
 bad_conn:
-	printk("smb_trans2_request: connection bad, setting invalid\n");
+#ifdef SMBFS_PARANOIA
+printk("smb_trans2_request: connection bad, setting invalid\n");
+#endif
 	server->state = CONN_INVALID;
 	smb_invalidate_inodes(server);
 	goto out;
diff -u --recursive --new-file v2.1.57/linux/fs/super.c linux/fs/super.c
--- v2.1.57/linux/fs/super.c	Wed Sep 24 20:05:48 1997
+++ linux/fs/super.c	Mon Oct 13 15:09:20 1997
@@ -17,8 +17,6 @@
  *  Added change_root: Werner Almesberger & Hans Lermen, Feb '96
  */
 
-#include <stdarg.h>
-
 #include <linux/config.h>
 #include <linux/sched.h>
 #include <linux/kernel.h>
@@ -252,29 +250,24 @@
 /*
  * Whee.. Weird sysv syscall. 
  */
-asmlinkage int sys_sysfs(int option, ...)
+asmlinkage int sys_sysfs(int option, unsigned long arg1, unsigned long arg2)
 {
-	va_list args;
 	int retval = -EINVAL;
-	unsigned int index;
 
 	lock_kernel();
-	va_start(args, option);
 	switch (option) {
 		case 1:
-			retval = fs_index(va_arg(args, const char *));
+			retval = fs_index((const char *) arg1);
 			break;
 
 		case 2:
-			index = va_arg(args, unsigned int);
-			retval = fs_name(index, va_arg(args, char *));
+			retval = fs_name(arg1, (char *) arg2);
 			break;
 
 		case 3:
 			retval = fs_maxindex();
 			break;
 	}
-	va_end(args);
 	unlock_kernel();
 	return retval;
 }
@@ -933,12 +926,11 @@
 	struct file_system_type * fstype;
 	struct dentry * dentry = NULL;
 	struct inode * inode = NULL;
-	struct file_operations * fops;
 	kdev_t dev;
 	int retval = -EPERM;
-	const char * t;
 	unsigned long flags = 0;
 	unsigned long page = 0;
+	struct file dummy;	/* allows read-write or read-only flag */
 
 	lock_kernel();
 	if (!suser())
@@ -954,6 +946,7 @@
 		free_page(page);
 		goto out;
 	}
+
 	retval = copy_mount_options (type, &page);
 	if (retval < 0)
 		goto out;
@@ -962,8 +955,8 @@
 	retval = -ENODEV;
 	if (!fstype)		
 		goto out;
-	t = fstype->name;
-	fops = NULL;
+
+	memset(&dummy, 0, sizeof(dummy));
 	if (fstype->fs_flags & FS_REQUIRES_DEV) {
 		dentry = namei(dev_name);
 		retval = PTR_ERR(dentry);
@@ -984,17 +977,15 @@
 		if (MAJOR(dev) >= MAX_BLKDEV)
 			goto dput_and_out;
 
-		fops = get_blkfops(MAJOR(dev));
 		retval = -ENOTBLK;
-		if (!fops)
+		dummy.f_op = get_blkfops(MAJOR(dev));
+		if (!dummy.f_op)
 			goto dput_and_out;
 
-		if (fops->open) {
-			struct file dummy;	/* allows read-write or read-only flag */
-			memset(&dummy, 0, sizeof(dummy));
+		if (dummy.f_op->open) {
 			dummy.f_dentry = dentry;
 			dummy.f_mode = (new_flags & MS_RDONLY) ? 1 : 3;
-			retval = fops->open(inode, &dummy);
+			retval = dummy.f_op->open(inode, &dummy);
 			if (retval)
 				goto dput_and_out;
 		}
@@ -1009,22 +1000,28 @@
 	if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL) {
 		flags = new_flags & ~MS_MGC_MSK;
 		retval = copy_mount_options(data, &page);
-		if (retval < 0) {
-			put_unnamed_dev(dev);
-			goto dput_and_out;
-		}
+		if (retval < 0)
+			goto clean_up;
 	}
-	retval = do_mount(dev,dev_name,dir_name,t,flags,(void *) page);
+	retval = do_mount(dev, dev_name, dir_name, fstype->name, flags,
+				(void *) page);
 	free_page(page);
-	if (retval && fops && fops->release) {
-		fops->release(inode, NULL);
-		put_unnamed_dev(dev);
-	}
+	if (retval)
+		goto clean_up;
+
 dput_and_out:
 	dput(dentry);
 out:
 	unlock_kernel();
 	return retval;
+
+clean_up:
+	if (dummy.f_op) {
+		if (dummy.f_op->release)
+			dummy.f_op->release(inode, NULL);
+	} else
+		put_unnamed_dev(dev);
+	goto dput_and_out;
 }
 
 __initfunc(static void do_mount_root(void))
diff -u --recursive --new-file v2.1.57/linux/include/linux/dcache.h linux/include/linux/dcache.h
--- v2.1.57/linux/include/linux/dcache.h	Tue Sep 23 16:48:49 1997
+++ linux/include/linux/dcache.h	Sun Oct 12 09:51:42 1997
@@ -104,6 +104,7 @@
 /* allocate/de-allocate */
 extern struct dentry * d_alloc(struct dentry * parent, const struct qstr *name);
 extern void prune_dcache(int);
+extern void shrink_dcache_sb(struct super_block *);
 extern int d_invalidate(struct dentry *);
 
 #define shrink_dcache() prune_dcache(0)
diff -u --recursive --new-file v2.1.57/linux/include/linux/fs.h linux/include/linux/fs.h
--- v2.1.57/linux/include/linux/fs.h	Tue Sep 23 16:48:50 1997
+++ linux/include/linux/fs.h	Tue Oct 14 18:29:28 1997
@@ -42,9 +42,10 @@
 
 /* And dynamically-tunable limits and defaults: */
 extern int max_inodes;
-extern int max_files, nr_files;
+extern int max_files, nr_files, nr_free_files;
 #define NR_INODE 4096	/* this should be bigger than NR_FILE */
 #define NR_FILE 1024	/* this can well be larger on a larger system */
+#define NR_RESERVED_FILES 10 /* reserved for root */
 
 #define MAY_EXEC 1
 #define MAY_WRITE 2
@@ -627,6 +628,10 @@
 
 extern void init_fifo(struct inode * inode);
 extern struct inode_operations fifo_inode_operations;
+
+/* Invalid inode operations -- fs/bad_inode.c */
+extern void make_bad_inode(struct inode * inode);
+extern int is_bad_inode(struct inode * inode);
 
 extern struct file_operations connecting_fifo_fops;
 extern struct file_operations read_fifo_fops;
diff -u --recursive --new-file v2.1.57/linux/include/linux/list.h linux/include/linux/list.h
--- v2.1.57/linux/include/linux/list.h	Thu Jul 17 10:06:08 1997
+++ linux/include/linux/list.h	Sun Oct 12 10:46:27 1997
@@ -3,7 +3,14 @@
 
 /*
  * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
  */
+
 struct list_head {
 	struct list_head *next, *prev;
 };
@@ -15,22 +22,46 @@
 	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
 } while (0)
 
-static inline void list_add(struct list_head *new, struct list_head *head)
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head * new,
+	struct list_head * prev,
+	struct list_head * next)
 {
-	struct list_head *next = head->next;
 	next->prev = new;
 	new->next = next;
-	new->prev = head;
-	head->next = new;
+	new->prev = prev;
+	prev->next = new;
 }
 
-static inline void list_del(struct list_head *entry)
+/*
+ * Insert a new entry after the specified head..
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
 {
-	struct list_head *next, *prev;
-	next = entry->next;
-	prev = entry->prev;
 	next->prev = prev;
 	prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
 }
 
 static inline int list_empty(struct list_head *head)
diff -u --recursive --new-file v2.1.57/linux/include/linux/mm.h linux/include/linux/mm.h
--- v2.1.57/linux/include/linux/mm.h	Mon Aug  4 16:25:40 1997
+++ linux/include/linux/mm.h	Tue Oct 14 18:29:28 1997
@@ -296,6 +296,8 @@
 extern unsigned long page_unuse(unsigned long);
 extern int shrink_mmap(int, int);
 extern void truncate_inode_pages(struct inode *, unsigned long);
+extern unsigned long get_cached_page(struct inode *, unsigned long, int);
+extern void put_cached_page(unsigned long);
 
 #define GFP_BUFFER	0x00
 #define GFP_ATOMIC	0x01
diff -u --recursive --new-file v2.1.57/linux/include/linux/nfs.h linux/include/linux/nfs.h
--- v2.1.57/linux/include/linux/nfs.h	Mon Apr 14 16:28:26 1997
+++ linux/include/linux/nfs.h	Sun Oct 12 10:15:56 1997
@@ -129,14 +129,6 @@
 	struct nfs_time		mtime;
 };
 
-struct nfs_entry {
-	__u32			fileid;
-	char *			name;
-	unsigned int		length:31,
-				eof:1;
-	__u32			cookie;
-};
-
 struct nfs_fsinfo {
 	__u32			tsize;
 	__u32			bsize;
diff -u --recursive --new-file v2.1.57/linux/include/linux/nfs_fs.h linux/include/linux/nfs_fs.h
--- v2.1.57/linux/include/linux/nfs_fs.h	Tue Sep 23 16:48:50 1997
+++ linux/include/linux/nfs_fs.h	Tue Oct 14 18:29:28 1997
@@ -125,7 +125,7 @@
 extern int nfs_proc_rmdir(struct nfs_server *server, struct nfs_fh *dir,
 			const char *name);
 extern int nfs_proc_readdir(struct nfs_server *server, struct nfs_fh *fhandle,
-			u32 cookie, unsigned int size, struct nfs_entry *entry);
+			u32 cookie, unsigned int size, __u32 *entry);
 extern int nfs_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle,
 			struct nfs_fsinfo *res);
 
@@ -138,7 +138,7 @@
 extern int init_nfs_fs(void);
 extern struct inode *nfs_fhget(struct super_block *sb, struct nfs_fh *fhandle,
 			       struct nfs_fattr *fattr);
-extern void nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr);
+extern int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr);
 extern int nfs_revalidate(struct inode *);
 extern int _nfs_revalidate_inode(struct nfs_server *, struct inode *);
 
diff -u --recursive --new-file v2.1.57/linux/include/linux/proc_fs.h linux/include/linux/proc_fs.h
--- v2.1.57/linux/include/linux/proc_fs.h	Thu Sep 11 09:02:24 1997
+++ linux/include/linux/proc_fs.h	Tue Oct 14 18:29:36 1997
@@ -237,13 +237,15 @@
 	unsigned long size;
 	struct inode_operations * ops;
 	int (*get_info)(char *, char **, off_t, int, int);
-	void (*fill_inode)(struct inode *);
+	void (*fill_inode)(struct inode *, int);
 	struct proc_dir_entry *next, *parent, *subdir;
 	void *data;
 	int (*read_proc)(char *page, char **start, off_t off,
 			 int count, int *eof, void *data);
 	int (*write_proc)(struct file *file, const char *buffer,
 			  unsigned long count, void *data);
+	unsigned int count;	/* use count */
+	int deleted;		/* delete flag */
 };
 
 extern int (* dispatch_scsi_info_ptr) (int ino, char *buffer, char **start,
diff -u --recursive --new-file v2.1.57/linux/include/linux/smb_fs.h linux/include/linux/smb_fs.h
--- v2.1.57/linux/include/linux/smb_fs.h	Tue Sep 23 16:48:50 1997
+++ linux/include/linux/smb_fs.h	Tue Oct 14 18:31:26 1997
@@ -9,6 +9,7 @@
 #ifndef _LINUX_SMB_FS_H
 #define _LINUX_SMB_FS_H
 
+#include <linux/dirent.h>
 #include <linux/smb.h>
 
 /*
@@ -71,30 +72,24 @@
 
 /* linux/fs/smbfs/dir.c */
 extern struct inode_operations smb_dir_inode_operations;
-struct smb_inode_info *smb_find_inode(struct smb_sb_info *server, ino_t ino);
-void smb_free_inode_info(struct smb_inode_info *i);
-void smb_free_all_inodes(struct smb_sb_info *server);
-void smb_init_root(struct smb_sb_info *server);
-int  smb_stat_root(struct smb_sb_info *server);
-void smb_init_dir_cache(void);
-void smb_invalid_dir_cache(struct inode *);
-void smb_free_dir_cache(void);
+void smb_init_root(struct smb_sb_info *);
+int  smb_stat_root(struct smb_sb_info *);
+void smb_renew_times(struct dentry *);
 
 /* linux/fs/smbfs/ioctl.c */
 int smb_ioctl (struct inode * inode, struct file * filp,
                unsigned int cmd, unsigned long arg);
 
 /* linux/fs/smbfs/inode.c */
-struct super_block *smb_read_super(struct super_block *sb,
-                                   void *raw_data, int silent);
+struct super_block *smb_read_super(struct super_block *, void *, int);
 extern int init_smb_fs(void);
-void smb_invalidate_inodes(struct smb_sb_info *server);
-int smb_revalidate_inode(struct inode *i);
-int smb_refresh_inode(struct inode *i);
-int smb_notify_change(struct inode *inode, struct iattr *attr);
-void smb_invalidate_connection(struct smb_sb_info *server);
-int smb_conn_is_valid(struct smb_sb_info *server);
-unsigned long smb_invent_inos(unsigned long n);
+void smb_invalidate_inodes(struct smb_sb_info *);
+int  smb_revalidate_inode(struct inode *);
+int  smb_refresh_inode(struct inode *);
+int  smb_notify_change(struct inode *, struct iattr *);
+void smb_invalidate_connection(struct smb_sb_info *);
+int  smb_conn_is_valid(struct smb_sb_info *);
+unsigned long smb_invent_inos(unsigned long);
 struct inode *smb_iget(struct super_block *, struct smb_fattr *);
 
 /* linux/fs/smbfs/proc.c */
@@ -105,6 +100,7 @@
 int smb_offerconn(struct smb_sb_info *server);
 int smb_newconn(struct smb_sb_info *server, struct smb_conn_opt *opt);
 int smb_close(struct inode *);
+void smb_close_dentry(struct dentry *);
 int smb_open(struct dentry *, int);
 static inline int
 smb_is_open(struct inode *i)
@@ -112,34 +108,31 @@
 	return (i->u.smbfs_i.open == SMB_SERVER(i)->generation);
 }
 
-int smb_proc_read(struct inode *, off_t, long, char *);
+int smb_proc_read(struct inode *, off_t, int, char *);
 int smb_proc_write(struct inode *, off_t, int, const char *);
 int smb_proc_create(struct dentry *, struct qstr *, __u16, time_t);
 int smb_proc_mv(struct dentry *, struct qstr *, struct dentry *, struct qstr *);
 int smb_proc_mkdir(struct dentry *, struct qstr *);
 int smb_proc_rmdir(struct dentry *, struct qstr *);
 int smb_proc_unlink(struct dentry *dir, struct qstr *);
-int smb_proc_readdir(struct dentry *dir, int fpos, int cache_size, struct smb_dirent *entry);
-int smb_proc_getattr(struct dentry *dir, struct qstr *name,
-		     struct smb_fattr *entry);
-int smb_proc_setattr(struct smb_sb_info *server,
-                     struct dentry *dir,
-                     struct smb_fattr *new_finfo);
-int smb_proc_dskattr(struct super_block *sb, struct statfs *attr);
-int smb_proc_reconnect(struct smb_sb_info *server);
-int smb_proc_connect(struct smb_sb_info *server);
-int smb_proc_disconnect(struct smb_sb_info *server);
-int smb_proc_trunc(struct smb_sb_info *server, __u16 fid, __u32 length);
+int smb_proc_readdir(struct dentry *, int, void *);
+int smb_proc_getattr(struct dentry *, struct qstr *, struct smb_fattr *);
+int smb_proc_setattr(struct smb_sb_info *, struct dentry *, struct smb_fattr *);
+int smb_proc_dskattr(struct super_block *, struct statfs *);
+int smb_proc_reconnect(struct smb_sb_info *);
+int smb_proc_connect(struct smb_sb_info *);
+int smb_proc_disconnect(struct smb_sb_info *);
+int smb_proc_trunc(struct smb_sb_info *, __u16, __u32);
 void smb_init_root_dirent(struct smb_sb_info *server, struct smb_fattr *);
 
 /* linux/fs/smbfs/sock.c */
+int smb_valid_socket(struct inode *);
+void smb_close_socket(struct smb_sb_info *);
 int smb_release(struct smb_sb_info *server);
 int smb_connect(struct smb_sb_info *server);
 int smb_request(struct smb_sb_info *server);
-int smb_request_read_raw(struct smb_sb_info *server,
-                         unsigned char *target, int max_len);
-int smb_request_write_raw(struct smb_sb_info *server,
-                          unsigned const char *source, int length);
+int smb_request_read_raw(struct smb_sb_info *, unsigned char *, int);
+int smb_request_write_raw(struct smb_sb_info *, unsigned const char *, int);
 int smb_catch_keepalive(struct smb_sb_info *server);
 int smb_dont_catch_keepalive(struct smb_sb_info *server);
 int smb_trans2_request(struct smb_sb_info *server, __u16 trans2_command,
@@ -150,6 +143,72 @@
 
 /* linux/fs/smbfs/mmap.c */
 int smb_mmap(struct file * file, struct vm_area_struct * vma);
+
+/* fs/smbfs/cache.c */
+
+/*
+ * The cache index describes the pages mapped starting
+ * at offset PAGE_SIZE.  We keep only a minimal amount
+ * of information here.
+ */
+struct cache_index {
+	unsigned short num_entries;
+	unsigned short space;
+	struct cache_block * block;
+};
+
+#define NINDEX (PAGE_SIZE-64)/sizeof(struct cache_index)
+/*
+ * The cache head is mapped as the page at offset 0.
+ */
+struct cache_head {
+	int	valid;
+	int	status;		/* error code or 0 */
+	int	entries;	/* total entries */
+	int	pages;		/* number of data pages */
+	int	idx;		/* index of current data page */
+	struct cache_index index[NINDEX];
+};
+
+/*
+ * An array of cache_entry structures holds information
+ * for each object in the cache_block.
+ */
+struct cache_entry {
+	ino_t ino;
+	unsigned short namelen;
+	unsigned short offset;
+};
+
+/*
+ * The cache blocks hold the actual data.  The entry table grows up
+ * while the names grow down, and we have space until they meet.
+ */
+struct cache_block {
+	union {
+		struct cache_entry table[1];
+		char	names[PAGE_SIZE];
+	} cb_data;
+};
+
+/*
+ * To return an entry, we can pass a reference to the
+ * name instead of having to copy it.
+ */
+struct cache_dirent {
+	ino_t ino;
+	unsigned long pos;
+	int len;
+	char * name;
+};
+
+struct cache_head * smb_get_dircache(struct dentry *);
+void smb_init_dircache(struct cache_head *);
+void smb_free_dircache(struct cache_head *);
+int  smb_refill_dircache(struct cache_head *, struct dentry *);
+void smb_add_to_cache(struct cache_head *, struct dirent *, off_t);
+int  smb_find_in_cache(struct cache_head *, off_t, struct cache_dirent *);
+void smb_invalid_dir_cache(struct inode *);
 
 #endif /* __KERNEL__ */
 
diff -u --recursive --new-file v2.1.57/linux/include/linux/smb_fs_i.h linux/include/linux/smb_fs_i.h
--- v2.1.57/linux/include/linux/smb_fs_i.h	Tue Sep 23 16:48:50 1997
+++ linux/include/linux/smb_fs_i.h	Sun Oct 12 10:17:05 1997
@@ -22,11 +22,13 @@
 	 * (open == generation).
 	 */
         unsigned int open;
-	void * dentry;		/* The dentry we were opened with */
 	__u16 fileid;		/* What id to handle a file with? */
 	__u16 attr;		/* Attribute fields, DOS value */
 
 	__u16 access;		/* Access bits. */
+	__u16 cache_valid;	/* dircache valid? */
+	unsigned long oldmtime;	/* last time refreshed */
+	void * dentry;		/* The dentry we were opened with */
 };
 
 #endif
diff -u --recursive --new-file v2.1.57/linux/include/linux/smb_fs_sb.h linux/include/linux/smb_fs_sb.h
--- v2.1.57/linux/include/linux/smb_fs_sb.h	Wed Sep  3 20:52:44 1997
+++ linux/include/linux/smb_fs_sb.h	Sun Oct 12 10:17:05 1997
@@ -15,6 +15,11 @@
 #include <linux/smb.h>
 #include <linux/smb_mount.h>
 
+/* Get the server for the specified dentry */
+#define server_from_dentry(dentry) &dentry->d_sb->u.smbfs_sb
+#define SB_of(server) ((struct super_block *) ((char *)(server) - \
+	(unsigned long)(&((struct super_block *)0)->u.smbfs_sb)))
+
 struct smb_sb_info {
         enum smb_conn_state state;
 	struct file * sock_file;
@@ -29,6 +34,7 @@
 	struct smb_conn_opt opt;
 
 	struct semaphore sem;
+	struct wait_queue * wait;
 
 	__u32              packet_size;
 	unsigned char *    packet;
diff -u --recursive --new-file v2.1.57/linux/include/linux/sunrpc/sched.h linux/include/linux/sunrpc/sched.h
--- v2.1.57/linux/include/linux/sunrpc/sched.h	Thu Jun 12 15:30:27 1997
+++ linux/include/linux/sunrpc/sched.h	Sun Oct 12 10:17:46 1997
@@ -143,7 +143,7 @@
 void		rpc_delay(struct rpc_task *, unsigned long);
 void *		rpc_allocate(unsigned int flags, unsigned int);
 void		rpc_free(void *);
-void		rpciod_up(void);
+int		rpciod_up(void);
 void		rpciod_down(void);
 
 extern __inline__ void *
diff -u --recursive --new-file v2.1.57/linux/kernel/fork.c linux/kernel/fork.c
--- v2.1.57/linux/kernel/fork.c	Wed Sep 24 20:05:48 1997
+++ linux/kernel/fork.c	Sun Oct 12 10:12:38 1997
@@ -208,7 +208,6 @@
 	struct vm_area_struct * mpnt, *tmp, **pprev;
 	int retval;
 
-	mm->mmap = mm->mmap_cache = NULL;
 	flush_cache_mm(current->mm);
 	pprev = &mm->mmap;
 	for (mpnt = current->mm->mmap ; mpnt ; mpnt = mpnt->vm_next) {
@@ -254,8 +253,7 @@
 		if (retval)
 			goto fail_nomem;
 	}
-	flush_tlb_mm(current->mm);
-	return 0;
+	retval = 0;
 
 fail_nomem:
 	flush_tlb_mm(current->mm);
@@ -276,7 +274,10 @@
 		mm->count = 1;
 		mm->def_flags = 0;
 		mm->mmap_sem = MUTEX;
-		mm->pgd = NULL;
+		/*
+		 * Leave mm->pgd set to the parent's pgd
+		 * so that pgd_offset() is always valid.
+		 */
 		mm->mmap = mm->mmap_cache = NULL;
 
 		/* It has not run yet, so cannot be present in anyone's
@@ -324,10 +325,12 @@
 		goto free_mm;
 	retval = dup_mmap(mm);
 	if (retval)
-		goto free_mm;
+		goto free_pt;
 	return 0;
 
 free_mm:
+	mm->pgd = NULL;
+free_pt:
 	tsk->mm = NULL;
 	mmput(mm);
 fail_nomem:
@@ -376,7 +379,13 @@
 	struct files_struct *oldf, *newf;
 	struct file **old_fds, **new_fds;
 
+	/*
+	 * A background process may not have any files ...
+	 */
 	oldf = current->files;
+	if (!oldf)
+		return 0;
+
 	if (clone_flags & CLONE_FILES) {
 		oldf->count++;
 		return 0;
@@ -516,7 +525,9 @@
 	}
 	++total_forks;
 	error = p->pid;
-	goto fork_out;
+bad_fork:
+	unlock_kernel();
+	return error;
 
 bad_fork_cleanup_sighand:
 	exit_sighand(p);
@@ -536,10 +547,7 @@
 	nr_tasks--;
 bad_fork_free:
 	free_task_struct(p);
-bad_fork:
-fork_out:
-	unlock_kernel();
-	return error;
+	goto bad_fork;
 }
 
 static void files_ctor(void *fp, kmem_cache_t *cachep, unsigned long flags)
diff -u --recursive --new-file v2.1.57/linux/kernel/ksyms.c linux/kernel/ksyms.c
--- v2.1.57/linux/kernel/ksyms.c	Thu Sep 11 09:02:24 1997
+++ linux/kernel/ksyms.c	Sun Oct 12 10:11:28 1997
@@ -192,6 +192,8 @@
 EXPORT_SYMBOL(posix_block_lock);
 EXPORT_SYMBOL(posix_unblock_lock);
 EXPORT_SYMBOL(dput);
+EXPORT_SYMBOL(get_cached_page);
+EXPORT_SYMBOL(put_cached_page);
 
 #if !defined(CONFIG_NFSD) && defined(CONFIG_NFSD_MODULE)
 EXPORT_SYMBOL(do_nfsservctl);
@@ -369,6 +371,8 @@
 EXPORT_SYMBOL(get_hash_table);
 EXPORT_SYMBOL(get_empty_inode);
 EXPORT_SYMBOL(insert_inode_hash);
+EXPORT_SYMBOL(make_bad_inode);
+EXPORT_SYMBOL(is_bad_inode);
 EXPORT_SYMBOL(event);
 EXPORT_SYMBOL(__down);
 EXPORT_SYMBOL(__up);
diff -u --recursive --new-file v2.1.57/linux/kernel/sysctl.c linux/kernel/sysctl.c
--- v2.1.57/linux/kernel/sysctl.c	Thu Jul 17 10:06:09 1997
+++ linux/kernel/sysctl.c	Sun Oct 12 10:10:40 1997
@@ -147,7 +147,7 @@
 	 0444, NULL, &proc_dointvec},
 	{KERN_MAXINODE, "inode-max", &max_inodes, sizeof(int),
 	 0644, NULL, &proc_dointvec},
-	{KERN_NRFILE, "file-nr", &nr_files, sizeof(int),
+	{KERN_NRFILE, "file-nr", &nr_files, 3*sizeof(int),
 	 0444, NULL, &proc_dointvec},
 	{KERN_MAXFILE, "file-max", &max_files, sizeof(int),
 	 0644, NULL, &proc_dointvec},
diff -u --recursive --new-file v2.1.57/linux/mm/filemap.c linux/mm/filemap.c
--- v2.1.57/linux/mm/filemap.c	Tue Sep 23 16:48:50 1997
+++ linux/mm/filemap.c	Sun Oct 12 10:11:28 1997
@@ -1398,3 +1398,60 @@
 		return written;
 	return status;
 }
+
+/*
+ * Support routines for directory cacheing using the page cache.
+ */
+
+/*
+ * Finds the page at the specified offset, installing a new page
+ * if requested.  The count is incremented and the page is locked.
+ *
+ * Note: we don't have to worry about races here, as the caller
+ * is holding the inode semaphore.
+ */
+unsigned long get_cached_page(struct inode * inode, unsigned long offset,
+				int new)
+{
+	struct page * page;
+	struct page ** hash;
+	unsigned long page_cache;
+
+	hash = page_hash(inode, offset);
+	page = __find_page(inode, offset, *hash);
+	if (!page) {
+		if (!new)
+			goto out;
+		page_cache = get_free_page(GFP_KERNEL);
+		if (!page_cache)
+			goto out;
+		page = mem_map + MAP_NR(page_cache);
+		add_to_page_cache(page, inode, offset, hash);
+	}
+	if (atomic_read(&page->count) != 2)
+		printk("get_cached_page: page count=%d\n",
+			atomic_read(&page->count));
+	if (test_bit(PG_locked, &page->flags))
+		printk("get_cached_page: page already locked!\n");
+	set_bit(PG_locked, &page->flags);
+
+out:
+	return page_address(page);
+}
+
+/*
+ * Unlock and free a page.
+ */
+void put_cached_page(unsigned long addr)
+{
+	struct page * page = mem_map + MAP_NR(addr);
+
+	if (!test_bit(PG_locked, &page->flags))
+		printk("put_cached_page: page not locked!\n");
+	if (atomic_read(&page->count) != 2)
+		printk("put_cached_page: page count=%d\n", 
+			atomic_read(&page->count));
+	clear_bit(PG_locked, &page->flags);
+	wake_up(&page->wait);
+	__free_page(page);
+}
diff -u --recursive --new-file v2.1.57/linux/mm/vmscan.c linux/mm/vmscan.c
--- v2.1.57/linux/mm/vmscan.c	Wed Sep 24 20:05:48 1997
+++ linux/mm/vmscan.c	Sun Oct 12 10:13:19 1997
@@ -24,9 +24,6 @@
 #include <linux/smp_lock.h>
 #include <linux/slab.h>
 
-#include <asm/dma.h>
-#include <asm/system.h> /* for cli()/sti() */
-#include <asm/uaccess.h> /* for copy_to/from_user */
 #include <asm/bitops.h>
 #include <asm/pgtable.h>
 
@@ -418,6 +415,7 @@
        printk ("Starting kswapd v%.*s\n", i, s);
 }
 
+#define MAX_SWAP_FAIL 3
 /*
  * The background pageout daemon.
  * Started as a kernel thread from the init process.
@@ -445,6 +443,8 @@
 	init_swap_timer();
 	
 	while (1) {
+		int fail;
+
 		kswapd_awake = 0;
 		current->signal = 0;
 		run_task_queue(&tq_disk);
@@ -455,13 +455,27 @@
 		 * We now only swap out as many pages as needed.
 		 * When we are truly low on memory, we swap out
 		 * synchronously (WAIT == 1).  -- Rik.
+		 * If we've had too many consecutive failures,
+		 * go back to sleep to let other tasks run.
+		 */
+		for (fail = 0; fail++ < MAX_SWAP_FAIL;) {
+			int pages, wait;
+
+			pages = nr_free_pages;
+			if (nr_free_pages >= min_free_pages)
+				pages += atomic_read(&nr_async_pages);
+			if (pages >= free_pages_high)
+				break;
+			wait = (pages < free_pages_low);
+			if (try_to_free_page(GFP_KERNEL, 0, wait))
+				fail = 0;
+		}
+		/*
+		 * Report failure if we couldn't reach the minimum goal.
 		 */
-		while(nr_free_pages < min_free_pages)
-			try_to_free_page(GFP_KERNEL, 0, 1);
-		while((nr_free_pages + atomic_read(&nr_async_pages)) < free_pages_low)
-			try_to_free_page(GFP_KERNEL, 0, 1);
-		while((nr_free_pages + atomic_read(&nr_async_pages)) < free_pages_high)
-			try_to_free_page(GFP_KERNEL, 0, 0);
+		if (nr_free_pages < min_free_pages)
+			printk("kswapd: failed, got %d of %d\n",
+				nr_free_pages, min_free_pages);
 	}
 }
 
diff -u --recursive --new-file v2.1.57/linux/net/sunrpc/clnt.c linux/net/sunrpc/clnt.c
--- v2.1.57/linux/net/sunrpc/clnt.c	Wed Sep 24 20:05:49 1997
+++ linux/net/sunrpc/clnt.c	Sun Oct 12 10:17:46 1997
@@ -116,17 +116,23 @@
 
 /*
  * Properly shut down an RPC client, terminating all outstanding
- * requests.
+ * requests. Note that we must be certain that cl_oneshot and
+ * cl_dead are cleared, or else the client would be destroyed
+ * when the last task releases it.
  */
 int
 rpc_shutdown_client(struct rpc_clnt *clnt)
 {
 	dprintk("RPC: shutting down %s client for %s\n",
-			clnt->cl_protname, clnt->cl_server);
+		clnt->cl_protname, clnt->cl_server);
 	while (clnt->cl_users) {
-		dprintk("sigmask %08lx\n", current->signal);
-		dprintk("users %d\n", clnt->cl_users);
-		clnt->cl_dead = 1;
+#ifdef RPC_DEBUG
+		printk("rpc_shutdown_client: client %s, tasks=%d\n",
+			clnt->cl_protname, clnt->cl_users);
+#endif
+		/* Don't let rpc_release_client destroy us */
+		clnt->cl_oneshot = 0;
+		clnt->cl_dead = 0;
 		rpc_killall_tasks(clnt);
 		sleep_on(&destroy_wait);
 	}
@@ -162,12 +168,16 @@
 {
 	dprintk("RPC:      rpc_release_client(%p, %d)\n",
 				clnt, clnt->cl_users);
-	if (--(clnt->cl_users) == 0) {
-		wake_up(&destroy_wait);
-		if (clnt->cl_oneshot || clnt->cl_dead)
-			rpc_destroy_client(clnt);
-	}
-	dprintk("RPC:      rpc_release_client done\n");
+	if (clnt->cl_users) {
+		if (--(clnt->cl_users) > 0)
+			return;
+	} else
+		printk("rpc_release_client: %s client already free??\n",
+			clnt->cl_protname);
+
+	wake_up(&destroy_wait);
+	if (clnt->cl_oneshot || clnt->cl_dead)
+		rpc_destroy_client(clnt);
 }
 
 /*
@@ -205,10 +215,9 @@
 	if ((async = (flags & RPC_TASK_ASYNC)) != 0) {
 		if (!func)
 			func = rpc_default_callback;
-		if (!(task = rpc_new_task(clnt, func, flags))) {
-			current->blocked = oldmask;
-			return -ENOMEM;
-		}
+		status = -ENOMEM;
+		if (!(task = rpc_new_task(clnt, func, flags)))
+			goto out;
 		task->tk_calldata = data;
 	} else {
 		rpc_init_task(task, clnt, NULL, flags);
@@ -222,12 +231,13 @@
 	} else
 		async = 0;
 
+	status = 0;
 	if (!async) {
 		status = task->tk_status;
 		rpc_release_task(task);
-	} else
-		status = 0;
+	}
 
+out:
 	current->blocked = oldmask;
 	return status;
 }
@@ -354,6 +364,7 @@
 
 	if ((task->tk_buffer = rpc_malloc(task, bufsiz)) != NULL)
 		return;
+	printk("RPC: buffer allocation failed for task %p\n", task); 
 
 	if (1 || !signalled()) {
 		xprt_release(task);
diff -u --recursive --new-file v2.1.57/linux/net/sunrpc/pmap_clnt.c linux/net/sunrpc/pmap_clnt.c
--- v2.1.57/linux/net/sunrpc/pmap_clnt.c	Thu May 15 16:48:07 1997
+++ linux/net/sunrpc/pmap_clnt.c	Sun Oct 12 10:17:46 1997
@@ -54,15 +54,16 @@
 	}
 	clnt->cl_binding = 1;
 
-	task->tk_status = 0;
-	if (!(pmap_clnt = pmap_create(clnt->cl_server, sap, map->pm_prot))) {
-		task->tk_status = -EACCES;
+	task->tk_status = -EACCES; /* why set this? returns -EIO below */
+	if (!(pmap_clnt = pmap_create(clnt->cl_server, sap, map->pm_prot)))
 		goto bailout;
-	}
-	if (!(child = rpc_new_child(pmap_clnt, task))) {
-		rpc_destroy_client(pmap_clnt);
+	task->tk_status = 0;
+
+	/*
+	 * Note: rpc_new_child will release client after a failure.
+	 */
+	if (!(child = rpc_new_child(pmap_clnt, task)))
 		goto bailout;
-	}
 
 	/* Setup the call info struct */
 	rpc_call_setup(child, PMAP_GETPORT, map, &clnt->cl_port, 0);
diff -u --recursive --new-file v2.1.57/linux/net/sunrpc/sched.c linux/net/sunrpc/sched.c
--- v2.1.57/linux/net/sunrpc/sched.c	Tue Sep 23 16:48:50 1997
+++ linux/net/sunrpc/sched.c	Sun Oct 12 10:17:46 1997
@@ -56,7 +56,8 @@
  */
 static struct wait_queue *	rpciod_idle = NULL;
 static struct wait_queue *	rpciod_killer = NULL;
-static int			rpciod_sema = 0;
+static struct semaphore		rpciod_sema = MUTEX;
+static unsigned int		rpciod_users = 0;
 static pid_t			rpciod_pid = 0;
 static int			rpc_inhibit = 0;
 
@@ -575,19 +576,36 @@
 				current->pid);
 }
 
+/*
+ * Create a new task for the specified client.  We have to
+ * clean up after an allocation failure, as the client may
+ * have specified "oneshot".
+ */
 struct rpc_task *
 rpc_new_task(struct rpc_clnt *clnt, rpc_action callback, int flags)
 {
 	struct rpc_task	*task;
 
-	if (!(task = (struct rpc_task *) rpc_allocate(flags, sizeof(*task))))
-		return NULL;
+	task = (struct rpc_task *) rpc_allocate(flags, sizeof(*task));
+	if (!task)
+		goto cleanup;
 
 	rpc_init_task(task, clnt, callback, flags);
 
 	dprintk("RPC: %4d allocated task\n", task->tk_pid);
 	task->tk_flags |= RPC_TASK_DYNAMIC;
+out:
 	return task;
+
+cleanup:
+	/* Check whether to release the client */
+	if (clnt) {
+		printk("rpc_new_task: failed, users=%d, oneshot=%d\n",
+			clnt->cl_users, clnt->cl_oneshot);
+		clnt->cl_users++; /* pretend we were used ... */
+		rpc_release_client(clnt);
+	}
+	goto out;
 }
 
 void
@@ -662,6 +680,9 @@
 	rpc_release_task(child);
 }
 
+/*
+ * Note: rpc_new_task releases the client after a failure.
+ */
 struct rpc_task *
 rpc_new_child(struct rpc_clnt *clnt, struct rpc_task *parent)
 {
@@ -715,11 +736,15 @@
 	unsigned long	oldflags;
 	int		rounds = 0;
 
+	MOD_INC_USE_COUNT;
 	lock_kernel();
+	/*
+	 * Let our maker know we're running ...
+	 */
 	rpciod_pid = current->pid;
+	wake_up(&rpciod_idle);
 
-	MOD_INC_USE_COUNT;
-	/* exit_files(current); */
+	exit_files(current);
 	exit_mm(current);
 	current->blocked |= ~_S(SIGKILL);
 	current->session = 1;
@@ -727,25 +752,28 @@
 	sprintf(current->comm, "rpciod");
 
 	dprintk("RPC: rpciod starting (pid %d)\n", rpciod_pid);
-	while (rpciod_sema) {
+	while (rpciod_users) {
 		if (signalled()) {
 			if (current->signal & _S(SIGKILL)) {
 				rpciod_killall();
 			} else {
 				printk("rpciod: ignoring signal (%d users)\n",
-					rpciod_sema);
+					rpciod_users);
 			}
 			current->signal &= current->blocked;
 		}
 		__rpc_schedule();
 
-		if (++rounds >= 64)	/* safeguard */
+		if (++rounds >= 64) {	/* safeguard */
 			schedule();
+			rounds = 0;
+		}
 		save_flags(oldflags); cli();
 		if (!schedq.task) {
 			dprintk("RPC: rpciod back to sleep\n");
 			interruptible_sleep_on(&rpciod_idle);
 			dprintk("RPC: switch to rpciod\n");
+			rounds = 0;
 		}
 		restore_flags(oldflags);
 	}
@@ -780,26 +808,84 @@
 	}
 }
 
-void
+/*
+ * Start up the rpciod process if it's not already running.
+ */
+int
 rpciod_up(void)
 {
-	dprintk("rpciod_up pid %d sema %d\n", rpciod_pid, rpciod_sema);
-	if (!(rpciod_sema++) || !rpciod_pid)
-		kernel_thread(rpciod, &rpciod_killer, 0);
+	int error = 0;
+
+	MOD_INC_USE_COUNT;
+	down(&rpciod_sema);
+	dprintk("rpciod_up: pid %d, users %d\n", rpciod_pid, rpciod_users);
+	rpciod_users++;
+	if (rpciod_pid)
+		goto out;
+	/*
+	 * If there's no pid, we should be the first user.
+	 */
+	if (rpciod_users > 1)
+		printk("rpciod_up: no pid, %d users??\n", rpciod_users);
+	/*
+	 * Create the rpciod thread and wait for it to start.
+	 */
+	error = kernel_thread(rpciod, &rpciod_killer, 0);
+	if (error < 0) {
+		printk("rpciod_up: create thread failed, error=%d\n", error);
+		goto out;
+	}
+	sleep_on(&rpciod_idle);
+	error = 0;
+out:
+	up(&rpciod_sema);
+	MOD_DEC_USE_COUNT;
+	return error;
 }
 
 void
 rpciod_down(void)
 {
-	dprintk("rpciod_down pid %d sema %d\n", rpciod_pid, rpciod_sema);
-	if (--rpciod_sema > 0)
-		return;
+	unsigned long oldflags;
+
+	MOD_INC_USE_COUNT;
+	down(&rpciod_sema);
+	dprintk("rpciod_down pid %d sema %d\n", rpciod_pid, rpciod_users);
+	if (rpciod_users) {
+		if (--rpciod_users)
+			goto out;
+	} else
+		printk("rpciod_down: pid=%d, no users??\n", rpciod_pid);
+
+	if (!rpciod_pid) {
+		printk("rpciod_down: Nothing to do!\n");
+		goto out;
+	}
 
-	rpciod_sema = 0;
 	kill_proc(rpciod_pid, SIGKILL, 1);
+	/*
+	 * Usually rpciod will exit very quickly, so we
+	 * wait briefly before checking the process id.
+	 */
+	oldflags = current->signal;
+	current->signal = 0;
+	current->state = TASK_INTERRUPTIBLE;
+	current->timeout = jiffies + 1;
+	schedule();
+	current->timeout = 0;
+	/*
+	 * Display a message if we're going to wait longer.
+	 */
 	while (rpciod_pid) {
-		if (signalled())
-			return;
+		printk("rpciod_down: waiting for pid %d to exit\n", rpciod_pid);
+		if (signalled()) {
+			printk("rpciod_down: caught signal\n");
+			break;
+		}
 		interruptible_sleep_on(&rpciod_killer);
 	}
+	current->signal = oldflags;
+out:
+	up(&rpciod_sema);
+	MOD_DEC_USE_COUNT;
 }
diff -u --recursive --new-file v2.1.57/linux/net/sunrpc/svc.c linux/net/sunrpc/svc.c
--- v2.1.57/linux/net/sunrpc/svc.c	Mon Apr  7 11:35:33 1997
+++ linux/net/sunrpc/svc.c	Sun Oct 12 10:17:46 1997
@@ -62,8 +62,12 @@
 				serv->sv_program->pg_name,
 				serv->sv_nrthreads);
 
-	if (--(serv->sv_nrthreads) != 0)
-		return;
+	if (serv->sv_nrthreads) {
+		if (--(serv->sv_nrthreads) != 0)
+			return;
+	} else
+		printk("svc_destroy: no threads for serv=%p!\n", serv);
+
 	while ((svsk = serv->sv_allsocks) != NULL)
 		svc_delete_socket(svsk);
 
@@ -110,30 +114,31 @@
 int
 svc_create_thread(svc_thread_fn func, struct svc_serv *serv)
 {
-	struct svc_rqst	*rqstp = 0;
-	int		error;
+	struct svc_rqst	*rqstp;
+	int		error = -ENOMEM;
 
-	if (!(rqstp = kmalloc(sizeof(*rqstp), GFP_KERNEL)))
-		return -ENOMEM;
+	rqstp = kmalloc(sizeof(*rqstp), GFP_KERNEL);
+	if (!rqstp)
+		goto out;
 
 	memset(rqstp, 0, sizeof(*rqstp));
 	if (!(rqstp->rq_argp = (u32 *) kmalloc(serv->sv_xdrsize, GFP_KERNEL))
 	 || !(rqstp->rq_resp = (u32 *) kmalloc(serv->sv_xdrsize, GFP_KERNEL))
-	 || !svc_init_buffer(&rqstp->rq_defbuf, serv->sv_bufsz)) {
-		error = -ENOMEM;
-		goto failure;
-	}
+	 || !svc_init_buffer(&rqstp->rq_defbuf, serv->sv_bufsz))
+		goto out_thread;
 
 	serv->sv_nrthreads++;
-	if ((error = kernel_thread((int (*)(void *)) func, rqstp, 0)) < 0)
-		goto failure;
-
 	rqstp->rq_server = serv;
-	return 0;
+	error = kernel_thread((int (*)(void *)) func, rqstp, 0);
+	if (error < 0)
+		goto out_thread;
+	error = 0;
+out:
+	return error;
 
-failure:
+out_thread:
 	svc_exit_thread(rqstp);
-	return error;
+	goto out;
 }
 
 /*
@@ -152,7 +157,8 @@
 	kfree(rqstp);
 
 	/* Release the server */
-	svc_destroy(serv);
+	if (serv)
+		svc_destroy(serv);
 }
 
 /*
diff -u --recursive --new-file v2.1.57/linux/net/sunrpc/svcsock.c linux/net/sunrpc/svcsock.c
--- v2.1.57/linux/net/sunrpc/svcsock.c	Tue Sep 23 16:48:50 1997
+++ linux/net/sunrpc/svcsock.c	Sun Oct 12 10:17:46 1997
@@ -743,14 +743,18 @@
 	if ((svsk = svc_sock_dequeue(serv)) != NULL) {
 		enable_bh(NET_BH);
 		rqstp->rq_sock = svsk;
-		svsk->sk_inuse++;
+		svsk->sk_inuse++; /* N.B. where is this decremented? */
 	} else {
 		/* No data pending. Go to sleep */
 		rqstp->rq_sock = NULL;
 		rqstp->rq_wait = NULL;
 		svc_serv_enqueue(serv, rqstp);
 
-		current->state = TASK_UNINTERRUPTIBLE;
+		/*
+		 * We have to be able to interrupt this wait
+		 * to bring down the daemons ...
+		 */
+		current->state = TASK_INTERRUPTIBLE;
 		add_wait_queue(&rqstp->rq_wait, &wait);
 		enable_bh(NET_BH);
 		schedule();
@@ -762,6 +766,7 @@
 		}
 	}
 
+printk("svc_recv: svsk=%p, use count=%d\n", svsk, svsk->sk_inuse);
 	dprintk("svc: server %p servicing socket %p\n", rqstp, svsk);
 	len = svsk->sk_recvfrom(rqstp);
 
