From 1275be70ca6cd6c4bec07a3381f7b510086c5526 Mon Sep 17 00:00:00 2001 From: neilbrown Date: Wed, 21 May 2003 06:25:15 +0000 Subject: [PATCH] Support new kernel upcalls for export cache management. --- ChangeLog | 16 ++ support/export/client.c | 98 ++++++++++ support/include/exportfs.h | 2 + support/include/nfslib.h | 4 + support/nfs/cacheio.c | 16 ++ utils/mountd/Makefile | 2 +- utils/mountd/auth.c | 68 +++++-- utils/mountd/cache.c | 358 +++++++++++++++++++++++++++++++++++++ utils/mountd/mountd.c | 30 +++- utils/mountd/svc_run.c | 89 +++++++++ 10 files changed, 666 insertions(+), 17 deletions(-) create mode 100644 utils/mountd/cache.c create mode 100644 utils/mountd/svc_run.c diff --git a/ChangeLog b/ChangeLog index 1494b88..654469b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2003-05-21 NeilBrown + + * support/export/client.c: Add client_compose, client_member to + handle new composite client names + * support/include/exportfs.h: Declare above functions. + * utils/mountd/auth.c: use client_compose to determine calling + client if new cache is being used. + * utils/mountd/auth.c: call cache_export to export a filesystem if + new_cache, check if new cache is used, and call my_svc_run instead + of svc_run + * utils/mountd/svc_run.c: new file defining my_svc_run + * utils/mountd/cache.c: new file for handling cache upcalls. + * support/nfs/cacheio.c(check_new_cache): new function to check if + new cache should be used. + + 2003-05-21 NeilBrown * support/include/nfs/export.h: New CROSSMNT distinct from NOHIDE diff --git a/support/export/client.c b/support/export/client.c index 6d5d306..3db21ae 100644 --- a/support/export/client.c +++ b/support/export/client.c @@ -229,6 +229,104 @@ client_find(struct hostent *hp) return NULL; } +/* + * Find client name given an IP address + * This is found by gathering all known names that match that IP address, + * sorting them and joining them with '+' + * + */ +static char *add_name(char *old, char *add); + +char * +client_compose(struct in_addr addr) +{ + struct hostent *he = NULL; + char *name = NULL; + int i; + + if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP]) + he = get_reliable_hostbyaddr((const char*)&addr, sizeof(addr), AF_INET); + if (he == NULL) + he = get_hostent((const char*)&addr, sizeof(addr), AF_INET); + + for (i = 0 ; i < MCL_MAXTYPES; i++) { + nfs_client *clp; + for (clp = clientlist[i]; clp ; clp = clp->m_next) { + if (!client_check(clp, he)) + continue; + name = add_name(name, clp->m_hostname); + } + } + return name; +} + +int +client_member(char *client, char *name) +{ + /* check if "client" (a ',' separated list of names) + * contains 'name' as a member + */ + int l = strlen(name); + while (*client) { + if (strncmp(client, name, l) == 0 && + (client[l] == ',' || client[l] == '\0')) + return 1; + client = strchr(client, ','); + if (client == NULL) + return 0; + client++; + } + return 0; +} + + +int +name_cmp(char *a, char *b) +{ + /* compare strings a and b, but only upto ',' in a */ + while (*a && *b && *a != ',' && *a == *b) + a++, b++; + if (!*b && (!*a || !a == ',') ) + return 0; + if (!*b) return 1; + if (!*a || *a == ',') return -1; + return *a - *b; +} + +static char * +add_name(char *old, char *add) +{ + int len = strlen(add)+2; + char *new; + char *cp; + if (old) len += strlen(old); + + new = malloc(len); + if (!new) { + free(old); + return NULL; + } + cp = old; + while (cp && *cp && name_cmp(cp, add) < 0) { + /* step cp forward over a name */ + char *e = strchr(cp, ','); + if (e) + cp = e+1; + else + cp = cp + strlen(cp); + } + strncpy(new, old, cp-old); + new[cp-old] = 0; + if (cp != old && !*cp) + strcat(new, ","); + strcat(new, add); + if (cp && *cp) { + strcat(new, ","); + strcat(new, cp); + } + return new; +} + /* * Match a host (given its hostent record) to a client record. This * is usually called from mountd. diff --git a/support/include/exportfs.h b/support/include/exportfs.h index 4021e60..dfd51f2 100644 --- a/support/include/exportfs.h +++ b/support/include/exportfs.h @@ -54,6 +54,8 @@ int client_check(nfs_client *, struct hostent *); int client_match(nfs_client *, char *hname); void client_release(nfs_client *); void client_freeall(void); +char * client_compose(struct in_addr addr); +int client_member(char *client, char *name); int export_read(char *fname); void export_add(nfs_export *); diff --git a/support/include/nfslib.h b/support/include/nfslib.h index 90bd33a..5112b91 100644 --- a/support/include/nfslib.h +++ b/support/include/nfslib.h @@ -126,6 +126,10 @@ void qword_print(FILE *f, char *str); void qword_printhex(FILE *f, char *str, int slen); void qword_printint(FILE *f, int num); void qword_eol(FILE *f); +int readline(int fd, char **buf, int *lenp); +int qword_get(char **bpp, char *dest, int bufsize); +int qword_get_int(char **bpp, int *anint); +int check_new_cache(void); /* lockd. */ int lockdsvc(); diff --git a/support/nfs/cacheio.c b/support/nfs/cacheio.c index 960d801..2af4fa3 100644 --- a/support/nfs/cacheio.c +++ b/support/nfs/cacheio.c @@ -19,6 +19,10 @@ #include #include #include +#include +#include +#include +#include void qword_add(char **bpp, int *lp, char *str) { @@ -214,3 +218,15 @@ int readline(int fd, char **buf, int *lenp) return 1; } + +/* Check if we should use the new caching interface + * This succeeds iff the "nfsd" filesystem is mounted on + * /proc/fs/nfs + */ +int +check_new_cache(void) +{ + struct stat stb; + return (stat("/proc/fs/nfs/filehandle", &stb) == 0); +} + diff --git a/utils/mountd/Makefile b/utils/mountd/Makefile index 49b9900..34a2f4f 100644 --- a/utils/mountd/Makefile +++ b/utils/mountd/Makefile @@ -4,7 +4,7 @@ PROGRAM = mountd PREFIX = rpc. -OBJS = mountd.o mount_dispatch.o auth.o rmtab.o +OBJS = mountd.o mount_dispatch.o auth.o rmtab.o cache.o svc_run.o LIBDEPS = $(TOP)support/lib/libexport.a $(TOP)/support/lib/libnfs.a LIBS = -lexport -lnfs -lmisc $(LIBBSD) $(LIBWRAP) $(LIBNSL) MAN8 = mountd diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c index eb9cdeb..e921389 100644 --- a/utils/mountd/auth.c +++ b/utils/mountd/auth.c @@ -31,6 +31,8 @@ enum auth_error static void auth_fixpath(char *path); static char *export_file = NULL; +extern int new_cache; + void auth_init(char *exports) { @@ -66,22 +68,60 @@ auth_authenticate_internal(char *what, struct sockaddr_in *caller, { nfs_export *exp; - if (!(exp = export_find(hp, path))) { - *error = no_entry; - return NULL; - } - if (!exp->m_mayexport) { + if (new_cache) { + static nfs_export my_exp; + static nfs_client my_client; + int i; + /* return static nfs_export with details filled in */ + if (my_client.m_naddr != 1 || + my_client.m_addrlist[0].s_addr != caller->sin_addr.s_addr) { + /* different client to last time, so do a lookup */ + char *n; + my_client.m_naddr = 0; + my_client.m_addrlist[0] = caller->sin_addr; + n = client_compose(caller->sin_addr); + if (!n) + return NULL; + strcpy(my_client.m_hostname, *n?n:"DEFAULT"); + free(n); + my_client.m_naddr = 1; + } + + my_exp.m_client = &my_client; + + exp = NULL; + for (i = 0; !exp && i < MCL_MAXTYPES; i++) + for (exp = exportlist[i]; exp; exp = exp->m_next) { + if (!client_member(my_client.m_hostname, exp->m_client->m_hostname)) + continue; + if (strcmp(path, exp->m_export.e_path)) + continue; + break; + } *error = not_exported; - return NULL; + if (!exp) + return exp; + + my_exp.m_export = exp->m_export; + exp = &my_exp; + + } else { + if (!(exp = export_find(hp, path))) { + *error = no_entry; + return NULL; + } + if (!exp->m_mayexport) { + *error = not_exported; + return NULL; + } + + if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && + (ntohs(caller->sin_port) < IPPORT_RESERVED/2 || + ntohs(caller->sin_port) >= IPPORT_RESERVED)) { + *error = illegal_port; + return NULL; + } } - - if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && - (ntohs(caller->sin_port) < IPPORT_RESERVED/2 || - ntohs(caller->sin_port) >= IPPORT_RESERVED)) { - *error = illegal_port; - return NULL; - } - *error = success; return exp; diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c new file mode 100644 index 0000000..1d1567d --- /dev/null +++ b/utils/mountd/cache.c @@ -0,0 +1,358 @@ + +/* + * Handle communication with knfsd internal cache + * + * We open /proc/net/rpc/{auth.unix.ip,nfsd.export,nfsd.fh}/channel + * and listen for requests (using my_svc_run) + * + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" +#include "nfslib.h" +#include "exportfs.h" +#include "mountd.h" +#include "xmalloc.h" + +/* + * Support routines for text-based upcalls. + * Fields are separated by spaces. + * Fields are either mangled to quote space tab newline slosh with slosh + * or a hexified with a leading \x + * Record is terminated with newline. + * + */ +void cache_export_ent(char *domain, struct exportent *exp); + + +char *lbuf = NULL; +int lbuflen = 0; + +void auth_unix_ip(FILE *f) +{ + /* requests are + * class IP-ADDR + * Ignore if class != "nfsd" + * Otherwise find domainname and write back: + * + * "nfsd" IP-ADDR expiry domainname + */ + char *cp; + char class[20]; + char ipaddr[20]; + char *client; + struct in_addr addr; + if (readline(fileno(f), &lbuf, &lbuflen) != 1) + return; + + cp = lbuf; + + if (qword_get(&cp, class, 20) <= 0 || + strcmp(class, "nfsd") != 0) + return; + + if (qword_get(&cp, ipaddr, 20) <= 0) + return; + + if (inet_aton(ipaddr, &addr)==0) + return; + + /* addr is a valid, interesting address, find the domain name... */ + client = client_compose(addr); + + + qword_print(f, "nfsd"); + qword_print(f, ipaddr); + qword_printint(f, time(0)+30*60); + if (client) + qword_print(f, *client?client:"DEFAULT"); + qword_eol(f); + + if (client) free(client); + +} + +void nfsd_fh(FILE *f) +{ + /* request are: + * domain fsidtype fsid + * interpret fsid, find export point and options, and write: + * domain fsidtype fsid expiry path + */ + char *cp; + char *dom; + int fsidtype; + int fsidlen; + unsigned int dev, major=0, minor=0; + unsigned int inode=0; + unsigned int fsidnum=0; + char fsid[32]; + struct exportent *found = NULL; + nfs_export *exp; + int i; + + if (readline(fileno(f), &lbuf, &lbuflen) != 1) + return; + + cp = lbuf; + + dom = malloc(strlen(cp)); + if (dom == NULL) + return; + if (qword_get(&cp, dom, strlen(cp)) <= 0) + goto out; + if (qword_get_int(&cp, &fsidtype) != 0) + goto out; + if (fsidtype < 0 || fsidtype > 1) + goto out; /* unknown type */ + if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0) + goto out; + switch(fsidtype) { + case 0: /* 4 bytes: 2 major, 2 minor, 4 inode */ + if (fsidlen != 8) + goto out; + memcpy(&dev, fsid, 4); + memcpy(&inode, fsid+4, 4); + major = ntohl(dev)>>16; + minor = ntohl(dev) & 0xFFFF; + break; + + case 1: /* 4 bytes - fsid */ + if (fsidlen != 4) + goto out; + memcpy(&fsidnum, fsid, 4); + break; + } + + /* Now determine export point for this fsid/domain */ + for (i=0 ; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i]; exp; exp = exp->m_next) { + if (!client_member(dom, exp->m_client->m_hostname)) + continue; + if (fsidtype == 1 && + ((exp->m_export.e_flags & NFSEXP_FSID) == 0 || + exp->m_export.e_fsid != fsidnum)) + continue; + if (fsidtype == 0) { + struct stat stb; + if (stat(exp->m_export.e_path, &stb) != 0) + continue; + if (stb.st_ino != inode) + continue; + if (major != major(stb.st_dev) || + minor != minor(stb.st_dev)) + continue; + } + /* It's a match !! */ + if (!found) + found = &exp->m_export; + else if (strcmp(found->e_path, exp->m_export.e_path)!= 0) + { + xlog(L_WARNING, "%s and %s have name filehandle for %s, using first", + found->e_path, exp->m_export.e_path, dom); + } + } + } + cache_export_ent(dom, found); + + qword_print(f, dom); + qword_printint(f, fsidtype); + qword_printhex(f, fsid, fsidlen); + qword_printint(f, time(0)+30*60); + if (found) + qword_print(f, found->e_path); + qword_eol(f); + out: + free(dom); + return; +} + +void nfsd_export(FILE *f) +{ + /* requests are: + * domain path + * determine export options and return: + * domain path expiry flags anonuid anongid fsid + */ + + char *cp; + int i; + char *dom, *path; + nfs_export *exp, *found = NULL; + + + if (readline(fileno(f), &lbuf, &lbuflen) != 1) + return; + + cp = lbuf; + dom = malloc(strlen(cp)); + path = malloc(strlen(cp)); + + if (!dom || !path) + goto out; + + if (qword_get(&cp, dom, strlen(lbuf)) <= 0) + goto out; + if (qword_get(&cp, path, strlen(lbuf)) <= 0) + goto out; + + /* now find flags for this export point in this domain */ + for (i=0 ; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i]; exp; exp = exp->m_next) { + if (!client_member(dom, exp->m_client->m_hostname)) + continue; + if (strcmp(path, exp->m_export.e_path)) + continue; + if (!found) + found = exp; + else { + xlog(L_WARNING, "%s exported to both %s and %s in %s", + path, exp->m_client->m_hostname, found->m_client->m_hostname, + dom); + } + } + } + + qword_print(f, dom); + qword_print(f, path); + qword_printint(f, time(0)+30*60); + if (found) { + qword_printint(f, found->m_export.e_flags); + qword_printint(f, found->m_export.e_anonuid); + qword_printint(f, found->m_export.e_anongid); + qword_printint(f, found->m_export.e_fsid); + } + qword_eol(f); + out: + if (dom) free(dom); + if (path) free(path); +} + + +struct { + char *cache_name; + void (*cache_handle)(FILE *f); + FILE *f; +} cachelist[] = { + { "auth.unix.ip", auth_unix_ip}, + { "nfsd.export", nfsd_export}, + { "nfsd.fh", nfsd_fh}, + { NULL, NULL } +}; + +void cache_open(void) +{ + int i; + for (i=0; cachelist[i].cache_name; i++ ){ + char path[100]; + sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name); + cachelist[i].f = fopen(path, "r+"); + } +} + +void cache_set_fds(fd_set *fdset) +{ + int i; + for (i=0; cachelist[i].cache_name; i++) { + if (cachelist[i].f) + FD_SET(fileno(cachelist[i].f), fdset); + } +} + +int cache_process_req(fd_set *readfds) +{ + int i; + int cnt = 0; + for (i=0; cachelist[i].cache_name; i++) { + if (cachelist[i].f != NULL && + FD_ISSET(fileno(cachelist[i].f), readfds)) { + cnt++; + cachelist[i].cache_handle(cachelist[i].f); + } + } + return cnt; +} + + +/* + * Give IP->domain and domain+path->options to kernel + * % echo nfsd $IP $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel + * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel + */ + +void cache_export_ent(char *domain, struct exportent *exp) +{ + + FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "r+"); + if (!f) + return; + + qword_print(f, domain); + qword_print(f, exp->e_path); + qword_printint(f, time(0)+30*60); + qword_printint(f, exp->e_flags); + qword_printint(f, exp->e_anonuid); + qword_printint(f, exp->e_anongid); + qword_printint(f, exp->e_fsid); + qword_eol(f); + + fclose(f); +} + +void cache_export(nfs_export *exp) +{ + FILE *f; + + f = fopen("/proc/net/rpc/auth.unix.ip/channel", "r+"); + if (!f) + return; + + qword_print(f, "nfsd"); + qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0])); + qword_printint(f, time(0)+30*60); + qword_print(f, exp->m_client->m_hostname); + qword_eol(f); + + fclose(f); + + cache_export_ent(exp->m_client->m_hostname, &exp->m_export); +} + +/* Get a filehandle. + * { + * echo $domain $path $length + * read filehandle <&0 + * } <> /proc/fs/nfs/filehandle + */ +struct nfs_fh_len * +cache_get_filehandle(nfs_export *exp, int len) +{ + FILE *f = fopen("/proc/fs/nfs/filehandle", "r+"); + char buf[200]; + char *bp = buf; + static struct nfs_fh_len fh; + if (!f) + return NULL; + + qword_print(f, exp->m_client->m_hostname); + qword_print(f, exp->m_export.e_path); + qword_printint(f, len); + qword_eol(f); + + if (fgets(buf, sizeof(buf), f) == NULL) + return NULL; + memset(fh.fh_handle, 0, sizeof(fh.fh_handle)); + fh.fh_size = qword_get(&bp, fh.fh_handle, NFS3_FHSIZE); + return &fh; +} + diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c index 971e4f4..145a4da 100644 --- a/utils/mountd/mountd.c +++ b/utils/mountd/mountd.c @@ -24,10 +24,18 @@ #include "mountd.h" #include "rpcmisc.h" +extern void cache_open(void); +extern struct nfs_fh_len *cache_get_filehandle(nfs_export *exp, int len); +extern void cache_export(nfs_export *exp); + +extern void my_svc_run(void); + static void usage(const char *, int exitcode); static exports get_exportlist(void); static struct nfs_fh_len *get_rootfh(struct svc_req *, dirpath *, int *, int v3); +int new_cache = 0; + static struct option longopts[] = { { "foreground", 0, 0, 'F' }, @@ -182,7 +190,7 @@ mount_pathconf_2_svc(struct svc_req *rqstp, dirpath *path, ppathcnf *res) } /* Now authenticate the intruder... */ - if (!(exp = auth_authenticate("mount", sin, p))) { + if (!(exp = auth_authenticate("pathconf", sin, p))) { return 1; } else if (stat(p, &stb) < 0) { xlog(L_WARNING, "can't stat exported dir %s: %s", @@ -264,6 +272,20 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, int *error, int v3) } else if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) { xlog(L_WARNING, "%s is not a directory or regular file", p); *error = NFSERR_NOTDIR; + } else if (new_cache) { + /* This will be a static private nfs_export with just one + * address. We feed it to kernel then extract the filehandle, + * + */ + struct nfs_fh_len *fh; + + cache_export(exp); + fh = cache_get_filehandle(exp, v3?64:32); + if (fh == NULL) + *error = NFSERR_ACCES; + else + *error = NFS_OK; + return fh; } else { struct nfs_fh_len *fh; @@ -491,6 +513,10 @@ main(int argc, char **argv) (void) close(fd); } + new_cache = check_new_cache(); + if (new_cache) + cache_open(); + if (nfs_version & 0x1) rpc_init("mountd", MOUNTPROG, MOUNTVERS, mount_dispatch, port); @@ -529,7 +555,7 @@ main(int argc, char **argv) xlog_background(); } - svc_run(); + my_svc_run(); xlog(L_ERROR, "Ack! Gack! svc_run returned!\n"); exit(1); diff --git a/utils/mountd/svc_run.c b/utils/mountd/svc_run.c new file mode 100644 index 0000000..a1ef74a --- /dev/null +++ b/utils/mountd/svc_run.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 1984 Sun Microsystems, Inc. + * Based on svc_run.c from statd which claimed: + * Modified by Jeffrey A. Uphoff, 1995, 1997-1999. + * Modified by Olaf Kirch, 1996. + * + */ + +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * Allow svc_run to listen to other file descriptors as well + */ + +/* + * This is the RPC server side idle loop. + * Wait for input, call server program. + */ +#include "config.h" +#include +#include +#include "xlog.h" +#include +#include + +void cache_set_fds(fd_set *fdset); +int cache_process_req(fd_set *readfds); + + +/* + * The heart of the server. A crib from libc for the most part... + */ +void +my_svc_run(void) +{ + fd_set readfds; + int selret; + + for (;;) { + + readfds = svc_fdset; + cache_set_fds(&readfds); + + selret = select(FD_SETSIZE, &readfds, + (void *) 0, (void *) 0, (struct timeval *) 0); + + + switch (selret) { + case -1: + if (errno == EINTR || errno == ECONNREFUSED + || errno == ENETUNREACH || errno == EHOSTUNREACH) + continue; + xlog(L_ERROR, "my_svc_run() - select: %m"); + return; + + default: + selret -= cache_process_req(&readfds); + if (selret) + svc_getreqset(&readfds); + } + } +} -- 2.39.2