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

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

diff -u --recursive --new-file v2.1.55/linux/fs/smbfs/dir.c linux/fs/smbfs/dir.c
@@ -19,6 +19,12 @@
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
 
+#define SMBFS_PARANOIA 1
+/* #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);
@@ -69,6 +75,15 @@
 	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)
@@ -76,166 +91,240 @@
 	return -EISDIR;
 }
 
+/*
+ * This is the callback from dput().  We close the file so that
+ * cached dentries don't keep the file open.
+ */
+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 kdev_t c_dev;
-static int c_size;
 static int c_seen_eof;
+static int c_size;
 static int c_last_returned_index;
-static struct smb_dirent *c_entry = NULL;
 
 static struct smb_dirent *
 smb_search_in_cache(struct inode *dir, unsigned long f_pos)
 {
 	int i;
 
-	if ((dir->i_dev != c_dev) || (dir->i_ino != c_ino))
-	{
-		return NULL;
-	}
-	for (i = 0; i < c_size; i++)
-	{
-		if (f_pos == c_entry[i].f_pos)
+	if (this_dir_cached(dir))
+		for (i = 0; i < c_size; i++)
 		{
-			c_last_returned_index = i;
-			return &(c_entry[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();
+	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.
+ */
 static int
 smb_refill_dir_cache(struct dentry *dentry, unsigned long f_pos)
 {
-	int result;
 	struct inode *dir = dentry->d_inode;
-	static struct semaphore sem = MUTEX;
-	int i;
-	ino_t ino;
+	ino_t ino_start;
+	int i, result;
 
-	do
+	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)
+	{
+		/*
+		 * 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++)
 	{
-		down(&sem);
-		result = smb_proc_readdir(dentry, f_pos,
-					  SMB_READDIR_CACHE_SIZE, c_entry);
+		struct dentry * new_dentry;
+		struct qstr qname;
 
-		if (result <= 0)
+		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)
 		{
-			smb_invalid_dir_cache(dir->i_ino);
-			up(&sem);
-			return result;
+			struct inode * inode = new_dentry->d_inode;
+			if (inode)
+				c_entry[i].attr.f_ino = inode->i_ino;
+			dput(new_dentry);
 		}
-		c_seen_eof = (result < SMB_READDIR_CACHE_SIZE);
-		c_dev = dir->i_dev;
-		c_ino = dir->i_ino;
-		c_size = result;
-		c_last_returned_index = 0;
-
-		ino = smb_invent_inos(c_size);
-
-		for (i = 0; i < c_size; i++)
-			c_entry[i].attr.f_ino = ino++;
-
-		up(&sem);
 	}
-	while ((c_dev != dir->i_dev) || (c_ino != dir->i_ino));
-
+out:
 	return result;
 }
 
-static int smb_readdir(struct file *filp,
-	    void *dirent, filldir_t filldir)
+static int 
+smb_readdir(struct file *filp, void *dirent, filldir_t filldir)
 {
 	struct dentry *dentry = filp->f_dentry;
 	struct inode *dir = dentry->d_inode;
-	int result, i = 0;
-	struct smb_dirent *entry = NULL;
+	struct smb_dirent *entry;
+	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))
-	{
-		return -EBADF;
-	}
+		goto out;
+
+	/*
+	 * Check whether the directory cache exists yet
+	 */
 	if (c_entry == NULL)
 	{
-		i = sizeof(struct smb_dirent) * SMB_READDIR_CACHE_SIZE;
-		c_entry = (struct smb_dirent *) smb_vmalloc(i);
-		if (c_entry == NULL)
+		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)
 		{
-			return -ENOMEM;
+			if (!entry)
+				goto out;
+			c_entry = entry;
+		} else if (entry) {
+			printk("smb_readdir: cache already alloced!\n");
+			smb_vfree(entry);
 		}
 	}
-	if (filp->f_pos == 0)
-	{
-		c_ino = 0;
-		c_dev = 0;
-		c_seen_eof = 0;
-
-		if (filldir(dirent, ".", 1, filp->f_pos, dir->i_ino) < 0)
-			return 0;
 
-		filp->f_pos += 1;
-	}
-	if (filp->f_pos == 1)
+	result = 0;
+	switch ((unsigned int) filp->f_pos)
 	{
-		if (filldir(dirent, "..", 2, filp->f_pos,
-			    filp->f_dentry->d_parent->d_inode->i_ino) < 0)
-			return 0;
-
-		filp->f_pos += 1;
-	}
+	case 0:
+		if (filldir(dirent, ".", 1, 0, dir->i_ino) < 0)
+			goto out;
+		filp->f_pos = 1;
+	case 1:
+		if (filldir(dirent, "..", 2, 1,
+				dentry->d_parent->d_inode->i_ino) < 0)
+			goto 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)
 	{
-		if (c_seen_eof)
+		/* 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)
 		{
-			/* End of directory */
-			return 0;
+#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;
 		}
 		result = smb_refill_dir_cache(dentry, filp->f_pos);
 		if (result <= 0)
-		{
-			return result;
-		}
+			goto up_and_out;
 		entry = c_entry;
 	}
+
 	while (entry < &(c_entry[c_size]))
 	{
-		ino_t ino = entry->attr.f_ino;
-
 		pr_debug("smb_readdir: entry->name = %s\n", entry->name);
 
-		if (filldir(dirent, entry->name, strlen(entry->name),
-			    entry->f_pos, ino) < 0)
-			break;
-
-		if ((dir->i_dev != c_dev) || (dir->i_ino != c_ino)
-		    || (entry->f_pos != filp->f_pos))
+		if (filldir(dirent, entry->name, entry->len, 
+				    entry->f_pos, entry->attr.f_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;
 	}
-	return 0;
+	result = 0;
+
+up_and_out:
+	up(&refill_cache_sem);
+out:
+	return result;
 }
 
 void
 smb_init_dir_cache(void)
 {
-	c_ino = 0;
-	c_dev = 0;
 	c_entry = NULL;
+	c_sb = NULL;
+	c_ino = 0;
+	c_seen_eof = 0;
 }
 
 void
-smb_invalid_dir_cache(unsigned long ino)
+smb_invalid_dir_cache(struct inode * dir)
 {
-	/* FIXME: check for dev as well */
-	if (ino == c_ino)
+	if (this_dir_cached(dir))
 	{
+		c_sb = NULL;
 		c_ino = 0;
 		c_seen_eof = 0;
 	}
@@ -246,6 +335,7 @@
 {
 	if (c_entry != NULL)
 	{
+		/* N.B. can this block?? */
 		smb_vfree(c_entry);
 	}
 	c_entry = NULL;
@@ -256,106 +346,111 @@
 {
 	struct smb_fattr finfo;
 	struct inode *inode;
-	int len = d_entry->d_name.len;
 	int error;
 
-	if (!dir || !S_ISDIR(dir->i_mode)) {
-		printk("smb_lookup: inode is NULL or not a directory\n");
-		return -ENOENT;
-	}
-
-	if (len > SMB_MAXNAMELEN)
-		return -ENAMETOOLONG;
-
-	error = smb_proc_getattr(d_entry, &(d_entry->d_name), &finfo);
+	error = -ENAMETOOLONG;
+	if (d_entry->d_name.len > SMB_MAXNAMELEN)
+		goto out;
+
+	error = smb_proc_getattr(d_entry->d_parent, &(d_entry->d_name), &finfo);
+#if 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);
+#endif
 
 	inode = NULL;
-	if (!error) {
-		error = -ENOENT;
+	if (error == -ENOENT)
+		goto add_entry;
+	if (!error)
+	{
 		finfo.f_ino = smb_invent_inos(1);
 		inode = smb_iget(dir->i_sb, &finfo);
-		if (!inode)
-			return -EACCES;
-	} else if (error != -ENOENT)
-		return error;
-
-	d_add(d_entry, inode);
-	return 0;
+		error = -EACCES;
+		if (inode)
+		{
+			/* cache the dentry pointer */
+			inode->u.smbfs_i.dentry = d_entry;
+	add_entry:
+			d_entry->d_op = &smbfs_dentry_operations;
+			d_add(d_entry, inode);
+			error = 0;
+		}
+	}
+out:
+	return error;
 }
 
-static int smb_create(struct inode *dir, struct dentry *dentry, int mode)
+/*
+ * This code is common to all routines creating a new inode.
+ */
+static int
+smb_instantiate(struct inode *dir, struct dentry *dentry)
 {
 	struct smb_fattr fattr;
-	struct inode *inode;
 	int error;
 
-	if (!dir || !S_ISDIR(dir->i_mode))
+	smb_invalid_dir_cache(dir);
+
+	error = smb_proc_getattr(dentry->d_parent, &(dentry->d_name), &fattr);
+	if (!error)
 	{
-		printk("smb_create: inode is NULL or not a directory\n");
-		return -ENOENT;
+		struct inode *inode;
+		error = -EACCES;
+		fattr.f_ino = smb_invent_inos(1);
+		inode = smb_iget(dir->i_sb, &fattr);
+		if (inode)
+		{
+			/* cache the dentry pointer */
+			inode->u.smbfs_i.dentry = dentry;
+			d_instantiate(dentry, inode);
+			error = 0;
+		}
 	}
+	return error;
+}
 
-	if (dentry->d_name.len > SMB_MAXNAMELEN)
-		return -ENAMETOOLONG;
-
-	error = smb_proc_create(dentry, &(dentry->d_name), 0, CURRENT_TIME);
-	if (error < 0)
-		return error;
+/* N.B. Should the mode argument be put into the fattr? */
+static int
+smb_create(struct inode *dir, struct dentry *dentry, int mode)
+{
+	int error;
 
-	smb_invalid_dir_cache(dir->i_ino);
+	error = -ENAMETOOLONG;
+	if (dentry->d_name.len > SMB_MAXNAMELEN)
+		goto out;
 
 	/* FIXME: In the CIFS create call we get the file in open
          * state. Currently we close it directly again, although this
 	 * is not necessary anymore. */
 
-	error = smb_proc_getattr(dentry, &(dentry->d_name), &fattr);
-	if (error < 0)
-		return error;
-
-	fattr.f_ino = smb_invent_inos(1);
-
-	inode = smb_iget(dir->i_sb, &fattr);
-	if (!inode)
-		return -EACCES;
-
-	d_instantiate(dentry, inode);
-	return 0;
+	error = smb_proc_create(dentry->d_parent, &(dentry->d_name), 
+				0, CURRENT_TIME);
+	if (!error)
+	{
+		error = smb_instantiate(dir, dentry);
+	}
+out:
+	return error;
 }
 
+/* N.B. Should the mode argument be put into the fattr? */
 static int
 smb_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
-	struct smb_fattr fattr;
-	struct inode *inode;
 	int error;
 
-	if (!dir || !S_ISDIR(dir->i_mode))
-	{
-		printk("smb_mkdir: inode is NULL or not a directory\n");
-		return -ENOENT;
-	}
-
+	error = -ENAMETOOLONG;
 	if (dentry->d_name.len > SMB_MAXNAMELEN)
-		return -ENAMETOOLONG;
-
-	error = smb_proc_mkdir(dentry, &(dentry->d_name));
-	if (error)
-		return error;
-
-	smb_invalid_dir_cache(dir->i_ino);
-
-	error = smb_proc_getattr(dentry, &(dentry->d_name), &fattr);
-	if (error < 0)
-		return error;
+		goto out;
 
-	fattr.f_ino = smb_invent_inos(1);
-
-	inode = smb_iget(dir->i_sb, &fattr);
-	if (!inode)
-		return -EACCES;
-
-	d_instantiate(dentry, inode);
-	return 0;
+	error = smb_proc_mkdir(dentry->d_parent, &(dentry->d_name));
+	if (!error)
+	{
+		error = smb_instantiate(dir, dentry);
+	}
+out:
+	return error;
 }
 
 static int
@@ -363,21 +458,25 @@
 {
 	int error;
 
-	if (!dir || !S_ISDIR(dir->i_mode))
-	{
-		printk("smb_rmdir: inode is NULL or not a directory\n");
-		return -ENOENT;
-	}
-
+	error = -ENAMETOOLONG;
 	if (dentry->d_name.len > NFS_MAXNAMLEN)
-		return -ENAMETOOLONG;
+		goto out;
 
-	error = smb_proc_rmdir(dentry, &(dentry->d_name));
-	if (error)
-		return error;
+	/*
+	 * Since the dentry is holding an inode, the file
+	 * is in use, so we have to close it first.
+	 */
+	if (dentry->d_inode)
+		smb_close(dentry->d_inode);
+	smb_invalid_dir_cache(dir);
 
-	d_delete(dentry);
-	return 0;
+	error = smb_proc_rmdir(dentry->d_parent, &(dentry->d_name));
+	if (!error)
+	{
+		d_delete(dentry);
+	}
+out:
+	return error;
 }
 
 static int
@@ -385,64 +484,96 @@
 {
 	int error;
 
-	if (!dir || !S_ISDIR(dir->i_mode))
-	{
-		printk("smb_unlink: inode is NULL or not a directory\n");
-		return -ENOENT;
-	}
-
+	error = -ENAMETOOLONG;
 	if (dentry->d_name.len > SMB_MAXNAMELEN)
-		return -ENAMETOOLONG;
-
-	error = smb_proc_unlink(dentry, &(dentry->d_name));
-	if (error)
-		return error;
+		goto out;
 
-	smb_invalid_dir_cache(dir->i_ino);
+	/*
+	 * Since the dentry is holding an inode, the file
+	 * is in use, so we have to close it first.
+	 */
+	if (dentry->d_inode)
+		smb_close(dentry->d_inode);
+	smb_invalid_dir_cache(dir);
 
-	d_delete(dentry);
-	return 0;
+	error = smb_proc_unlink(dentry->d_parent, &(dentry->d_name));
+	if (!error)
+	{
+		d_delete(dentry);
+	}
+out:
+	return error;
 }
 
-static int smb_rename(struct inode *old_dir, struct dentry *old_dentry,
-		      struct inode *new_dir, struct dentry *new_dentry)
+static int
+smb_rename(struct inode *old_dir, struct dentry *old_dentry,
+	   struct inode *new_dir, struct dentry *new_dentry)
 {
 	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");
-		return -ENOENT;
+		goto out;
 	}
 
 	if (!new_dir || !S_ISDIR(new_dir->i_mode))
 	{
 		printk("smb_rename: new inode is NULL or not a directory\n");
-		return -ENOENT;
+		goto out;
 	}
 
+	error = -ENAMETOOLONG;
 	if (old_dentry->d_name.len > SMB_MAXNAMELEN ||
 	    new_dentry->d_name.len > SMB_MAXNAMELEN)
-		return -ENAMETOOLONG;
-
-	error = smb_proc_mv(old_dentry, &(old_dentry->d_name),
-			    new_dentry, &(new_dentry->d_name));
+		goto out;
 
+	/*
+	 * Since the old and new dentries are holding the files open,
+	 * we have to close the files first.
+	 */
+	if (old_dentry->d_inode)
+		smb_close(old_dentry->d_inode);
+	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));
+	/*
+	 * If the new file exists, attempt to delete it.
+	 */
 	if (error == -EEXIST)
 	{
-		error = smb_proc_unlink(old_dentry, &(new_dentry->d_name));
-					
+#ifdef SMBFS_PARANOIA
+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
+printk("smb_rename: after unlink error=%d\n", error);
+#endif
 		if (error)
-			return error;
+			goto out;
+		d_delete(new_dentry);
 
-		error = smb_proc_mv(old_dentry, &(old_dentry->d_name),
-				    new_dentry, &(new_dentry->d_name));
+		error = smb_proc_mv(old_dentry->d_parent, &(old_dentry->d_name),
+				    new_dentry->d_parent, &(new_dentry->d_name));
 	}
 
-	if (error)
-		return error;
-
-	smb_invalid_dir_cache(old_dir->i_ino);
-	smb_invalid_dir_cache(new_dir->i_ino);
-	return 0;
+	/*
+	 * Update the dcache
+	 */
+	if (!error)
+	{
+		d_move(old_dentry, new_dentry);
+	}
+out:
+	return error;
 }

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