Merge tag 'for-v3.13/clock-fixes-a' of git://git.kernel.org/pub/scm/linux/kernel...
[cascardo/linux.git] / fs / nfs / nfs4proc.c
index d53d678..5ab33c0 100644 (file)
@@ -105,9 +105,6 @@ nfs4_label_init_security(struct inode *dir, struct dentry *dentry,
        if (nfs_server_capable(dir, NFS_CAP_SECURITY_LABEL) == 0)
                return NULL;
 
-       if (NFS_SERVER(dir)->nfs_client->cl_minorversion < 2)
-               return NULL;
-
        err = security_dentry_init_security(dentry, sattr->ia_mode,
                                &dentry->d_name, (void **)&label->label, &label->len);
        if (err == 0)
@@ -384,6 +381,14 @@ static int nfs4_handle_exception(struct nfs_server *server, int errorcode, struc
                case -NFS4ERR_STALE_CLIENTID:
                        nfs4_schedule_lease_recovery(clp);
                        goto wait_on_recovery;
+               case -NFS4ERR_MOVED:
+                       ret = nfs4_schedule_migration_recovery(server);
+                       if (ret < 0)
+                               break;
+                       goto wait_on_recovery;
+               case -NFS4ERR_LEASE_MOVED:
+                       nfs4_schedule_lease_moved_recovery(clp);
+                       goto wait_on_recovery;
 #if defined(CONFIG_NFS_V4_1)
                case -NFS4ERR_BADSESSION:
                case -NFS4ERR_BADSLOT:
@@ -431,6 +436,8 @@ static int nfs4_handle_exception(struct nfs_server *server, int errorcode, struc
        return nfs4_map_errors(ret);
 wait_on_recovery:
        ret = nfs4_wait_clnt_recover(clp);
+       if (test_bit(NFS_MIG_FAILED, &server->mig_status))
+               return -EIO;
        if (ret == 0)
                exception->retry = 1;
        return ret;
@@ -1318,31 +1325,24 @@ _nfs4_opendata_reclaim_to_nfs4_state(struct nfs4_opendata *data)
        int ret;
 
        if (!data->rpc_done) {
-               ret = data->rpc_status;
-               goto err;
+               if (data->rpc_status) {
+                       ret = data->rpc_status;
+                       goto err;
+               }
+               /* cached opens have already been processed */
+               goto update;
        }
 
-       ret = -ESTALE;
-       if (!(data->f_attr.valid & NFS_ATTR_FATTR_TYPE) ||
-           !(data->f_attr.valid & NFS_ATTR_FATTR_FILEID) ||
-           !(data->f_attr.valid & NFS_ATTR_FATTR_CHANGE))
-               goto err;
-
-       ret = -ENOMEM;
-       state = nfs4_get_open_state(inode, data->owner);
-       if (state == NULL)
-               goto err;
-
        ret = nfs_refresh_inode(inode, &data->f_attr);
        if (ret)
                goto err;
 
-       nfs_setsecurity(inode, &data->f_attr, data->f_label);
-
        if (data->o_res.delegation_type != 0)
                nfs4_opendata_check_deleg(data, state);
+update:
        update_open_stateid(state, &data->o_res.stateid, NULL,
                            data->o_arg.fmode);
+       atomic_inc(&state->count);
 
        return state;
 err:
@@ -1575,6 +1575,12 @@ static int nfs4_handle_delegation_recall_error(struct nfs_server *server, struct
                        /* Don't recall a delegation if it was lost */
                        nfs4_schedule_lease_recovery(server->nfs_client);
                        return -EAGAIN;
+               case -NFS4ERR_MOVED:
+                       nfs4_schedule_migration_recovery(server);
+                       return -EAGAIN;
+               case -NFS4ERR_LEASE_MOVED:
+                       nfs4_schedule_lease_moved_recovery(server->nfs_client);
+                       return -EAGAIN;
                case -NFS4ERR_DELEG_REVOKED:
                case -NFS4ERR_ADMIN_REVOKED:
                case -NFS4ERR_BAD_STATEID:
@@ -2697,6 +2703,10 @@ static void nfs4_close_context(struct nfs_open_context *ctx, int is_sync)
                nfs4_close_state(ctx->state, ctx->mode);
 }
 
+#define FATTR4_WORD1_NFS40_MASK (2*FATTR4_WORD1_MOUNTED_ON_FILEID - 1UL)
+#define FATTR4_WORD2_NFS41_MASK (2*FATTR4_WORD2_SUPPATTR_EXCLCREAT - 1UL)
+#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_CHANGE_SECURITY_LABEL - 1UL)
+
 static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)
 {
        struct nfs4_server_caps_arg args = {
@@ -2712,12 +2722,25 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
 
        status = nfs4_call_sync(server->client, server, &msg, &args.seq_args, &res.seq_res, 0);
        if (status == 0) {
+               /* Sanity check the server answers */
+               switch (server->nfs_client->cl_minorversion) {
+               case 0:
+                       res.attr_bitmask[1] &= FATTR4_WORD1_NFS40_MASK;
+                       res.attr_bitmask[2] = 0;
+                       break;
+               case 1:
+                       res.attr_bitmask[2] &= FATTR4_WORD2_NFS41_MASK;
+                       break;
+               case 2:
+                       res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
+               }
                memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
                server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
                                NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
                                NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
                                NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
-                               NFS_CAP_CTIME|NFS_CAP_MTIME);
+                               NFS_CAP_CTIME|NFS_CAP_MTIME|
+                               NFS_CAP_SECURITY_LABEL);
                if (res.attr_bitmask[0] & FATTR4_WORD0_ACL)
                        server->caps |= NFS_CAP_ACLS;
                if (res.has_links != 0)
@@ -2746,14 +2769,12 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
 #endif
                memcpy(server->attr_bitmask_nl, res.attr_bitmask,
                                sizeof(server->attr_bitmask));
+               server->attr_bitmask_nl[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
 
-               if (server->caps & NFS_CAP_SECURITY_LABEL) {
-                       server->attr_bitmask_nl[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
-                       res.attr_bitmask[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
-               }
                memcpy(server->cache_consistency_bitmask, res.attr_bitmask, sizeof(server->cache_consistency_bitmask));
                server->cache_consistency_bitmask[0] &= FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE;
                server->cache_consistency_bitmask[1] &= FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY;
+               server->cache_consistency_bitmask[2] = 0;
                server->acl_bitmask = res.acl_bitmask;
                server->fh_expire_type = res.fh_expire_type;
        }
@@ -2864,11 +2885,24 @@ static int nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
        int status = -EPERM;
        size_t i;
 
-       for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
-               status = nfs4_lookup_root_sec(server, fhandle, info, flav_array[i]);
-               if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
-                       continue;
-               break;
+       if (server->auth_info.flavor_len > 0) {
+               /* try each flavor specified by user */
+               for (i = 0; i < server->auth_info.flavor_len; i++) {
+                       status = nfs4_lookup_root_sec(server, fhandle, info,
+                                               server->auth_info.flavors[i]);
+                       if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
+                               continue;
+                       break;
+               }
+       } else {
+               /* no flavors specified by user, try default list */
+               for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
+                       status = nfs4_lookup_root_sec(server, fhandle, info,
+                                                     flav_array[i]);
+                       if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
+                               continue;
+                       break;
+               }
        }
 
        /*
@@ -2910,9 +2944,6 @@ int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle,
                status = nfs4_lookup_root(server, fhandle, info);
                if (status != -NFS4ERR_WRONGSEC)
                        break;
-               /* Did user force a 'sec=' mount option? */
-               if (server->flags & NFS_MOUNT_SECFLAVOUR)
-                       break;
        default:
                status = nfs4_do_find_root_sec(server, fhandle, info);
        }
@@ -2981,11 +3012,16 @@ static int nfs4_get_referral(struct rpc_clnt *client, struct inode *dir,
        status = nfs4_proc_fs_locations(client, dir, name, locations, page);
        if (status != 0)
                goto out;
-       /* Make sure server returned a different fsid for the referral */
+
+       /*
+        * If the fsid didn't change, this is a migration event, not a
+        * referral.  Cause us to drop into the exception handler, which
+        * will kick off migration recovery.
+        */
        if (nfs_fsid_equal(&NFS_SERVER(dir)->fsid, &locations->fattr.fsid)) {
                dprintk("%s: server did not return a different fsid for"
                        " a referral at %s\n", __func__, name->name);
-               status = -EIO;
+               status = -NFS4ERR_MOVED;
                goto out;
        }
        /* Fixup attributes for the nfs_lookup() call to nfs_fhget() */
@@ -3165,9 +3201,6 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
                        err = -EPERM;
                        if (client != *clnt)
                                goto out;
-                       /* No security negotiation if the user specified 'sec=' */
-                       if (NFS_SERVER(dir)->flags & NFS_MOUNT_SECFLAVOUR)
-                               goto out;
                        client = nfs4_create_sec_client(client, dir, name);
                        if (IS_ERR(client))
                                return PTR_ERR(client);
@@ -4221,7 +4254,13 @@ static void nfs4_renew_done(struct rpc_task *task, void *calldata)
        unsigned long timestamp = data->timestamp;
 
        trace_nfs4_renew_async(clp, task->tk_status);
-       if (task->tk_status < 0) {
+       switch (task->tk_status) {
+       case 0:
+               break;
+       case -NFS4ERR_LEASE_MOVED:
+               nfs4_schedule_lease_moved_recovery(clp);
+               break;
+       default:
                /* Unless we're shutting down, schedule state recovery! */
                if (test_bit(NFS_CS_RENEWD, &clp->cl_res_state) == 0)
                        return;
@@ -4575,7 +4614,7 @@ static int _nfs4_get_security_label(struct inode *inode, void *buf,
        struct nfs4_label label = {0, 0, buflen, buf};
 
        u32 bitmask[3] = { 0, 0, FATTR4_WORD2_SECURITY_LABEL };
-       struct nfs4_getattr_arg args = {
+       struct nfs4_getattr_arg arg = {
                .fh             = NFS_FH(inode),
                .bitmask        = bitmask,
        };
@@ -4586,14 +4625,14 @@ static int _nfs4_get_security_label(struct inode *inode, void *buf,
        };
        struct rpc_message msg = {
                .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_GETATTR],
-               .rpc_argp       = &args,
+               .rpc_argp       = &arg,
                .rpc_resp       = &res,
        };
        int ret;
 
        nfs_fattr_init(&fattr);
 
-       ret = rpc_call_sync(server->client, &msg, 0);
+       ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 0);
        if (ret)
                return ret;
        if (!(fattr.valid & NFS_ATTR_FATTR_V4_SECURITY_LABEL))
@@ -4630,7 +4669,7 @@ static int _nfs4_do_set_security_label(struct inode *inode,
        struct iattr sattr = {0};
        struct nfs_server *server = NFS_SERVER(inode);
        const u32 bitmask[3] = { 0, 0, FATTR4_WORD2_SECURITY_LABEL };
-       struct nfs_setattrargs args = {
+       struct nfs_setattrargs arg = {
                .fh             = NFS_FH(inode),
                .iap            = &sattr,
                .server         = server,
@@ -4644,14 +4683,14 @@ static int _nfs4_do_set_security_label(struct inode *inode,
        };
        struct rpc_message msg = {
                .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
-               .rpc_argp       = &args,
+               .rpc_argp       = &arg,
                .rpc_resp       = &res,
        };
        int status;
 
-       nfs4_stateid_copy(&args.stateid, &zero_stateid);
+       nfs4_stateid_copy(&arg.stateid, &zero_stateid);
 
-       status = rpc_call_sync(server->client, &msg, 0);
+       status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
        if (status)
                dprintk("%s failed: %d\n", __func__, status);
 
@@ -4735,17 +4774,24 @@ nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server,
                        if (state == NULL)
                                break;
                        if (nfs4_schedule_stateid_recovery(server, state) < 0)
-                               goto stateid_invalid;
+                               goto recovery_failed;
                        goto wait_on_recovery;
                case -NFS4ERR_EXPIRED:
                        if (state != NULL) {
                                if (nfs4_schedule_stateid_recovery(server, state) < 0)
-                                       goto stateid_invalid;
+                                       goto recovery_failed;
                        }
                case -NFS4ERR_STALE_STATEID:
                case -NFS4ERR_STALE_CLIENTID:
                        nfs4_schedule_lease_recovery(clp);
                        goto wait_on_recovery;
+               case -NFS4ERR_MOVED:
+                       if (nfs4_schedule_migration_recovery(server) < 0)
+                               goto recovery_failed;
+                       goto wait_on_recovery;
+               case -NFS4ERR_LEASE_MOVED:
+                       nfs4_schedule_lease_moved_recovery(clp);
+                       goto wait_on_recovery;
 #if defined(CONFIG_NFS_V4_1)
                case -NFS4ERR_BADSESSION:
                case -NFS4ERR_BADSLOT:
@@ -4757,29 +4803,28 @@ nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server,
                        dprintk("%s ERROR %d, Reset session\n", __func__,
                                task->tk_status);
                        nfs4_schedule_session_recovery(clp->cl_session, task->tk_status);
-                       task->tk_status = 0;
-                       return -EAGAIN;
+                       goto restart_call;
 #endif /* CONFIG_NFS_V4_1 */
                case -NFS4ERR_DELAY:
                        nfs_inc_server_stats(server, NFSIOS_DELAY);
                case -NFS4ERR_GRACE:
                        rpc_delay(task, NFS4_POLL_RETRY_MAX);
-                       task->tk_status = 0;
-                       return -EAGAIN;
                case -NFS4ERR_RETRY_UNCACHED_REP:
                case -NFS4ERR_OLD_STATEID:
-                       task->tk_status = 0;
-                       return -EAGAIN;
+                       goto restart_call;
        }
        task->tk_status = nfs4_map_errors(task->tk_status);
        return 0;
-stateid_invalid:
+recovery_failed:
        task->tk_status = -EIO;
        return 0;
 wait_on_recovery:
        rpc_sleep_on(&clp->cl_rpcwaitq, task, NULL);
        if (test_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state) == 0)
                rpc_wake_up_queued_task(&clp->cl_rpcwaitq, task);
+       if (test_bit(NFS_MIG_FAILED, &server->mig_status))
+               goto recovery_failed;
+restart_call:
        task->tk_status = 0;
        return -EAGAIN;
 }
@@ -5106,6 +5151,7 @@ static int _nfs4_proc_getlk(struct nfs4_state *state, int cmd, struct file_lock
                        status = 0;
        }
        request->fl_ops->fl_release_private(request);
+       request->fl_ops = NULL;
 out:
        return status;
 }
@@ -5779,6 +5825,7 @@ struct nfs_release_lockowner_data {
        struct nfs_release_lockowner_args args;
        struct nfs4_sequence_args seq_args;
        struct nfs4_sequence_res seq_res;
+       unsigned long timestamp;
 };
 
 static void nfs4_release_lockowner_prepare(struct rpc_task *task, void *calldata)
@@ -5786,12 +5833,27 @@ static void nfs4_release_lockowner_prepare(struct rpc_task *task, void *calldata
        struct nfs_release_lockowner_data *data = calldata;
        nfs40_setup_sequence(data->server,
                                &data->seq_args, &data->seq_res, task);
+       data->timestamp = jiffies;
 }
 
 static void nfs4_release_lockowner_done(struct rpc_task *task, void *calldata)
 {
        struct nfs_release_lockowner_data *data = calldata;
+       struct nfs_server *server = data->server;
+
        nfs40_sequence_done(task, &data->seq_res);
+
+       switch (task->tk_status) {
+       case 0:
+               renew_lease(server, data->timestamp);
+               break;
+       case -NFS4ERR_STALE_CLIENTID:
+       case -NFS4ERR_EXPIRED:
+       case -NFS4ERR_LEASE_MOVED:
+       case -NFS4ERR_DELAY:
+               if (nfs4_async_handle_error(task, server, NULL) == -EAGAIN)
+                       rpc_restart_call_prepare(task);
+       }
 }
 
 static void nfs4_release_lockowner_release(void *calldata)
@@ -5990,6 +6052,283 @@ int nfs4_proc_fs_locations(struct rpc_clnt *client, struct inode *dir,
        return err;
 }
 
+/*
+ * This operation also signals the server that this client is
+ * performing migration recovery.  The server can stop returning
+ * NFS4ERR_LEASE_MOVED to this client.  A RENEW operation is
+ * appended to this compound to identify the client ID which is
+ * performing recovery.
+ */
+static int _nfs40_proc_get_locations(struct inode *inode,
+                                    struct nfs4_fs_locations *locations,
+                                    struct page *page, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct rpc_clnt *clnt = server->client;
+       u32 bitmask[2] = {
+               [0] = FATTR4_WORD0_FSID | FATTR4_WORD0_FS_LOCATIONS,
+       };
+       struct nfs4_fs_locations_arg args = {
+               .clientid       = server->nfs_client->cl_clientid,
+               .fh             = NFS_FH(inode),
+               .page           = page,
+               .bitmask        = bitmask,
+               .migration      = 1,            /* skip LOOKUP */
+               .renew          = 1,            /* append RENEW */
+       };
+       struct nfs4_fs_locations_res res = {
+               .fs_locations   = locations,
+               .migration      = 1,
+               .renew          = 1,
+       };
+       struct rpc_message msg = {
+               .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_FS_LOCATIONS],
+               .rpc_argp       = &args,
+               .rpc_resp       = &res,
+               .rpc_cred       = cred,
+       };
+       unsigned long now = jiffies;
+       int status;
+
+       nfs_fattr_init(&locations->fattr);
+       locations->server = server;
+       locations->nlocations = 0;
+
+       nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
+       nfs4_set_sequence_privileged(&args.seq_args);
+       status = nfs4_call_sync_sequence(clnt, server, &msg,
+                                       &args.seq_args, &res.seq_res);
+       if (status)
+               return status;
+
+       renew_lease(server, now);
+       return 0;
+}
+
+#ifdef CONFIG_NFS_V4_1
+
+/*
+ * This operation also signals the server that this client is
+ * performing migration recovery.  The server can stop asserting
+ * SEQ4_STATUS_LEASE_MOVED for this client.  The client ID
+ * performing this operation is identified in the SEQUENCE
+ * operation in this compound.
+ *
+ * When the client supports GETATTR(fs_locations_info), it can
+ * be plumbed in here.
+ */
+static int _nfs41_proc_get_locations(struct inode *inode,
+                                    struct nfs4_fs_locations *locations,
+                                    struct page *page, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct rpc_clnt *clnt = server->client;
+       u32 bitmask[2] = {
+               [0] = FATTR4_WORD0_FSID | FATTR4_WORD0_FS_LOCATIONS,
+       };
+       struct nfs4_fs_locations_arg args = {
+               .fh             = NFS_FH(inode),
+               .page           = page,
+               .bitmask        = bitmask,
+               .migration      = 1,            /* skip LOOKUP */
+       };
+       struct nfs4_fs_locations_res res = {
+               .fs_locations   = locations,
+               .migration      = 1,
+       };
+       struct rpc_message msg = {
+               .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_FS_LOCATIONS],
+               .rpc_argp       = &args,
+               .rpc_resp       = &res,
+               .rpc_cred       = cred,
+       };
+       int status;
+
+       nfs_fattr_init(&locations->fattr);
+       locations->server = server;
+       locations->nlocations = 0;
+
+       nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
+       nfs4_set_sequence_privileged(&args.seq_args);
+       status = nfs4_call_sync_sequence(clnt, server, &msg,
+                                       &args.seq_args, &res.seq_res);
+       if (status == NFS4_OK &&
+           res.seq_res.sr_status_flags & SEQ4_STATUS_LEASE_MOVED)
+               status = -NFS4ERR_LEASE_MOVED;
+       return status;
+}
+
+#endif /* CONFIG_NFS_V4_1 */
+
+/**
+ * nfs4_proc_get_locations - discover locations for a migrated FSID
+ * @inode: inode on FSID that is migrating
+ * @locations: result of query
+ * @page: buffer
+ * @cred: credential to use for this operation
+ *
+ * Returns NFS4_OK on success, a negative NFS4ERR status code if the
+ * operation failed, or a negative errno if a local error occurred.
+ *
+ * On success, "locations" is filled in, but if the server has
+ * no locations information, NFS_ATTR_FATTR_V4_LOCATIONS is not
+ * asserted.
+ *
+ * -NFS4ERR_LEASE_MOVED is returned if the server still has leases
+ * from this client that require migration recovery.
+ */
+int nfs4_proc_get_locations(struct inode *inode,
+                           struct nfs4_fs_locations *locations,
+                           struct page *page, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct nfs_client *clp = server->nfs_client;
+       const struct nfs4_mig_recovery_ops *ops =
+                                       clp->cl_mvops->mig_recovery_ops;
+       struct nfs4_exception exception = { };
+       int status;
+
+       dprintk("%s: FSID %llx:%llx on \"%s\"\n", __func__,
+               (unsigned long long)server->fsid.major,
+               (unsigned long long)server->fsid.minor,
+               clp->cl_hostname);
+       nfs_display_fhandle(NFS_FH(inode), __func__);
+
+       do {
+               status = ops->get_locations(inode, locations, page, cred);
+               if (status != -NFS4ERR_DELAY)
+                       break;
+               nfs4_handle_exception(server, status, &exception);
+       } while (exception.retry);
+       return status;
+}
+
+/*
+ * This operation also signals the server that this client is
+ * performing "lease moved" recovery.  The server can stop
+ * returning NFS4ERR_LEASE_MOVED to this client.  A RENEW operation
+ * is appended to this compound to identify the client ID which is
+ * performing recovery.
+ */
+static int _nfs40_proc_fsid_present(struct inode *inode, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
+       struct rpc_clnt *clnt = server->client;
+       struct nfs4_fsid_present_arg args = {
+               .fh             = NFS_FH(inode),
+               .clientid       = clp->cl_clientid,
+               .renew          = 1,            /* append RENEW */
+       };
+       struct nfs4_fsid_present_res res = {
+               .renew          = 1,
+       };
+       struct rpc_message msg = {
+               .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_FSID_PRESENT],
+               .rpc_argp       = &args,
+               .rpc_resp       = &res,
+               .rpc_cred       = cred,
+       };
+       unsigned long now = jiffies;
+       int status;
+
+       res.fh = nfs_alloc_fhandle();
+       if (res.fh == NULL)
+               return -ENOMEM;
+
+       nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
+       nfs4_set_sequence_privileged(&args.seq_args);
+       status = nfs4_call_sync_sequence(clnt, server, &msg,
+                                               &args.seq_args, &res.seq_res);
+       nfs_free_fhandle(res.fh);
+       if (status)
+               return status;
+
+       do_renew_lease(clp, now);
+       return 0;
+}
+
+#ifdef CONFIG_NFS_V4_1
+
+/*
+ * This operation also signals the server that this client is
+ * performing "lease moved" recovery.  The server can stop asserting
+ * SEQ4_STATUS_LEASE_MOVED for this client.  The client ID performing
+ * this operation is identified in the SEQUENCE operation in this
+ * compound.
+ */
+static int _nfs41_proc_fsid_present(struct inode *inode, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct rpc_clnt *clnt = server->client;
+       struct nfs4_fsid_present_arg args = {
+               .fh             = NFS_FH(inode),
+       };
+       struct nfs4_fsid_present_res res = {
+       };
+       struct rpc_message msg = {
+               .rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_FSID_PRESENT],
+               .rpc_argp       = &args,
+               .rpc_resp       = &res,
+               .rpc_cred       = cred,
+       };
+       int status;
+
+       res.fh = nfs_alloc_fhandle();
+       if (res.fh == NULL)
+               return -ENOMEM;
+
+       nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
+       nfs4_set_sequence_privileged(&args.seq_args);
+       status = nfs4_call_sync_sequence(clnt, server, &msg,
+                                               &args.seq_args, &res.seq_res);
+       nfs_free_fhandle(res.fh);
+       if (status == NFS4_OK &&
+           res.seq_res.sr_status_flags & SEQ4_STATUS_LEASE_MOVED)
+               status = -NFS4ERR_LEASE_MOVED;
+       return status;
+}
+
+#endif /* CONFIG_NFS_V4_1 */
+
+/**
+ * nfs4_proc_fsid_present - Is this FSID present or absent on server?
+ * @inode: inode on FSID to check
+ * @cred: credential to use for this operation
+ *
+ * Server indicates whether the FSID is present, moved, or not
+ * recognized.  This operation is necessary to clear a LEASE_MOVED
+ * condition for this client ID.
+ *
+ * Returns NFS4_OK if the FSID is present on this server,
+ * -NFS4ERR_MOVED if the FSID is no longer present, a negative
+ *  NFS4ERR code if some error occurred on the server, or a
+ *  negative errno if a local failure occurred.
+ */
+int nfs4_proc_fsid_present(struct inode *inode, struct rpc_cred *cred)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       struct nfs_client *clp = server->nfs_client;
+       const struct nfs4_mig_recovery_ops *ops =
+                                       clp->cl_mvops->mig_recovery_ops;
+       struct nfs4_exception exception = { };
+       int status;
+
+       dprintk("%s: FSID %llx:%llx on \"%s\"\n", __func__,
+               (unsigned long long)server->fsid.major,
+               (unsigned long long)server->fsid.minor,
+               clp->cl_hostname);
+       nfs_display_fhandle(NFS_FH(inode), __func__);
+
+       do {
+               status = ops->fsid_present(inode, cred);
+               if (status != -NFS4ERR_DELAY)
+                       break;
+               nfs4_handle_exception(server, status, &exception);
+       } while (exception.retry);
+       return status;
+}
+
 /**
  * If 'use_integrity' is true and the state managment nfs_client
  * cl_rpcclient is using krb5i/p, use the integrity protected cl_rpcclient
@@ -6276,8 +6615,14 @@ static int _nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred,
        struct nfs41_exchange_id_args args = {
                .verifier = &verifier,
                .client = clp,
+#ifdef CONFIG_NFS_V4_1_MIGRATION
                .flags = EXCHGID4_FLAG_SUPP_MOVED_REFER |
-                       EXCHGID4_FLAG_BIND_PRINC_STATEID,
+                        EXCHGID4_FLAG_BIND_PRINC_STATEID |
+                        EXCHGID4_FLAG_SUPP_MOVED_MIGR,
+#else
+               .flags = EXCHGID4_FLAG_SUPP_MOVED_REFER |
+                        EXCHGID4_FLAG_BIND_PRINC_STATEID,
+#endif
        };
        struct nfs41_exchange_id_res res = {
                0
@@ -7616,6 +7961,9 @@ nfs41_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
                        break;
                }
 
+               if (!nfs_auth_info_match(&server->auth_info, flavor))
+                       flavor = RPC_AUTH_MAXFLAVOR;
+
                if (flavor != RPC_AUTH_MAXFLAVOR) {
                        err = nfs4_lookup_root_sec(server, fhandle,
                                                   info, flavor);
@@ -7887,6 +8235,18 @@ static const struct nfs4_state_maintenance_ops nfs41_state_renewal_ops = {
 };
 #endif
 
+static const struct nfs4_mig_recovery_ops nfs40_mig_recovery_ops = {
+       .get_locations = _nfs40_proc_get_locations,
+       .fsid_present = _nfs40_proc_fsid_present,
+};
+
+#if defined(CONFIG_NFS_V4_1)
+static const struct nfs4_mig_recovery_ops nfs41_mig_recovery_ops = {
+       .get_locations = _nfs41_proc_get_locations,
+       .fsid_present = _nfs41_proc_fsid_present,
+};
+#endif /* CONFIG_NFS_V4_1 */
+
 static const struct nfs4_minor_version_ops nfs_v4_0_minor_ops = {
        .minor_version = 0,
        .init_caps = NFS_CAP_READDIRPLUS
@@ -7902,6 +8262,7 @@ static const struct nfs4_minor_version_ops nfs_v4_0_minor_ops = {
        .reboot_recovery_ops = &nfs40_reboot_recovery_ops,
        .nograce_recovery_ops = &nfs40_nograce_recovery_ops,
        .state_renewal_ops = &nfs40_state_renewal_ops,
+       .mig_recovery_ops = &nfs40_mig_recovery_ops,
 };
 
 #if defined(CONFIG_NFS_V4_1)
@@ -7922,6 +8283,7 @@ static const struct nfs4_minor_version_ops nfs_v4_1_minor_ops = {
        .reboot_recovery_ops = &nfs41_reboot_recovery_ops,
        .nograce_recovery_ops = &nfs41_nograce_recovery_ops,
        .state_renewal_ops = &nfs41_state_renewal_ops,
+       .mig_recovery_ops = &nfs41_mig_recovery_ops,
 };
 #endif