#pragma ident "@(#)autod_main.c 1.69 05/06/08 SMI"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <sys/resource.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <locale.h>
#include <assert.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "automount.h"
#include "automountd.h"
#include "autofs_protServer.h"
#include <arpa/inet.h>
#include "deflt.h"
#include <strings.h>
#include "sysctl_fsid.h"
#define MAXVAL(a, b) ((a) > (b) ? (a) : (b))
#define AUTOFS_MAX_MSG_SIZE \
MAXVAL(sizeof (union __RequestUnion__autofs_subsystem), \
sizeof (union __ReplyUnion__autofs_subsystem))
static void usage(void);
static void *automount_thread(void *);
static void new_worker_thread(void);
static void compute_new_timeout(struct timespec *);
static void *shutdown_thread(void *);
static void *timeout_thread(void *);
static void *wait_for_flush_indication_thread(void *);
static int do_mount_trigger(autofs_pathname, autofs_pathname,
autofs_pathname, autofs_opts, autofs_pathname, autofs_pathname,
autofs_component, uint32_t, uint32_t, int32_t, int32_t, int32_t,
int32_t *, int32_t *, boolean_t *);
#define CTIME_BUF_LEN 26
#define MAXTHREADS 64
#define SHUTDOWN_TIMEOUT 2
#define TIMEOUT RDDIR_CACHE_TIME
#define APPLE_PREFIX "com.apple."
#define MAXLABEL 256
static pthread_mutex_t numthreads_lock;
static pthread_cond_t numthreads_cv;
static int numthreads;
static pthread_attr_t attr;
static time_t timeout = TIMEOUT;
static int bye = 0;
static sigset_t waitset;
static sigset_t contset;
static mach_port_t service_port_receive_right;
#define RESOURCE_FACTOR 8
struct autodir *dir_head;
struct autodir *dir_tail;
time_t timenow;
int verbose = 0;
int trace = 0;
int automountd_nobrowse = 0;
char *automountd_defopts = NULL;
int
main(argc, argv)
int argc;
char *argv[];
{
int c, error;
int autofs_fd;
kern_return_t ret;
pthread_t thread, timeout_thr, shutdown_thr;
char *defval;
int defflags;
char bname[MAXLABEL] = { APPLE_PREFIX };
char *myname;
setlinebuf(stdout);
setlinebuf(stderr);
myname = strrchr(argv[0], '/');
myname = myname ? myname + 1 : argv[0];
strlcat(bname, myname, sizeof(bname));
if ((defopen(AUTOFSADMIN)) == 0) {
if ((defval = defread("AUTOMOUNTD_VERBOSE=")) != NULL) {
if (strncasecmp("true", defval, 4) == 0)
verbose = TRUE;
else
verbose = FALSE;
}
if ((defval = defread("AUTOMOUNTD_NOBROWSE=")) != NULL) {
if (strncasecmp("true", defval, 4) == 0)
automountd_nobrowse = TRUE;
else
automountd_nobrowse = FALSE;
}
if ((defval = defread("AUTOMOUNTD_TRACE=")) != NULL) {
errno = 0;
trace = (int)strtol(defval, (char **)NULL, 10);
if (errno != 0)
trace = 0;
}
if ((defval = defread("AUTOMOUNTD_ENV=")) != NULL) {
(void) putenv(strdup(defval));
defflags = defcntl(DC_GETFLAGS, 0);
TURNON(defflags, DC_NOREWIND);
defflags = defcntl(DC_SETFLAGS, defflags);
while ((defval = defread("AUTOMOUNTD_ENV=")) != NULL)
(void) putenv(strdup(defval));
(void) defcntl(DC_SETFLAGS, defflags);
}
if ((defval = defread("AUTOMOUNTD_MNTOPTS=")) != NULL
&& *defval != '\0') {
automountd_defopts = strdup(defval);
if (automountd_defopts == NULL) {
syslog(LOG_ERR, "Memory allocation failed: %m");
exit(2);
}
}
defopen(NULL);
}
while ((c = getopt(argc, argv, "vnTo:D:")) != EOF) {
switch (c) {
case 'v':
verbose++;
break;
case 'n':
automountd_nobrowse++;
break;
case 'T':
trace++;
break;
case 'o':
if (automountd_defopts != NULL)
free(automountd_defopts);
automountd_defopts = strdup(optarg);
break;
case 'D':
(void) putenv(optarg);
break;
default:
usage();
}
}
openlog(myname, LOG_PID, LOG_DAEMON);
(void) setlocale(LC_ALL, "");
if (getenv("ARCH") == NULL)
(void) putenv("ARCH=macintosh");
if (getenv("CPU") == NULL) {
#if defined(__ppc__)
(void) putenv("CPU=powerpc");
#elif defined(__i386__) || defined(__x86_64__)
(void) putenv("CPU=i386");
#else
#error "can't determine processor type");
#endif
}
sigemptyset(&waitset);
sigaddset(&waitset, SIGINT);
sigaddset(&waitset, SIGHUP);
contset = waitset;
sigaddset(&waitset, SIGTERM);
pthread_sigmask(SIG_BLOCK, &waitset, NULL);
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
(void) pthread_mutex_init(&numthreads_lock, NULL);
(void) pthread_cond_init(&numthreads_cv, NULL);
(void) pthread_rwlock_init(&cache_lock, NULL);
(void) pthread_rwlock_init(&rddir_cache_lock, NULL);
(void) pthread_mutex_init(&gssd_port_lock, NULL);
#if 0
(void) ns_setup(NULL, NULL);
#endif
autofs_fd = open("/dev/" AUTOFS_DEVICE, O_RDONLY);
if (autofs_fd == -1) {
syslog(LOG_ERR, "Can't open /dev/autofs: %s",
strerror(errno));
exit(2);
}
ret = bootstrap_check_in(bootstrap_port, bname, &service_port_receive_right);
if (ret != BOOTSTRAP_SUCCESS) {
syslog(LOG_ERR, "Could not get receive right for %s: %s (%d)\n",
bname, bootstrap_strerror(ret), ret);
exit(EXIT_FAILURE);
}
error = pthread_create(&shutdown_thr, &attr, shutdown_thread, NULL);
if (error) {
syslog(LOG_ERR, "unable to create shutdown thread: %s", strerror(error));
exit(EXIT_FAILURE);
}
error = pthread_create(&timeout_thr, NULL, timeout_thread, NULL);
if (error) {
syslog(LOG_ERR, "unable to create time out thread: %s", strerror(error));
exit(EXIT_FAILURE);
}
error = pthread_create(&thread, &attr, cache_cleanup, NULL);
if (error) {
syslog(LOG_ERR, "unable to create cache_cleanup thread: %s",
strerror(error));
exit(1);
}
error = pthread_create(&thread, &attr, wait_for_flush_indication_thread,
&autofs_fd);
if (error) {
syslog(LOG_ERR, "unable to create wait-for-flush-indication thread: %s",
strerror(error));
exit(1);
}
new_worker_thread();
pthread_join(timeout_thr, NULL);
pthread_attr_destroy(&attr);
if (ioctl(autofs_fd, AUTOFS_NOTIFYCHANGE, 0) == -1)
pr_msg("AUTOFS_NOTIFYCHANGE failed: %m");
return (EXIT_SUCCESS);
}
static void
usage(void)
{
(void) fprintf(stderr, "Usage: automountd\n"
"\t[-o opts]\t\t(default mount options)\n"
"\t[-T]\t\t(trace requests)\n"
"\t[-v]\t\t(verbose error msgs)\n"
"\t[-D n=s]\t(define env variable)\n");
exit(1);
}
static void *
automount_thread(__unused void *arg)
{
kern_return_t ret;
ret = mach_msg_server_once(autofs_server, AUTOFS_MAX_MSG_SIZE,
service_port_receive_right,
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_SENDER) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
if (ret != KERN_SUCCESS) {
syslog(LOG_ERR, "automounter mach_msg_server_once failed: %s",
mach_error_string(ret));
}
return NULL;
}
static void
new_worker_thread(void)
{
pthread_t thread;
int error;
(void) pthread_mutex_lock(&numthreads_lock);
while (bye == 0 && numthreads >= MAXTHREADS)
(void) pthread_cond_wait(&numthreads_cv, &numthreads_lock);
if (bye)
goto out;
numthreads++;
error = pthread_create(&thread, &attr, automount_thread, NULL);
if (error) {
syslog(LOG_ERR, "unable to create worker thread: %s",
strerror(error));
numthreads--;
}
out:
(void) pthread_mutex_unlock(&numthreads_lock);
}
static void
end_worker_thread(void)
{
(void) pthread_mutex_lock(&numthreads_lock);
numthreads--;
if (numthreads < MAXTHREADS)
pthread_cond_signal(&numthreads_cv);
(void) pthread_mutex_unlock(&numthreads_lock);
}
static void*
shutdown_thread(__unused void *arg)
{
int sig;
do {
sigwait(&waitset, &sig);
switch (sig) {
case SIGHUP:
syslog(LOG_ERR, "SIGHUP received: ignored");
break;
}
} while (sigismember(&contset, sig));
pthread_mutex_lock(&numthreads_lock);
bye = 1;
timeout = SHUTDOWN_TIMEOUT;
pthread_cond_broadcast(&numthreads_cv);
pthread_mutex_unlock(&numthreads_lock);
return (NULL);
}
static void
compute_new_timeout(struct timespec *new)
{
struct timeval current;
gettimeofday(¤t, NULL);
new->tv_sec = current.tv_sec + timeout;
new->tv_nsec = 1000 * current.tv_usec;
}
static void*
timeout_thread(__unused void *arg)
{
int rv = 0;
struct timespec exittime;
(void) pthread_mutex_lock(&numthreads_lock);
while ((rv == 0 || numthreads > 1) && bye < 2) {
compute_new_timeout(&exittime);
if (bye)
bye++;
rv = pthread_cond_timedwait(&numthreads_cv,
&numthreads_lock, &exittime);
}
(void) pthread_mutex_unlock(&numthreads_lock);
return (NULL);
}
static void *
wait_for_flush_indication_thread(void *arg)
{
int *autofs_fdp = arg;
for (;;) {
pthread_mutex_lock(&numthreads_lock);
if (bye >= 1) {
pthread_mutex_unlock(&numthreads_lock);
break;
}
pthread_mutex_unlock(&numthreads_lock);
if (ioctl(*autofs_fdp, AUTOFS_WAITFORFLUSH, 0) == -1) {
if (errno != EINTR) {
syslog(LOG_ERR,
"AUTOFS_WAITFORFLUSH failed: %s",
strerror(errno));
}
} else
flush_caches();
}
return (NULL);
}
kern_return_t
autofs_readdir(__unused mach_port_t server, autofs_pathname rda_map,
int64_t rda_offset, uint32_t rda_count, int *status, int64_t *rddir_offset,
boolean_t *rddir_eof, byte_buffer *rddir_entries,
mach_msg_type_number_t *rddir_entriesCnt, security_token_t token)
{
new_worker_thread();
if (token.val[0] != 0) {
*status = EPERM;
end_worker_thread();
return KERN_SUCCESS;
}
if (trace > 0)
trace_prt(1, "READDIR REQUEST : %s @ %llu\n",
rda_map, rda_offset);
*status = do_readdir(rda_map, rda_offset, rda_count, rddir_offset,
rddir_eof, rddir_entries, rddir_entriesCnt);
if (trace > 0)
trace_prt(1, "READDIR REPLY : status=%d\n", *status);
end_worker_thread();
return KERN_SUCCESS;
}
kern_return_t
autofs_unmount(__unused mach_port_t server, int32_t fsid_val0,
int32_t fsid_val1, autofs_pathname mntresource,
autofs_pathname mntpnt, autofs_component fstype, autofs_opts mntopts,
int *status, security_token_t token)
{
new_worker_thread();
if (token.val[0] != 0) {
*status = EPERM;
end_worker_thread();
return KERN_SUCCESS;
}
if (trace > 0) {
char ctime_buf[CTIME_BUF_LEN];
if (ctime_r(&timenow, ctime_buf) == NULL)
ctime_buf[0] = '\0';
trace_prt(1, "UNMOUNT REQUEST: %s", ctime_buf);
trace_prt(1, " resource=%s fstype=%s mntpnt=%s"
" mntopts=%s\n",
mntresource,
fstype,
mntpnt,
mntopts);
}
*status = do_unmount1(fsid_val0, fsid_val1, mntresource,
mntpnt, fstype, mntopts);
if (trace > 0)
trace_prt(1, "UNMOUNT REPLY: status=%d\n", *status);
end_worker_thread();
return KERN_SUCCESS;
}
kern_return_t
autofs_lookup(__unused mach_port_t server, autofs_pathname map,
autofs_pathname path, autofs_component name, mach_msg_type_number_t nameCnt,
autofs_pathname subdir, autofs_opts opts, boolean_t isdirect,
uint32_t sendereuid, int *err, int *lu_action, boolean_t *lu_verbose,
security_token_t token)
{
char *key;
new_worker_thread();
if (token.val[0] != 0) {
*err = EPERM;
*lu_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
if (nameCnt < 1 || nameCnt > MAXNAMLEN) {
*err = ENOENT;
*lu_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
key = malloc(nameCnt + 1);
if (key == NULL) {
*err = ENOMEM;
*lu_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
memcpy(key, name, nameCnt);
key[nameCnt] = '\0';
if (trace > 0) {
char ctime_buf[CTIME_BUF_LEN];
if (ctime_r(&timenow, ctime_buf) == NULL)
ctime_buf[0] = '\0';
trace_prt(1, "LOOKUP REQUEST: %s", ctime_buf);
trace_prt(1, " name=%s[%s] map=%s opts=%s path=%s direct=%d\n",
key, subdir, map, opts, path, isdirect);
}
*err = do_lookup1(map, key, subdir, opts, isdirect, sendereuid,
lu_action);
*lu_verbose = verbose;
free(key);
if (trace > 0)
trace_prt(1, "LOOKUP REPLY : status=%d\n", *err);
end_worker_thread();
return KERN_SUCCESS;
}
kern_return_t
autofs_mount(__unused mach_port_t server, autofs_pathname map,
autofs_pathname path, autofs_component name, mach_msg_type_number_t nameCnt,
autofs_pathname subdir, autofs_opts opts, boolean_t isdirect,
uint32_t sendereuid, mach_port_t gssd_port, int *mr_type,
byte_buffer *actions, mach_msg_type_number_t *actionsCnt,
int *err, boolean_t *mr_verbose, security_token_t token)
{
char *key;
int status;
new_worker_thread();
if (token.val[0] != 0) {
mach_port_deallocate(current_task(), gssd_port);
*mr_type = AUTOFS_DONE;
*err = EPERM;
*mr_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
if (nameCnt < 1 || nameCnt > MAXNAMLEN) {
mach_port_deallocate(current_task(), gssd_port);
*mr_type = AUTOFS_DONE;
*err = ENOENT;
*mr_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
key = malloc(nameCnt + 1);
if (key == NULL) {
mach_port_deallocate(current_task(), gssd_port);
*mr_type = AUTOFS_DONE;
*err = ENOMEM;
*mr_verbose = 0;
end_worker_thread();
return KERN_SUCCESS;
}
memcpy(key, name, nameCnt);
key[nameCnt] = '\0';
if (trace > 0) {
char ctime_buf[CTIME_BUF_LEN];
if (ctime_r(&timenow, ctime_buf) == NULL)
ctime_buf[0] = '\0';
trace_prt(1, "MOUNT REQUEST: %s", ctime_buf);
trace_prt(1, " name=%s[%s] map=%s opts=%s path=%s direct=%d\n",
key, subdir, map, opts, path, isdirect);
}
status = do_mount1(map, key, subdir, opts, path, isdirect, sendereuid,
gssd_port, actions, actionsCnt);
mach_port_deallocate(current_task(), gssd_port);
if (status == 0 && *actionsCnt != 0)
*mr_type = AUTOFS_ACTION;
else
*mr_type = AUTOFS_DONE;
*err = status;
*mr_verbose = verbose;
if (trace > 0) {
switch (*mr_type) {
case AUTOFS_ACTION:
trace_prt(1,
"MOUNT REPLY : status=%d, AUTOFS_ACTION\n",
status);
break;
case AUTOFS_DONE:
trace_prt(1,
"MOUNT REPLY : status=%d, AUTOFS_DONE\n",
status);
break;
default:
trace_prt(1, "MOUNT REPLY : status=%d, UNKNOWN\n",
status);
}
}
if (status && verbose) {
if (isdirect) {
syslog(LOG_ERR, "mount of %s failed", path);
} else {
syslog(LOG_ERR,
"mount of %s/%s failed", path, key);
}
}
free(key);
end_worker_thread();
return KERN_SUCCESS;
}
kern_return_t
autofs_mount_trigger(__unused mach_port_t server,
autofs_pathname mntpt, autofs_pathname submntpt,
autofs_pathname path, autofs_opts opts,
autofs_pathname map, autofs_pathname subdir,
autofs_component key, uint32_t flags, uint32_t mntflags,
int32_t mount_to, int32_t mach_to, int32_t direct,
int32_t *fsid_val0, int32_t *fsid_val1, boolean_t *top_level,
int *err, security_token_t token)
{
new_worker_thread();
if (token.val[0] != 0) {
*err = EPERM;
end_worker_thread();
return KERN_SUCCESS;
}
*err = do_mount_trigger(mntpt, submntpt, path, opts, map, subdir, key,
flags, mntflags, mount_to, mach_to, direct, fsid_val0, fsid_val1,
top_level);
if (*err)
syslog(LOG_ERR, "trigger mount on %s failed: %s", mntpt,
strerror(*err));
end_worker_thread();
return KERN_SUCCESS;
}
static int
do_mount_trigger(autofs_pathname mntpt, autofs_pathname submntpt,
autofs_pathname path, autofs_opts opts,
autofs_pathname map, autofs_pathname subdir,
autofs_component key, uint32_t flags, uint32_t mntflags,
int32_t mount_to, int32_t mach_to, int32_t direct,
int32_t *fsid_val0, int32_t *fsid_val1, boolean_t *top_level)
{
struct stat statb;
struct autofs_args mnt_args;
struct statfs buf;
int err;
if (lstat(mntpt, &statb) == 0) {
if (S_ISLNK(statb.st_mode)) {
syslog(LOG_ERR, "%s symbolic link: not a valid mountpoint - mount failed",
mntpt);
return (ENOENT);
}
}
mnt_args.version = AUTOFS_ARGSVERSION;
mnt_args.path = path;
mnt_args.opts = opts;
mnt_args.map = map;
mnt_args.subdir = subdir;
mnt_args.key = key;
mnt_args.mntflags = mntflags;
mnt_args.mount_to = mount_to;
mnt_args.mach_to = mach_to;
mnt_args.direct = direct;
mnt_args.trigger = 1;
if (mount(MNTTYPE_AUTOFS, mntpt, flags|MNT_AUTOMOUNTED|MNT_DONTBROWSE,
&mnt_args) == -1)
return (errno);
if (statfs(mntpt, &buf) == -1) {
err = errno;
unmount(mntpt, MNT_FORCE);
return (err);
}
*fsid_val0 = buf.f_fsid.val[0];
*fsid_val1 = buf.f_fsid.val[1];
*top_level = (strcmp(submntpt, ".") == 0);
return (0);
}
kern_return_t
autofs_check_thishost(__unused mach_port_t server, autofs_component name,
mach_msg_type_number_t nameCnt, boolean_t *is_us, security_token_t token)
{
new_worker_thread();
if (token.val[0] != 0) {
*is_us = 0;
end_worker_thread();
return KERN_SUCCESS;
}
*is_us = host_is_us(name, nameCnt);
end_worker_thread();
return KERN_SUCCESS;
}
kern_return_t
autofs_check_trigger(__unused mach_port_t server, autofs_pathname map,
autofs_pathname path, autofs_component name, mach_msg_type_number_t nameCnt,
autofs_pathname subdir, autofs_opts opts, boolean_t isdirect,
int *err, boolean_t *istrigger, security_token_t token)
{
char *key;
int status;
new_worker_thread();
if (token.val[0] != 0) {
*err = EPERM;
end_worker_thread();
return KERN_SUCCESS;
}
if (nameCnt < 1 || nameCnt > MAXNAMLEN) {
*err = ENOENT;
end_worker_thread();
return KERN_SUCCESS;
}
key = malloc(nameCnt + 1);
if (key == NULL) {
*err = ENOMEM;
end_worker_thread();
return KERN_SUCCESS;
}
memcpy(key, name, nameCnt);
key[nameCnt] = '\0';
if (trace > 0) {
char ctime_buf[CTIME_BUF_LEN];
if (ctime_r(&timenow, ctime_buf) == NULL)
ctime_buf[0] = '\0';
trace_prt(1, "CHECK TRIGGER REQUEST: %s", ctime_buf);
trace_prt(1, " name=%s[%s] map=%s opts=%s path=%s direct=%d\n",
key, subdir, map, opts, path, isdirect);
}
status = do_check_trigger(map, key, subdir, opts, path, isdirect, istrigger);
*err = status;
if (trace > 0) {
trace_prt(1, "CHECK TRIGGER REPLY : status=%d, istrigger = %d\n",
status, *istrigger);
}
if (status && verbose) {
if (isdirect) {
syslog(LOG_ERR, "check trigger of %s failed", path);
} else {
syslog(LOG_ERR,
"check trigger of %s/%s failed", path, key);
}
}
free(key);
end_worker_thread();
return KERN_SUCCESS;
}
void
pr_msg(const char *fmt, ...)
{
va_list ap;
#if 0
fmt = gettext(fmt);
#endif
va_start(ap, fmt);
(void) vsyslog(LOG_ERR, fmt, ap);
va_end(ap);
}