]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/nfs/nfs4proc.c
Merge tag 'nfs-for-3.12-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[karo-tx-linux.git] / fs / nfs / nfs4proc.c
index 7b702bc4941995eb64ba0a4b7455abe880c6d9b7..39b6cf2d1683fae228f71d1954383f53550e0439 100644 (file)
@@ -152,6 +152,7 @@ static int nfs4_map_errors(int err)
        case -NFS4ERR_RECALLCONFLICT:
                return -EREMOTEIO;
        case -NFS4ERR_WRONGSEC:
+       case -NFS4ERR_WRONG_CRED:
                return -EPERM;
        case -NFS4ERR_BADOWNER:
        case -NFS4ERR_BADNAME:
@@ -484,6 +485,67 @@ static void nfs4_set_sequence_privileged(struct nfs4_sequence_args *args)
        args->sa_privileged = 1;
 }
 
+static int nfs40_setup_sequence(const struct nfs_server *server,
+                               struct nfs4_sequence_args *args,
+                               struct nfs4_sequence_res *res,
+                               struct rpc_task *task)
+{
+       struct nfs4_slot_table *tbl = server->nfs_client->cl_slot_tbl;
+       struct nfs4_slot *slot;
+
+       /* slot already allocated? */
+       if (res->sr_slot != NULL)
+               goto out_start;
+
+       spin_lock(&tbl->slot_tbl_lock);
+       if (nfs4_slot_tbl_draining(tbl) && !args->sa_privileged)
+               goto out_sleep;
+
+       slot = nfs4_alloc_slot(tbl);
+       if (IS_ERR(slot)) {
+               if (slot == ERR_PTR(-ENOMEM))
+                       task->tk_timeout = HZ >> 2;
+               goto out_sleep;
+       }
+       spin_unlock(&tbl->slot_tbl_lock);
+
+       args->sa_slot = slot;
+       res->sr_slot = slot;
+
+out_start:
+       rpc_call_start(task);
+       return 0;
+
+out_sleep:
+       if (args->sa_privileged)
+               rpc_sleep_on_priority(&tbl->slot_tbl_waitq, task,
+                               NULL, RPC_PRIORITY_PRIVILEGED);
+       else
+               rpc_sleep_on(&tbl->slot_tbl_waitq, task, NULL);
+       spin_unlock(&tbl->slot_tbl_lock);
+       return -EAGAIN;
+}
+
+static int nfs40_sequence_done(struct rpc_task *task,
+                              struct nfs4_sequence_res *res)
+{
+       struct nfs4_slot *slot = res->sr_slot;
+       struct nfs4_slot_table *tbl;
+
+       if (!RPC_WAS_SENT(task))
+               goto out;
+
+       tbl = slot->table;
+       spin_lock(&tbl->slot_tbl_lock);
+       if (!nfs41_wake_and_assign_slot(tbl, slot))
+               nfs4_free_slot(tbl, slot);
+       spin_unlock(&tbl->slot_tbl_lock);
+
+       res->sr_slot = NULL;
+out:
+       return 1;
+}
+
 #if defined(CONFIG_NFS_V4_1)
 
 static void nfs41_sequence_free_slot(struct nfs4_sequence_res *res)
@@ -629,6 +691,8 @@ static int nfs4_sequence_done(struct rpc_task *task,
 {
        if (res->sr_slot == NULL)
                return 1;
+       if (!res->sr_slot->table->session)
+               return nfs40_sequence_done(task, res);
        return nfs41_sequence_done(task, res);
 }
 
@@ -704,17 +768,15 @@ static int nfs4_setup_sequence(const struct nfs_server *server,
        struct nfs4_session *session = nfs4_get_session(server);
        int ret = 0;
 
-       if (session == NULL) {
-               rpc_call_start(task);
-               goto out;
-       }
+       if (!session)
+               return nfs40_setup_sequence(server, args, res, task);
 
        dprintk("--> %s clp %p session %p sr_slot %u\n",
                __func__, session->clp, session, res->sr_slot ?
                        res->sr_slot->slot_nr : NFS4_NO_SLOT);
 
        ret = nfs41_setup_sequence(session, args, res, task);
-out:
+
        dprintk("<-- %s status=%d\n", __func__, ret);
        return ret;
 }
@@ -741,6 +803,42 @@ static const struct rpc_call_ops nfs41_call_sync_ops = {
        .rpc_call_done = nfs41_call_sync_done,
 };
 
+#else  /* !CONFIG_NFS_V4_1 */
+
+static int nfs4_setup_sequence(const struct nfs_server *server,
+                              struct nfs4_sequence_args *args,
+                              struct nfs4_sequence_res *res,
+                              struct rpc_task *task)
+{
+       return nfs40_setup_sequence(server, args, res, task);
+}
+
+static int nfs4_sequence_done(struct rpc_task *task,
+                              struct nfs4_sequence_res *res)
+{
+       return nfs40_sequence_done(task, res);
+}
+
+#endif /* !CONFIG_NFS_V4_1 */
+
+static void nfs40_call_sync_prepare(struct rpc_task *task, void *calldata)
+{
+       struct nfs4_call_sync_data *data = calldata;
+       nfs4_setup_sequence(data->seq_server,
+                               data->seq_args, data->seq_res, task);
+}
+
+static void nfs40_call_sync_done(struct rpc_task *task, void *calldata)
+{
+       struct nfs4_call_sync_data *data = calldata;
+       nfs4_sequence_done(task, data->seq_res);
+}
+
+static const struct rpc_call_ops nfs40_call_sync_ops = {
+       .rpc_call_prepare = nfs40_call_sync_prepare,
+       .rpc_call_done = nfs40_call_sync_done,
+};
+
 static int nfs4_call_sync_sequence(struct rpc_clnt *clnt,
                                   struct nfs_server *server,
                                   struct rpc_message *msg,
@@ -749,6 +847,7 @@ static int nfs4_call_sync_sequence(struct rpc_clnt *clnt,
 {
        int ret;
        struct rpc_task *task;
+       struct nfs_client *clp = server->nfs_client;
        struct nfs4_call_sync_data data = {
                .seq_server = server,
                .seq_args = args,
@@ -757,7 +856,7 @@ static int nfs4_call_sync_sequence(struct rpc_clnt *clnt,
        struct rpc_task_setup task_setup = {
                .rpc_client = clnt,
                .rpc_message = msg,
-               .callback_ops = &nfs41_call_sync_ops,
+               .callback_ops = clp->cl_mvops->call_sync_ops,
                .callback_data = &data
        };
 
@@ -771,33 +870,6 @@ static int nfs4_call_sync_sequence(struct rpc_clnt *clnt,
        return ret;
 }
 
-#else
-static int nfs4_setup_sequence(const struct nfs_server *server,
-                              struct nfs4_sequence_args *args,
-                              struct nfs4_sequence_res *res,
-                              struct rpc_task *task)
-{
-       rpc_call_start(task);
-       return 0;
-}
-
-static int nfs4_sequence_done(struct rpc_task *task,
-                              struct nfs4_sequence_res *res)
-{
-       return 1;
-}
-#endif /* CONFIG_NFS_V4_1 */
-
-static
-int _nfs4_call_sync(struct rpc_clnt *clnt,
-                   struct nfs_server *server,
-                   struct rpc_message *msg,
-                   struct nfs4_sequence_args *args,
-                   struct nfs4_sequence_res *res)
-{
-       return rpc_call_sync(clnt, msg, 0);
-}
-
 static
 int nfs4_call_sync(struct rpc_clnt *clnt,
                   struct nfs_server *server,
@@ -807,8 +879,7 @@ int nfs4_call_sync(struct rpc_clnt *clnt,
                   int cache_reply)
 {
        nfs4_init_sequence(args, res, cache_reply);
-       return server->nfs_client->cl_mvops->call_sync(clnt, server, msg,
-                                               args, res);
+       return nfs4_call_sync_sequence(clnt, server, msg, args, res);
 }
 
 static void update_changeattr(struct inode *dir, struct nfs4_change_info *cinfo)
@@ -1540,10 +1611,20 @@ int nfs4_open_delegation_recall(struct nfs_open_context *ctx, struct nfs4_state
        return nfs4_handle_delegation_recall_error(server, state, stateid, err);
 }
 
+static void nfs4_open_confirm_prepare(struct rpc_task *task, void *calldata)
+{
+       struct nfs4_opendata *data = calldata;
+
+       nfs40_setup_sequence(data->o_arg.server, &data->o_arg.seq_args,
+                               &data->o_res.seq_res, task);
+}
+
 static void nfs4_open_confirm_done(struct rpc_task *task, void *calldata)
 {
        struct nfs4_opendata *data = calldata;
 
+       nfs40_sequence_done(task, &data->o_res.seq_res);
+
        data->rpc_status = task->tk_status;
        if (data->rpc_status == 0) {
                nfs4_stateid_copy(&data->o_res.stateid, &data->c_res.stateid);
@@ -1572,6 +1653,7 @@ out_free:
 }
 
 static const struct rpc_call_ops nfs4_open_confirm_ops = {
+       .rpc_call_prepare = nfs4_open_confirm_prepare,
        .rpc_call_done = nfs4_open_confirm_done,
        .rpc_release = nfs4_open_confirm_release,
 };
@@ -1599,6 +1681,7 @@ static int _nfs4_proc_open_confirm(struct nfs4_opendata *data)
        };
        int status;
 
+       nfs4_init_sequence(&data->o_arg.seq_args, &data->o_res.seq_res, 1);
        kref_get(&data->kref);
        data->rpc_done = 0;
        data->rpc_status = 0;
@@ -2533,6 +2616,9 @@ int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait)
        };
        int status = -ENOMEM;
 
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_CLEANUP,
+               &task_setup_data.rpc_client, &msg);
+
        calldata = kzalloc(sizeof(*calldata), gfp_mask);
        if (calldata == NULL)
                goto out;
@@ -2798,18 +2884,27 @@ static int nfs4_do_find_root_sec(struct nfs_server *server,
  * @server: initialized nfs_server handle
  * @fhandle: we fill in the pseudo-fs root file handle
  * @info: we fill in an FSINFO struct
+ * @auth_probe: probe the auth flavours
  *
  * Returns zero on success, or a negative errno.
  */
 int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle,
-                        struct nfs_fsinfo *info)
+                        struct nfs_fsinfo *info,
+                        bool auth_probe)
 {
        int status;
 
-       status = nfs4_lookup_root(server, fhandle, info);
-       if ((status == -NFS4ERR_WRONGSEC) &&
-           !(server->flags & NFS_MOUNT_SECFLAVOUR))
+       switch (auth_probe) {
+       case false:
+               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);
+       }
 
        if (status == 0)
                status = nfs4_server_capabilities(server, fhandle);
@@ -3059,7 +3154,9 @@ 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);
@@ -3099,15 +3196,13 @@ struct rpc_clnt *
 nfs4_proc_lookup_mountpoint(struct inode *dir, struct qstr *name,
                            struct nfs_fh *fhandle, struct nfs_fattr *fattr)
 {
+       struct rpc_clnt *client = NFS_CLIENT(dir);
        int status;
-       struct rpc_clnt *client = rpc_clone_client(NFS_CLIENT(dir));
 
        status = nfs4_proc_lookup_common(&client, dir, name, fhandle, fattr, NULL);
-       if (status < 0) {
-               rpc_shutdown_client(client);
+       if (status < 0)
                return ERR_PTR(status);
-       }
-       return client;
+       return (client == NFS_CLIENT(dir)) ? rpc_clone_client(client) : client;
 }
 
 static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry)
@@ -3946,15 +4041,19 @@ static void nfs4_proc_read_setup(struct nfs_read_data *data, struct rpc_message
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 0);
 }
 
-static void nfs4_proc_read_rpc_prepare(struct rpc_task *task, struct nfs_read_data *data)
+static int nfs4_proc_read_rpc_prepare(struct rpc_task *task, struct nfs_read_data *data)
 {
        if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
                        &data->args.seq_args,
                        &data->res.seq_res,
                        task))
-               return;
-       nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                       data->args.lock_context, FMODE_READ);
+               return 0;
+       if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
+                               data->args.lock_context, FMODE_READ) == -EIO)
+               return -EIO;
+       if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
+               return -EIO;
+       return 0;
 }
 
 static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_write_data *data)
@@ -4030,15 +4129,19 @@ static void nfs4_proc_write_setup(struct nfs_write_data *data, struct rpc_messag
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 1);
 }
 
-static void nfs4_proc_write_rpc_prepare(struct rpc_task *task, struct nfs_write_data *data)
+static int nfs4_proc_write_rpc_prepare(struct rpc_task *task, struct nfs_write_data *data)
 {
        if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
                        &data->args.seq_args,
                        &data->res.seq_res,
                        task))
-               return;
-       nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                       data->args.lock_context, FMODE_WRITE);
+               return 0;
+       if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
+                               data->args.lock_context, FMODE_WRITE) == -EIO)
+               return -EIO;
+       if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
+               return -EIO;
+       return 0;
 }
 
 static void nfs4_proc_commit_rpc_prepare(struct rpc_task *task, struct nfs_commit_data *data)
@@ -4848,7 +4951,6 @@ static void nfs4_delegreturn_release(void *calldata)
        kfree(calldata);
 }
 
-#if defined(CONFIG_NFS_V4_1)
 static void nfs4_delegreturn_prepare(struct rpc_task *task, void *data)
 {
        struct nfs4_delegreturndata *d_data;
@@ -4860,12 +4962,9 @@ static void nfs4_delegreturn_prepare(struct rpc_task *task, void *data)
                        &d_data->res.seq_res,
                        task);
 }
-#endif /* CONFIG_NFS_V4_1 */
 
 static const struct rpc_call_ops nfs4_delegreturn_ops = {
-#if defined(CONFIG_NFS_V4_1)
        .rpc_call_prepare = nfs4_delegreturn_prepare,
-#endif /* CONFIG_NFS_V4_1 */
        .rpc_call_done = nfs4_delegreturn_done,
        .rpc_release = nfs4_delegreturn_release,
 };
@@ -5144,6 +5243,9 @@ static struct rpc_task *nfs4_do_unlck(struct file_lock *fl,
                .flags = RPC_TASK_ASYNC,
        };
 
+       nfs4_state_protect(NFS_SERVER(lsp->ls_state->inode)->nfs_client,
+               NFS_SP4_MACH_CRED_CLEANUP, &task_setup_data.rpc_client, &msg);
+
        /* Ensure this is an unlock - when canceling a lock, the
         * canceled lock is passed in, and it won't be an unlock.
         */
@@ -5448,6 +5550,10 @@ static int nfs4_lock_expired(struct nfs4_state *state, struct file_lock *request
        err = nfs4_set_lock_state(state, request);
        if (err != 0)
                return err;
+       if (!recover_lost_locks) {
+               set_bit(NFS_LOCK_LOST, &request->fl_u.nfs4_fl.owner->ls_flags);
+               return 0;
+       }
        do {
                if (test_bit(NFS_DELEGATED_STATE, &state->flags) != 0)
                        return 0;
@@ -5659,8 +5765,23 @@ struct nfs_release_lockowner_data {
        struct nfs4_lock_state *lsp;
        struct nfs_server *server;
        struct nfs_release_lockowner_args args;
+       struct nfs4_sequence_args seq_args;
+       struct nfs4_sequence_res seq_res;
 };
 
+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);
+}
+
+static void nfs4_release_lockowner_done(struct rpc_task *task, void *calldata)
+{
+       struct nfs_release_lockowner_data *data = calldata;
+       nfs40_sequence_done(task, &data->seq_res);
+}
+
 static void nfs4_release_lockowner_release(void *calldata)
 {
        struct nfs_release_lockowner_data *data = calldata;
@@ -5669,6 +5790,8 @@ static void nfs4_release_lockowner_release(void *calldata)
 }
 
 static const struct rpc_call_ops nfs4_release_lockowner_ops = {
+       .rpc_call_prepare = nfs4_release_lockowner_prepare,
+       .rpc_call_done = nfs4_release_lockowner_done,
        .rpc_release = nfs4_release_lockowner_release,
 };
 
@@ -5681,14 +5804,17 @@ static int nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_st
 
        if (server->nfs_client->cl_mvops->minor_version != 0)
                return -EINVAL;
+
        data = kmalloc(sizeof(*data), GFP_NOFS);
        if (!data)
                return -ENOMEM;
+       nfs4_init_sequence(&data->seq_args, &data->seq_res, 0);
        data->lsp = lsp;
        data->server = server;
        data->args.lock_owner.clientid = server->nfs_client->cl_clientid;
        data->args.lock_owner.id = lsp->ls_seqid.owner_id;
        data->args.lock_owner.s_dev = server->s_dev;
+
        msg.rpc_argp = &data->args;
        rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
        return 0;
@@ -5882,6 +6008,10 @@ static int _nfs4_proc_secinfo(struct inode *dir, const struct qstr *name, struct
        }
 
        dprintk("NFS call  secinfo %s\n", name->name);
+
+       nfs4_state_protect(NFS_SERVER(dir)->nfs_client,
+               NFS_SP4_MACH_CRED_SECINFO, &clnt, &msg);
+
        status = nfs4_call_sync(clnt, NFS_SERVER(dir), &msg, &args.seq_args,
                                &res.seq_res, 0);
        dprintk("NFS reply  secinfo: %d\n", status);
@@ -6007,16 +6137,124 @@ out:
 }
 
 /*
- * nfs4_proc_exchange_id()
+ * Minimum set of SP4_MACH_CRED operations from RFC 5661 in the enforce map
+ * and operations we'd like to see to enable certain features in the allow map
+ */
+static const struct nfs41_state_protection nfs4_sp4_mach_cred_request = {
+       .how = SP4_MACH_CRED,
+       .enforce.u.words = {
+               [1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
+                     1 << (OP_EXCHANGE_ID - 32) |
+                     1 << (OP_CREATE_SESSION - 32) |
+                     1 << (OP_DESTROY_SESSION - 32) |
+                     1 << (OP_DESTROY_CLIENTID - 32)
+       },
+       .allow.u.words = {
+               [0] = 1 << (OP_CLOSE) |
+                     1 << (OP_LOCKU),
+               [1] = 1 << (OP_SECINFO - 32) |
+                     1 << (OP_SECINFO_NO_NAME - 32) |
+                     1 << (OP_TEST_STATEID - 32) |
+                     1 << (OP_FREE_STATEID - 32)
+       }
+};
+
+/*
+ * Select the state protection mode for client `clp' given the server results
+ * from exchange_id in `sp'.
  *
- * Returns zero, a negative errno, or a negative NFS4ERR status code.
+ * Returns 0 on success, negative errno otherwise.
+ */
+static int nfs4_sp4_select_mode(struct nfs_client *clp,
+                                struct nfs41_state_protection *sp)
+{
+       static const u32 supported_enforce[NFS4_OP_MAP_NUM_WORDS] = {
+               [1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
+                     1 << (OP_EXCHANGE_ID - 32) |
+                     1 << (OP_CREATE_SESSION - 32) |
+                     1 << (OP_DESTROY_SESSION - 32) |
+                     1 << (OP_DESTROY_CLIENTID - 32)
+       };
+       unsigned int i;
+
+       if (sp->how == SP4_MACH_CRED) {
+               /* Print state protect result */
+               dfprintk(MOUNT, "Server SP4_MACH_CRED support:\n");
+               for (i = 0; i <= LAST_NFS4_OP; i++) {
+                       if (test_bit(i, sp->enforce.u.longs))
+                               dfprintk(MOUNT, "  enforce op %d\n", i);
+                       if (test_bit(i, sp->allow.u.longs))
+                               dfprintk(MOUNT, "  allow op %d\n", i);
+               }
+
+               /* make sure nothing is on enforce list that isn't supported */
+               for (i = 0; i < NFS4_OP_MAP_NUM_WORDS; i++) {
+                       if (sp->enforce.u.words[i] & ~supported_enforce[i]) {
+                               dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
+                               return -EINVAL;
+                       }
+               }
+
+               /*
+                * Minimal mode - state operations are allowed to use machine
+                * credential.  Note this already happens by default, so the
+                * client doesn't have to do anything more than the negotiation.
+                *
+                * NOTE: we don't care if EXCHANGE_ID is in the list -
+                *       we're already using the machine cred for exchange_id
+                *       and will never use a different cred.
+                */
+               if (test_bit(OP_BIND_CONN_TO_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_CREATE_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_DESTROY_SESSION, sp->enforce.u.longs) &&
+                   test_bit(OP_DESTROY_CLIENTID, sp->enforce.u.longs)) {
+                       dfprintk(MOUNT, "sp4_mach_cred:\n");
+                       dfprintk(MOUNT, "  minimal mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_MINIMAL, &clp->cl_sp4_flags);
+               } else {
+                       dfprintk(MOUNT, "sp4_mach_cred: disabled\n");
+                       return -EINVAL;
+               }
+
+               if (test_bit(OP_CLOSE, sp->allow.u.longs) &&
+                   test_bit(OP_LOCKU, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  cleanup mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_CLEANUP, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_SECINFO, sp->allow.u.longs) &&
+                   test_bit(OP_SECINFO_NO_NAME, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  secinfo mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_SECINFO, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_TEST_STATEID, sp->allow.u.longs) &&
+                   test_bit(OP_FREE_STATEID, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  stateid mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_STATEID, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_WRITE, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  write mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_WRITE, &clp->cl_sp4_flags);
+               }
+
+               if (test_bit(OP_COMMIT, sp->allow.u.longs)) {
+                       dfprintk(MOUNT, "  commit mode enabled\n");
+                       set_bit(NFS_SP4_MACH_CRED_COMMIT, &clp->cl_sp4_flags);
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * _nfs4_proc_exchange_id()
  *
- * Since the clientid has expired, all compounds using sessions
- * associated with the stale clientid will be returning
- * NFS4ERR_BADSESSION in the sequence operation, and will therefore
- * be in some phase of session reset.
+ * Wrapper for EXCHANGE_ID operation.
  */
-int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
+static int _nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred,
+       u32 sp4_how)
 {
        nfs4_verifier verifier;
        struct nfs41_exchange_id_args args = {
@@ -6063,11 +6301,30 @@ int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
                goto out_server_scope;
        }
 
+       switch (sp4_how) {
+       case SP4_NONE:
+               args.state_protect.how = SP4_NONE;
+               break;
+
+       case SP4_MACH_CRED:
+               args.state_protect = nfs4_sp4_mach_cred_request;
+               break;
+
+       default:
+               /* unsupported! */
+               WARN_ON_ONCE(1);
+               status = -EINVAL;
+               goto out_server_scope;
+       }
+
        status = rpc_call_sync(clp->cl_rpcclient, &msg, RPC_TASK_TIMEOUT);
        trace_nfs4_exchange_id(clp, status);
        if (status == 0)
                status = nfs4_check_cl_exchange_flags(res.flags);
 
+       if (status == 0)
+               status = nfs4_sp4_select_mode(clp, &res.state_protect);
+
        if (status == 0) {
                clp->cl_clientid = res.clientid;
                clp->cl_exchange_flags = (res.flags & ~EXCHGID4_FLAG_CONFIRMED_R);
@@ -6114,6 +6371,35 @@ out:
        return status;
 }
 
+/*
+ * nfs4_proc_exchange_id()
+ *
+ * Returns zero, a negative errno, or a negative NFS4ERR status code.
+ *
+ * Since the clientid has expired, all compounds using sessions
+ * associated with the stale clientid will be returning
+ * NFS4ERR_BADSESSION in the sequence operation, and will therefore
+ * be in some phase of session reset.
+ *
+ * Will attempt to negotiate SP4_MACH_CRED if krb5i / krb5p auth is used.
+ */
+int nfs4_proc_exchange_id(struct nfs_client *clp, struct rpc_cred *cred)
+{
+       rpc_authflavor_t authflavor = clp->cl_rpcclient->cl_auth->au_flavor;
+       int status;
+
+       /* try SP4_MACH_CRED if krb5i/p */
+       if (authflavor == RPC_AUTH_GSS_KRB5I ||
+           authflavor == RPC_AUTH_GSS_KRB5P) {
+               status = _nfs4_proc_exchange_id(clp, cred, SP4_MACH_CRED);
+               if (!status)
+                       return 0;
+       }
+
+       /* try SP4_NONE */
+       return _nfs4_proc_exchange_id(clp, cred, SP4_NONE);
+}
+
 static int _nfs4_proc_destroy_clientid(struct nfs_client *clp,
                struct rpc_cred *cred)
 {
@@ -7195,7 +7481,8 @@ out:
  */
 static int
 _nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
-                   struct nfs_fsinfo *info, struct nfs4_secinfo_flavors *flavors)
+                   struct nfs_fsinfo *info,
+                   struct nfs4_secinfo_flavors *flavors, bool use_integrity)
 {
        struct nfs41_secinfo_no_name_args args = {
                .style = SECINFO_STYLE_CURRENT_FH,
@@ -7208,8 +7495,23 @@ _nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
                .rpc_argp = &args,
                .rpc_resp = &res,
        };
-       return nfs4_call_sync(server->nfs_client->cl_rpcclient, server, &msg,
-                               &args.seq_args, &res.seq_res, 0);
+       struct rpc_clnt *clnt = server->client;
+       int status;
+
+       if (use_integrity) {
+               clnt = server->nfs_client->cl_rpcclient;
+               msg.rpc_cred = nfs4_get_clid_cred(server->nfs_client);
+       }
+
+       dprintk("--> %s\n", __func__);
+       status = nfs4_call_sync(clnt, server, &msg, &args.seq_args,
+                               &res.seq_res, 0);
+       dprintk("<-- %s status=%d\n", __func__, status);
+
+       if (msg.rpc_cred)
+               put_rpccred(msg.rpc_cred);
+
+       return status;
 }
 
 static int
@@ -7219,7 +7521,24 @@ nfs41_proc_secinfo_no_name(struct nfs_server *server, struct nfs_fh *fhandle,
        struct nfs4_exception exception = { };
        int err;
        do {
-               err = _nfs41_proc_secinfo_no_name(server, fhandle, info, flavors);
+               /* first try using integrity protection */
+               err = -NFS4ERR_WRONGSEC;
+
+               /* try to use integrity protection with machine cred */
+               if (_nfs4_is_integrity_protected(server->nfs_client))
+                       err = _nfs41_proc_secinfo_no_name(server, fhandle, info,
+                                                         flavors, true);
+
+               /*
+                * if unable to use integrity protection, or SECINFO with
+                * integrity protection returns NFS4ERR_WRONGSEC (which is
+                * disallowed by spec, but exists in deployed servers) use
+                * the current filesystem's rpc_client and the user cred.
+                */
+               if (err == -NFS4ERR_WRONGSEC)
+                       err = _nfs41_proc_secinfo_no_name(server, fhandle, info,
+                                                         flavors, false);
+
                switch (err) {
                case 0:
                case -NFS4ERR_WRONGSEC:
@@ -7289,11 +7608,15 @@ static int _nfs41_test_stateid(struct nfs_server *server,
                .rpc_resp = &res,
                .rpc_cred = cred,
        };
+       struct rpc_clnt *rpc_client = server->client;
+
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_STATEID,
+               &rpc_client, &msg);
 
        dprintk("NFS call  test_stateid %p\n", stateid);
        nfs4_init_sequence(&args.seq_args, &res.seq_res, 0);
        nfs4_set_sequence_privileged(&args.seq_args);
-       status = nfs4_call_sync_sequence(server->client, server, &msg,
+       status = nfs4_call_sync_sequence(rpc_client, server, &msg,
                        &args.seq_args, &res.seq_res);
        if (status != NFS_OK) {
                dprintk("NFS reply test_stateid: failed, %d\n", status);
@@ -7385,6 +7708,9 @@ static struct rpc_task *_nfs41_free_stateid(struct nfs_server *server,
        };
        struct nfs_free_stateid_data *data;
 
+       nfs4_state_protect(server->nfs_client, NFS_SP4_MACH_CRED_STATEID,
+               &task_setup.rpc_client, &msg);
+
        dprintk("NFS call  free_stateid %p\n", stateid);
        data = kmalloc(sizeof(*data), GFP_NOFS);
        if (!data)
@@ -7525,10 +7851,12 @@ static const struct nfs4_minor_version_ops nfs_v4_0_minor_ops = {
                | NFS_CAP_ATOMIC_OPEN
                | NFS_CAP_CHANGE_ATTR
                | NFS_CAP_POSIX_LOCK,
-       .call_sync = _nfs4_call_sync,
+       .init_client = nfs40_init_client,
+       .shutdown_client = nfs40_shutdown_client,
        .match_stateid = nfs4_match_stateid,
        .find_root_sec = nfs4_find_root_sec,
        .free_lock_state = nfs4_release_lockowner,
+       .call_sync_ops = &nfs40_call_sync_ops,
        .reboot_recovery_ops = &nfs40_reboot_recovery_ops,
        .nograce_recovery_ops = &nfs40_nograce_recovery_ops,
        .state_renewal_ops = &nfs40_state_renewal_ops,
@@ -7543,10 +7871,12 @@ static const struct nfs4_minor_version_ops nfs_v4_1_minor_ops = {
                | NFS_CAP_POSIX_LOCK
                | NFS_CAP_STATEID_NFSV41
                | NFS_CAP_ATOMIC_OPEN_V1,
-       .call_sync = nfs4_call_sync_sequence,
+       .init_client = nfs41_init_client,
+       .shutdown_client = nfs41_shutdown_client,
        .match_stateid = nfs41_match_stateid,
        .find_root_sec = nfs41_find_root_sec,
        .free_lock_state = nfs41_free_lock_state,
+       .call_sync_ops = &nfs41_call_sync_ops,
        .reboot_recovery_ops = &nfs41_reboot_recovery_ops,
        .nograce_recovery_ops = &nfs41_nograce_recovery_ops,
        .state_renewal_ops = &nfs41_state_renewal_ops,
@@ -7562,10 +7892,12 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
                | NFS_CAP_POSIX_LOCK
                | NFS_CAP_STATEID_NFSV41
                | NFS_CAP_ATOMIC_OPEN_V1,
-       .call_sync = nfs4_call_sync_sequence,
+       .init_client = nfs41_init_client,
+       .shutdown_client = nfs41_shutdown_client,
        .match_stateid = nfs41_match_stateid,
        .find_root_sec = nfs41_find_root_sec,
        .free_lock_state = nfs41_free_lock_state,
+       .call_sync_ops = &nfs41_call_sync_ops,
        .reboot_recovery_ops = &nfs41_reboot_recovery_ops,
        .nograce_recovery_ops = &nfs41_nograce_recovery_ops,
        .state_renewal_ops = &nfs41_state_renewal_ops,