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