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