xref: /freebsd/usr.sbin/autofs/automountd.c (revision ff0ba87247820afbdfdc1b307c803f7923d0e4d3)
1 /*-
2  * Copyright (c) 2014 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Edward Tomasz Napierala under sponsorship
6  * from the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <sys/types.h>
35 #include <sys/time.h>
36 #include <sys/ioctl.h>
37 #include <sys/param.h>
38 #include <sys/linker.h>
39 #include <sys/mount.h>
40 #include <sys/socket.h>
41 #include <sys/stat.h>
42 #include <sys/wait.h>
43 #include <sys/utsname.h>
44 #include <assert.h>
45 #include <ctype.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <libgen.h>
49 #include <netdb.h>
50 #include <signal.h>
51 #include <stdbool.h>
52 #include <stdint.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 #include <libutil.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 		/*
87 		 * Do this instead of log_err() to avoid calling
88 		 * done() again with error, from atexit handler.
89 		 */
90 		log_warn("AUTOFSDONE");
91 	}
92 	quick_exit(1);
93 }
94 
95 /*
96  * Remove "fstype=whatever" from optionsp and return the "whatever" part.
97  */
98 static char *
99 pick_option(const char *option, char **optionsp)
100 {
101 	char *tofree, *pair, *newoptions;
102 	char *picked = NULL;
103 	bool first = true;
104 
105 	tofree = *optionsp;
106 
107 	newoptions = calloc(strlen(*optionsp) + 1, 1);
108 	if (newoptions == NULL)
109 		log_err(1, "calloc");
110 
111 	while ((pair = strsep(optionsp, ",")) != NULL) {
112 		/*
113 		 * XXX: strncasecmp(3) perhaps?
114 		 */
115 		if (strncmp(pair, option, strlen(option)) == 0) {
116 			picked = checked_strdup(pair + strlen(option));
117 		} else {
118 			if (first == false)
119 				strcat(newoptions, ",");
120 			else
121 				first = false;
122 			strcat(newoptions, pair);
123 		}
124 	}
125 
126 	free(tofree);
127 	*optionsp = newoptions;
128 
129 	return (picked);
130 }
131 
132 static void
133 create_subtree(const struct node *node, bool incomplete)
134 {
135 	const struct node *child;
136 	char *path;
137 	bool wildcard_found = false;
138 
139 	/*
140 	 * Skip wildcard nodes.
141 	 */
142 	if (strcmp(node->n_key, "*") == 0)
143 		return;
144 
145 	path = node_path(node);
146 	log_debugx("creating subtree at %s", path);
147 	create_directory(path);
148 
149 	if (incomplete) {
150 		TAILQ_FOREACH(child, &node->n_children, n_next) {
151 			if (strcmp(child->n_key, "*") == 0) {
152 				wildcard_found = true;
153 				break;
154 			}
155 		}
156 
157 		if (wildcard_found) {
158 			log_debugx("node %s contains wildcard entry; "
159 			    "not creating its subdirectories due to -d flag",
160 			    path);
161 			free(path);
162 			return;
163 		}
164 	}
165 
166 	free(path);
167 
168 	TAILQ_FOREACH(child, &node->n_children, n_next)
169 		create_subtree(child, incomplete);
170 }
171 
172 static void
173 exit_callback(void)
174 {
175 
176 	done(EIO, true);
177 }
178 
179 static void
180 handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
181     bool incomplete_hierarchy)
182 {
183 	const char *map;
184 	struct node *root, *parent, *node;
185 	FILE *f;
186 	char *options, *fstype, *nobrowse, *retrycnt, *tmp;
187 	int error;
188 	bool wildcards;
189 
190 	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
191 	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
192 	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
193 
194 	/*
195 	 * Try to notify the kernel about any problems.
196 	 */
197 	request_id = adr->adr_id;
198 	atexit(exit_callback);
199 
200 	if (strncmp(adr->adr_from, "map ", 4) != 0) {
201 		log_errx(1, "invalid mountfrom \"%s\"; failing request",
202 		    adr->adr_from);
203 	}
204 
205 	map = adr->adr_from + 4; /* 4 for strlen("map "); */
206 	root = node_new_root();
207 	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
208 		parent = root;
209 	} else {
210 		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
211 		    checked_strdup(adr->adr_options), checked_strdup(map),
212 		    checked_strdup("[kernel request]"), lineno);
213 	}
214 
215 	/*
216 	 * "Wildcards" here actually means "make autofs(4) request
217 	 * automountd(8) action if the node being looked up does not
218 	 * exist, even though the parent is marked as cached".  This
219 	 * needs to be done for maps with wildcard entries, but also
220 	 * for special and executable maps.
221 	 */
222 	parse_map(parent, map, adr->adr_key[0] != '\0' ? adr->adr_key : NULL,
223 	    &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 (adr->adr_key[0] != '\0')
232 		node_expand_wildcard(root, adr->adr_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 	if (node->n_location == NULL) {
241 		log_debugx("found node defined at %s:%d; not a mountpoint",
242 		    node->n_config_file, node->n_config_line);
243 
244 		options = node_options(node);
245 
246 		/*
247 		 * Prepend options passed via automountd(8) command line.
248 		 */
249 		if (cmdline_options != NULL) {
250 			options =
251 			    separated_concat(cmdline_options, options, ',');
252 		}
253 
254 		nobrowse = pick_option("nobrowse", &options);
255 		if (nobrowse != NULL && adr->adr_key[0] == '\0') {
256 			log_debugx("skipping map %s due to \"nobrowse\" "
257 			    "option; exiting", map);
258 			done(0, true);
259 
260 			/*
261 			 * Exit without calling exit_callback().
262 			 */
263 			quick_exit(0);
264 		}
265 
266 		/*
267 		 * Not a mountpoint; create directories in the autofs mount
268 		 * and complete the request.
269 		 */
270 		create_subtree(node, incomplete_hierarchy);
271 
272 		if (incomplete_hierarchy && adr->adr_key[0] != '\0') {
273 			/*
274 			 * We still need to create the single subdirectory
275 			 * user is trying to access.
276 			 */
277 			tmp = separated_concat(adr->adr_path,
278 			    adr->adr_key, '/');
279 			node = node_find(root, tmp);
280 			if (node != NULL)
281 				create_subtree(node, false);
282 		}
283 
284 		log_debugx("nothing to mount; exiting");
285 		done(0, wildcards);
286 
287 		/*
288 		 * Exit without calling exit_callback().
289 		 */
290 		quick_exit(0);
291 	}
292 
293 	log_debugx("found node defined at %s:%d; it is a mountpoint",
294 	    node->n_config_file, node->n_config_line);
295 
296 	node_expand_ampersand(node,
297 	    adr->adr_key[0] != '\0' ? adr->adr_key : NULL);
298 	error = node_expand_defined(node);
299 	if (error != 0) {
300 		log_errx(1, "variable expansion failed for %s; "
301 		    "failing mount", adr->adr_path);
302 	}
303 
304 	options = node_options(node);
305 
306 	/*
307 	 * Prepend options passed via automountd(8) command line.
308 	 */
309 	if (cmdline_options != NULL)
310 		options = separated_concat(cmdline_options, options, ',');
311 
312 	/*
313 	 * Append "automounted".
314 	 */
315 	options = separated_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 = separated_concat(options,
344 			    separated_concat("retrycnt", "1", '='), ',');
345 		} else {
346 			options = separated_concat(options,
347 			    separated_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 			if (options == NULL) {
472 				options = checked_strdup(optarg);
473 			} else {
474 				options =
475 				    separated_concat(options, optarg, ',');
476 			}
477 			break;
478 		case 'v':
479 			debug++;
480 			break;
481 		case '?':
482 		default:
483 			usage_automountd();
484 		}
485 	}
486 	argc -= optind;
487 	if (argc != 0)
488 		usage_automountd();
489 
490 	log_init(debug);
491 
492 	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
493 	if (pidfh == NULL) {
494 		if (errno == EEXIST) {
495 			log_errx(1, "daemon already running, pid: %jd.",
496 			    (intmax_t)otherpid);
497 		}
498 		log_err(1, "cannot open or create pidfile \"%s\"",
499 		    pidfile_path);
500 	}
501 
502 	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
503 	if (autofs_fd < 0 && errno == ENOENT) {
504 		saved_errno = errno;
505 		retval = kldload("autofs");
506 		if (retval != -1)
507 			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
508 		else
509 			errno = saved_errno;
510 	}
511 	if (autofs_fd < 0)
512 		log_err(1, "failed to open %s", AUTOFS_PATH);
513 
514 	if (dont_daemonize == false) {
515 		if (daemon(0, 0) == -1) {
516 			log_warn("cannot daemonize");
517 			pidfile_remove(pidfh);
518 			exit(1);
519 		}
520 	} else {
521 		lesser_daemon();
522 	}
523 
524 	pidfile_write(pidfh);
525 
526 	register_sigchld();
527 
528 	for (;;) {
529 		log_debugx("waiting for request from the kernel");
530 
531 		memset(&request, 0, sizeof(request));
532 		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
533 		if (error != 0) {
534 			if (errno == EINTR) {
535 				nchildren -= wait_for_children(false);
536 				assert(nchildren >= 0);
537 				continue;
538 			}
539 
540 			log_err(1, "AUTOFSREQUEST");
541 		}
542 
543 		if (dont_daemonize) {
544 			log_debugx("not forking due to -d flag; "
545 			    "will exit after servicing a single request");
546 		} else {
547 			nchildren -= wait_for_children(false);
548 			assert(nchildren >= 0);
549 
550 			while (maxproc > 0 && nchildren >= maxproc) {
551 				log_debugx("maxproc limit of %d child processes hit; "
552 				    "waiting for child process to exit", maxproc);
553 				nchildren -= wait_for_children(true);
554 				assert(nchildren >= 0);
555 			}
556 			log_debugx("got request; forking child process #%d",
557 			    nchildren);
558 			nchildren++;
559 
560 			pid = fork();
561 			if (pid < 0)
562 				log_err(1, "fork");
563 			if (pid > 0)
564 				continue;
565 		}
566 
567 		pidfile_close(pidfh);
568 		handle_request(&request, options, incomplete_hierarchy);
569 	}
570 
571 	pidfile_close(pidfh);
572 
573 	return (0);
574 }
575 
576