Unverified Commit a19239ba authored by David Howells's avatar David Howells Committed by Christian Brauner
Browse files

afs: Add support for RENAME_NOREPLACE and RENAME_EXCHANGE



Add support for RENAME_NOREPLACE and RENAME_EXCHANGE, if the server
supports them.

The default is translated to YFS.Rename_Replace, falling back to
YFS.Rename; RENAME_NOREPLACE is translated to YFS.Rename_NoReplace and
RENAME_EXCHANGE to YFS.Rename_Exchange, both of which fall back to
reporting EINVAL.

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Link: https://lore.kernel.org/740476.1758718189@warthog.procyon.org.uk


cc: Marc Dionne <marc.dionne@auristor.com>
cc: Dan Carpenter <dan.carpenter@linaro.org>
cc: linux-afs@lists.infradead.org
Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent 8f5ae30d
Loading
Loading
Loading
Loading
+176 −47
Original line number Diff line number Diff line
@@ -1823,7 +1823,8 @@ static int afs_symlink(struct mnt_idmap *idmap, struct inode *dir,

static void afs_rename_success(struct afs_operation *op)
{
	struct afs_vnode *vnode = AFS_FS_I(d_inode(op->dentry));
	struct afs_vnode *vnode = op->more_files[0].vnode;
	struct afs_vnode *new_vnode = op->more_files[1].vnode;

	_enter("op=%08x", op->debug_id);

@@ -1834,12 +1835,16 @@ static void afs_rename_success(struct afs_operation *op)
		op->ctime = op->file[1].scb.status.mtime_client;
		afs_vnode_commit_status(op, &op->file[1]);
	}
	if (op->more_files[0].scb.have_status)
		afs_vnode_commit_status(op, &op->more_files[0]);
	if (op->more_files[1].scb.have_status)
		afs_vnode_commit_status(op, &op->more_files[1]);

	/* If we're moving a subdir between dirs, we need to update
	 * its DV counter too as the ".." will be altered.
	 */
	if (S_ISDIR(vnode->netfs.inode.i_mode) &&
	    op->file[0].vnode != op->file[1].vnode) {
	if (op->file[0].vnode != op->file[1].vnode) {
		if (S_ISDIR(vnode->netfs.inode.i_mode)) {
			u64 new_dv;

			write_seqlock(&vnode->cb_lock);
@@ -1851,6 +1856,20 @@ static void afs_rename_success(struct afs_operation *op)

			write_sequnlock(&vnode->cb_lock);
		}

		if ((op->rename.rename_flags & RENAME_EXCHANGE) &&
		    S_ISDIR(new_vnode->netfs.inode.i_mode)) {
			u64 new_dv;

			write_seqlock(&new_vnode->cb_lock);

			new_dv = new_vnode->status.data_version + 1;
			new_vnode->status.data_version = new_dv;
			inode_set_iversion_raw(&new_vnode->netfs.inode, new_dv);

			write_sequnlock(&new_vnode->cb_lock);
		}
	}
}

static void afs_rename_edit_dir(struct afs_operation *op)
@@ -1900,7 +1919,7 @@ static void afs_rename_edit_dir(struct afs_operation *op)
	if (S_ISDIR(vnode->netfs.inode.i_mode) &&
	    new_dvnode != orig_dvnode &&
	    test_bit(AFS_VNODE_DIR_VALID, &vnode->flags))
		afs_edit_dir_update_dotdot(vnode, new_dvnode,
		afs_edit_dir_update(vnode, &dotdot_name, new_dvnode,
				    afs_edit_dir_for_rename_sub);

	new_inode = d_inode(new_dentry);
@@ -1915,9 +1934,6 @@ static void afs_rename_edit_dir(struct afs_operation *op)

	/* Now we can update d_fsdata on the dentries to reflect their
	 * new parent's data_version.
	 *
	 * Note that if we ever implement RENAME_EXCHANGE, we'll have
	 * to update both dentries with opposing dir versions.
	 */
	afs_update_dentry_version(op, new_dvp, op->dentry);
	afs_update_dentry_version(op, new_dvp, op->dentry_2);
@@ -1930,6 +1946,67 @@ static void afs_rename_edit_dir(struct afs_operation *op)
		fscache_end_operation(&new_cres);
}

static void afs_rename_exchange_edit_dir(struct afs_operation *op)
{
	struct afs_vnode_param *orig_dvp = &op->file[0];
	struct afs_vnode_param *new_dvp = &op->file[1];
	struct afs_vnode *orig_dvnode = orig_dvp->vnode;
	struct afs_vnode *new_dvnode = new_dvp->vnode;
	struct afs_vnode *old_vnode = op->more_files[0].vnode;
	struct afs_vnode *new_vnode = op->more_files[1].vnode;
	struct dentry *old_dentry = op->dentry;
	struct dentry *new_dentry = op->dentry_2;

	_enter("op=%08x", op->debug_id);

	if (new_dvnode == orig_dvnode) {
		down_write(&orig_dvnode->validate_lock);
		if (test_bit(AFS_VNODE_DIR_VALID, &orig_dvnode->flags) &&
		    orig_dvnode->status.data_version == orig_dvp->dv_before + orig_dvp->dv_delta) {
			afs_edit_dir_update(orig_dvnode, &old_dentry->d_name,
					    new_vnode, afs_edit_dir_for_rename_0);
			afs_edit_dir_update(orig_dvnode, &new_dentry->d_name,
					    old_vnode, afs_edit_dir_for_rename_1);
		}

		d_exchange(old_dentry, new_dentry);
		up_write(&orig_dvnode->validate_lock);
	} else {
		down_write(&orig_dvnode->validate_lock);
		if (test_bit(AFS_VNODE_DIR_VALID, &orig_dvnode->flags) &&
		    orig_dvnode->status.data_version == orig_dvp->dv_before + orig_dvp->dv_delta)
			afs_edit_dir_update(orig_dvnode, &old_dentry->d_name,
					    new_vnode, afs_edit_dir_for_rename_0);

		up_write(&orig_dvnode->validate_lock);
		down_write(&new_dvnode->validate_lock);

		if (test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags) &&
		    new_dvnode->status.data_version == new_dvp->dv_before + new_dvp->dv_delta)
			afs_edit_dir_update(new_dvnode, &new_dentry->d_name,
					    old_vnode, afs_edit_dir_for_rename_1);

		if (S_ISDIR(old_vnode->netfs.inode.i_mode) &&
		    test_bit(AFS_VNODE_DIR_VALID, &old_vnode->flags))
			afs_edit_dir_update(old_vnode, &dotdot_name, new_dvnode,
					    afs_edit_dir_for_rename_sub);

		if (S_ISDIR(new_vnode->netfs.inode.i_mode) &&
		    test_bit(AFS_VNODE_DIR_VALID, &new_vnode->flags))
			afs_edit_dir_update(new_vnode, &dotdot_name, orig_dvnode,
					    afs_edit_dir_for_rename_sub);

		/* Now we can update d_fsdata on the dentries to reflect their
		 * new parents' data_version.
		 */
		afs_update_dentry_version(op, new_dvp, old_dentry);
		afs_update_dentry_version(op, orig_dvp, new_dentry);

		d_exchange(old_dentry, new_dentry);
		up_write(&new_dvnode->validate_lock);
	}
}

static void afs_rename_put(struct afs_operation *op)
{
	_enter("op=%08x", op->debug_id);
@@ -1948,6 +2025,32 @@ static const struct afs_operation_ops afs_rename_operation = {
	.put		= afs_rename_put,
};

#if 0 /* Autoswitched in yfs_fs_rename_replace(). */
static const struct afs_operation_ops afs_rename_replace_operation = {
	.issue_afs_rpc	= NULL,
	.issue_yfs_rpc	= yfs_fs_rename_replace,
	.success	= afs_rename_success,
	.edit_dir	= afs_rename_edit_dir,
	.put		= afs_rename_put,
};
#endif

static const struct afs_operation_ops afs_rename_noreplace_operation = {
	.issue_afs_rpc	= NULL,
	.issue_yfs_rpc	= yfs_fs_rename_noreplace,
	.success	= afs_rename_success,
	.edit_dir	= afs_rename_edit_dir,
	.put		= afs_rename_put,
};

static const struct afs_operation_ops afs_rename_exchange_operation = {
	.issue_afs_rpc	= NULL,
	.issue_yfs_rpc	= yfs_fs_rename_exchange,
	.success	= afs_rename_success,
	.edit_dir	= afs_rename_exchange_edit_dir,
	.put		= afs_rename_put,
};

/*
 * rename a file in an AFS filesystem and/or move it between directories
 */
@@ -1956,10 +2059,10 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
		      struct dentry *new_dentry, unsigned int flags)
{
	struct afs_operation *op;
	struct afs_vnode *orig_dvnode, *new_dvnode, *vnode;
	struct afs_vnode *orig_dvnode, *new_dvnode, *vnode, *new_vnode = NULL;
	int ret;

	if (flags)
	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
		return -EINVAL;

	/* Don't allow silly-rename files be moved around. */
@@ -1969,6 +2072,8 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
	vnode = AFS_FS_I(d_inode(old_dentry));
	orig_dvnode = AFS_FS_I(old_dir);
	new_dvnode = AFS_FS_I(new_dir);
	if (d_is_positive(new_dentry))
		new_vnode = AFS_FS_I(d_inode(new_dentry));

	_enter("{%llx:%llu},{%llx:%llu},{%llx:%llu},{%pd}",
	       orig_dvnode->fid.vid, orig_dvnode->fid.vnode,
@@ -1989,6 +2094,11 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
	if (ret < 0)
		goto error;

	ret = -ENOMEM;
	op->more_files = kvcalloc(2, sizeof(struct afs_vnode_param), GFP_KERNEL);
	if (!op->more_files)
		goto error;

	afs_op_set_vnode(op, 0, orig_dvnode);
	afs_op_set_vnode(op, 1, new_dvnode); /* May be same as orig_dvnode */
	op->file[0].dv_delta = 1;
@@ -1997,20 +2107,36 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
	op->file[1].modification = true;
	op->file[0].update_ctime = true;
	op->file[1].update_ctime = true;
	op->more_files[0].vnode		= vnode;
	op->more_files[0].speculative	= true;
	op->more_files[1].vnode		= new_vnode;
	op->more_files[1].speculative	= true;
	op->nr_files = 4;

	op->dentry		= old_dentry;
	op->dentry_2		= new_dentry;
	op->rename.rename_flags	= flags;
	op->rename.new_negative	= d_is_negative(new_dentry);

	if (flags & RENAME_NOREPLACE) {
		op->ops		= &afs_rename_noreplace_operation;
	} else if (flags & RENAME_EXCHANGE) {
		op->ops		= &afs_rename_exchange_operation;
		d_drop(new_dentry);
	} else {
		/* If we might displace the target, we might need to do silly
		 * rename.
		 */
		op->ops	= &afs_rename_operation;

	/* 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.
		/* 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.
			/* 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);
@@ -2039,6 +2165,7 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
				op->rename.new_negative = true;
			}
		}
	}

	/* This bit is potentially nasty as there's a potential race with
	 * afs_d_revalidate{,_rcu}().  We have to change d_fsdata on the dentry
@@ -2052,6 +2179,8 @@ static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
	d_drop(old_dentry);

	ret = afs_do_sync_operation(op);
	if (ret == -ENOTSUPP)
		ret = -EINVAL;
out:
	afs_dir_unuse_cookie(orig_dvnode, ret);
	if (new_dvnode != orig_dvnode)
+9 −9
Original line number Diff line number Diff line
@@ -522,11 +522,11 @@ void afs_edit_dir_remove(struct afs_vnode *vnode,
}

/*
 * Edit a subdirectory that has been moved between directories to update the
 * ".." entry.
 * Edit an entry in a directory to update the vnode it refers to.  This is also
 * used to update the ".." entry in a directory.
 */
void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_dvnode,
				enum afs_edit_dir_reason why)
void afs_edit_dir_update(struct afs_vnode *vnode, const struct qstr *name,
			 struct afs_vnode *new_dvnode, enum afs_edit_dir_reason why)
{
	union afs_xdr_dir_block *block;
	union afs_xdr_dirent *de;
@@ -557,7 +557,7 @@ void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_d
		if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags))
			goto already_invalidated;

		slot = afs_dir_scan_block(block, &dotdot_name, b);
		slot = afs_dir_scan_block(block, name, b);
		if (slot >= 0)
			goto found_dirent;

@@ -566,7 +566,7 @@ void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_d

	/* Didn't find the dirent to clobber.  Download the directory again. */
	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_nodd,
			   0, 0, 0, 0, "..");
			   0, 0, 0, 0, name->name);
	afs_invalidate_dir(vnode, afs_dir_invalid_edit_upd_no_dd);
	goto out;

@@ -576,7 +576,7 @@ void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_d
	de->u.unique = htonl(new_dvnode->fid.unique);

	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_dd, b, slot,
			   ntohl(de->u.vnode), ntohl(de->u.unique), "..");
			   ntohl(de->u.vnode), ntohl(de->u.unique), name->name);

	kunmap_local(block);
	netfs_single_mark_inode_dirty(&vnode->netfs.inode);
@@ -589,12 +589,12 @@ void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_d
already_invalidated:
	kunmap_local(block);
	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_inval,
			   0, 0, 0, 0, "..");
			   0, 0, 0, 0, name->name);
	goto out;

error:
	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_error,
			   0, 0, 0, 0, "..");
			   0, 0, 0, 0, name->name);
	goto out;
}

+11 −0
Original line number Diff line number Diff line
@@ -69,6 +69,12 @@ static int afs_do_silly_rename(struct afs_vnode *dvnode, struct afs_vnode *vnode
	if (IS_ERR(op))
		return PTR_ERR(op);

	op->more_files = kvcalloc(2, sizeof(struct afs_vnode_param), GFP_KERNEL);
	if (!op->more_files) {
		afs_put_operation(op);
		return -ENOMEM;
	}

	afs_op_set_vnode(op, 0, dvnode);
	afs_op_set_vnode(op, 1, dvnode);
	op->file[0].dv_delta = 1;
@@ -77,6 +83,11 @@ static int afs_do_silly_rename(struct afs_vnode *dvnode, struct afs_vnode *vnode
	op->file[1].modification = true;
	op->file[0].update_ctime = true;
	op->file[1].update_ctime = true;
	op->more_files[0].vnode		= AFS_FS_I(d_inode(old));
	op->more_files[0].speculative	= true;
	op->more_files[1].vnode		= AFS_FS_I(d_inode(new));
	op->more_files[1].speculative	= true;
	op->nr_files = 4;

	op->dentry		= old;
	op->dentry_2		= new;
+10 −5
Original line number Diff line number Diff line
@@ -562,6 +562,7 @@ struct afs_server {
#define AFS_SERVER_FL_NO_IBULK	17		/* Fileserver doesn't support FS.InlineBulkStatus */
#define AFS_SERVER_FL_NO_RM2	18		/* Fileserver doesn't support YFS.RemoveFile2 */
#define AFS_SERVER_FL_HAS_FS64	19		/* Fileserver supports FS.{Fetch,Store}Data64 */
#define AFS_SERVER_FL_NO_RENAME2 20		/* YFS Fileserver doesn't support enhanced rename */
	refcount_t		ref;		/* Object refcount */
	atomic_t		active;		/* Active user count */
	u32			addr_version;	/* Address list version */
@@ -893,6 +894,7 @@ struct afs_operation {
		struct {
			struct dentry	*rehash;
			struct dentry	*tmp;
			unsigned int	rename_flags;
			bool		new_negative;
		} rename;
		struct {
@@ -1100,8 +1102,8 @@ int afs_single_writepages(struct address_space *mapping,
extern void afs_edit_dir_add(struct afs_vnode *, struct qstr *, struct afs_fid *,
			     enum afs_edit_dir_reason);
extern void afs_edit_dir_remove(struct afs_vnode *, struct qstr *, enum afs_edit_dir_reason);
void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_dvnode,
				enum afs_edit_dir_reason why);
void afs_edit_dir_update(struct afs_vnode *vnode, const struct qstr *name,
			 struct afs_vnode *new_dvnode, enum afs_edit_dir_reason why);
void afs_mkdir_init_dir(struct afs_vnode *dvnode, struct afs_vnode *parent_vnode);

/*
@@ -1693,6 +1695,9 @@ extern void yfs_fs_remove_dir(struct afs_operation *);
extern void yfs_fs_link(struct afs_operation *);
extern void yfs_fs_symlink(struct afs_operation *);
extern void yfs_fs_rename(struct afs_operation *);
void yfs_fs_rename_replace(struct afs_operation *op);
void yfs_fs_rename_noreplace(struct afs_operation *op);
void yfs_fs_rename_exchange(struct afs_operation *op);
extern void yfs_fs_store_data(struct afs_operation *);
extern void yfs_fs_setattr(struct afs_operation *);
extern void yfs_fs_get_volume_status(struct afs_operation *);
+1 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ int afs_abort_to_error(u32 abort_code)
	case KRB5_PROG_KEYTYPE_NOSUPP:	return -ENOPKG;

	case RXGEN_OPCODE:	return -ENOTSUPP;
	case RX_INVALID_OPERATION:	return -ENOTSUPP;

	default:		return -EREMOTEIO;
	}
Loading