diff options
author | David Howells <dhowells@redhat.com> | 2019-04-25 15:26:51 +0200 |
---|---|---|
committer | David Howells <dhowells@redhat.com> | 2019-04-25 15:26:51 +0200 |
commit | 79ddbfa500b37a94fa7501e65ebdd5c0e4c7592d (patch) | |
tree | 1521d6e9276078aaf142ea40ab0f1e156c0fc276 /fs/afs/dir.c | |
parent | afs: Add directory reload tracepoint (diff) | |
download | linux-79ddbfa500b37a94fa7501e65ebdd5c0e4c7592d.tar.xz linux-79ddbfa500b37a94fa7501e65ebdd5c0e4c7592d.zip |
afs: Implement sillyrename for unlink and rename
Implement sillyrename for AFS unlink and rename, using the NFS variant
implementation as a basis.
Note that the asynchronous file locking extender/releaser has to be
notified with a state change to stop it complaining if there's a race
between that and the actual file deletion.
A tracepoint, afs_silly_rename, is also added to note the silly rename and
the cleanup. The afs_edit_dir tracepoint is given some extra reason
indicators and the afs_flock_ev tracepoint is given a silly-delete file
lock cancellation indicator.
Signed-off-by: David Howells <dhowells@redhat.com>
Diffstat (limited to 'fs/afs/dir.c')
-rw-r--r-- | fs/afs/dir.c | 116 |
1 files changed, 107 insertions, 9 deletions
diff --git a/fs/afs/dir.c b/fs/afs/dir.c index be5d2f932b77..6c8523501639 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -26,6 +26,7 @@ static int afs_dir_open(struct inode *inode, struct file *file); static int afs_readdir(struct file *file, struct dir_context *ctx); static int afs_d_revalidate(struct dentry *dentry, unsigned int flags); static int afs_d_delete(const struct dentry *dentry); +static void afs_d_iput(struct dentry *dentry, struct inode *inode); static int afs_lookup_one_filldir(struct dir_context *ctx, const char *name, int nlen, loff_t fpos, u64 ino, unsigned dtype); static int afs_lookup_filldir(struct dir_context *ctx, const char *name, int nlen, @@ -85,6 +86,7 @@ const struct dentry_operations afs_fs_dentry_operations = { .d_delete = afs_d_delete, .d_release = afs_d_release, .d_automount = afs_d_automount, + .d_iput = afs_d_iput, }; struct afs_lookup_one_cookie { @@ -1084,6 +1086,16 @@ zap: } /* + * Clean up sillyrename files on dentry removal. + */ +static void afs_d_iput(struct dentry *dentry, struct inode *inode) +{ + if (dentry->d_flags & DCACHE_NFSFS_RENAMED) + afs_silly_iput(dentry, inode); + iput(inode); +} + +/* * handle dentry release */ void afs_d_release(struct dentry *dentry) @@ -1225,6 +1237,12 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry) goto error_key; } + if (vnode) { + ret = down_write_killable(&vnode->rmdir_lock); + if (ret < 0) + goto error_key; + } + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, dvnode, key)) { while (afs_select_fileserver(&fc)) { @@ -1243,6 +1261,8 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry) } } + if (vnode) + up_write(&vnode->rmdir_lock); error_key: key_put(key); error: @@ -1259,9 +1279,9 @@ error: * However, if we didn't have a callback promise outstanding, or it was * outstanding on a different server, then it won't break it either... */ -static int afs_dir_remove_link(struct dentry *dentry, struct key *key, - unsigned long d_version_before, - unsigned long d_version_after) +int afs_dir_remove_link(struct dentry *dentry, struct key *key, + unsigned long d_version_before, + unsigned long d_version_after) { bool dir_valid; int ret = 0; @@ -1308,6 +1328,7 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) struct afs_vnode *dvnode = AFS_FS_I(dir), *vnode = NULL; struct key *key; unsigned long d_version = (unsigned long)dentry->d_fsdata; + bool need_rehash = false; u64 data_version = dvnode->status.data_version; int ret; @@ -1331,6 +1352,21 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) goto error_key; } + spin_lock(&dentry->d_lock); + if (vnode && d_count(dentry) > 1) { + spin_unlock(&dentry->d_lock); + /* Start asynchronous writeout of the inode */ + write_inode_now(d_inode(dentry), 0); + ret = afs_sillyrename(dvnode, vnode, dentry, key); + goto error_key; + } + if (!d_unhashed(dentry)) { + /* Prevent a race with RCU lookup. */ + __d_drop(dentry); + need_rehash = true; + } + spin_unlock(&dentry->d_lock); + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, dvnode, key)) { while (afs_select_fileserver(&fc)) { @@ -1362,6 +1398,9 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) afs_edit_dir_for_unlink); } + if (need_rehash && ret < 0 && ret != -ENOENT) + d_rehash(dentry); + error_key: key_put(key); error: @@ -1582,6 +1621,8 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, { struct afs_fs_cursor fc; struct afs_vnode *orig_dvnode, *new_dvnode, *vnode; + struct dentry *tmp = NULL, *rehash = NULL; + struct inode *new_inode; struct key *key; u64 orig_data_version, new_data_version; bool new_negative = d_is_negative(new_dentry); @@ -1590,6 +1631,10 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, if (flags) return -EINVAL; + /* Don't allow silly-rename files be moved around. */ + if (old_dentry->d_flags & DCACHE_NFSFS_RENAMED) + return -EINVAL; + vnode = AFS_FS_I(d_inode(old_dentry)); orig_dvnode = AFS_FS_I(old_dir); new_dvnode = AFS_FS_I(new_dir); @@ -1608,12 +1653,48 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, goto error; } + /* For non-directories, check whether the target is busy and if so, + * make a copy of the dentry and then do a silly-rename. If the + * silly-rename succeeds, the copied dentry is hashed and becomes the + * new target. + */ + if (d_is_positive(new_dentry) && !d_is_dir(new_dentry)) { + /* To prevent any new references to the target during the + * rename, we unhash the dentry in advance. + */ + if (!d_unhashed(new_dentry)) { + d_drop(new_dentry); + rehash = new_dentry; + } + + if (d_count(new_dentry) > 2) { + /* copy the target dentry's name */ + ret = -ENOMEM; + tmp = d_alloc(new_dentry->d_parent, + &new_dentry->d_name); + if (!tmp) + goto error_rehash; + + ret = afs_sillyrename(new_dvnode, + AFS_FS_I(d_inode(new_dentry)), + new_dentry, key); + if (ret) + goto error_rehash; + + new_dentry = tmp; + rehash = NULL; + new_negative = true; + orig_data_version = orig_dvnode->status.data_version; + new_data_version = new_dvnode->status.data_version; + } + } + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, orig_dvnode, key)) { if (orig_dvnode != new_dvnode) { if (mutex_lock_interruptible_nested(&new_dvnode->io_lock, 1) < 0) { afs_end_vnode_operation(&fc); - goto error_key; + goto error_rehash; } } while (afs_select_fileserver(&fc)) { @@ -1630,25 +1711,42 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, mutex_unlock(&new_dvnode->io_lock); ret = afs_end_vnode_operation(&fc); if (ret < 0) - goto error_key; + goto error_rehash; } if (ret == 0) { + if (rehash) + d_rehash(rehash); if (test_bit(AFS_VNODE_DIR_VALID, &orig_dvnode->flags)) afs_edit_dir_remove(orig_dvnode, &old_dentry->d_name, - afs_edit_dir_for_rename); + afs_edit_dir_for_rename_0); if (!new_negative && test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags)) afs_edit_dir_remove(new_dvnode, &new_dentry->d_name, - afs_edit_dir_for_rename); + afs_edit_dir_for_rename_1); if (test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags)) afs_edit_dir_add(new_dvnode, &new_dentry->d_name, - &vnode->fid, afs_edit_dir_for_rename); + &vnode->fid, afs_edit_dir_for_rename_2); + + new_inode = d_inode(new_dentry); + if (new_inode) { + spin_lock(&new_inode->i_lock); + if (new_inode->i_nlink > 0) + drop_nlink(new_inode); + spin_unlock(&new_inode->i_lock); + } + d_move(old_dentry, new_dentry); + goto error_tmp; } -error_key: +error_rehash: + if (rehash) + d_rehash(rehash); +error_tmp: + if (tmp) + dput(tmp); key_put(key); error: _leave(" = %d", ret); |