All of the above --- fs/nfs/client.c | 107 ++++++++++++++++++++++++++++- fs/nfs/direct.c | 71 ++++++++++++------- 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, 554 insertions(+), 54 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(struct nfs_server *source, { 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(struct nfs_server *source, 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..fd985f9 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -122,19 +122,25 @@ ssize_t nfs_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_ return -EINVAL; } -static void nfs_direct_dirty_pages(struct page **pages, int npages) +static void nfs_direct_dirty_pages(struct page **pages, unsigned int pgbase, size_t count) { - int i; + unsigned int npages; + unsigned int i; + + if (count == 0) + return; + pages += (pgbase >> PAGE_SHIFT); + npages = (count + (pgbase & ~PAGE_MASK) + PAGE_SIZE - 1) >> PAGE_SHIFT; for (i = 0; i < npages; i++) { struct page *page = pages[i]; if (!PageCompound(page)) - set_page_dirty_lock(page); + set_page_dirty(page); } } -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]); } @@ -224,17 +230,18 @@ static void nfs_direct_read_result(struct rpc_task *task, void *calldata) if (nfs_readpage_result(task, data) != 0) return; - nfs_direct_dirty_pages(data->pagevec, data->npages); - nfs_direct_release_pages(data->pagevec, data->npages); - spin_lock(&dreq->lock); - - if (likely(task->tk_status >= 0)) - dreq->count += data->res.count; - else + if (unlikely(task->tk_status < 0)) { dreq->error = task->tk_status; - - spin_unlock(&dreq->lock); + spin_unlock(&dreq->lock); + } else { + dreq->count += data->res.count; + spin_unlock(&dreq->lock); + nfs_direct_dirty_pages(data->pagevec, + data->args.pgbase, + data->res.count); + } + nfs_direct_release_pages(data->pagevec, data->npages); if (put_dreq(dreq)) nfs_direct_complete(dreq); @@ -279,12 +286,20 @@ static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned lo 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) { + bytes = result * PAGE_SIZE; + if (bytes <= pgbase) { + nfs_direct_release_pages(data->pagevec, result); + nfs_readdata_release(data); + break; + } + bytes -= pgbase; + data->npages = result; + } get_dreq(dreq); @@ -610,12 +625,20 @@ static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned l 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) { + bytes = result * PAGE_SIZE; + if (bytes <= pgbase) { + nfs_direct_release_pages(data->pagevec, result); + nfs_writedata_release(data); + break; + } + bytes -= pgbase; + data->npages = result; + } get_dreq(dreq); @@ -744,10 +767,8 @@ ssize_t nfs_file_direct_read(struct kiocb *iocb, const struct iovec *iov, (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 +816,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 +829,7 @@ ssize_t nfs_file_direct_write(struct kiocb *iocb, const struct iovec *iov, (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 dentry *, const char *, void *, size_t); 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(struct rpc_task *, const struct nfs_server *) 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_server *server, struct nfs_fh *dirfh, } 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 inode *dir, struct qstr *name, .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 *dir, struct qstr *name, struct nfs_fh 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 *clnt, long *timeout) 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 struct nfs_server *server, int errorcode, 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 struct nfs_server *server, int errorcode, 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 *dir, struct qstr *name, 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 @@ -92,6 +93,8 @@ static int nfs4_stat_to_errno(int); 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 @@ static int nfs4_stat_to_errno(int); #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 @@ static int nfs4_stat_to_errno(int); 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_pageio_descriptor *desc) 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, struct page *page) */ 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, struct page *page) 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 page *page) 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(struct seq_file *m, struct nfs_server *nfss, { 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 super_block *sb, void *data) { 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_type *fs_type, 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_type *fs_type, 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_system_type *fs_type, int flags, 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_system_type *fs_type, int flags, 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_system_type *fs_type, 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_system_type *fs_type, 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_system_type *fs_type, int flags, 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_system_type *fs_type, int flags, 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 file_system_type *fs_type, int flags, 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 file_system_type *fs_type, int flags, 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 nfs_open_context *ctx, struct page *page, } /* 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 nfs_pageio_descriptor *pgio, * 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 page *page, struct writeback_control *wbc 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 page *page, struct writeback_control *wbc 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, struct page *page, 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(struct rpc_task *task, void *calldata) 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_task *task, void *calldata) * 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 @@ struct nfs4_mount_data { #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 nfs_fattr *fattr) { 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 @@ struct nfs_mount_data { #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_pageio_descriptor *desc, 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 pseudoflavor) 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;