xref: /netbsd-src/usr.sbin/autofs/automountd.c (revision d7d7f74c03a83d5f4adb8334de8735eeffc16dac)
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@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
done(int request_error,bool wildcards)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 *
pick_option(const char * option,char ** optionsp)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
create_subtree(const struct node * node,bool incomplete)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
exit_callback(void)160 exit_callback(void)
161 {
162 
163 	done(EIO, true);
164 }
165 
166 __dead static void
handle_request(const struct autofs_daemon_request * adr,char * cmdline_options,bool incomplete_hierarchy)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
sigchld_handler(int dummy __unused)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
register_sigchld(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
wait_for_children(bool block)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
usage_automountd(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
load_autofs(void)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
main_automountd(int argc,char ** argv)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