automountd.c revision 270276
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: head/usr.sbin/autofs/automountd.c 270276 2014-08-21 15:07:25Z trasz $");
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
66static int nchildren = 0;
67static int autofs_fd;
68static int request_id;
69
70static void
71done(int request_error)
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_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		/*
86		 * Do this instead of log_err() to avoid calling
87		 * done() again with error, from atexit handler.
88		 */
89		log_warn("AUTOFSDONE");
90	}
91	quick_exit(1);
92}
93
94/*
95 * Remove "fstype=whatever" from optionsp and return the "whatever" part.
96 */
97static char *
98pick_option(const char *option, char **optionsp)
99{
100	char *tofree, *pair, *newoptions;
101	char *picked = NULL;
102	bool first = true;
103
104	tofree = *optionsp;
105
106	newoptions = calloc(strlen(*optionsp) + 1, 1);
107	if (newoptions == NULL)
108		log_err(1, "calloc");
109
110	while ((pair = strsep(optionsp, ",")) != NULL) {
111		/*
112		 * XXX: strncasecmp(3) perhaps?
113		 */
114		if (strncmp(pair, option, strlen(option)) == 0) {
115			picked = checked_strdup(pair + strlen(option));
116		} else {
117			if (first == false)
118				strcat(newoptions, ",");
119			else
120				first = false;
121			strcat(newoptions, pair);
122		}
123	}
124
125	free(tofree);
126	*optionsp = newoptions;
127
128	return (picked);
129}
130
131static void
132create_subtree(const struct node *node, bool incomplete)
133{
134	const struct node *child;
135	char *path;
136	bool wildcard_found = false;
137
138	/*
139	 * Skip wildcard nodes.
140	 */
141	if (strcmp(node->n_key, "*") == 0)
142		return;
143
144	path = node_path(node);
145	log_debugx("creating subtree at %s", path);
146	create_directory(path);
147
148	if (incomplete) {
149		TAILQ_FOREACH(child, &node->n_children, n_next) {
150			if (strcmp(child->n_key, "*") == 0) {
151				wildcard_found = true;
152				break;
153			}
154		}
155
156		if (wildcard_found) {
157			log_debugx("node %s contains wildcard entry; "
158			    "not creating its subdirectories due to -d flag",
159			    path);
160			free(path);
161			return;
162		}
163	}
164
165	free(path);
166
167	TAILQ_FOREACH(child, &node->n_children, n_next)
168		create_subtree(child, incomplete);
169}
170
171static void
172exit_callback(void)
173{
174
175	done(EIO);
176}
177
178static void
179handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
180    bool incomplete_hierarchy)
181{
182	const char *map;
183	struct node *root, *parent, *node;
184	FILE *f;
185	char *options, *fstype, *retrycnt, *tmp;
186	int error;
187
188	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
189	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
190	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
191
192	/*
193	 * Try to notify the kernel about any problems.
194	 */
195	request_id = adr->adr_id;
196	atexit(exit_callback);
197
198	if (strncmp(adr->adr_from, "map ", 4) != 0) {
199		log_errx(1, "invalid mountfrom \"%s\"; failing request",
200		    adr->adr_from);
201	}
202
203	map = adr->adr_from + 4; /* 4 for strlen("map "); */
204	root = node_new_root();
205	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
206		parent = root;
207	} else {
208		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
209		    checked_strdup(adr->adr_options), checked_strdup(map),
210		    checked_strdup("[kernel request]"), lineno);
211	}
212	parse_map(parent, map, adr->adr_key[0] != '\0' ? adr->adr_key : NULL);
213	if (adr->adr_key[0] != '\0')
214		node_expand_wildcard(root, adr->adr_key);
215	node = node_find(root, adr->adr_path);
216	if (node == NULL) {
217		log_errx(1, "map %s does not contain key for \"%s\"; "
218		    "failing mount", map, adr->adr_path);
219	}
220
221	if (node->n_location == NULL) {
222		log_debugx("found node defined at %s:%d; not a mountpoint",
223		    node->n_config_file, node->n_config_line);
224
225		/*
226		 * Not a mountpoint; create directories in the autofs mount
227		 * and complete the request.
228		 */
229		create_subtree(node, incomplete_hierarchy);
230
231		if (incomplete_hierarchy && adr->adr_key[0] != '\0') {
232			/*
233			 * We still need to create the single subdirectory
234			 * user is trying to access.
235			 */
236			tmp = separated_concat(adr->adr_path,
237			    adr->adr_key, '/');
238			node = node_find(root, tmp);
239			if (node != NULL)
240				create_subtree(node, false);
241		}
242		done(0);
243
244		log_debugx("nothing to mount; exiting");
245
246		/*
247		 * Exit without calling exit_callback().
248		 */
249		quick_exit(0);
250	}
251
252	log_debugx("found node defined at %s:%d; it is a mountpoint",
253	    node->n_config_file, node->n_config_line);
254
255	node_expand_ampersand(node,
256	    adr->adr_key[0] != '\0' ? adr->adr_key : NULL);
257	error = node_expand_defined(node);
258	if (error != 0) {
259		log_errx(1, "variable expansion failed for %s; "
260		    "failing mount", adr->adr_path);
261	}
262
263	options = node_options(node);
264
265	/*
266	 * Prepend options passed via automountd(8) command line.
267	 */
268	if (cmdline_options != NULL)
269		options = separated_concat(cmdline_options, options, ',');
270
271	/*
272	 * Append "automounted".
273	 */
274	options = separated_concat(options, "automounted", ',');
275
276	/*
277	 * Figure out fstype.
278	 */
279	fstype = pick_option("fstype=", &options);
280	if (fstype == NULL) {
281		log_debugx("fstype not specified in options; "
282		    "defaulting to \"nfs\"");
283		fstype = checked_strdup("nfs");
284	}
285
286	if (strcmp(fstype, "nfs") == 0) {
287		/*
288		 * The mount_nfs(8) command defaults to retry undefinitely.
289		 * We do not want that behaviour, because it leaves mount_nfs(8)
290		 * instances and automountd(8) children hanging forever.
291		 * Disable retries unless the option was passed explicitly.
292		 */
293		retrycnt = pick_option("retrycnt=", &options);
294		if (retrycnt == NULL) {
295			log_debugx("retrycnt not specified in options; "
296			    "defaulting to 1");
297			options = separated_concat(options,
298			    separated_concat("retrycnt", "1", '='), ',');
299		} else {
300			options = separated_concat(options,
301			    separated_concat("retrycnt", retrycnt, '='), ',');
302		}
303	}
304
305	f = auto_popen("mount", "-t", fstype, "-o", options,
306	    node->n_location, adr->adr_path, NULL);
307	assert(f != NULL);
308	error = auto_pclose(f);
309	if (error != 0)
310		log_errx(1, "mount failed");
311
312	done(0);
313	log_debugx("mount done; exiting");
314
315	/*
316	 * Exit without calling exit_callback().
317	 */
318	quick_exit(0);
319}
320
321static int
322wait_for_children(bool block)
323{
324	pid_t pid;
325	int status;
326	int num = 0;
327
328	for (;;) {
329		/*
330		 * If "block" is true, wait for at least one process.
331		 */
332		if (block && num == 0)
333			pid = wait4(-1, &status, 0, NULL);
334		else
335			pid = wait4(-1, &status, WNOHANG, NULL);
336		if (pid <= 0)
337			break;
338		if (WIFSIGNALED(status)) {
339			log_warnx("child process %d terminated with signal %d",
340			    pid, WTERMSIG(status));
341		} else if (WEXITSTATUS(status) != 0) {
342			log_warnx("child process %d terminated with exit status %d",
343			    pid, WEXITSTATUS(status));
344		} else {
345			log_debugx("child process %d terminated gracefully", pid);
346		}
347		num++;
348	}
349
350	return (num);
351}
352
353static void
354usage_automountd(void)
355{
356
357	fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
358	    "[-o opts][-Tidv]\n");
359	exit(1);
360}
361
362int
363main_automountd(int argc, char **argv)
364{
365	struct pidfh *pidfh;
366	pid_t pid, otherpid;
367	const char *pidfile_path = AUTOMOUNTD_PIDFILE;
368	char *options = NULL;
369	struct autofs_daemon_request request;
370	int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
371	bool dont_daemonize = false, incomplete_hierarchy = false;
372
373	defined_init();
374
375	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
376		switch (ch) {
377		case 'D':
378			defined_parse_and_add(optarg);
379			break;
380		case 'T':
381			/*
382			 * For compatibility with other implementations,
383			 * such as OS X.
384			 */
385			debug++;
386			break;
387		case 'd':
388			dont_daemonize = true;
389			debug++;
390			break;
391		case 'i':
392			incomplete_hierarchy = true;
393			break;
394		case 'm':
395			maxproc = atoi(optarg);
396			break;
397		case 'o':
398			if (options == NULL) {
399				options = checked_strdup(optarg);
400			} else {
401				options =
402				    separated_concat(options, optarg, ',');
403			}
404			break;
405		case 'v':
406			debug++;
407			break;
408		case '?':
409		default:
410			usage_automountd();
411		}
412	}
413	argc -= optind;
414	if (argc != 0)
415		usage_automountd();
416
417	log_init(debug);
418
419	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
420	if (pidfh == NULL) {
421		if (errno == EEXIST) {
422			log_errx(1, "daemon already running, pid: %jd.",
423			    (intmax_t)otherpid);
424		}
425		log_err(1, "cannot open or create pidfile \"%s\"",
426		    pidfile_path);
427	}
428
429	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
430	if (autofs_fd < 0 && errno == ENOENT) {
431		saved_errno = errno;
432		retval = kldload("autofs");
433		if (retval != -1)
434			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
435		else
436			errno = saved_errno;
437	}
438	if (autofs_fd < 0)
439		log_err(1, "failed to open %s", AUTOFS_PATH);
440
441	if (dont_daemonize == false) {
442		if (daemon(0, 0) == -1) {
443			log_warn("cannot daemonize");
444			pidfile_remove(pidfh);
445			exit(1);
446		}
447	} else {
448		lesser_daemon();
449	}
450
451	pidfile_write(pidfh);
452
453	for (;;) {
454		log_debugx("waiting for request from the kernel");
455
456		memset(&request, 0, sizeof(request));
457		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
458		if (error != 0) {
459			if (errno == EINTR) {
460				nchildren -= wait_for_children(false);
461				assert(nchildren >= 0);
462				continue;
463			}
464
465			log_err(1, "AUTOFSREQUEST");
466		}
467
468		if (dont_daemonize) {
469			log_debugx("not forking due to -d flag; "
470			    "will exit after servicing a single request");
471		} else {
472			nchildren -= wait_for_children(false);
473			assert(nchildren >= 0);
474
475			while (maxproc > 0 && nchildren >= maxproc) {
476				log_debugx("maxproc limit of %d child processes hit; "
477				    "waiting for child process to exit", maxproc);
478				nchildren -= wait_for_children(true);
479				assert(nchildren >= 0);
480			}
481			log_debugx("got request; forking child process #%d",
482			    nchildren);
483			nchildren++;
484
485			pid = fork();
486			if (pid < 0)
487				log_err(1, "fork");
488			if (pid > 0)
489				continue;
490		}
491
492		pidfile_close(pidfh);
493		handle_request(&request, options, incomplete_hierarchy);
494	}
495
496	pidfile_close(pidfh);
497
498	return (0);
499}
500
501