#include <sys/param.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/ucred.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <oncrpc/rpc.h>
#include <oncrpc/pmap_clnt.h>
#include <oncrpc/pmap_prot.h>
#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfs/nfs.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int bindresvport_sa(int sd, struct sockaddr *sa);
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#ifdef __LP64__
typedef int xdr_long_t;
#else
typedef long xdr_long_t;
#endif
#include "pathnames.h"
#include "common.h"
struct expidlist {
LIST_ENTRY(expidlist) xid_list;
char xid_path[MAXPATHLEN];
u_int32_t xid_id;
};
struct uuidlist {
TAILQ_ENTRY(uuidlist) ul_list;
char ul_mntfromname[MAXPATHLEN];
char ul_mntonname[MAXPATHLEN];
u_char ul_uuid[16];
u_char ul_dauuid[16];
char ul_davalid;
char ul_exported;
u_int32_t ul_fsid;
LIST_HEAD(expidhead,expidlist) ul_exportids;
};
TAILQ_HEAD(,uuidlist) ulhead;
#define UL_CHECK_MNTFROM 0x1
#define UL_CHECK_MNTON 0x2
#define UL_CHECK_ALL 0x3
#define UUID2FSID(U) \
(*((u_int32_t*)(U)) ^ *(((u_int32_t*)(U))+1) ^ \
*(((u_int32_t*)(U))+2) ^ *(((u_int32_t*)(U))+3))
struct mountlist {
struct mountlist *ml_next;
char *ml_host;
char *ml_dir;
};
struct namelist {
TAILQ_ENTRY(namelist) nl_next;
char *nl_name;
};
TAILQ_HEAD(namelisttqh, namelist);
struct dirlist {
struct dirlist *dl_next;
char *dl_dir;
};
struct errlist {
LIST_ENTRY(errlist) el_next;
uint32_t el_linenum;
char *el_msg;
};
TAILQ_HEAD(expfstqh, expfs);
TAILQ_HEAD(expdirtqh, expdir);
TAILQ_HEAD(hosttqh, host);
struct expfs {
TAILQ_ENTRY(expfs) xf_next;
struct expdirtqh xf_dirl;
int xf_flag;
u_char xf_uuid[16];
u_int32_t xf_fsid;
char *xf_fsdir;
};
#define XF_LINKED 0x1
struct expdir {
TAILQ_ENTRY(expdir) xd_next;
struct hosttqh xd_hosts;
struct expdirtqh xd_mountdirs;
int xd_iflags;
int xd_flags;
struct xucred xd_cred;
struct nfs_sec xd_sec;
int xd_oflags;
struct xucred xd_ocred;
struct nfs_sec xd_osec;
struct nfs_sec xd_ssec;
char *xd_dir;
struct expidlist *xd_xid;
};
struct hostinfo {
char *h_name;
struct addrinfo *h_ailist;
};
struct netmsk {
char *nt_name;
sa_family_t nt_family;
union {
struct {
in_addr_t net;
in_addr_t mask;
} ipv4;
struct {
struct in6_addr net;
struct in6_addr mask;
} ipv6;
} nt_u;
};
#define nt_net nt_u.ipv4.net
#define nt_mask nt_u.ipv4.mask
#define nt_net6 nt_u.ipv6.net
#define nt_mask6 nt_u.ipv6.mask
union grouptypes {
struct hostinfo gt_hostinfo;
struct netmsk gt_net;
char * gt_netgroup;
};
struct grouplist {
struct grouplist *gr_cache;
struct grouplist *gr_next;
int gr_refcnt;
int16_t gr_type;
int16_t gr_flags;
union grouptypes gr_u;
};
#define GT_NULL 0x0
#define GT_NETGROUP 0x1
#define GT_NET 0x2
#define GT_HOST 0x3
#define GF_SHOW 0x1
struct host {
TAILQ_ENTRY(host) ht_next;
int ht_flags;
struct xucred ht_cred;
struct grouplist *ht_grp;
struct nfs_sec ht_sec;
};
struct fhreturn {
int fhr_flags;
int fhr_vers;
struct nfs_sec fhr_sec;
fhandle_t fhr_fh;
};
int add_name(struct namelisttqh *, char *);
void free_namelist(struct namelisttqh *);
int add_dir(struct dirlist **, char *);
char * add_expdir(struct expdir **, char *, int);
int add_grp(struct grouplist **, struct grouplist *);
int add_host(struct hosttqh *, struct host *);
void add_mlist(char *, char *);
int addrinfo_cmp(struct addrinfo *, struct addrinfo *);
int check_dirpath(char *);
int check_options(int);
void clear_export_error(uint32_t);
int cmp_secflavs(struct nfs_sec *, struct nfs_sec *);
void merge_secflavs(struct nfs_sec *, struct nfs_sec *);
void del_mlist(char *, char *);
int expdir_search(struct expfs *, char *, struct sockaddr *, int *, struct nfs_sec *);
int do_export(int, struct expfs *, struct expdir *, struct grouplist *, int,
struct xucred *, struct nfs_sec *);
int do_opt(char **, char **, struct grouplist *, int *,
int *, int *, struct xucred *, struct nfs_sec *, char *, u_char *);
struct expfs *ex_search(u_char *);
void export_error(int, const char *, ...);
void export_error_cleanup(struct expfs *);
struct host *find_group_address_match_in_host_list(struct hosttqh *, struct grouplist *);
struct host *find_host(struct hosttqh *, struct sockaddr *);
void free_dirlist(struct dirlist *dl);
void free_expdir(struct expdir *);
void free_expfs(struct expfs *);
void free_grp(struct grouplist *);
void free_hosts(struct hosttqh *);
void free_host(struct host *);
struct expdir *get_expdir(void);
struct expfs *get_expfs(void);
int get_host_addresses(char *, struct grouplist *);
struct host *get_host(void);
int get_export_entry(void);
void get_mountlist(void);
int get_net(char *, struct netmsk *, int);
int get_sec_flavors(char *flavorlist, struct nfs_sec *);
struct grouplist *get_grp(struct grouplist *);
const char *grp_addr(struct grouplist *);
char * grp_name(struct grouplist *);
int hang_options_setup(struct expdir *, int, struct xucred *, struct grouplist *, struct nfs_sec *, int *);
void hang_options_finalize(struct expdir *);
void hang_options_cleanup(struct expdir *);
int hang_options_mountdir(struct expdir *, char *, int, struct grouplist *, struct nfs_sec *);
void mntsrv(struct svc_req *, SVCXPRT *);
void nextfield(char **, char **);
int parsecred(char *, struct xucred *);
int put_exlist(struct expdir *, XDR *);
int subdir_check(char *, char *);
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 *);
void dump_exports(void);
void snprintf_cred(char *buf, int, struct xucred *cr);
pthread_mutex_t export_mutex;
struct expfstqh xfshead;
struct dirlist *xpaths;
int xpaths_complete = 1;
struct mountlist *mlhead;
struct grouplist *grpcache;
struct xucred def_anon = {
XUCRED_VERSION,
(uid_t) -2,
1,
{ (gid_t) -2 },
};
LIST_HEAD(,errlist) xerrs;
int export_errors, hostnamecount, hostnamegoodcount, missingexportcount;
SVCXPRT *udptransp, *tcptransp;
SVCXPRT *udp6transp, *tcp6transp;
int mounttcpsock, mountudpsock;
int mounttcp6sock, mountudp6sock;
#define OP_MAPROOT 0x00000001
#define OP_MAPALL 0x00000002
#define OP_SECFLAV 0x00000004
#define OP_MASK 0x00000008
#define OP_NET 0x00000010
#define OP_MANGLEDNAMES 0x00000020
#define OP_ALLDIRS 0x00000040
#define OP_READONLY 0x00000080
#define OP_32BITCLIENTS 0x00000100
#define OP_FSPATH 0x00000200
#define OP_FSUUID 0x00000400
#define OP_OFFLINE 0x00000800
#define OP_ONLINE 0x04000000
#define OP_SHOW 0x08000000
#define OP_MISSING 0x10000000
#define OP_DEFEXP 0x20000000
#define OP_ADD 0x40000000
#define OP_DEL 0x80000000
#define OP_EXOPTMASK 0x100009E3
#define OP_EXOPTS(X) ((X) & OP_EXOPTMASK)
#define RECHECKEXPORTS_TIMEOUT 600
#define RECHECKEXPORTS_DELAYED_STARTUP_TIMEOUT 120
#define RECHECKEXPORTS_DELAYED_STARTUP_INTERVAL 5
void *
mountd_thread(__unused void *arg)
{
set_thread_sigmask();
svc_run();
log(LOG_ERR, "mountd died");
exit(1);
}
void
mountd_init(void)
{
int error;
TAILQ_INIT(&xfshead);
xpaths = NULL;
mlhead = NULL;
grpcache = NULL;
TAILQ_INIT(&ulhead);
LIST_INIT(&xerrs);
error = pthread_mutex_init(&export_mutex, NULL);
if (error) {
log(LOG_ERR, "export mutex init failed: %s (%d)", strerror(error), error);
exit(1);
}
uuidlist_restore();
}
void
mountd(void)
{
struct sockaddr_storage saddr;
struct sockaddr_in *sin = (struct sockaddr_in*)&saddr;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&saddr;
socklen_t socklen;
struct nfs_export_args nxa;
int error, on = 1, init_retry, svcregcnt;
pthread_t thd;
time_t init_start;
mountd_init();
check_for_mount_changes();
unlink(_PATH_RMOUNTLIST);
bzero(&nxa, sizeof(nxa));
nxa.nxa_flags = NXA_DELETE_ALL;
error = nfssvc(NFSSVC_EXPORT, &nxa);
if (error && (errno != ENOENT))
log(LOG_ERR, "Can't delete all exports: %s (%d)", strerror(errno), errno);
init_start = time(NULL);
init_retry = 0;
while (1) {
DEBUG(1, "Getting export list.");
get_exportlist();
if (!hostnamecount || hostnamegoodcount) {
if (init_retry)
log(LOG_WARNING, "host name resolution seems to be working now... continuing initialization");
break;
}
if (!init_retry) {
log(LOG_WARNING, "host name resolution seems to be having problems... delaying initialization");
init_retry = 1;
} else if (time(NULL) > (init_start + RECHECKEXPORTS_DELAYED_STARTUP_TIMEOUT)) {
log(LOG_WARNING, "giving up on host name resolution... continuing initialization");
break;
}
sleep(RECHECKEXPORTS_DELAYED_STARTUP_INTERVAL);
}
DEBUG(1, "Getting mount list.");
get_mountlist();
DEBUG(1, "Here we go.");
if (!config.udp && !config.tcp) {
log(LOG_WARNING, "No network transport(s) configured. mountd thread not starting.");
return;
}
mountudpsock = mounttcpsock = -1;
mountudp6sock = mounttcp6sock = -1;
if (config.udp) {
if ((mountudpsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
log(LOG_ERR, "can't create MOUNT/UDP IPv4 socket: %s (%d)", strerror(errno), errno);
if (mountudpsock >= 0) {
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(config.mount_port);
sin->sin_len = sizeof(*sin);
if (bindresvport_sa(mountudpsock, (struct sockaddr*)sin) < 0) {
sleep(6);
if (bindresvport_sa(mountudpsock, (struct sockaddr*)sin) < 0) {
log(LOG_ERR, "can't bind MOUNT/UDP IPv4 addr: %s (%d)", strerror(errno), errno);
close(mountudpsock);
mountudpsock = -1;
}
}
}
if (mountudpsock >= 0) {
socklen = sizeof(*sin);
if (getsockname(mountudpsock, (struct sockaddr*)sin, &socklen)) {
log(LOG_ERR, "can't getsockname on MOUNT/UDP IPv4 socket: %s (%d)", strerror(errno), errno);
close(mountudpsock);
mountudpsock = -1;
} else {
mountudpport = ntohs(sin->sin_port);
}
}
if ((mountudpsock >= 0) && ((udptransp = svcudp_create(mountudpsock)) == NULL)) {
log(LOG_ERR, "Can't create MOUNT/UDP IPv4 service");
close(mountudpsock);
mountudpsock = -1;
mountudpport = 0;
}
if (mountudpsock >= 0) {
svcregcnt = 0;
if (!svc_register(udptransp, RPCPROG_MNT, 1, mntsrv, 0))
log(LOG_ERR, "Can't register IPv4 MOUNT/UDP v1 service");
else
svcregcnt++;
if (!svc_register(udptransp, RPCPROG_MNT, 3, mntsrv, 0))
log(LOG_ERR, "Can't register IPv4 MOUNT/UDP v3 service");
else
svcregcnt++;
if (!svcregcnt) {
svc_destroy(udptransp);
close(mountudpsock);
mountudpsock = -1;
mountudpport = 0;
}
}
if ((mountudp6sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
log(LOG_ERR, "can't create MOUNT/UDP IPv6 socket: %s (%d)", strerror(errno), errno);
if (mountudp6sock >= 0) {
setsockopt(mountudp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
sin6->sin6_port = htons(config.mount_port);
sin6->sin6_len = sizeof(*sin6);
if (bindresvport_sa(mountudp6sock, (struct sockaddr*)sin6) < 0) {
sleep(6);
if (bindresvport_sa(mountudp6sock, (struct sockaddr*)sin6) < 0) {
log(LOG_ERR, "can't bind MOUNT/UDP IPv6 addr: %s (%d)", strerror(errno), errno);
close(mountudp6sock);
mountudp6sock = -1;
}
}
}
if (mountudp6sock >= 0) {
socklen = sizeof(*sin6);
if (getsockname(mountudp6sock, (struct sockaddr*)sin6, &socklen)) {
log(LOG_ERR, "can't getsockname on MOUNT/UDP IPv6 socket: %s (%d)", strerror(errno), errno);
close(mountudp6sock);
mountudp6sock = -1;
} else {
mountudp6port = ntohs(sin6->sin6_port);
}
}
if ((mountudp6sock >= 0) && ((udp6transp = svcudp_create(mountudp6sock)) == NULL)) {
log(LOG_ERR, "Can't create MOUNT/UDP IPv6 service");
close(mountudp6sock);
mountudp6sock = -1;
mountudp6port = 0;
}
if (mountudp6sock >= 0) {
svcregcnt = 0;
if (!svc_register(udp6transp, RPCPROG_MNT, 1, mntsrv, 0))
log(LOG_ERR, "Can't register IPv6 MOUNT/UDP v1 service");
else
svcregcnt++;
if (!svc_register(udp6transp, RPCPROG_MNT, 3, mntsrv, 0))
log(LOG_ERR, "Can't register IPv6 MOUNT/UDP v3 service");
else
svcregcnt++;
if (!svcregcnt) {
svc_destroy(udp6transp);
close(mountudp6sock);
mountudp6sock = -1;
mountudp6port = 0;
}
}
}
if (config.tcp) {
if ((mounttcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
log(LOG_ERR, "can't create MOUNT/TCP IPv4 socket: %s (%d)", strerror(errno), errno);
if (mounttcpsock >= 0) {
if (setsockopt(mounttcpsock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
log(LOG_WARNING, "setsockopt MOUNT/TCP IPv4 SO_REUSEADDR: %s (%d)", strerror(errno), errno);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(config.mount_port);
sin->sin_len = sizeof(*sin);
if (bindresvport_sa(mounttcpsock, (struct sockaddr*)sin) < 0) {
log(LOG_ERR, "can't bind MOUNT/TCP IPv4 addr: %s (%d)", strerror(errno), errno);
close(mounttcpsock);
mounttcpsock = -1;
}
}
if ((mounttcpsock >= 0) && (listen(mounttcpsock, 128) < 0)) {
log(LOG_ERR, "MOUNT IPv4 listen failed: %s (%d)", strerror(errno), errno);
close(mounttcpsock);
mounttcpsock = -1;
}
if (mounttcpsock >= 0) {
socklen = sizeof(*sin);
if (getsockname(mounttcpsock, (struct sockaddr*)sin, &socklen)) {
log(LOG_ERR, "can't getsockname on MOUNT/TCP IPv4 socket: %s (%d)", strerror(errno), errno);
close(mounttcpsock);
mounttcpsock = -1;
} else {
mounttcpport = ntohs(sin->sin_port);
}
}
if ((mounttcpsock >= 0) && ((tcptransp = svctcp_create(mounttcpsock, 0, 0)) == NULL)) {
log(LOG_ERR, "Can't create MOUNT/TCP IPv4 service");
close(mounttcpsock);
mounttcpsock = -1;
mounttcpport = 0;
}
if (mounttcpsock >= 0) {
svcregcnt = 0;
if (!svc_register(tcptransp, RPCPROG_MNT, 1, mntsrv, 0))
log(LOG_ERR, "Can't register IPv4 MOUNT/TCP v1 service");
else
svcregcnt++;
if (!svc_register(tcptransp, RPCPROG_MNT, 3, mntsrv, 0))
log(LOG_ERR, "Can't register IPv4 MOUNT/TCP v3 service");
else
svcregcnt++;
if (!svcregcnt) {
svc_destroy(tcptransp);
close(mounttcpsock);
mounttcpsock = -1;
mounttcpport = 0;
}
}
if ((mounttcp6sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
log(LOG_ERR, "can't create MOUNT/TCP IPv6 socket: %s (%d)", strerror(errno), errno);
if (mounttcp6sock >= 0) {
if (setsockopt(mounttcp6sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
log(LOG_WARNING, "setsockopt MOUNT/TCP IPv6 SO_REUSEADDR: %s (%d)", strerror(errno), errno);
setsockopt(mounttcp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
sin6->sin6_port = htons(config.mount_port);
sin6->sin6_len = sizeof(*sin6);
if (bindresvport_sa(mounttcp6sock, (struct sockaddr*)sin6) < 0) {
log(LOG_ERR, "can't bind MOUNT/TCP IPv6 addr: %s (%d)", strerror(errno), errno);
close(mounttcp6sock);
mounttcp6sock = -1;
}
}
if ((mounttcp6sock >= 0) && (listen(mounttcp6sock, 128) < 0)) {
log(LOG_ERR, "MOUNT IPv6 listen failed: %s (%d)", strerror(errno), errno);
close(mounttcp6sock);
mounttcp6sock = -1;
}
if (mounttcp6sock >= 0) {
socklen = sizeof(*sin6);
if (getsockname(mounttcp6sock, (struct sockaddr*)sin6, &socklen)) {
log(LOG_ERR, "can't getsockname on MOUNT/TCP IPv6 socket: %s (%d)", strerror(errno), errno);
close(mounttcp6sock);
mounttcp6sock = -1;
} else {
mounttcp6port = ntohs(sin6->sin6_port);
}
}
if ((mounttcp6sock >= 0) && ((tcp6transp = svctcp_create(mounttcp6sock, 0, 0)) == NULL)) {
log(LOG_ERR, "Can't create MOUNT/TCP IPv6 service");
close(mounttcp6sock);
mounttcp6sock = -1;
mounttcp6port = 0;
}
if (mounttcp6sock >= 0) {
svcregcnt = 0;
if (!svc_register(tcp6transp, RPCPROG_MNT, 1, mntsrv, 0))
log(LOG_ERR, "Can't register IPv6 MOUNT/TCP v1 service");
else
svcregcnt++;
if (!svc_register(tcp6transp, RPCPROG_MNT, 3, mntsrv, 0))
log(LOG_ERR, "Can't register IPv6 MOUNT/TCP v3 service");
else
svcregcnt++;
if (!svcregcnt) {
svc_destroy(tcp6transp);
close(mounttcp6sock);
mounttcp6sock = -1;
mounttcp6port = 0;
}
}
}
if ((mountudp6sock < 0) && (mounttcp6sock < 0))
log(LOG_WARNING, "Can't create MOUNT IPv6 sockets");
if ((mountudpsock < 0) && (mounttcpsock < 0))
log(LOG_WARNING, "Can't create MOUNT IPv4 sockets");
if ((mountudp6sock < 0) && (mounttcp6sock < 0) &&
(mountudpsock < 0) && (mounttcpsock < 0)) {
log(LOG_ERR, "Can't create any MOUNT sockets!");
exit(1);
}
error = pthread_create(&thd, &pattr, mountd_thread, NULL);
if (error) {
log(LOG_ERR, "mountd pthread_create: %s (%d)", strerror(error), error);
exit(1);
}
}
void
lock_exports(void)
{
int error;
if (checkexports)
return;
error = pthread_mutex_lock(&export_mutex);
if (error)
log(LOG_ERR, "export mutex lock failed: %s (%d)", strerror(error), error);
}
void
unlock_exports(void)
{
int error;
if (checkexports)
return;
error = pthread_mutex_unlock(&export_mutex);
if (error)
log(LOG_ERR, "export mutex unlock failed: %s (%d)", strerror(error), error);
}
void
mntsrv(struct svc_req *rqstp, SVCXPRT *transp)
{
struct expfs *xf;
struct fhreturn fhr;
struct stat stb;
struct statfs fsb;
struct nfs_sec secflavs;
struct sockaddr *sa;
u_short sport;
char rpcpath[RPCMNT_PATHLEN + 1], dirpath[MAXPATHLEN];
char addrbuf[2*INET6_ADDRSTRLEN], hostbuf[NI_MAXHOST];
int bad = ENOENT, options;
u_char uuid[16];
sa = svc_getcaller_sa(transp);
if (sa->sa_family == AF_INET)
sport = ntohs(((struct sockaddr_in*)sa)->sin_port);
else if (sa->sa_family == AF_INET6)
sport = ntohs(((struct sockaddr_in6*)sa)->sin6_port);
else
sport = 0;
strlcpy(hostbuf, "unknown_host", sizeof(hostbuf));
strlcpy(addrbuf, "unknown_host", sizeof(addrbuf));
switch (rqstp->rq_proc) {
case NULLPROC:
if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
log(LOG_ERR, "Can't send NULL MOUNT reply");
return;
case RPCMNT_MOUNT:
if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) {
svcerr_weakauth(transp);
return;
}
if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) {
svcerr_decode(transp);
return;
}
lock_exports();
if (realpath(rpcpath, dirpath) == 0 ||
stat(dirpath, &stb) < 0 ||
(!S_ISDIR(stb.st_mode) &&
(!config.mount_regular_files || !S_ISREG(stb.st_mode))) ||
statfs(dirpath, &fsb) < 0) {
unlock_exports();
chdir("/");
DEBUG(1, "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");
if (config.verbose) {
getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath);
}
return;
}
if (!get_uuid_from_list(&fsb, uuid, UL_CHECK_ALL)) {
unlock_exports();
DEBUG(1, "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");
if (config.verbose) {
getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath);
}
return;
}
xf = ex_search(uuid);
if (xf && expdir_search(xf, dirpath, sa, &options, &secflavs)) {
fhr.fhr_flags = options;
fhr.fhr_vers = rqstp->rq_vers;
bcopy(&secflavs, &fhr.fhr_sec, sizeof(struct nfs_sec));
memset(&fhr.fhr_fh, 0, sizeof(fhandle_t));
fhr.fhr_fh.fh_len = (fhr.fhr_vers < 3) ? NFSV2_MAX_FH_SIZE : NFSV3_MAX_FH_SIZE;
if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) {
DEBUG(1, "Can't get fh for %s: %s (%d)", dirpath,
strerror(errno), errno);
bad = EACCES;
if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad))
log(LOG_ERR, "Can't send reply for failed mount");
unlock_exports();
return;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr))
log(LOG_ERR, "Can't send mount reply");
if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, 0))
add_mlist(hostbuf, dirpath);
log(LOG_INFO, "Mount successful: %s %s", hostbuf, dirpath);
} else {
bad = EACCES;
if (config.verbose) {
getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath);
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad))
log(LOG_ERR, "Can't send reply for failed mount");
}
unlock_exports();
return;
case RPCMNT_DUMP:
if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL))
log(LOG_ERR, "Can't send MOUNT dump reply");
if (config.verbose >= 3) {
getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
DEBUG(1, "dump: %s", addrbuf);
}
return;
case RPCMNT_UMOUNT:
if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) {
svcerr_weakauth(transp);
return;
}
if (!svc_getargs(transp, (xdrproc_t)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");
if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NAMEREQD))
del_mlist(hostbuf, dirpath);
else
hostbuf[0] = '\0';
if (!getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST))
del_mlist(addrbuf, dirpath);
log(LOG_INFO, "umount: %s %s", hostbuf[0] ? hostbuf : addrbuf, dirpath);
return;
case RPCMNT_UMNTALL:
if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) {
svcerr_weakauth(transp);
return;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
log(LOG_ERR, "Can't send UMNTALL reply");
if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NAMEREQD))
del_mlist(hostbuf, NULL);
else
hostbuf[0] = '\0';
if (!getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST))
del_mlist(addrbuf, (char *)NULL);
log(LOG_INFO, "umount all: %s", hostbuf[0] ? hostbuf : addrbuf);
return;
case RPCMNT_EXPORT:
lock_exports();
if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL))
log(LOG_ERR, "Can't send EXPORT reply");
unlock_exports();
if (config.verbose >= 3) {
getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
DEBUG(1, "export: %s", addrbuf);
}
return;
default:
svcerr_noproc(transp);
return;
}
}
int
xdr_dir(XDR *xdrsp, char *dirp)
{
return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN));
}
int
xdr_fhs(XDR *xdrsp, caddr_t cp)
{
struct fhreturn *fhrp = (struct fhreturn *)cp;
xdr_long_t ok = 0, len, auth;
int32_t i;
if (!xdr_long(xdrsp, &ok))
return (0);
switch (fhrp->fhr_vers) {
case 1:
return (xdr_opaque(xdrsp, 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, fhrp->fhr_fh.fh_data, fhrp->fhr_fh.fh_len))
return (0);
if (fhrp->fhr_sec.count == 0) {
auth = RPCAUTH_SYS;
len = 1;
if (!xdr_long(xdrsp, &len))
return (0);
return (xdr_long(xdrsp, &auth));
}
len = fhrp->fhr_sec.count;
if (!xdr_long(xdrsp, &len))
return (0);
for (i = 0; i < fhrp->fhr_sec.count; i++) {
auth = (xdr_long_t)fhrp->fhr_sec.flavors[i];
if (!xdr_long(xdrsp, &auth))
return (0);
}
return (TRUE);
};
return (0);
}
int
xdr_mlist(XDR *xdrsp, __unused 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);
}
int
xdr_explist(XDR *xdrsp, __unused caddr_t cp)
{
struct expfs *xf;
struct expdir *xd;
int falseval = 0;
TAILQ_FOREACH(xf, &xfshead, xf_next) {
TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) {
if (put_exlist(xd, xdrsp))
goto errout;
}
}
if (!xdr_bool(xdrsp, &falseval))
return (0);
return (1);
errout:
return (0);
}
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;
char offline_all[] = "<offline>";
char offline_some[] = "<offline*>";
char everyone[] = "(Everyone)";
char abuf[RPCMNT_NAMELEN+1];
int offline = 0, auth = 0, i;
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_iflags & OP_OFFLINE) {
if (!xdr_bool(xdrsp, &trueval))
return (1);
strp = (xd->xd_iflags & OP_ONLINE) ? offline_some : offline_all;
if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN))
return (1);
offline = 1;
}
if ((xd->xd_ssec.count > 1) || (xd->xd_ssec.flavors[0] != RPCAUTH_SYS)) {
if (!xdr_bool(xdrsp, &trueval))
return (1);
abuf[0] = '\0';
strlcpy(abuf, "<", sizeof(abuf));
for (i=0; i < xd->xd_ssec.count; i++) {
if (xd->xd_ssec.flavors[i] == RPCAUTH_SYS)
strlcat(abuf, "sys", sizeof(abuf));
else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5)
strlcat(abuf, "krb5", sizeof(abuf));
else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5I)
strlcat(abuf, "krb5i", sizeof(abuf));
else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5P)
strlcat(abuf, "krb5p", sizeof(abuf));
else
continue;
if (i < xd->xd_ssec.count-1)
strlcat(abuf, ":", sizeof(abuf));
}
strlcat(abuf, ">", sizeof(abuf));
strp = abuf;
if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN))
return (1);
auth = 1;
}
if (!(xd->xd_flags & OP_DEFEXP)) {
TAILQ_FOREACH(hp, &xd->xd_hosts, ht_next) {
if (!(hp->ht_flags & OP_SHOW))
continue;
grp = hp->ht_grp;
switch (grp->gr_type) {
case GT_HOST:
case GT_NET:
case GT_NETGROUP:
if (!xdr_bool(xdrsp, &trueval))
return (1);
strp = grp_name(grp);
if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN))
return (1);
}
}
} else if (offline || auth) {
if (!xdr_bool(xdrsp, &trueval))
return (1);
strp = everyone;
if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN))
return (1);
}
if (!xdr_bool(xdrsp, &falseval))
return (1);
TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) {
if (put_exlist(mxd, xdrsp))
return (1);
}
return (0);
}
char *
clean_pathname(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;
}
for (; len > 1 && s[len-1] == '/'; len--)
;
s[len] = '\0';
return (s);
}
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) {
DEBUG(1, "DADiskCreateFromBSDName(%s) failed", path);
rv = 0;
goto out;
}
dd = DADiskCopyDescription(disk);
if (!dd) {
DEBUG(1, "DADiskCopyDescription(%s) failed", path);
rv = 0;
goto out;
}
if (!CFDictionaryGetValueIfPresent(dd, (kDADiskDescriptionVolumeUUIDKey), &val)) {
DEBUG(1, "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);
}
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);
}
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);
}
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);
}
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));
strlcpy(ulpnew->ul_mntfromname, fsb->f_mntfromname, sizeof(ulpnew->ul_mntfromname));
strlcpy(ulpnew->ul_mntonname, fsb->f_mntonname, sizeof(ulpnew->ul_mntonname));
xfsid = UUID2FSID(uuid);
ulpnew->ul_fsid = xfsid;
while (find_uuid_by_fsid(ulpnew->ul_fsid))
if (++ulpnew->ul_fsid == xfsid) {
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);
}
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];
davalid = get_uuid_from_diskarb(fsb->f_mntfromname, dauuid);
if (davalid) {
DEBUG(2, "get_uuid: %s %s DiskArb says: %s",
fsb->f_mntfromname, fsb->f_mntonname,
uuidstring(dauuid, buf));
}
if ((ulp = get_uuid_from_list(fsb, uuid, UL_CHECK_MNTON))) {
DEBUG(2, "get_uuid: %s %s found: %s",
fsb->f_mntfromname, fsb->f_mntonname,
uuidstring(uuid, buf));
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) {
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
strlcpy(buf2, "------------------------------------", sizeof(buf2));
if (ulp->ul_exported) {
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));
TAILQ_REMOVE(&ulhead, ulp, ul_list);
free(ulp);
ulp = NULL;
} else {
ulp->ul_exported = 1;
}
} else if (davalid) {
bcopy(dauuid, uuid, sizeof(dauuid));
} else {
reportuuid = 1;
cfuuid = CFUUIDCreate(NULL);
uuidbytes = CFUUIDGetUUIDBytes(cfuuid);
bcopy(&uuidbytes, uuid, sizeof(uuidbytes));
CFRelease(cfuuid);
}
if (!ulp) {
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);
}
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]) {
strlcpy(ulp->ul_mntfromname, fsb->f_mntfromname, sizeof(ulp->ul_mntfromname));
}
if (reportuuid)
log(LOG_WARNING, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf));
else
DEBUG(1, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf));
return (ulp);
}
void
uuidlist_clearexport(void)
{
struct uuidlist *ulp;
TAILQ_FOREACH(ulp, &ulhead, ul_list) {
ulp->ul_exported = 0;
}
}
#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)
{
snprintf(string, (16*2)+4+1,
"%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);
}
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
strlcpy(buf, "------------------------------------", sizeof(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);
}
void
uuidlist_restore(void)
{
struct uuidlist *ulp;
struct expidlist *xid;
char *cp, str[2*MAXPATHLEN];
FILE *ulfile;
int i, slen, davalid, uuidchanged;
uint32_t linenum;
struct statfs fsb;
u_char dauuid[16];
char buf[64], buf2[64];
if ((ulfile = fopen(_PATH_MOUNTEXPLIST, "r")) == NULL) {
if (errno != ENOENT)
log(LOG_WARNING, "Can't open %s: %s (%d)", _PATH_MOUNTEXPLIST,
strerror(errno), errno);
else
DEBUG(1, "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)) {
if (!ulp) {
log(LOG_ERR, "ignoring orphaned export ID at line %d of %s",
linenum, _PATH_MOUNTEXPLIST);
continue;
}
xid = malloc(sizeof(*xid));
if (xid == NULL) {
log(LOG_ERR, "uuidlist_restore: Out of memory");
exit(2);
}
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 >= (int)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
strlcpy(xid->xid_path, cp, sizeof(xid->xid_path));
LIST_INSERT_HEAD(&ulp->ul_exportids, xid, xid_list);
continue;
}
ulp = malloc(sizeof(*ulp));
if (ulp == NULL) {
log(LOG_ERR, "uuidlist_restore: Out of memory");
exit(2);
}
bzero(ulp, sizeof(*ulp));
LIST_INIT(&ulp->ul_exportids);
cp = str;
if (*cp == '-') {
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 < (int)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 < (int)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';
if (!check_dirpath(ulp->ul_mntonname) ||
(statfs(ulp->ul_mntonname, &fsb) < 0) ||
strcmp(ulp->ul_mntonname, fsb.f_mntonname)) {
DEBUG(1, "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;
}
strncpy(ulp->ul_mntfromname, fsb.f_mntfromname, MAXPATHLEN);
ulp->ul_mntfromname[MAXPATHLEN-1] = '\0';
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) {
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
strlcpy(buf2, "------------------------------------", sizeof(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);
xid = malloc(sizeof(*xid));
if (!xid) {
log(LOG_ERR, "get_export_id: out of memory");
return (NULL);
}
bzero(xid, sizeof(*xid));
strlcpy(xid->xid_path, path, sizeof(xid->xid_path));
xid->xid_id = maxid + 1;
while (find_export_id(ulp, xid->xid_id)) {
xid->xid_id++;
if (xid->xid_id == maxid) {
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);
}
struct uuidlist *
find_exported_fs_by_path_and_uuid(char *fspath, u_char *fsuuid)
{
struct uuidlist *ulp;
ulp = TAILQ_FIRST(&ulhead);
while (ulp) {
while (ulp && fsuuid) {
if (ulp->ul_davalid && !bcmp(ulp->ul_dauuid, fsuuid, sizeof(ulp->ul_dauuid)))
break;
if (!bcmp(ulp->ul_uuid, fsuuid, sizeof(ulp->ul_uuid)))
break;
ulp = TAILQ_NEXT(ulp, ul_list);
}
if (!ulp)
break;
if (!fspath || !strcmp(ulp->ul_mntonname, fspath))
break;
ulp = TAILQ_NEXT(ulp, ul_list);
}
return (ulp);
}
struct uuidlist *
find_exported_fs_by_dirlist(struct dirlist *dirhead)
{
struct dirlist *dirl;
char *path, *p;
int cmp, bestlen, len;
struct uuidlist *ulp, *bestulp;
if (!dirhead)
return (NULL);
path = strdup(dirhead->dl_dir);
dirl = dirhead->dl_next;
while (dirl) {
cmp = subdir_check(path, dirl->dl_dir);
if (cmp >= 0) {
dirl = dirl->dl_next;
continue;
}
p = strrchr(path, '/');
if (p == path) {
p[1] = '\0';
break;
}
p[0] = '\0';
}
DEBUG(4, "find_exported_fs: %s", path);
bestulp = NULL;
bestlen = -1;
TAILQ_FOREACH(ulp, &ulhead, ul_list) {
if (subdir_check(ulp->ul_mntonname, path) < 0)
continue;
len = strlen(ulp->ul_mntonname);
if (len > bestlen) {
bestulp = ulp;
bestlen = strlen(ulp->ul_mntonname);
}
}
free(path);
DEBUG(4, "find_exported_fs: best exported fs: %s", bestulp ? bestulp->ul_mntonname : "<none>");
return (bestulp);
}
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;
DEBUG(4, "subdir_check: %s %s %d", s1, s2, rv);
return rv;
}
char *line = NULL;
uint32_t linesize = 0;
FILE *exp_file;
uint32_t linenum, entrylines;
int
get_exportlist(void)
{
struct expfs *xf, *xf2, *xf3;
struct grouplist ngrp, *grp, *tgrp, tmpgrp;
struct expdir *xd, *xd2, *xd3;
struct expidlist *xid;
struct dirlist *dirhead, *dirl, *dirl2;
struct namelisttqh names, netgroups;
struct namelist *nl;
struct host *ht, *ht2, *ht3;
struct hosttqh htfree;
struct statfs fsb;
struct xucred anon;
struct nfs_sec secflavs;
char *cp, *endcp, *name, *word, *hst, *usr, *dom, savedc, *savedcp, *subdir, *mntonname, *word2;
int len, dlen, hostcount, badhostcount, exflags, got_nondir, netgrp, cmp, show;
char fspath[MAXPATHLEN];
u_char uuid[16], fsuuid[16];
struct uuidlist *ulp, *bestulp;
char buf[64], buf2[64];
int error, opt_flags, need_export, saved_errors;
lock_exports();
TAILQ_FOREACH(xf, &xfshead, xf_next) {
TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) {
xd->xd_iflags &= ~(OP_ADD|OP_OFFLINE|OP_ONLINE);
xd->xd_ssec.count = 0;
xd->xd_oflags = xd->xd_flags;
xd->xd_ocred = xd->xd_cred;
xd->xd_osec = xd->xd_sec;
xd->xd_flags |= OP_DEL;
TAILQ_FOREACH(ht, &xd->xd_hosts, ht_next)
ht->ht_flags |= OP_DEL;
TAILQ_FOREACH(xd2, &xd->xd_mountdirs, xd_next) {
xd2->xd_flags |= OP_DEL;
TAILQ_FOREACH(ht, &xd2->xd_hosts, ht_next)
ht->ht_flags |= OP_DEL;
}
}
}
uuidlist_clearexport();
if (xpaths) {
free_dirlist(xpaths);
xpaths = NULL;
}
xpaths_complete = 1;
TAILQ_INIT(&names);
TAILQ_INIT(&netgroups);
hostnamecount = hostnamegoodcount = 0;
missingexportcount = 0;
export_errors = 0;
linenum = 0;
if ((exp_file = fopen(exportsfilepath, "r")) == NULL) {
log(LOG_WARNING, "Can't open %s", exportsfilepath);
export_errors = 1;
goto exports_read;
}
dirhead = NULL;
while (get_export_entry()) {
DEBUG(1, "---> Got line: %s", line);
saved_errors = export_errors;
hostcount = badhostcount = 0;
anon = def_anon;
exflags = 0;
opt_flags = 0;
got_nondir = 0;
xf = NULL;
ulp = NULL;
fspath[0] = '\0';
bzero(fsuuid, sizeof(fsuuid));
tgrp = NULL;
bzero(&ngrp, sizeof(ngrp));
bzero(&secflavs, sizeof(secflavs));
secflavs.flavors[0] = RPCAUTH_SYS;
secflavs.count = 1;
cp = line;
nextfield(&cp, &endcp);
if (*cp == '#')
goto nextline;
while (endcp > cp) {
DEBUG(2, "got field: %.*s", endcp-cp, cp);
if (*cp == '-') {
if (dirhead == NULL) {
export_error(LOG_ERR, "got options with no exported directory: %s", cp);
export_error_cleanup(xf);
goto nextline;
}
DEBUG(3, "processing option: %.*s", endcp-cp, cp);
got_nondir = 1;
savedcp = cp;
if (do_opt(&cp, &endcp, &ngrp, &hostcount, &opt_flags,
&exflags, &anon, &secflavs, fspath, fsuuid)) {
export_error(LOG_ERR, "error processing options: %s", savedcp);
export_error_cleanup(xf);
goto nextline;
}
} else if ((*cp == '/') || ((*cp == '\'' || *cp == '"') && (*(cp+1) == '/'))) {
DEBUG(2, "processing pathname: %.*s", endcp-cp, cp);
word = clean_pathname(cp);
DEBUG(3, " cleaned pathname: %s", word);
if (word == NULL) {
export_error(LOG_ERR, "error processing pathname (out of memory)");
export_error_cleanup(xf);
goto nextline;
}
if (got_nondir) {
export_error(LOG_ERR, "Directories must be first: %s", word);
export_error_cleanup(xf);
free(word);
goto nextline;
}
if (strlen(word) > RPCMNT_NAMELEN) {
export_error(LOG_ERR, "pathname too long (%d > %d): %s",
strlen(word), RPCMNT_NAMELEN, word);
export_error_cleanup(xf);
free(word);
goto nextline;
}
word2 = strdup(word);
if ((error = add_dir(&xpaths, word2)))
free(word2);
if (error == ENOMEM) {
log(LOG_WARNING, "Can't allocate memory to add export path: %s", word);
xpaths_complete = 0;
}
error = add_dir(&dirhead, word);
if (error == EEXIST) {
export_error(LOG_WARNING, "duplicate directory: %s", word);
free(word);
} else if (error == ENOMEM) {
export_error(LOG_ERR, "Can't allocate memory to add directory: %s", word);
export_error_cleanup(xf);
free(word);
goto nextline;
}
} else {
savedc = *endcp;
*endcp = '\0';
DEBUG(2, "got host/netgroup: %s", cp);
got_nondir = 1;
if (dirhead == NULL) {
export_error(LOG_ERR, "got host/group with no directory?: %s", cp);
export_error_cleanup(xf);
goto nextline;
}
error = add_name(&names, cp);
if (error == ENOMEM) {
export_error(LOG_ERR, "Can't allocate memory to add host/group: %s", cp);
export_error_cleanup(xf);
goto nextline;
}
show = 1;
while ((nl = TAILQ_FIRST(&names))) {
TAILQ_REMOVE(&names, nl, nl_next);
name = nl->nl_name;
free(nl);
setnetgrent(name);
netgrp = getnetgrent(&hst, &usr, &dom);
if (netgrp) {
DEBUG(3, "got netgroup: %s", name);
error = add_name(&netgroups, name);
if (error == ENOMEM) {
export_error(LOG_ERR, "Can't allocate memory to add host/group: %s", cp);
export_error_cleanup(xf);
endnetgrent();
free(name);
goto nextline;
}
if (show) {
DEBUG(3, "add netgroup w/show: %s", name);
show = 0;
bzero(&tmpgrp, sizeof(tmpgrp));
tmpgrp.gr_type = GT_NETGROUP;
tmpgrp.gr_flags = GF_SHOW;
tmpgrp.gr_u.gt_netgroup = strdup(name);
if (!tmpgrp.gr_u.gt_netgroup || !(grp = get_grp(&tmpgrp))) {
export_error(LOG_ERR, "Can't allocate memory to add netgroup - %s", name);
export_error_cleanup(xf);
endnetgrent();
free(name);
goto nextline;
} else if (!add_grp(&tgrp, grp)) {
DEBUG(3, "duplicate netgroup: %s", name);
free_grp(grp);
}
}
if (error == -1) {
DEBUG(3, "netgroup already in netgroup list: %s", name);
endnetgrent();
free(name);
continue;
}
do {
DEBUG(3, "add netgroup member: %s", hst);
error = add_name(&names, hst);
if (error == ENOMEM) {
export_error(LOG_ERR, "Can't allocate memory to add netgroup:host - %s", name, hst);
export_error_cleanup(xf);
endnetgrent();
free(name);
goto nextline;
}
} while (getnetgrent(&hst, &usr, &dom));
endnetgrent();
free(name);
continue;
}
endnetgrent();
bzero(&tmpgrp, sizeof(tmpgrp));
if (get_host_addresses(name, &tmpgrp)) {
export_error(LOG_WARNING, "couldn't get address for host: %s", name);
badhostcount++;
} else {
DEBUG(3, "got host: %s", name);
if (show) {
show = 0;
tmpgrp.gr_flags |= GF_SHOW;
}
grp = get_grp(&tmpgrp);
if (!grp) {
export_error(LOG_ERR, "Can't allocate memory to add host - %s", name);
} else if (!add_grp(&tgrp, grp)) {
DEBUG(3, "duplicate host: %s", name);
free_grp(grp);
} else {
hostcount++;
}
}
free(name);
}
*endcp = savedc;
}
cp = endcp;
nextfield(&cp, &endcp);
}
if (!(opt_flags & (OP_MAPROOT|OP_MAPALL))) {
exflags |= NX_MAPROOT;
opt_flags |= OP_MAPROOT;
}
if (check_options(opt_flags)) {
export_error(LOG_ERR, "bad export options");
export_error_cleanup(xf);
goto nextline;
}
if (!hostcount) {
if (badhostcount) {
export_error(LOG_ERR, "no valid hosts found for export");
export_error_cleanup(xf);
goto nextline;
}
DEBUG(1, "default export");
} else if (opt_flags & OP_NET) {
if (tgrp) {
export_error(LOG_ERR, "can't specify both network and hosts on same line");
export_error_cleanup(xf);
goto nextline;
}
ngrp.gr_flags |= GF_SHOW;
tgrp = get_grp(&ngrp);
if (!tgrp) {
export_error(LOG_ERR, "Can't allocate memory to add network - %s",
grp_name(&ngrp));
export_error_cleanup(xf);
goto nextline;
}
ngrp.gr_type = GT_NULL;
}
if (dirhead == NULL) {
export_error(LOG_ERR, "no directories for export entry?");
export_error_cleanup(xf);
goto nextline;
}
mntonname = NULL;
if (opt_flags & (OP_FSPATH|OP_FSUUID))
bestulp = find_exported_fs_by_path_and_uuid(
(opt_flags & OP_FSPATH) ? fspath : NULL,
(opt_flags & OP_FSUUID) ? fsuuid : NULL);
else
bestulp = find_exported_fs_by_dirlist(dirhead);
dirl = dirhead;
while (dirl && !check_dirpath(dirl->dl_dir)) {
export_error(LOG_WARNING, "path contains non-directory or non-existent components: %s", dirl->dl_dir);
dirl2 = dirl->dl_next;
while (dirl2 && (subdir_check(dirl->dl_dir, dirl2->dl_dir) == 1))
dirl2 = dirl2->dl_next;
dirl = dirl2;
}
if (!dirl) {
if (!bestulp) {
export_error(LOG_ERR, "no usable directories in export entry and no fallback");
export_error_cleanup(xf);
goto nextline;
}
export_error(LOG_WARNING, "no usable directories in export entry");
goto prepare_offline_export;
}
if (statfs(dirl->dl_dir, &fsb) < 0) {
export_error(LOG_ERR, "statfs failed (%s (%d)) for path: %s",
strerror(errno), errno, dirl->dl_dir);
export_error_cleanup(xf);
goto nextline;
}
if ((opt_flags & OP_FSPATH) && strcmp(fsb.f_mntonname, fspath)) {
if (!bestulp) {
export_error(LOG_ERR, "file system path (%s) does not match fspath (%s) and no fallback",
fsb.f_mntonname, fspath);
export_error_cleanup(xf);
goto nextline;
}
export_error(LOG_WARNING, "file system path (%s) does not match fspath (%s)",
fsb.f_mntonname, fspath);
goto prepare_offline_export;
}
if (bestulp && (subdir_check(fsb.f_mntonname, bestulp->ul_mntonname) > 0)) {
export_error(LOG_WARNING, "Exported file system (%s) doesn't match best guess (%s).",
fsb.f_mntonname, bestulp->ul_mntonname);
if (!(opt_flags & (OP_FSUUID|OP_FSPATH)))
export_error(LOG_WARNING, "Suggest using fspath=/path and/or fsuuid=uuid to disambiguate.");
goto prepare_offline_export;
}
if (!(ulp = get_uuid(&fsb, uuid))) {
export_error(LOG_ERR, "couldn't get UUID for volume: %s", fsb.f_mntonname);
export_error_cleanup(xf);
goto nextline;
}
if ((opt_flags & OP_FSUUID) && bcmp(uuid, fsuuid, sizeof(fsuuid))) {
if (!bestulp) {
export_error(LOG_ERR, "file system UUID (%s) does not match fsuuid (%s) and no fallback",
uuidstring(uuid, buf), uuidstring(fsuuid, buf2));
export_error_cleanup(xf);
goto nextline;
}
export_error(LOG_WARNING, "file system UUID (%s) does not match fsuuid (%s)",
uuidstring(uuid, buf), uuidstring(fsuuid, buf2));
goto prepare_offline_export;
}
if (bestulp && (opt_flags & OP_MISSING)) {
prepare_offline_export:
missingexportcount++;
export_error(LOG_WARNING, "using fallback (marked offline): %s", bestulp->ul_mntonname);
exflags |= NX_OFFLINE;
opt_flags |= OP_MISSING;
ulp = bestulp;
mntonname = ulp->ul_mntonname;
bcopy(ulp->ul_uuid, uuid, sizeof(uuid));
} else {
mntonname = fsb.f_mntonname;
}
xf = ex_search(uuid);
if (xf == NULL) {
xf = get_expfs();
if (xf)
xf->xf_fsdir = malloc(strlen(mntonname) + 1);
if (!xf || !xf->xf_fsdir) {
export_error(LOG_ERR, "Can't allocate memory to export volume: %s",
mntonname);
export_error_cleanup(xf);
goto nextline;
}
bcopy(uuid, xf->xf_uuid, sizeof(uuid));
xf->xf_fsid = ulp->ul_fsid;
strlcpy(xf->xf_fsdir, mntonname, strlen(mntonname)+1);
DEBUG(2, "New expfs uuid=%s", uuidstring(uuid, buf));
} else {
DEBUG(2, "Found expfs uuid=%s", uuidstring(uuid, buf));
}
if (dirl)
dirl = dirl->dl_next;
for (; dirl; dirl = dirl->dl_next) {
DEBUG(2, "dir: %s", dirl->dl_dir);
if (!check_dirpath(dirl->dl_dir)) {
export_error(LOG_WARNING, "path contains non-directory or non-existent components: %s", dirl->dl_dir);
continue;
}
if (statfs(dirl->dl_dir, &fsb) < 0) {
export_error(LOG_WARNING, "statfs failed (%s (%d)) for path: %s",
strerror(errno), errno, dirl->dl_dir);
continue;
}
if (strcmp(xf->xf_fsdir, fsb.f_mntonname)) {
export_error(LOG_WARNING, "Volume mismatch for: %s\ndirectories must be on same volume", dirl->dl_dir);
continue;
}
}
dirl = dirhead;
while (dirl) {
DEBUG(2, "dir: %s", dirl->dl_dir);
TAILQ_FOREACH(xd2, &xf->xf_dirl, xd_next) {
if ((xd2->xd_iflags & OP_ADD) &&
((subdir_check(xd2->xd_dir, dirl->dl_dir) == 1) ||
(subdir_check(dirl->dl_dir, xd2->xd_dir) == 1))) {
export_error(LOG_ERR, "%s conflicts with existing export %s",
dirl->dl_dir, xd2->xd_dir);
export_error_cleanup(xf);
goto nextline;
}
}
len = strlen(dirl->dl_dir);
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);
DEBUG(3, " %s compare %s %d", dirl->dl_dir,
xd2->xd_dir, cmp);
if (!cmp) {
if (len == dlen)
break;
cmp = 1;
}
if (cmp > 0)
break;
xd3 = xd2;
}
if (!cmp) {
xd = xd2;
DEBUG(2, " %s xd is %s", dirl->dl_dir, xd->xd_dir);
} else {
if (strncmp(dirl->dl_dir, mntonname, strlen(mntonname))) {
export_error(LOG_ERR, "exported dir/fs mismatch: %s %s",
dirl->dl_dir, mntonname);
export_error_cleanup(xf);
goto nextline;
}
subdir = dirl->dl_dir + strlen(mntonname);
while (*subdir && (*subdir == '/'))
subdir++;
xid = get_export_id(ulp, subdir);
if (!xid) {
export_error(LOG_ERR, "unable to get export ID for %s", dirl->dl_dir);
export_error_cleanup(xf);
goto nextline;
}
xd = get_expdir();
if (xd)
xd->xd_dir = strdup(dirl->dl_dir);
if (!xd || !xd->xd_dir) {
if (xd)
free_expdir(xd);
export_error(LOG_ERR, "can't allocate memory for export %s", dirl->dl_dir);
export_error_cleanup(xf);
goto nextline;
}
xd->xd_xid = xid;
DEBUG(2, " %s new xd", xd->xd_dir);
}
if (hang_options_setup(xd, opt_flags, &anon, tgrp, &secflavs, &need_export)) {
export_error(LOG_ERR, "export option conflict for %s", xd->xd_dir);
hang_options_cleanup(xd);
if (cmp)
free_expdir(xd);
export_error_cleanup(xf);
goto nextline;
}
if (need_export && !(checkexports && (opt_flags & OP_MISSING))) {
int expcmd = checkexports ? NXA_CHECK : NXA_REPLACE;
if (do_export(expcmd, xf, xd, tgrp, exflags, &anon, &secflavs)) {
if ((errno == ENOTSUP) || (errno == EISDIR)) {
export_error(LOG_ERR, "kernel export registration failed: "
"NFS exporting not supported by fstype \"%s\" (%s)",
statfs(xf->xf_fsdir, &fsb) ? "?" : fsb.f_fstypename,
(errno == EISDIR) ? "readdir" : "fh");
} else {
export_error(LOG_ERR, "kernel export registration failed");
}
hang_options_cleanup(xd);
if (cmp)
free_expdir(xd);
export_error_cleanup(xf);
goto nextline;
}
DEBUG(1, "kernel export registered for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path);
} else {
DEBUG(2, "kernel export already registered for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path);
}
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, tgrp, &secflavs);
if (error == EEXIST) {
export_error(LOG_WARNING, "mount subdirectory export option conflict for %s",
dirl2->dl_dir);
} else if (error == ENOMEM) {
export_error(LOG_WARNING, "unable to add mount subdirectory for %s, %s",
xd->xd_dir, dirl2->dl_dir);
}
dirl2 = dirl2->dl_next;
}
dirl = dirl2;
hang_options_finalize(xd);
xd->xd_iflags |= OP_ADD;
if (cmp) {
if (xd3) {
TAILQ_INSERT_AFTER(&xf->xf_dirl, xd3, xd, xd_next);
} else {
TAILQ_INSERT_HEAD(&xf->xf_dirl, xd, xd_next);
}
}
}
if ((xf->xf_flag & XF_LINKED) == 0) {
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;
}
if (export_errors == saved_errors) {
if (clear_export_errors(linenum))
log(LOG_WARNING, "exports:%d: export entry OK (previous errors cleared)", linenum);
}
nextline:
if (!TAILQ_EMPTY(&netgroups))
free_namelist(&netgroups);
if (!TAILQ_EMPTY(&names))
free_namelist(&names);
if (dirhead) {
free_dirlist(dirhead);
dirhead = NULL;
}
switch (ngrp.gr_type) {
case GT_NET:
if (ngrp.gr_u.gt_net.nt_name)
free(ngrp.gr_u.gt_net.nt_name);
break;
}
while (tgrp) {
grp = tgrp;
tgrp = tgrp->gr_next;
grp->gr_flags &= ~GF_SHOW;
free_grp(grp);
}
}
fclose(exp_file);
if (config.verbose >= 5) {
DEBUG(3, "========> get_exportlist() CURRENT EXPORTS UPDATED:");
dump_exports();
}
exports_read:
xf = TAILQ_FIRST(&xfshead);
while (xf && !checkexports) {
xd = TAILQ_FIRST(&xf->xf_dirl);
while (xd) {
TAILQ_FOREACH_SAFE(xd2, &xd->xd_mountdirs, xd_next, xd3) {
TAILQ_FOREACH_SAFE(ht, &xd2->xd_hosts, ht_next, ht2)
if (ht->ht_flags & OP_DEL) {
TAILQ_REMOVE(&xd2->xd_hosts, ht, ht_next);
free_host(ht);
}
if (xd2->xd_flags & OP_DEL)
xd2->xd_flags = xd2->xd_oflags = 0;
if (!(xd2->xd_flags & OP_DEFEXP) && TAILQ_EMPTY(&xd2->xd_hosts)) {
TAILQ_REMOVE(&xd->xd_mountdirs, xd2, xd_next);
free_expdir(xd2);
}
}
TAILQ_INIT(&htfree);
TAILQ_FOREACH_SAFE(ht, &xd->xd_hosts, ht_next, ht2)
if (ht->ht_flags & OP_DEL) {
TAILQ_REMOVE(&xd->xd_hosts, ht, ht_next);
TAILQ_INSERT_TAIL(&htfree, ht, ht_next);
}
tgrp = NULL;
TAILQ_FOREACH_SAFE(ht, &htfree, ht_next, ht2) {
grp = ht->ht_grp;
if (grp->gr_type == GT_NETGROUP) {
} else if (!find_group_address_match_in_host_list(&xd->xd_hosts, grp)) {
ht->ht_grp = NULL;
if (!add_grp(&tgrp, grp)) {
log(LOG_ERR, "failure to queue group for export deletion");
grp->gr_next = tgrp;
tgrp = grp;
}
} else if (grp->gr_type == GT_HOST) {
struct addrinfo *a1, *a2, *prea3, *a3, atmp, *anexttmp;
a1 = grp->gr_u.gt_hostinfo.h_ailist;
prea3 = NULL;
a3 = a1;
while (a1) {
TAILQ_FOREACH(ht3, &xd->xd_hosts, ht_next) {
if (ht3->ht_grp->gr_type != GT_HOST)
continue;
a2 = ht3->ht_grp->gr_u.gt_hostinfo.h_ailist;
while (a2 && addrinfo_cmp(a1, a2))
a2 = a2->ai_next;
if (a2)
break;
}
if (!ht3) {
if (a3 != a1) {
atmp = *a3;
anexttmp = a3->ai_next;
*a3 = *a1;
a3->ai_next = anexttmp;
atmp.ai_next = a1->ai_next;
*a1 = atmp;
}
prea3 = a3;
a3 = a3->ai_next;
}
a1 = a1->ai_next;
}
if (a3 == grp->gr_u.gt_hostinfo.h_ailist) {
} else {
if (prea3)
prea3->ai_next = NULL;
freeaddrinfo(a3);
ht->ht_grp = NULL;
if (!add_grp(&tgrp, grp)) {
log(LOG_ERR, "failure to queue group for export deletion");
grp->gr_next = tgrp;
tgrp = grp;
}
}
}
TAILQ_REMOVE(&htfree, ht, ht_next);
free_host(ht);
}
if ((config.verbose >= 3) && tgrp) {
struct grouplist *g;
DEBUG(1, "deleting export for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path);
g = tgrp;
while (g) {
DEBUG(3, " %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g));
g = g->gr_next;
}
}
if (tgrp && do_export(NXA_DELETE, xf, xd, tgrp, 0, NULL, NULL)) {
log(LOG_ERR, "kernel export unregistration failed for %s, %s%s",
xd->xd_dir, grp_name(tgrp), tgrp->gr_next ? ", ..." : "");
}
while (tgrp) {
grp = tgrp;
tgrp = tgrp->gr_next;
free_grp(grp);
}
if ((xd->xd_flags & (OP_DEL|OP_DEFEXP)) == (OP_DEL|OP_DEFEXP)) {
DEBUG(1, "deleting default export for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path);
xd->xd_flags = xd->xd_oflags = 0;
if (do_export(NXA_DELETE, xf, xd, NULL, 0, NULL, NULL)) {
log(LOG_ERR, "kernel export unregistration failed for %s,"
" default export", xd->xd_dir);
}
}
xd3 = TAILQ_NEXT(xd, xd_next);
if (!(xd->xd_flags & OP_DEFEXP) && TAILQ_EMPTY(&xd->xd_hosts)) {
TAILQ_REMOVE(&xf->xf_dirl, xd, xd_next);
free_expdir(xd);
} else {
xd->xd_iflags &= ~OP_ADD;
xd->xd_flags &= ~OP_DEL;
}
xd = xd3;
}
xf2 = TAILQ_NEXT(xf, xf_next);
if (TAILQ_EMPTY(&xf->xf_dirl)) {
TAILQ_REMOVE(&xfshead, xf, xf_next);
free_expfs(xf);
}
xf = xf2;
}
if (config.verbose >= 4) {
DEBUG(2, "========> get_exportlist() NEW EXPORTS LIST:");
dump_exports();
}
if (!checkexports)
uuidlist_save();
if (!checkexports && (recheckexports_until == 0)) {
if (hostnamegoodcount != hostnamecount) {
log(LOG_WARNING, "There seem to be problems resolving host names...");
log(LOG_WARNING, "...will periodically recheck exports for a while.");
recheckexports_until = time(NULL) + RECHECKEXPORTS_TIMEOUT;
} else {
recheckexports_until = -1;
}
} else if (recheckexports_until > 0) {
if (hostnamegoodcount == hostnamecount) {
recheckexports_until = -1;
} else if (recheckexports_until < time(NULL)) {
log(LOG_WARNING, "Giving up on automatic rechecking of exports.");
recheckexports_until = -1;
}
}
if (!checkexports && ((recheckexports_until > 0) || missingexportcount))
recheckexports = 1;
else
recheckexports = 0;
unlock_exports();
return (export_errors);
}
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_hosts);
TAILQ_INIT(&xd->xd_mountdirs);
return (xd);
}
static char unknown_group[] = "unknown group";
char *
grp_name(struct grouplist *grp)
{
if (grp->gr_type == GT_NETGROUP)
return (grp->gr_u.gt_netgroup);
if (grp->gr_type == GT_NET)
return (grp->gr_u.gt_net.nt_name);
if (grp->gr_type == GT_HOST)
return (grp->gr_u.gt_hostinfo.h_name);
return (unknown_group);
}
static char grpaddrbuf[MAXPATHLEN];
const char *
grp_addr(struct grouplist *grp)
{
struct addrinfo *ai;
void *sinaddr;
const char *s = NULL;
if (grp->gr_type == GT_HOST) {
if ((ai = grp->gr_u.gt_hostinfo.h_ailist)) {
sinaddr = (ai->ai_family == AF_INET) ?
(void*)&((struct sockaddr_in*)ai->ai_addr)->sin_addr :
(void*)&((struct sockaddr_in6*)ai->ai_addr)->sin6_addr;
if (inet_ntop(ai->ai_family, sinaddr, grpaddrbuf, sizeof(grpaddrbuf)))
s = grpaddrbuf;
}
} else if (grp->gr_type == GT_NET) {
sinaddr = (grp->gr_u.gt_net.nt_family == AF_INET) ?
(void*)&grp->gr_u.gt_net.nt_net : (void*)&grp->gr_u.gt_net.nt_net6;
if (inet_ntop(grp->gr_u.gt_net.nt_family, sinaddr, grpaddrbuf, sizeof(grpaddrbuf)))
s = grpaddrbuf;
} else if (grp->gr_type == GT_NETGROUP) {
s = "";
}
if (!s)
s = "???";
return (s);
}
int
addrinfo_cmp(struct addrinfo *a1, struct addrinfo *a2)
{
if (a1->ai_family != a2->ai_family)
return (a1->ai_family - a2->ai_family);
if (a1->ai_addrlen != a2->ai_addrlen)
return (a1->ai_addrlen - a2->ai_addrlen);
return bcmp(a1->ai_addr, a2->ai_addr, a1->ai_addrlen);
}
int
grpcmp(struct grouplist *g1, struct grouplist *g2)
{
struct netmsk *n1, *n2;
struct addrinfo *a1, *a2;
int rv;
rv = g1->gr_type - g2->gr_type;
if (rv)
return (rv);
switch (g1->gr_type) {
case GT_NETGROUP:
rv = strcmp(g1->gr_u.gt_netgroup, g2->gr_u.gt_netgroup);
break;
case GT_NET:
n1 = &g1->gr_u.gt_net;
n2 = &g2->gr_u.gt_net;
rv = strcmp(n1->nt_name, n2->nt_name);
if (rv)
break;
rv = n1->nt_family - n2->nt_family;
if (rv)
break;
if (n1->nt_family == AF_INET) {
rv = bcmp(&n1->nt_net, &n2->nt_net, sizeof(n1->nt_net));
if (rv)
break;
rv = bcmp(&n1->nt_mask, &n2->nt_mask, sizeof(n1->nt_mask));
} else if (n1->nt_family == AF_INET6) {
rv = bcmp(&n1->nt_net6, &n2->nt_net6, sizeof(n1->nt_net6));
if (rv)
break;
rv = bcmp(&n1->nt_mask6, &n2->nt_mask6, sizeof(n1->nt_mask6));
}
break;
case GT_HOST:
rv = strcmp(g1->gr_u.gt_hostinfo.h_name, g2->gr_u.gt_hostinfo.h_name);
if (rv)
break;
a1 = g1->gr_u.gt_hostinfo.h_ailist;
a2 = g2->gr_u.gt_hostinfo.h_ailist;
while (a1 && a2) {
rv = addrinfo_cmp(a1, a2);
if (rv)
break;
a1 = a1->ai_next;
a2 = a2->ai_next;
}
if (!rv && !(!a1 && !a2)) {
if (a1)
rv = 1;
else
rv = -1;
}
break;
}
return (rv);
}
struct grouplist *
get_grp(struct grouplist *grptmp)
{
struct grouplist *g, *g2;
int cmp = 1, clean_up_gr_u = 1;
if (config.verbose >= 7) {
DEBUG(5, "get_grp: %s %s", grp_name(grptmp), grp_addr(grptmp));
g = grpcache;
while (g) {
DEBUG(6, "grpcache: %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g));
g = g->gr_cache;
}
}
g2 = NULL;
g = grpcache;
while (g && ((cmp = grpcmp(grptmp, g)) > 0)) {
g2 = g;
g = g->gr_cache;
}
if (!cmp) {
g->gr_refcnt++;
if (config.verbose >= 7)
DEBUG(5, "get_grp: found %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g));
g->gr_flags |= grptmp->gr_flags;
goto out;
}
g = malloc(sizeof(*g));
if (g == NULL)
goto out;
memset(g, 0, sizeof(*g));
g->gr_refcnt = 1;
g->gr_type = grptmp->gr_type;
g->gr_flags = grptmp->gr_flags;
g->gr_u = grptmp->gr_u;
clean_up_gr_u = 0;
if (g2) {
g->gr_cache = g2->gr_cache;
g2->gr_cache = g;
} else {
g->gr_cache = grpcache;
grpcache = g;
}
if (config.verbose >= 7) {
DEBUG(5, "get_grp: ----- NEW %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g));
g2 = grpcache;
while (g2) {
DEBUG(6, "grpcache: %p %d %s %s", g2, g2->gr_refcnt, grp_name(g2), grp_addr(g2));
g2 = g2->gr_cache;
}
}
out:
if (clean_up_gr_u) {
switch (grptmp->gr_type) {
case GT_HOST:
if (grptmp->gr_u.gt_hostinfo.h_ailist)
freeaddrinfo(grptmp->gr_u.gt_hostinfo.h_ailist);
if (grptmp->gr_u.gt_hostinfo.h_name)
free(grptmp->gr_u.gt_hostinfo.h_name);
break;
case GT_NET:
if (grptmp->gr_u.gt_net.nt_name)
free(grptmp->gr_u.gt_net.nt_name);
break;
case GT_NETGROUP:
if (grptmp->gr_u.gt_netgroup)
free(grptmp->gr_u.gt_netgroup);
break;
}
}
return (g);
}
void
free_grp(struct grouplist *grp)
{
struct grouplist **g;
grp->gr_refcnt--;
if (config.verbose >= 7)
DEBUG(5, "free_grp: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp));
if (grp->gr_refcnt > 0)
return;
g = &grpcache;
while (*g && (*g != grp))
g = &(*g)->gr_cache;
if (*g)
*g = (*g)->gr_cache;
if (grp->gr_type == GT_HOST) {
if (grp->gr_u.gt_hostinfo.h_ailist)
freeaddrinfo(grp->gr_u.gt_hostinfo.h_ailist);
if (grp->gr_u.gt_hostinfo.h_name)
free(grp->gr_u.gt_hostinfo.h_name);
} else if (grp->gr_type == GT_NET) {
if (grp->gr_u.gt_net.nt_name)
free(grp->gr_u.gt_net.nt_name);
} else if (grp->gr_type == GT_NETGROUP) {
if (grp->gr_u.gt_netgroup)
free(grp->gr_u.gt_netgroup);
}
free((caddr_t)grp);
}
int
add_grp(struct grouplist **glp, struct grouplist *newg)
{
struct grouplist *g1, *g2;
int cmp = 1;
g2 = NULL;
g1 = *glp;
while (g1 && ((cmp = grpcmp(newg, g1)) > 0)) {
g2 = g1;
g1 = g1->gr_next;
}
if (!cmp) {
if (newg->gr_flags & GF_SHOW)
g1->gr_flags |= GF_SHOW;
return (0);
}
if (g2) {
newg->gr_next = g2->gr_next;
g2->gr_next = newg;
} else {
newg->gr_next = *glp;
*glp = newg;
}
return (1);
}
int
add_host(struct hosttqh *head, struct host *newht)
{
struct host *ht;
int cmp = 1;
TAILQ_FOREACH(ht, head, ht_next)
if ((cmp = grpcmp(newht->ht_grp, ht->ht_grp)) <= 0)
break;
if (!cmp && !(ht->ht_flags & OP_DEL)) {
if (newht->ht_flags & OP_SHOW)
ht->ht_flags |= OP_SHOW;
return (0);
}
if (ht)
TAILQ_INSERT_BEFORE(ht, newht, ht_next);
else
TAILQ_INSERT_TAIL(head, newht, ht_next);
return (1);
}
void
export_error(int level, const char *fmt, ...)
{
struct errlist *elnew, *el, *elp;
char *s = NULL;
va_list ap;
export_errors++;
va_start(ap, fmt);
vasprintf(&s, fmt, ap);
va_end(ap);
LIST_FOREACH(el, &xerrs, el_next) {
if (linenum < el->el_linenum)
continue;
if (linenum > el->el_linenum) {
el = NULL;
break;
}
if (!strcmp(el->el_msg, s))
break;
}
if (!el)
log(level, "exports:%d: %s", linenum, s);
elnew = malloc(sizeof(*elnew));
if (!elnew) {
free(s);
return;
}
elnew->el_linenum = linenum;
elnew->el_msg = s;
elp = NULL;
LIST_FOREACH(el, &xerrs, el_next) {
if (linenum < el->el_linenum)
break;
elp = el;
}
if (elp)
LIST_INSERT_AFTER(elp, elnew, el_next);
else
LIST_INSERT_HEAD(&xerrs, elnew, el_next);
}
int
clear_export_errors(uint32_t linenum)
{
struct errlist *el, *elnext;
int cleared = 0;
LIST_FOREACH_SAFE(el, &xerrs, el_next, elnext) {
if (linenum) {
if (linenum < el->el_linenum)
break;
if (linenum > el->el_linenum)
continue;
}
cleared = 1;
LIST_REMOVE(el, el_next);
if (el->el_msg)
free(el->el_msg);
free(el);
}
return (cleared);
}
void
export_error_cleanup(struct expfs *xf)
{
if (xf && (xf->xf_flag & XF_LINKED) == 0)
free_expfs(xf);
}
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);
}
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);
DEBUG(3, "add_dir: %s compare %s %d", cp, dl->dl_dir, cmp);
if (cmp < 0)
break;
if (cmp == 0) {
if (cplen == dlen)
return (EEXIST);
if (cp[dlen] == '/') {
DEBUG(3, "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 {
DEBUG(3, "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))
DEBUG(3, "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 (config.verbose >= 6) {
dl = *dlpp;
while (dl) {
DEBUG(4, "DIRLIST: %s", dl->dl_dir);
dl = dl->dl_next;
}
}
return (0);
}
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;
}
}
int
add_name(struct namelisttqh *names, char *name)
{
struct namelist *nl;
TAILQ_FOREACH(nl, names, nl_next)
if (!strcmp(nl->nl_name, name))
return (-1);
nl = malloc(sizeof(*nl));
if (!nl)
return (ENOMEM);
nl->nl_name = strdup(name);
if (!nl->nl_name) {
free(nl);
return (ENOMEM);
}
TAILQ_INSERT_TAIL(names, nl, nl_next);
return (0);
}
void
free_namelist(struct namelisttqh *names)
{
struct namelist *nl, *nlnext;
TAILQ_FOREACH_SAFE(nl, names, nl_next, nlnext) {
TAILQ_REMOVE(names, nl, nl_next);
if (nl->nl_name)
free(nl->nl_name);
free(nl);
}
}
struct host *
find_group_match_in_host_list(struct hosttqh *head, struct grouplist *grp)
{
struct host *ht;
TAILQ_FOREACH(ht, head, ht_next)
if (!grpcmp(grp, ht->ht_grp))
break;
return (ht);
}
struct host *
find_group_address_match_in_host_list(struct hosttqh *head, struct grouplist *grp)
{
struct host *ht;
struct netmsk *n1, *n2;
struct addrinfo *a1, *a2;
int i;
switch (grp->gr_type) {
case GT_HOST:
a1 = grp->gr_u.gt_hostinfo.h_ailist;
while (a1) {
TAILQ_FOREACH(ht, head, ht_next) {
if (ht->ht_grp->gr_type != GT_HOST)
continue;
if (ht->ht_flags & OP_DEL)
continue;
a2 = ht->ht_grp->gr_u.gt_hostinfo.h_ailist;
while (a2) {
if (!addrinfo_cmp(a1, a2))
return (ht);
a2 = a2->ai_next;
}
}
a1 = a1->ai_next;
}
break;
case GT_NET:
n1 = &grp->gr_u.gt_net;
TAILQ_FOREACH(ht, head, ht_next) {
if (ht->ht_grp->gr_type != GT_NET)
continue;
if (ht->ht_flags & OP_DEL)
continue;
n2 = &ht->ht_grp->gr_u.gt_net;
if (n1->nt_family != n2->nt_family)
continue;
if (n1->nt_family == AF_INET) {
in_addr_t ina1, ina2;
ina1 = n1->nt_net & n1->nt_mask;
ina2 = n2->nt_net & n2->nt_mask;
if (ina1 == ina2)
return (ht);
} else if (n1->nt_family == AF_INET6) {
struct in6_addr ina1, ina2;
for (i=0; i < (int)sizeof(ina1.s6_addr); i++) {
ina1.s6_addr[i] = n1->nt_net6.s6_addr[i] & n1->nt_mask6.s6_addr[i];
ina2.s6_addr[i] = n2->nt_net6.s6_addr[i] & n2->nt_mask6.s6_addr[i];
}
if (!bcmp(&ina1, &ina2, sizeof(ina1)))
return (ht);
}
}
break;
}
return (NULL);
}
int
crcmp(struct xucred *cr1, struct xucred *cr2)
{
int i;
if (cr1 == cr2)
return 0;
if (cr1 == NULL || cr2 == NULL)
return 1;
if (cr1->cr_uid != cr2->cr_uid)
return 1;
if (cr1->cr_ngroups != cr2->cr_ngroups)
return 1;
for (i=0; i < cr1->cr_ngroups; i++)
if (cr1->cr_groups[i] != cr2->cr_groups[i])
return 1;
return (0);
}
int
hang_options_setup(struct expdir *xd, int opt_flags, struct xucred *cr, struct grouplist *grp,
struct nfs_sec *secflavs, int *need_export)
{
struct host *ht;
*need_export = 0;
if (!grp) {
if (xd->xd_flags & OP_DEFEXP) {
if ((OP_EXOPTS(xd->xd_flags) == OP_EXOPTS(opt_flags)) &&
(!(opt_flags & (OP_MAPALL|OP_MAPROOT)) || !crcmp(&xd->xd_cred, cr)) &&
(!cmp_secflavs(&xd->xd_sec, secflavs))) {
if (!(xd->xd_flags & OP_DEL))
log(LOG_WARNING, "duplicate default export for %s", xd->xd_dir);
xd->xd_flags &= ~OP_EXOPTMASK;
xd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD;
return (0);
} else if (!(xd->xd_flags & OP_DEL)) {
log(LOG_ERR, "multiple/conflicting default exports for %s", xd->xd_dir);
return (EEXIST);
}
}
xd->xd_flags &= ~OP_EXOPTMASK;
xd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD;
if (cr)
xd->xd_cred = *cr;
bcopy(secflavs, &xd->xd_sec, sizeof(struct nfs_sec));
DEBUG(3, "hang_options_setup: %s default 0x%x", xd->xd_dir, xd->xd_flags);
*need_export = 1;
return (0);
}
while (grp) {
ht = find_group_match_in_host_list(&xd->xd_hosts, grp);
if (ht) {
if ((OP_EXOPTS(ht->ht_flags) == OP_EXOPTS(opt_flags)) &&
(!(opt_flags & (OP_MAPALL|OP_MAPROOT)) || !crcmp(&ht->ht_cred, cr)) &&
(!cmp_secflavs(&ht->ht_sec, secflavs))) {
if (!(ht->ht_flags & OP_ADD) && !(grp->gr_flags & GF_SHOW))
ht->ht_flags &= ~OP_SHOW;
ht->ht_flags |= OP_ADD;
if (grp->gr_flags & GF_SHOW)
ht->ht_flags |= OP_SHOW;
grp = grp->gr_next;
continue;
}
if (!(ht->ht_flags & OP_DEL)) {
log(LOG_ERR, "conflicting exports for %s, %s", xd->xd_dir, grp_name(grp));
return (EEXIST);
}
}
ht = find_group_address_match_in_host_list(&xd->xd_hosts, grp);
if (ht) {
if ((OP_EXOPTS(ht->ht_flags) != OP_EXOPTS(opt_flags)) ||
((opt_flags & (OP_MAPALL|OP_MAPROOT)) && crcmp(&ht->ht_cred, cr)) ||
(cmp_secflavs(&ht->ht_sec, secflavs))) {
log(LOG_ERR, "conflicting exports for %s, %s", xd->xd_dir, grp_name(grp));
return (EEXIST);
}
log(LOG_WARNING, "duplicate export for %s, %s vs. %s", xd->xd_dir,
grp_name(grp), grp_name(ht->ht_grp));
grp = grp->gr_next;
continue;
}
ht = get_host();
if (!ht) {
log(LOG_ERR, "Can't allocate memory for host: %s", grp_name(grp));
return(ENOMEM);
}
ht->ht_flags = opt_flags | OP_ADD;
ht->ht_cred = *cr;
ht->ht_grp = grp;
grp->gr_refcnt++;
bcopy(secflavs, &ht->ht_sec, sizeof(struct nfs_sec));
if (grp->gr_flags & GF_SHOW)
ht->ht_flags |= OP_SHOW;
if (config.verbose >= 6)
DEBUG(4, "grp2host: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp));
if (!add_host(&xd->xd_hosts, ht)) {
log(LOG_ERR, "duplicate host in export list: %s", grp_name(grp));
free_host(ht);
return (EEXIST);
}
*need_export = 1;
DEBUG(3, "hang_options_setup: %s %s 0x%x", xd->xd_dir, grp_name(grp), opt_flags);
grp = grp->gr_next;
}
return (0);
}
void
hang_options_finalize(struct expdir *xd)
{
struct host *ht;
struct expdir *mxd;
if (xd->xd_flags & OP_ADD) {
xd->xd_iflags |= (xd->xd_flags & (OP_OFFLINE|OP_MISSING)) ? OP_OFFLINE : OP_ONLINE;
merge_secflavs(&xd->xd_ssec, &xd->xd_sec);
xd->xd_flags &= ~(OP_ADD|OP_DEL);
xd->xd_oflags = xd->xd_flags;
xd->xd_ocred = xd->xd_cred;
bcopy(&xd->xd_sec, &xd->xd_osec, sizeof(struct nfs_sec));
}
TAILQ_FOREACH(ht, &xd->xd_hosts, ht_next) {
if (!(ht->ht_flags & OP_ADD))
continue;
ht->ht_flags &= ~(OP_ADD|OP_DEL);
xd->xd_iflags |= (ht->ht_flags & (OP_OFFLINE|OP_MISSING)) ? OP_OFFLINE : OP_ONLINE;
merge_secflavs(&xd->xd_ssec, &ht->ht_sec);
}
TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) {
hang_options_finalize(mxd);
}
}
void
hang_options_cleanup(struct expdir *xd)
{
struct host *ht, *htnext;
if (xd->xd_flags & OP_ADD) {
xd->xd_flags = xd->xd_oflags;
xd->xd_cred = xd->xd_ocred;
bcopy(&xd->xd_osec, &xd->xd_sec, sizeof(struct nfs_sec));
}
TAILQ_FOREACH_SAFE(ht, &xd->xd_hosts, ht_next, htnext) {
if (!(ht->ht_flags & OP_ADD))
continue;
if (ht->ht_flags & OP_DEL) {
ht->ht_flags &= ~OP_ADD;
continue;
}
TAILQ_REMOVE(&xd->xd_hosts, ht, ht_next);
free_host(ht);
}
}
int
hang_options_mountdir(struct expdir *xd, char *dir, int opt_flags, struct grouplist *grp, struct nfs_sec *secflavs)
{
struct host *ht;
struct expdir *mxd, *mxd2, *mxd3;
int cmp;
mxd = mxd3 = NULL;
TAILQ_FOREACH(mxd2, &xd->xd_mountdirs, xd_next) {
cmp = strcmp(dir, mxd2->xd_dir);
if (!cmp) {
mxd = mxd2;
break;
} else if (cmp < 0) {
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) {
if (mxd->xd_flags & OP_DEFEXP) {
if ((OP_EXOPTS(mxd->xd_flags) == OP_EXOPTS(opt_flags)) &&
(!cmp_secflavs(&mxd->xd_sec, secflavs))) {
if (!(mxd->xd_flags & OP_DEL))
log(LOG_WARNING, "duplicate default export for %s", mxd->xd_dir);
mxd->xd_flags &= ~OP_EXOPTMASK;
mxd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD;
return (0);
} else if (!(mxd->xd_flags & OP_DEL)) {
log(LOG_ERR, "multiple/conflicting default exports for %s", mxd->xd_dir);
return (EEXIST);
}
}
mxd->xd_flags &= ~OP_EXOPTMASK;
mxd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD;
bcopy(secflavs, &mxd->xd_sec, sizeof(struct nfs_sec));
DEBUG(3, "hang_options_mountdir: %s default 0x%x",
mxd->xd_dir, mxd->xd_flags);
return (0);
}
while (grp) {
ht = find_group_match_in_host_list(&mxd->xd_hosts, grp);
if (ht) {
if ((OP_EXOPTS(ht->ht_flags) == OP_EXOPTS(opt_flags)) &&
(!cmp_secflavs(&ht->ht_sec, secflavs))) {
ht->ht_flags |= OP_ADD;
grp = grp->gr_next;
continue;
}
if (!(ht->ht_flags & OP_DEL)) {
log(LOG_ERR, "conflicting mountdir exports for %s, %s",
mxd->xd_dir, grp_name(grp));
return (EEXIST);
}
}
ht = find_group_address_match_in_host_list(&mxd->xd_hosts, grp);
if (ht) {
if ((OP_EXOPTS(ht->ht_flags) != OP_EXOPTS(opt_flags)) ||
(cmp_secflavs(&ht->ht_sec, secflavs))) {
log(LOG_ERR, "conflicting mountdir exports for %s, %s",
mxd->xd_dir, grp_name(grp));
return (EEXIST);
}
log(LOG_WARNING, "duplicate mountdir export for %s, %s vs. %s",
mxd->xd_dir, grp_name(grp), grp_name(ht->ht_grp));
grp = grp->gr_next;
continue;
}
ht = get_host();
if (!ht) {
log(LOG_ERR, "Can't allocate memory for host: %s", grp_name(grp));
return(ENOMEM);
}
ht->ht_flags = opt_flags | OP_ADD;
ht->ht_grp = grp;
grp->gr_refcnt++;
bcopy(secflavs, &ht->ht_sec, sizeof(struct nfs_sec));
if (grp->gr_flags & GF_SHOW)
ht->ht_flags |= OP_SHOW;
if (config.verbose >= 6)
DEBUG(4, "grp2host: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp));
if (!add_host(&mxd->xd_hosts, ht)) {
log(LOG_ERR, "Can't add host to mountdir export list: %s", grp_name(grp));
free_host(ht);
return (EEXIST);
}
DEBUG(3, "hang_options_mountdir: %s %s 0x%x",
mxd->xd_dir, grp_name(grp), opt_flags);
grp = grp->gr_next;
}
return (0);
}
int
expdir_search(struct expfs *xf, char *dirpath, struct sockaddr *sa, int *options, struct nfs_sec *secflavs)
{
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) {
DEBUG(1, "expdir_search: no matching export: %s", dirpath);
return (0);
}
DEBUG(1, "expdir_search: %s -> %s", dirpath, xd->xd_dir);
if (cmp == 0) {
check_xd_hosts:
hp = find_host(&xd->xd_hosts, sa);
if (hp && (!chkalldirs || (hp->ht_flags & OP_ALLDIRS))) {
DEBUG(2, "expdir_search: %s host %s", dirpath,
(chkalldirs ? "alldirs" : "match"));
*options = hp->ht_flags;
bcopy(&hp->ht_sec, secflavs, sizeof(struct nfs_sec));
} else if ((xd->xd_flags & OP_DEFEXP) &&
(!chkalldirs || (xd->xd_flags & OP_ALLDIRS))) {
DEBUG(2, "expdir_search: %s defexp %s",
dirpath, (chkalldirs ? "alldirs" : "match"));
*options = xd->xd_flags;
bcopy(&xd->xd_sec, secflavs, sizeof(struct nfs_sec));
} else {
*options = 0;
DEBUG(2, "expdir_search: %s NO match", dirpath);
return (0);
}
return (1);
}
TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) {
cmp = subdir_check(mxd->xd_dir, dirpath);
if (cmp < 0)
continue;
DEBUG(1, "expdir_search: %s subdir path match %s",
dirpath, mxd->xd_dir);
chkalldirs = (cmp != 0);
hp = find_host(&mxd->xd_hosts, sa);
if (hp && (!chkalldirs || (hp->ht_flags & OP_ALLDIRS))) {
DEBUG(2, "expdir_search: %s -> %s subdir host %s",
dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match"));
*options = hp->ht_flags;
bcopy(&hp->ht_sec, secflavs, sizeof(struct nfs_sec));
return (1);
} else if ((mxd->xd_flags & OP_DEFEXP) &&
(!chkalldirs || (mxd->xd_flags & OP_ALLDIRS))) {
DEBUG(2, "expdir_search: %s -> %s subdir defexp %s",
dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match"));
*options = mxd->xd_flags;
bcopy(&mxd->xd_sec, secflavs, sizeof(struct nfs_sec));
return (1);
}
}
DEBUG(1, "expdir_search: %s NO match, check alldirs", dirpath);
chkalldirs = 1;
goto check_xd_hosts;
}
struct host *
find_host(struct hosttqh *head, struct sockaddr *sa)
{
struct host *hp;
struct grouplist *grp;
struct addrinfo *ai;
int i;
TAILQ_FOREACH(hp, head, ht_next) {
grp = hp->ht_grp;
switch (grp->gr_type) {
case GT_HOST:
for (ai = grp->gr_u.gt_hostinfo.h_ailist; ai; ai = ai->ai_next) {
if (ai->ai_family != sa->sa_family)
continue;
if (ai->ai_addrlen != sa->sa_len)
continue;
if (ai->ai_family == AF_INET) {
struct sockaddr_in *sin1, *sin2;
sin1 = (struct sockaddr_in*)ai->ai_addr;
sin2 = (struct sockaddr_in*)sa;
if (!bcmp(&sin1->sin_addr, &sin2->sin_addr, sizeof(sin1->sin_addr)))
return (hp);
} else if (ai->ai_family == AF_INET6) {
struct sockaddr_in6 *sin1, *sin2;
sin1 = (struct sockaddr_in6*)ai->ai_addr;
sin2 = (struct sockaddr_in6*)sa;
if (!bcmp(&sin1->sin6_addr, &sin2->sin6_addr, sizeof(sin1->sin6_addr)))
return (hp);
}
}
break;
case GT_NET:
if (grp->gr_u.gt_net.nt_family != sa->sa_family)
break;
if (grp->gr_u.gt_net.nt_family == AF_INET) {
in_addr_t ina = ((struct sockaddr_in*)sa)->sin_addr.s_addr;
if ((ina & grp->gr_u.gt_net.nt_mask) == grp->gr_u.gt_net.nt_net)
return (hp);
} else if (grp->gr_u.gt_net.nt_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
struct in6_addr ina;
for (i=0; i < (int)sizeof(ina.s6_addr); i++)
ina.s6_addr[i] = sa6->sin6_addr.s6_addr[i] & grp->gr_u.gt_net.nt_mask6.s6_addr[i];
if (!bcmp(&ina, &grp->gr_u.gt_net.nt_net6, sizeof(ina)))
return (hp);
}
break;
}
}
return (NULL);
}
int
do_opt( char **cpp,
char **endcpp,
struct grouplist *ngrp,
int *hostcountp,
int *opt_flagsp,
int *exflagsp,
struct xucred *cr,
struct nfs_sec *sec_flavs,
char *fspath,
u_char *fsuuid)
{
char *cpoptarg = NULL, *cpoptend = NULL;
char *cp, *endcp, *cpopt, *cpu, savedc, savedc2 = '\0', savedc3 = '\0';
int mapallflag, usedarg;
int i, rv = 0;
size_t len;
cpopt = *cpp;
cpopt++;
cp = *endcpp;
savedc = *cp;
*cp = '\0';
while (cpopt && *cpopt) {
mapallflag = 1;
usedarg = -2;
if (NULL != (cpoptend = strchr(cpopt, ','))) {
*cpoptend++ = '\0';
if (NULL != (cpoptarg = strchr(cpopt, '='))) {
savedc3 = *cpoptarg;
*cpoptarg++ = '\0';
}
} else {
if (NULL != (cpoptarg = strchr(cpopt, '='))) {
savedc3 = *cpoptarg;
*cpoptarg++ = '\0';
} else {
*cp = savedc;
nextfield(&cp, &endcp);
**endcpp = '\0';
if (endcp > cp && *cp != '-') {
cpoptarg = cp;
savedc2 = *endcp;
*endcp = '\0';
usedarg = 0;
}
}
}
if (!strcmp(cpopt, "ro") || !strcmp(cpopt, "o")) {
*exflagsp |= NX_READONLY;
*opt_flagsp |= OP_READONLY;
} else if (cpoptarg && (!strcmp(cpopt, "maproot") ||
!(mapallflag = strcmp(cpopt, "mapall")) ||
!strcmp(cpopt, "root") || !strcmp(cpopt, "r"))) {
usedarg++;
rv = parsecred(cpoptarg, cr);
if (rv) {
log(LOG_ERR, "map credential error");
goto out;
} else if (mapallflag == 0) {
*exflagsp |= NX_MAPALL;
*opt_flagsp |= OP_MAPALL;
} else {
*exflagsp |= NX_MAPROOT;
*opt_flagsp |= OP_MAPROOT;
}
} else if (cpoptarg && (!strcmp(cpopt, "mask") ||
!strcmp(cpopt, "m"))) {
if (*opt_flagsp & OP_MASK) {
log(LOG_ERR, "Network option conflict");
rv = 1;
goto out;
}
if (get_net(cpoptarg, &ngrp->gr_u.gt_net, 1)) {
log(LOG_ERR, "Bad mask: %s", cpoptarg);
rv = 1;
goto out;
}
usedarg++;
*opt_flagsp |= OP_MASK;
} else if (cpoptarg && (!strcmp(cpopt, "network") ||
!strcmp(cpopt, "n"))) {
if (*opt_flagsp & OP_NET) {
log(LOG_ERR, "Network option conflict");
rv = 1;
goto out;
}
if (get_net(cpoptarg, &ngrp->gr_u.gt_net, 0)) {
log(LOG_ERR, "Bad net: %s", cpoptarg);
rv = 1;
goto out;
}
ngrp->gr_type = GT_NET;
*hostcountp = *hostcountp + 1;
usedarg++;
*opt_flagsp |= OP_NET;
} else if (cpoptarg && (!strcmp(cpopt, "sec"))) {
if (*opt_flagsp & OP_SECFLAV)
log(LOG_WARNING, "A security option was already specified and will be replaced.");
if (get_sec_flavors(cpoptarg, sec_flavs)) {
log(LOG_ERR, "Bad security option: %s", cpoptarg);
rv = 1;
goto out;
}
usedarg++;
*opt_flagsp |= OP_SECFLAV;
} else if (!strcmp(cpopt, "alldirs")) {
*opt_flagsp |= OP_ALLDIRS;
} else if (!strcmp(cpopt, "32bitclients")) {
*exflagsp |= NX_32BITCLIENTS;
*opt_flagsp |= OP_32BITCLIENTS;
} else if (!strcmp(cpopt, "manglednames")) {
*exflagsp |= NX_MANGLEDNAMES;
*opt_flagsp |= OP_MANGLEDNAMES;
} else if (!strcmp(cpopt, "fspath")) {
if (!cpoptarg) {
log(LOG_WARNING, "export option '%s' missing a value.", cpopt);
} else if (cpoptarg[0] != '/') {
log(LOG_ERR, "invalid fspath: %s", cpoptarg);
rv = 1;
goto out;
} else {
len = strlcpy(fspath, cpoptarg, MAXPATHLEN);
if (len >= MAXPATHLEN) {
log(LOG_ERR, "%s option path too long: %s", cpopt, cpoptarg);
rv = 1;
goto out;
}
*opt_flagsp |= OP_FSPATH;
}
} else if (!strcmp(cpopt, "fsuuid")) {
if (!cpoptarg) {
log(LOG_WARNING, "export option '%s' missing a value.", cpopt);
} else {
cpu = cpoptarg;
for (i=0; i < 16; i++, cpu+=2) {
if (*cpu == '-')
cpu++;
if (!isxdigit(*cpu) || !isxdigit(*(cpu+1))) {
log(LOG_ERR, "invalid fsuuid: %s", cpoptarg);
rv = 1;
goto out;
}
fsuuid[i] = HEXSTRTOI(cpu);
}
*opt_flagsp |= OP_FSUUID;
}
} else if (!strcmp(cpopt, "offline")) {
*exflagsp |= NX_OFFLINE;
*opt_flagsp |= OP_OFFLINE;
} else {
log(LOG_WARNING, "unrecognized export option: %s", cpopt);
goto out;
}
if (usedarg >= 0) {
*endcp = savedc2;
**endcpp = savedc;
if (usedarg > 0) {
*cpp = cp;
*endcpp = endcp;
}
return (0);
}
if (cpoptend)
*(cpoptend-1) = ',';
if (savedc3) {
*(cpoptarg-1) = savedc3;
savedc3 = '\0';
}
cpopt = cpoptend;
}
out:
if (savedc2)
*endcp = savedc2;
if (cpoptend)
*(cpoptend-1) = ',';
if (savedc3)
*(cpoptarg-1) = savedc3;
**endcpp = savedc;
return (rv);
}
int
get_host_addresses(char *cp, struct grouplist *grp)
{
struct addrinfo *ailist, aihints;
if (grp->gr_type != GT_NULL)
return (1);
bzero(&aihints, sizeof(aihints));
aihints.ai_socktype = SOCK_STREAM;
if (getaddrinfo(cp, NULL, &aihints, &ailist)) {
log(LOG_ERR, "getaddrinfo() failed for %s", cp);
hostnamecount++;
return (1);
}
hostnamecount++;
hostnamegoodcount++;
grp->gr_type = GT_HOST;
grp->gr_u.gt_hostinfo.h_ailist = ailist;
grp->gr_u.gt_hostinfo.h_name = strdup(cp);
return (0);
}
void
free_expdir(struct expdir *xd)
{
struct expdir *xd2;
free_hosts(&xd->xd_hosts);
while ((xd2 = TAILQ_FIRST(&xd->xd_mountdirs))) {
TAILQ_REMOVE(&xd->xd_mountdirs, xd2, xd_next);
free_expdir(xd2);
}
if (xd->xd_dir)
free(xd->xd_dir);
free(xd);
}
void
free_expfs(struct expfs *xf)
{
struct expdir *xd;
while ((xd = TAILQ_FIRST(&xf->xf_dirl))) {
TAILQ_REMOVE(&xf->xf_dirl, xd, xd_next);
free_expdir(xd);
}
if (xf->xf_fsdir)
free(xf->xf_fsdir);
free(xf);
}
void
free_host(struct host *hp)
{
if (hp->ht_grp)
free_grp(hp->ht_grp);
free(hp);
}
void
free_hosts(struct hosttqh *head)
{
struct host *hp, *hp2;
TAILQ_FOREACH_SAFE(hp, head, ht_next, hp2) {
TAILQ_REMOVE(head, hp, ht_next);
free_host(hp);
}
}
struct host *
get_host(void)
{
struct host *hp;
hp = malloc(sizeof(struct host));
if (hp == NULL)
return (NULL);
hp->ht_flags = 0;
return (hp);
}
int
do_export(
int expcmd,
struct expfs *xf,
struct expdir *xd,
struct grouplist *grplist,
int exflags,
struct xucred *cr,
struct nfs_sec *secflavs)
{
struct nfs_export_args nxa;
struct nfs_export_net_args *netargs, *na;
struct grouplist *grp;
struct addrinfo *ai;
struct sockaddr_in *sin, *imask;
struct sockaddr_in6 *sin6, *imask6;
uint32_t net;
nxa.nxa_flags = expcmd;
if ((exflags & NX_OFFLINE) && (expcmd != NXA_CHECK))
nxa.nxa_flags |= NXA_OFFLINE;
nxa.nxa_fsid = xf->xf_fsid;
nxa.nxa_fspath = xf->xf_fsdir;
nxa.nxa_exppath = xd->xd_xid->xid_path;
nxa.nxa_expid = xd->xd_xid->xid_id;
nxa.nxa_netcount = (!grplist ? 1 : 0);
grp = grplist;
while (grp) {
if (grp->gr_type == GT_HOST) {
for (ai = grp->gr_u.gt_hostinfo.h_ailist; ai; ai = ai->ai_next)
if ((ai->ai_family == AF_INET) || (ai->ai_family == AF_INET6))
nxa.nxa_netcount++;
} else if (grp->gr_type == GT_NET) {
nxa.nxa_netcount++;
}
grp = grp->gr_next;
}
netargs = malloc(nxa.nxa_netcount * sizeof(struct nfs_export_net_args));
if (!netargs) {
log(LOG_ERR, "do_export(): malloc failed for %d net args", nxa.nxa_netcount);
return (1);
}
nxa.nxa_nets = netargs;
#define INIT_NETARG(N, F) \
do { \
(N)->nxna_flags = exflags; \
(N)->nxna_cred = cr ? *cr : def_anon; \
memset(&(N)->nxna_sec, 0, sizeof(struct nfs_sec)); \
if (secflavs != NULL) \
bcopy(secflavs, &(N)->nxna_sec, sizeof(struct nfs_sec)); \
if ((F) == AF_INET) { \
sin = (struct sockaddr_in*)&(N)->nxna_addr; \
imask = (struct sockaddr_in*)&(N)->nxna_mask; \
memset(sin, 0, sizeof(*sin)); \
memset(imask, 0, sizeof(*imask)); \
sin->sin_family = AF_INET; \
sin->sin_len = sizeof(*sin); \
imask->sin_family = AF_INET; \
imask->sin_len = sizeof(*imask); \
} else if ((F) == AF_INET6) { \
sin6 = (struct sockaddr_in6*)&(N)->nxna_addr; \
imask6 = (struct sockaddr_in6*)&(N)->nxna_mask; \
memset(sin6, 0, sizeof(*sin6)); \
memset(imask6, 0, sizeof(*imask6)); \
sin6->sin6_family = AF_INET6; \
sin6->sin6_len = sizeof(*sin6); \
imask6->sin6_family = AF_INET6; \
imask6->sin6_len = sizeof(*imask6); \
} \
} while (0)
na = netargs;
if (!grplist) {
INIT_NETARG(na, AF_INET);
sin->sin_len = 0;
imask->sin_len = 0;
na++;
}
grp = grplist;
while (grp) {
switch (grp->gr_type) {
case GT_HOST:
for (ai = grp->gr_u.gt_hostinfo.h_ailist; ai; ai = ai->ai_next) {
if ((ai->ai_family != AF_INET) && (ai->ai_family != AF_INET6))
continue;
INIT_NETARG(na, ai->ai_family);
if (ai->ai_family == AF_INET) {
sin->sin_addr.s_addr = ((struct sockaddr_in*)ai->ai_addr)->sin_addr.s_addr;
imask->sin_len = 0;
} else if (ai->ai_family == AF_INET6) {
bcopy(&((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, &sin6->sin6_addr, sizeof(sin6->sin6_addr));
imask6->sin6_len = 0;
}
na++;
}
break;
case GT_NET:
INIT_NETARG(na, grp->gr_u.gt_net.nt_family);
if (grp->gr_u.gt_net.nt_family == AF_INET) {
if (grp->gr_u.gt_net.nt_mask)
imask->sin_addr.s_addr = grp->gr_u.gt_net.nt_mask;
else {
net = ntohl(grp->gr_u.gt_net.nt_net);
if (IN_CLASSA(net))
imask->sin_addr.s_addr = inet_addr("255.0.0.0");
else if (IN_CLASSB(net))
imask->sin_addr.s_addr =
inet_addr("255.255.0.0");
else
imask->sin_addr.s_addr =
inet_addr("255.255.255.0");
grp->gr_u.gt_net.nt_mask = imask->sin_addr.s_addr;
}
sin->sin_addr.s_addr = grp->gr_u.gt_net.nt_net;
na++;
} else if (grp->gr_u.gt_net.nt_family == AF_INET6) {
bcopy(&grp->gr_u.gt_net.nt_net6, &sin6->sin6_addr, sizeof(sin6->sin6_addr));
bcopy(&grp->gr_u.gt_net.nt_mask6, &imask6->sin6_addr, sizeof(imask6->sin6_addr));
na++;
}
break;
case GT_NETGROUP:
break;
default:
log(LOG_ERR, "Bad grouptype");
free(netargs);
return (1);
}
grp = grp->gr_next;
}
if (nfssvc(NFSSVC_EXPORT, &nxa)) {
if ((expcmd != NXA_CHECK) && (expcmd != NXA_DELETE) && (errno == EPERM)) {
log(LOG_ERR, "Can't change attributes for %s. See 'exports' man page.",
xd->xd_dir);
free(netargs);
return (1);
}
log(LOG_ERR, "Can't %sexport %s: %s (%d)",
(expcmd == NXA_DELETE) ? "un" : "",
xd->xd_dir, strerror(errno), errno);
free(netargs);
return (1);
}
free(netargs);
return (0);
}
int
get_net(char *cp, struct netmsk *net, int maskflg)
{
struct netent *np;
uint32_t netaddr;
struct in_addr inetaddr, inetaddr2;
struct in6_addr inet6addr;
const char *name;
sa_family_t family;
char addrbuf[2*INET6_ADDRSTRLEN];
if (NULL != (np = getnetbyname(cp))) {
inetaddr = inet_makeaddr(np->n_net, 0);
family = AF_INET;
} else if (inet_pton(AF_INET6, cp, &inet6addr) == 1) {
family = AF_INET6;
} else if (isdigit(*cp)) {
if ((netaddr = inet_network(cp)) == INADDR_NONE)
return (1);
inetaddr = inet_makeaddr(netaddr, 0);
family = AF_INET;
if (!maskflg) {
setnetent(0);
while (NULL != (np = getnetent())) {
inetaddr2 = inet_makeaddr(np->n_net, 0);
if (inetaddr2.s_addr == inetaddr.s_addr)
break;
}
endnetent();
}
} else {
return (1);
}
if (maskflg) {
if (net->nt_family == AF_UNSPEC) {
net->nt_family = family;
} else if (net->nt_family != family) {
log(LOG_ERR, "net mask family (%d) does not match net address family (%d): %s", family, net->nt_family, cp);
return (1);
}
if (family == AF_INET6)
net->nt_mask6 = inet6addr;
else
net->nt_mask = inetaddr.s_addr;
} else {
if (net->nt_family == AF_UNSPEC) {
net->nt_family = family;
} else if (net->nt_family != family) {
log(LOG_ERR, "net mask family (%d) does not match net address family (%d): %s", net->nt_family, family, cp);
return (1);
}
if (np) {
name = np->n_name;
} else if (family == AF_INET6) {
name = inet_ntop(AF_INET6, &inet6addr, addrbuf, sizeof(addrbuf));
if (!name) {
log(LOG_ERR, "can't convert IPv6 addr to name: %s", cp);
return (1);
}
} else {
name = inet_ntoa(inetaddr);
}
net->nt_name = malloc(strlen(name) + 1);
if (net->nt_name == NULL) {
log(LOG_ERR, "can't allocate memory for net: %s", cp);
return (1);
}
strlcpy(net->nt_name, name, strlen(name)+1);
if (family == AF_INET6)
net->nt_net6 = inet6addr;
else
net->nt_net = inetaddr.s_addr;
DEBUG(3, "got net: %s", net->nt_name);
}
return (0);
}
int
get_sec_flavors(char *flavorlist, struct nfs_sec *sec_flavs)
{
char *flavorlistcopy;
char *flavor;
u_int32_t flav_bits;
#define SYS_BIT 0x00000001
#define KRB5_BIT 0x00000002
#define KRB5I_BIT 0x00000004
#define KRB5P_BIT 0x00000008
flavorlistcopy = strdup(flavorlist);
if (flavorlistcopy)
flavorlist = flavorlistcopy;
bzero(sec_flavs, sizeof(struct nfs_sec));
flav_bits = 0;
while ( ((flavor = strsep(&flavorlist, ":")) != NULL) && (sec_flavs->count < NX_MAX_SEC_FLAVORS)) {
if (flavor[0] == '\0')
continue;
if (!strcmp("krb5p", flavor)) {
if (flav_bits & KRB5P_BIT) {
log(LOG_WARNING, "krb5p appears more than once: %s", flavorlist);
continue;
}
flav_bits |= KRB5P_BIT;
sec_flavs->flavors[sec_flavs->count++] = RPCAUTH_KRB5P;
} else if (!strcmp("krb5i", flavor)) {
if (flav_bits & KRB5I_BIT) {
log(LOG_WARNING, "krb5i appears more than once: %s", flavorlist);
continue;
}
flav_bits |= KRB5I_BIT;
sec_flavs->flavors[sec_flavs->count++] = RPCAUTH_KRB5I;
} else if (!strcmp("krb5", flavor)) {
if (flav_bits & KRB5_BIT) {
log(LOG_WARNING, "krb5 appears more than once: %s", flavorlist);
continue;
}
flav_bits |= KRB5_BIT;
sec_flavs->flavors[sec_flavs->count++] = RPCAUTH_KRB5;
} else if (!strcmp("sys", flavor)) {
if (flav_bits & SYS_BIT) {
log(LOG_WARNING, "Security mechanism 'sys' appears more than once: %s", flavorlist);
continue;
}
flav_bits |= SYS_BIT;
sec_flavs->flavors[sec_flavs->count++] = RPCAUTH_SYS;
} else {
log(LOG_ERR, "Unknown security mechanism '%s'. See the exports(5) man page.", flavor);
bzero(sec_flavs, sizeof(struct nfs_sec));
break;
}
}
if (flavorlistcopy)
free(flavorlistcopy);
if (sec_flavs->count)
return 0;
else
return 1;
}
int
cmp_secflavs(struct nfs_sec *sf1, struct nfs_sec *sf2)
{
int32_t i;
if (sf1->count != sf2->count)
return 1;
for (i = 0; i < sf1->count; i++)
if (sf1->flavors[i] != sf2->flavors[i])
return 1;
return 0;
}
void
merge_secflavs(struct nfs_sec *cur, struct nfs_sec *new)
{
int32_t i, j;
for (i = 0; i < new->count; i++) {
for (j = 0; j < cur->count; j++)
if (new->flavors[i] == cur->flavors[j])
break;
if (j < cur->count)
continue;
if (cur->count < NX_MAX_SEC_FLAVORS) {
cur->flavors[j] = new->flavors[i];
cur->count++;
}
}
}
void
nextfield(char **line_start, char **line_end)
{
char *a, q;
u_int32_t esc;
if (line_start == NULL)
return;
a = *line_start;
while (*a == ' ' || *a == '\t')
a++;
*line_start = a;
if (*a == '\n' || *a == '\0') {
*line_end = a;
return;
}
if (*a == '\'' || *a == '"') {
q = *a;
a++;
for (esc = 0; *a != '\0'; a++) {
if (esc)
esc = 0;
else if (*a == '\\')
esc = 1;
else if (*a == q || *a == '\n')
break;
}
if (*a == q)
a++;
*line_end = a;
return;
}
for (;; a++) {
if (*a == '\0' || *a == '\n')
break;
else if (*a == '\\') {
a++;
if (*a == '\n' || *a == '\0')
break;
} else if (*a == ' ' || *a == '\t')
break;
}
*line_end = a;
}
int
get_export_entry(void)
{
char *p, *cp, *newline;
size_t len, totlen;
int cont_line;
if (linenum == 0)
linenum = 1;
else
linenum += entrylines;
entrylines = 1;
totlen = 0;
do {
if ((p = fgetln(exp_file, &len)) == NULL)
return (0);
cp = p + len - 1;
cont_line = 0;
while (cp >= p &&
(*cp == ' ' || *cp == '\t' || *cp == '\n' ||
*cp == '\\')) {
if (*cp == '\\')
cont_line = 1;
cp--;
len--;
}
if (linesize < (totlen + len + 1)) {
newline = realloc(line, (totlen + len + 1));
if (!newline) {
log(LOG_ERR, "Exports line too long, can't allocate memory");
return (0);
}
line = newline;
linesize = (totlen + len + 1);
}
memcpy(line + totlen, p, len);
totlen += len;
line[totlen] = '\0';
if (cont_line) {
entrylines++;
} else if (totlen == 0) {
linenum += entrylines;
entrylines = 1;
}
} while (totlen == 0 || cont_line);
return (1);
}
int
parsecred(char *namelist, struct xucred *cr)
{
char *namelistcopy;
char *name;
int cnt;
char *names;
struct passwd *pw;
struct group *gr;
int ngroups, groups[NGROUPS];
namelistcopy = strdup(namelist);
if (namelistcopy)
namelist = namelistcopy;
cr->cr_version = XUCRED_VERSION;
cr->cr_uid = -2;
cr->cr_groups[0] = -2;
cr->cr_ngroups = 1;
names = strsep(&namelist, " \t\n");
name = strsep(&names, ":");
if (isdigit(*name) || *name == '-')
pw = getpwuid(atoi(name));
else
pw = getpwnam(name);
if (names == NULL) {
if (pw == NULL) {
log(LOG_ERR, "Unknown user: %s", name);
if (namelistcopy)
free(namelistcopy);
return (ENOENT);
}
cr->cr_uid = pw->pw_uid;
ngroups = NGROUPS;
if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups))
log(LOG_NOTICE, "Too many groups for %s", pw->pw_name);
cr->cr_ngroups = (ngroups <= NGROUPS) ? ngroups : NGROUPS;
for (cnt = 0; cnt < cr->cr_ngroups; cnt++)
cr->cr_groups[cnt] = groups[cnt];
if (namelistcopy)
free(namelistcopy);
goto out;
}
if (pw != NULL)
cr->cr_uid = pw->pw_uid;
else if (isdigit(*name) || *name == '-')
cr->cr_uid = atoi(name);
else {
log(LOG_ERR, "Unknown user: %s", name);
if (namelistcopy)
free(namelistcopy);
return (ENOENT);
}
cr->cr_ngroups = 0;
while (names != NULL && *names != '\0' && cr->cr_ngroups < NGROUPS) {
name = strsep(&names, ":");
if (isdigit(*name) || *name == '-') {
cr->cr_groups[cr->cr_ngroups++] = atoi(name);
} else {
if ((gr = getgrnam(name)) == NULL) {
log(LOG_ERR, "Unknown group: %s", name);
continue;
}
cr->cr_groups[cr->cr_ngroups++] = gr->gr_gid;
}
}
if (names != NULL && *names != '\0' && cr->cr_ngroups == NGROUPS)
log(LOG_ERR, "Too many groups in %s", namelist);
if (namelistcopy)
free(namelistcopy);
out:
if (config.verbose >= 5) {
char buf[256];
snprintf_cred(buf, sizeof(buf), cr);
DEBUG(3, "got cred: %s", buf);
}
if (cr->cr_ngroups < 1) {
log(LOG_ERR, "no groups found: %s", namelist);
return (EINVAL);
}
return (0);
}
#define STRSIZ (RPCMNT_NAMELEN+RPCMNT_PATHLEN+50)
void
get_mountlist(void)
{
struct mountlist *mlp, *lastmlp;
char *host, *dir, *cp;
char str[STRSIZ];
FILE *mlfile;
int hlen, dlen;
if ((mlfile = fopen(_PATH_RMOUNTLIST, "r")) == NULL) {
if (errno != ENOENT)
log(LOG_ERR, "Can't open %s: %s (%d)",
_PATH_RMOUNTLIST, strerror(errno), errno);
else
DEBUG(1, "Can't open %s: %s (%d)",
_PATH_RMOUNTLIST, strerror(errno), errno);
return;
}
lastmlp = NULL;
while (fgets(str, STRSIZ, mlfile) != NULL) {
cp = str;
host = strsep(&cp, " \t\n");
dir = strsep(&cp, " \t\n");
if ((host == NULL) || (dir == NULL))
continue;
hlen = strlen(host);
if (hlen > RPCMNT_NAMELEN)
hlen = RPCMNT_NAMELEN;
dlen = strlen(dir);
if (dlen > RPCMNT_PATHLEN)
dlen = RPCMNT_PATHLEN;
mlp = malloc(sizeof(*mlp));
if (mlp) {
mlp->ml_host = malloc(hlen+1);
mlp->ml_dir = malloc(dlen+1);
}
if (!mlp || !mlp->ml_host || !mlp->ml_dir) {
log(LOG_ERR, "can't allocate memory while reading in mount list: %s %s",
host, dir);
if (mlp) {
if (mlp->ml_host)
free(mlp->ml_host);
if (mlp->ml_dir)
free(mlp->ml_dir);
free(mlp);
}
break;
}
strncpy(mlp->ml_host, host, hlen);
mlp->ml_host[hlen] = '\0';
strncpy(mlp->ml_dir, dir, dlen);
mlp->ml_dir[dlen] = '\0';
mlp->ml_next = NULL;
if (lastmlp)
lastmlp->ml_next = mlp;
else
mlhead = mlp;
lastmlp = mlp;
}
fclose(mlfile);
}
void
del_mlist(char *host, char *dir)
{
struct mountlist *mlp, **mlpp;
struct mountlist *mlp2;
FILE *mlfile;
int fnd = 0;
mlpp = &mlhead;
mlp = mlhead;
while (mlp) {
if (!strcmp(mlp->ml_host, host) &&
(!dir || !strcmp(mlp->ml_dir, dir))) {
fnd = 1;
mlp2 = mlp;
*mlpp = mlp = mlp->ml_next;
free(mlp2->ml_host);
free(mlp2->ml_dir);
free(mlp2);
} else {
mlpp = &mlp->ml_next;
mlp = mlp->ml_next;
}
}
if (fnd) {
if ((mlfile = fopen(_PATH_RMOUNTLIST, "w")) == NULL) {
log(LOG_ERR, "Can't write %s: %s (%d)",
_PATH_RMOUNTLIST, strerror(errno), errno);
return;
}
mlp = mlhead;
while (mlp) {
fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dir);
mlp = mlp->ml_next;
}
fclose(mlfile);
}
}
void
add_mlist(char *host, char *dir)
{
struct mountlist *mlp, **mlpp;
FILE *mlfile;
int hlen, dlen;
mlpp = &mlhead;
mlp = mlhead;
while (mlp) {
if (!strcmp(mlp->ml_host, host) && !strcmp(mlp->ml_dir, dir))
return;
mlpp = &mlp->ml_next;
mlp = mlp->ml_next;
}
hlen = strlen(host);
if (hlen > RPCMNT_NAMELEN)
hlen = RPCMNT_NAMELEN;
dlen = strlen(dir);
if (dlen > RPCMNT_PATHLEN)
dlen = RPCMNT_PATHLEN;
mlp = malloc(sizeof(*mlp));
if (mlp) {
mlp->ml_host = malloc(hlen+1);
mlp->ml_dir = malloc(dlen+1);
}
if (!mlp || !mlp->ml_host || !mlp->ml_dir) {
if (mlp) {
if (mlp->ml_host)
free(mlp->ml_host);
if (mlp->ml_dir)
free(mlp->ml_dir);
free(mlp);
}
log(LOG_ERR, "can't allocate memory to add to mount list: %s %s", host, dir);
return;
}
strncpy(mlp->ml_host, host, hlen);
mlp->ml_host[hlen] = '\0';
strncpy(mlp->ml_dir, dir, dlen);
mlp->ml_dir[dlen] = '\0';
mlp->ml_next = NULL;
*mlpp = mlp;
if ((mlfile = fopen(_PATH_RMOUNTLIST, "a")) == NULL) {
log(LOG_ERR, "Can't append %s: %s (%d)",
_PATH_RMOUNTLIST, strerror(errno), errno);
return;
}
fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dir);
fclose(mlfile);
}
int
check_options(int opt_flags)
{
if ((opt_flags & (OP_MAPROOT|OP_MAPALL)) == (OP_MAPROOT|OP_MAPALL)) {
log(LOG_ERR, "-mapall and -maproot mutually exclusive");
return (1);
}
if ((opt_flags & OP_MASK) && (opt_flags & OP_NET) == 0) {
log(LOG_ERR, "-mask requires -net");
return (1);
}
return (0);
}
int
check_dirpath(char *dir)
{
char *cp;
int ret = 1;
struct stat sb;
for (cp = dir + 1; *cp && ret; cp++)
if (*cp == '/') {
*cp = '\0';
if ((lstat(dir, &sb) < 0) || !S_ISDIR(sb.st_mode))
ret = 0;
*cp = '/';
}
if (ret && ((lstat(dir, &sb) < 0) || !S_ISDIR(sb.st_mode)))
ret = 0;
return (ret);
}
void
snprintf_cred(char *buf, int buflen, struct xucred *cr)
{
char crbuf2[32];
int i;
buf[0] = '\0';
if (!cr)
return;
snprintf(crbuf2, sizeof(crbuf2), "%d", cr->cr_uid);
strlcat(buf, crbuf2, buflen);
for (i=0; i < cr->cr_ngroups; i++) {
snprintf(crbuf2, sizeof(crbuf2), ":%d", cr->cr_groups[i]);
strlcat(buf, crbuf2, buflen);
}
}
void
snprintf_flags(char *buf, int buflen, int flags, struct xucred *cr)
{
char crbuf[256];
if (flags & (OP_MAPALL|OP_MAPROOT))
snprintf_cred(crbuf, sizeof(crbuf), cr);
else
crbuf[0] = '\0';
snprintf(buf, buflen, "FLAGS: 0x%08x %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s %s",
flags,
(flags & OP_DEL) ? " DEL" : "",
(flags & OP_ADD) ? " ADD" : "",
(flags & OP_DEFEXP) ? " DEFEXP" : "",
(flags & OP_MISSING) ? " MISSING" : "",
(flags & OP_SHOW) ? " SHOW" : "",
(flags & OP_ONLINE) ? " ONLINE" : "",
(flags & OP_OFFLINE) ? " OFFLINE" : "",
(flags & OP_FSUUID) ? " FSUUID" : "",
(flags & OP_FSPATH) ? " FSPATH" : "",
(flags & OP_32BITCLIENTS) ? " 32" : "",
(flags & OP_READONLY) ? " READONLY" : "",
(flags & OP_ALLDIRS) ? " ALLDIRS" : "",
(flags & OP_NET) ? " NET" : "",
(flags & OP_MASK) ? " MASK" : "",
(flags & OP_SECFLAV) ? " SEC" : "",
(flags & OP_MAPALL) ? " MAPALL" : "",
(flags & OP_MAPROOT) ? " MAPROOT" : "",
crbuf);
}
void
dump_expdir(struct expfs *xf, struct expdir *xd, int mdir)
{
struct expdir *mxd;
struct host *ht;
struct grouplist *gr;
char buf[2048];
struct addrinfo *ai;
void *sinaddr;
char addrbuf[2*INET6_ADDRSTRLEN];
const char *s;
snprintf_flags(buf, sizeof(buf), xd->xd_iflags, NULL);
DEBUG(1, " %s %s", xd->xd_dir, buf);
if (!mdir) {
DEBUG(1, " %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path);
DEBUG(1, " XID: 0x%08x", xd->xd_xid->xid_id);
}
snprintf_flags(buf, sizeof(buf), xd->xd_flags, mdir ? NULL : &xd->xd_cred);
DEBUG(1, " %s", buf);
DEBUG(1, " HOSTS:");
TAILQ_FOREACH(ht, &xd->xd_hosts, ht_next) {
snprintf_flags(buf, sizeof(buf), ht->ht_flags, mdir ? NULL : &ht->ht_cred);
DEBUG(1, " * %s", buf);
gr = ht->ht_grp;
if (gr) {
switch(gr->gr_type) {
case GT_NET:
sinaddr = (gr->gr_u.gt_net.nt_family == AF_INET) ?
(void*)&gr->gr_u.gt_net.nt_net : (void*)&gr->gr_u.gt_net.nt_net6;
if (inet_ntop(gr->gr_u.gt_net.nt_family, sinaddr, addrbuf, sizeof(addrbuf)))
s = addrbuf;
else
s = "???";
snprintf(buf, sizeof(buf), "%s %s/", grp_name(gr), s);
sinaddr = (gr->gr_u.gt_net.nt_family == AF_INET) ?
(void*)&gr->gr_u.gt_net.nt_mask : (void*)&gr->gr_u.gt_net.nt_mask6;
if (inet_ntop(gr->gr_u.gt_net.nt_family, sinaddr, addrbuf, sizeof(addrbuf)))
s = addrbuf;
else
s = "???";
strlcat(buf, s, sizeof(buf));
break;
case GT_HOST:
ai = gr->gr_u.gt_hostinfo.h_ailist;
sinaddr = (ai->ai_family == AF_INET) ?
(void*)&((struct sockaddr_in*)ai->ai_addr)->sin_addr :
(void*)&((struct sockaddr_in6*)ai->ai_addr)->sin6_addr;
if (inet_ntop(ai->ai_family, sinaddr, addrbuf, sizeof(addrbuf)))
s = addrbuf;
else
s = "???";
snprintf(buf, sizeof(buf), "%s %s", grp_name(gr), s);
ai = ai->ai_next;
while (ai) {
strlcat(buf, " ", sizeof(buf));
sinaddr = (ai->ai_family == AF_INET) ?
(void*)&((struct sockaddr_in*)ai->ai_addr)->sin_addr :
(void*)&((struct sockaddr_in6*)ai->ai_addr)->sin6_addr;
if (inet_ntop(ai->ai_family, sinaddr, addrbuf, sizeof(addrbuf)))
s = addrbuf;
else
s = "???";
if (strlcat(buf, s, sizeof(buf)) > 2000) {
strlcat(buf, " ...", sizeof(buf));
break;
}
ai = ai->ai_next;
}
break;
default:
snprintf(buf, sizeof(buf), "%s", grp_name(gr));
break;
}
DEBUG(1, " %s", buf);
}
}
if (mdir)
return;
DEBUG(1, " MOUNTDIRS:");
TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) {
dump_expdir(xf, mxd, 1);
}
}
void
dump_exports(void)
{
struct expfs *xf;
struct expdir *xd;
char buf[64];
if (!config.verbose)
return;
TAILQ_FOREACH(xf, &xfshead, xf_next) {
DEBUG(1, "** %s %s (0x%08x)", xf->xf_fsdir,
uuidstring(xf->xf_uuid, buf), UUID2FSID(xf->xf_uuid));
TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) {
dump_expdir(xf, xd, 0);
}
}
}
static struct statfs *sfs[2];
static int size[2], cnt[2], cur, lastfscnt;
#define PREV ((cur + 1) & 1)
static int
sfscmp(const void *arg1, const void *arg2)
{
const struct statfs *sfs1 = arg1;
const struct statfs *sfs2 = arg2;
return strcmp(sfs1->f_mntonname, sfs2->f_mntonname);
}
static void
get_mounts(void)
{
cur = (cur + 1) % 2;
while (size[cur] < (lastfscnt = getfsstat(sfs[cur], size[cur] * sizeof(struct statfs), MNT_NOWAIT))) {
free(sfs[cur]);
size[cur] = lastfscnt + 32;
sfs[cur] = malloc(size[cur] * sizeof(struct statfs));
if (!sfs[cur])
err(1, "no memory");
}
cnt[cur] = lastfscnt;
qsort(sfs[cur], cnt[cur], sizeof(struct statfs), sfscmp);
}
static int
check_xpaths(char *path)
{
struct dirlist *dirl = xpaths;
while (dirl) {
if (subdir_check(path, dirl->dl_dir) >= 0) {
DEBUG(1, "check_for_mount_changes: %s %s\n", path, dirl->dl_dir);
return (1);
}
dirl = dirl->dl_next;
}
return (0);
}
int
check_for_mount_changes(void)
{
int i, j, cmp, gotmount = 0;
#define RETURN_IF_DONE do { if (gotmount && (config.verbose < 3)) return (gotmount); } while (0)
get_mounts();
if (!xpaths_complete) {
DEBUG(1, "check_for_mount_changes: xpaths not complete\n");
return (1);
}
for (i=j=0; (i < cnt[PREV]) && (j < cnt[cur]); ) {
cmp = sfscmp(&sfs[PREV][i], &sfs[cur][j]);
if (!cmp) {
i++;
j++;
continue;
}
if (cmp < 0) {
DEBUG(1, "- %s\n", sfs[PREV][i].f_mntonname);
gotmount |= check_xpaths(sfs[PREV][i].f_mntonname);
RETURN_IF_DONE;
i++;
}
if (cmp > 0) {
DEBUG(1, "+ %s\n", sfs[cur][j].f_mntonname);
gotmount |= check_xpaths(sfs[cur][j].f_mntonname);
RETURN_IF_DONE;
j++;
}
}
while (i < cnt[PREV]) {
DEBUG(1, "- %s\n", sfs[PREV][i].f_mntonname);
gotmount |= check_xpaths(sfs[PREV][i].f_mntonname);
RETURN_IF_DONE;
i++;
}
while (j < cnt[cur]) {
DEBUG(1, "+ %s\n", sfs[cur][j].f_mntonname);
gotmount |= check_xpaths(sfs[cur][j].f_mntonname);
RETURN_IF_DONE;
j++;
}
return (gotmount);
}