From c5a2a15f8146fdfe45078df7873a6dc1006b3869 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Tue, 30 Apr 2013 12:43:42 -0400 Subject: NFSv4.x: Fix handling of partially delegated locks If a NFS client receives a delegation for a file after it has taken a lock on that file, we can currently end up in a situation where we mistakenly skip unlocking that file. The following patch swaps an erroneous check in nfs4_proc_unlck for whether or not the file has a delegation to one which checks whether or not we hold a lock stateid for that file. Reported-by: Chuck Lever Signed-off-by: Trond Myklebust Cc: stable@vger.kernel.org [>=3.7] Tested-by: Chuck Lever --- fs/nfs/nfs4proc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 9da4bd55eb30..dc1da2adc459 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -4766,9 +4766,9 @@ static int nfs4_proc_unlck(struct nfs4_state *state, int cmd, struct file_lock * if (status != 0) goto out; /* Is this a delegated lock? */ - if (test_bit(NFS_DELEGATED_STATE, &state->flags)) - goto out; lsp = request->fl_u.nfs4_fl.owner; + if (test_bit(NFS_LOCK_INITIALIZED, &lsp->ls_flags) == 0) + goto out; seqid = nfs_alloc_seqid(&lsp->ls_seqid, GFP_KERNEL); status = -ENOMEM; if (seqid == NULL) -- cgit v1.2.3 From 9b1d75b75505cff4468ceba0da51a1ba47e15ea2 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Wed, 1 May 2013 09:17:50 -0400 Subject: SUNRPC: Don't spam syslog with "Pseudoflavor not found" messages Just convert those messages to dprintk()s so that they can be used when debugging. Signed-off-by: Trond Myklebust --- net/sunrpc/auth_gss/auth_gss.c | 3 +-- net/sunrpc/clnt.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c index 51415b07174e..b5dd6922493a 100644 --- a/net/sunrpc/auth_gss/auth_gss.c +++ b/net/sunrpc/auth_gss/auth_gss.c @@ -867,8 +867,7 @@ gss_create(struct rpc_clnt *clnt, rpc_authflavor_t flavor) err = -EINVAL; gss_auth->mech = gss_mech_get_by_pseudoflavor(flavor); if (!gss_auth->mech) { - printk(KERN_WARNING "%s: Pseudoflavor %d not found!\n", - __func__, flavor); + dprintk("RPC: Pseudoflavor %d not found!\n", flavor); goto err_free; } gss_auth->service = gss_pseudoflavor_to_service(gss_auth->mech, flavor); diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index d259fa966927..e9143edcb23b 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -360,7 +360,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args, stru auth = rpcauth_create(args->authflavor, clnt); if (IS_ERR(auth)) { - printk(KERN_INFO "RPC: Couldn't create auth handle (flavor %u)\n", + dprintk("RPC: Couldn't create auth handle (flavor %u)\n", args->authflavor); err = PTR_ERR(auth); goto out_no_auth; -- cgit v1.2.3 From 7c1d5fae4a87d3cf3e9ffd68bcdbaf6529013009 Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 3 May 2013 14:40:01 -0400 Subject: NFSv4: Convert nfs41_free_stateid to use an asynchronous RPC call The main reason for doing this is will be to allow for an asynchronous RPC mode that we can use for freeing lock stateids as per section 8.2.4 of RFC5661. Signed-off-by: Trond Myklebust --- fs/nfs/nfs4proc.c | 98 +++++++++++++++++++++++++++++++++++++------------ fs/nfs/nfs4xdr.c | 2 +- include/linux/nfs_xdr.h | 2 +- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index dc1da2adc459..1f3c6118c8ce 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -6783,26 +6783,76 @@ static int nfs41_test_stateid(struct nfs_server *server, nfs4_stateid *stateid) return err; } -static int _nfs4_free_stateid(struct nfs_server *server, nfs4_stateid *stateid) -{ - struct nfs41_free_stateid_args args = { - .stateid = stateid, - }; +struct nfs_free_stateid_data { + struct nfs_server *server; + struct nfs41_free_stateid_args args; struct nfs41_free_stateid_res res; +}; + +static void nfs41_free_stateid_prepare(struct rpc_task *task, void *calldata) +{ + struct nfs_free_stateid_data *data = calldata; + nfs41_setup_sequence(nfs4_get_session(data->server), + &data->args.seq_args, + &data->res.seq_res, + task); +} + +static void nfs41_free_stateid_done(struct rpc_task *task, void *calldata) +{ + struct nfs_free_stateid_data *data = calldata; + + nfs41_sequence_done(task, &data->res.seq_res); + + switch (task->tk_status) { + case -NFS4ERR_DELAY: + if (nfs4_async_handle_error(task, data->server, NULL) == -EAGAIN) + rpc_restart_call_prepare(task); + } +} + +static void nfs41_free_stateid_release(void *calldata) +{ + kfree(calldata); +} + +const struct rpc_call_ops nfs41_free_stateid_ops = { + .rpc_call_prepare = nfs41_free_stateid_prepare, + .rpc_call_done = nfs41_free_stateid_done, + .rpc_release = nfs41_free_stateid_release, +}; + +static struct rpc_task *_nfs41_free_stateid(struct nfs_server *server, + nfs4_stateid *stateid, + bool privileged) +{ struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_FREE_STATEID], - .rpc_argp = &args, - .rpc_resp = &res, }; - int status; + struct rpc_task_setup task_setup = { + .rpc_client = server->client, + .rpc_message = &msg, + .callback_ops = &nfs41_free_stateid_ops, + .flags = RPC_TASK_ASYNC, + }; + struct nfs_free_stateid_data *data; dprintk("NFS call free_stateid %p\n", stateid); - nfs41_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, - &args.seq_args, &res.seq_res); - dprintk("NFS reply free_stateid: %d\n", status); - return status; + data = kmalloc(sizeof(*data), GFP_NOFS); + if (!data) + return ERR_PTR(-ENOMEM); + data->server = server; + nfs4_stateid_copy(&data->args.stateid, stateid); + + task_setup.callback_data = data; + + msg.rpc_argp = &data->args; + msg.rpc_resp = &data->res; + nfs41_init_sequence(&data->args.seq_args, &data->res.seq_res, 0); + if (privileged) + nfs4_set_sequence_privileged(&data->args.seq_args); + + return rpc_run_task(&task_setup); } /** @@ -6816,15 +6866,17 @@ static int _nfs4_free_stateid(struct nfs_server *server, nfs4_stateid *stateid) */ static int nfs41_free_stateid(struct nfs_server *server, nfs4_stateid *stateid) { - struct nfs4_exception exception = { }; - int err; - do { - err = _nfs4_free_stateid(server, stateid); - if (err != -NFS4ERR_DELAY) - break; - nfs4_handle_exception(server, err, &exception); - } while (exception.retry); - return err; + struct rpc_task *task; + int ret; + + task = _nfs41_free_stateid(server, stateid, true); + if (IS_ERR(task)) + return PTR_ERR(task); + ret = rpc_wait_for_completion_task(task); + if (!ret) + ret = task->tk_status; + rpc_put_task(task); + return ret; } static bool nfs41_match_stateid(const nfs4_stateid *s1, diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index 3c79c5878c6d..4be8d135ed61 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -2003,7 +2003,7 @@ static void encode_free_stateid(struct xdr_stream *xdr, struct compound_hdr *hdr) { encode_op_hdr(xdr, OP_FREE_STATEID, decode_free_stateid_maxsz, hdr); - encode_nfs4_stateid(xdr, args->stateid); + encode_nfs4_stateid(xdr, &args->stateid); } #endif /* CONFIG_NFS_V4_1 */ diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 766c5bc9d441..104b62f23ee0 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -1176,7 +1176,7 @@ struct nfs41_test_stateid_res { struct nfs41_free_stateid_args { struct nfs4_sequence_args seq_args; - nfs4_stateid *stateid; + nfs4_stateid stateid; }; struct nfs41_free_stateid_res { -- cgit v1.2.3 From c8b2d0bfd3370a5e19e64ddb23f8bc1276410b6c Mon Sep 17 00:00:00 2001 From: Trond Myklebust Date: Fri, 3 May 2013 16:22:55 -0400 Subject: NFSv4.1: Ensure that we free the lock stateid on the server This ensures that the server doesn't need to keep huge numbers of lock stateids waiting around for the final CLOSE. See section 8.2.4 in RFC5661. Signed-off-by: Trond Myklebust --- fs/nfs/nfs4_fs.h | 3 ++- fs/nfs/nfs4proc.c | 17 +++++++++++++++-- fs/nfs/nfs4state.c | 11 +++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h index 553a83cc4106..a1dd768d0a35 100644 --- a/fs/nfs/nfs4_fs.h +++ b/fs/nfs/nfs4_fs.h @@ -47,6 +47,8 @@ struct nfs4_minor_version_ops { const nfs4_stateid *); int (*find_root_sec)(struct nfs_server *, struct nfs_fh *, struct nfs_fsinfo *); + int (*free_lock_state)(struct nfs_server *, + struct nfs4_lock_state *); const struct nfs4_state_recovery_ops *reboot_recovery_ops; const struct nfs4_state_recovery_ops *nograce_recovery_ops; const struct nfs4_state_maintenance_ops *state_renewal_ops; @@ -234,7 +236,6 @@ extern int nfs4_proc_fs_locations(struct rpc_clnt *, struct inode *, const struc extern struct rpc_clnt *nfs4_proc_lookup_mountpoint(struct inode *, struct qstr *, struct nfs_fh *, struct nfs_fattr *); extern int nfs4_proc_secinfo(struct inode *, const struct qstr *, struct nfs4_secinfo_flavors *); -extern int nfs4_release_lockowner(struct nfs4_lock_state *); extern const struct xattr_handler *nfs4_xattr_handlers[]; extern int nfs4_set_rw_stateid(nfs4_stateid *stateid, const struct nfs_open_context *ctx, diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 1f3c6118c8ce..8fbc10054115 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -5238,9 +5238,8 @@ static const struct rpc_call_ops nfs4_release_lockowner_ops = { .rpc_release = nfs4_release_lockowner_release, }; -int nfs4_release_lockowner(struct nfs4_lock_state *lsp) +static int nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp) { - struct nfs_server *server = lsp->ls_state->owner->so_server; struct nfs_release_lockowner_data *data; struct rpc_message msg = { .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_RELEASE_LOCKOWNER], @@ -6879,6 +6878,18 @@ static int nfs41_free_stateid(struct nfs_server *server, nfs4_stateid *stateid) return ret; } +static int nfs41_free_lock_state(struct nfs_server *server, struct nfs4_lock_state *lsp) +{ + struct rpc_task *task; + + task = _nfs41_free_stateid(server, &lsp->ls_stateid, false); + nfs4_free_lock_state(server, lsp); + if (IS_ERR(task)) + return PTR_ERR(task); + rpc_put_task(task); + return 0; +} + static bool nfs41_match_stateid(const nfs4_stateid *s1, const nfs4_stateid *s2) { @@ -6968,6 +6979,7 @@ static const struct nfs4_minor_version_ops nfs_v4_0_minor_ops = { .call_sync = _nfs4_call_sync, .match_stateid = nfs4_match_stateid, .find_root_sec = nfs4_find_root_sec, + .free_lock_state = nfs4_release_lockowner, .reboot_recovery_ops = &nfs40_reboot_recovery_ops, .nograce_recovery_ops = &nfs40_nograce_recovery_ops, .state_renewal_ops = &nfs40_state_renewal_ops, @@ -6985,6 +6997,7 @@ static const struct nfs4_minor_version_ops nfs_v4_1_minor_ops = { .call_sync = nfs4_call_sync_sequence, .match_stateid = nfs41_match_stateid, .find_root_sec = nfs41_find_root_sec, + .free_lock_state = nfs41_free_lock_state, .reboot_recovery_ops = &nfs41_reboot_recovery_ops, .nograce_recovery_ops = &nfs41_nograce_recovery_ops, .state_renewal_ops = &nfs41_state_renewal_ops, diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c index 0b32f9483b7a..300d17d85c0e 100644 --- a/fs/nfs/nfs4state.c +++ b/fs/nfs/nfs4state.c @@ -921,6 +921,7 @@ static struct nfs4_lock_state *nfs4_get_lock_state(struct nfs4_state *state, fl_ */ void nfs4_put_lock_state(struct nfs4_lock_state *lsp) { + struct nfs_server *server; struct nfs4_state *state; if (lsp == NULL) @@ -932,11 +933,13 @@ void nfs4_put_lock_state(struct nfs4_lock_state *lsp) if (list_empty(&state->lock_states)) clear_bit(LK_STATE_IN_USE, &state->flags); spin_unlock(&state->state_lock); + server = state->owner->so_server; if (test_bit(NFS_LOCK_INITIALIZED, &lsp->ls_flags)) { - if (nfs4_release_lockowner(lsp) == 0) - return; - } - nfs4_free_lock_state(lsp->ls_state->owner->so_server, lsp); + struct nfs_client *clp = server->nfs_client; + + clp->cl_mvops->free_lock_state(server, lsp); + } else + nfs4_free_lock_state(server, lsp); } static void nfs4_fl_copy_lock(struct file_lock *dst, struct file_lock *src) -- cgit v1.2.3 From d497ab975141666e674e7bd8729e00095ec23c9d Mon Sep 17 00:00:00 2001 From: Weston Andros Adamson Date: Mon, 6 May 2013 17:12:13 -0400 Subject: NFSv3: match sec= flavor against server list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Older linux clients match the 'sec=' mount option flavor against the server's flavor list (if available) and return EPERM if the specified flavor or AUTH_NULL (which "matches" any flavor) is not found. Recent changes skip this step and allow the vfs mount even though no operations will succeed, creating a 'dud' mount. This patch reverts back to the old behavior of matching specified flavors against the server list and also returns EPERM when no sec= is specified and none of the flavors returned by the server are supported by the client. Example of behavior change: the server's /etc/exports: /export/krb5 *(sec=krb5,rw,no_root_squash) old client behavior: $ uname -a Linux one.apikia.fake 3.8.8-202.fc18.x86_64 #1 SMP Wed Apr 17 23:25:17 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux $ sudo mount -v -o sec=sys,vers=3 zero:/export/krb5 /mnt mount.nfs: timeout set for Sun May 5 17:32:04 2013 mount.nfs: trying text-based options 'sec=sys,vers=3,addr=192.168.100.10' mount.nfs: prog 100003, trying vers=3, prot=6 mount.nfs: trying 192.168.100.10 prog 100003 vers 3 prot TCP port 2049 mount.nfs: prog 100005, trying vers=3, prot=17 mount.nfs: trying 192.168.100.10 prog 100005 vers 3 prot UDP port 20048 mount.nfs: mount(2): Permission denied mount.nfs: access denied by server while mounting zero:/export/krb5 recently changed behavior: $ uname -a Linux one.apikia.fake 3.9.0-testing+ #2 SMP Fri May 3 20:29:32 EDT 2013 x86_64 x86_64 x86_64 GNU/Linux $ sudo mount -v -o sec=sys,vers=3 zero:/export/krb5 /mnt mount.nfs: timeout set for Sun May 5 17:37:17 2013 mount.nfs: trying text-based options 'sec=sys,vers=3,addr=192.168.100.10' mount.nfs: prog 100003, trying vers=3, prot=6 mount.nfs: trying 192.168.100.10 prog 100003 vers 3 prot TCP port 2049 mount.nfs: prog 100005, trying vers=3, prot=17 mount.nfs: trying 192.168.100.10 prog 100005 vers 3 prot UDP port 20048 $ ls /mnt ls: cannot open directory /mnt: Permission denied $ sudo ls /mnt ls: cannot open directory /mnt: Permission denied $ sudo df /mnt df: ‘/mnt’: Permission denied df: no file systems processed $ sudo umount /mnt $ Signed-off-by: Weston Andros Adamson Signed-off-by: Trond Myklebust --- fs/nfs/super.c | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 3bb8318f6d0c..b65001c0a119 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1607,16 +1607,15 @@ out_security_failure: /* * Select a security flavor for this mount. The selected flavor * is planted in args->auth_flavors[0]. + * + * Returns 0 on success, -EACCES on failure. */ -static void nfs_select_flavor(struct nfs_parsed_mount_data *args, +static int nfs_select_flavor(struct nfs_parsed_mount_data *args, struct nfs_mount_request *request) { unsigned int i, count = *(request->auth_flav_len); rpc_authflavor_t flavor; - if (args->auth_flavors[0] != RPC_AUTH_MAXFLAVOR) - goto out; - /* * The NFSv2 MNT operation does not return a flavor list. */ @@ -1630,6 +1629,25 @@ static void nfs_select_flavor(struct nfs_parsed_mount_data *args, if (count == 0) goto out_default; + /* + * If the sec= mount option is used, the specified flavor or AUTH_NULL + * must be in the list returned by the server. + * + * AUTH_NULL has a special meaning when it's in the server list - it + * means that the server will ignore the rpc creds, so any flavor + * can be used. + */ + if (args->auth_flavors[0] != RPC_AUTH_MAXFLAVOR) { + for (i = 0; i < count; i++) { + if (args->auth_flavors[0] == request->auth_flavs[i] || + request->auth_flavs[i] == RPC_AUTH_NULL) + goto out; + } + dfprintk(MOUNT, "NFS: auth flavor %d not supported by server\n", + args->auth_flavors[0]); + goto out_err; + } + /* * RFC 2623, section 2.7 suggests we SHOULD prefer the * flavor listed first. However, some servers list @@ -1650,12 +1668,29 @@ static void nfs_select_flavor(struct nfs_parsed_mount_data *args, } } + /* + * As a last chance, see if the server list contains AUTH_NULL - + * if it does, use the default flavor. + */ + for (i = 0; i < count; i++) { + if (request->auth_flavs[i] == RPC_AUTH_NULL) + goto out_default; + } + + dfprintk(MOUNT, "NFS: no auth flavors in common with server\n"); + goto out_err; + out_default: - flavor = RPC_AUTH_UNIX; + /* use default if flavor not already set */ + flavor = (args->auth_flavors[0] == RPC_AUTH_MAXFLAVOR) ? + RPC_AUTH_UNIX : args->auth_flavors[0]; out_set: args->auth_flavors[0] = flavor; out: dfprintk(MOUNT, "NFS: using auth flavor %d\n", args->auth_flavors[0]); + return 0; +out_err: + return -EACCES; } /* @@ -1718,8 +1753,7 @@ static int nfs_request_mount(struct nfs_parsed_mount_data *args, return status; } - nfs_select_flavor(args, &request); - return 0; + return nfs_select_flavor(args, &request); } struct dentry *nfs_try_mount(int flags, const char *dev_name, -- cgit v1.2.3 From c23266d532b4de796a346f57a66587c5db17d27e Mon Sep 17 00:00:00 2001 From: Andy Adamson Date: Wed, 8 May 2013 16:21:18 -0400 Subject: NFS4.1 Fix data server connection race Unlike meta data server mounts which support multiple mount points to the same server via struct nfs_server, data servers support a single connection. Concurrent calls to setup the data server connection can race where the first call allocates the nfs_client struct, and before the cache struct nfs_client pointer can be set, a second call also tries to setup the connection, finds the already allocated nfs_client, bumps the reference count, re-initializes the session,etc. This results in a hanging data server session after umount. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust --- fs/nfs/nfs4filelayout.h | 2 ++ fs/nfs/nfs4filelayoutdev.c | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fs/nfs/nfs4filelayout.h b/fs/nfs/nfs4filelayout.h index b8da95548d3d..235ff952d3c8 100644 --- a/fs/nfs/nfs4filelayout.h +++ b/fs/nfs/nfs4filelayout.h @@ -70,6 +70,8 @@ struct nfs4_pnfs_ds { struct list_head ds_addrs; struct nfs_client *ds_clp; atomic_t ds_count; + unsigned long ds_state; +#define NFS4DS_CONNECTING 0 /* ds is establishing connection */ }; struct nfs4_file_layout_dsaddr { diff --git a/fs/nfs/nfs4filelayoutdev.c b/fs/nfs/nfs4filelayoutdev.c index 1fe284f01f8b..661a0f611215 100644 --- a/fs/nfs/nfs4filelayoutdev.c +++ b/fs/nfs/nfs4filelayoutdev.c @@ -775,6 +775,22 @@ nfs4_fl_select_ds_fh(struct pnfs_layout_segment *lseg, u32 j) return flseg->fh_array[i]; } +static void nfs4_wait_ds_connect(struct nfs4_pnfs_ds *ds) +{ + might_sleep(); + wait_on_bit(&ds->ds_state, NFS4DS_CONNECTING, + nfs_wait_bit_killable, TASK_KILLABLE); +} + +static void nfs4_clear_ds_conn_bit(struct nfs4_pnfs_ds *ds) +{ + smp_mb__before_clear_bit(); + clear_bit(NFS4DS_CONNECTING, &ds->ds_state); + smp_mb__after_clear_bit(); + wake_up_bit(&ds->ds_state, NFS4DS_CONNECTING); +} + + struct nfs4_pnfs_ds * nfs4_fl_prepare_ds(struct pnfs_layout_segment *lseg, u32 ds_idx) { @@ -791,16 +807,22 @@ nfs4_fl_prepare_ds(struct pnfs_layout_segment *lseg, u32 ds_idx) filelayout_mark_devid_invalid(devid); return NULL; } + if (ds->ds_clp) + return ds; - if (!ds->ds_clp) { + if (test_and_set_bit(NFS4DS_CONNECTING, &ds->ds_state) == 0) { struct nfs_server *s = NFS_SERVER(lseg->pls_layout->plh_inode); int err; err = nfs4_ds_connect(s, ds); if (err) { nfs4_mark_deviceid_unavailable(devid); - return NULL; + ds = NULL; } + nfs4_clear_ds_conn_bit(ds); + } else { + /* Either ds is connected, or ds is NULL */ + nfs4_wait_ds_connect(ds); } return ds; } -- cgit v1.2.3