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