#pragma ident "@(#)automount.c 1.50 05/06/08 SMI"
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <locale.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <fstab.h>
#include <mntopts.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <fts.h>
#include <OpenDirectory/OpenDirectory.h>
#include "deflt.h"
#include "autofs.h"
#include "automount.h"
#include "automount_od.h"
#include "umount_by_fsid.h"
static int parse_mntopts(const char *, int *, int *);
static int paths_match(struct autodir *, struct autodir *);
static int mkdir_r(char *);
static void rmdir_r(char *);
static int have_ad(void);
struct autodir *dir_head;
struct autodir *dir_tail;
static int num_current_mounts;
static struct statfs *current_mounts;
static void make_symlink(const char *, const char *);
static struct statfs *find_mount(const char *);
int verbose = 0;
int trace = 0;
static int autofs_control_fd;
static void usage(void);
static void do_unmounts(void);
static int load_autofs(void);
static int mount_timeout = AUTOFS_MOUNT_TIMEOUT;
static char gKextLoadCommand[] = "/sbin/kextload";
static char gKextLoadPath[] = "/System/Library/Extensions/autofs.kext";
pthread_mutex_t cleanup_lock;
pthread_cond_t cleanup_start_cv;
pthread_cond_t cleanup_done_cv;
int
main(int argc, char *argv[])
{
long timeout_val;
int c;
int flushcache = 0;
int unmount_automounted = 0; struct autodir *dir, *d;
char real_mntpnt[PATH_MAX];
struct stat stbuf;
char *master_map = "auto_master";
int null;
struct statfs *mntp;
int count = 0;
char *stack[STACKSIZ];
char **stkptr;
char *defval;
int fd;
int flags, altflags;
struct staticmap *static_ent;
if ((defopen(AUTOFSADMIN)) == 0) {
if ((defval = defread("AUTOMOUNT_TIMEOUT=")) != NULL) {
errno = 0;
timeout_val = strtol(defval, (char **)NULL, 10);
if (errno == 0 && timeout_val > 0 &&
timeout_val <= INT_MAX)
mount_timeout = (int)timeout_val;
}
if ((defval = defread("AUTOMOUNT_VERBOSE=")) != NULL) {
if (strncasecmp("true", defval, 4) == 0)
verbose = TRUE;
else
verbose = FALSE;
}
defopen(NULL);
}
while ((c = getopt(argc, argv, "mM:D:f:t:vcu?")) != EOF) {
switch (c) {
case 'm':
pr_msg("Warning: -m option not supported");
break;
case 'M':
pr_msg("Warning: -M option not supported");
break;
case 'D':
pr_msg("Warning: -D option not supported");
break;
case 'f':
pr_msg("Error: -f option no longer supported");
usage();
break;
case 't':
if (strchr(optarg, '=')) {
pr_msg("Error: invalid value for -t");
usage();
}
mount_timeout = atoi(optarg);
break;
case 'v':
verbose++;
break;
case 'c':
flushcache++;
break;
case 'u':
unmount_automounted++;
break;
default:
usage();
break;
}
}
if (optind < argc) {
pr_msg("%s: command line mountpoints/maps "
"no longer supported",
argv[optind]);
usage();
}
num_current_mounts = getmntinfo(¤t_mounts, MNT_NOWAIT);
if (num_current_mounts == 0) {
pr_msg("Couldn't get current mounts: %m");
exit(1);
}
autofs_control_fd = open("/dev/" AUTOFS_CONTROL_DEVICE, O_RDONLY);
if (autofs_control_fd == -1 && errno == ENOENT) {
FTS *fts;
static char *const paths[] = { "/Network", NULL };
FTSENT *ftsent;
int error;
fts = fts_open(paths, FTS_NOCHDIR|FTS_PHYSICAL|FTS_XDEV,
NULL);
if (fts != NULL) {
while ((ftsent = fts_read(fts)) != NULL) {
if (ftsent->fts_info == FTS_DP &&
ftsent->fts_level > FTS_ROOTLEVEL)
rmdir(ftsent->fts_accpath);
}
fts_close(fts);
}
error = load_autofs();
if (error != 0) {
pr_msg("can't load autofs kext");
exit(1);
}
autofs_control_fd = open("/dev/" AUTOFS_CONTROL_DEVICE,
O_RDONLY);
}
if (autofs_control_fd == -1) {
if (errno == EBUSY)
pr_msg("Another automount is running");
else
pr_msg("Couldn't open %s: %m", "/dev/" AUTOFS_CONTROL_DEVICE);
exit(1);
}
if (ioctl(autofs_control_fd, AUTOFS_SET_MOUNT_TO, &mount_timeout) == -1)
pr_msg("AUTOFS_SET_MOUNT_TO failed: %m");
if (unmount_automounted) {
if (verbose)
pr_msg("Unmounting triggered mounts");
if (ioctl(autofs_control_fd, AUTOFS_UNMOUNT_TRIGGERED, 0) == -1)
pr_msg("AUTOFS_UNMOUNT_TRIGGERED failed: %m");
exit(0);
}
if (flushcache) {
if (ioctl(autofs_control_fd, AUTOFS_NOTIFYCHANGE, 0) == -1)
pr_msg("AUTOFS_NOTIFYCHANGE failed: %m");
}
(void) umask(0);
ns_setup(stack, &stkptr);
(void) loadmaster_map(master_map, "", stack, &stkptr);
for (dir = dir_head; dir; dir = dir->dir_next) {
if (realpath(dir->dir_name, real_mntpnt) == NULL) {
if (errno != ENOENT) {
pr_msg("%s: Can't convert to real path: %m",
dir->dir_name);
continue;
}
dir->dir_realpath = NULL;
} else {
dir->dir_realpath = strdup(real_mntpnt);
if (dir->dir_realpath == NULL) {
pr_msg("Couldn't allocate real path: %m");
exit(1);
}
}
if (strcmp(dir->dir_map, "-null") == 0)
continue;
null = 0;
for (d = dir->dir_prev; d; d = d->dir_prev) {
if (paths_match(dir, d))
null = 1;
}
if (null)
continue;
if (strcmp(dir->dir_map, "-fstab") == 0) {
if (!have_ad() && !havefstabkeys()) {
free(dir->dir_map);
dir->dir_map = strdup("-null");
continue;
}
endfsent();
}
if (strcmp(dir->dir_map, "-fstab") == 0 ||
strcmp(dir->dir_map, "-static") == 0) {
for (d = dir_head; d; d = d->dir_next) {
if (paths_match(dir, d) &&
strcmp(d->dir_map, "-fstab") != 0 &&
strcmp(d->dir_map, "-static") != 0) {
pr_msg("%s: ignoring redundant %s map",
dir->dir_name, dir->dir_map);
continue;
}
}
}
if (!parse_mntopts(dir->dir_opts, &flags, &altflags)) {
continue;
}
if (strcmp(dir->dir_map, "-static") == 0) {
static_ent = get_staticmap_entry(dir->dir_name);
if (static_ent == NULL) {
pr_msg("can't find fstab entry for %s",
dir->dir_name);
continue;
}
if (host_is_us(static_ent->host, strlen(static_ent->host)) ||
self_check(static_ent->host)) {
make_symlink(static_ent->localpath,
dir->dir_name);
release_staticmap_entry(static_ent);
continue;
}
release_staticmap_entry(static_ent);
}
if (dir->dir_realpath != NULL &&
(mntp = find_mount(dir->dir_realpath)) != NULL) {
struct autofs_update_args au;
if (strcmp(mntp->f_fstypename, MNTTYPE_AUTOFS) != 0) {
pr_msg("%s: already mounted on %s",
mntp->f_mntfromname, dir->dir_realpath);
continue;
}
au.fsid = mntp->f_fsid;
au.opts = dir->dir_opts;
au.map = dir->dir_map;
au.mntflags = altflags;
au.direct = dir->dir_direct;
au.node_type = dir->dir_direct ? NT_TRIGGER : 0;
if (ioctl(autofs_control_fd, AUTOFS_UPDATE_OPTIONS,
&au) < 0) {
pr_msg("update %s: %m", dir->dir_realpath);
continue;
}
if (verbose)
pr_msg("%s updated", dir->dir_realpath);
} else {
struct autofs_args ai;
int st_flags = 0;
if (lstat(dir->dir_name, &stbuf) == 0) {
if ((stbuf.st_mode & S_IFMT) != S_IFDIR) {
pr_msg("%s: Not a directory", dir->dir_name);
continue;
}
st_flags = stbuf.st_flags;
if (dir->dir_realpath == NULL) {
errno = ENOENT;
pr_msg("%s: Can't convert to real path: %m",
dir->dir_name);
continue;
}
} else {
if (mkdir_r(dir->dir_name)) {
pr_msg("%s: %m", dir->dir_name);
continue;
}
if (realpath(dir->dir_name, real_mntpnt) == NULL) {
pr_msg("%s: Can't convert to real path: %m",
dir->dir_name);
continue;
}
dir->dir_realpath = strdup(real_mntpnt);
if (dir->dir_realpath == NULL) {
pr_msg("Couldn't allocate real path for %s: %m",
dir->dir_name);
continue;
}
}
if (altflags & AUTOFS_MNT_HIDEFROMFINDER)
st_flags |= UF_HIDDEN;
else
st_flags &= ~UF_HIDDEN;
if (chflags(dir->dir_name, st_flags) < 0)
pr_msg("%s: can't set hidden", dir->dir_name);
ai.version = AUTOFS_ARGSVERSION;
ai.path = dir->dir_realpath;
ai.opts = dir->dir_opts;
ai.map = dir->dir_map;
ai.subdir = "";
ai.direct = dir->dir_direct;
if (dir->dir_direct)
ai.key = dir->dir_name;
else
ai.key = "";
ai.mntflags = altflags;
ai.mount_type = MOUNT_TYPE_MAP;
ai.node_type = dir->dir_direct ? NT_TRIGGER : 0;
if (mount(MNTTYPE_AUTOFS, dir->dir_realpath,
MNT_DONTBROWSE | MNT_AUTOMOUNTED | flags,
&ai) < 0) {
pr_msg("mount %s: %m", dir->dir_realpath);
continue;
}
if (verbose)
pr_msg("%s mounted", dir->dir_realpath);
}
count++;
}
if (verbose && count == 0)
pr_msg("no mounts");
do_unmounts();
fd = open("/var/run/automount.initialized", O_CREAT|O_WRONLY, 0600);
close(fd);
return (0);
}
static void
make_symlink(const char *target, const char *path)
{
struct stat stbuf;
char linktarget[PATH_MAX + 1];
ssize_t pathlength;
struct statfs *mnt;
if (lstat(path, &stbuf) == 0) {
if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
pathlength = readlink(path, linktarget, PATH_MAX);
if (pathlength == -1) {
pr_msg("can't read target of %s: %m", path);
return;
}
linktarget[pathlength] = '\0';
if (strcmp(linktarget, target) == 0) {
if (verbose)
pr_msg("link %s unchanged", path);
return;
}
if (unlink(path) == -1) {
pr_msg("can't unlink %s: %m", path);
return;
}
} else if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
mnt = find_mount(path);
if (mnt != NULL) {
if (strcmp(mnt->f_fstypename, MNTTYPE_AUTOFS) == 0) {
if (ioctl(autofs_control_fd,
AUTOFS_UNMOUNT, &mnt->f_fsid) != 0) {
return;
}
}
}
if (rmdir(path) != 0) {
return;
}
} else {
return;
}
} else {
if (errno != ENOENT) {
return;
}
}
if (symlink(target, path) == -1) {
pr_msg("can't create symlink from %s to %s: %m",
path, target);
}
}
static struct statfs *
find_mount(mntpnt)
const char *mntpnt;
{
int i;
struct statfs *mnt;
for (i = 0; i < num_current_mounts; i++) {
mnt = ¤t_mounts[i];
if (strcmp(mntpnt, mnt->f_mntonname) == 0)
return (mnt);
}
return (NULL);
}
static void
usage()
{
pr_msg("Usage: automount [ -vcu ] [ -t duration ]");
exit(1);
}
static const struct mntopt mopts_autofs[] = {
{ "browse", 1, AUTOFS_MNT_NOBROWSE, 1 },
MOPT_STDOPTS,
{ MNTOPT_RESTRICT, 0, AUTOFS_MNT_RESTRICT, 1 },
{ MNTOPT_HIDEFROMFINDER, 0, AUTOFS_MNT_HIDEFROMFINDER, 1 },
{ NULL, 0, 0, 0 }
};
static int
parse_mntopts(const char *opts, int *flags, int *altflags)
{
mntoptparse_t mp;
*flags = *altflags = 0;
getmnt_silent = 1;
mp = getmntopts(opts, mopts_autofs, flags, altflags);
if (mp == NULL) {
pr_msg("memory allocation failure");
return (0);
}
freemntopts(mp);
return (1);
}
static void
do_unmounts(void)
{
int i;
struct statfs *mnt;
struct autodir *dir;
int current;
int count = 0;
static const char triggered[] = "triggered";
for (i = 0; i < num_current_mounts; i++) {
mnt = ¤t_mounts[i];
if (strcmp(mnt->f_fstypename, MNTTYPE_AUTOFS) != 0)
continue;
if (strcmp(mnt->f_mntfromname, "subtrigger") == 0 ||
strncmp(mnt->f_mntfromname, triggered, sizeof (triggered) - 1) == 0)
continue;
current = 0;
for (dir = dir_head; dir; dir = dir->dir_next) {
if (dir->dir_realpath != NULL &&
strcmp(dir->dir_realpath, mnt->f_mntonname) == 0) {
current = strcmp(dir->dir_map, "-null");
break;
}
}
if (current)
continue;
if (ioctl(autofs_control_fd, AUTOFS_UNMOUNT,
&mnt->f_fsid) == 0) {
static const char slashnetwork[] = "/Network/";
if (verbose) {
pr_msg("%s unmounted",
mnt->f_mntonname);
}
count++;
if (strncmp(mnt->f_mntonname, slashnetwork,
sizeof slashnetwork - 1) == 0)
rmdir_r(mnt->f_mntonname);
}
}
if (verbose && count == 0)
pr_msg("no unmounts");
}
static int
paths_match(struct autodir *d1, struct autodir *d2)
{
if (strcmp(d1->dir_name, d2->dir_name) == 0)
return (1);
if (d1->dir_realpath != NULL) {
if (strcmp(d1->dir_realpath, d2->dir_name) == 0)
return (1);
if (d2->dir_realpath != NULL) {
if (strcmp(d1->dir_realpath, d2->dir_realpath) == 0)
return (1);
}
}
if (d2->dir_realpath != NULL) {
if (strcmp(d1->dir_name, d2->dir_realpath) == 0)
return (1);
}
return (0);
}
static int
mkdir_r(dir)
char *dir;
{
int err;
char *slash;
if (mkdir(dir, 0555) == 0) {
return (0);
}
if (errno == EEXIST) {
return (0);
}
if (errno != ENOENT) {
return (-1);
}
slash = strrchr(dir, '/');
if (slash == NULL)
return (-1);
*slash = '\0';
err = mkdir_r(dir);
*slash++ = '/';
if (err || !*slash)
return (err);
return (mkdir(dir, 0555));
}
void
rmdir_r(char *path)
{
char *p;
for (;;) {
p = strrchr(path, '/');
if (p == NULL) {
break;
}
if (p == path) {
break;
}
if (rmdir(path) == -1)
break;
*p = '\0';
}
}
static int
have_ad(void)
{
int have_it = 0;
CFErrorRef error;
char *errstring;
ODNodeRef node_ref;
CFArrayRef paths;
CFIndex num_paths;
CFIndex i;
CFStringRef path;
error = NULL;
node_ref = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault,
kODNodeTypeAuthentication, &error);
if (node_ref == NULL) {
errstring = od_get_error_string(error);
pr_msg("have_ad: can't create search node for /Search: %s",
errstring);
free(errstring);
return (0);
}
paths = ODNodeCopySubnodeNames(node_ref, &error);
if (paths == NULL) {
errstring = od_get_error_string(error);
pr_msg("have_ad: can't get subnode names for /Search: %s",
errstring);
free(errstring);
return (0);
}
num_paths = CFArrayGetCount(paths);
for (i = 0; i < num_paths && !have_it; i++) {
path = CFArrayGetValueAtIndex(paths, i);
have_it = CFStringHasPrefix(path, CFSTR("/Active Directory"));
}
CFRelease(paths);
CFRelease(node_ref);
return (have_it);
}
void
pr_msg(const char *fmt, ...)
{
va_list ap;
char buf[BUFSIZ], *p2;
const char *p1;
(void) strlcpy(buf, "automount: ", sizeof buf);
p2 = buf + strlen(buf);
for (p1 = fmt; *p1; p1++) {
if (*p1 == '%' && *(p1+1) == 'm') {
(void) strlcpy(p2, strerror(errno),
sizeof buf - (p2 - buf));
p2 += strlen(p2);
p1++;
} else {
*p2++ = *p1;
}
}
if (p2 > buf && *(p2-1) != '\n')
*p2++ = '\n';
*p2 = '\0';
va_start(ap, fmt);
(void) vfprintf(stderr, buf, ap);
va_end(ap);
}
static int
load_autofs(void)
{
pid_t pid, terminated_pid;
int result;
union wait status;
pid = fork();
if (pid == 0) {
result = execl(gKextLoadCommand, gKextLoadCommand, "-q",
gKextLoadPath, NULL);
return (result ? result : ECHILD);
}
if (pid == -1)
return (-1);
while ((terminated_pid = wait4(pid, (int *)&status, 0, NULL)) < 0) {
if (errno != EINTR)
break;
}
if (terminated_pid == pid && WIFEXITED(status))
result = WEXITSTATUS(status);
else
result = -1;
return (result);
}