/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * autod_mount.c * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "automount.h" #include "replica.h" static int unmount_mntpnt(struct mnttab *); static int call_fork_exec(char *, char *, char **, int); static void remove_browse_options(char *); static int inherit_options(char *, char **); int do_mount1( char *mapname, char *key, char *subdir, char *mapopts, char *path, uint_t isdirect, uid_t uid, action_list **alpp, int flags) { struct mapline ml; struct mapent *me, *mapents = NULL; char mntpnt[MAXPATHLEN]; char spec_mntpnt[MAXPATHLEN]; int err = 0; char *private; /* fs specific data. eg prevhost in case of nfs */ int mount_ok = 0; ssize_t len; action_list *alp, *prev, *tmp; char root[MAXPATHLEN]; int overlay = 1; char next_subdir[MAXPATHLEN]; bool_t mount_access = TRUE; bool_t iswildcard; bool_t isrestricted = hasrestrictopt(mapopts); char *stack[STACKSIZ]; char **stkptr = stack; retry: iswildcard = FALSE; /* initialize the stack of open files for this thread */ stack_op(INIT, NULL, stack, &stkptr); err = getmapent(key, mapname, &ml, stack, &stkptr, &iswildcard, isrestricted); if (err == 0) { mapents = parse_entry(key, mapname, mapopts, &ml, subdir, isdirect, mount_access); } if (trace) { struct mapfs *mfs; trace_prt(1, " do_mount1:\n"); for (me = mapents; me; me = me->map_next) { trace_prt(1, " (%s,%s)\t%s%s%s\n", me->map_fstype ? me->map_fstype : "", me->map_mounter ? me->map_mounter : "", path ? path : "", me->map_root ? me->map_root : "", me->map_mntpnt ? me->map_mntpnt : ""); trace_prt(0, "\t\t-%s\n", me->map_mntopts ? me->map_mntopts : ""); for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next) trace_prt(0, "\t\t%s:%s\tpenalty=%d\n", mfs->mfs_host ? mfs->mfs_host: "", mfs->mfs_dir ? mfs->mfs_dir : "", mfs->mfs_penalty); } } *alpp = NULL; /* * Each mapent in the list describes a mount to be done. * Normally there's just a single entry, though in the * case of /net mounts there may be many entries, that * must be mounted as a hierarchy. For each mount the * automountd must make sure the required mountpoint * exists and invoke the appropriate mount command for * the fstype. */ private = ""; for (me = mapents; me && !err; me = me->map_next) { len = snprintf(mntpnt, sizeof (mntpnt), "%s%s%s", path, mapents->map_root, me->map_mntpnt); if (len >= sizeof (mntpnt)) { free_mapent(mapents); return (ENAMETOOLONG); } /* * remove trailing /'s from mountpoint to avoid problems * stating a directory with two or more trailing slashes. * This will let us mount directories from machines * which export with two or more slashes (apollo for instance). */ len -= 1; while (mntpnt[len] == '/') mntpnt[len--] = '\0'; (void) strcpy(spec_mntpnt, mntpnt); if (isrestricted && inherit_options(mapopts, &me->map_mntopts) != 0) { syslog(LOG_ERR, "malloc of options failed"); free_mapent(mapents); return (EAGAIN); } if (strcmp(me->map_fstype, MNTTYPE_NFS) == 0) { remove_browse_options(me->map_mntopts); if (flags == DOMOUNT_KERNEL) { alp = (action_list *)malloc( sizeof (action_list)); if (alp == NULL) { syslog(LOG_ERR, "malloc of alp failed"); continue; } memset(alp, 0, sizeof (action_list)); } else alp = NULL; err = mount_nfs(me, spec_mntpnt, private, overlay, uid, &alp); /* * We must retry if we don't have access to the * root file system and there are other * following mapents. The reason we can't * continue because the rest of the mapent list * depends on whether mount_access is TRUE or FALSE. */ if (err == NFSERR_ACCES && me->map_next != NULL) { /* * don't expect mount_access to be * FALSE here, but we do a check * anyway. */ if (mount_access == TRUE) { mount_access = FALSE; err = 0; free_mapent(mapents); if (alp) { free(alp); alp = NULL; } goto retry; } } if (alp) { if (*alpp == NULL) *alpp = alp; else { for (tmp = *alpp; tmp != NULL; tmp = tmp->next) prev = tmp; prev->next = alp; } } mount_ok = !err; } else if (strcmp(me->map_fstype, MNTTYPE_AUTOFS) == 0) { if (isdirect) { len = strlcpy(root, path, sizeof (root)); } else { len = snprintf(root, sizeof (root), "%s/%s", path, key); } if (len >= sizeof (root)) { free_mapent(mapents); return (ENAMETOOLONG); } alp = (action_list *)malloc(sizeof (action_list)); if (alp == NULL) { syslog(LOG_ERR, "malloc of alp failed"); continue; } memset(alp, 0, sizeof (action_list)); /* * get the next subidr, but only if its a modified * or faked autofs mount */ if (me->map_modified || me->map_faked) { len = snprintf(next_subdir, sizeof (next_subdir), "%s%s", subdir, me->map_mntpnt); } else { next_subdir[0] = '\0'; len = 0; } if (trace > 2) trace_prt(1, " root=%s\t next_subdir=%s\n", root, next_subdir); if (len < sizeof (next_subdir)) { err = mount_autofs(me, spec_mntpnt, alp, root, next_subdir, key); } else { err = ENAMETOOLONG; } if (err == 0) { /* * append to action list */ mount_ok++; if (*alpp == NULL) *alpp = alp; else { for (tmp = *alpp; tmp != NULL; tmp = tmp->next) prev = tmp; prev->next = alp; } } else { free(alp); mount_ok = 0; } } else if (strcmp(me->map_fstype, MNTTYPE_LOFS) == 0) { remove_browse_options(me->map_mntopts); err = loopbackmount(me->map_fs->mfs_dir, spec_mntpnt, me->map_mntopts, overlay); mount_ok = !err; } else { remove_browse_options(me->map_mntopts); err = mount_generic(me->map_fs->mfs_dir, me->map_fstype, me->map_mntopts, spec_mntpnt, overlay); mount_ok = !err; } } if (mapents) free_mapent(mapents); /* * If an error occurred, * the filesystem doesn't exist, or could not be * mounted. Return EACCES to autofs indicating that * the mountpoint can not be accessed if this is not * a wildcard access. If it is a wildcard access we * return ENOENT since the lookup that triggered * this mount request will fail and the entry will not * be available. */ if (mount_ok) { /* * No error occurred, return 0 to indicate success. */ err = 0; } else { /* * The filesystem does not exist or could not be mounted. * Return ENOENT if the lookup was triggered by a wildcard * access. Wildcard entries only exist if they can be * mounted. They can not be listed otherwise (through * a readdir(2)). * Return EACCES if the lookup was not triggered by a * wildcard access. Map entries that are explicitly defined * in maps are visible via readdir(2), therefore we return * EACCES to indicate that the entry exists, but the directory * can not be opened. This is the same behavior of a Unix * directory that exists, but has its execute bit turned off. * The directory is there, but the user does not have access * to it. */ if (iswildcard) err = ENOENT; else err = EACCES; } return (err); } #define ARGV_MAX 16 #define VFS_PATH "/usr/lib/fs" int mount_generic(special, fstype, opts, mntpnt, overlay) char *special, *fstype, *opts, *mntpnt; int overlay; { struct mnttab m; struct stat stbuf; int i, res; char *newargv[ARGV_MAX]; if (trace > 1) { trace_prt(1, " mount: %s %s %s %s\n", special, mntpnt, fstype, opts); } if (stat(mntpnt, &stbuf) < 0) { syslog(LOG_ERR, "Couldn't stat %s: %m", mntpnt); return (ENOENT); } i = 2; if (overlay) newargv[i++] = "-O"; /* * Use "quiet" option to suppress warnings about unsupported * mount options. */ newargv[i++] = "-q"; if (opts && *opts) { m.mnt_mntopts = opts; if (hasmntopt(&m, MNTOPT_RO) != NULL) newargv[i++] = "-r"; newargv[i++] = "-o"; newargv[i++] = opts; } newargv[i++] = "--"; newargv[i++] = special; newargv[i++] = mntpnt; newargv[i] = NULL; res = call_fork_exec(fstype, "mount", newargv, verbose); if (res == 0 && trace > 1) { if (stat(mntpnt, &stbuf) == 0) { trace_prt(1, " mount of %s dev=%x rdev=%x OK\n", mntpnt, stbuf.st_dev, stbuf.st_rdev); } else { trace_prt(1, " failed to stat %s\n", mntpnt); } } return (res); } void automountd_do_fork_exec(void *cookie, char *argp, size_t arg_size, door_desc_t *dfd, uint_t n_desc) { int stat_loc; int fd = 0; struct stat stbuf; int res; int child_pid; command_t *command; char *newargv[ARGV_MAX]; int i; command = (command_t *)argp; if (sizeof (*command) != arg_size) { res = EINVAL; door_return((char *)&res, sizeof (res), NULL, 0); } switch ((child_pid = fork1())) { case -1: syslog(LOG_ERR, "Cannot fork: %m"); res = errno; break; case 0: /* * Child */ (void) setsid(); fd = open(command->console ? "/dev/console" : "/dev/null", O_WRONLY); if (fd != -1) { (void) dup2(fd, 1); (void) dup2(fd, 2); (void) close(fd); } for (i = 0; *command->argv[i]; i++) { newargv[i] = strdup(command->argv[i]); if (newargv[i] == (char *)NULL) { syslog(LOG_ERR, "failed to copy argument '%s'" " of %s: %m", command->argv[i], command->file); _exit(errno); } } newargv[i] = NULL; (void) execv(command->file, newargv); if (errno == EACCES) syslog(LOG_ERR, "exec %s: %m", command->file); _exit(errno); default: /* * Parent */ (void) waitpid(child_pid, &stat_loc, WUNTRACED); if (WIFEXITED(stat_loc)) { if (trace > 1) { trace_prt(1, " fork_exec: returns exit status %d\n", WEXITSTATUS(stat_loc)); } res = WEXITSTATUS(stat_loc); } else if (WIFSIGNALED(stat_loc)) { if (trace > 1) trace_prt(1, " fork_exec: returns signal status %d\n", WTERMSIG(stat_loc)); res = 1; } else { if (trace > 1) trace_prt(1, " fork_exec: returns unknown status\n"); res = 1; } } door_return((char *)&res, sizeof (res), NULL, 0); trace_prt(1, "automountd_do_fork_exec, door return failed %s, %s\n", command->file, strerror(errno)); door_return(NULL, 0, NULL, 0); } int do_unmount1(ur) umntrequest *ur; { struct mnttab m; int res = 0; m.mnt_special = ur->mntresource; m.mnt_mountp = ur->mntpnt; m.mnt_fstype = ur->fstype; m.mnt_mntopts = ur->mntopts; /* * Special case for NFS mounts. * Don't want to attempt unmounts from * a dead server. If any member of a * hierarchy belongs to a dead server * give up (try later). */ if (strcmp(ur->fstype, MNTTYPE_NFS) == 0) { struct replica *list; int i, n; bool_t pubopt = FALSE; int nfs_port; int got_port; /* * See if a port number was specified. If one was * specified that is too large to fit in 16 bits, truncate * the high-order bits (for historical compatibility). Use * zero to indicate "no port specified". */ got_port = nopt(&m, MNTOPT_PORT, &nfs_port); if (!got_port) nfs_port = 0; nfs_port &= USHRT_MAX; if (hasmntopt(&m, MNTOPT_PUBLIC)) pubopt = TRUE; list = parse_replica(ur->mntresource, &n); if (list == NULL) { if (n >= 0) syslog(LOG_ERR, "Memory allocation failed: %m"); res = 1; goto done; } for (i = 0; i < n; i++) { if (pingnfs(list[i].host, 1, NULL, 0, nfs_port, pubopt, list[i].path, NULL) != RPC_SUCCESS) { res = 1; free_replica(list, n); goto done; } } free_replica(list, n); } res = unmount_mntpnt(&m); done: return (res); } static int unmount_mntpnt(mnt) struct mnttab *mnt; { char *fstype = mnt->mnt_fstype; char *mountp = mnt->mnt_mountp; char *newargv[ARGV_MAX]; int res; if (strcmp(fstype, MNTTYPE_NFS) == 0) { res = nfsunmount(mnt); } else if (strcmp(fstype, MNTTYPE_LOFS) == 0) { if ((res = umount(mountp)) < 0) res = errno; } else { newargv[2] = mountp; newargv[3] = NULL; res = call_fork_exec(fstype, "umount", newargv, verbose); if (res == ENOENT) { /* * filesystem specific unmount command not found */ if ((res = umount(mountp)) < 0) res = errno; } } if (trace > 1) trace_prt(1, " unmount %s %s\n", mountp, res ? "failed" : "OK"); return (res); } /* * Remove the autofs specific options 'browse', 'nobrowse' and * 'restrict' from 'opts'. */ static void remove_browse_options(char *opts) { char *p, *pb; char buf[MAXOPTSLEN], new[MAXOPTSLEN]; char *placeholder; new[0] = '\0'; (void) strcpy(buf, opts); pb = buf; while (p = (char *)strtok_r(pb, ",", &placeholder)) { pb = NULL; if (strcmp(p, MNTOPT_NOBROWSE) != 0 && strcmp(p, MNTOPT_BROWSE) != 0 && strcmp(p, MNTOPT_RESTRICT) != 0) { if (new[0] != '\0') (void) strcat(new, ","); (void) strcat(new, p); } } (void) strcpy(opts, new); } static const char *restropts[] = { RESTRICTED_MNTOPTS }; #define NROPTS (sizeof (restropts)/sizeof (restropts[0])) static int inherit_options(char *opts, char **mapentopts) { int i; char *new; struct mnttab mtmap; struct mnttab mtopt; size_t len = strlen(*mapentopts); for (i = 0; i < NROPTS; i++) len += strlen(restropts[i]); /* "," for each new option plus the trailing NUL */ len += NROPTS + 1; new = malloc(len); if (new == 0) return (-1); (void) strcpy(new, *mapentopts); mtmap.mnt_mntopts = *mapentopts; mtopt.mnt_mntopts = opts; for (i = 0; i < NROPTS; i++) { if (hasmntopt(&mtopt, (char *)restropts[i]) != NULL && hasmntopt(&mtmap, (char *)restropts[i]) == NULL) { if (*new != '\0') (void) strcat(new, ","); (void) strcat(new, restropts[i]); } } free(*mapentopts); *mapentopts = new; return (0); } bool_t hasrestrictopt(char *opts) { struct mnttab mt; mt.mnt_mntopts = opts; return (hasmntopt(&mt, MNTOPT_RESTRICT) != NULL); } static int call_fork_exec(fstype, cmd, newargv, console) char *fstype; char *cmd; char **newargv; int console; { command_t command; door_arg_t darg; char path[MAXPATHLEN]; struct stat stbuf; int ret; int sz; int status; int i; bzero(&command, sizeof (command)); /* build the full path name of the fstype dependent command */ (void) snprintf(path, MAXPATHLEN, "%s/%s/%s", VFS_PATH, fstype, cmd); if (stat(path, &stbuf) != 0) { ret = errno; return (ret); } strlcpy(command.file, path, MAXPATHLEN); strlcpy(command.argv[0], path, MAXOPTSLEN); for (i = 2; newargv[i]; i++) { strlcpy(command.argv[i-1], newargv[i], MAXOPTSLEN); } if (trace > 1) { trace_prt(1, " call_fork_exec: %s ", command.file); for (i = 0; *command.argv[i]; i++) trace_prt(0, "%s ", command.argv[i]); trace_prt(0, "\n"); } command.console = console; darg.data_ptr = (char *)&command; darg.data_size = sizeof (command); darg.desc_ptr = NULL; darg.desc_num = 0; darg.rbuf = (char *)&status; darg.rsize = sizeof (status); ret = door_call(did_fork_exec, &darg); if (trace > 1) { trace_prt(1, " call_fork_exec: door_call failed %d\n", ret); } return (status); }