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