GIT e4bf56330c7fd1eb9573b3ed7746a1fe511845e2 git://git.linux-nfs.org/pub/linux/nfs-2.6.git commit Author: Andy Adamson Date: Fri May 18 16:52:47 2007 -0400 NFSv4: Add security flavor to struct nfs_fattr Set fattr->flavor upon successful nfs4_proc_lookup retry in WRONGSEC error processing. This will ignored in all nfs4_proc_lookup returns except in nfs_follow_mount where the fattr->flavor is passed through to nfs_clone_server via the nfs_mount_data->fattr field. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 608a6003814b92feb093debd2689fe03ab57d9f6 Author: Andy Adamson Date: Fri May 18 16:52:46 2007 -0400 NFSv4: Handle NFS4ERR_WRONGSEC in nfs4_proc_lookup The first time through the wrongsec handler, setup the ex_list via a secinfo call. The existance of ex_list signals to skip setup and try the next flavor on the list, which will only occur if the server secinfo list is incorret, or if we are trying a list of flavors (from mount) on a PUTROOTFH/PUTFH WRONGSEC. Free the wrongsec handler resources upon success. NOTE: future patches will pass a successful flavor to nfs_clone_server() for new fsid nfs_server setup. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 414838943395d4ce0d5ba06b9fc51dd3e9c5a95e Author: Andy Adamson Date: Fri May 18 16:52:45 2007 -0400 NFSv4: Collect SECINFO parameters for nfs_lookup WRONGSEC handling Add an exception structure to __nfs4_proc_lookup and to the lookup XDR result structure and collect the operation, directory filehandle, and name needed to call SECINFO on WRONGSEC error. NOTE: The WRONGSEC error could have (incorrectly) been on the PUTFH, but we don't care. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit db1acf9a199bce0f71260913e0e7c5d145c585c2 Author: Andy Adamson Date: Fri May 18 16:52:44 2007 -0400 NFSv4: Add the NFS SECINFO proc Place the secinfo call on the wire Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit cb4420716b37cee5d2327a747a6d0d2f731fb5cf Author: Andy Adamson Date: Fri May 18 16:52:43 2007 -0400 NFSv4: Try each security flavor in WRONGSEC list Main function for NFS4ERR_WRONGSEC handling. Success returns zero with ex_cred set and ready for exception retry. Use a private rpc_clnt to obtain credentials for a flavor in the list. An rpc_clnt and not an rpc_auth is created and destroyed upon each attempt to get a credential due to the RPC_AUTH_GSS upcall pipe creation/destruction and rpc.gssd notification. NOTE: We could update rpc.gssd to do the right thing when the pipe file goes away, and then we could keep the ex_clnt around and get creds with new rpc_auth's. Signed-off-by: Trond Myklebust commit e099c4fa6d0137071b0609814d2741be610709bd Author: Andy Adamson Date: Fri May 18 16:52:42 2007 -0400 NFSv4: Obtain an RPC credential for NFS4ERR_WRONGSEC handler Helper function for WRONGSEC handler Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 08bc50b07858cc3899dd5bc457b47b140dadc84c Author: Andy Adamson Date: Fri May 18 16:52:41 2007 -0400 NFSv4: Add a helper to return the next untried security flavor Helper function for NFS4ERR_WRONGSEC handler Returns RPC_AUTH_MAXFLAVOR when all flavors have been tried. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 2766f1e049a96f89a094f869af2be1106d88adf8 Author: Andy Adamson Date: Fri May 18 16:52:40 2007 -0400 NFSv4: Add the NFS SECINFO xdr encode/decode routines Encode and decode routines for OP_SECINFO. Decode routine saves an orderd list of up to NFS4_SECINFO_MAXFLAVORS server supplied pseudoflavors supported on the client. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 2234427dca459dde13eeb9258ba34d5765c1d8f6 Author: Andy Adamson Date: Fri May 18 16:52:39 2007 -0400 SUNRPC: Add a helper function to convert a GSS triple into pseudoflavor Helper function for OP_SECINFO. Return the pseudoflavor for a supported security mechanism OID, qop, and service. Return RPC_AUTH_MAXFLAVOR upon failure. Signed-off-by: Andy Adamson Signed-off-by: Trond Myklebust commit 0c6eb0b83c50dfbd8fe000bcac114ccd2eb1904b Author: Trond Myklebust Date: Wed May 16 16:53:28 2007 -0400 NFS: Error when mounting the same filesystem with different options Unless the user sets the NFS_MOUNT_NOSHAREDCACHE mount flag, we should return EBUSY if the filesystem is already mounted on a superblock that has set conflicting mount options. Signed-off-by: Trond Myklebust commit adf4fa7fbc3396af5bd7a163c8adc58c8b536c4b Author: Trond Myklebust Date: Wed May 16 16:53:28 2007 -0400 NFS: Add the mount option "nosharecache" Prior to David Howell's mount changes in 2.6.18, users who mounted different directories which happened to be from the same filesystem on the server would get different super blocks, and hence could choose different mount options. As long as there were no hard linked files that crossed from one subtree to another, this was quite safe. Post the changes, if the two directories are on the same filesystem (have the same 'fsid'), they will share the same super block, and hence the same mount options. Add a flag to allow users to elect not to share the NFS super block with another mount point, even if the fsids are the same. This will allow users to set different mount options for the two different super blocks, as was previously possible. It is still up to the user to ensure that there are no cache coherency issues when doing this, however the default behaviour will be to share super blocks whenever two paths result in the same fsid. Signed-off-by: Trond Myklebust commit 0a1580ccff3072a623b34433e024b7457ee55691 Author: Trond Myklebust Date: Sun May 20 13:05:05 2007 -0400 NFS: Minor read optimisation... Since PG_uptodate may now end up getting set during the call to nfs_wb_page(), we can avoid putting a read request on the wire in those situations. Signed-off-by: Trond Myklebust commit 386d0f48e8303ebc8644fd56178d08131e2793a8 Author: Trond Myklebust Date: Sat May 19 11:58:03 2007 -0400 NFS: Don't mark a written page as uptodate until it is on disk The write may fail, so we should not mark the page as uptodate until we are certain that the data has been accepted and written to disk by the server. Signed-off-by: Trond Myklebust commit 9da042daf0c98c13070b2d3f1af26acf85626b92 Author: Chuck Lever Date: Sat May 19 17:22:52 2007 -0400 NFS: Clean ups in fs/nfs/direct.c Signed-off-by: Chuck Lever Signed-off-by: Trond Myklebust commit dfe3dffed80a7707de5161ec285f9a6e9e867115 Author: Chuck Lever Date: Sat May 19 17:22:46 2007 -0400 NFS: Fix handful of compiler warnings in direct.c This patch fixes a couple of signage issues that were causing an Oops when running the LTP diotest4 test. get_user_pages() returns a signed error, hence we need to be careful when comparing with the unsigned number of pages from data->npages. Signed-off-by: Chuck Lever Signed-off-by: Trond Myklebust commit cbb1cb79e2c8a73d5bc0a5306d216ff12f05d29c Author: Trond Myklebust Date: Sun May 20 10:18:27 2007 -0400 NFS: Avoid a deadlock situation on write When processes are allowed to attempt to lock a non-contiguous range of nfs write requests, it is possible for generic_writepages to 'wrap round' the address space, and call writepage() on a request that is already locked by the same process. We avoid the deadlock by checking if the page index is contiguous with the list of nfs write requests that is already held in our nfs_pageio_descriptor prior to attempting to lock a new request. Signed-off-by: Trond Myklebust fs/nfs/client.c | 107 ++++++++++++++++++++++++++++- fs/nfs/direct.c | 34 +++++---- fs/nfs/nfs4_fs.h | 13 +++ fs/nfs/nfs4proc.c | 87 +++++++++++++++++++++++ fs/nfs/nfs4xdr.c | 123 ++++++++++++++++++++++++++++++++- fs/nfs/pagelist.c | 20 +++++ fs/nfs/read.c | 34 ++++++--- fs/nfs/super.c | 77 +++++++++++++++++++-- fs/nfs/write.c | 20 ++++- include/linux/nfs4.h | 1 include/linux/nfs4_mount.h | 1 include/linux/nfs_fs.h | 1 include/linux/nfs_mount.h | 1 include/linux/nfs_page.h | 1 include/linux/nfs_xdr.h | 12 +++ include/linux/sunrpc/gss_api.h | 1 net/sunrpc/auth_gss/gss_mech_switch.c | 38 ++++++++++ 17 files changed, 527 insertions(+), 44 deletions(-) diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 881fa49..140ed2f 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -1102,6 +1102,106 @@ error: return ERR_PTR(error); } +/* + * Return the next untried security flavor or RPC_AUTH_MAXFLAVOR + * when all flavors have been tried. + */ +static rpc_authflavor_t nfs4_next_flavor(struct nfs4_exception *exc, + rpc_authflavor_t cur_flavor) +{ + struct nfs4_secinfo_res *sec; + rpc_authflavor_t ret = RPC_AUTH_MAXFLAVOR; + int i; + + dprintk("-->nfs4_next_flavor\n"); + sec = exc->ex_list; + for (i = 0; i < sec->fl_num; i++){ + + /* Skip flavors that have been tried. */ + if (sec->flavors[i] == RPC_AUTH_MAXFLAVOR) + continue; + + /* Try this flavor */ + ret = sec->flavors[i]; + sec->flavors[i] = RPC_AUTH_MAXFLAVOR; + goto out; + } +out: + dprintk("<-- nfs4_next_flavor() error %d\n", ret); + return ret; +} + +static int nfs4_set_ex_cred(struct nfs4_exception *exc, rpc_authflavor_t flavor) +{ + struct rpc_auth *auth; + int error = 0; + + dprintk("--> nfs4_set_ex_cred: flavor %d\n", flavor); + + auth = rpcauth_create(flavor, exc->ex_clnt); + if (IS_ERR(auth)) { + dprintk("%s: couldn't create credcache!\n", __FUNCTION__); + return PTR_ERR(auth); + } + exc->ex_cred = rpcauth_lookupcred(exc->ex_clnt->cl_auth, 0); + if (IS_ERR(exc->ex_cred)) { + error = PTR_ERR(exc->ex_cred); + exc->ex_cred = NULL; + } + dprintk("<-- nfs4_set_ex_cred %d\n", error); + return error; +} + +/* + * Shutdown WRONGSEC exception handling + */ +void nfs4_shutdown_wrongsec(struct nfs4_exception *exc) +{ + kfree(exc->ex_list); + if (!IS_ERR(exc->ex_clnt)) + rpc_shutdown_client(exc->ex_clnt); +} + +/* + * Try the next security flavor. + */ +int nfs4_clnt_try_next_flavor(struct nfs_server *server, + struct nfs4_exception *exc) +{ + rpc_authflavor_t ret, cur_flavor = server->client->cl_auth->au_flavor; + int error; + + dprintk("--> nfs4_clnt_try_next_flavor() current flavor %d\n", cur_flavor); + +retry: + if (exc->ex_clnt) + rpc_shutdown_client(exc->ex_clnt); + + exc->ex_clnt = rpc_clone_client(server->nfs_client->cl_rpcclient); + if (IS_ERR(exc->ex_clnt)) { + error = PTR_ERR(exc->ex_clnt); + goto out_free; + } + ret = nfs4_next_flavor(exc, cur_flavor); + if (ret == RPC_AUTH_MAXFLAVOR) { + dprintk("%s Tried all flavors\n", __FUNCTION__); + error = -EACCES; + goto out_free; + } + + error = nfs4_set_ex_cred(exc, ret); + if (error < 0) { + dprintk("%s No creds for flavor %d\n", __FUNCTION__, ret); + goto retry; + } +out: + dprintk("<-- nfs4_clnt_try_next_flavor() error %d\n", error); + return error; +out_free: + nfs4_shutdown_wrongsec(exc); + goto out; +} + #endif /* CONFIG_NFS_V4 */ /* @@ -1113,6 +1213,7 @@ struct nfs_server *nfs_clone_server(stru { struct nfs_server *server; struct nfs_fattr fattr_fsinfo; + rpc_authflavor_t flavor; int error; dprintk("--> nfs_clone_server(,%llx:%llx,)\n", @@ -1130,7 +1231,11 @@ struct nfs_server *nfs_clone_server(stru server->fsid = fattr->fsid; - error = nfs_init_server_rpcclient(server, source->client->cl_auth->au_flavor); + flavor = source->client->cl_auth->au_flavor; + if (fattr->flavor != RPC_AUTH_MAXFLAVOR) + flavor = fattr->flavor; + + error = nfs_init_server_rpcclient(server, flavor); if (error < 0) goto out_free_server; if (!IS_ERR(source->client_acl)) diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 345aa5c..49e4823 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -122,9 +122,9 @@ ssize_t nfs_direct_IO(int rw, struct kio return -EINVAL; } -static void nfs_direct_dirty_pages(struct page **pages, int npages) +static void nfs_direct_dirty_pages(struct page **pages, unsigned int npages) { - int i; + unsigned int i; for (i = 0; i < npages; i++) { struct page *page = pages[i]; if (!PageCompound(page)) @@ -132,9 +132,9 @@ static void nfs_direct_dirty_pages(struc } } -static void nfs_direct_release_pages(struct page **pages, int npages) +static void nfs_direct_release_pages(struct page **pages, unsigned int npages) { - int i; + unsigned int i; for (i = 0; i < npages; i++) page_cache_release(pages[i]); } @@ -279,9 +279,12 @@ static ssize_t nfs_direct_read_schedule( result = get_user_pages(current, current->mm, user_addr, data->npages, 1, 0, data->pagevec, NULL); up_read(¤t->mm->mmap_sem); - if (unlikely(result < data->npages)) { - if (result > 0) - nfs_direct_release_pages(data->pagevec, result); + if (result < 0) { + nfs_readdata_release(data); + break; + } + if ((unsigned)result < data->npages) { + nfs_direct_release_pages(data->pagevec, result); nfs_readdata_release(data); break; } @@ -610,9 +613,12 @@ static ssize_t nfs_direct_write_schedule result = get_user_pages(current, current->mm, user_addr, data->npages, 0, 0, data->pagevec, NULL); up_read(¤t->mm->mmap_sem); - if (unlikely(result < data->npages)) { - if (result > 0) - nfs_direct_release_pages(data->pagevec, result); + if (result < 0) { + nfs_writedata_release(data); + break; + } + if ((unsigned)result < data->npages) { + nfs_direct_release_pages(data->pagevec, result); nfs_writedata_release(data); break; } @@ -744,10 +750,8 @@ ssize_t nfs_file_direct_read(struct kioc (unsigned long) count, (long long) pos); if (nr_segs != 1) - return -EINVAL; - - if (count < 0) goto out; + retval = -EFAULT; if (!access_ok(VERIFY_WRITE, buf, count)) goto out; @@ -795,7 +799,7 @@ out: ssize_t nfs_file_direct_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos) { - ssize_t retval; + ssize_t retval = -EINVAL; struct file *file = iocb->ki_filp; struct address_space *mapping = file->f_mapping; /* XXX: temporary */ @@ -808,7 +812,7 @@ ssize_t nfs_file_direct_write(struct kio (unsigned long) count, (long long) pos); if (nr_segs != 1) - return -EINVAL; + goto out; retval = generic_write_checks(file, &pos, &count, 0); if (retval) diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h index cf3a17e..f85f2b2 100644 --- a/fs/nfs/nfs4_fs.h +++ b/fs/nfs/nfs4_fs.h @@ -140,9 +140,19 @@ struct nfs4_state { }; +struct nfs4_secinfo_exception { + int op; + struct nfs_fh *fh; + struct qstr *name; +}; + struct nfs4_exception { long timeout; int retry; + struct nfs4_secinfo_exception ex_info; + struct nfs4_secinfo_res *ex_list; + struct rpc_clnt *ex_clnt; + struct rpc_cred *ex_cred; }; struct nfs4_state_recovery_ops { @@ -158,6 +168,9 @@ extern ssize_t nfs4_getxattr(struct dent extern int nfs4_setxattr(struct dentry *, const char *, const void *, size_t, int); extern ssize_t nfs4_listxattr(struct dentry *, char *, size_t); +/* client.c */ +extern void nfs4_shutdown_wrongsec(struct nfs4_exception *); +extern int nfs4_clnt_try_next_flavor(struct nfs_server *, struct nfs4_exception *); /* nfs4proc.c */ extern int nfs4_map_errors(int err); diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 648e0ac..8e46e3e 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -65,6 +65,8 @@ static int nfs4_async_handle_error(struc static int _nfs4_proc_access(struct inode *inode, struct nfs_access_entry *entry); static int nfs4_handle_exception(const struct nfs_server *server, int errorcode, struct nfs4_exception *exception); static int nfs4_wait_clnt_recover(struct rpc_clnt *clnt, struct nfs_client *clp); +int nfs4_proc_secinfo(struct nfs_server *, struct nfs_fh *, struct qstr *, struct nfs4_secinfo_res *); + /* Prevent leaks of NFSv4 errors into userland */ int nfs4_map_errors(int err) @@ -1580,7 +1582,8 @@ static int nfs4_proc_lookupfh(struct nfs } static int _nfs4_proc_lookup(struct inode *dir, struct qstr *name, - struct nfs_fh *fhandle, struct nfs_fattr *fattr) + struct nfs_fh *fhandle, struct nfs_fattr *fattr, + struct nfs4_exception *exc) { int status; struct nfs_server *server = NFS_SERVER(dir); @@ -1600,12 +1603,22 @@ static int _nfs4_proc_lookup(struct inod .rpc_resp = &res, }; + if (exc->ex_cred) + msg.rpc_cred = exc->ex_cred; nfs_fattr_init(fattr); dprintk("NFS call lookup %s\n", name->name); status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); if (status == -NFS4ERR_MOVED) status = nfs4_get_referral(dir, name, fattr, fhandle); + if (status == -NFS4ERR_WRONGSEC) { + exc->ex_info.op = OP_LOOKUP; + exc->ex_info.fh = NFS_FH(dir); + exc->ex_info.name = name; + } + if (!status && exc->ex_clnt) + fattr->flavor = exc->ex_clnt->cl_auth->au_flavor; + dprintk("NFS reply lookup: %d\n", status); return status; } @@ -1616,7 +1629,8 @@ static int nfs4_proc_lookup(struct inode int err; do { err = nfs4_handle_exception(NFS_SERVER(dir), - _nfs4_proc_lookup(dir, name, fhandle, fattr), + _nfs4_proc_lookup(dir, name, fhandle, fattr, + &exception), &exception); } while (exception.retry); return err; @@ -2745,6 +2759,37 @@ static int nfs4_delay(struct rpc_clnt *c return res; } +static int nfs4_wrongsec(struct nfs4_exception *exc, struct nfs_server *server) +{ + int err = -EACCES; + + dprintk("--> nfs4_wrongsec\n"); + + if (exc->ex_list) + goto try; + switch(exc->ex_info.op) { + case OP_LOOKUP: + err = -ENOMEM; + exc->ex_list = kzalloc(sizeof(*exc->ex_list), GFP_KERNEL); + if (!exc->ex_list) + goto out; + err = nfs4_proc_secinfo(server, exc->ex_info.fh, + exc->ex_info.name, + exc->ex_list); + break; + default: + printk("ERROR: %s() don't know what to do for op=%d\n", __FUNCTION__, exc->ex_info.op); + break; + } + if (err != 0) + goto out; +try: + err = nfs4_clnt_try_next_flavor(server, exc); +out: + dprintk("<-- nfs4_wrongsec error %d\n", err); + return err; +} + /* This is the error handling routine for processes that are allowed * to sleep. */ @@ -2756,6 +2801,8 @@ static int nfs4_handle_exception(const s exception->retry = 0; switch(errorcode) { case 0: + if (exception->ex_list) + nfs4_shutdown_wrongsec(exception); return 0; case -NFS4ERR_STALE_CLIENTID: case -NFS4ERR_STALE_STATEID: @@ -2773,6 +2820,11 @@ static int nfs4_handle_exception(const s break; case -NFS4ERR_OLD_STATEID: exception->retry = 1; + break; + case -NFS4ERR_WRONGSEC: + ret = nfs4_wrongsec(exception, (struct nfs_server *)server); + if (ret == 0) + exception->retry = 1; } /* We failed to handle the error */ return nfs4_map_errors(ret); @@ -3574,6 +3626,37 @@ int nfs4_proc_fs_locations(struct inode return status; } +static int +_nfs4_proc_secinfo(struct nfs_server *server, struct nfs_fh *dir_fh, + struct qstr *name, struct nfs4_secinfo_res *res) +{ + struct nfs4_secinfo_arg args = { + .dir_fh = dir_fh, + .name = name, + }; + struct rpc_message msg = { + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SECINFO], + .rpc_argp = &args, + .rpc_resp = res, + }; + + return rpc_call_sync(server->client, &msg, 0); +} + +int nfs4_proc_secinfo(struct nfs_server *server, struct nfs_fh *dir_fh, + struct qstr *name, struct nfs4_secinfo_res *res) +{ + struct nfs4_exception exception = { }; + int err; + + do { + err = nfs4_handle_exception(server, + _nfs4_proc_secinfo(server, dir_fh, name, res), + &exception); + } while (exception.retry); + return err; +} + struct nfs4_state_recovery_ops nfs4_reboot_recovery_ops = { .recover_open = nfs4_open_reclaim, .recover_lock = nfs4_lock_reclaim, diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index 8003c91..4eb8a59 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -47,6 +47,7 @@ #include #include #include #include +#include #include #include #include @@ -92,6 +93,8 @@ #define nfs4_fattr_value_maxsz (1 + (1 + 3 + 3 + 3 + 2 * nfs4_name_maxsz)) #define nfs4_fattr_maxsz (nfs4_fattr_bitmap_maxsz + \ nfs4_fattr_value_maxsz) +#define gss_oid_maxsz (GSS_OID_MAX_LEN >> 2) +#define nfs4_flav_info_maxsz (1 + gss_oid_maxsz + 1 + 1) #define decode_getattr_maxsz (op_decode_hdr_maxsz + nfs4_fattr_maxsz) #define encode_savefh_maxsz (op_encode_hdr_maxsz) #define decode_savefh_maxsz (op_decode_hdr_maxsz) @@ -137,6 +140,10 @@ #define encode_create_maxsz (op_encode_h #define decode_create_maxsz (op_decode_hdr_maxsz + 8) #define encode_delegreturn_maxsz (op_encode_hdr_maxsz + 4) #define decode_delegreturn_maxsz (op_decode_hdr_maxsz) +#define encode_secinfo_maxsz (op_encode_hdr_maxsz + 1 + nfs4_name_maxsz) +#define decode_secinfo_maxsz (op_decode_hdr_maxsz + 1 + \ + (1 + nfs4_flav_info_maxsz) * \ + NFS4_SECINFO_MAXFLAVORS) #define NFS4_enc_compound_sz (1024) /* XXX: large enough? */ #define NFS4_dec_compound_sz (1024) /* XXX: large enough? */ #define NFS4_enc_read_sz (compound_encode_hdr_maxsz + \ @@ -423,6 +430,12 @@ #define NFS4_dec_fs_locations_sz \ decode_putfh_maxsz + \ op_decode_hdr_maxsz + \ nfs4_fattr_bitmap_maxsz) +#define NFS4_enc_secinfo_sz (compound_encode_hdr_maxsz + \ + encode_putfh_maxsz + \ + encode_secinfo_maxsz) +#define NFS4_dec_secinfo_sz (compound_decode_hdr_maxsz + \ + decode_putfh_maxsz + \ + decode_secinfo_maxsz) static struct { unsigned int mode; @@ -1215,6 +1228,18 @@ encode_savefh(struct xdr_stream *xdr) return 0; } +static int encode_secinfo(struct xdr_stream *xdr, const struct qstr *name) +{ + uint32_t *p; + + RESERVE_SPACE(8 + name->len); + WRITE32(OP_SECINFO); + WRITE32(name->len); + WRITEMEM(name->name, name->len); + +return 0; +} + static int encode_setattr(struct xdr_stream *xdr, const struct nfs_setattrargs *arg, const struct nfs_server *server) { int status; @@ -1763,6 +1788,25 @@ out: } /* + * Encode SECINFO request + */ +static int nfs4_xdr_enc_secinfo(struct rpc_rqst *req, uint32_t *p, const struct nfs4_secinfo_arg *args) +{ + int status; + struct xdr_stream xdr; + struct compound_hdr hdr = { + .nops = 2, + }; + + xdr_init_encode(&xdr, &req->rq_snd_buf, p); + encode_compound_hdr(&xdr, &hdr); + status = encode_putfh(&xdr, args->dir_fh); + if (!status) + status = encode_secinfo(&xdr, args->name); + return status; +} + +/* * Encode an SETATTR request */ static int nfs4_xdr_enc_setattr(struct rpc_rqst *req, __be32 *p, struct nfs_setattrargs *args) @@ -3558,6 +3602,62 @@ decode_savefh(struct xdr_stream *xdr) return decode_op_hdr(xdr, OP_SAVEFH); } +/* + * Save up to NFS4_SECINFO_MAXFLAVORS supported pseudoflavors. + */ +static int decode_secinfo(struct xdr_stream *xdr, struct nfs4_secinfo_res *res) +{ + uint32_t *p; + uint32_t qop; + uint32_t service; + int status; + int i, nflavors; + rpc_authflavor_t flavor; + int oidlen; + char oiddata[GSS_OID_MAX_LEN]; + + status = decode_op_hdr(xdr, OP_SECINFO); + if (status) + return status; + + READ_BUF(4); + READ32(nflavors); + res->fl_num = 0; + for (i = 0; i < nflavors ; i++) { + READ_BUF(4); + READ32(flavor); + switch (flavor) { + case RPC_AUTH_GSS: + READ_BUF(4); + READ32(oidlen); + READ_BUF(oidlen + 8); + + if (oidlen > GSS_OID_MAX_LEN || + res->fl_num > NFS4_SECINFO_MAXFLAVORS) + break; + + COPYMEM(oiddata, oidlen); + READ32(qop); + READ32(service); + flavor = gss_triple_to_pseudoflavor(oidlen, + oiddata, qop, service); + if (flavor == RPC_AUTH_MAXFLAVOR) + /* unsupported pseudoflavor */ + break; + res->flavors[i] = flavor; + res->fl_num++; + break; + case RPC_AUTH_UNIX: + case RPC_AUTH_NULL: + if (res->fl_num > NFS4_SECINFO_MAXFLAVORS) + break; + res->flavors[i] = flavor; + res->fl_num++; + } + } + return 0; +} + static int decode_setattr(struct xdr_stream *xdr, struct nfs_setattrres *res) { __be32 *p; @@ -4046,6 +4146,24 @@ out: } /* + * Decode SECINFO response + */ +static int nfs4_xdr_dec_secinfo(struct rpc_rqst *rqstp, uint32_t *p, struct nfs4_secinfo_res *res) +{ + struct xdr_stream xdr; + struct compound_hdr hdr; + int status; + + xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p); + status = decode_compound_hdr(&xdr, &hdr); + if (!status) + status = decode_putfh(&xdr); + if (!status) + status = decode_secinfo(&xdr, res); + return status; +} + +/* * Decode SETATTR response */ static int nfs4_xdr_dec_setattr(struct rpc_rqst *rqstp, __be32 *p, struct nfs_setattrres *res) @@ -4518,10 +4636,6 @@ static struct { { NFS4ERR_SYMLINK, ELOOP }, { NFS4ERR_OP_ILLEGAL, EOPNOTSUPP }, { NFS4ERR_DEADLOCK, EDEADLK }, - { NFS4ERR_WRONGSEC, EPERM }, /* FIXME: this needs - * to be handled by a - * middle-layer. - */ { -1, EIO } }; @@ -4595,6 +4709,7 @@ struct rpc_procinfo nfs4_procedures[] = PROC(GETACL, enc_getacl, dec_getacl), PROC(SETACL, enc_setacl, dec_setacl), PROC(FS_LOCATIONS, enc_fs_locations, dec_fs_locations), + PROC(SECINFO, enc_secinfo, dec_secinfo), }; struct rpc_version nfs_version4 = { diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c index cbdd1c6..c5bb51a 100644 --- a/fs/nfs/pagelist.c +++ b/fs/nfs/pagelist.c @@ -355,6 +355,26 @@ void nfs_pageio_complete(struct nfs_page nfs_pageio_doio(desc); } +/** + * nfs_pageio_cond_complete - Conditional I/O completion + * @desc: pointer to io descriptor + * @index: page index + * + * It is important to ensure that processes don't try to take locks + * on non-contiguous ranges of pages as that might deadlock. This + * function should be called before attempting to wait on a locked + * nfs_page. It will complete the I/O if the page index 'index' + * is not contiguous with the existing list of pages in 'desc'. + */ +void nfs_pageio_cond_complete(struct nfs_pageio_descriptor *desc, pgoff_t index) +{ + if (!list_empty(&desc->pg_list)) { + struct nfs_page *prev = nfs_list_entry(desc->pg_list.prev); + if (index != prev->wb_index + 1) + nfs_pageio_doio(desc); + } +} + #define NFS_SCAN_MAXENTRIES 16 /** * nfs_scan_list - Scan a list for matching requests diff --git a/fs/nfs/read.c b/fs/nfs/read.c index 7bd7cb9..c07d0d1 100644 --- a/fs/nfs/read.c +++ b/fs/nfs/read.c @@ -483,17 +483,19 @@ int nfs_readpage(struct file *file, stru */ error = nfs_wb_page(inode, page); if (error) - goto out_error; + goto out_unlock; + if (PageUptodate(page)) + goto out_unlock; error = -ESTALE; if (NFS_STALE(inode)) - goto out_error; + goto out_unlock; if (file == NULL) { error = -EBADF; ctx = nfs_find_open_context(inode, NULL, FMODE_READ); if (ctx == NULL) - goto out_error; + goto out_unlock; } else ctx = get_nfs_open_context((struct nfs_open_context *) file->private_data); @@ -502,8 +504,7 @@ int nfs_readpage(struct file *file, stru put_nfs_open_context(ctx); return error; - -out_error: +out_unlock: unlock_page(page); return error; } @@ -520,21 +521,32 @@ readpage_async_filler(void *data, struct struct inode *inode = page->mapping->host; struct nfs_page *new; unsigned int len; + int error; + + error = nfs_wb_page(inode, page); + if (error) + goto out_unlock; + if (PageUptodate(page)) + goto out_unlock; - nfs_wb_page(inode, page); len = nfs_page_length(page); if (len == 0) return nfs_return_empty_page(page); + new = nfs_create_request(desc->ctx, inode, page, 0, len); - if (IS_ERR(new)) { - SetPageError(page); - unlock_page(page); - return PTR_ERR(new); - } + if (IS_ERR(new)) + goto out_error; + if (len < PAGE_CACHE_SIZE) zero_user_page(page, len, PAGE_CACHE_SIZE - len, KM_USER0); nfs_pageio_add_request(desc->pgio, new); return 0; +out_error: + error = PTR_ERR(new); + SetPageError(page); +out_unlock: + unlock_page(page); + return error; } int nfs_readpages(struct file *filp, struct address_space *mapping, diff --git a/fs/nfs/super.c b/fs/nfs/super.c index ca20d3c..f7f8844 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -291,6 +291,7 @@ static void nfs_show_mount_options(struc { NFS_MOUNT_NONLM, ",nolock", "" }, { NFS_MOUNT_NOACL, ",noacl", "" }, { NFS_MOUNT_NORDIRPLUS, ",nordirplus", "" }, + { NFS_MOUNT_UNSHARED, ",nosharecache", ""}, { 0, NULL, NULL } }; const struct proc_nfs_info *nfs_infop; @@ -600,13 +601,51 @@ static int nfs_compare_super(struct supe { struct nfs_server *server = data, *old = NFS_SB(sb); - if (old->nfs_client != server->nfs_client) + if (memcmp(&old->nfs_client->cl_addr, + &server->nfs_client->cl_addr, + sizeof(old->nfs_client->cl_addr)) != 0) + return 0; + /* Note: NFS_MOUNT_UNSHARED == NFS4_MOUNT_UNSHARED */ + if (old->flags & NFS_MOUNT_UNSHARED) return 0; if (memcmp(&old->fsid, &server->fsid, sizeof(old->fsid)) != 0) return 0; return 1; } +#define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS) + +static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b, int flags) +{ + const struct nfs_server *a = s->s_fs_info; + const struct rpc_clnt *clnt_a = a->client; + const struct rpc_clnt *clnt_b = b->client; + + if ((s->s_flags & NFS_MS_MASK) != (flags & NFS_MS_MASK)) + goto Ebusy; + if (a->nfs_client != b->nfs_client) + goto Ebusy; + if (a->flags != b->flags) + goto Ebusy; + if (a->wsize != b->wsize) + goto Ebusy; + if (a->rsize != b->rsize) + goto Ebusy; + if (a->acregmin != b->acregmin) + goto Ebusy; + if (a->acregmax != b->acregmax) + goto Ebusy; + if (a->acdirmin != b->acdirmin) + goto Ebusy; + if (a->acdirmax != b->acdirmax) + goto Ebusy; + if (clnt_a->cl_auth->au_flavor != clnt_b->cl_auth->au_flavor) + goto Ebusy; + return 0; +Ebusy: + return -EBUSY; +} + static int nfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *raw_data, struct vfsmount *mnt) { @@ -615,6 +654,7 @@ static int nfs_get_sb(struct file_system struct nfs_fh mntfh; struct nfs_mount_data *data = raw_data; struct dentry *mntroot; + int (*compare_super)(struct super_block *,void *) = nfs_compare_super; int error; /* Validate the mount data */ @@ -629,16 +669,22 @@ static int nfs_get_sb(struct file_system goto out_err_noserver; } + if (server->flags & NFS_MOUNT_UNSHARED) + compare_super = NULL; + /* Get a superblock - note that we may end up sharing one that already exists */ - s = sget(fs_type, nfs_compare_super, nfs_set_super, server); + s = sget(fs_type, compare_super, nfs_set_super, server); if (IS_ERR(s)) { error = PTR_ERR(s); goto out_err_nosb; } if (s->s_fs_info != server) { + error = nfs_compare_mount_options(s, server, flags); nfs_free_server(server); server = NULL; + if (error < 0) + goto error_splat_super; } if (!s->s_root) { @@ -691,6 +737,7 @@ static int nfs_xdev_get_sb(struct file_s struct super_block *s; struct nfs_server *server; struct dentry *mntroot; + int (*compare_super)(struct super_block *,void *) = nfs_compare_super; int error; dprintk("--> nfs_xdev_get_sb()\n"); @@ -702,8 +749,11 @@ static int nfs_xdev_get_sb(struct file_s goto out_err_noserver; } + if (server->flags & NFS_MOUNT_UNSHARED) + compare_super = NULL; + /* Get a superblock - note that we may end up sharing one that already exists */ - s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server); + s = sget(&nfs_fs_type, compare_super, nfs_set_super, server); if (IS_ERR(s)) { error = PTR_ERR(s); goto out_err_nosb; @@ -808,6 +858,7 @@ static int nfs4_get_sb(struct file_syste struct dentry *mntroot; char *mntpath = NULL, *hostname = NULL, ip_addr[16]; void *p; + int (*compare_super)(struct super_block *,void *) = nfs_compare_super; int error; if (data == NULL) { @@ -879,16 +930,22 @@ static int nfs4_get_sb(struct file_syste goto out_err_noserver; } + if (server->flags & NFS4_MOUNT_UNSHARED) + compare_super = NULL; + /* Get a superblock - note that we may end up sharing one that already exists */ - s = sget(fs_type, nfs_compare_super, nfs_set_super, server); + s = sget(fs_type, compare_super, nfs_set_super, server); if (IS_ERR(s)) { error = PTR_ERR(s); goto out_free; } if (s->s_fs_info != server) { + error = nfs_compare_mount_options(s, server, flags); nfs_free_server(server); server = NULL; + if (error < 0) + goto error_splat_super; } if (!s->s_root) { @@ -949,6 +1006,7 @@ static int nfs4_xdev_get_sb(struct file_ struct super_block *s; struct nfs_server *server; struct dentry *mntroot; + int (*compare_super)(struct super_block *,void *) = nfs_compare_super; int error; dprintk("--> nfs4_xdev_get_sb()\n"); @@ -960,8 +1018,11 @@ static int nfs4_xdev_get_sb(struct file_ goto out_err_noserver; } + if (server->flags & NFS4_MOUNT_UNSHARED) + compare_super = NULL; + /* Get a superblock - note that we may end up sharing one that already exists */ - s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server); + s = sget(&nfs_fs_type, compare_super, nfs_set_super, server); if (IS_ERR(s)) { error = PTR_ERR(s); goto out_err_nosb; @@ -1016,6 +1077,7 @@ static int nfs4_referral_get_sb(struct f struct nfs_server *server; struct dentry *mntroot; struct nfs_fh mntfh; + int (*compare_super)(struct super_block *,void *) = nfs_compare_super; int error; dprintk("--> nfs4_referral_get_sb()\n"); @@ -1027,8 +1089,11 @@ static int nfs4_referral_get_sb(struct f goto out_err_noserver; } + if (server->flags & NFS4_MOUNT_UNSHARED) + compare_super = NULL; + /* Get a superblock - note that we may end up sharing one that already exists */ - s = sget(&nfs_fs_type, nfs_compare_super, nfs_set_super, server); + s = sget(&nfs_fs_type, compare_super, nfs_set_super, server); if (IS_ERR(s)) { error = PTR_ERR(s); goto out_err_nosb; diff --git a/fs/nfs/write.c b/fs/nfs/write.c index b084c03..b853959 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -191,8 +191,6 @@ static int nfs_writepage_setup(struct nf } /* Update file length */ nfs_grow_file(page, offset, count); - /* Set the PG_uptodate flag? */ - nfs_mark_uptodate(page, offset, count); nfs_unlock_request(req); return 0; } @@ -273,8 +271,6 @@ static int nfs_page_async_flush(struct n * request as dirty (in which case we don't care). */ spin_unlock(req_lock); - /* Prevent deadlock! */ - nfs_pageio_complete(pgio); ret = nfs_wait_on_request(req); nfs_release_request(req); if (ret != 0) @@ -321,6 +317,8 @@ static int nfs_writepage_locked(struct p pgio = &mypgio; } + nfs_pageio_cond_complete(pgio, page->index); + err = nfs_page_async_flush(pgio, page); if (err <= 0) goto out; @@ -329,6 +327,8 @@ static int nfs_writepage_locked(struct p if (!offset) goto out; + nfs_pageio_cond_complete(pgio, page->index); + ctx = nfs_find_open_context(inode, NULL, FMODE_WRITE); if (ctx == NULL) { err = -EBADF; @@ -749,7 +749,12 @@ int nfs_updatepage(struct file *file, st static void nfs_writepage_release(struct nfs_page *req) { - if (PageError(req->wb_page) || !nfs_reschedule_unstable_write(req)) { + if (PageError(req->wb_page)) { + nfs_end_page_writeback(req->wb_page); + nfs_inode_remove_request(req); + } else if (!nfs_reschedule_unstable_write(req)) { + /* Set the PG_uptodate flag */ + nfs_mark_uptodate(req->wb_page, req->wb_pgbase, req->wb_bytes); nfs_end_page_writeback(req->wb_page); nfs_inode_remove_request(req); } else @@ -1037,6 +1042,8 @@ static void nfs_writeback_done_full(stru dprintk(" marked for commit\n"); goto next; } + /* Set the PG_uptodate flag? */ + nfs_mark_uptodate(page, req->wb_pgbase, req->wb_bytes); dprintk(" OK\n"); remove_request: nfs_end_page_writeback(page); @@ -1247,6 +1254,9 @@ static void nfs_commit_done(struct rpc_t * returned by the server against all stored verfs. */ if (!memcmp(req->wb_verf.verifier, data->verf.verifier, sizeof(data->verf.verifier))) { /* We have a match */ + /* Set the PG_uptodate flag */ + nfs_mark_uptodate(req->wb_page, req->wb_pgbase, + req->wb_bytes); nfs_inode_remove_request(req); dprintk(" OK\n"); goto next; diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index 7e7f33a..16116b8 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -391,6 +391,7 @@ enum { NFSPROC4_CLNT_GETACL, NFSPROC4_CLNT_SETACL, NFSPROC4_CLNT_FS_LOCATIONS, + NFSPROC4_CLNT_SECINFO, }; #endif diff --git a/include/linux/nfs4_mount.h b/include/linux/nfs4_mount.h index 26b4c83..ad1bd4a 100644 --- a/include/linux/nfs4_mount.h +++ b/include/linux/nfs4_mount.h @@ -65,6 +65,7 @@ #define NFS4_MOUNT_INTR 0x0002 /* 1 */ #define NFS4_MOUNT_NOCTO 0x0010 /* 1 */ #define NFS4_MOUNT_NOAC 0x0020 /* 1 */ #define NFS4_MOUNT_STRICTLOCK 0x1000 /* 1 */ +#define NFS4_MOUNT_UNSHARED 0x8000 /* 1 */ #define NFS4_MOUNT_FLAGMASK 0xFFFF #endif diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 0543439..6f07ad6 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -318,6 +318,7 @@ static inline void nfs_fattr_init(struct { fattr->valid = 0; fattr->time_start = jiffies; + fattr->flavor = RPC_AUTH_MAXFLAVOR; } /* diff --git a/include/linux/nfs_mount.h b/include/linux/nfs_mount.h index cc8b9c5..3e3b521 100644 --- a/include/linux/nfs_mount.h +++ b/include/linux/nfs_mount.h @@ -62,6 +62,7 @@ #define NFS_MOUNT_NOACL 0x0800 /* 4 */ #define NFS_MOUNT_STRICTLOCK 0x1000 /* reserved for NFSv4 */ #define NFS_MOUNT_SECFLAVOUR 0x2000 /* 5 */ #define NFS_MOUNT_NORDIRPLUS 0x4000 /* 5 */ +#define NFS_MOUNT_UNSHARED 0x8000 /* 5 */ #define NFS_MOUNT_FLAGMASK 0xFFFF #endif diff --git a/include/linux/nfs_page.h b/include/linux/nfs_page.h index 41afab6..bd193af 100644 --- a/include/linux/nfs_page.h +++ b/include/linux/nfs_page.h @@ -81,6 +81,7 @@ extern void nfs_pageio_init(struct nfs_p extern int nfs_pageio_add_request(struct nfs_pageio_descriptor *, struct nfs_page *); extern void nfs_pageio_complete(struct nfs_pageio_descriptor *desc); +extern void nfs_pageio_cond_complete(struct nfs_pageio_descriptor *, pgoff_t); extern int nfs_wait_on_request(struct nfs_page *); extern void nfs_unlock_request(struct nfs_page *req); extern int nfs_set_page_writeback_locked(struct nfs_page *req); diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h index 10c26ed..f8c55e5 100644 --- a/include/linux/nfs_xdr.h +++ b/include/linux/nfs_xdr.h @@ -56,6 +56,7 @@ struct nfs_fattr { __u64 change_attr; /* NFSv4 change attribute */ __u64 pre_change_attr;/* pre-op NFSv4 change attribute */ unsigned long time_start; + rpc_authflavor_t flavor; /* wrongsec handling */ }; #define NFS_ATTR_WCC 0x0001 /* pre-op WCC data */ @@ -320,6 +321,17 @@ struct nfs_renameargs { unsigned int tolen; }; +struct nfs4_secinfo_arg { + const struct nfs_fh * dir_fh; + const struct qstr * name; +}; + +#define NFS4_SECINFO_MAXFLAVORS 12 +struct nfs4_secinfo_res { + unsigned int fl_num; + rpc_authflavor_t flavors[NFS4_SECINFO_MAXFLAVORS]; +}; + struct nfs_setattrargs { struct nfs_fh * fh; nfs4_stateid stateid; diff --git a/include/linux/sunrpc/gss_api.h b/include/linux/sunrpc/gss_api.h index 5eca9e4..ac71e2b 100644 --- a/include/linux/sunrpc/gss_api.h +++ b/include/linux/sunrpc/gss_api.h @@ -60,6 +60,7 @@ u32 gss_delete_sec_context( u32 gss_pseudoflavor_to_service(struct gss_api_mech *, u32 pseudoflavor); char *gss_service_to_auth_domain_name(struct gss_api_mech *, u32 service); +u32 gss_triple_to_pseudoflavor(u32 oidlen, char *oiddata, u32 qop, u32 service); struct pf_desc { u32 pseudoflavor; diff --git a/net/sunrpc/auth_gss/gss_mech_switch.c b/net/sunrpc/auth_gss/gss_mech_switch.c index 2687251..b1eebba 100644 --- a/net/sunrpc/auth_gss/gss_mech_switch.c +++ b/net/sunrpc/auth_gss/gss_mech_switch.c @@ -194,6 +194,44 @@ gss_mech_get_by_pseudoflavor(u32 pseudof EXPORT_SYMBOL(gss_mech_get_by_pseudoflavor); u32 +gss_triple_to_pseudoflavor(u32 oid_len, char *oid_data, u32 qop, u32 service) +{ + struct gss_api_mech *pos, *gm = NULL; + struct xdr_netobj oid = { + .len = oid_len, + .data = oid_data, + }; + u32 pseudoflavor = RPC_AUTH_MAXFLAVOR; + int i; + + /* Only support default QOP */ + if (qop != 0) + goto out; + + spin_lock(®istered_mechs_lock); + list_for_each_entry(pos, ®istered_mechs, gm_list) { + if (g_OID_equal(&oid, &pos->gm_oid)) { + gm = pos; + break; + } + } + if (!gm) + goto out_locked; + + for (i = 0; i < gm->gm_pf_num; i++) { + if (service == gm->gm_pfs[i].service) { + pseudoflavor = gm->gm_pfs[i].pseudoflavor; + break; + } + } +out_locked: + spin_unlock(®istered_mechs_lock); +out: + return pseudoflavor; +} +EXPORT_SYMBOL(gss_triple_to_pseudoflavor); + +u32 gss_pseudoflavor_to_service(struct gss_api_mech *gm, u32 pseudoflavor) { int i;