patch-2.1.62 linux/fs/nfs/dir.c
Next file: linux/include/linux/isdnif.h
Previous file: linux/fs/dquot.c
Back to the patch index
Back to the overall index
- Lines: 537
- Date:
Sun Nov 2 11:00:08 1997
- Orig file:
v2.1.61/linux/fs/nfs/dir.c
- Orig date:
Sat Nov 1 11:04:27 1997
diff -u --recursive --new-file v2.1.61/linux/fs/nfs/dir.c linux/fs/nfs/dir.c
@@ -29,6 +29,8 @@
#include <asm/segment.h> /* for fs functions */
+#define NFS_MAX_AGE 10*HZ /* max age for dentry validation */
+
/* needed by smbfs as well ... move to dcache? */
extern void nfs_renew_times(struct dentry *);
extern void nfs_invalidate_dircache_sb(struct super_block *);
@@ -51,6 +53,8 @@
__u32 * entry; /* three __u32's per entry */
};
+static int nfs_safe_remove(struct dentry *);
+
static int nfs_dir_open(struct inode * inode, struct file * file);
static ssize_t nfs_dir_read(struct file *, char *, size_t, loff_t *);
static int nfs_readdir(struct file *, void *, filldir_t);
@@ -62,7 +66,8 @@
static int nfs_symlink(struct inode *, struct dentry *, const char *);
static int nfs_link(struct inode *, struct inode *, struct dentry *);
static int nfs_mknod(struct inode *, struct dentry *, int, int);
-static int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
+static int nfs_rename(struct inode *, struct dentry *,
+ struct inode *, struct dentry *);
static struct file_operations nfs_dir_operations = {
NULL, /* lseek - default */
@@ -387,7 +392,7 @@
goto bad;
}
if (S_ISDIR(dentry->d_inode->i_mode))
- max = 10*HZ;
+ max = NFS_MAX_AGE;
}
return (time < max) || IS_ROOT(dentry);
@@ -395,13 +400,44 @@
return 0;
}
-static void nfs_silly_delete(struct dentry *);
+/*
+ * This is called from dput() when d_count is going to 0.
+ * We use it to clean up silly-renamed files, and to check
+ * for dentries that have already expired.
+ */
+static void nfs_dentry_delete(struct dentry *dentry)
+{
+ if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
+ int error;
+
+ dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
+#ifdef NFS_DEBUG
+printk("nfs_dentry_delete: unlinking %s/%s\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
+ /* Unhash it first */
+ d_drop(dentry);
+ error = nfs_safe_remove(dentry);
+ if (error)
+ printk("NFS: can't silly-delete %s/%s, error=%d\n",
+ dentry->d_parent->d_name.name,
+ dentry->d_name.name, error);
+ }
+ /*
+ * Check whether to expire the dentry ...
+ */
+ else {
+ unsigned long age = jiffies - dentry->d_time;
+ if (age > NFS_MAX_AGE)
+ d_drop(dentry);
+ }
+}
static struct dentry_operations nfs_dentry_operations = {
nfs_lookup_revalidate, /* d_validate(struct dentry *) */
0, /* d_hash */
0, /* d_compare */
- nfs_silly_delete, /* d_delete(struct dentry *) */
+ nfs_dentry_delete /* d_delete(struct dentry *) */
};
/*
@@ -564,8 +600,12 @@
}
/*
- * Update inode->i_nlink immediately after a successful operation.
- * (See comments for nfs_unlink.)
+ * To avoid retaining a stale inode reference, we check the dentry
+ * use count prior to the operation, and return EBUSY if it has
+ * multiple users.
+ *
+ * Update inode->i_nlink immediately after a successful operation.
+ * (See comments for nfs_unlink.)
*/
static int nfs_rmdir(struct inode *dir, struct dentry *dentry)
{
@@ -579,17 +619,27 @@
return -ENOENT;
}
+ error = -ENAMETOOLONG;
if (dentry->d_name.len > NFS_MAXNAMLEN)
- return -ENAMETOOLONG;
+ goto out;
+ error = -EBUSY;
+ if (dentry->d_count > 1) {
+ /* Attempt to shrink child dentries ... */
+ shrink_dcache_sb(dentry->d_sb); /* Arghhh */
+ if (dentry->d_count > 1)
+ goto out;
+ }
+ /* Drop the dentry to force a new lookup */
+ d_drop(dentry);
error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dir), dentry->d_name.name);
if (!error) {
if (dentry->d_inode->i_nlink)
dentry->d_inode->i_nlink --;
nfs_invalidate_dircache(dir);
nfs_renew_times(dentry);
- d_delete(dentry);
}
+out:
return error;
}
@@ -606,24 +656,22 @@
*
* Concerning my choice of the temp name: it is just nice to have
* i_ino part of the temp name, as this offers another check whether
- * somebody attempts to remove the "silly renamed" dentry
- * itself. Which is something that I consider evil. Your opinion may
- * vary.
+ * somebody attempts to remove the "silly renamed" dentry itself.
+ * Which is something that I consider evil. Your opinion may vary.
* BUT:
* Now that I compute the hash value right, it should be possible to simply
* check for the DCACHE_NFSFS_RENAMED flag in dentry->d_flag instead of
* doing the string compare.
* WHICH MEANS:
* This offers the opportunity to shorten the temp name. Currently, I use
- * the hex representation of i_ino + the hex value of jiffies. This
- * sums up to as much as 36 characters for a 64 bit machine, and needs
- * 20 chars on a 32 bit machine. Have a look at jiffiesize etc.
+ * the hex representation of i_ino + an event counter. This sums up to
+ * as much as 36 characters for a 64 bit machine, and needs 20 chars on
+ * a 32 bit machine.
* QUINTESSENCE
* The use of i_ino is simply cosmetic. All we need is a unique temp
- * file name for the .nfs files. The hex representation of "jiffies"
- * seemed to be adequate. And as we retry in case such a file already
- * exists we are guaranteed to succed (after some jiffies have passed
- * by :)
+ * file name for the .nfs files. The event counter seemed to be adequate.
+ * And as we retry in case such a file already exists, we are guaranteed
+ * to succeed.
*/
static
@@ -631,15 +679,11 @@
{
struct qstr sqstr;
struct dentry *sdentry;
- unsigned long hash;
- int i, error;
+ int error;
sqstr.name = silly;
sqstr.len = slen;
- hash = init_name_hash();
- for (i= 0; i < slen; i++)
- hash = partial_name_hash(silly[i], hash);
- sqstr.hash = end_name_hash(hash);
+ sqstr.hash = full_name_hash(silly, slen);
sdentry = d_lookup(parent, &sqstr);
if (!sdentry) {
sdentry = d_alloc(parent, &sqstr);
@@ -661,11 +705,16 @@
const int countersize = sizeof(sillycounter)*2;
const int slen = strlen(".nfs") + i_inosize + countersize;
char silly[slen+1];
- int error;
struct dentry *sdentry;
+ int error = -EIO;
+ /*
+ * Note that a silly-renamed file can be deleted once it's
+ * no longer in use -- it's just an ordinary file now.
+ */
if (dentry->d_count == 1) {
- return -EIO; /* No need to silly rename. */
+ dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
+ goto out; /* No need to silly rename. */
}
#ifdef NFS_PARANOIA
@@ -673,12 +722,12 @@
printk("NFS: silly-renaming %s/%s, negative dentry??\n",
dentry->d_parent->d_name.name, dentry->d_name.name);
#endif
- if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
- return -EBUSY; /* don't allow to unlink silly inode -- nope,
- * think a bit: silly DENTRY, NOT inode --
- * itself
- */
- }
+ /*
+ * We don't allow a dentry to be silly-renamed twice.
+ */
+ error = -EBUSY;
+ if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+ goto out;
sprintf(silly, ".nfs%*.*lx",
i_inosize, i_inosize, dentry->d_inode->i_ino);
@@ -695,9 +744,12 @@
dentry->d_name.name, silly);
sdentry = nfs_silly_lookup(dentry->d_parent, silly, slen);
- if (IS_ERR(sdentry)) {
- return -EIO; /* FIXME ? */
- }
+ /*
+ * N.B. Better to return EBUSY here ... it could be
+ * dangerous to delete the file while it's in use.
+ */
+ if (IS_ERR(sdentry))
+ goto out;
} while(sdentry->d_inode != NULL); /* need negative lookup */
error = nfs_proc_rename(NFS_SERVER(dir),
@@ -711,58 +763,67 @@
/* If we return 0 we don't unlink */
}
dput(sdentry);
+out:
return error;
}
-static void nfs_silly_delete(struct dentry *dentry)
+/*
+ * Remove a file after making sure there are no pending writes,
+ * and after checking that the file has only one user.
+ *
+ * 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_safe_remove(struct dentry *dentry)
{
- if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
- struct inode *dir = dentry->d_parent->d_inode;
- int error;
+ struct inode *dir = dentry->d_parent->d_inode;
+ struct inode *inode = dentry->d_inode;
+ int error;
- dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
-
- /* Unhash it first */
- d_drop(dentry);
- dfprintk(VFS, "trying to unlink %s\n", dentry->d_name.name);
- error = nfs_proc_remove(NFS_SERVER(dir),
- NFS_FH(dir), dentry->d_name.name);
- if (error < 0)
- printk("NFS: can't silly-delete %s/%s, error=%d\n",
- dentry->d_parent->d_name.name,
- dentry->d_name.name, error);
- if (dentry->d_inode) {
- if (dentry->d_inode->i_nlink)
- dentry->d_inode->i_nlink --;
- } else {
+ error = -EBUSY;
+ if (inode) {
+ if (NFS_WRITEBACK(inode)) {
+ nfs_flush_dirty_pages(inode, 0, 0, 0);
+ if (NFS_WRITEBACK(inode)) {
#ifdef NFS_PARANOIA
- printk("nfs_silly_delete: negative dentry %s/%s\n",
- dentry->d_parent->d_name.name,
- dentry->d_name.name);
+printk("nfs_safe_remove: %s/%s writes pending, d_count=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
#endif
+ goto out;
+ }
}
- nfs_invalidate_dircache(dir);
+ } else {
+#ifdef NFS_PARANOIA
+printk("nfs_safe_remove: %s/%s already negative??\n",
+dentry->d_parent->d_name.name, dentry->d_name.name);
+#endif
}
- /*
- * Check whether to expire the dentry ...
- */
- else {
- unsigned long age = jiffies - dentry->d_time;
- if (age > 10*HZ)
- d_drop(dentry);
+
+ if (dentry->d_count > 1) {
+#ifdef NFS_PARANOIA
+printk("nfs_safe_remove: %s/%s busy, d_count=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
+#endif
+ goto out;
}
+ error = nfs_proc_remove(NFS_SERVER(dir),
+ NFS_FH(dir), dentry->d_name.name);
+ if (!error) {
+ nfs_invalidate_dircache(dir);
+ if (inode && inode->i_nlink)
+ inode->i_nlink --;
+ }
+out:
+ return error;
}
/* We do silly rename. In case sillyrename() returns -EBUSY, the inode
* belongs to an active ".nfs..." file and we return -EBUSY.
*
* If sillyrename() returns 0, we do nothing, otherwise we unlink.
- *
- * 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)
{
@@ -781,23 +842,9 @@
goto out;
error = nfs_sillyrename(dir, dentry);
-
if (error && error != -EBUSY) {
-#ifdef NFS_PARANOIA
-if (dentry->d_count > 1)
-printk("nfs_unlink: dentry %s/%s, d_count=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
-if (dentry->d_inode && dentry->d_inode->i_count > 1)
-printk("nfs_unlink: dentry %s/%s, inode i_count=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_inode->i_count);
-#endif
- /* N.B. should check for d_count > 1 and fail */
- error = nfs_proc_remove(NFS_SERVER(dir),
- NFS_FH(dir), dentry->d_name.name);
+ error = nfs_safe_remove(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);
}
@@ -868,31 +915,13 @@
if (dentry->d_name.len > NFS_MAXNAMLEN)
goto out;
- /*
- * The NFS server may want to use a new fileid for the link,
- * so we can't reuse the existing inode for the new dentry.
- * To force a new lookup after the link operation, we can just
- * drop the new dentry, as long as it's not busy. (See above.)
- */
- error = -EBUSY;
- if (dentry->d_count > 1) {
-#ifdef NFS_PARANOIA
-printk("nfs_link: dentry %s/%s busy, count=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
-#endif
- goto out;
- }
- d_drop(dentry);
-
error = nfs_proc_link(NFS_SERVER(inode), NFS_FH(inode), NFS_FH(dir),
dentry->d_name.name);
if (!error) {
nfs_invalidate_dircache(dir);
-#if 0
inode->i_count ++;
inode->i_nlink ++; /* no need to wait for nfs_refresh_inode() */
d_instantiate(dentry, inode);
-#endif
}
out:
return error;
@@ -916,23 +945,16 @@
* using the inode layer
*
* Unfortunately, things are a little more complicated than indicated
- * above. The NFS server may decide to use a new fileid for the renamed
- * file, so we can't link the new name to the old inode. Otherwise, the
- * server might reuse the fileid after the old file has been removed,
- * which would leave the new dentry holding an invalid fileid (possibly
- * leading to file corruption). To handle this consider these cases:
- * (1) within-directory:
- * -- no problem, just use nfs_proc_rename
- * (2) cross-directory, only one user for old and new dentry:
- * -- drop both dentries to force new lookups, then use rename
- * (3) cross-directory, multiple users for old, one user for new:
- * -- drop new dentry, silly-rename old dentry and make a link
- * (4) cross-directory, multiple users for new dentry:
- * -- sorry, we're busy.
+ * above. For a cross-directory move, we want to make sure we can get
+ * rid of the old inode after the operation. This means there must be
+ * no pending writes (if it's a file), and the use count must be 1.
+ * If these conditions are met, we can drop the dentries before doing
+ * the rename.
*/
static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
+ struct inode *inode = old_dentry->d_inode;
int update = 1, error;
#ifdef NFS_DEBUG_VERBOSE
@@ -955,60 +977,71 @@
new_dentry->d_name.len > NFS_MAXNAMLEN)
goto out;
/*
- * Examine the cases as noted above.
+ * Check for within-directory rename ... no complications.
*/
if (new_dir == old_dir)
- goto simple_case;
+ goto do_rename;
+ /*
+ * Cross-directory move ... check whether it's a file.
+ */
error = -EBUSY;
- if (new_dentry->d_count > 1) {
+ if (S_ISREG(inode->i_mode)) {
+ if (NFS_WRITEBACK(inode)) {
#ifdef NFS_PARANOIA
-printk("nfs_rename: new dentry %s/%s busy, count=%d\n",
-new_dentry->d_parent->d_name.name, new_dentry->d_name.name,
-new_dentry->d_count);
+printk("nfs_rename: %s/%s has pending writes\n",
+old_dentry->d_parent->d_name.name, old_dentry->d_name.name);
#endif
- goto out;
+ nfs_flush_dirty_pages(inode, 0, 0, 0);
+ if (NFS_WRITEBACK(inode)) {
+#ifdef NFS_PARANOIA
+printk("nfs_rename: %s/%s has pending writes after flush\n",
+old_dentry->d_parent->d_name.name, old_dentry->d_name.name);
+#endif
+ goto out;
+ }
+ }
}
- d_drop(new_dentry);
- if (old_dentry->d_count > 1)
- goto complex_case;
- d_drop(old_dentry);
- update = 0;
-
- /* no need for silly rename, proceed as usual */
-simple_case:
- 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)
- goto out;
- nfs_invalidate_dircache(new_dir);
- nfs_invalidate_dircache(old_dir);
-
- /* Update the dcache if needed */
- if (update)
- d_move(old_dentry, new_dentry);
- goto out;
+ /*
+ * Moving a directory ... prune child dentries if needed.
+ */
+ else if (old_dentry->d_count > 1)
+ shrink_dcache_sb(old_dentry->d_sb); /* Arghhh */
/*
- * We don't need to update the dcache in this case ... the
- * new dentry has been dropped, and the old one silly-renamed.
+ * Now check the use counts ... we can't safely do the
+ * rename unless we can drop the dentries first.
*/
-complex_case:
- error = nfs_sillyrename(old_dir, old_dentry);
- if (error)
+ if (old_dentry->d_count > 1) {
+#ifdef NFS_PARANOIA
+printk("nfs_rename: old dentry %s/%s busy, d_count=%d\n",
+old_dentry->d_parent->d_name.name,old_dentry->d_name.name,old_dentry->d_count);
+#endif
goto out;
- nfs_invalidate_dircache(old_dir);
+ }
- error = nfs_link(old_dentry->d_inode, new_dir, new_dentry);
- if (error)
- goto out;
- nfs_invalidate_dircache(new_dir);
+ if (new_dentry->d_count > 1) {
#ifdef NFS_PARANOIA
-printk("nfs_rename: dentry %s/%s linked to %s/%s, old count=%d\n",
-new_dentry->d_parent->d_name.name,new_dentry->d_name.name,
-old_dentry->d_parent->d_name.name,old_dentry->d_name.name,old_dentry->d_count);
+printk("nfs_rename: new dentry %s/%s busy, d_count=%d\n",
+new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
#endif
+ goto out;
+ }
+ d_drop(old_dentry);
+ d_drop(new_dentry);
+ update = 0;
+
+do_rename:
+ 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) {
+ nfs_invalidate_dircache(new_dir);
+ nfs_invalidate_dircache(old_dir);
+ /* Update the dcache if needed */
+ if (update)
+ 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