/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2014 The FreeBSD Foundation
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libutil.h>
#include <netdb.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "autofs_ioctl.h"
#include "common.h"
#define AUTOMOUNTD_PIDFILE "/var/run/automountd.pid"
static int nchildren = 0;
static int autofs_fd;
static int request_id;
static void
done(int request_error, bool wildcards)
{
struct autofs_daemon_done add;
int error;
memset(&add, 0, sizeof(add));
add.add_id = request_id;
add.add_wildcards = wildcards;
add.add_error = request_error;
log_debugx("completing request %d with error %d",
request_id, request_error);
error = ioctl(autofs_fd, AUTOFSDONE, &add);
if (error != 0)
log_warn("AUTOFSDONE");
}
/*
* Remove "fstype=whatever" from optionsp and return the "whatever" part.
*/
static char *
pick_option(const char *option, char **optionsp)
{
char *tofree, *pair, *newoptions;
char *picked = NULL;
bool first = true;
tofree = *optionsp;
newoptions = calloc(1, strlen(*optionsp) + 1);
if (newoptions == NULL)
log_err(1, "calloc");
while ((pair = strsep(optionsp, ",")) != NULL) {
/*
* XXX: strncasecmp(3) perhaps?
*/
if (strncmp(pair, option, strlen(option)) == 0) {
picked = checked_strdup(pair + strlen(option));
} else {
if (first == false)
strcat(newoptions, ",");
else
first = false;
strcat(newoptions, pair);
}
}
free(tofree);
*optionsp = newoptions;
return (picked);
}
static void
create_subtree(const struct node *node, bool incomplete)
{
const struct node *child;
char *path;
bool wildcard_found = false;
/*
* Skip wildcard nodes.
*/
if (strcmp(node->n_key, "*") == 0)
return;
path = node_path(node);
log_debugx("creating subtree at %s", path);
create_directory(path);
if (incomplete) {
TAILQ_FOREACH(child, &node->n_children, n_next) {
if (strcmp(child->n_key, "*") == 0) {
wildcard_found = true;
break;
}
}
if (wildcard_found) {
log_debugx("node %s contains wildcard entry; "
"not creating its subdirectories due to -d flag",
path);
free(path);
return;
}
}
free(path);
TAILQ_FOREACH(child, &node->n_children, n_next)
create_subtree(child, incomplete);
}
static void
exit_callback(void)
{
done(EIO, true);
}
static void
handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
bool incomplete_hierarchy)
{
const char *map;
struct node *root, *parent, *node;
FILE *f;
char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
int error;
bool wildcards;
log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
"key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
/*
* Try to notify the kernel about any problems.
*/
request_id = adr->adr_id;
atexit(exit_callback);
if (strncmp(adr->adr_from, "map ", 4) != 0) {
log_errx(1, "invalid mountfrom \"%s\"; failing request",
adr->adr_from);
}
map = adr->adr_from + 4; /* 4 for strlen("map "); */
root = node_new_root();
if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
/*
* Direct map. autofs(4) doesn't have a way to determine
* correct map key, but since it's a direct map, we can just
* use adr_path instead.
*/
parent = root;
key = checked_strdup(adr->adr_path);
} else {
/*
* Indirect map.
*/
parent = node_new_map(root, checked_strdup(adr->adr_prefix),
NULL, checked_strdup(map),
checked_strdup("[kernel request]"), lineno);
if (adr->adr_key[0] == '\0')
key = NULL;
else
key = checked_strdup(adr->adr_key);
}
/*
* "Wildcards" here actually means "make autofs(4) request
* automountd(8) action if the node being looked up does not
* exist, even though the parent is marked as cached". This
* needs to be done for maps with wildcard entries, but also
* for special and executable maps.
*/
parse_map(parent, map, key, &wildcards);
if (!wildcards)
wildcards = node_has_wildcards(parent);
if (wildcards)
log_debugx("map may contain wildcard entries");
else
log_debugx("map does not contain wildcard entries");
if (key != NULL)
node_expand_wildcard(root, key);
node = node_find(root, adr->adr_path);
if (node == NULL) {
log_errx(1, "map %s does not contain key for \"%s\"; "
"failing mount", map, adr->adr_path);
}
options = node_options(node);
/*
* Append options from auto_master.
*/
options = concat(options, ',', adr->adr_options);
/*
* Prepend options passed via automountd(8) command line.
*/
options = concat(cmdline_options, ',', options);
if (node->n_location == NULL) {
log_debugx("found node defined at %s:%d; not a mountpoint",
node->n_config_file, node->n_config_line);
nobrowse = pick_option("nobrowse", &options);
if (nobrowse != NULL && key == NULL) {
log_debugx("skipping map %s due to \"nobrowse\" "
"option; exiting", map);
done(0, true);
/*
* Exit without calling exit_callback().
*/
quick_exit(0);
}
/*
* Not a mountpoint; create directories in the autofs mount
* and complete the request.
*/
create_subtree(node, incomplete_hierarchy);
if (incomplete_hierarchy && key != NULL) {
/*
* We still need to create the single subdirectory
* user is trying to access.
*/
tmp = concat(adr->adr_path, '/', key);
node = node_find(root, tmp);
if (node != NULL)
create_subtree(node, false);
}
log_debugx("nothing to mount; exiting");
done(0, wildcards);
/*
* Exit without calling exit_callback().
*/
quick_exit(0);
}
log_debugx("found node defined at %s:%d; it is a mountpoint",
node->n_config_file, node->n_config_line);
if (key != NULL)
node_expand_ampersand(node, key);
error = node_expand_defined(node);
if (error != 0) {
log_errx(1, "variable expansion failed for %s; "
"failing mount", adr->adr_path);
}
/*
* Append "automounted".
*/
options = concat(options, ',', "automounted");
/*
* Remove "nobrowse", mount(8) doesn't understand it.
*/
pick_option("nobrowse", &options);
/*
* Figure out fstype.
*/
fstype = pick_option("fstype=", &options);
if (fstype == NULL) {
log_debugx("fstype not specified in options; "
"defaulting to \"nfs\"");
fstype = checked_strdup("nfs");
}
if (strcmp(fstype, "nfs") == 0) {
/*
* The mount_nfs(8) command defaults to retry undefinitely.
* We do not want that behaviour, because it leaves mount_nfs(8)
* instances and automountd(8) children hanging forever.
* Disable retries unless the option was passed explicitly.
*/
retrycnt = pick_option("retrycnt=", &options);
if (retrycnt == NULL) {
log_debugx("retrycnt not specified in options; "
"defaulting to 1");
options = concat(options, ',', "retrycnt=1");
} else {
options = concat(options, ',',
concat("retrycnt", '=', retrycnt));
}
}
f = auto_popen("mount", "-t", fstype, "-o", options,
node->n_location, adr->adr_path, NULL);
assert(f != NULL);
error = auto_pclose(f);
if (error != 0)
log_errx(1, "mount failed");
log_debugx("mount done; exiting");
done(0, wildcards);
/*
* Exit without calling exit_callback().
*/
quick_exit(0);
}
static void
sigchld_handler(int dummy __unused)
{
/*
* The only purpose of this handler is to make SIGCHLD
* interrupt the AUTOFSREQUEST ioctl(2), so we can call
* wait_for_children().
*/
}
static void
register_sigchld(void)
{
struct sigaction sa;
int error;
bzero(&sa, sizeof(sa));
sa.sa_handler = sigchld_handler;
sigfillset(&sa.sa_mask);
error = sigaction(SIGCHLD, &sa, NULL);
if (error != 0)
log_err(1, "sigaction");
}
static int
wait_for_children(bool block)
{
pid_t pid;
int status;
int num = 0;
for (;;) {
/*
* If "block" is true, wait for at least one process.
*/
if (block && num == 0)
pid = wait4(-1, &status, 0, NULL);
else
pid = wait4(-1, &status, WNOHANG, NULL);
if (pid <= 0)
break;
if (WIFSIGNALED(status)) {
log_warnx("child process %d terminated with signal %d",
pid, WTERMSIG(status));
} else if (WEXITSTATUS(status) != 0) {
log_debugx("child process %d terminated with exit status %d",
pid, WEXITSTATUS(status));
} else {
log_debugx("child process %d terminated gracefully", pid);
}
num++;
}
return (num);
}
static void
usage_automountd(void)
{
fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
"[-o opts][-Tidv]\n");
exit(1);
}
int
main_automountd(int argc, char **argv)
{
struct pidfh *pidfh;
pid_t pid, otherpid;
const char *pidfile_path = AUTOMOUNTD_PIDFILE;
char *options = NULL;
struct autofs_daemon_request request;
int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
bool dont_daemonize = false, incomplete_hierarchy = false;
defined_init();
while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
switch (ch) {
case 'D':
defined_parse_and_add(optarg);
break;
case 'T':
/*
* For compatibility with other implementations,
* such as OS X.
*/
debug++;
break;
case 'd':
dont_daemonize = true;
debug++;
break;
case 'i':
incomplete_hierarchy = true;
break;
case 'm':
maxproc = atoi(optarg);
break;
case 'o':
options = concat(options, ',', optarg);
break;
case 'v':
debug++;
break;
case '?':
default:
usage_automountd();
}
}
argc -= optind;
if (argc != 0)
usage_automountd();
log_init(debug);
pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
if (pidfh == NULL) {
if (errno == EEXIST) {
log_errx(1, "daemon already running, pid: %jd.",
(intmax_t)otherpid);
}
log_err(1, "cannot open or create pidfile \"%s\"",
pidfile_path);
}
autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
if (autofs_fd < 0 && errno == ENOENT) {
saved_errno = errno;
retval = kldload("autofs");
if (retval != -1)
autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
else
errno = saved_errno;
}
if (autofs_fd < 0)
log_err(1, "failed to open %s", AUTOFS_PATH);
if (dont_daemonize == false) {
if (daemon(0, 0) == -1) {
log_warn("cannot daemonize");
pidfile_remove(pidfh);
exit(1);
}
} else {
lesser_daemon();
}
pidfile_write(pidfh);
register_sigchld();
for (;;) {
log_debugx("waiting for request from the kernel");
memset(&request, 0, sizeof(request));
error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
if (error != 0) {
if (errno == EINTR) {
nchildren -= wait_for_children(false);
assert(nchildren >= 0);
continue;
}
log_err(1, "AUTOFSREQUEST");
}
if (dont_daemonize) {
log_debugx("not forking due to -d flag; "
"will exit after servicing a single request");
} else {
nchildren -= wait_for_children(false);
assert(nchildren >= 0);
while (maxproc > 0 && nchildren >= maxproc) {
log_debugx("maxproc limit of %d child processes hit; "
"waiting for child process to exit", maxproc);
nchildren -= wait_for_children(true);
assert(nchildren >= 0);
}
log_debugx("got request; forking child process #%d",
nchildren);
nchildren++;
pid = fork();
if (pid < 0)
log_err(1, "fork");
if (pid > 0)
continue;
}
pidfile_close(pidfh);
handle_request(&request, options, incomplete_hierarchy);
}
pidfile_close(pidfh);
return (0);
}