in turbonfs/src/rpc_task.cpp [2317:2555]
void rename_callback(
struct rpc_context *rpc,
int rpc_status,
void *data,
void *private_data)
{
rpc_task *task = (rpc_task*) private_data;
assert(task->magic == RPC_TASK_MAGIC);
assert(task->rpc_api->optype == FUSE_RENAME);
struct nfs_client *client = task->get_client();
const fuse_ino_t parent_ino = task->rpc_api->rename_task.get_parent_ino();
struct nfs_inode *parent_inode = client->get_nfs_inode_from_ino(parent_ino);
const fuse_ino_t newparent_ino = task->rpc_api->rename_task.get_newparent_ino();
struct nfs_inode *newparent_inode = client->get_nfs_inode_from_ino(newparent_ino);
const bool silly_rename = task->rpc_api->rename_task.get_silly_rename();
const bool rename_triggered_silly_rename =
task->rpc_api->rename_task.get_rename_triggered_silly_rename();
assert(!rename_triggered_silly_rename || silly_rename);
auto res = (RENAME3res*) data;
#if 0
/*
* Don't inject jukebox for non-idempotent requests.
*/
INJECT_JUKEBOX(res, task);
#endif
int status = task->status(rpc_status, NFS_STATUS(res));
const bool noent_is_success =
(rpc_pdu_is_retransmitted(rpc_get_pdu(rpc)) &&
aznfsc_cfg.sys.nodrc.rename_noent_as_success);
/*
* Now that the request has completed, we can query libnfs for the
* dispatch time.
*/
task->get_stats().on_rpc_complete(rpc_get_pdu(rpc), NFS_STATUSX(rpc_status, res));
/*
* If this rename is a silly rename for an unlink/rename operation, we need
* to store the directory inode and the renamed filename so that we can
* delete the silly renamed file when the last open count on this inode
* is dropped.
*
* Note: Silly rename is done in response to a user unlink call or user
* rename calls and VFS holds the inode lock for the duration of the
* unlink, which means we will not get any other call for this inode,
* so we can safely access the inode w/o lock.
*/
if (silly_rename) {
const fuse_ino_t silly_rename_ino =
task->rpc_api->rename_task.get_silly_rename_ino();
assert(client->magic == NFS_CLIENT_MAGIC);
struct nfs_inode *silly_rename_inode =
client->get_nfs_inode_from_ino(silly_rename_ino);
assert(silly_rename_inode->magic == NFS_INODE_MAGIC);
// Silly rename has the same source and target dir.
assert(parent_ino == newparent_ino);
/*
* For silly rename it's safe to assume NFS3ERR_NOENT as success,
* because we will only issue silly rename for an existing file, so
* if we get a NFS3ERR_NOENT failure and RPC was retransmitted, it's
* highly likely that rename succeeded.
*
* For a regular rename, it's more riskier as user can issue rename
* for a non-existent file, but it's more likely that they won't do
* that and also chances of RPC retransmit is very low and even if
* it happens we return success for a rename that didn't happen
* vs we would have returned failure for a rename that succeeded.
* We make a choice that's less likely to break applications.
*/
if (status == 0 ||
(NFS_STATUS(res) == NFS3ERR_NOENT && noent_is_success)) {
silly_rename_inode->silly_renamed_name =
task->rpc_api->rename_task.get_newname();
silly_rename_inode->parent_ino =
task->rpc_api->rename_task.get_newparent_ino();
silly_rename_inode->is_silly_renamed = true;
/*
* Successfully (silly)renamed, hold a ref on the parent directory
* inode so that it doesn't go away until we have deleted the
* silly-renamed file. This ref is dropped in unlink_callback().
*/
parent_inode->incref();
AZLogInfo("[{}] Silly rename ({}) {}! to-delete: {}/{}",
silly_rename_ino,
rename_triggered_silly_rename ? "rename" : "unlink",
status == 0 ? "successfully completed"
: "failed with NFS3ERR_NOENT and treated as success",
silly_rename_inode->parent_ino,
silly_rename_inode->silly_renamed_name);
#ifdef ENABLE_PARANOID
assert(silly_rename_inode->silly_renamed_name.find(".nfs") == 0);
#endif
}
/*
* Drop the opencnt that was incremented when silly rename was
* triggered. If this is the last opencnt on the file, i.e., application
* dropped its opencnt before silly rename could complete, it'll delete
* the silly renamed file.
*/
silly_rename_inode->release();
}
if (NFS_STATUS(res) == NFS3ERR_JUKEBOX) {
task->get_client()->jukebox_retry(task);
} else if (NFS_STATUS(res) == NFS3ERR_NOENT && noent_is_success) {
AZLogWarn("[{}/{} -> {}/{}] {}rename_callback{}: Treating NFS3ERR_NOENT "
"as success for a retransmitted RENAME RPC",
parent_ino,
task->rpc_api->rename_task.get_name(),
parent_ino,
task->rpc_api->rename_task.get_newname(),
silly_rename ? "(silly) " : "");
status = 0;
goto handle_success;
} else {
handle_success:
/*
* RENAME3res_u.resok and RENAME3res_u.resfail have the same layout,
* so we can safely access RENAME3res_u.resok for both success and
* failure cases.
*/
static_assert(sizeof(res->RENAME3res_u.resok) ==
sizeof(res->RENAME3res_u.resfail));
if (status == 0) {
if (!res->RENAME3res_u.resok.fromdir_wcc.after.attributes_follow) {
AZLogDebug("[{}] Postop attributes not received for rename, "
"invalidating old parent attribute cache", parent_ino);
/*
* Since the post-op attributes are not populated for the parent
* directory, invalidate the cache as the attributes may no longer
* be valid since rename() would have changed the parent directory
* attributes.
*/
parent_inode->invalidate_attribute_cache();
}
/*
* We cannot use UPDATE_INODE_WCC() here as we cannot update our
* readdir cache with the newly created file/dir, as the readdir
* cache also needs the cookie to be filled which only server can
* return.
* So we cause the cache to be invalidated.
*/
UPDATE_INODE_ATTR(parent_inode,
res->RENAME3res_u.resok.fromdir_wcc.after);
if (newparent_ino != parent_ino) {
if (!res->RENAME3res_u.resok.todir_wcc.after.attributes_follow) {
AZLogDebug("[{}] Postop attributes not received for rename, "
"invalidating new parent attribute cache", newparent_ino);
/*
* Since the post-op attributes are not populated for the parent
* directory, invalidate the cache as the attributes may no longer
* be valid since rename() would have changed the parent directory
* attributes.
*/
newparent_inode->invalidate_attribute_cache();
}
UPDATE_INODE_ATTR(newparent_inode,
res->RENAME3res_u.resok.todir_wcc.after);
}
}
if (!rename_triggered_silly_rename) {
/*
* rename_callback() would be called for the following rename
* calls:
* 1. rename requested by user.
* 2. silly rename for unlink requested by user.
* 3. silly rename for rename requested by user.
*
* For #1 and #2 we respond to fuse from here.
* For #3 we will need to issue the actual user requested rename,
* and we will respond to fuse when that complete.
*/
task->reply_error(status);
} else {
if (status != 0) {
AZLogError("Failed to silly rename {}/{} -> {}/{}, failing "
"user initiated rename of {}/{} -> {}/{}",
parent_ino,
task->rpc_api->rename_task.get_name(),
parent_ino,
task->rpc_api->rename_task.get_newname(),
task->rpc_api->rename_task.get_oldparent_ino(),
task->rpc_api->rename_task.get_oldname(),
parent_ino,
task->rpc_api->rename_task.get_name());
task->reply_error(status);
} else {
/*
* Now that silly rename of destination file is done, issue
* the original rename.
* Create a new task to carry out this request.
*/
AZLogInfo("Renaming file {}/{} -> {}/{} after successful silly "
"rename",
task->rpc_api->rename_task.get_oldparent_ino(),
task->rpc_api->rename_task.get_oldname(),
parent_ino,
task->rpc_api->rename_task.get_name());
struct rpc_task *rename_tsk =
task->get_client()->get_rpc_task_helper()->alloc_rpc_task_reserved(FUSE_RENAME);
rename_tsk->init_rename(
task->rpc_api->req,
task->rpc_api->rename_task.get_oldparent_ino(),
task->rpc_api->rename_task.get_oldname(),
parent_ino,
task->rpc_api->rename_task.get_name());
rename_tsk->run_rename();
/*
* The response will be sent by the actual rename call
* made above, hence free the current task.
*/
task->free_rpc_task();
}
}
}
}