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
- Lines: 717
- Date:
Sun Sep 14 15:14:56 1997
- Orig file:
v2.1.55/linux/fs/smbfs/dir.c
- Orig date:
Thu Sep 11 09:02:24 1997
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