#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <notify.h>
#include <errno.h>
#include <err.h>
#include <pthread.h>
#include <spawn.h>
#include <dns_sd.h>
#include <mach/mach.h>
#include <mach/host_special_ports.h>
#include <sys/syslog.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <libutil.h>
#include <launch.h>
#include <netinet/in.h>
#include <oncrpc/rpc.h>
#include <oncrpc/rpcb.h>
#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <CoreFoundation/CoreFoundation.h>
#include <ServiceManagement/ServiceManagement.h>
#include <ServiceManagement/ServiceManagement_Private.h>
#include "lockd_mach.h"
#include "pathnames.h"
#include "common.h"
#define GETOPT "F:Nn:P:p:Rrtuv"
#define MAX_NFSD_THREADS 128
const struct nfs_conf_server config_defaults =
{
0,
1,
0,
64,
1,
0,
0,
1,
8,
NFS_PORT,
64,
128,
0,
1,
1,
1,
0,
1000,
0,
};
pthread_attr_t pattr;
struct nfs_conf_server config;
char exportsfilepath[MAXPATHLEN];
volatile int gothup, gotterm;
int checkexports = 0, log_to_stderr = 0;
int nfsudpport = 0, nfstcpport = 0;
int nfsudp6port = 0, nfstcp6port = 0;
int mountudpport = 0, mounttcpport = 0;
int mountudp6port = 0, mounttcp6port = 0;
time_t recheckexports_until = 0;
int recheckexports = 0;
DNSServiceRef nfs_dns_service;
static int config_read(struct nfs_conf_server *);
static void config_sanity_check(struct nfs_conf_server *conf);
static void config_sysctl_changed(struct nfs_conf_server *, struct nfs_conf_server *);
static void config_loop(void);
static pid_t get_pid(const char *);
static pid_t get_nfsd_pid(void);
static void signal_nfsd(int);
static void sigmux(int);
static int service_is_enabled(CFStringRef);
static int service_is_loaded(CFStringRef);
static int nfsd_is_enabled(void);
static int nfsd_is_loaded(void);
static int nfsd_is_running(void);
static int nfsd_enable(void);
static int nfsd_disable(void);
static int nfsd_load(void);
static int nfsd_unload(void);
static int nfsd_start(void);
static int nfsd_stop(void);
static void register_services(void);
static int safe_exec(char *const*, int);
static void do_lockd_ping(void);
static void do_lockd_shutdown(void);
static int rquotad_start(void);
static int rquotad_stop(void);
static void
usage(void)
{
fprintf(stderr, "usage: nfsd [-NRrtuv] [-F export_file] [-n num_servers] "
"[-p nfsport] [-P mountport] [command]\n");
fprintf(stderr, "commands: enable, disable, start, stop, restart, update, status, checkexports, verbose [up|down]\n");
exit(1);
}
int
main(int argc, char *argv[], __unused char *envp[])
{
struct pidfh *nfsd_pfh, *mountd_pfh;
pid_t pid;
struct stat st;
int ch, reregister, rv;
int tcpflag, udpflag, protocnt;
int nfsdcnt, nfsport, mountport;
int mount_require_resv_port = 1;
int mount_regular_files = 0;
extern int optind;
config = config_defaults;
config_read(&config);
reregister = 0;
nfsdcnt = 0;
protocnt = tcpflag = udpflag = 0;
nfsport = mountport = 0;
exportsfilepath[0] = '\0';
while ((ch = getopt(argc, argv, GETOPT)) != EOF)
switch (ch) {
case 'n':
nfsdcnt = atoi(optarg);
break;
case 'p':
nfsport = atoi(optarg);
break;
case 'r':
reregister = 1;
break;
case 't':
tcpflag = 1;
protocnt++;
break;
case 'u':
udpflag = 1;
protocnt++;
break;
case 'F':
strlcpy(exportsfilepath, optarg, MAXPATHLEN);
break;
case 'N':
mount_require_resv_port = 0;
break;
case 'P':
mountport = atoi(optarg);
break;
case 'R':
mount_regular_files = 1;
break;
case 'v':
config.verbose++;
break;
default:
case '?':
usage();
};
argv += optind;
argc -= optind;
if (nfsdcnt)
config.nfsd_threads = nfsdcnt;
if (protocnt) {
config.tcp = tcpflag;
config.udp = udpflag;
}
if (nfsport)
config.port = nfsport;
if (mountport)
config.mount_port = mountport;
if (!mount_require_resv_port)
config.mount_require_resv_port = mount_require_resv_port;
if (mount_regular_files)
config.mount_regular_files = mount_regular_files;
if (!exportsfilepath[0])
strlcpy(exportsfilepath, _PATH_EXPORTS, sizeof(exportsfilepath));
if (reregister || (argc > 0))
log_to_stderr = 1;
if (reregister) {
signal_nfsd(SIGHUP);
exit(0);
}
rv = 0;
if (argc > 0) {
if (!strcmp(argv[0], "status")) {
int enabled, loaded;
enabled = nfsd_is_enabled();
printf("nfsd service is %s\n", enabled ? "enabled" : "disabled");
if (config.verbose) {
loaded = nfsd_is_loaded();
printf("nfsd service is %s\n", loaded ? "loaded" : "not loaded");
}
pid = get_nfsd_pid();
if (pid <= 0) {
printf("nfsd is not running\n");
} else {
int cur = 0;
sysctl_get("vfs.generic.nfs.server.nfsd_thread_count", &cur);
printf("nfsd is running (pid %d, %d threads)\n", pid, cur);
}
rv = enabled ? 0 : 1;
if (config.verbose) {
enabled = service_is_enabled(CFSTR(_LOCKD_SERVICE_LABEL));
printf("lockd service is %s\n", enabled ? "enabled" : "disabled");
loaded = service_is_loaded(CFSTR(_LOCKD_SERVICE_LABEL));
printf("lockd service is %s\n", loaded ? "loaded" : "not loaded");
pid = get_pid(_PATH_LOCKD_PID);
if (pid <= 0)
printf("lockd is not running\n");
else
printf("lockd is running (pid %d)\n", pid);
enabled = service_is_enabled(CFSTR(_STATD_NOTIFY_SERVICE_LABEL));
printf("statd.notify service is %s\n", enabled ? "enabled" : "disabled");
loaded = service_is_loaded(CFSTR(_STATD_NOTIFY_SERVICE_LABEL));
printf("statd.notify service is %s\n", loaded ? "loaded" : "not loaded");
pid = get_pid(_PATH_STATD_NOTIFY_PID);
if (pid <= 0)
printf("statd.notify is not running\n");
else
printf("statd.notify is running (pid %d)\n", pid);
loaded = service_is_loaded(CFSTR(_STATD_SERVICE_LABEL));
printf("statd service is %s\n", loaded ? "loaded" : "not loaded");
pid = get_pid(_PATH_STATD_PID);
if (pid <= 0)
printf("statd is not running\n");
else
printf("statd is running (pid %d)\n", pid);
loaded = service_is_loaded(CFSTR(_RQUOTAD_SERVICE_LABEL));
printf("rquotad service is %s\n", loaded ? "loaded" : "not loaded");
pid = get_pid(_PATH_RQUOTAD_PID);
if (pid <= 0)
printf("rquotad is not running\n");
else
printf("rquotad is running (pid %d)\n", pid);
}
exit(rv);
} else if (!strcmp(argv[0], "checkexports")) {
checkexports = 1;
mountd_init();
rv = get_exportlist();
exit(rv);
}
}
if (getuid()) {
printf("Sorry, nfsd must be run as root\n");
printf("unprivileged usage: nfsd [ status | [-F file] checkexports]\n");
if (nfsd_is_loaded())
nfsd_unload();
exit(2);
}
if (argc > 0) {
if (!strcmp(argv[0], "enable")) {
if (!nfsd_is_enabled()) {
rv = nfsd_enable();
} else {
printf("The nfsd service is already enabled.\n");
if (!nfsd_is_running())
rv = nfsd_is_loaded() ? nfsd_start() : nfsd_load();
}
} else if (!strcmp(argv[0], "disable")) {
if (nfsd_is_enabled()) {
rv = nfsd_disable();
} else {
printf("The nfsd service is already disabled.\n");
if (nfsd_is_loaded())
rv = nfsd_unload();
}
} else if (!strcmp(argv[0], "start")) {
if (nfsd_is_running()) {
printf("The nfsd service is already running.\n");
} else {
printf("Starting the nfsd service%s\n",
nfsd_is_enabled() ? "" : " (use 'enable' to make permanent)");
rv = nfsd_is_loaded() ? nfsd_start() : nfsd_load();
}
} else if (!strcmp(argv[0], "stop")) {
if (!nfsd_is_running()) {
printf("The nfsd service is not running.\n");
} else {
printf("Stopping the nfsd service%s\n",
!nfsd_is_enabled() ? "" : " (use 'disable' to make permanent)");
rv = nfsd_unload();
}
} else if (!strcmp(argv[0], "restart")) {
if (!nfsd_is_running() || !nfsd_is_loaded())
printf("The nfsd service does not appear to be running.\n");
if (nfsd_is_running()) {
rv = nfsd_stop();
} else {
printf("Starting the nfsd service%s\n",
nfsd_is_enabled() ? "" : " (use 'enable' to permanently enable)");
rv = nfsd_is_loaded() ? nfsd_start() : nfsd_load();
}
} else if (!strcmp(argv[0], "update")) {
signal_nfsd(SIGHUP);
} else if (!strcmp(argv[0], "verbose")) {
argc--;
argv++;
for (;argc;argc--,argv++) {
if (!strcmp(argv[0], "up"))
signal_nfsd(SIGUSR1);
else if (!strcmp(argv[0], "down"))
signal_nfsd(SIGUSR2);
else
errx(1, "unknown verbose command: %s", argv[0]);
usleep(100000);
}
} else {
warnx("unknown command: %s", argv[0]);
usage();
}
exit(rv);
}
pthread_attr_init(&pattr);
pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED);
signal(SIGQUIT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSYS, sigmux);
signal(SIGTERM, sigmux);
signal(SIGHUP, sigmux);
signal(SIGUSR1, sigmux);
signal(SIGUSR2, sigmux);
openlog(NULL, LOG_PID, LOG_DAEMON);
setlogmask(LOG_UPTO(LOG_LEVEL));
config_sanity_check(&config);
if (stat(exportsfilepath, &st)) {
log(LOG_WARNING, "no exports file, unloading nfsd service");
rv = nfsd_unload();
exit(rv);
}
nfsd_pfh = pidfile_open(_PATH_NFSD_PID, 0644, &pid);
if (nfsd_pfh == NULL) {
log(LOG_ERR, "can't open nfsd pidfile: %s (%d)", strerror(errno), errno);
if ((errno == EACCES) && getuid())
log(LOG_ERR, "nfsd is expected to be run as root, not as uid %d.", getuid());
else if (errno == EEXIST)
log(LOG_ERR, "nfsd already running, pid: %d", pid);
exit(2);
}
if (pidfile_write(nfsd_pfh) == -1)
log(LOG_WARNING, "can't write to nfsd pidfile: %s (%d)", strerror(errno), errno);
mountd_pfh = pidfile_open(_PATH_MOUNTD_PID, 0644, &pid);
if (mountd_pfh == NULL) {
log(LOG_ERR, "can't open mountd pidfile: %s (%d)", strerror(errno), errno);
if (errno == EEXIST)
log(LOG_ERR, "mountd already running, pid: %d", pid);
exit(2);
}
if (pidfile_write(mountd_pfh) == -1)
log(LOG_WARNING, "can't write to mountd pidfile: %s (%d)", strerror(errno), errno);
log(LOG_NOTICE, "nfsd starting");
if (config.verbose)
log(LOG_NOTICE, "verbose level set to %d", config.verbose);
config_sysctl_changed(NULL, &config);
mountd();
nfsd();
do_lockd_ping();
rquotad_start();
register_services();
config_loop();
sysctl_set("vfs.generic.nfs.server.nfsd_thread_max", 0);
do_lockd_shutdown();
rquotad_stop();
alarm(1);
rpcb_unset(NULL, RPCPROG_NFS, 2);
rpcb_unset(NULL, RPCPROG_NFS, 3);
rpcb_unset(NULL, RPCPROG_MNT, 1);
rpcb_unset(NULL, RPCPROG_MNT, 3);
if (nfs_dns_service)
DNSServiceRefDeallocate(nfs_dns_service);
pidfile_remove(mountd_pfh);
pidfile_remove(nfsd_pfh);
exit(0);
}
static int
config_read(struct nfs_conf_server *conf)
{
FILE *f;
size_t len, linenum = 0;
char *line, *p, *key, *value;
long val;
if (!(f = fopen(_PATH_NFS_CONF, "r"))) {
if (errno != ENOENT)
log(LOG_WARNING, "%s", _PATH_NFS_CONF);
return (1);
}
for (;(line = fparseln(f, &len, &linenum, NULL, 0)); free(line)) {
if (len <= 0)
continue;
p = line + len - 1;
while ((p > line) && isspace(*p))
*p-- = '\0';
key = line;
while (isspace(*key))
key++;
value = p = strchr(line, '=');
if (p)
do { *p-- = '\0'; } while ((p > line) && isspace(*p));
if (value)
do { value++; } while (isspace(*value));
if (strncmp(key, "nfs.server.", 11)) {
DEBUG(3, "%4ld %s=%s", linenum, key, value ? value : "");
continue;
}
val = !value ? 1 : strtol(value, NULL, 0);
DEBUG(2, "%4ld %s=%s (%d)", linenum, key, value ? value : "", val);
if (!strcmp(key, "nfs.server.async")) {
conf->async = val;
} else if (!strcmp(key, "nfs.server.bonjour")) {
conf->bonjour = val;
} else if (!strcmp(key, "nfs.server.bonjour.local_domain_only")) {
conf->bonjour_local_domain_only = val;
} else if (!strcmp(key, "nfs.server.export_hash_size")) {
if (value && val)
conf->export_hash_size = val;
} else if (!strcmp(key, "nfs.server.fsevents")) {
conf->fsevents = val;
} else if (!strcmp(key, "nfs.server.mount.port")) {
if (value && val)
conf->mount_port = val;
} else if (!strcmp(key, "nfs.server.mount.regular_files")) {
conf->mount_regular_files = val;
} else if (!strcmp(key, "nfs.server.mount.require_resv_port")) {
conf->mount_require_resv_port = val;
} else if (!strcmp(key, "nfs.server.nfsd_threads")) {
if (value && val)
conf->nfsd_threads = val;
} else if (!strcmp(key, "nfs.server.port")) {
if (value && val)
conf->port = val;
} else if (!strcmp(key, "nfs.server.reqcache_size")) {
if (value && val)
conf->reqcache_size = val;
} else if (!strcmp(key, "nfs.server.request_queue_length")) {
if (value && val)
conf->request_queue_length = val;
} else if (!strcmp(key, "nfs.server.require_resv_port")) {
conf->require_resv_port = val;
} else if (!strcmp(key, "nfs.server.tcp")) {
conf->tcp = val;
} else if (!strcmp(key, "nfs.server.udp")) {
conf->udp = val;
} else if (!strcmp(key, "nfs.server.user_stats")) {
conf->user_stats = val;
} else if (!strcmp(key, "nfs.server.verbose")) {
conf->verbose = val;
} else if (!strcmp(key, "nfs.server.wg_delay")) {
if (value && val)
conf->wg_delay = val;
} else if (!strcmp(key, "nfs.server.wg_delay_v3")) {
if (value && val)
conf->wg_delay_v3 = val;
} else {
DEBUG(1, "ignoring unknown config value: %4ld %s=%s", linenum, key, value ? value : "");
}
}
fclose(f);
return (0);
}
static void
config_sanity_check(struct nfs_conf_server *conf)
{
if (conf->nfsd_threads < 1) {
log(LOG_WARNING, "nfsd thread count %d; reset to %d", conf->nfsd_threads, config_defaults.nfsd_threads);
conf->nfsd_threads = config_defaults.nfsd_threads;
} else if (conf->nfsd_threads > MAX_NFSD_THREADS) {
log(LOG_WARNING, "nfsd thread count %d; limited to %d", conf->nfsd_threads, MAX_NFSD_THREADS);
conf->nfsd_threads = MAX_NFSD_THREADS;
}
if (!config.udp && !config.tcp)
log(LOG_WARNING, "No network transport(s) configured.");
}
static void
config_loop(void)
{
int kq, rv, gotmount = 0, exports_changed;
struct kevent ke;
struct nfs_conf_server newconf;
struct stat st, stnew;
struct timespec ts = { 10, 0 };
if ((kq = kqueue()) < 0) {
log(LOG_ERR, "kqueue: %s (%d)", strerror(errno), errno);
exit(1);
}
EV_SET(&ke, 0, EVFILT_FS, EV_ADD, 0, 0, 0);
rv = kevent(kq, &ke, 1, NULL, 0, NULL);
if (rv < 0) {
log(LOG_ERR, "kevent(EVFILT_FS): %s (%d)", strerror(errno), errno);
exit(1);
}
stat(exportsfilepath, &st);
while (!gotterm) {
DEBUG(1, "config_loop: waiting...");
rv = kevent(kq, NULL, 0, &ke, 1, (recheckexports ? &ts : NULL));
if ((rv > 0) && !(ke.flags & EV_ERROR) && (ke.fflags & (VQ_MOUNT|VQ_UNMOUNT))) {
log(LOG_INFO, "mount list changed: 0x%x", ke.fflags);
gotmount = check_for_mount_changes();
}
if (recheckexports) {
if (!gotmount)
log(LOG_INFO, "rechecking exports");
gotmount = 1;
}
while (!gotterm && (gothup || gotmount)) {
if (gothup) {
DEBUG(1, "handling HUP");
newconf = config_defaults;
if (!config_read(&newconf)) {
config_sanity_check(&newconf);
if ((newconf.port != config.port) || (newconf.mount_port != config.mount_port) ||
(newconf.tcp != config.tcp) || (newconf.udp != config.udp) ||
(newconf.reqcache_size != config.reqcache_size) ||
(newconf.export_hash_size != config.export_hash_size)) {
if (newconf.reqcache_size != config.reqcache_size)
log(LOG_NOTICE, "request cache size change (%d -> %d) requires restart",
config.reqcache_size, newconf.reqcache_size);
if (newconf.export_hash_size != config.export_hash_size)
log(LOG_NOTICE, "export hash size change (%d -> %d) requires restart",
config.export_hash_size, newconf.export_hash_size);
if ((newconf.port != config.port) || (newconf.mount_port != config.mount_port) ||
(newconf.tcp != config.tcp) || (newconf.udp != config.udp))
log(LOG_NOTICE, "port/transport changes require restart");
gotterm = 1;
break;
}
config_sysctl_changed(&config, &newconf);
sysctl_set("vfs.generic.nfs.server.nfsd_thread_max", newconf.nfsd_threads);
if (newconf.nfsd_threads > config.nfsd_threads)
nfsd_start_server_threads(newconf.nfsd_threads - config.nfsd_threads);
config = newconf;
}
do_lockd_ping();
rquotad_start();
register_services();
}
if (stat(exportsfilepath, &stnew)) {
exports_changed = 0;
} else {
exports_changed =
(stnew.st_dev != st.st_dev) || (stnew.st_ino != st.st_ino) ||
(stnew.st_ctimespec.tv_sec != st.st_ctimespec.tv_sec) ||
(stnew.st_ctimespec.tv_nsec != st.st_ctimespec.tv_nsec);
st = stnew;
}
if (exports_changed && clear_export_errors(0))
log(LOG_WARNING, "exports file changed: previous errors cleared");
gotmount = gothup = 0;
get_exportlist();
}
}
}
static void
config_sysctl_changed(struct nfs_conf_server *old, struct nfs_conf_server *new)
{
if (!old || (old->async != new->async))
sysctl_set("vfs.generic.nfs.server.async", new->async);
if (!old || (old->export_hash_size != new->export_hash_size))
sysctl_set("vfs.generic.nfs.server.export_hash_size", new->export_hash_size);
if (!old || (old->fsevents != new->fsevents))
sysctl_set("vfs.generic.nfs.server.fsevents", new->fsevents);
if (!old)
sysctl_set("vfs.generic.nfs.server.reqcache_size", new->reqcache_size);
if (!old || (old->request_queue_length != new->request_queue_length))
sysctl_set("vfs.generic.nfs.server.request_queue_length", new->request_queue_length);
if (!old || (old->require_resv_port != new->require_resv_port))
sysctl_set("vfs.generic.nfs.server.require_resv_port", new->require_resv_port);
if (!old || (old->user_stats != new->user_stats))
sysctl_set("vfs.generic.nfs.server.user_stats", new->user_stats);
if (!old || (old->wg_delay != new->wg_delay))
sysctl_set("vfs.generic.nfs.server.wg_delay", new->wg_delay);
if (!old || (old->wg_delay_v3 != new->wg_delay_v3))
sysctl_set("vfs.generic.nfs.server.wg_delay_v3", new->wg_delay_v3);
}
int
sysctl_get(const char *name, int *val)
{
size_t size = sizeof(int);
return sysctlbyname(name, val, &size, NULL, 0);
}
int
sysctl_set(const char *name, int val)
{
int rv = sysctlbyname(name, NULL, 0, &val, sizeof(val));
DEBUG(1, "sysctl_set: %s = %d, rv %d", name, val, rv);
return (rv);
}
void
set_thread_sigmask(void)
{
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGQUIT);
sigaddset(&sigset, SIGSYS);
sigaddset(&sigset, SIGPIPE);
sigaddset(&sigset, SIGTERM);
sigaddset(&sigset, SIGHUP);
sigaddset(&sigset, SIGUSR1);
sigaddset(&sigset, SIGUSR2);
sigaddset(&sigset, SIGABRT);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
}
static void
sigmux(int sig)
{
switch (sig) {
case SIGHUP:
gothup = 1;
log(LOG_NOTICE, "SIGHUP\n");
break;
case SIGSYS:
log(LOG_ERR, "missing system call: NFS not available.");
case SIGTERM:
gotterm = 1;
break;
case SIGUSR1:
config.verbose++;
setlogmask(LOG_UPTO(LOG_LEVEL));
log(LOG_WARNING, "verbose level is now %d\n", config.verbose);
break;
case SIGUSR2:
if (config.verbose > 0)
config.verbose--;
setlogmask(LOG_UPTO(LOG_LEVEL));
log(LOG_WARNING, "verbose level is now %d\n", config.verbose);
break;
}
}
#ifndef HOST_LOCKD_PORT
#define HOST_LOCKD_PORT (5 + HOST_MAX_SPECIAL_KERNEL_PORT)
#define host_get_lockd_port(host, port) \
(host_get_special_port((host), \
HOST_LOCAL_NODE, HOST_LOCKD_PORT, (port)))
#endif
static void
do_lockd_ping(void)
{
kern_return_t kr;
mach_port_t mp;
kr = host_get_lockd_port(mach_host_self(), &mp);
if (kr != KERN_SUCCESS || !MACH_PORT_VALID(mp)) {
log(LOG_ERR, "Can't get lockd mach port!");
return;
}
kr = lockd_ping(mp);
mach_port_destroy(mach_task_self(), mp);
if (kr != KERN_SUCCESS) {
log(LOG_ERR, "Lockd did not start!");
return;
}
return;
}
static void
do_lockd_shutdown(void)
{
kern_return_t kr;
mach_port_t mp;
kr = host_get_lockd_port(mach_host_self(), &mp);
if (kr != KERN_SUCCESS || !MACH_PORT_VALID(mp)) {
log(LOG_ERR, "Can't get lockd mach port!");
return;
}
kr = lockd_shutdown(mp);
mach_port_destroy(mach_task_self(), mp);
if (kr != KERN_SUCCESS) {
log(LOG_ERR, "lockd shutdown failed!");
return;
}
return;
}
static void
register_services(void)
{
struct sockaddr_storage ss, ss6;
struct sockaddr *sa = (struct sockaddr*)&ss;
struct sockaddr *sa6 = (struct sockaddr*)&ss6;
struct sockaddr_in *sin = (struct sockaddr_in*)&ss;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&ss6;
int errcnt;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_len = sizeof(*sin);
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
sin6->sin6_len = sizeof(*sin6);
rpcb_unset(NULL, RPCPROG_NFS, 2);
rpcb_unset(NULL, RPCPROG_NFS, 3);
if (config.udp) {
errcnt = 0;
if (nfsudpport) {
sin->sin_port = htons(nfsudpport);
if (!rpcb_set("udp", RPCPROG_NFS, 2, sa))
errcnt++;
if (!rpcb_set("udp", RPCPROG_NFS, 3, sa))
errcnt++;
}
if (nfsudp6port) {
sin6->sin6_port = htons(nfsudp6port);
if (!rpcb_set("udp6", RPCPROG_NFS, 2, sa6))
errcnt++;
if (!rpcb_set("udp6", RPCPROG_NFS, 3, sa6))
errcnt++;
}
if (errcnt)
log(LOG_ERR, "couldn't register NFS/UDP service.");
}
if (config.tcp) {
errcnt = 0;
if (nfstcpport) {
sin->sin_port = htons(nfstcpport);
if (!rpcb_set("tcp", RPCPROG_NFS, 2, sa))
errcnt++;
if (!rpcb_set("tcp", RPCPROG_NFS, 3, sa))
errcnt++;
}
if (nfstcp6port) {
sin6->sin6_port = htons(nfstcp6port);
if (!rpcb_set("tcp6", RPCPROG_NFS, 2, sa6))
errcnt++;
if (!rpcb_set("tcp6", RPCPROG_NFS, 3, sa6))
errcnt++;
}
if (errcnt)
log(LOG_ERR, "couldn't register NFS/TCP service.");
}
rpcb_unset(NULL, RPCPROG_MNT, 1);
rpcb_unset(NULL, RPCPROG_MNT, 3);
if (config.udp) {
errcnt = 0;
if (mountudpport) {
sin->sin_port = htons(mountudpport);
if (!rpcb_set("udp", RPCPROG_MNT, 1, sa))
errcnt++;
if (!rpcb_set("udp", RPCPROG_MNT, 3, sa))
errcnt++;
}
if (mountudp6port) {
sin6->sin6_port = htons(mountudp6port);
if (!rpcb_set("udp6", RPCPROG_MNT, 1, sa6))
errcnt++;
if (!rpcb_set("udp6", RPCPROG_MNT, 3, sa6))
errcnt++;
}
if (errcnt)
log(LOG_ERR, "couldn't register MOUNT/UDP service.");
}
if (config.tcp) {
errcnt = 0;
if (mounttcpport) {
sin->sin_port = htons(mounttcpport);
if (!rpcb_set("tcp", RPCPROG_MNT, 1, sa))
errcnt++;
if (!rpcb_set("tcp", RPCPROG_MNT, 3, sa))
errcnt++;
}
if (mounttcp6port) {
sin6->sin6_port = htons(mounttcp6port);
if (!rpcb_set("tcp6", RPCPROG_MNT, 1, sa6))
errcnt++;
if (!rpcb_set("tcp6", RPCPROG_MNT, 3, sa6))
errcnt++;
}
if (errcnt)
log(LOG_ERR, "couldn't register MOUNT/TCP service.");
}
if (nfs_dns_service) {
DNSServiceRefDeallocate(nfs_dns_service);
nfs_dns_service = NULL;
}
if (config.bonjour && (config.tcp || config.udp)) {
DNSServiceErrorType dserr;
dserr = DNSServiceRegister(&nfs_dns_service, 0, 0, NULL,
config.tcp ? "_nfs._tcp" : "_nfs._udp",
config.bonjour_local_domain_only ? "local" : NULL,
NULL, htons(config.port), 0, NULL, NULL, NULL);
if (dserr != kDNSServiceErr_NoError) {
log(LOG_ERR, "DNSServiceRegister(_nfs._tcp) failed with %d\n", dserr);
nfs_dns_service = NULL;
}
}
}
void
SYSLOG(int pri, const char *fmt, ...)
{
va_list ap;
if (pri > LOG_LEVEL)
return;
va_start(ap, fmt);
if (log_to_stderr) {
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
fflush(stderr);
} else {
vsyslog(pri, fmt, ap);
}
va_end(ap);
}
static pid_t
get_pid(const char *path)
{
char pidbuf[128], *pidend;
int fd, len, rv;
pid_t pid;
struct flock lock;
if ((fd = open(path, O_RDONLY)) < 0) {
DEBUG(5, "%s: %s (%d)", path, strerror(errno), errno);
return (0);
}
len = sizeof(pidbuf) - 1;
if ((len = read(fd, pidbuf, len)) < 0) {
DEBUG(1, "%s: %s (%d)", path, strerror(errno), errno);
return (0);
}
pidbuf[len] = '\0';
pid = strtol(pidbuf, &pidend, 10);
if (!len || (pid < 1)) {
DEBUG(1, "%s: bogus pid: %s", path, pidbuf);
return (0);
}
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
rv = fcntl(fd, F_GETLK, &lock);
close(fd);
if (rv != 0) {
DEBUG(1, "%s: fcntl: %s (%d)", path, strerror(errno), errno);
return (0);
} else if (lock.l_type == F_UNLCK) {
DEBUG(1, "%s: not locked", path);
return (0);
}
return (pid);
}
static pid_t
get_nfsd_pid(void)
{
return (get_pid(_PATH_NFSD_PID));
}
static void
signal_nfsd(int signal)
{
pid_t pid = get_nfsd_pid();
if (pid <= 0)
errx(1, "nfsd not running?");
if (kill(pid, signal) < 0)
err(1, "kill(%d, %d)", pid, signal);
}
static int
service_is_enabled(CFStringRef service)
{
Boolean loaded = false, persistence = false;
loaded = SMJobIsEnabled(kSMDomainSystemLaunchd, service, &persistence);
return (!(loaded ^ persistence));
}
static int
service_is_loaded(CFStringRef service)
{
Boolean dummy;
return (SMJobIsEnabled(kSMDomainSystemLaunchd, service, &dummy));
}
static int
nfsd_is_enabled(void)
{
return service_is_enabled(CFSTR(_NFSD_SERVICE_LABEL));
}
static int
nfsd_is_loaded(void)
{
return service_is_loaded(CFSTR(_NFSD_SERVICE_LABEL));
}
static int
nfsd_enable(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "load", "-w", _PATH_NFSD_PLIST, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_disable(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "unload", "-w", _PATH_NFSD_PLIST, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_load(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "load", "-F", _PATH_NFSD_PLIST, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_unload(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "unload", _PATH_NFSD_PLIST, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_start(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "start", _NFSD_SERVICE_LABEL, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_stop(void)
{
const char *const args[] = { _PATH_LAUNCHCTL, "stop", _NFSD_SERVICE_LABEL, NULL };
return safe_exec((char *const*)args, 0);
}
static int
nfsd_is_running(void)
{
return (get_nfsd_pid() > 0);
}
static int
safe_exec(char *const argv[], int silent)
{
posix_spawn_file_actions_t psfileact, *psfileactp = NULL;
pid_t pid;
int status;
extern char **environ;
if (silent) {
psfileactp = &psfileact;
if ((status = posix_spawn_file_actions_init(psfileactp))) {
log(LOG_ERR, "spawn init of %s failed: %s (%d)", argv[0], strerror(status), status);
return (1);
}
posix_spawn_file_actions_addopen(psfileactp, STDIN_FILENO, "/dev/null", O_RDONLY, 0);
posix_spawn_file_actions_addopen(psfileactp, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
posix_spawn_file_actions_addopen(psfileactp, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
}
status = posix_spawn(&pid, argv[0], psfileactp, NULL, argv, environ);
if (psfileactp)
posix_spawn_file_actions_destroy(psfileactp);
if (status) {
log(LOG_ERR, "spawn of %s failed: %s (%d)", argv[0], strerror(status), status);
return (1);
}
while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR))
usleep(1000);
if (WIFSIGNALED(status)) {
log(LOG_ERR, "%s aborted by signal %d", argv[0], WTERMSIG(status));
return (1);
} else if (WIFSTOPPED(status)) {
log(LOG_ERR, "%s stopped by signal %d ?", argv[0], WSTOPSIG(status));
return (1);
} else if (WEXITSTATUS(status) && !silent) {
log(LOG_ERR, "%s exited with status %d", argv[0], WEXITSTATUS(status));
}
return (WEXITSTATUS(status));
}
static int
rquotad_is_loaded(void)
{
launch_data_t msg, resp;
int rv = 0;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (0);
launch_data_dict_insert(msg, launch_data_new_string(_RQUOTAD_SERVICE_LABEL), LAUNCH_KEY_GETJOB);
resp = launch_msg(msg);
if (resp) {
if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY)
rv = 1;
launch_data_free(resp);
} else {
syslog(LOG_ERR, "launch_msg(): %m");
}
launch_data_free(msg);
return (rv);
}
static int
rquotad_load(void)
{
launch_data_t msg, job, args, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
job = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
args = launch_data_alloc(LAUNCH_DATA_ARRAY);
if (!msg || !job || !args) {
if (msg) launch_data_free(msg);
if (job) launch_data_free(job);
if (args) launch_data_free(args);
return (1);
}
launch_data_array_set_index(args, launch_data_new_string(_PATH_RQUOTAD), 0);
launch_data_dict_insert(job, launch_data_new_string(_RQUOTAD_SERVICE_LABEL), LAUNCH_JOBKEY_LABEL);
launch_data_dict_insert(job, launch_data_new_bool(FALSE), LAUNCH_JOBKEY_ONDEMAND);
launch_data_dict_insert(job, args, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
launch_data_dict_insert(msg, job, LAUNCH_KEY_SUBMITJOB);
resp = launch_msg(msg);
if (!resp) {
rv = errno;
} else {
if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(resp);
}
launch_data_free(msg);
return (rv);
}
static int
rquotad_service_start(void)
{
launch_data_t msg, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (1);
launch_data_dict_insert(msg, launch_data_new_string(_RQUOTAD_SERVICE_LABEL), LAUNCH_KEY_STARTJOB);
resp = launch_msg(msg);
if (!resp) {
rv = errno;
} else {
if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(resp);
}
launch_data_free(msg);
return (rv);
}
static int
rquotad_start(void)
{
if (get_pid(_PATH_RQUOTAD_PID) > 0)
return (0);
else if (rquotad_is_loaded())
return (rquotad_service_start());
return (rquotad_load());
}
static int
rquotad_stop(void)
{
launch_data_t msg, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (1);
launch_data_dict_insert(msg, launch_data_new_string(_RQUOTAD_SERVICE_LABEL), LAUNCH_KEY_REMOVEJOB);
resp = launch_msg(msg);
if (!resp) {
rv = errno;
} else {
if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(resp);
}
launch_data_free(msg);
return (rv);
}