diff options
Diffstat (limited to 'fs/nfsd/export.c')
| -rw-r--r-- | fs/nfsd/export.c | 1517 |
1 files changed, 825 insertions, 692 deletions
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 057aff74550..13b85f94d9e 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -1,7 +1,4 @@ -#define MSNFS /* HACK HACK */ /* - * linux/fs/nfsd/export.c - * * NFS exporting and validation. * * We maintain a list of clients, each of which has a list of @@ -14,34 +11,17 @@ * Copyright (C) 1995, 1996 Olaf Kirch, <okir@monad.swb.de> */ -#include <linux/unistd.h> #include <linux/slab.h> -#include <linux/sched.h> -#include <linux/stat.h> -#include <linux/in.h> -#include <linux/seq_file.h> -#include <linux/syscalls.h> -#include <linux/rwsem.h> -#include <linux/dcache.h> #include <linux/namei.h> -#include <linux/mount.h> -#include <linux/hash.h> #include <linux/module.h> +#include <linux/exportfs.h> +#include <linux/sunrpc/svc_xprt.h> -#include <linux/sunrpc/svc.h> -#include <linux/nfsd/nfsd.h> -#include <linux/nfsd/nfsfh.h> -#include <linux/nfsd/syscall.h> -#include <linux/lockd/bind.h> +#include "nfsd.h" +#include "nfsfh.h" +#include "netns.h" #define NFSDDBG_FACILITY NFSDDBG_EXPORT -#define NFSD_PARANOIA 1 - -typedef struct auth_domain svc_client; -typedef struct svc_export svc_export; - -static void exp_do_unexport(svc_export *unexp); -static int exp_verify_string(char *cp, int max); /* * We have two caches. @@ -55,29 +35,16 @@ static int exp_verify_string(char *cp, int max); #define EXPKEY_HASHBITS 8 #define EXPKEY_HASHMAX (1 << EXPKEY_HASHBITS) #define EXPKEY_HASHMASK (EXPKEY_HASHMAX -1) -static struct cache_head *expkey_table[EXPKEY_HASHMAX]; -static inline int svc_expkey_hash(struct svc_expkey *item) +static void expkey_put(struct kref *ref) { - int hash = item->ek_fsidtype; - char * cp = (char*)item->ek_fsid; - int len = key_len(item->ek_fsidtype); + struct svc_expkey *key = container_of(ref, struct svc_expkey, h.ref); - hash ^= hash_mem(cp, len, EXPKEY_HASHBITS); - hash ^= hash_ptr(item->ek_client, EXPKEY_HASHBITS); - return hash & EXPKEY_HASHMASK; -} - -void expkey_put(struct cache_head *item, struct cache_detail *cd) -{ - if (cache_put(item, cd)) { - struct svc_expkey *key = container_of(item, struct svc_expkey, h); - if (test_bit(CACHE_VALID, &item->flags) && - !test_bit(CACHE_NEGATIVE, &item->flags)) - exp_put(key->ek_export); - auth_domain_put(key->ek_client); - kfree(key); - } + if (test_bit(CACHE_VALID, &key->h.flags) && + !test_bit(CACHE_NEGATIVE, &key->h.flags)) + path_put(&key->ek_path); + auth_domain_put(key->ek_client); + kfree(key); } static void expkey_request(struct cache_detail *cd, @@ -95,10 +62,13 @@ static void expkey_request(struct cache_detail *cd, (*bpp)[-1] = '\n'; } -static struct svc_expkey *svc_expkey_lookup(struct svc_expkey *, int); +static struct svc_expkey *svc_expkey_update(struct cache_detail *cd, struct svc_expkey *new, + struct svc_expkey *old); +static struct svc_expkey *svc_expkey_lookup(struct cache_detail *cd, struct svc_expkey *); + static int expkey_parse(struct cache_detail *cd, char *mesg, int mlen) { - /* client fsidtype fsid [path] */ + /* client fsidtype fsid expiry [path] */ char *buf; int len; struct auth_domain *dom = NULL; @@ -106,14 +76,16 @@ static int expkey_parse(struct cache_detail *cd, char *mesg, int mlen) int fsidtype; char *ep; struct svc_expkey key; + struct svc_expkey *ek = NULL; - if (mesg[mlen-1] != '\n') + if (mesg[mlen - 1] != '\n') return -EINVAL; mesg[mlen-1] = 0; buf = kmalloc(PAGE_SIZE, GFP_KERNEL); err = -ENOMEM; - if (!buf) goto out; + if (!buf) + goto out; err = -EINVAL; if ((len=qword_get(&mesg, buf, PAGE_SIZE)) <= 0) @@ -132,7 +104,7 @@ static int expkey_parse(struct cache_detail *cd, char *mesg, int mlen) if (*ep) goto out; dprintk("found fsidtype %d\n", fsidtype); - if (fsidtype > 2) + if (key_len(fsidtype)==0) /* invalid type */ goto out; if ((len=qword_get(&mesg, buf, PAGE_SIZE)) <= 0) goto out; @@ -150,48 +122,42 @@ static int expkey_parse(struct cache_detail *cd, char *mesg, int mlen) key.ek_fsidtype = fsidtype; memcpy(key.ek_fsid, buf, len); + ek = svc_expkey_lookup(cd, &key); + err = -ENOMEM; + if (!ek) + goto out; + /* now we want a pathname, or empty meaning NEGATIVE */ - if ((len=qword_get(&mesg, buf, PAGE_SIZE)) < 0) + err = -EINVAL; + len = qword_get(&mesg, buf, PAGE_SIZE); + if (len < 0) goto out; dprintk("Path seems to be <%s>\n", buf); err = 0; if (len == 0) { - struct svc_expkey *ek; set_bit(CACHE_NEGATIVE, &key.h.flags); - ek = svc_expkey_lookup(&key, 1); - if (ek) - expkey_put(&ek->h, &svc_expkey_cache); + ek = svc_expkey_update(cd, &key, ek); + if (!ek) + err = -ENOMEM; } else { - struct nameidata nd; - struct svc_expkey *ek; - struct svc_export *exp; - err = path_lookup(buf, 0, &nd); + err = kern_path(buf, 0, &key.ek_path); if (err) goto out; dprintk("Found the path %s\n", buf); - exp = exp_get_by_name(dom, nd.mnt, nd.dentry, NULL); - - err = -ENOENT; - if (!exp) - goto out_nd; - key.ek_export = exp; - dprintk("And found export\n"); - - ek = svc_expkey_lookup(&key, 1); - if (ek) - expkey_put(&ek->h, &svc_expkey_cache); - exp_put(exp); - err = 0; - out_nd: - path_release(&nd); + + ek = svc_expkey_update(cd, &key, ek); + if (!ek) + err = -ENOMEM; + path_put(&key.ek_path); } cache_flush(); out: + if (ek) + cache_put(&ek->h, cd); if (dom) auth_domain_put(dom); - if (buf) - kfree(buf); + kfree(buf); return err; } @@ -200,90 +166,153 @@ static int expkey_show(struct seq_file *m, struct cache_head *h) { struct svc_expkey *ek ; + int i; if (h ==NULL) { seq_puts(m, "#domain fsidtype fsid [path]\n"); return 0; } ek = container_of(h, struct svc_expkey, h); - seq_printf(m, "%s %d 0x%08x", ek->ek_client->name, - ek->ek_fsidtype, ek->ek_fsid[0]); - if (ek->ek_fsidtype != 1) - seq_printf(m, "%08x", ek->ek_fsid[1]); - if (ek->ek_fsidtype == 2) - seq_printf(m, "%08x", ek->ek_fsid[2]); + seq_printf(m, "%s %d 0x", ek->ek_client->name, + ek->ek_fsidtype); + for (i=0; i < key_len(ek->ek_fsidtype)/4; i++) + seq_printf(m, "%08x", ek->ek_fsid[i]); if (test_bit(CACHE_VALID, &h->flags) && !test_bit(CACHE_NEGATIVE, &h->flags)) { seq_printf(m, " "); - seq_path(m, ek->ek_export->ex_mnt, ek->ek_export->ex_dentry, "\\ \t\n"); + seq_path(m, &ek->ek_path, "\\ \t\n"); } seq_printf(m, "\n"); return 0; } - -struct cache_detail svc_expkey_cache = { + +static inline int expkey_match (struct cache_head *a, struct cache_head *b) +{ + struct svc_expkey *orig = container_of(a, struct svc_expkey, h); + struct svc_expkey *new = container_of(b, struct svc_expkey, h); + + if (orig->ek_fsidtype != new->ek_fsidtype || + orig->ek_client != new->ek_client || + memcmp(orig->ek_fsid, new->ek_fsid, key_len(orig->ek_fsidtype)) != 0) + return 0; + return 1; +} + +static inline void expkey_init(struct cache_head *cnew, + struct cache_head *citem) +{ + struct svc_expkey *new = container_of(cnew, struct svc_expkey, h); + struct svc_expkey *item = container_of(citem, struct svc_expkey, h); + + kref_get(&item->ek_client->ref); + new->ek_client = item->ek_client; + new->ek_fsidtype = item->ek_fsidtype; + + memcpy(new->ek_fsid, item->ek_fsid, sizeof(new->ek_fsid)); +} + +static inline void expkey_update(struct cache_head *cnew, + struct cache_head *citem) +{ + struct svc_expkey *new = container_of(cnew, struct svc_expkey, h); + struct svc_expkey *item = container_of(citem, struct svc_expkey, h); + + new->ek_path = item->ek_path; + path_get(&item->ek_path); +} + +static struct cache_head *expkey_alloc(void) +{ + struct svc_expkey *i = kmalloc(sizeof(*i), GFP_KERNEL); + if (i) + return &i->h; + else + return NULL; +} + +static struct cache_detail svc_expkey_cache_template = { .owner = THIS_MODULE, .hash_size = EXPKEY_HASHMAX, - .hash_table = expkey_table, .name = "nfsd.fh", .cache_put = expkey_put, .cache_request = expkey_request, .cache_parse = expkey_parse, .cache_show = expkey_show, + .match = expkey_match, + .init = expkey_init, + .update = expkey_update, + .alloc = expkey_alloc, }; -static inline int svc_expkey_match (struct svc_expkey *a, struct svc_expkey *b) +static int +svc_expkey_hash(struct svc_expkey *item) { - if (a->ek_fsidtype != b->ek_fsidtype || - a->ek_client != b->ek_client || - memcmp(a->ek_fsid, b->ek_fsid, key_len(a->ek_fsidtype)) != 0) - return 0; - return 1; + int hash = item->ek_fsidtype; + char * cp = (char*)item->ek_fsid; + int len = key_len(item->ek_fsidtype); + + hash ^= hash_mem(cp, len, EXPKEY_HASHBITS); + hash ^= hash_ptr(item->ek_client, EXPKEY_HASHBITS); + hash &= EXPKEY_HASHMASK; + return hash; } -static inline void svc_expkey_init(struct svc_expkey *new, struct svc_expkey *item) +static struct svc_expkey * +svc_expkey_lookup(struct cache_detail *cd, struct svc_expkey *item) { - cache_get(&item->ek_client->h); - new->ek_client = item->ek_client; - new->ek_fsidtype = item->ek_fsidtype; - new->ek_fsid[0] = item->ek_fsid[0]; - new->ek_fsid[1] = item->ek_fsid[1]; - new->ek_fsid[2] = item->ek_fsid[2]; + struct cache_head *ch; + int hash = svc_expkey_hash(item); + + ch = sunrpc_cache_lookup(cd, &item->h, hash); + if (ch) + return container_of(ch, struct svc_expkey, h); + else + return NULL; } -static inline void svc_expkey_update(struct svc_expkey *new, struct svc_expkey *item) +static struct svc_expkey * +svc_expkey_update(struct cache_detail *cd, struct svc_expkey *new, + struct svc_expkey *old) { - cache_get(&item->ek_export->h); - new->ek_export = item->ek_export; + struct cache_head *ch; + int hash = svc_expkey_hash(new); + + ch = sunrpc_cache_update(cd, &new->h, &old->h, hash); + if (ch) + return container_of(ch, struct svc_expkey, h); + else + return NULL; } -static DefineSimpleCacheLookup(svc_expkey,0) /* no inplace updates */ #define EXPORT_HASHBITS 8 #define EXPORT_HASHMAX (1<< EXPORT_HASHBITS) -#define EXPORT_HASHMASK (EXPORT_HASHMAX -1) - -static struct cache_head *export_table[EXPORT_HASHMAX]; -static inline int svc_export_hash(struct svc_export *item) +static void nfsd4_fslocs_free(struct nfsd4_fs_locations *fsloc) { - int rv; + struct nfsd4_fs_location *locations = fsloc->locations; + int i; - rv = hash_ptr(item->ex_client, EXPORT_HASHBITS); - rv ^= hash_ptr(item->ex_dentry, EXPORT_HASHBITS); - rv ^= hash_ptr(item->ex_mnt, EXPORT_HASHBITS); - return rv; + if (!locations) + return; + + for (i = 0; i < fsloc->locations_count; i++) { + kfree(locations[i].path); + kfree(locations[i].hosts); + } + + kfree(locations); + fsloc->locations = NULL; } -void svc_export_put(struct cache_head *item, struct cache_detail *cd) +static void svc_export_put(struct kref *ref) { - if (cache_put(item, cd)) { - struct svc_export *exp = container_of(item, struct svc_export, h); - dput(exp->ex_dentry); - mntput(exp->ex_mnt); - auth_domain_put(exp->ex_client); - kfree(exp); - } + struct svc_export *exp = container_of(ref, struct svc_export, h.ref); + path_put(&exp->ex_path); + auth_domain_put(exp->ex_client); + nfsd4_fslocs_free(&exp->ex_fslocs); + kfree(exp->ex_uuid); + kfree(exp); } static void svc_export_request(struct cache_detail *cd, @@ -295,7 +324,7 @@ static void svc_export_request(struct cache_detail *cd, char *pth; qword_add(bpp, blen, exp->ex_client->name); - pth = d_path(exp->ex_dentry, exp->ex_mnt, *bpp, *blen); + pth = d_path(&exp->ex_path, *bpp, *blen); if (IS_ERR(pth)) { /* is this correct? */ (*bpp)[0] = '\n'; @@ -305,43 +334,177 @@ static void svc_export_request(struct cache_detail *cd, (*bpp)[-1] = '\n'; } -static struct svc_export *svc_export_lookup(struct svc_export *, int); +static struct svc_export *svc_export_update(struct svc_export *new, + struct svc_export *old); +static struct svc_export *svc_export_lookup(struct svc_export *); -static int check_export(struct inode *inode, int flags) +static int check_export(struct inode *inode, int *flags, unsigned char *uuid) { - /* We currently export only dirs and regular files. - * This is what umountd does. + /* + * We currently export only dirs, regular files, and (for v4 + * pseudoroot) symlinks. */ if (!S_ISDIR(inode->i_mode) && + !S_ISLNK(inode->i_mode) && !S_ISREG(inode->i_mode)) return -ENOTDIR; + /* + * Mountd should never pass down a writeable V4ROOT export, but, + * just to make sure: + */ + if (*flags & NFSEXP_V4ROOT) + *flags |= NFSEXP_READONLY; + /* There are two requirements on a filesystem to be exportable. * 1: We must be able to identify the filesystem from a number. * either a device number (so FS_REQUIRES_DEV needed) - * or an FSID number (so NFSEXP_FSID needed). + * or an FSID number (so NFSEXP_FSID or ->uuid is needed). * 2: We must be able to find an inode from a filehandle. * This means that s_export_op must be set. */ if (!(inode->i_sb->s_type->fs_flags & FS_REQUIRES_DEV) && - !(flags & NFSEXP_FSID)) { - dprintk("exp_export: export of non-dev fs without fsid"); + !(*flags & NFSEXP_FSID) && + uuid == NULL) { + dprintk("exp_export: export of non-dev fs without fsid\n"); return -EINVAL; } - if (!inode->i_sb->s_export_op) { + + if (!inode->i_sb->s_export_op || + !inode->i_sb->s_export_op->fh_to_dentry) { dprintk("exp_export: export of invalid fs type.\n"); return -EINVAL; } - /* Ok, we can export it */; - if (!inode->i_sb->s_export_op->find_exported_dentry) - inode->i_sb->s_export_op->find_exported_dentry = - find_exported_dentry; return 0; } +#ifdef CONFIG_NFSD_V4 + +static int +fsloc_parse(char **mesg, char *buf, struct nfsd4_fs_locations *fsloc) +{ + int len; + int migrated, i, err; + + /* more than one fsloc */ + if (fsloc->locations) + return -EINVAL; + + /* listsize */ + err = get_uint(mesg, &fsloc->locations_count); + if (err) + return err; + if (fsloc->locations_count > MAX_FS_LOCATIONS) + return -EINVAL; + if (fsloc->locations_count == 0) + return 0; + + fsloc->locations = kzalloc(fsloc->locations_count + * sizeof(struct nfsd4_fs_location), GFP_KERNEL); + if (!fsloc->locations) + return -ENOMEM; + for (i=0; i < fsloc->locations_count; i++) { + /* colon separated host list */ + err = -EINVAL; + len = qword_get(mesg, buf, PAGE_SIZE); + if (len <= 0) + goto out_free_all; + err = -ENOMEM; + fsloc->locations[i].hosts = kstrdup(buf, GFP_KERNEL); + if (!fsloc->locations[i].hosts) + goto out_free_all; + err = -EINVAL; + /* slash separated path component list */ + len = qword_get(mesg, buf, PAGE_SIZE); + if (len <= 0) + goto out_free_all; + err = -ENOMEM; + fsloc->locations[i].path = kstrdup(buf, GFP_KERNEL); + if (!fsloc->locations[i].path) + goto out_free_all; + } + /* migrated */ + err = get_int(mesg, &migrated); + if (err) + goto out_free_all; + err = -EINVAL; + if (migrated < 0 || migrated > 1) + goto out_free_all; + fsloc->migrated = migrated; + return 0; +out_free_all: + nfsd4_fslocs_free(fsloc); + return err; +} + +static int secinfo_parse(char **mesg, char *buf, struct svc_export *exp) +{ + struct exp_flavor_info *f; + u32 listsize; + int err; + + /* more than one secinfo */ + if (exp->ex_nflavors) + return -EINVAL; + + err = get_uint(mesg, &listsize); + if (err) + return err; + if (listsize > MAX_SECINFO_LIST) + return -EINVAL; + + for (f = exp->ex_flavors; f < exp->ex_flavors + listsize; f++) { + err = get_uint(mesg, &f->pseudoflavor); + if (err) + return err; + /* + * XXX: It would be nice to also check whether this + * pseudoflavor is supported, so we can discover the + * problem at export time instead of when a client fails + * to authenticate. + */ + err = get_uint(mesg, &f->flags); + if (err) + return err; + /* Only some flags are allowed to differ between flavors: */ + if (~NFSEXP_SECINFO_FLAGS & (f->flags ^ exp->ex_flags)) + return -EINVAL; + } + exp->ex_nflavors = listsize; + return 0; +} + +#else /* CONFIG_NFSD_V4 */ +static inline int +fsloc_parse(char **mesg, char *buf, struct nfsd4_fs_locations *fsloc){return 0;} +static inline int +secinfo_parse(char **mesg, char *buf, struct svc_export *exp) { return 0; } +#endif + +static inline int +uuid_parse(char **mesg, char *buf, unsigned char **puuid) +{ + int len; + + /* more than one uuid */ + if (*puuid) + return -EINVAL; + + /* expect a 16 byte uuid encoded as \xXXXX... */ + len = qword_get(mesg, buf, PAGE_SIZE); + if (len != EX_UUID_LEN) + return -EINVAL; + + *puuid = kmemdup(buf, EX_UUID_LEN, GFP_KERNEL); + if (*puuid == NULL) + return -ENOMEM; + + return 0; +} + static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen) { /* client path expiry [flags anonuid anongid fsid] */ @@ -349,24 +512,22 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen) int len; int err; struct auth_domain *dom = NULL; - struct nameidata nd; - struct svc_export exp, *expp; + struct svc_export exp = {}, *expp; int an_int; - nd.dentry = NULL; - if (mesg[mlen-1] != '\n') return -EINVAL; mesg[mlen-1] = 0; buf = kmalloc(PAGE_SIZE, GFP_KERNEL); - err = -ENOMEM; - if (!buf) goto out; + if (!buf) + return -ENOMEM; /* client */ - len = qword_get(&mesg, buf, PAGE_SIZE); err = -EINVAL; - if (len <= 0) goto out; + len = qword_get(&mesg, buf, PAGE_SIZE); + if (len <= 0) + goto out; err = -ENOENT; dom = auth_domain_find(buf); @@ -375,65 +536,118 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen) /* path */ err = -EINVAL; - if ((len=qword_get(&mesg, buf, PAGE_SIZE)) <= 0) - goto out; - err = path_lookup(buf, 0, &nd); - if (err) goto out; + if ((len = qword_get(&mesg, buf, PAGE_SIZE)) <= 0) + goto out1; + + err = kern_path(buf, 0, &exp.ex_path); + if (err) + goto out1; - exp.h.flags = 0; exp.ex_client = dom; - exp.ex_mnt = nd.mnt; - exp.ex_dentry = nd.dentry; + exp.cd = cd; /* expiry */ err = -EINVAL; exp.h.expiry_time = get_expiry(&mesg); if (exp.h.expiry_time == 0) - goto out; + goto out3; /* flags */ err = get_int(&mesg, &an_int); - if (err == -ENOENT) + if (err == -ENOENT) { + err = 0; set_bit(CACHE_NEGATIVE, &exp.h.flags); - else { - if (err || an_int < 0) goto out; + } else { + if (err || an_int < 0) + goto out3; exp.ex_flags= an_int; /* anon uid */ err = get_int(&mesg, &an_int); - if (err) goto out; - exp.ex_anon_uid= an_int; + if (err) + goto out3; + exp.ex_anon_uid= make_kuid(&init_user_ns, an_int); /* anon gid */ err = get_int(&mesg, &an_int); - if (err) goto out; - exp.ex_anon_gid= an_int; + if (err) + goto out3; + exp.ex_anon_gid= make_kgid(&init_user_ns, an_int); /* fsid */ err = get_int(&mesg, &an_int); - if (err) goto out; + if (err) + goto out3; exp.ex_fsid = an_int; - err = check_export(nd.dentry->d_inode, exp.ex_flags); - if (err) goto out; + while ((len = qword_get(&mesg, buf, PAGE_SIZE)) > 0) { + if (strcmp(buf, "fsloc") == 0) + err = fsloc_parse(&mesg, buf, &exp.ex_fslocs); + else if (strcmp(buf, "uuid") == 0) + err = uuid_parse(&mesg, buf, &exp.ex_uuid); + else if (strcmp(buf, "secinfo") == 0) + err = secinfo_parse(&mesg, buf, &exp); + else + /* quietly ignore unknown words and anything + * following. Newer user-space can try to set + * new values, then see what the result was. + */ + break; + if (err) + goto out4; + } + + err = check_export(exp.ex_path.dentry->d_inode, &exp.ex_flags, + exp.ex_uuid); + if (err) + goto out4; + /* + * No point caching this if it would immediately expire. + * Also, this protects exportfs's dummy export from the + * anon_uid/anon_gid checks: + */ + if (exp.h.expiry_time < seconds_since_boot()) + goto out4; + /* + * For some reason exportfs has been passing down an + * invalid (-1) uid & gid on the "dummy" export which it + * uses to test export support. To make sure exportfs + * sees errors from check_export we therefore need to + * delay these checks till after check_export: + */ + err = -EINVAL; + if (!uid_valid(exp.ex_anon_uid)) + goto out4; + if (!gid_valid(exp.ex_anon_gid)) + goto out4; + err = 0; } - expp = svc_export_lookup(&exp, 1); + expp = svc_export_lookup(&exp); if (expp) - exp_put(expp); - err = 0; + expp = svc_export_update(&exp, expp); + else + err = -ENOMEM; cache_flush(); - out: - if (nd.dentry) - path_release(&nd); - if (dom) - auth_domain_put(dom); - if (buf) - kfree(buf); + if (expp == NULL) + err = -ENOMEM; + else + exp_put(expp); +out4: + nfsd4_fslocs_free(&exp.ex_fslocs); + kfree(exp.ex_uuid); +out3: + path_put(&exp.ex_path); +out1: + auth_domain_put(dom); +out: + kfree(buf); return err; } -static void exp_flags(struct seq_file *m, int flag, int fsid, uid_t anonu, uid_t anong); +static void exp_flags(struct seq_file *m, int flag, int fsid, + kuid_t anonu, kgid_t anong, struct nfsd4_fs_locations *fslocs); +static void show_secinfo(struct seq_file *m, struct svc_export *exp); static int svc_export_show(struct seq_file *m, struct cache_detail *cd, @@ -446,426 +660,205 @@ static int svc_export_show(struct seq_file *m, return 0; } exp = container_of(h, struct svc_export, h); - seq_path(m, exp->ex_mnt, exp->ex_dentry, " \t\n\\"); + seq_path(m, &exp->ex_path, " \t\n\\"); seq_putc(m, '\t'); seq_escape(m, exp->ex_client->name, " \t\n\\"); seq_putc(m, '('); if (test_bit(CACHE_VALID, &h->flags) && - !test_bit(CACHE_NEGATIVE, &h->flags)) - exp_flags(m, exp->ex_flags, exp->ex_fsid, - exp->ex_anon_uid, exp->ex_anon_gid); + !test_bit(CACHE_NEGATIVE, &h->flags)) { + exp_flags(m, exp->ex_flags, exp->ex_fsid, + exp->ex_anon_uid, exp->ex_anon_gid, &exp->ex_fslocs); + if (exp->ex_uuid) { + int i; + seq_puts(m, ",uuid="); + for (i = 0; i < EX_UUID_LEN; i++) { + if ((i&3) == 0 && i) + seq_putc(m, ':'); + seq_printf(m, "%02x", exp->ex_uuid[i]); + } + } + show_secinfo(m, exp); + } seq_puts(m, ")\n"); return 0; } -struct cache_detail svc_export_cache = { - .owner = THIS_MODULE, - .hash_size = EXPORT_HASHMAX, - .hash_table = export_table, - .name = "nfsd.export", - .cache_put = svc_export_put, - .cache_request = svc_export_request, - .cache_parse = svc_export_parse, - .cache_show = svc_export_show, -}; - -static inline int svc_export_match(struct svc_export *a, struct svc_export *b) +static int svc_export_match(struct cache_head *a, struct cache_head *b) { - return a->ex_client == b->ex_client && - a->ex_dentry == b->ex_dentry && - a->ex_mnt == b->ex_mnt; + struct svc_export *orig = container_of(a, struct svc_export, h); + struct svc_export *new = container_of(b, struct svc_export, h); + return orig->ex_client == new->ex_client && + orig->ex_path.dentry == new->ex_path.dentry && + orig->ex_path.mnt == new->ex_path.mnt; } -static inline void svc_export_init(struct svc_export *new, struct svc_export *item) + +static void svc_export_init(struct cache_head *cnew, struct cache_head *citem) { - cache_get(&item->ex_client->h); + struct svc_export *new = container_of(cnew, struct svc_export, h); + struct svc_export *item = container_of(citem, struct svc_export, h); + + kref_get(&item->ex_client->ref); new->ex_client = item->ex_client; - new->ex_dentry = dget(item->ex_dentry); - new->ex_mnt = mntget(item->ex_mnt); + new->ex_path.dentry = dget(item->ex_path.dentry); + new->ex_path.mnt = mntget(item->ex_path.mnt); + new->ex_fslocs.locations = NULL; + new->ex_fslocs.locations_count = 0; + new->ex_fslocs.migrated = 0; + new->ex_uuid = NULL; + new->cd = item->cd; } -static inline void svc_export_update(struct svc_export *new, struct svc_export *item) +static void export_update(struct cache_head *cnew, struct cache_head *citem) { + struct svc_export *new = container_of(cnew, struct svc_export, h); + struct svc_export *item = container_of(citem, struct svc_export, h); + int i; + new->ex_flags = item->ex_flags; new->ex_anon_uid = item->ex_anon_uid; new->ex_anon_gid = item->ex_anon_gid; new->ex_fsid = item->ex_fsid; -} - -static DefineSimpleCacheLookup(svc_export,1) /* allow inplace updates */ - - -struct svc_expkey * -exp_find_key(svc_client *clp, int fsid_type, u32 *fsidv, struct cache_req *reqp) -{ - struct svc_expkey key, *ek; - int err; - - if (!clp) - return NULL; - - key.ek_client = clp; - key.ek_fsidtype = fsid_type; - memcpy(key.ek_fsid, fsidv, key_len(fsid_type)); - - ek = svc_expkey_lookup(&key, 0); - if (ek != NULL) - if ((err = cache_check(&svc_expkey_cache, &ek->h, reqp))) - ek = ERR_PTR(err); - return ek; -} - -static int exp_set_key(svc_client *clp, int fsid_type, u32 *fsidv, - struct svc_export *exp) -{ - struct svc_expkey key, *ek; - - key.ek_client = clp; - key.ek_fsidtype = fsid_type; - memcpy(key.ek_fsid, fsidv, key_len(fsid_type)); - key.ek_export = exp; - key.h.expiry_time = NEVER; - key.h.flags = 0; - - ek = svc_expkey_lookup(&key, 1); - if (ek) { - expkey_put(&ek->h, &svc_expkey_cache); - return 0; + new->ex_uuid = item->ex_uuid; + item->ex_uuid = NULL; + new->ex_fslocs.locations = item->ex_fslocs.locations; + item->ex_fslocs.locations = NULL; + new->ex_fslocs.locations_count = item->ex_fslocs.locations_count; + item->ex_fslocs.locations_count = 0; + new->ex_fslocs.migrated = item->ex_fslocs.migrated; + item->ex_fslocs.migrated = 0; + new->ex_nflavors = item->ex_nflavors; + for (i = 0; i < MAX_SECINFO_LIST; i++) { + new->ex_flavors[i] = item->ex_flavors[i]; } - return -ENOMEM; } -/* - * Find the client's export entry matching xdev/xino. - */ -static inline struct svc_expkey * -exp_get_key(svc_client *clp, dev_t dev, ino_t ino) -{ - u32 fsidv[3]; - - if (old_valid_dev(dev)) { - mk_fsid_v0(fsidv, dev, ino); - return exp_find_key(clp, 0, fsidv, NULL); - } - mk_fsid_v3(fsidv, dev, ino); - return exp_find_key(clp, 3, fsidv, NULL); -} - -/* - * Find the client's export entry matching fsid - */ -static inline struct svc_expkey * -exp_get_fsid_key(svc_client *clp, int fsid) -{ - u32 fsidv[2]; - - mk_fsid_v1(fsidv, fsid); - - return exp_find_key(clp, 1, fsidv, NULL); -} - -svc_export * -exp_get_by_name(svc_client *clp, struct vfsmount *mnt, struct dentry *dentry, - struct cache_req *reqp) +static struct cache_head *svc_export_alloc(void) { - struct svc_export *exp, key; - - if (!clp) + struct svc_export *i = kmalloc(sizeof(*i), GFP_KERNEL); + if (i) + return &i->h; + else return NULL; - - key.ex_client = clp; - key.ex_mnt = mnt; - key.ex_dentry = dentry; - - exp = svc_export_lookup(&key, 0); - if (exp != NULL) - switch (cache_check(&svc_export_cache, &exp->h, reqp)) { - case 0: break; - case -EAGAIN: - exp = ERR_PTR(-EAGAIN); - break; - default: - exp = NULL; - } - - return exp; -} - -/* - * Find the export entry for a given dentry. - */ -struct svc_export * -exp_parent(svc_client *clp, struct vfsmount *mnt, struct dentry *dentry, - struct cache_req *reqp) -{ - svc_export *exp; - - dget(dentry); - exp = exp_get_by_name(clp, mnt, dentry, reqp); - - while (exp == NULL && !IS_ROOT(dentry)) { - struct dentry *parent; - - parent = dget_parent(dentry); - dput(dentry); - dentry = parent; - exp = exp_get_by_name(clp, mnt, dentry, reqp); - } - dput(dentry); - return exp; -} - -/* - * Hashtable locking. Write locks are placed only by user processes - * wanting to modify export information. - * Write locking only done in this file. Read locking - * needed externally. - */ - -static DECLARE_RWSEM(hash_sem); - -void -exp_readlock(void) -{ - down_read(&hash_sem); } -static inline void -exp_writelock(void) -{ - down_write(&hash_sem); -} +static struct cache_detail svc_export_cache_template = { + .owner = THIS_MODULE, + .hash_size = EXPORT_HASHMAX, + .name = "nfsd.export", + .cache_put = svc_export_put, + .cache_request = svc_export_request, + .cache_parse = svc_export_parse, + .cache_show = svc_export_show, + .match = svc_export_match, + .init = svc_export_init, + .update = export_update, + .alloc = svc_export_alloc, +}; -void -exp_readunlock(void) +static int +svc_export_hash(struct svc_export *exp) { - up_read(&hash_sem); -} + int hash; -static inline void -exp_writeunlock(void) -{ - up_write(&hash_sem); + hash = hash_ptr(exp->ex_client, EXPORT_HASHBITS); + hash ^= hash_ptr(exp->ex_path.dentry, EXPORT_HASHBITS); + hash ^= hash_ptr(exp->ex_path.mnt, EXPORT_HASHBITS); + return hash; } -static void exp_fsid_unhash(struct svc_export *exp) +static struct svc_export * +svc_export_lookup(struct svc_export *exp) { - struct svc_expkey *ek; - - if ((exp->ex_flags & NFSEXP_FSID) == 0) - return; + struct cache_head *ch; + int hash = svc_export_hash(exp); - ek = exp_get_fsid_key(exp->ex_client, exp->ex_fsid); - if (ek && !IS_ERR(ek)) { - ek->h.expiry_time = get_seconds()-1; - expkey_put(&ek->h, &svc_expkey_cache); - } - svc_expkey_cache.nextcheck = get_seconds(); + ch = sunrpc_cache_lookup(exp->cd, &exp->h, hash); + if (ch) + return container_of(ch, struct svc_export, h); + else + return NULL; } -static int exp_fsid_hash(svc_client *clp, struct svc_export *exp) +static struct svc_export * +svc_export_update(struct svc_export *new, struct svc_export *old) { - u32 fsid[2]; - - if ((exp->ex_flags & NFSEXP_FSID) == 0) - return 0; + struct cache_head *ch; + int hash = svc_export_hash(old); - mk_fsid_v1(fsid, exp->ex_fsid); - return exp_set_key(clp, 1, fsid, exp); + ch = sunrpc_cache_update(old->cd, &new->h, &old->h, hash); + if (ch) + return container_of(ch, struct svc_export, h); + else + return NULL; } -static int exp_hash(struct auth_domain *clp, struct svc_export *exp) -{ - u32 fsid[2]; - struct inode *inode = exp->ex_dentry->d_inode; - dev_t dev = inode->i_sb->s_dev; - if (old_valid_dev(dev)) { - mk_fsid_v0(fsid, dev, inode->i_ino); - return exp_set_key(clp, 0, fsid, exp); - } - mk_fsid_v3(fsid, dev, inode->i_ino); - return exp_set_key(clp, 3, fsid, exp); -} - -static void exp_unhash(struct svc_export *exp) +static struct svc_expkey * +exp_find_key(struct cache_detail *cd, struct auth_domain *clp, int fsid_type, + u32 *fsidv, struct cache_req *reqp) { - struct svc_expkey *ek; - struct inode *inode = exp->ex_dentry->d_inode; - - ek = exp_get_key(exp->ex_client, inode->i_sb->s_dev, inode->i_ino); - if (ek && !IS_ERR(ek)) { - ek->h.expiry_time = get_seconds()-1; - expkey_put(&ek->h, &svc_expkey_cache); - } - svc_expkey_cache.nextcheck = get_seconds(); -} + struct svc_expkey key, *ek; + int err; -/* - * Export a file system. - */ -int -exp_export(struct nfsctl_export *nxp) -{ - svc_client *clp; - struct svc_export *exp = NULL; - struct svc_export new; - struct svc_expkey *fsid_key = NULL; - struct nameidata nd; - int err; - - /* Consistency check */ - err = -EINVAL; - if (!exp_verify_string(nxp->ex_path, NFS_MAXPATHLEN) || - !exp_verify_string(nxp->ex_client, NFSCLNT_IDMAX)) - goto out; - - dprintk("exp_export called for %s:%s (%x/%ld fl %x).\n", - nxp->ex_client, nxp->ex_path, - (unsigned)nxp->ex_dev, (long)nxp->ex_ino, - nxp->ex_flags); - - /* Try to lock the export table for update */ - exp_writelock(); - - /* Look up client info */ - if (!(clp = auth_domain_find(nxp->ex_client))) - goto out_unlock; + if (!clp) + return ERR_PTR(-ENOENT); + key.ek_client = clp; + key.ek_fsidtype = fsid_type; + memcpy(key.ek_fsid, fsidv, key_len(fsid_type)); - /* Look up the dentry */ - err = path_lookup(nxp->ex_path, 0, &nd); + ek = svc_expkey_lookup(cd, &key); + if (ek == NULL) + return ERR_PTR(-ENOMEM); + err = cache_check(cd, &ek->h, reqp); if (err) - goto out_unlock; - err = -EINVAL; - - exp = exp_get_by_name(clp, nd.mnt, nd.dentry, NULL); - - /* must make sure there won't be an ex_fsid clash */ - if ((nxp->ex_flags & NFSEXP_FSID) && - (fsid_key = exp_get_fsid_key(clp, nxp->ex_dev)) && - !IS_ERR(fsid_key) && - fsid_key->ek_export && - fsid_key->ek_export != exp) - goto finish; - - if (exp) { - /* just a flags/id/fsid update */ - - exp_fsid_unhash(exp); - exp->ex_flags = nxp->ex_flags; - exp->ex_anon_uid = nxp->ex_anon_uid; - exp->ex_anon_gid = nxp->ex_anon_gid; - exp->ex_fsid = nxp->ex_dev; - - err = exp_fsid_hash(clp, exp); - goto finish; - } - - err = check_export(nd.dentry->d_inode, nxp->ex_flags); - if (err) goto finish; - - err = -ENOMEM; + return ERR_PTR(err); + return ek; +} - dprintk("nfsd: creating export entry %p for client %p\n", exp, clp); +static struct svc_export * +exp_get_by_name(struct cache_detail *cd, struct auth_domain *clp, + const struct path *path, struct cache_req *reqp) +{ + struct svc_export *exp, key; + int err; - new.h.expiry_time = NEVER; - new.h.flags = 0; - new.ex_client = clp; - new.ex_mnt = nd.mnt; - new.ex_dentry = nd.dentry; - new.ex_flags = nxp->ex_flags; - new.ex_anon_uid = nxp->ex_anon_uid; - new.ex_anon_gid = nxp->ex_anon_gid; - new.ex_fsid = nxp->ex_dev; + if (!clp) + return ERR_PTR(-ENOENT); - exp = svc_export_lookup(&new, 1); + key.ex_client = clp; + key.ex_path = *path; + key.cd = cd; + exp = svc_export_lookup(&key); if (exp == NULL) - goto finish; - - err = 0; - - if (exp_hash(clp, exp) || - exp_fsid_hash(clp, exp)) { - /* failed to create at least one index */ - exp_do_unexport(exp); - cache_flush(); - err = -ENOMEM; - } - -finish: - if (exp) - exp_put(exp); - if (fsid_key && !IS_ERR(fsid_key)) - expkey_put(&fsid_key->h, &svc_expkey_cache); - if (clp) - auth_domain_put(clp); - path_release(&nd); -out_unlock: - exp_writeunlock(); -out: - return err; -} - -/* - * Unexport a file system. The export entry has already - * been removed from the client's list of exported fs's. - */ -static void -exp_do_unexport(svc_export *unexp) -{ - unexp->h.expiry_time = get_seconds()-1; - svc_export_cache.nextcheck = get_seconds(); - exp_unhash(unexp); - exp_fsid_unhash(unexp); + return ERR_PTR(-ENOMEM); + err = cache_check(cd, &exp->h, reqp); + if (err) + return ERR_PTR(err); + return exp; } - /* - * unexport syscall. + * Find the export entry for a given dentry. */ -int -exp_unexport(struct nfsctl_export *nxp) +static struct svc_export * +exp_parent(struct cache_detail *cd, struct auth_domain *clp, struct path *path) { - struct auth_domain *dom; - svc_export *exp; - struct nameidata nd; - int err; - - /* Consistency check */ - if (!exp_verify_string(nxp->ex_path, NFS_MAXPATHLEN) || - !exp_verify_string(nxp->ex_client, NFSCLNT_IDMAX)) - return -EINVAL; - - exp_writelock(); - - err = -EINVAL; - dom = auth_domain_find(nxp->ex_client); - if (!dom) { - dprintk("nfsd: unexport couldn't find %s\n", nxp->ex_client); - goto out_unlock; + struct dentry *saved = dget(path->dentry); + struct svc_export *exp = exp_get_by_name(cd, clp, path, NULL); + + while (PTR_ERR(exp) == -ENOENT && !IS_ROOT(path->dentry)) { + struct dentry *parent = dget_parent(path->dentry); + dput(path->dentry); + path->dentry = parent; + exp = exp_get_by_name(cd, clp, path, NULL); } + dput(path->dentry); + path->dentry = saved; + return exp; +} - err = path_lookup(nxp->ex_path, 0, &nd); - if (err) - goto out_domain; - - err = -EINVAL; - exp = exp_get_by_name(dom, nd.mnt, nd.dentry, NULL); - path_release(&nd); - if (!exp) - goto out_domain; - exp_do_unexport(exp); - exp_put(exp); - err = 0; - -out_domain: - auth_domain_put(dom); - cache_flush(); -out_unlock: - exp_writeunlock(); - return err; -} /* * Obtain the root fh on behalf of a client. @@ -873,28 +866,31 @@ out_unlock: * since its harder to fool a kernel module than a user space program. */ int -exp_rootfh(svc_client *clp, char *path, struct knfsd_fh *f, int maxsize) +exp_rootfh(struct net *net, struct auth_domain *clp, char *name, + struct knfsd_fh *f, int maxsize) { struct svc_export *exp; - struct nameidata nd; + struct path path; struct inode *inode; struct svc_fh fh; int err; + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct cache_detail *cd = nn->svc_export_cache; err = -EPERM; /* NB: we probably ought to check that it's NUL-terminated */ - if (path_lookup(path, 0, &nd)) { - printk("nfsd: exp_rootfh path not found %s", path); + if (kern_path(name, 0, &path)) { + printk("nfsd: exp_rootfh path not found %s", name); return err; } - inode = nd.dentry->d_inode; + inode = path.dentry->d_inode; dprintk("nfsd: exp_rootfh(%s [%p] %s:%s/%ld)\n", - path, nd.dentry, clp->name, + name, path.dentry, clp->name, inode->i_sb->s_id, inode->i_ino); - exp = exp_parent(clp, nd.mnt, nd.dentry, NULL); - if (!exp) { - dprintk("nfsd: exp_rootfh export not found.\n"); + exp = exp_parent(cd, clp, &path); + if (IS_ERR(exp)) { + err = PTR_ERR(exp); goto out; } @@ -902,7 +898,7 @@ exp_rootfh(svc_client *clp, char *path, struct knfsd_fh *f, int maxsize) * fh must be initialized before calling fh_compose */ fh_init(&fh, maxsize); - if (fh_compose(&fh, exp, nd.dentry, NULL)) + if (fh_compose(&fh, exp, path.dentry, NULL)) err = -EINVAL; else err = 0; @@ -910,49 +906,182 @@ exp_rootfh(svc_client *clp, char *path, struct knfsd_fh *f, int maxsize) fh_put(&fh); exp_put(exp); out: - path_release(&nd); + path_put(&path); return err; } +static struct svc_export *exp_find(struct cache_detail *cd, + struct auth_domain *clp, int fsid_type, + u32 *fsidv, struct cache_req *reqp) +{ + struct svc_export *exp; + struct nfsd_net *nn = net_generic(cd->net, nfsd_net_id); + struct svc_expkey *ek = exp_find_key(nn->svc_expkey_cache, clp, fsid_type, fsidv, reqp); + if (IS_ERR(ek)) + return ERR_CAST(ek); + + exp = exp_get_by_name(cd, clp, &ek->ek_path, reqp); + cache_put(&ek->h, nn->svc_expkey_cache); + + if (IS_ERR(exp)) + return ERR_CAST(exp); + return exp; +} + +__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp) +{ + struct exp_flavor_info *f; + struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors; + + /* legacy gss-only clients are always OK: */ + if (exp->ex_client == rqstp->rq_gssclient) + return 0; + /* ip-address based client; check sec= export option: */ + for (f = exp->ex_flavors; f < end; f++) { + if (f->pseudoflavor == rqstp->rq_cred.cr_flavor) + return 0; + } + /* defaults in absence of sec= options: */ + if (exp->ex_nflavors == 0) { + if (rqstp->rq_cred.cr_flavor == RPC_AUTH_NULL || + rqstp->rq_cred.cr_flavor == RPC_AUTH_UNIX) + return 0; + } + return nfserr_wrongsec; +} + /* - * Called when we need the filehandle for the root of the pseudofs, - * for a given NFSv4 client. The root is defined to be the - * export point with fsid==0 + * Uses rq_client and rq_gssclient to find an export; uses rq_client (an + * auth_unix client) if it's available and has secinfo information; + * otherwise, will try to use rq_gssclient. + * + * Called from functions that handle requests; functions that do work on + * behalf of mountd are passed a single client name to use, and should + * use exp_get_by_name() or exp_find(). */ -int -exp_pseudoroot(struct auth_domain *clp, struct svc_fh *fhp, - struct cache_req *creq) +struct svc_export * +rqst_exp_get_by_name(struct svc_rqst *rqstp, struct path *path) +{ + struct svc_export *gssexp, *exp = ERR_PTR(-ENOENT); + struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); + struct cache_detail *cd = nn->svc_export_cache; + + if (rqstp->rq_client == NULL) + goto gss; + + /* First try the auth_unix client: */ + exp = exp_get_by_name(cd, rqstp->rq_client, path, &rqstp->rq_chandle); + if (PTR_ERR(exp) == -ENOENT) + goto gss; + if (IS_ERR(exp)) + return exp; + /* If it has secinfo, assume there are no gss/... clients */ + if (exp->ex_nflavors > 0) + return exp; +gss: + /* Otherwise, try falling back on gss client */ + if (rqstp->rq_gssclient == NULL) + return exp; + gssexp = exp_get_by_name(cd, rqstp->rq_gssclient, path, &rqstp->rq_chandle); + if (PTR_ERR(gssexp) == -ENOENT) + return exp; + if (!IS_ERR(exp)) + exp_put(exp); + return gssexp; +} + +struct svc_export * +rqst_exp_find(struct svc_rqst *rqstp, int fsid_type, u32 *fsidv) +{ + struct svc_export *gssexp, *exp = ERR_PTR(-ENOENT); + struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); + struct cache_detail *cd = nn->svc_export_cache; + + if (rqstp->rq_client == NULL) + goto gss; + + /* First try the auth_unix client: */ + exp = exp_find(cd, rqstp->rq_client, fsid_type, + fsidv, &rqstp->rq_chandle); + if (PTR_ERR(exp) == -ENOENT) + goto gss; + if (IS_ERR(exp)) + return exp; + /* If it has secinfo, assume there are no gss/... clients */ + if (exp->ex_nflavors > 0) + return exp; +gss: + /* Otherwise, try falling back on gss client */ + if (rqstp->rq_gssclient == NULL) + return exp; + gssexp = exp_find(cd, rqstp->rq_gssclient, fsid_type, fsidv, + &rqstp->rq_chandle); + if (PTR_ERR(gssexp) == -ENOENT) + return exp; + if (!IS_ERR(exp)) + exp_put(exp); + return gssexp; +} + +struct svc_export * +rqst_exp_parent(struct svc_rqst *rqstp, struct path *path) +{ + struct dentry *saved = dget(path->dentry); + struct svc_export *exp = rqst_exp_get_by_name(rqstp, path); + + while (PTR_ERR(exp) == -ENOENT && !IS_ROOT(path->dentry)) { + struct dentry *parent = dget_parent(path->dentry); + dput(path->dentry); + path->dentry = parent; + exp = rqst_exp_get_by_name(rqstp, path); + } + dput(path->dentry); + path->dentry = saved; + return exp; +} + +struct svc_export *rqst_find_fsidzero_export(struct svc_rqst *rqstp) { - struct svc_expkey *fsid_key; - int rv; u32 fsidv[2]; - mk_fsid_v1(fsidv, 0); + mk_fsid(FSID_NUM, fsidv, 0, 0, 0, NULL); - fsid_key = exp_find_key(clp, 1, fsidv, creq); - if (IS_ERR(fsid_key) && PTR_ERR(fsid_key) == -EAGAIN) - return nfserr_dropit; - if (!fsid_key || IS_ERR(fsid_key)) - return nfserr_perm; + return rqst_exp_find(rqstp, FSID_NUM, fsidv); +} - rv = fh_compose(fhp, fsid_key->ek_export, - fsid_key->ek_export->ex_dentry, NULL); - expkey_put(&fsid_key->h, &svc_expkey_cache); +/* + * Called when we need the filehandle for the root of the pseudofs, + * for a given NFSv4 client. The root is defined to be the + * export point with fsid==0 + */ +__be32 +exp_pseudoroot(struct svc_rqst *rqstp, struct svc_fh *fhp) +{ + struct svc_export *exp; + __be32 rv; + + exp = rqst_find_fsidzero_export(rqstp); + if (IS_ERR(exp)) + return nfserrno(PTR_ERR(exp)); + rv = fh_compose(fhp, exp, exp->ex_path.dentry, NULL); + exp_put(exp); return rv; } /* Iterator */ static void *e_start(struct seq_file *m, loff_t *pos) + __acquires(((struct cache_detail *)m->private)->hash_lock) { loff_t n = *pos; unsigned hash, export; struct cache_head *ch; - - exp_readlock(); - read_lock(&svc_export_cache.hash_lock); + struct cache_detail *cd = m->private; + struct cache_head **export_table = cd->hash_table; + + read_lock(&cd->hash_lock); if (!n--) - return (void *)1; + return SEQ_START_TOKEN; hash = n >> 32; export = n & ((1LL<<32) - 1); @@ -975,8 +1104,10 @@ static void *e_next(struct seq_file *m, void *p, loff_t *pos) { struct cache_head *ch = p; int hash = (*pos >> 32); + struct cache_detail *cd = m->private; + struct cache_head **export_table = cd->hash_table; - if (p == (void *)1) + if (p == SEQ_START_TOKEN) hash = 0; else if (ch->next == NULL) { hash++; @@ -997,9 +1128,11 @@ static void *e_next(struct seq_file *m, void *p, loff_t *pos) } static void e_stop(struct seq_file *m, void *p) + __releases(((struct cache_detail *)m->private)->hash_lock) { - read_unlock(&svc_export_cache.hash_lock); - exp_readunlock(); + struct cache_detail *cd = m->private; + + read_unlock(&cd->hash_lock); } static struct flags { @@ -1016,51 +1149,118 @@ static struct flags { { NFSEXP_CROSSMOUNT, {"crossmnt", ""}}, { NFSEXP_NOSUBTREECHECK, {"no_subtree_check", ""}}, { NFSEXP_NOAUTHNLM, {"insecure_locks", ""}}, -#ifdef MSNFS - { NFSEXP_MSNFS, {"msnfs", ""}}, -#endif + { NFSEXP_V4ROOT, {"v4root", ""}}, { 0, {"", ""}} }; -static void exp_flags(struct seq_file *m, int flag, int fsid, uid_t anonu, uid_t anong) +static void show_expflags(struct seq_file *m, int flags, int mask) { - int first = 0; struct flags *flg; + int state, first = 0; for (flg = expflags; flg->flag; flg++) { - int state = (flg->flag & flag)?0:1; + if (flg->flag & ~mask) + continue; + state = (flg->flag & flags) ? 0 : 1; if (*flg->name[state]) seq_printf(m, "%s%s", first++?",":"", flg->name[state]); } +} + +static void show_secinfo_flags(struct seq_file *m, int flags) +{ + seq_printf(m, ","); + show_expflags(m, flags, NFSEXP_SECINFO_FLAGS); +} + +static bool secinfo_flags_equal(int f, int g) +{ + f &= NFSEXP_SECINFO_FLAGS; + g &= NFSEXP_SECINFO_FLAGS; + return f == g; +} + +static int show_secinfo_run(struct seq_file *m, struct exp_flavor_info **fp, struct exp_flavor_info *end) +{ + int flags; + + flags = (*fp)->flags; + seq_printf(m, ",sec=%d", (*fp)->pseudoflavor); + (*fp)++; + while (*fp != end && secinfo_flags_equal(flags, (*fp)->flags)) { + seq_printf(m, ":%d", (*fp)->pseudoflavor); + (*fp)++; + } + return flags; +} + +static void show_secinfo(struct seq_file *m, struct svc_export *exp) +{ + struct exp_flavor_info *f; + struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors; + int flags; + + if (exp->ex_nflavors == 0) + return; + f = exp->ex_flavors; + flags = show_secinfo_run(m, &f, end); + if (!secinfo_flags_equal(flags, exp->ex_flags)) + show_secinfo_flags(m, flags); + while (f != end) { + flags = show_secinfo_run(m, &f, end); + show_secinfo_flags(m, flags); + } +} + +static void exp_flags(struct seq_file *m, int flag, int fsid, + kuid_t anonu, kgid_t anong, struct nfsd4_fs_locations *fsloc) +{ + show_expflags(m, flag, NFSEXP_ALLFLAGS); if (flag & NFSEXP_FSID) - seq_printf(m, "%sfsid=%d", first++?",":"", fsid); - if (anonu != (uid_t)-2 && anonu != (0x10000-2)) - seq_printf(m, "%sanonuid=%d", first++?",":"", anonu); - if (anong != (gid_t)-2 && anong != (0x10000-2)) - seq_printf(m, "%sanongid=%d", first++?",":"", anong); + seq_printf(m, ",fsid=%d", fsid); + if (!uid_eq(anonu, make_kuid(&init_user_ns, (uid_t)-2)) && + !uid_eq(anonu, make_kuid(&init_user_ns, 0x10000-2))) + seq_printf(m, ",anonuid=%u", from_kuid(&init_user_ns, anonu)); + if (!gid_eq(anong, make_kgid(&init_user_ns, (gid_t)-2)) && + !gid_eq(anong, make_kgid(&init_user_ns, 0x10000-2))) + seq_printf(m, ",anongid=%u", from_kgid(&init_user_ns, anong)); + if (fsloc && fsloc->locations_count > 0) { + char *loctype = (fsloc->migrated) ? "refer" : "replicas"; + int i; + + seq_printf(m, ",%s=", loctype); + seq_escape(m, fsloc->locations[0].path, ",;@ \t\n\\"); + seq_putc(m, '@'); + seq_escape(m, fsloc->locations[0].hosts, ",;@ \t\n\\"); + for (i = 1; i < fsloc->locations_count; i++) { + seq_putc(m, ';'); + seq_escape(m, fsloc->locations[i].path, ",;@ \t\n\\"); + seq_putc(m, '@'); + seq_escape(m, fsloc->locations[i].hosts, ",;@ \t\n\\"); + } + } } static int e_show(struct seq_file *m, void *p) { struct cache_head *cp = p; struct svc_export *exp = container_of(cp, struct svc_export, h); - svc_client *clp; + struct cache_detail *cd = m->private; - if (p == (void *)1) { + if (p == SEQ_START_TOKEN) { seq_puts(m, "# Version 1.1\n"); seq_puts(m, "# Path Client(Flags) # IPs\n"); return 0; } - clp = exp->ex_client; cache_get(&exp->h); - if (cache_check(&svc_export_cache, &exp->h, NULL)) + if (cache_check(cd, &exp->h, NULL)) return 0; - if (cache_put(&exp->h, &svc_export_cache)) BUG(); - return svc_export_show(m, &svc_export_cache, cp); + exp_put(exp); + return svc_export_show(m, cd, cp); } -struct seq_operations nfs_exports_op = { +const struct seq_operations nfs_exports_op = { .start = e_start, .next = e_next, .stop = e_stop, @@ -1068,136 +1268,69 @@ struct seq_operations nfs_exports_op = { }; /* - * Add or modify a client. - * Change requests may involve the list of host addresses. The list of - * exports and possibly existing uid maps are left untouched. - */ -int -exp_addclient(struct nfsctl_client *ncp) -{ - struct auth_domain *dom; - int i, err; - - /* First, consistency check. */ - err = -EINVAL; - if (! exp_verify_string(ncp->cl_ident, NFSCLNT_IDMAX)) - goto out; - if (ncp->cl_naddr > NFSCLNT_ADDRMAX) - goto out; - - /* Lock the hashtable */ - exp_writelock(); - - dom = unix_domain_find(ncp->cl_ident); - - err = -ENOMEM; - if (!dom) - goto out_unlock; - - /* Insert client into hashtable. */ - for (i = 0; i < ncp->cl_naddr; i++) - auth_unix_add_addr(ncp->cl_addrlist[i], dom); - - auth_unix_forget_old(dom); - auth_domain_put(dom); - - err = 0; - -out_unlock: - exp_writeunlock(); -out: - return err; -} - -/* - * Delete a client given an identifier. + * Initialize the exports module. */ int -exp_delclient(struct nfsctl_client *ncp) +nfsd_export_init(struct net *net) { - int err; - struct auth_domain *dom; + int rv; + struct nfsd_net *nn = net_generic(net, nfsd_net_id); - err = -EINVAL; - if (!exp_verify_string(ncp->cl_ident, NFSCLNT_IDMAX)) - goto out; + dprintk("nfsd: initializing export module (net: %p).\n", net); - /* Lock the hashtable */ - exp_writelock(); + nn->svc_export_cache = cache_create_net(&svc_export_cache_template, net); + if (IS_ERR(nn->svc_export_cache)) + return PTR_ERR(nn->svc_export_cache); + rv = cache_register_net(nn->svc_export_cache, net); + if (rv) + goto destroy_export_cache; - dom = auth_domain_find(ncp->cl_ident); - /* just make sure that no addresses work - * and that it will expire soon - */ - if (dom) { - err = auth_unix_forget_old(dom); - dom->h.expiry_time = get_seconds(); - auth_domain_put(dom); + nn->svc_expkey_cache = cache_create_net(&svc_expkey_cache_template, net); + if (IS_ERR(nn->svc_expkey_cache)) { + rv = PTR_ERR(nn->svc_expkey_cache); + goto unregister_export_cache; } - - exp_writeunlock(); -out: - return err; -} - -/* - * Verify that string is non-empty and does not exceed max length. - */ -static int -exp_verify_string(char *cp, int max) -{ - int i; - - for (i = 0; i < max; i++) - if (!cp[i]) - return i; - cp[i] = 0; - printk(KERN_NOTICE "nfsd: couldn't validate string %s\n", cp); + rv = cache_register_net(nn->svc_expkey_cache, net); + if (rv) + goto destroy_expkey_cache; return 0; -} - -/* - * Initialize the exports module. - */ -void -nfsd_export_init(void) -{ - dprintk("nfsd: initializing export module.\n"); - - cache_register(&svc_export_cache); - cache_register(&svc_expkey_cache); +destroy_expkey_cache: + cache_destroy_net(nn->svc_expkey_cache, net); +unregister_export_cache: + cache_unregister_net(nn->svc_export_cache, net); +destroy_export_cache: + cache_destroy_net(nn->svc_export_cache, net); + return rv; } /* * Flush exports table - called when last nfsd thread is killed */ void -nfsd_export_flush(void) +nfsd_export_flush(struct net *net) { - exp_writelock(); - cache_purge(&svc_expkey_cache); - cache_purge(&svc_export_cache); - exp_writeunlock(); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + + cache_purge(nn->svc_expkey_cache); + cache_purge(nn->svc_export_cache); } /* * Shutdown the exports module. */ void -nfsd_export_shutdown(void) +nfsd_export_shutdown(struct net *net) { + struct nfsd_net *nn = net_generic(net, nfsd_net_id); - dprintk("nfsd: shutting down export module.\n"); - - exp_writelock(); + dprintk("nfsd: shutting down export module (net: %p).\n", net); - if (cache_unregister(&svc_expkey_cache)) - printk(KERN_ERR "nfsd: failed to unregister expkey cache\n"); - if (cache_unregister(&svc_export_cache)) - printk(KERN_ERR "nfsd: failed to unregister export cache\n"); - svcauth_unix_purge(); + cache_unregister_net(nn->svc_expkey_cache, net); + cache_unregister_net(nn->svc_export_cache, net); + cache_destroy_net(nn->svc_expkey_cache, net); + cache_destroy_net(nn->svc_export_cache, net); + svcauth_unix_purge(net); - exp_writeunlock(); - dprintk("nfsd: export shutdown complete.\n"); + dprintk("nfsd: export shutdown complete (net: %p).\n", net); } |
