Home | History | Annotate | Line # | Download | only in autofs
      1 /*	$NetBSD: automountd.c,v 1.2 2018/01/11 13:44:26 christos Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2017 The NetBSD Foundation, Inc.
      5  * Copyright (c) 2016 The DragonFly Project
      6  * Copyright (c) 2014 The FreeBSD Foundation
      7  * All rights reserved.
      8  *
      9  * This code is derived from software contributed to The NetBSD Foundation
     10  * by Tomohiro Kusumi <kusumi.tomohiro (at) gmail.com>.
     11  *
     12  * This software was developed by Edward Tomasz Napierala under sponsorship
     13  * from the FreeBSD Foundation.
     14  *
     15  * Redistribution and use in source and binary forms, with or without
     16  * modification, are permitted provided that the following conditions
     17  * are met:
     18  * 1. Redistributions of source code must retain the above copyright
     19  *    notice, this list of conditions and the following disclaimer.
     20  * 2. Redistributions in binary form must reproduce the above copyright
     21  *    notice, this list of conditions and the following disclaimer in the
     22  *    documentation and/or other materials provided with the distribution.
     23  *
     24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     34  * SUCH DAMAGE.
     35  *
     36  */
     37 #include <sys/cdefs.h>
     38 __RCSID("$NetBSD: automountd.c,v 1.2 2018/01/11 13:44:26 christos Exp $");
     39 
     40 #include <sys/types.h>
     41 #include <sys/ioctl.h>
     42 #include <sys/module.h>
     43 #include <sys/wait.h>
     44 #include <assert.h>
     45 #include <errno.h>
     46 #include <fcntl.h>
     47 #include <signal.h>
     48 #include <stdio.h>
     49 #include <stdlib.h>
     50 #include <string.h>
     51 #include <unistd.h>
     52 #include <util.h>
     53 #include <fs/autofs/autofs_ioctl.h>
     54 
     55 #include "common.h"
     56 
     57 static int nchildren = 0;
     58 static int autofs_fd;
     59 static int request_id;
     60 static char nfs_def_retry[] = "1";
     61 
     62 static void
     63 done(int request_error, bool wildcards)
     64 {
     65 	struct autofs_daemon_done add;
     66 	int error;
     67 
     68 	memset(&add, 0, sizeof(add));
     69 	add.add_id = request_id;
     70 	add.add_wildcards = wildcards;
     71 	add.add_error = request_error;
     72 
     73 	log_debugx("completing request %d with error %d",
     74 	    request_id, request_error);
     75 
     76 	error = ioctl(autofs_fd, AUTOFSDONE, &add);
     77 	if (error != 0)
     78 		log_warn("AUTOFSDONE");
     79 }
     80 
     81 /*
     82  * Remove "fstype=whatever" from optionsp and return the "whatever" part.
     83  */
     84 static char *
     85 pick_option(const char *option, char **optionsp)
     86 {
     87 	char *tofree, *pair, *newoptions;
     88 	char *picked = NULL;
     89 	bool first = true;
     90 
     91 	tofree = *optionsp;
     92 
     93 	newoptions = calloc(1, strlen(*optionsp) + 1);
     94 	if (newoptions == NULL)
     95 		log_err(1, "calloc");
     96 
     97 	size_t olen = strlen(option);
     98 	while ((pair = strsep(optionsp, ",")) != NULL) {
     99 		/*
    100 		 * XXX: strncasecmp(3) perhaps?
    101 		 */
    102 		if (strncmp(pair, option, olen) == 0) {
    103 			picked = checked_strdup(pair + olen);
    104 		} else {
    105 			if (first)
    106 				first = false;
    107 			else
    108 				strcat(newoptions, ",");
    109 			strcat(newoptions, pair);
    110 		}
    111 	}
    112 
    113 	free(tofree);
    114 	*optionsp = newoptions;
    115 
    116 	return picked;
    117 }
    118 
    119 static void
    120 create_subtree(const struct node *node, bool incomplete)
    121 {
    122 	const struct node *child;
    123 	char *path;
    124 	bool wildcard_found = false;
    125 
    126 	/*
    127 	 * Skip wildcard nodes.
    128 	 */
    129 	if (strcmp(node->n_key, "*") == 0)
    130 		return;
    131 
    132 	path = node_path(node);
    133 	log_debugx("creating subtree at %s", path);
    134 	create_directory(path);
    135 
    136 	if (incomplete) {
    137 		TAILQ_FOREACH(child, &node->n_children, n_next) {
    138 			if (strcmp(child->n_key, "*") == 0) {
    139 				wildcard_found = true;
    140 				break;
    141 			}
    142 		}
    143 
    144 		if (wildcard_found) {
    145 			log_debugx("node %s contains wildcard entry; "
    146 			    "not creating its subdirectories due to -d flag",
    147 			    path);
    148 			free(path);
    149 			return;
    150 		}
    151 	}
    152 
    153 	free(path);
    154 
    155 	TAILQ_FOREACH(child, &node->n_children, n_next)
    156 		create_subtree(child, incomplete);
    157 }
    158 
    159 static void
    160 exit_callback(void)
    161 {
    162 
    163 	done(EIO, true);
    164 }
    165 
    166 __dead static void
    167 handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
    168     bool incomplete_hierarchy)
    169 {
    170 	const char *map;
    171 	struct node *root, *parent, *node;
    172 	FILE *f;
    173 	char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
    174 	int error;
    175 	bool wildcards;
    176 
    177 	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
    178 	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
    179 	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
    180 
    181 	/*
    182 	 * Try to notify the kernel about any problems.
    183 	 */
    184 	request_id = adr->adr_id;
    185 	atexit(exit_callback);
    186 
    187 	if (strncmp(adr->adr_from, "map ", 4) != 0) {
    188 		log_errx(1, "invalid mountfrom \"%s\"; failing request",
    189 		    adr->adr_from);
    190 	}
    191 
    192 	map = adr->adr_from + 4; /* 4 for strlen("map "); */
    193 	root = node_new_root();
    194 	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
    195 		/*
    196 		 * Direct map.  autofs(4) doesn't have a way to determine
    197 		 * correct map key, but since it's a direct map, we can just
    198 		 * use adr_path instead.
    199 		 */
    200 		parent = root;
    201 		key = checked_strdup(adr->adr_path);
    202 	} else {
    203 		/*
    204 		 * Indirect map.
    205 		 */
    206 		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
    207 		    NULL,  checked_strdup(map),
    208 		    checked_strdup("[kernel request]"), lineno);
    209 
    210 		if (adr->adr_key[0] == '\0')
    211 			key = NULL;
    212 		else
    213 			key = checked_strdup(adr->adr_key);
    214 	}
    215 
    216 	/*
    217 	 * "Wildcards" here actually means "make autofs(4) request
    218 	 * automountd(8) action if the node being looked up does not
    219 	 * exist, even though the parent is marked as cached".  This
    220 	 * needs to be done for maps with wildcard entries, but also
    221 	 * for special and executable maps.
    222 	 */
    223 	parse_map(parent, map, key, &wildcards);
    224 	if (!wildcards)
    225 		wildcards = node_has_wildcards(parent);
    226 	if (wildcards)
    227 		log_debugx("map may contain wildcard entries");
    228 	else
    229 		log_debugx("map does not contain wildcard entries");
    230 
    231 	if (key != NULL)
    232 		node_expand_wildcard(root, key);
    233 
    234 	node = node_find(root, adr->adr_path);
    235 	if (node == NULL) {
    236 		log_errx(1, "map %s does not contain key for \"%s\"; "
    237 		    "failing mount", map, adr->adr_path);
    238 	}
    239 
    240 	options = node_options(node);
    241 
    242 	/*
    243 	 * Append options from auto_master.
    244 	 */
    245 	options = concat(options, ',', adr->adr_options);
    246 
    247 	/*
    248 	 * Prepend options passed via automountd(8) command line.
    249 	 */
    250 	options = concat(cmdline_options, ',', options);
    251 
    252 	if (node->n_location == NULL) {
    253 		log_debugx("found node defined at %s:%d; not a mountpoint",
    254 		    node->n_config_file, node->n_config_line);
    255 
    256 		nobrowse = pick_option("nobrowse", &options);
    257 		if (nobrowse != NULL && key == NULL) {
    258 			log_debugx("skipping map %s due to \"nobrowse\" "
    259 			    "option; exiting", map);
    260 			done(0, true);
    261 
    262 			/*
    263 			 * Exit without calling exit_callback().
    264 			 */
    265 			quick_exit(EXIT_SUCCESS);
    266 		}
    267 
    268 		/*
    269 		 * Not a mountpoint; create directories in the autofs mount
    270 		 * and complete the request.
    271 		 */
    272 		create_subtree(node, incomplete_hierarchy);
    273 
    274 		if (incomplete_hierarchy && key != NULL) {
    275 			/*
    276 			 * We still need to create the single subdirectory
    277 			 * user is trying to access.
    278 			 */
    279 			tmp = concat(adr->adr_path, '/', key);
    280 			node = node_find(root, tmp);
    281 			if (node != NULL)
    282 				create_subtree(node, false);
    283 		}
    284 
    285 		log_debugx("nothing to mount; exiting");
    286 		done(0, wildcards);
    287 
    288 		/*
    289 		 * Exit without calling exit_callback().
    290 		 */
    291 		quick_exit(EXIT_SUCCESS);
    292 	}
    293 
    294 	log_debugx("found node defined at %s:%d; it is a mountpoint",
    295 	    node->n_config_file, node->n_config_line);
    296 
    297 	if (key != NULL)
    298 		node_expand_ampersand(node, key);
    299 	error = node_expand_defined(node);
    300 	if (error != 0) {
    301 		log_errx(1, "variable expansion failed for %s; "
    302 		    "failing mount", adr->adr_path);
    303 	}
    304 
    305 	/*
    306 	 * Append "automounted".
    307 	 */
    308 	options = concat(options, ',', "automounted");
    309 
    310 	/*
    311 	 * Remove "nobrowse", mount(8) doesn't understand it.
    312 	 */
    313 	pick_option("nobrowse", &options);
    314 
    315 	/*
    316 	 * Figure out fstype.
    317 	 */
    318 	fstype = pick_option("fstype=", &options);
    319 	if (fstype == NULL) {
    320 		log_debugx("fstype not specified in options; "
    321 		    "defaulting to \"nfs\"");
    322 		fstype = checked_strdup("nfs");
    323 	}
    324 
    325 	retrycnt = NULL;
    326 	if (strcmp(fstype, "nfs") == 0) {
    327 		/*
    328 		 * The mount_nfs(8) command defaults to retry DEF_RETRY(10000).
    329 		 * We do not want that behaviour, because it leaves mount_nfs(8)
    330 		 * instances and automountd(8) children hanging forever.
    331 		 * Disable retries unless the option was passed explicitly.
    332 		 */
    333 		retrycnt = pick_option("retrycnt=", &options);
    334 		if (retrycnt == NULL) {
    335 			log_debugx("retrycnt not specified in options; "
    336 			    "defaulting to 1");
    337 			retrycnt = nfs_def_retry;
    338 		}
    339 	}
    340 
    341 	/*
    342 	 * NetBSD doesn't have -o retrycnt=... option which is available
    343 	 * on FreeBSD and DragonFlyBSD, so use -R if the target type is NFS
    344 	 * (or add -o retrycnt=... to mount_nfs(8)).
    345 	 */
    346 	if (retrycnt) {
    347 		assert(!strcmp(fstype, "nfs"));
    348 		f = auto_popen("mount_nfs", "-o", options, "-R", retrycnt,
    349 		    node->n_location, adr->adr_path, NULL);
    350 	} else {
    351 		f = auto_popen("mount", "-t", fstype, "-o", options,
    352 		    node->n_location, adr->adr_path, NULL);
    353 	}
    354 	assert(f != NULL);
    355 	error = auto_pclose(f);
    356 	if (error != 0)
    357 		log_errx(1, "mount failed");
    358 
    359 	log_debugx("mount done; exiting");
    360 	done(0, wildcards);
    361 
    362 	/*
    363 	 * Exit without calling exit_callback().
    364 	 */
    365 	quick_exit(EXIT_SUCCESS);
    366 }
    367 
    368 static void
    369 sigchld_handler(int dummy __unused)
    370 {
    371 
    372 	/*
    373 	 * The only purpose of this handler is to make SIGCHLD
    374 	 * interrupt the AUTOFSREQUEST ioctl(2), so we can call
    375 	 * wait_for_children().
    376 	 */
    377 }
    378 
    379 static void
    380 register_sigchld(void)
    381 {
    382 	struct sigaction sa;
    383 	int error;
    384 
    385 	bzero(&sa, sizeof(sa));
    386 	sa.sa_handler = sigchld_handler;
    387 	sigfillset(&sa.sa_mask);
    388 	error = sigaction(SIGCHLD, &sa, NULL);
    389 	if (error != 0)
    390 		log_err(1, "sigaction");
    391 }
    392 
    393 
    394 static int
    395 wait_for_children(bool block)
    396 {
    397 	pid_t pid;
    398 	int status;
    399 	int num = 0;
    400 
    401 	for (;;) {
    402 		/*
    403 		 * If "block" is true, wait for at least one process.
    404 		 */
    405 		if (block && num == 0)
    406 			pid = wait4(-1, &status, 0, NULL);
    407 		else
    408 			pid = wait4(-1, &status, WNOHANG, NULL);
    409 		if (pid <= 0)
    410 			break;
    411 		if (WIFSIGNALED(status)) {
    412 			log_warnx("child process %d terminated with signal %d",
    413 			    pid, WTERMSIG(status));
    414 		} else if (WEXITSTATUS(status) != 0) {
    415 			log_debugx("child process %d terminated with exit "
    416 			    "status %d", pid, WEXITSTATUS(status));
    417 		} else {
    418 			log_debugx("child process %d terminated gracefully",
    419 			    pid);
    420 		}
    421 		num++;
    422 	}
    423 
    424 	return num;
    425 }
    426 
    427 __dead static void
    428 usage_automountd(void)
    429 {
    430 
    431 	fprintf(stderr, "Usage: %s [-D name=value][-m maxproc]"
    432 	    "[-o opts][-Tidv]\n", getprogname());
    433 	exit(EXIT_FAILURE);
    434 }
    435 
    436 static int
    437 load_autofs(void)
    438 {
    439 	modctl_load_t args = {
    440 		.ml_filename = "autofs",
    441 		.ml_flags = MODCTL_NO_PROP,
    442 		.ml_props = NULL,
    443 		.ml_propslen = 0
    444 	};
    445 	int error;
    446 
    447 	error = modctl(MODCTL_LOAD, &args);
    448 	if (error && errno != EEXIST)
    449 		log_warn("failed to load %s: %s", args.ml_filename,
    450 		    strerror(errno));
    451 
    452 	return error;
    453 }
    454 
    455 int
    456 main_automountd(int argc, char **argv)
    457 {
    458 	pid_t pid;
    459 	char *options = NULL;
    460 	struct autofs_daemon_request request;
    461 	int ch, debug = 0, error, maxproc = 30, saved_errno;
    462 	bool dont_daemonize = false, incomplete_hierarchy = false;
    463 
    464 	defined_init();
    465 
    466 	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
    467 		switch (ch) {
    468 		case 'D':
    469 			defined_parse_and_add(optarg);
    470 			break;
    471 		case 'T':
    472 			/*
    473 			 * For compatibility with other implementations,
    474 			 * such as OS X.
    475 			 */
    476 			debug++;
    477 			break;
    478 		case 'd':
    479 			dont_daemonize = true;
    480 			debug++;
    481 			break;
    482 		case 'i':
    483 			incomplete_hierarchy = true;
    484 			break;
    485 		case 'm':
    486 			maxproc = atoi(optarg);
    487 			break;
    488 		case 'o':
    489 			options = concat(options, ',', optarg);
    490 			break;
    491 		case 'v':
    492 			debug++;
    493 			break;
    494 		case '?':
    495 		default:
    496 			usage_automountd();
    497 		}
    498 	}
    499 	argc -= optind;
    500 	if (argc != 0)
    501 		usage_automountd();
    502 
    503 	log_init(debug);
    504 
    505 	/*
    506 	 * XXX: Workaround for NetBSD.
    507 	 * load_autofs() should be needed only if open(2) failed with ENXIO.
    508 	 * We attempt to load autofs before open(2) to suppress below warning
    509 	 * "module error: incompatible module class for `autofs' (3 != 2)",
    510 	 * which comes from sys/miscfs/specfs/spec_vnops.c:spec_open().
    511 	 * spec_open() tries to load autofs as MODULE_CLASS_DRIVER while autofs
    512 	 * is of MODULE_CLASS_VFS.
    513 	 */
    514 	load_autofs();
    515 
    516 	/*
    517 	 * NetBSD needs to check ENXIO here, but might not need ENOENT.
    518 	 */
    519 	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
    520 	if (autofs_fd < 0 && (errno == ENOENT || errno == ENXIO)) {
    521 		saved_errno = errno;
    522 		if (!load_autofs())
    523 			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
    524 		else
    525 			errno = saved_errno;
    526 	}
    527 	if (autofs_fd < 0)
    528 		log_err(1, "failed to open %s", AUTOFS_PATH);
    529 
    530 	if (dont_daemonize == false) {
    531 		if (daemon(0, 0) == -1) {
    532 			log_warn("cannot daemonize");
    533 			pidfile_clean();
    534 			exit(EXIT_FAILURE);
    535 		}
    536 	} else {
    537 		lesser_daemon();
    538 	}
    539 
    540 	/*
    541 	 * Call pidfile(3) after daemon(3).
    542 	 */
    543 	if (pidfile(NULL) == -1) {
    544 		if (errno == EEXIST)
    545 			log_errx(1, "daemon already running");
    546 		else if (errno == ENAMETOOLONG)
    547 			log_errx(1, "pidfile name too long");
    548 		log_err(1, "cannot create pidfile");
    549 	}
    550 	if (pidfile_lock(NULL) == -1)
    551 		log_err(1, "cannot lock pidfile");
    552 
    553 	register_sigchld();
    554 
    555 	for (;;) {
    556 		log_debugx("waiting for request from the kernel");
    557 
    558 		memset(&request, 0, sizeof(request));
    559 		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
    560 		if (error != 0) {
    561 			if (errno == EINTR) {
    562 				nchildren -= wait_for_children(false);
    563 				assert(nchildren >= 0);
    564 				continue;
    565 			}
    566 
    567 			log_err(1, "AUTOFSREQUEST");
    568 		}
    569 
    570 		if (dont_daemonize) {
    571 			log_debugx("not forking due to -d flag; "
    572 			    "will exit after servicing a single request");
    573 		} else {
    574 			nchildren -= wait_for_children(false);
    575 			assert(nchildren >= 0);
    576 
    577 			while (maxproc > 0 && nchildren >= maxproc) {
    578 				log_debugx("maxproc limit of %d child processes"
    579 				    " hit; waiting for child process to exit",
    580 				    maxproc);
    581 				nchildren -= wait_for_children(true);
    582 				assert(nchildren >= 0);
    583 			}
    584 			log_debugx("got request; forking child process #%d",
    585 			    nchildren);
    586 			nchildren++;
    587 
    588 			pid = fork();
    589 			if (pid < 0)
    590 				log_err(1, "fork");
    591 			if (pid > 0)
    592 				continue;
    593 		}
    594 
    595 		handle_request(&request, options, incomplete_hierarchy);
    596 	}
    597 
    598 	pidfile_clean();
    599 
    600 	return EXIT_SUCCESS;
    601 }
    602