/* * Copyright (c) 1999-2005 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights * Reserved. This file contains Original Code and/or Modifications of * Original Code as defined in and that are subject to the Apple Public * Source License Version 1.0 (the 'License'). You may not use this file * except in compliance with the License. Please obtain a copy of the * License at http://www.apple.com/publicsource and read it before using * this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License." * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)pathnames.h 8.1 (Berkeley) 6/5/93 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ISO #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" #include #include #include #define EXPORT_FROM_NETINFO 0 /* export list from NetInfo */ #define EXPORT_FROM_FILE 1 /* export list from file only */ #define EXPORT_FROM_FILEFIRST 2 /* export list from file first, then NetInfo */ static int source = EXPORT_FROM_FILEFIRST; #include /* * Structure for maintaining list of export IDs for exported volumes */ struct expidlist { LIST_ENTRY(expidlist) xid_list; char xid_path[MAXPATHLEN]; /* exported sub-directory */ u_int32_t xid_id; /* export ID */ }; /* * Structure for maintaining list of UUIDs for exported volumes */ struct uuidlist { TAILQ_ENTRY(uuidlist) ul_list; char ul_mntfromname[MAXPATHLEN]; char ul_mntonname[MAXPATHLEN]; u_char ul_uuid[16]; /* UUID used */ u_char ul_dauuid[16]; /* DiskArb UUID */ char ul_davalid; /* DiskArb UUID valid */ char ul_exported; /* currently exported? */ u_int32_t ul_fsid; /* exported FS ID */ LIST_HEAD(expidhead,expidlist) ul_exportids; /* export ID list */ }; TAILQ_HEAD(,uuidlist) ulhead; #define UL_CHECK_MNTFROM 0x1 #define UL_CHECK_MNTON 0x2 #define UL_CHECK_ALL 0x3 /* * Default FSID is just a "hash" of UUID */ #define UUID2FSID(U) \ (*((u_int32_t*)(U)) ^ *(((u_int32_t*)(U))+1) ^ \ *(((u_int32_t*)(U))+2) ^ *(((u_int32_t*)(U))+3)) /* * Structure for keeping the (outstanding) mount list */ struct mountlist { struct mountlist *ml_next; char *ml_host; /* NFS client name or address */ char *ml_dir; /* mounted directory */ }; /* * Structure used to hold a list of directories */ struct dirlist { struct dirlist *dl_next; char *dl_dir; }; /* * Structures for keeping the export lists */ TAILQ_HEAD(expfstqh, expfs); TAILQ_HEAD(expdirtqh, expdir); /* * Structure to hold the exports for each exported file system. */ struct expfs { TAILQ_ENTRY(expfs) xf_next; struct expdirtqh xf_dirl; /* list of exported directories */ int xf_flag; /* internal flags for this struct */ u_char xf_uuid[16]; /* file system's UUID */ char *xf_fsdir; /* mount point of this file system */ }; /* xf_flag bits */ #define XF_LINKED 0x1 /* * Structure to hold info about an exported directory */ struct expdir { TAILQ_ENTRY(expdir) xd_next; struct host *xd_hosts; /* List of hosts this dir exported to */ struct expdirtqh xd_mountdirs; /* list of mountable sub-directories */ int xd_flags; /* default export flags */ char *xd_dir; /* pathname of exported directory */ }; /* * Structures for holding sets of exported-to hosts/nets/addresses */ /* holds a network/mask and name */ struct netmsk { u_long nt_net; /* network address */ u_long nt_mask; /* network mask */ char *nt_name; /* network name */ }; /* holds either a host or network */ union grouptypes { struct hostent *gt_hostent; struct netmsk gt_net; #ifdef ISO struct sockaddr_iso *gt_isoaddr; #endif }; /* host/network list entry */ struct grouplist { int gr_type; /* type of group */ union grouptypes gr_u; /* the host/network */ struct grouplist *gr_next; }; /* Group types */ #define GT_NULL 0x0 /* not fully-initialized yet */ #define GT_HOST 0x1 /* this is a single host address */ #define GT_NET 0x2 /* this is a network */ #define GT_ISO 0x4 /* * host/network flags list entry */ struct host { int ht_flag; /* export options for these hosts */ struct grouplist *ht_grp; /* host/network flags applies to */ struct host *ht_next; }; #define DEFAULTHOSTNAME "localhost" struct fhreturn { int fhr_flag; int fhr_vers; fhandle_t fhr_fh; }; /* Global defs */ int add_dir(struct dirlist **, char *); char * add_expdir(struct expdir **, char *, int); void add_mlist(char *, char *); int check_dirpath(char *); int check_options(int); void del_mlist(char *, char *); int expdir_search(struct expfs *, char *, u_long, int *); int do_export(struct expfs *, struct grouplist *, int, struct xucred *, char *, struct statfs *, struct uuidlist *); int do_opt(char **, char **, struct grouplist *, int *, int *, int *, struct xucred *); struct expfs *ex_search(u_char *); struct host *find_host(struct host *, u_long); void free_dirlist(struct dirlist *dl); void free_expdir(struct expdir *); void free_expfs(struct expfs *); void free_grp(struct grouplist *); void free_host(struct host *); struct expdir *get_expdir(void); struct expfs *get_expfs(void); void get_hostnames(char **hostnamearray); char * get_ifinfo(int, int, int *); void get_exportlist(void); int get_host_addresses(char *, struct grouplist *); struct host *get_host(void); int get_line(int); int ni_get_line(void); int file_get_line(void); void get_mountlist(void); int get_net(char *, struct netmsk *, int); void getexp_err(struct expfs *, struct grouplist *); struct grouplist *get_grp(void); int hang_options(struct expdir *, int, struct grouplist *); int hang_options_mountdir(struct expdir *, char *, int, struct grouplist *); void mntsrv(struct svc_req *, SVCXPRT *); void my_svc_run(void); void nextfield(char **, char **); void out_of_mem(char *); void parsecred(char *, struct xucred *); int put_exlist(struct expdir *, XDR *); int register_export(const char *, char * const *, int); void sigmux(int); int xdr_dir(XDR *, char *); int xdr_explist(XDR *, caddr_t); int xdr_fhs(XDR *, caddr_t); int xdr_mlist(XDR *, caddr_t); int get_uuid_from_diskarb(const char *, u_char *); struct uuidlist * get_uuid_from_list(const struct statfs *, u_char *, const int); struct uuidlist * add_uuid_to_list(const struct statfs *, u_char *, u_char *); struct uuidlist * get_uuid(const struct statfs *, u_char *); struct uuidlist * find_uuid(u_char *); struct uuidlist * find_uuid_by_fsid(u_int32_t); void uuidlist_clearexport(void); char * uuidstring(u_char *, char *); void uuidlist_save(void); void uuidlist_restore(void); struct expidlist * find_export_id(struct uuidlist *, u_int32_t); struct expidlist * get_export_id(struct uuidlist *, char *); /* C library */ int getnetgrent(char **host, char **user, char **domain); void endnetgrent(void); void setnetgrent(const char *netgroup); #ifdef ISO struct iso_addr *iso_addr(void); #endif void ni_exports_open(void); void ni_exports_close(void); struct expfstqh xfshead; struct mountlist *mlhead; struct grouplist *grphead; char exportsfilepath[MAXPATHLEN]; struct xucred def_anon = { XUCRED_VERSION, (uid_t) -2, 1, { (gid_t) -2 }, }; int resvport_only = 1; int dir_only = 1; /* export options */ #define OP_MAPROOT 0x00000001 /* map root credentials */ #define OP_MAPALL 0x00000002 /* map all credentials */ #define OP_KERB 0x00000004 /* use Kerberos */ #define OP_MASK 0x00000008 /* network mask specified */ #define OP_NET 0x00000010 /* network address specified */ #define OP_ISO 0x00000020 /* ISO address specified */ #define OP_ALLDIRS 0x00000040 /* allow mounting subdirs */ #define OP_READONLY 0x00000080 /* export read-only */ #define OP_32BITCLIENTS 0x00000100 /* use 32-bit directory cookies */ #define OP_DEFEXP 0x40000000 /* default export for everyone (else) */ #define OP_EXOPTMASK 0x000000C7 /* export options mask */ #define MAXHOSTNAMES 10 char *our_hostnames[MAXHOSTNAMES]; /* for system we are running on */ static char urlprefix[] = "nfs://"; static char slpprefix[] = "service:x-file-service:nfs://"; static int maxprefix = (sizeof(slpprefix) > sizeof(urlprefix) ? sizeof(slpprefix) : sizeof(urlprefix)); static char URLRegistrar[] = _PATH_SLP_REG; #define ADD_URL 1 #define DELETE_URL 0 int debug = 0; void SYSLOG(int, const char *, ...); #define log SYSLOG /* * Mountd server for NFS mount protocol as described in: * NFS: Network File System Protocol Specification, RFC1094, Appendix A * The optional arguments are the exports file name * default: _PATH_EXPORTS * and "-n" to allow nonroot mount. */ int main(int argc, char *argv[]) { SVCXPRT *udptransp, *tcptransp; int c, i; while ((c = getopt(argc, argv, "Ddfnr")) != EOF) { switch (c) { case 'D': debug = 1; break; case 'd': debug = 2; fprintf(stderr, "Debug Enabled. Logging to stderr.\n"); break; case 'f': source = EXPORT_FROM_FILE; break; case 'n': resvport_only = 0; break; case 'r': dir_only = 0; break; default: fprintf(stderr, "Usage: mountd [-d] [-r] [-n] [-f] [export_file]\n"); exit(1); } } argc -= optind; argv += optind; TAILQ_INIT(&xfshead); mlhead = NULL; grphead = NULL; TAILQ_INIT(&ulhead); for (i = 0; i < MAXHOSTNAMES; ++i) our_hostnames[i] = NULL; if (argc == 1) { source = EXPORT_FROM_FILE; strncpy(exportsfilepath, *argv, MAXPATHLEN-1); exportsfilepath[MAXPATHLEN-1] = '\0'; } else strcpy(exportsfilepath, _PATH_EXPORTS); openlog("mountd", LOG_PID, LOG_DAEMON); if (debug < 2) log(LOG_DEBUG, "Debug Logging Enabled."); uuidlist_restore(); log(LOG_DEBUG, "Getting export list."); get_exportlist(); log(LOG_DEBUG, "Getting mount list."); get_mountlist(); log(LOG_DEBUG, "Here we go."); if (debug < 2) { daemon(0, 0); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } signal(SIGHUP, sigmux); signal(SIGTERM, sigmux); { FILE *pidfile = fopen(_PATH_MOUNTDPID, "w"); if (pidfile != NULL) { fprintf(pidfile, "%d\n", getpid()); fclose(pidfile); } } if ((udptransp = svcudp_create(RPC_ANYSOCK)) == NULL || (tcptransp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL) { log(LOG_ERR, "Can't create socket"); exit(1); } pmap_unset(RPCPROG_MNT, 1); pmap_unset(RPCPROG_MNT, 3); if (!svc_register(udptransp, RPCPROG_MNT, 1, mntsrv, IPPROTO_UDP) || !svc_register(udptransp, RPCPROG_MNT, 3, mntsrv, IPPROTO_UDP) || !svc_register(tcptransp, RPCPROG_MNT, 1, mntsrv, IPPROTO_TCP) || !svc_register(tcptransp, RPCPROG_MNT, 3, mntsrv, IPPROTO_TCP)) { log(LOG_ERR, "Can't register mount"); exit(1); } my_svc_run(); log(LOG_ERR, "Mountd died"); exit(1); } volatile static int gothup; volatile static int gotterm; void sigmux(int sig) { switch (sig) { case SIGHUP: gothup = 1; break; case SIGTERM: gotterm = 1; break; } } void my_svc_run(void) { fd_set readfdset; static int tsize = 0; int x; if (tsize == 0) tsize = getdtablesize(); for ( ; ; ) { bcopy(&svc_fdset, &readfdset, sizeof(svc_fdset)); x = select(tsize, &readfdset, NULL, NULL, NULL); if (x > 0) { svc_getreqset(&readfdset); } while (gothup && !gotterm) { gothup = 0; get_exportlist(); } if (gotterm) { gotterm = 0; exit(0); } } } /* * The mount rpc service */ void mntsrv( struct svc_req *rqstp, SVCXPRT *transp) { struct expfs *xf; struct fhreturn fhr; struct stat stb; struct statfs fsb; struct hostent *hp; u_long saddr; u_short sport; char rpcpath[RPCMNT_PATHLEN + 1], dirpath[MAXPATHLEN], *mhost; int bad = ENOENT, options; sigset_t sighup_mask; u_char uuid[16]; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); saddr = transp->xp_raddr.sin_addr.s_addr; sport = ntohs(transp->xp_raddr.sin_port); hp = NULL; switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send NULL reply"); return; case RPCMNT_MOUNT: if (sport >= IPPORT_RESERVED && resvport_only) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, xdr_dir, rpcpath)) { svcerr_decode(transp); return; } /* * Get the real pathname and make sure it is a directory * or a regular file if the -r option was specified * and it exists. */ if (realpath(rpcpath, dirpath) == 0 || stat(dirpath, &stb) < 0 || (!S_ISDIR(stb.st_mode) && (dir_only || !S_ISREG(stb.st_mode))) || statfs(dirpath, &fsb) < 0) { chdir("/"); /* Just in case realpath doesn't */ log(LOG_DEBUG, "stat failed on %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); return; } /* get UUID for volume */ if (!get_uuid_from_list(&fsb, uuid, UL_CHECK_ALL)) { log(LOG_DEBUG, "no exported volume uuid for %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); return; } /* Check in the exports list */ sigprocmask(SIG_BLOCK, &sighup_mask, NULL); xf = ex_search(uuid); if (xf && expdir_search(xf, dirpath, saddr, &options)) { fhr.fhr_flag = options; fhr.fhr_vers = rqstp->rq_vers; /* Get the file handle */ memset(&fhr.fhr_fh, 0, sizeof(fhandle_t)); if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) { log(LOG_DEBUG, "Can't get fh for %s: %s (%d)", dirpath, strerror(errno), errno); bad = EACCES; /* path must not be exported */ if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr)) log(LOG_ERR, "Can't send mount reply"); hp = gethostbyaddr((caddr_t)&transp->xp_raddr.sin_addr, sizeof(transp->xp_raddr.sin_addr), AF_INET); mhost = hp ? hp->h_name : inet_ntoa(transp->xp_raddr.sin_addr); add_mlist(mhost, dirpath); log(LOG_DEBUG, "Mount successful: %s %s", mhost, dirpath); } else { bad = EACCES; if (debug) { mhost = inet_ntoa(transp->xp_raddr.sin_addr); log(LOG_DEBUG, "Mount failed: %s %s", mhost, dirpath); } if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); } sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; case RPCMNT_DUMP: if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL)) log(LOG_ERR, "Can't send dump reply"); return; case RPCMNT_UMOUNT: if (sport >= IPPORT_RESERVED && resvport_only) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, xdr_dir, dirpath)) { svcerr_decode(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send umount reply"); hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET); if (hp) del_mlist(hp->h_name, dirpath); del_mlist(inet_ntoa(transp->xp_raddr.sin_addr), dirpath); return; case RPCMNT_UMNTALL: if (sport >= IPPORT_RESERVED && resvport_only) { svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send umntall reply"); hp = gethostbyaddr((caddr_t)&saddr, sizeof(saddr), AF_INET); if (hp) del_mlist(hp->h_name, (char *)NULL); del_mlist(inet_ntoa(transp->xp_raddr.sin_addr), (char *)NULL); return; case RPCMNT_EXPORT: if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL)) log(LOG_ERR, "Can't send export reply"); return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dirpath string */ int xdr_dir(xdrsp, dirp) XDR *xdrsp; char *dirp; { return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); } /* * Xdr routine to generate file handle reply */ int xdr_fhs(xdrsp, cp) XDR *xdrsp; caddr_t cp; { register struct fhreturn *fhrp = (struct fhreturn *)cp; long ok = 0, len, auth; if (!xdr_long(xdrsp, &ok)) return (0); switch (fhrp->fhr_vers) { case 1: return (xdr_opaque(xdrsp, (caddr_t)fhrp->fhr_fh.fh_data, NFSX_V2FH)); case 3: len = fhrp->fhr_fh.fh_len; if (!xdr_long(xdrsp, &len)) return (0); if (!xdr_opaque(xdrsp, (caddr_t)fhrp->fhr_fh.fh_data, fhrp->fhr_fh.fh_len)) return (0); if (fhrp->fhr_flag & OP_KERB) auth = RPCAUTH_KERB4; else auth = RPCAUTH_UNIX; len = 1; if (!xdr_long(xdrsp, &len)) return (0); return (xdr_long(xdrsp, &auth)); }; return (0); } int xdr_mlist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { struct mountlist *mlp; int trueval = 1; int falseval = 0; mlp = mlhead; while (mlp) { if (!xdr_bool(xdrsp, &trueval)) return (0); if (!xdr_string(xdrsp, &mlp->ml_host, RPCMNT_NAMELEN)) return (0); if (!xdr_string(xdrsp, &mlp->ml_dir, RPCMNT_PATHLEN)) return (0); mlp = mlp->ml_next; } if (!xdr_bool(xdrsp, &falseval)) return (0); return (1); } /* * Xdr conversion for export list */ int xdr_explist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { struct expfs *xf; struct expdir *xd; int falseval = 0; sigset_t sighup_mask; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); sigprocmask(SIG_BLOCK, &sighup_mask, NULL); TAILQ_FOREACH_REVERSE(xf, &xfshead, xf_next, expfstqh) { TAILQ_FOREACH_REVERSE(xd, &xf->xf_dirl, xd_next, expdirtqh) { if (put_exlist(xd, xdrsp)) goto errout; } } sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); if (!xdr_bool(xdrsp, &falseval)) return (0); return (1); errout: sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return (0); } /* * Called from xdr_explist() to output the mountable exported * directory paths. */ int put_exlist(struct expdir *xd, XDR *xdrsp) { struct expdir *mxd; struct grouplist *grp; struct host *hp; int trueval = 1; int falseval = 0; char *strp; if (!xd) return (0); if (!xdr_bool(xdrsp, &trueval)) return (1); strp = xd->xd_dir; if (!xdr_string(xdrsp, &strp, RPCMNT_PATHLEN)) return (1); if (!(xd->xd_flags & OP_DEFEXP)) { hp = xd->xd_hosts; while (hp) { grp = hp->ht_grp; if (grp->gr_type == GT_HOST) { if (!xdr_bool(xdrsp, &trueval)) return (1); strp = grp->gr_u.gt_hostent->h_name; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } else if (grp->gr_type == GT_NET) { if (!xdr_bool(xdrsp, &trueval)) return (1); strp = grp->gr_u.gt_net.nt_name; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } hp = hp->ht_next; } } if (!xdr_bool(xdrsp, &falseval)) return (1); TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) { if (put_exlist(mxd, xdrsp)) return (1); } return (0); } /* * Snitched from unpv12e (R. W. Stevens) * Fill in an array of sockaddr structs, * based on a bit array indicating presence. */ void get_rtinfo(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) { register int i; /* * Round up 'a' to next multiple of 'size' */ #define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a)) /* * Step to next socket address structure; * if sa_len is 0, assume it is sizeof(u_long). */ #define NEXT_SA(ap) ap = (struct sockaddr *) \ ((caddr_t) ap + (ap->sa_len ? \ ROUNDUP(ap->sa_len, sizeof (u_long)) : \ sizeof(u_long))) for (i = 0; i < RTAX_MAX; i++) if (addrs & (1 << i)) { rti_info[i] = sa; NEXT_SA(sa); } else rti_info[i] = NULL; } /* * Use sysctl() to get the info on interface-related * information. We can look for a specific device, or * a specific protocol family. */ char * get_ifinfo(int family, int index, int *buflen) { int mib[6]; char *buf; mib[0] = CTL_NET; mib[1] = AF_ROUTE; /* PF_ maybe */ mib[2] = 0; /* XXX */ mib[3] = family; /* Target family */ mib[4] = NET_RT_IFLIST; mib[5] = index; /* either a specific link or 0 */ /* Determine how much space is needed */ if (sysctl(mib, 6, NULL, (size_t *)buflen, NULL, 0) == -1) { log(LOG_ERR, "Error requesting network interface info size: errno = %d", errno); return (NULL); } /* ... allocate space, */ buf = (char *)malloc(*buflen); if (buf == NULL) { log(LOG_ERR, "Error allocating %d bytes for network interface into", buflen); return (NULL); } /* ... and then get a buffer full */ if (sysctl(mib, 6, buf, (size_t *)buflen, NULL, 0) == -1) { free(buf); log(LOG_ERR, "Error requesting network interface info: errno = %d", errno); return (NULL); } return (buf); } /* * Add a host name to the global table if it's not already in the table: */ void record_hostname(const char *hostname, char **hostnamearray) { int i, hostnamelen; hostnamelen = strlen(hostname); if (hostnamelen > MAXHOSTNAMELEN) return; for (i = 0; i < MAXHOSTNAMES && hostnamearray[i]; i++) if (!strcmp(hostnamearray[i], hostname)) return; if (i >= MAXHOSTNAMES) return; hostnamearray[i] = malloc(hostnamelen + 1); if (hostnamearray[i] == NULL) { log(LOG_ERR, "record_hostname: couldn't allocate memory for hostname '%s'.", hostname); return; } strcpy(hostnamearray[i], hostname); log(LOG_DEBUG, "record_hostname: hostnamearray[%d] = '%s'.", i, hostnamearray[i]); } /* * Get the list of host names: */ void get_hostnames(char **hostnamearray) { char *ifinfo; int ifinfo_len; int i; struct if_msghdr *ifm; struct ifa_msghdr *ifam; struct sockaddr *sa, *rti_info[RTAX_MAX]; int if_flags = 0; struct hostent *hostinfo; struct sockaddr_in *sain; ifinfo = get_ifinfo(AF_INET, 0, &ifinfo_len); if (ifinfo == NULL) { log(LOG_ERR, "get_hostnames: no ifinfo"); return; } /* Release any currently allocated host name strings: */ for (i = 0; (i < MAXHOSTNAMES) && (hostnamearray[i] != NULL); ++i) { free(hostnamearray[i]); hostnamearray[i] = NULL; } /* * Process info from the retrieved interface information * We're looking for 'if' info and associated socketaddrs * that describe assigned protocol addresses * From Stevens' Unix Network Programming, V1, 2nd Ed. * Ch. 17 (esp. 17.4). * We borrowed a bit of code, as indicated. */ /* 'ifinfo' contains a batch of 'ifm' and 'ifam' structs */ for (ifm = (struct if_msghdr *)ifinfo; (char *)ifm < ifinfo+ifinfo_len; ifm = (struct if_msghdr *)((char *)ifm + ifm->ifm_msglen)) { /* * We're interested in: * RTM_IFINFO (ifm structs describing an interface) * RTM_NEWADDR (ifam structs describing 'protocol' addresses * associated with the preceding ifm) * Note that each IFINFO struct is followed by all of its * NEWADDR structs. */ if (ifm->ifm_type == RTM_IFINFO) { if_flags = ifm->ifm_flags; /* To check IFF_LOOPBACK */ continue; } if (ifm->ifm_type != RTM_NEWADDR || (if_flags & IFF_LOOPBACK)) continue; ifam = (struct ifa_msghdr *)ifm; sa = (struct sockaddr *)(ifam+1); get_rtinfo(ifam->ifam_addrs, sa, rti_info); sa = rti_info[RTAX_IFA]; if (!sa) continue; if (sa->sa_family != AF_INET) { log(LOG_DEBUG, "unknown socket address family %d.", sa->sa_family); continue; } sain = (struct sockaddr_in *)sa; hostinfo = gethostbyaddr((caddr_t)&sain->sin_addr, sizeof(sain->sin_addr), AF_INET); if (hostinfo) { record_hostname(hostinfo->h_name, hostnamearray); } else { record_hostname(inet_ntoa(sain->sin_addr), hostnamearray); }; } free(ifinfo); /* allocated by get_ifinfo */ } /* * Strip escaped spaces and remove quotes around quoted strings. */ char * clean_white_space(char *line) { int len, esc; char c, *p, *s; if (line == NULL) return NULL; len = strlen(line); s = malloc(len + 1); if (s == NULL) return NULL; len = 0; esc = 0; c = '\0'; p = line; if (*p == '\'' || *p == '"') { c = *p; p++; } for (;*p != '\0'; p++) { if (esc == 1) { s[len++] = *p; esc = 0; } else if (*p == c) break; else if (*p == '\\') esc = 1; else if (c == '\0' && (*p == ' ' || *p == '\t')) break; else s[len++] = *p; } s[len] = '\0'; return (s); } int safe_exec(char *pgm, char *arg1, char *arg2) { int pid; union wait status; pid = fork(); if (pid == 0) { (void) execl(pgm, pgm, arg1, arg2, NULL); log(LOG_ERR, "Exec of %s failed: %s (%d)", pgm, strerror(errno), errno); exit(2); } if (pid == -1) { log(LOG_ERR, "Fork for %s failed: %s (%d)", pgm, strerror(errno), errno); exit(2); } if (wait4(pid, (int *)&status, 0, NULL) != pid) { log(LOG_ERR, "BUG executing %s", pgm); exit(2); } if (!WIFEXITED(status)) { log(LOG_ERR, "%s aborted by signal %d", pgm, WTERMSIG(status)); exit(2); } return (WEXITSTATUS(status)); } /* * slp_reg returns this if you try to delete an unregistered URL * Better is to use "slp_reg -l" to enumerate actual registration, but that * flag is not yet implemented */ #define URLNOTFOUND 253 int register_export(const char *path, char * const * hostnamearray, int addurl) { int i; int urlstringlength = maxprefix + MAXHOSTNAMELEN + strlen(path); char *urlstring; int rv, result = 0; /* note any errors we return are to be nonfatal and already logged */ urlstring = malloc(urlstringlength); if (urlstring == NULL) { log(LOG_ERR, "can't allocate url string to register export: %s", path); return (ENOMEM); } for (i = 0; (i < MAXHOSTNAMES) && (hostnamearray[i] != NULL); ++i) { if (urlstringlength < maxprefix + strlen(hostnamearray[i]) + strlen(path)) { log(LOG_ERR, "huge hostname (ignored): %s", hostnamearray[i]); result = ENAMETOOLONG; continue; } /* Register traditional URL: */ strcpy(urlstring, urlprefix); strcat(urlstring, hostnamearray[i]); strcat(urlstring, path); log(LOG_DEBUG, "%sregistering URL %s", (addurl ? "" : "un"), urlstring); rv = safe_exec(URLRegistrar, (addurl ? "-r" : "-d"), urlstring); if (rv && (addurl || rv != URLNOTFOUND)) { log(LOG_ERR, "%s exit status %d", URLRegistrar, rv); result = rv; /* arbitrarily retaining last failure */ } /* Register new-style URL: */ strcpy(urlstring, slpprefix); strcat(urlstring, hostnamearray[i]); strcat(urlstring, path); log(LOG_DEBUG, "%sregistering URL %s", (addurl ? "" : "un"), urlstring); rv = safe_exec(URLRegistrar, (addurl ? "-r" : "-d"), urlstring); if (rv && (addurl || rv != URLNOTFOUND)) { log(LOG_ERR, "%s exit status %d", URLRegistrar, rv); result = rv; /* arbitrarily retaining last failure */ } } free(urlstring); return (result); } /* * Query DiskArb for a volume's UUID */ int get_uuid_from_diskarb(const char *path, u_char *uuid) { DASessionRef session; DADiskRef disk; CFDictionaryRef dd; CFTypeRef val; CFUUIDBytes uuidbytes; int rv = 1; session = NULL; disk = NULL; dd = NULL; session = DASessionCreate(NULL); if (!session) { log(LOG_ERR, "can't create DiskArb session"); rv = 0; goto out; } disk = DADiskCreateFromBSDName(NULL, session, path); if (!disk) { log(LOG_DEBUG, "DADiskCreateFromBSDName(%s) failed", path); rv = 0; goto out; } dd = DADiskCopyDescription(disk); if (!dd) { log(LOG_DEBUG, "DADiskCopyDescription(%s) failed", path); rv = 0; goto out; } if (!CFDictionaryGetValueIfPresent(dd, (kDADiskDescriptionVolumeUUIDKey), &val)) { log(LOG_DEBUG, "unable to get UUID for volume %s", path); rv = 0; goto out; } uuidbytes = CFUUIDGetUUIDBytes(val); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); out: if (session) CFRelease(session); if (disk) CFRelease(disk); if (dd) CFRelease(dd); return (rv); } /* * find the UUID for this volume in the UUID list */ struct uuidlist * get_uuid_from_list(const struct statfs *fsb, u_char *uuid, const int flags) { struct uuidlist *ulp; if (!(flags & UL_CHECK_ALL)) return (NULL); TAILQ_FOREACH(ulp, &ulhead, ul_list) { if ((flags & UL_CHECK_MNTFROM) && strcmp(fsb->f_mntfromname, ulp->ul_mntfromname)) continue; if ((flags & UL_CHECK_MNTON) && strcmp(fsb->f_mntonname, ulp->ul_mntonname)) continue; if (uuid) bcopy(&ulp->ul_uuid, uuid, sizeof(ulp->ul_uuid)); break; } return (ulp); } /* * find UUID list entry with the given UUID */ struct uuidlist * find_uuid(u_char *uuid) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { if (!bcmp(ulp->ul_uuid, uuid, sizeof(ulp->ul_uuid))) break; } return (ulp); } /* * find UUID list entry with the given FSID */ struct uuidlist * find_uuid_by_fsid(u_int32_t fsid) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { if (ulp->ul_fsid == fsid) break; } return (ulp); } /* * add a UUID to the UUID list */ struct uuidlist * add_uuid_to_list(const struct statfs *fsb, u_char *dauuid, u_char *uuid) { struct uuidlist *ulpnew; u_int32_t xfsid; ulpnew = malloc(sizeof(struct uuidlist)); if (!ulpnew) { log(LOG_ERR, "add_uuid_to_list: out of memory"); return (NULL); } bzero(ulpnew, sizeof(*ulpnew)); LIST_INIT(&ulpnew->ul_exportids); if (dauuid) { bcopy(dauuid, ulpnew->ul_dauuid, sizeof(ulpnew->ul_dauuid)); ulpnew->ul_davalid = 1; } bcopy(uuid, ulpnew->ul_uuid, sizeof(ulpnew->ul_uuid)); strcpy(ulpnew->ul_mntfromname, fsb->f_mntfromname); strcpy(ulpnew->ul_mntonname, fsb->f_mntonname); /* make sure exported FS ID is unique */ xfsid = UUID2FSID(uuid); ulpnew->ul_fsid = xfsid; while (find_uuid_by_fsid(ulpnew->ul_fsid)) if (++ulpnew->ul_fsid == xfsid) { /* exhausted exported FS ID values! */ log(LOG_ERR, "exported FS ID values exhausted, can't add %s", ulpnew->ul_mntonname); free(ulpnew); return (NULL); } TAILQ_INSERT_TAIL(&ulhead, ulpnew, ul_list); return (ulpnew); } /* * get the UUID to use for this volume's file handles * and add it to the UUID list if it isn't there yet. */ struct uuidlist * get_uuid(const struct statfs *fsb, u_char *uuid) { CFUUIDRef cfuuid; CFUUIDBytes uuidbytes; struct uuidlist *ulp; u_char dauuid[16]; int davalid, uuidchanged, reportuuid = 0; char buf[64], buf2[64]; /* get DiskArb's idea of the UUID (if any) */ davalid = get_uuid_from_diskarb(fsb->f_mntfromname, dauuid); if (davalid) { log(LOG_DEBUG, "get_uuid: %s %s DiskArb says: %s", fsb->f_mntfromname, fsb->f_mntonname, uuidstring(dauuid, buf)); } /* try to get UUID out of UUID list */ if ((ulp = get_uuid_from_list(fsb, uuid, UL_CHECK_MNTON))) { log(LOG_DEBUG, "get_uuid: %s %s found: %s", fsb->f_mntfromname, fsb->f_mntonname, uuidstring(uuid, buf)); /* * Check against any DiskArb UUID. * If diskarb UUID is different then drop the uuid entry. */ if (davalid) { if (!ulp->ul_davalid) uuidchanged = 1; else if (bcmp(ulp->ul_dauuid, dauuid, sizeof(dauuid))) uuidchanged = 1; else uuidchanged = 0; } else { if (ulp->ul_davalid) { /* * We had a UUID before, but now we don't? * Assume this is just a transient error, * issue a warning, and stick with the old UUID. */ uuidstring(ulp->ul_dauuid, buf); log(LOG_WARNING, "lost UUID for %s, was %s, keeping old UUID", fsb->f_mntonname, buf); uuidchanged = 0; } else uuidchanged = 0; } if (uuidchanged) { uuidstring(ulp->ul_dauuid, buf); if (davalid) uuidstring(dauuid, buf2); else strcpy(buf2, "------------------------------------"); if (ulp->ul_exported) { /* * Woah! We already have this file system exported with * a different UUID (UUID changed while processing the * exports list). Ignore the UUID change for now so that * all the exports for this file system will be registered * using the same UUID/FSID. * * XXX Should we do something like set gothup=1 so that * we will reregister all the exports (with the new UUID)? * If so, what's to prevent an infinite loop if we always * seem to be hitting this problem? */ log(LOG_WARNING, "ignoring UUID change for already exported file system %s, was %s now %s", fsb->f_mntonname, buf, buf2); uuidchanged = 0; } } if (uuidchanged) { log(LOG_WARNING, "UUID changed for %s, was %s now %s", fsb->f_mntonname, buf, buf2); bcopy(dauuid, uuid, sizeof(dauuid)); /* remove old UUID from list */ TAILQ_REMOVE(&ulhead, ulp, ul_list); free(ulp); ulp = NULL; } else { ulp->ul_exported = 1; } } else if (davalid) { /* * The UUID wasn't in the list, but DiskArb has a UUID for it. * (If the DiskArb UUID conflicts with something already in the * list, we'll need to create a new UUID for it below.) */ bcopy(dauuid, uuid, sizeof(dauuid)); } else { /* * We need to create a UUID to use for this volume. * This is because it wasn't already in the list, and * either DiskArb didn't have a UUID for the volume or * the UUID DiskArb has for the volume conflicts with * a UUID for a volume already in the list. */ reportuuid = 1; cfuuid = CFUUIDCreate(NULL); uuidbytes = CFUUIDGetUUIDBytes(cfuuid); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); CFRelease(cfuuid); } if (!ulp) { /* * Add the UUID to the list, but make sure it is unique first. */ while ((ulp = find_uuid(uuid))) { reportuuid = 1; uuidstring(uuid, buf); log(LOG_WARNING, "%s UUID conflict with %s, %s", fsb->f_mntonname, ulp->ul_mntonname, buf); cfuuid = CFUUIDCreate(NULL); uuidbytes = CFUUIDGetUUIDBytes(cfuuid); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); CFRelease(cfuuid); /* double check that the UUID is unique */ } ulp = add_uuid_to_list(fsb, (davalid ? dauuid : NULL), uuid); if (!ulp) { log(LOG_ERR, "error adding %s", fsb->f_mntonname); } else { ulp->ul_exported = 1; } } else if (!ulp->ul_mntfromname[0]) { /* * If the volume didn't exist when mountd read the * mountdexptab, it's possible this ulp doesn't * have a copy of it's mntfromname. So, we make * sure to grab a copy here before the volume gets * exported. */ strcpy(ulp->ul_mntfromname, fsb->f_mntfromname); } if (reportuuid) log(LOG_WARNING, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf)); else log(LOG_DEBUG, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf)); return (ulp); } /* * clear export flags on all UUID entries */ void uuidlist_clearexport(void) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { ulp->ul_exported = 0; } } /* convert UUID bytes to UUID string */ #define HEXTOC(c) \ ((c) >= 'a' ? ((c) - ('a' - 10)) : \ ((c) >= 'A' ? ((c) - ('A' - 10)) : ((c) - '0'))) #define HEXSTRTOI(p) \ ((HEXTOC(p[0]) << 4) + HEXTOC(p[1])) char * uuidstring(u_char *uuid, char *string) { sprintf(string, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", uuid[0] & 0xff, uuid[1] & 0xff, uuid[2] & 0xff, uuid[3] & 0xff, uuid[4] & 0xff, uuid[5] & 0xff, uuid[6] & 0xff, uuid[7] & 0xff, uuid[8] & 0xff, uuid[9] & 0xff, uuid[10] & 0xff, uuid[11] & 0xff, uuid[12] & 0xff, uuid[13] & 0xff, uuid[14] & 0xff, uuid[15] & 0xff); return (string); } /* * save the exported volume UUID list to the mountdexptab file * * We have the option of saving all UUIDs in the list, or just * saving the ones that are currently exported. However, if we * have a volume exported, then removed from the export list, and * then added back to the export list, it may be expected that the * file handles/UUIDs will be the same. But if we don't save what * the UUIDs were before, we risk the chance of using a different * UUID for the second export. This can happen if the volume's * DiskArb UUID is not used for export (because DiskArb doesn't have * a UUID for it, or because there was a UUID conflict and we needed * to use a different UUID). */ void uuidlist_save(void) { FILE *ulfile; struct uuidlist *ulp; struct expidlist *xid; char buf[64], buf2[64]; if ((ulfile = fopen(_PATH_MOUNTEXPLIST, "w")) == NULL) { log(LOG_ERR, "Can't write %s: %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); return; } TAILQ_FOREACH(ulp, &ulhead, ul_list) { #ifdef SAVE_ONLY_EXPORTED_UUIDS if (!ulp->ul_exported) continue; #endif if (ulp->ul_davalid) uuidstring(ulp->ul_dauuid, buf); else strcpy(buf, "------------------------------------"); uuidstring(ulp->ul_uuid, buf2); fprintf(ulfile, "%s %s 0x%08X %s\n", buf, buf2, ulp->ul_fsid, ulp->ul_mntonname); LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { fprintf(ulfile, "XID 0x%08X %s\n", xid->xid_id, ((xid->xid_path[0] == '\0') ? "." : xid->xid_path)); } } fclose(ulfile); } /* * read in the exported volume UUID list from the mountdexptab file */ void uuidlist_restore(void) { struct uuidlist *ulp; struct expidlist *xid; char *cp, str[2*MAXPATHLEN]; FILE *ulfile; int i, slen, linenum, davalid, uuidchanged; struct statfs fsb; u_char dauuid[16]; char buf[64], buf2[64]; if ((ulfile = fopen(_PATH_MOUNTEXPLIST, "r")) == NULL) { if (errno != ENOENT) log(LOG_NOTICE, "Can't open %s: %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); else log(LOG_DEBUG, "Can't open %s, %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); return; } ulp = NULL; linenum = 0; while (fgets(str, 2*MAXPATHLEN, ulfile) != NULL) { linenum++; slen = strlen(str); if (str[slen-1] == '\n') str[slen-1] = '\0'; if (!strncmp(str, "XID ", 4)) { /* we have an export ID line for the current UUID */ if (!ulp) { log(LOG_ERR, "ignoring orphaned export ID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); continue; } /* parse XID and add to current UUID */ xid = malloc(sizeof(*xid)); if (xid == NULL) out_of_mem("uuidlist_restore"); cp = str + 4; slen -= 4; if (sscanf(cp, "%i", &xid->xid_id) != 1) { log(LOG_ERR, "invalid export ID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(xid); continue; } while (*cp && (*cp != ' ')) { cp++; slen--; } cp++; slen--; if (slen >= sizeof(xid->xid_path)) { log(LOG_ERR, "export ID path too long at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(xid); continue; } if ((cp[0] == '.') && (cp[1] == '\0')) xid->xid_path[0] = '\0'; else strcpy(xid->xid_path, cp); LIST_INSERT_HEAD(&ulp->ul_exportids, xid, xid_list); continue; } ulp = malloc(sizeof(*ulp)); if (ulp == NULL) out_of_mem("uuidlist_restore"); bzero(ulp, sizeof(*ulp)); LIST_INIT(&ulp->ul_exportids); cp = str; if (*cp == '-') { /* DiskArb UUID not present */ ulp->ul_davalid = 0; bzero(ulp->ul_dauuid, sizeof(ulp->ul_dauuid)); while (*cp && (*cp != ' ')) cp++; } else { ulp->ul_davalid = 1; for (i=0; i < sizeof(ulp->ul_dauuid); i++, cp+=2) { if (*cp == '-') cp++; if (!isxdigit(*cp) || !isxdigit(*(cp+1))) { log(LOG_ERR, "invalid UUID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); ulp = NULL; break; } ulp->ul_dauuid[i] = HEXSTRTOI(cp); } } if (ulp == NULL) continue; cp++; for (i=0; i < sizeof(ulp->ul_uuid); i++, cp+=2) { if (*cp == '-') cp++; if (!isxdigit(*cp) || !isxdigit(*(cp+1))) { log(LOG_ERR, "invalid UUID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); ulp = NULL; break; } ulp->ul_uuid[i] = HEXSTRTOI(cp); } if (ulp == NULL) continue; if (*cp != ' ') { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } cp++; if (sscanf(cp, "%i", &ulp->ul_fsid) != 1) { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } while (*cp && (*cp != ' ')) cp++; if (*cp != ' ') { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } cp++; strncpy(ulp->ul_mntonname, cp, MAXPATHLEN); ulp->ul_mntonname[MAXPATHLEN-1] = '\0'; /* verify the path exists and that it is a mount point */ if (!check_dirpath(ulp->ul_mntonname) || (statfs(ulp->ul_mntonname, &fsb) < 0) || strcmp(ulp->ul_mntonname, fsb.f_mntonname)) { /* don't drop the UUID record if the volume isn't currently mounted! */ /* If it's mounted/exported later, we want to use the same record. */ log(LOG_DEBUG, "export entry for non-existent file system %s at line %d of %s", ulp->ul_mntonname, linenum, _PATH_MOUNTEXPLIST); ulp->ul_mntfromname[0] = '\0'; TAILQ_INSERT_TAIL(&ulhead, ulp, ul_list); continue; } /* grab the path's mntfromname */ strncpy(ulp->ul_mntfromname, fsb.f_mntfromname, MAXPATHLEN); ulp->ul_mntfromname[MAXPATHLEN-1] = '\0'; /* * Grab DiskArb's UUID for this volume (if any) and * see if it has changed. */ davalid = get_uuid_from_diskarb(ulp->ul_mntfromname, dauuid); if (davalid) { if (!ulp->ul_davalid) uuidchanged = 1; else if (bcmp(ulp->ul_dauuid, dauuid, sizeof(dauuid))) uuidchanged = 1; else uuidchanged = 0; } else { if (ulp->ul_davalid) { /* * We had a UUID before, but now we don't? * Assume this is just a transient error, * issue a warning, and stick with the old UUID. */ uuidstring(ulp->ul_dauuid, buf); log(LOG_WARNING, "lost UUID for %s, was %s, keeping old UUID", fsb.f_mntonname, buf); uuidchanged = 0; } else uuidchanged = 0; } if (uuidchanged) { /* The UUID changed, so we'll drop any entry */ uuidstring(ulp->ul_dauuid, buf); if (davalid) uuidstring(dauuid, buf2); else strcpy(buf2, "------------------------------------"); log(LOG_WARNING, "UUID changed for %s, was %s now %s", ulp->ul_mntonname, buf, buf2); free(ulp); continue; } TAILQ_INSERT_TAIL(&ulhead, ulp, ul_list); } fclose(ulfile); } struct expidlist * find_export_id(struct uuidlist *ulp, u_int32_t id) { struct expidlist *xid; LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { if (xid->xid_id == id) break; } return (xid); } struct expidlist * get_export_id(struct uuidlist *ulp, char *path) { struct expidlist *xid; u_int32_t maxid = 0; LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { if (!strcmp(xid->xid_path, path)) break; if (maxid < xid->xid_id) maxid = xid->xid_id; } if (xid) return (xid); /* add it */ xid = malloc(sizeof(*xid)); if (!xid) { log(LOG_ERR, "get_export_id: out of memory"); return (NULL); } bzero(xid, sizeof(*xid)); strcpy(xid->xid_path, path); xid->xid_id = maxid + 1; while (find_export_id(ulp, xid->xid_id)) { xid->xid_id++; if (xid->xid_id == maxid) { /* exhausted export id values! */ log(LOG_ERR, "export ID values exhausted for %s", ulp->ul_mntonname); free(xid); return (NULL); } } LIST_INSERT_HEAD(&ulp->ul_exportids, xid, xid_list); return (xid); } /* * subdir_check() * * Compares two pathname strings to see if one is a subdir of the other. * Returns: * 1 if second path is a subdir of the first path * 0 if the paths are the same * -1 if the paths have just a substring match * -2 if the paths do not match at all * -3 if second path could not be a subdir of the first path (due to length) */ int subdir_check(char *s1, char *s2) { int len1, len2, rv; len1 = strlen(s1); len2 = strlen(s2); if (len1 > len2) rv = -3; else if (strncmp(s1, s2, len1)) rv = -2; else if (len1 == len2) rv = 0; else if ((s2[len1] == '/') || (len1 == 1)) rv = 1; else rv = -1; log(LOG_DEBUG, "subdir_check: %s %s %d", s1, s2, rv); return rv; } #define LINESIZ 10240 char line[LINESIZ]; FILE *exp_file; /* * Get the export list */ void get_exportlist(void) { struct expfs *xf, *xf2, *xf3; struct grouplist *grp, *tgrp, *pgrp; struct expdir *xd, *xd2, *xd3; struct dirlist *dirhead, *dirl, *dirl2; struct dirlist *reghead, *reg; struct statfs fsb, *fsp, *firstfsp; struct hostent *hpe; struct xucred anon; char *cp, *endcp, *word, *hst, *usr, *dom, savedc, *savedcp, *s; int len, dlen, hostcount, badhostcount, exflags, got_nondir, num, i, netgrp, cmp; struct nfs_export_args nxa; u_char uuid[16]; struct uuidlist *ulp; char buf[64]; int error; int opt_flags; /* * First, get rid of the old list */ log(LOG_DEBUG, "get_exportlist: freeing old exports..."); while ((xf = TAILQ_FIRST(&xfshead))) { TAILQ_REMOVE(&xfshead, xf, xf_next); free_expfs(xf); } TAILQ_INIT(&xfshead); log(LOG_DEBUG, "get_exportlist: freeing old groups..."); grp = grphead; while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } grphead = NULL; get_hostnames(our_hostnames); num = getmntinfo(&firstfsp, MNT_NOWAIT); /* * Unregistration is slow due to running slp_reg. We * do it *before* removing exports from kernel. The point is to * minimize the amount of time a legit export vanished from kernel * so active clients don't receive fatal errors. */ for (i = 0, fsp = firstfsp; i < num; i++, fsp++) register_export(fsp->f_mntonname, our_hostnames, DELETE_URL); /* * Delete exports that are in the kernel for all local file systems. */ nxa.nxa_flags = NXA_DELETE_ALL; nxa.nxa_expid = 0; nxa.nxa_exppath = NULL; nxa.nxa_netcount = 0; nxa.nxa_nets = NULL; nxa.nxa_fsid = 0; nxa.nxa_fspath = NULL; error = nfssvc(NFSSVC_EXPORT, &nxa); if (error && (errno != ENOENT)) log(LOG_ERR, "Can't delete all exports, %d", errno); uuidlist_clearexport(); if ((exp_file = fopen(exportsfilepath, "r")) != NULL) { source = EXPORT_FROM_FILE; } else { if (source == EXPORT_FROM_FILE) { log(LOG_ERR, "Can't open %s", exportsfilepath); exit(2); } ni_exports_open(); source = EXPORT_FROM_NETINFO; } /* * Read in the exports and build the list, calling nfssvc(NFSSVC_EXPORT) * as we go along to push the export rules into the kernel. */ dirhead = NULL; reghead = NULL; while (get_line(source)) { /* * Create new exports list entry */ log(LOG_DEBUG, "---> Got line: %s", line); /* * Set defaults. */ hostcount = badhostcount = 0; anon = def_anon; exflags = 0; opt_flags = 0; got_nondir = 0; xf = NULL; ulp = NULL; /* grab a group */ pgrp = NULL; tgrp = grp = get_grp(); if (!tgrp) { log(LOG_ERR, "can't allocate initial group for export"); getexp_err(xf, tgrp); goto nextline; } /* * process all the fields in this line * (we loop until nextfield finds nothing) */ cp = line; nextfield(&cp, &endcp); if (*cp == '#') goto nextline; while (endcp > cp) { log(LOG_DEBUG, "got field: %.*s", endcp-cp, cp); if (*cp == '-') { /* * looks like we have some options */ if (xf == NULL) { log(LOG_ERR, "got options with no exported directory: %s", cp); getexp_err(xf, tgrp); goto nextline; } log(LOG_DEBUG, "processing option: %.*s", endcp-cp, cp); got_nondir = 1; savedcp = cp; if (do_opt(&cp, &endcp, grp, &hostcount, &opt_flags, &exflags, &anon)) { log(LOG_ERR, "error processing options: %s", savedcp); getexp_err(xf, tgrp); goto nextline; } } else if ((*cp == '/') || ((*cp == '\'' || *cp == '"') && (*(cp+1) == '/'))) { /* * looks like we have a pathname */ log(LOG_DEBUG, "processing pathname: %.*s", endcp-cp, cp); word = clean_white_space(cp); log(LOG_DEBUG, " cleaned pathname: %s", word); if (word == NULL) { log(LOG_ERR, "error processing pathname (out of memory)"); getexp_err(xf, tgrp); goto nextline; } if (strlen(word) > RPCMNT_NAMELEN) { log(LOG_ERR, "pathname too long (%d > %d): %s", strlen(word), RPCMNT_NAMELEN, word); getexp_err(xf, tgrp); free(word); goto nextline; } if (!check_dirpath(word)) { log(LOG_ERR, "path contains symlinks: %s", word); getexp_err(xf, tgrp); free(word); goto nextline; } if (statfs(word, &fsb) < 0) { log(LOG_ERR, "statfs failed (%s) for path: %s", strerror(errno), word); getexp_err(xf, tgrp); free(word); goto nextline; } if (got_nondir) { log(LOG_ERR, "Directories must be first: %s", word); getexp_err(xf, tgrp); free(word); goto nextline; } /* get UUID for volume */ ulp = get_uuid(&fsb, uuid); if (!ulp) { log(LOG_ERR, "couldn't get UUID for volume: %s", fsb.f_mntonname); getexp_err(xf, tgrp); free(word); goto nextline; } if (xf) { if (bcmp(xf->xf_uuid, uuid, sizeof(uuid))) { log(LOG_ERR, "UUID mismatch for: %s\ndirectories must be on same volume", word); getexp_err(xf, tgrp); free(word); goto nextline; } } else { /* * See if this directory is already * in the export list. */ xf = ex_search(uuid); if (xf == NULL) { xf = get_expfs(); if (xf) xf->xf_fsdir = malloc(strlen(fsb.f_mntonname) + 1); if (!xf || !xf->xf_fsdir) { log(LOG_ERR, "Can't allocate memory to export volume: %s", fsb.f_mntonname); getexp_err(xf, tgrp); free(word); goto nextline; } bcopy(uuid, xf->xf_uuid, sizeof(uuid)); strcpy(xf->xf_fsdir, fsb.f_mntonname); log(LOG_DEBUG, "New expfs uuid=%s", uuidstring(uuid, buf)); } else { log(LOG_DEBUG, "Found expfs uuid=%s", uuidstring(uuid, buf)); } } /* * Add path to this line's directory list */ error = add_dir(&dirhead, word); if (error == EEXIST) { log(LOG_WARNING, "duplicate directory: %s", word); free(word); } else if (error == ENOMEM) { log(LOG_ERR, "Can't allocate memory to add directory: %s", word); getexp_err(xf, tgrp); free(word); goto nextline; } } else { savedc = *endcp; *endcp = '\0'; log(LOG_DEBUG, "got host/netgroup: %s", cp); got_nondir = 1; if (xf == NULL) { log(LOG_ERR, "got host/group with no directory?: %s", cp); getexp_err(xf, tgrp); goto nextline; } /* * Get the host or netgroup. */ setnetgrent(cp); netgrp = getnetgrent(&hst, &usr, &dom); do { if (hostcount && (grp->gr_type != GT_NULL)) { grp->gr_next = get_grp(); if (!grp->gr_next) { log(LOG_ERR, "can't allocate next group for export"); getexp_err(xf, tgrp); goto nextline; } pgrp = grp; grp = grp->gr_next; } if (netgrp) { if (get_host_addresses(hst, grp)) { log(LOG_ERR, "couldn't get address for netgroup:host - %s:%s", cp, hst); badhostcount++; } else { log(LOG_DEBUG, "got netgroup:host: %s:%s", cp, hst); hostcount++; } } else if (get_host_addresses(cp, grp)) { log(LOG_ERR, "couldn't get address for host: %s", cp); badhostcount++; } else { log(LOG_DEBUG, "got host: %s", cp); hostcount++; } } while (netgrp && getnetgrent(&hst, &usr, &dom)); endnetgrent(); *endcp = savedc; } cp = endcp; nextfield(&cp, &endcp); } /* * Done processing exports line fields. */ if (!(opt_flags & (OP_MAPROOT|OP_MAPALL|OP_KERB))) { /* If no mapping option specified, map root by default */ exflags |= NX_MAPROOT; opt_flags |= OP_MAPROOT; } if (check_options(opt_flags)) { log(LOG_ERR, "bad export options"); getexp_err(xf, tgrp); goto nextline; } if (!hostcount) { if (badhostcount) { log(LOG_ERR, "no valid hosts found for export"); getexp_err(xf, tgrp); goto nextline; } /* add a default group and make the grp list NULL */ grp->gr_type = GT_HOST; log(LOG_DEBUG, "Adding a default entry"); hpe = malloc(sizeof(struct hostent)); if (hpe) hpe->h_name = malloc(sizeof(DEFAULTHOSTNAME)); if (!hpe || !hpe->h_name) { if (hpe) free(hpe); log(LOG_ERR, "Can't allocate memory for default export entry %s", xf->xf_fsdir); getexp_err(xf, tgrp); goto nextline; } strcpy(hpe->h_name, DEFAULTHOSTNAME); hpe->h_addrtype = AF_INET; hpe->h_length = sizeof (u_long); hpe->h_addr_list = (char **)NULL; grp->gr_u.gt_hostent = hpe; } else if ((opt_flags & OP_NET) && tgrp->gr_next) { /* * Don't allow a network export coincide with a list of * host(s) on the same line. */ log(LOG_ERR, "can't specify both network and hosts on same line"); getexp_err(xf, tgrp); goto nextline; } else if (badhostcount && pgrp && (grp->gr_type == GT_NULL)) { /* * We ended with a bad host, so the final grp wasn't used. * Remove that unused grp from the end of the list. */ pgrp->gr_next = NULL; free_grp(grp); grp = pgrp; } /* * Walk the dirlist. * Verify the next dir is (or can be) an exported directory. * Check subsequent dirs to see if they are mount subdirs of that dir. */ dirl = dirhead; while (dirl) { /* check for existing/conflicting export */ log(LOG_DEBUG, "dir: %s", dirl->dl_dir); /* * Check for nesting conflicts with any existing entries. */ TAILQ_FOREACH(xd2, &xf->xf_dirl, xd_next) { if ((subdir_check(xd2->xd_dir, dirl->dl_dir) == 1) || (subdir_check(dirl->dl_dir, xd2->xd_dir) == 1)) { log(LOG_ERR, "%s conflicts with existing export %s", dirl->dl_dir, xd2->xd_dir); getexp_err(xf, tgrp); goto nextline; } } s = dirl->dl_dir; len = strlen(s); xd3 = NULL; cmp = 1; TAILQ_FOREACH(xd2, &xf->xf_dirl, xd_next) { dlen = strlen(xd2->xd_dir); cmp = strncmp(dirl->dl_dir, xd2->xd_dir, dlen); log(LOG_DEBUG, " %s compare %s %d", dirl->dl_dir, xd2->xd_dir, cmp); if (!cmp) { if (len == dlen) /* found an exact match */ break; /* dirl was actually longer than xd2 */ cmp = 1; } if (cmp > 0) break; xd3 = xd2; } if (cmp != 0) { xd = get_expdir(); if (xd) xd->xd_dir = strdup(dirl->dl_dir); if (!xd || !xd->xd_dir) { if (xd) free_expdir(xd); log(LOG_ERR, "can't allocate memory for export %s", dirl->dl_dir); getexp_err(xf, tgrp); goto nextline; } log(LOG_DEBUG, " %s new xd", xd->xd_dir); } else { xd = xd2; log(LOG_DEBUG, " %s xd is %s", dirl->dl_dir, xd->xd_dir); if (!hostcount && (xd->xd_flags & OP_DEFEXP)) { log(LOG_ERR, "%s already has a default export", s); getexp_err(xf, tgrp); goto nextline; } } /* * Send list of hosts to do_export for pushing the exports into * the kernel. */ if (do_export(xf, tgrp, exflags, &anon, dirl->dl_dir, &fsb, ulp)) { log(LOG_ERR, "kernel export registration failed"); getexp_err(xf, tgrp); goto nextline; } /* Success. Update the data structures. */ log(LOG_DEBUG, "kernel export registered for %s", dirl->dl_dir); /* add options to this exported directory */ if (hang_options(xd, opt_flags, (hostcount ? tgrp : NULL))) { log(LOG_ERR, "export option conflict for %s", xd->xd_dir); // XXX what to do about already successful exports? getexp_err(xf, tgrp); goto nextline; } /* add mount subdirectories of this directory */ dirl2 = dirl->dl_next; while (dirl2) { if (subdir_check(dirl->dl_dir, dirl2->dl_dir) != 1) break; error = hang_options_mountdir(xd, dirl2->dl_dir, opt_flags, (hostcount ? tgrp : NULL)); if (error == EEXIST) { log(LOG_WARNING, "mount subdirectory export option conflict for %s", dirl2->dl_dir); } else if (error == ENOMEM) { log(LOG_WARNING, "unable to add mount subdirectory for %s, %s", xd->xd_dir, dirl2->dl_dir); } dirl2 = dirl2->dl_next; } dirl = dirl2; if (cmp) { /* add new expdir to xf */ if (xd3) { TAILQ_INSERT_AFTER(&xf->xf_dirl, xd3, xd, xd_next); } else { TAILQ_INSERT_HEAD(&xf->xf_dirl, xd, xd_next); } /* * Add exported directory path to reghead, * the list of paths we will register. */ s = strdup(xd->xd_dir); if (add_dir(®head, s)) free(s); } } if (hostcount) { grp->gr_next = grphead; grphead = tgrp; } else { free_grp(grp); } if ((xf->xf_flag & XF_LINKED) == 0) { /* Insert in the list in alphabetical order. */ xf3 = NULL; TAILQ_FOREACH(xf2, &xfshead, xf_next) { if (strcmp(xf->xf_fsdir, xf2->xf_fsdir) < 0) break; xf3 = xf2; } if (xf3) { TAILQ_INSERT_AFTER(&xfshead, xf3, xf, xf_next); } else { TAILQ_INSERT_HEAD(&xfshead, xf, xf_next); } xf->xf_flag |= XF_LINKED; } nextline: if (dirhead) { free_dirlist(dirhead); dirhead = NULL; } } /* * Registration is slow due to running slp_reg. We * do it *after* adding exports from kernel. The point is to * minimize the amount of time a legit export vanished from kernel * so active clients won't receive fatal errors. */ reg = reghead; while (reg) { register_export(reg->dl_dir, our_hostnames, ADD_URL); reg = reg->dl_next; } free_dirlist(reghead); reghead = NULL; if (source == EXPORT_FROM_NETINFO) ni_exports_close(); else fclose(exp_file); uuidlist_save(); } /* * Allocate an export list element */ struct expfs * get_expfs(void) { struct expfs *xf; xf = malloc(sizeof(*xf)); if (xf == NULL) return (NULL); memset(xf, 0, sizeof(*xf)); TAILQ_INIT(&xf->xf_dirl); return (xf); } struct expdir * get_expdir(void) { struct expdir *xd; xd = malloc(sizeof(*xd)); if (xd == NULL) return (NULL); memset(xd, 0, sizeof(*xd)); TAILQ_INIT(&xd->xd_mountdirs); return (xd); } /* * Allocate a group list element */ struct grouplist * get_grp(void) { struct grouplist *grp; grp = malloc(sizeof(*grp)); if (grp == NULL) return (NULL); memset(grp, 0, sizeof(*grp)); return (grp); } /* * Clean up upon an error in get_exportlist(). */ void getexp_err(struct expfs *xf, struct grouplist *grp) { struct grouplist *tgrp; /* XXX might be nice if we could report the file/line# here */ log(LOG_ERR, "Error processing exports list line: %s", line); if (xf && (xf->xf_flag & XF_LINKED) == 0) free_expfs(xf); while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } } /* * Search the export list for a matching fs. */ struct expfs * ex_search(u_char *uuid) { struct expfs *xf; TAILQ_FOREACH(xf, &xfshead, xf_next) { if (!bcmp(xf->xf_uuid, uuid, 16)) return (xf); } return (xf); } /* * add a directory to a dirlist (sorted) * * Note that the list is sorted to place subdirectories * after the entry for their matching parent directory. * This isn't strictly sorted because other directories may * have similar names with characters that sort lower than '/'. * For example: /export /export.test /export/subdir */ int add_dir(struct dirlist **dlpp, char *cp) { struct dirlist *newdl, *dl, *dl2, *dl3, *dlstop; int cplen, dlen, cmp; dlstop = NULL; dl2 = NULL; dl = *dlpp; cplen = strlen(cp); while (dl && (dl != dlstop)) { dlen = strlen(dl->dl_dir); cmp = strncmp(cp, dl->dl_dir, dlen); log(LOG_DEBUG, "add_dir: %s compare %s %d", cp, dl->dl_dir, cmp); if (cmp < 0) break; if (cmp == 0) { if (cplen == dlen) // duplicate return (EEXIST); if (cp[dlen] == '/') { /* * Find the next entry that isn't a * subdirectory of this directory so * we know when to stop looking for * the insertion point. */ log(LOG_DEBUG, "add_dir: %s compare %s %d subdir match", cp, dl->dl_dir, cmp); dlstop = dl->dl_next; while (dlstop && (subdir_check(dl->dl_dir, dlstop->dl_dir) == 1)) dlstop = dlstop->dl_next; } else { /* * The new dir should go after this directory and * its subdirectories. So, skip subdirs of this dir. */ log(LOG_DEBUG, "add_dir: %s compare %s %d partial match", cp, dl->dl_dir, cmp); dl3 = dl; dl2 = dl; dl = dl->dl_next; while (dl && (subdir_check(dl3->dl_dir, dl->dl_dir) == 1)) { dl2 = dl; dl = dl->dl_next; } continue; } } dl2 = dl; dl = dl->dl_next; } if (dl && (dl == dlstop)) log(LOG_DEBUG, "add_dir: %s stopped before %s", cp, dlstop->dl_dir); newdl = malloc(sizeof(*dl)); if (newdl == NULL) { log(LOG_ERR, "can't allocate memory to add dir %s", cp); return (ENOMEM); } newdl->dl_dir = cp; if (dl2 == NULL) { newdl->dl_next = *dlpp; *dlpp = newdl; } else { newdl->dl_next = dl; dl2->dl_next = newdl; } if (debug > 1) { dl = *dlpp; while (dl) { log(LOG_DEBUG, "DIRLIST: %s", dl->dl_dir); dl = dl->dl_next; } } return (0); } /* * free up all the elements in a dirlist */ void free_dirlist(struct dirlist *dl) { struct dirlist *dl2; while (dl) { dl2 = dl->dl_next; if (dl->dl_dir) free(dl->dl_dir); free(dl); dl = dl2; } } /* * hang export options for a list of groups off of an exported directory */ int hang_options(struct expdir *xd, int opt_flags, struct grouplist *grp) { struct host *ht; if (!grp) { /* default export */ if (xd->xd_flags & OP_DEFEXP) { /* exported directory already has default export! */ if ((xd->xd_flags & OP_EXOPTMASK) == opt_flags) { log(LOG_WARNING, "duplicate default export for %s", xd->xd_dir); return (0); } else { log(LOG_ERR, "multiple/conflicting default exports for %s", xd->xd_dir); return (EEXIST); } } xd->xd_flags = opt_flags | OP_DEFEXP; log(LOG_DEBUG, "hang_options: %s default 0x%x", xd->xd_dir, xd->xd_flags); return (0); } while (grp) { // XXX should we first check for an existing entry for this group? ht = get_host(); if (!ht) { log(LOG_ERR, "Can't allocate memory for host: %s", ((grp->gr_type == GT_NET) ? grp->gr_u.gt_net.nt_name : ((grp->gr_type == GT_HOST) ? grp->gr_u.gt_hostent->h_name : "???"))); return(ENOMEM); } ht->ht_flag = opt_flags; ht->ht_grp = grp; ht->ht_next = xd->xd_hosts; xd->xd_hosts = ht; log(LOG_DEBUG, "hang_options: %s %s 0x%x", xd->xd_dir, ((grp->gr_type == GT_NET) ? grp->gr_u.gt_net.nt_name : ((grp->gr_type == GT_HOST) ? grp->gr_u.gt_hostent->h_name : "???")), opt_flags); grp = grp->gr_next; } return (0); } /* * hang export options for mountable subdirectories of an exported directory */ int hang_options_mountdir(struct expdir *xd, char *dir, int opt_flags, struct grouplist *grp) { struct host *ht; struct expdir *mxd, *mxd2, *mxd3; int cmp; /* check for existing mountdir */ mxd = mxd3 = NULL; TAILQ_FOREACH(mxd2, &xd->xd_mountdirs, xd_next) { cmp = strcmp(dir, mxd2->xd_dir); if (!cmp) { /* found it */ mxd = mxd2; break; } else if (cmp < 0) { /* found where it needs to be inserted */ break; } mxd3 = mxd2; } if (!mxd) { mxd = get_expdir(); if (mxd) mxd->xd_dir = strdup(dir); if (!mxd || !mxd->xd_dir) { if (mxd) free_expdir(mxd); log(LOG_ERR, "can't allocate memory for mountable sub-directory; %s", dir); return (ENOMEM); } if (mxd3) { TAILQ_INSERT_AFTER(&xd->xd_mountdirs, mxd3, mxd, xd_next); } else { TAILQ_INSERT_HEAD(&xd->xd_mountdirs, mxd, xd_next); } } if (!grp) { /* default export */ if (mxd->xd_flags & OP_DEFEXP) { /* exported directory already has default export! */ if ((mxd->xd_flags & OP_EXOPTMASK) == opt_flags) { log(LOG_WARNING, "duplicate default export for %s", mxd->xd_dir); return (0); } else { log(LOG_ERR, "multiple/conflicting default exports for %s", mxd->xd_dir); return (EEXIST); } } mxd->xd_flags = opt_flags | OP_DEFEXP; log(LOG_DEBUG, "hang_options_mountdir: %s default 0x%x", mxd->xd_dir, mxd->xd_flags); return (0); } while (grp) { // XXX should we first check for an existing entry for this group? ht = get_host(); if (!ht) { log(LOG_ERR, "Can't allocate memory for host: %s", ((grp->gr_type == GT_NET) ? grp->gr_u.gt_net.nt_name : ((grp->gr_type == GT_HOST) ? grp->gr_u.gt_hostent->h_name : "???"))); return(ENOMEM); } ht->ht_flag = opt_flags; ht->ht_grp = grp; ht->ht_next = mxd->xd_hosts; mxd->xd_hosts = ht; log(LOG_DEBUG, "hang_options_mountdir: %s %s 0x%x", mxd->xd_dir, ((grp->gr_type == GT_NET) ? grp->gr_u.gt_net.nt_name : ((grp->gr_type == GT_HOST) ? grp->gr_u.gt_hostent->h_name : "???")), opt_flags); grp = grp->gr_next; } return (0); } /* * Search for an exported directory on an exported file system that * a given host can mount and return the export options. * * Search order: * an exact match on exported directory path * an exact match on exported directory mountdir path * a subdir match on exported directory mountdir path with ALLDIRS * a subdir match on exported directory path with ALLDIRS */ int expdir_search(struct expfs *xf, char *dirpath, u_long saddr, int *options) { struct expdir *xd, *mxd; struct host *hp; int cmp = -1, chkalldirs = 0; TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) { if ((cmp = subdir_check(xd->xd_dir, dirpath)) >= 0) break; } if (!xd) { log(LOG_DEBUG, "expdir_search: no matching export: %s", dirpath); return (0); } log(LOG_DEBUG, "expdir_search: %s -> %s", dirpath, xd->xd_dir); if (cmp == 0) { /* exact match on exported directory path */ check_xd_hosts: /* find options for this host */ hp = find_host(xd->xd_hosts, saddr); if (hp && (!chkalldirs || (hp->ht_flag & OP_ALLDIRS))) { log(LOG_DEBUG, "expdir_search: %s host %s", dirpath, (chkalldirs ? "alldirs" : "match")); *options = hp->ht_flag; } else if ((xd->xd_flags & OP_DEFEXP) && (!chkalldirs || (xd->xd_flags & OP_ALLDIRS))) { log(LOG_DEBUG, "expdir_search: %s defexp %s", dirpath, (chkalldirs ? "alldirs" : "match")); *options = xd->xd_flags; } else { // not exported to this host *options = 0; log(LOG_DEBUG, "expdir_search: %s NO match", dirpath); return (0); } return (1); } // search for a matching mountdir TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) { cmp = subdir_check(mxd->xd_dir, dirpath); if (cmp < 0) continue; log(LOG_DEBUG, "expdir_search: %s subdir path match %s", dirpath, mxd->xd_dir); chkalldirs = (cmp != 0); /* found a match on a mountdir */ hp = find_host(mxd->xd_hosts, saddr); if (hp && (!chkalldirs || (hp->ht_flag & OP_ALLDIRS))) { log(LOG_DEBUG, "expdir_search: %s -> %s subdir host %s", dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match")); *options = hp->ht_flag; return (1); } else if ((mxd->xd_flags & OP_DEFEXP) && (!chkalldirs || (mxd->xd_flags & OP_ALLDIRS))) { log(LOG_DEBUG, "expdir_search: %s -> %s subdir defexp %s", dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match")); *options = mxd->xd_flags; return (1); } /* not exported to this host */ } log(LOG_DEBUG, "expdir_search: %s NO match, check alldirs", dirpath); chkalldirs = 1; goto check_xd_hosts; } /* * search a host list for a match for the given address */ struct host * find_host(struct host *hp, u_long saddr) { struct grouplist *grp; u_long **addrp; while (hp) { grp = hp->ht_grp; switch (grp->gr_type) { case GT_HOST: addrp = (u_long **) grp->gr_u.gt_hostent->h_addr_list; while (*addrp) { if (**addrp == saddr) return (hp); addrp++; } break; case GT_NET: if ((saddr & grp->gr_u.gt_net.nt_mask) == grp->gr_u.gt_net.nt_net) return (hp); break; } hp = hp->ht_next; } return (NULL); } /* * Parse the option string and update fields. * Option arguments may either be -