void rename_callback()

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();
            }
        }
    }
}