patch-2.1.58 linux/fs/smbfs/dir.c

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

diff -u --recursive --new-file v2.1.57/linux/fs/smbfs/dir.c linux/fs/smbfs/dir.c
@@ -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:

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov