time: Use clock_gettime
[dragonfly.git] / usr.sbin / autofs / automountd.c
blobbbdb4ddf6bab38c2bc2d8b9c13d1e47d60e1fc86
1 /*-
2 * Copyright (c) 2016 The DragonFly Project
3 * Copyright (c) 2014 The FreeBSD Foundation
4 * All rights reserved.
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
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.
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.
32 #include <sys/types.h>
33 #include <sys/ioctl.h>
34 #include <sys/linker.h>
35 #include <sys/wait.h>
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <libutil.h>
45 #include <vfs/autofs/autofs_ioctl.h>
47 #include "common.h"
49 #define AUTOMOUNTD_PIDFILE "/var/run/automountd.pid"
51 static int nchildren = 0;
52 static int autofs_fd;
53 static int request_id;
55 static void
56 done(int request_error, bool wildcards)
58 struct autofs_daemon_done add;
59 int error;
61 memset(&add, 0, sizeof(add));
62 add.add_id = request_id;
63 add.add_wildcards = wildcards;
64 add.add_error = request_error;
66 log_debugx("completing request %d with error %d",
67 request_id, request_error);
69 error = ioctl(autofs_fd, AUTOFSDONE, &add);
70 if (error != 0)
71 log_warn("AUTOFSDONE");
75 * Remove "fstype=whatever" from optionsp and return the "whatever" part.
77 static char *
78 pick_option(const char *option, char **optionsp)
80 char *tofree, *pair, *newoptions;
81 char *picked = NULL;
82 bool first = true;
84 tofree = *optionsp;
86 newoptions = calloc(1, strlen(*optionsp) + 1);
87 if (newoptions == NULL)
88 log_err(1, "calloc");
90 while ((pair = strsep(optionsp, ",")) != NULL) {
92 * XXX: strncasecmp(3) perhaps?
94 if (strncmp(pair, option, strlen(option)) == 0) {
95 picked = checked_strdup(pair + strlen(option));
96 } else {
97 if (first == false)
98 strcat(newoptions, ",");
99 else
100 first = false;
101 strcat(newoptions, pair);
105 free(tofree);
106 *optionsp = newoptions;
108 return (picked);
111 static void
112 create_subtree(const struct node *node, bool incomplete)
114 const struct node *child;
115 char *path;
116 bool wildcard_found = false;
119 * Skip wildcard nodes.
121 if (strcmp(node->n_key, "*") == 0)
122 return;
124 path = node_path(node);
125 log_debugx("creating subtree at %s", path);
126 create_directory(path);
128 if (incomplete) {
129 TAILQ_FOREACH(child, &node->n_children, n_next) {
130 if (strcmp(child->n_key, "*") == 0) {
131 wildcard_found = true;
132 break;
136 if (wildcard_found) {
137 log_debugx("node %s contains wildcard entry; "
138 "not creating its subdirectories due to -d flag",
139 path);
140 free(path);
141 return;
145 free(path);
147 TAILQ_FOREACH(child, &node->n_children, n_next)
148 create_subtree(child, incomplete);
151 static void
152 exit_callback(void)
155 done(EIO, true);
158 static void
159 handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
160 bool incomplete_hierarchy)
162 const char *map;
163 struct node *root, *parent, *node;
164 FILE *f;
165 char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
166 int error;
167 bool wildcards;
169 log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
170 "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
171 adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
174 * Try to notify the kernel about any problems.
176 request_id = adr->adr_id;
177 atexit(exit_callback);
179 if (strncmp(adr->adr_from, "map ", 4) != 0) {
180 log_errx(1, "invalid mountfrom \"%s\"; failing request",
181 adr->adr_from);
184 map = adr->adr_from + 4; /* 4 for strlen("map "); */
185 root = node_new_root();
186 if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
188 * Direct map. autofs(4) doesn't have a way to determine
189 * correct map key, but since it's a direct map, we can just
190 * use adr_path instead.
192 parent = root;
193 key = checked_strdup(adr->adr_path);
194 } else {
196 * Indirect map.
198 parent = node_new_map(root, checked_strdup(adr->adr_prefix),
199 NULL, checked_strdup(map),
200 checked_strdup("[kernel request]"), lineno);
202 if (adr->adr_key[0] == '\0')
203 key = NULL;
204 else
205 key = checked_strdup(adr->adr_key);
209 * "Wildcards" here actually means "make autofs(4) request
210 * automountd(8) action if the node being looked up does not
211 * exist, even though the parent is marked as cached". This
212 * needs to be done for maps with wildcard entries, but also
213 * for special and executable maps.
215 parse_map(parent, map, key, &wildcards);
216 if (!wildcards)
217 wildcards = node_has_wildcards(parent);
218 if (wildcards)
219 log_debugx("map may contain wildcard entries");
220 else
221 log_debugx("map does not contain wildcard entries");
223 if (key != NULL)
224 node_expand_wildcard(root, key);
226 node = node_find(root, adr->adr_path);
227 if (node == NULL) {
228 log_errx(1, "map %s does not contain key for \"%s\"; "
229 "failing mount", map, adr->adr_path);
232 options = node_options(node);
235 * Append options from auto_master.
237 options = concat(options, ',', adr->adr_options);
240 * Prepend options passed via automountd(8) command line.
242 options = concat(cmdline_options, ',', options);
244 if (node->n_location == NULL) {
245 log_debugx("found node defined at %s:%d; not a mountpoint",
246 node->n_config_file, node->n_config_line);
248 nobrowse = pick_option("nobrowse", &options);
249 if (nobrowse != NULL && key == NULL) {
250 log_debugx("skipping map %s due to \"nobrowse\" "
251 "option; exiting", map);
252 done(0, true);
255 * Exit without calling exit_callback().
257 quick_exit(0);
261 * Not a mountpoint; create directories in the autofs mount
262 * and complete the request.
264 create_subtree(node, incomplete_hierarchy);
266 if (incomplete_hierarchy && key != NULL) {
268 * We still need to create the single subdirectory
269 * user is trying to access.
271 tmp = concat(adr->adr_path, '/', key);
272 node = node_find(root, tmp);
273 if (node != NULL)
274 create_subtree(node, false);
277 log_debugx("nothing to mount; exiting");
278 done(0, wildcards);
281 * Exit without calling exit_callback().
283 quick_exit(0);
286 log_debugx("found node defined at %s:%d; it is a mountpoint",
287 node->n_config_file, node->n_config_line);
289 if (key != NULL)
290 node_expand_ampersand(node, key);
291 error = node_expand_defined(node);
292 if (error != 0) {
293 log_errx(1, "variable expansion failed for %s; "
294 "failing mount", adr->adr_path);
298 * Append "automounted".
300 options = concat(options, ',', "automounted");
303 * Remove "nobrowse", mount(8) doesn't understand it.
305 pick_option("nobrowse", &options);
308 * Figure out fstype.
310 fstype = pick_option("fstype=", &options);
311 if (fstype == NULL) {
312 log_debugx("fstype not specified in options; "
313 "defaulting to \"nfs\"");
314 fstype = checked_strdup("nfs");
317 if (strcmp(fstype, "nfs") == 0) {
319 * The mount_nfs(8) command defaults to retry undefinitely.
320 * We do not want that behaviour, because it leaves mount_nfs(8)
321 * instances and automountd(8) children hanging forever.
322 * Disable retries unless the option was passed explicitly.
324 retrycnt = pick_option("retrycnt=", &options);
325 if (retrycnt == NULL) {
326 log_debugx("retrycnt not specified in options; "
327 "defaulting to 1");
328 options = concat(options, ',', "retrycnt=1");
329 } else {
330 options = concat(options, ',',
331 concat("retrycnt", '=', retrycnt));
335 f = auto_popen("mount", "-t", fstype, "-o", options,
336 node->n_location, adr->adr_path, NULL);
337 assert(f != NULL);
338 error = auto_pclose(f);
339 if (error != 0)
340 log_errx(1, "mount failed");
342 log_debugx("mount done; exiting");
343 done(0, wildcards);
346 * Exit without calling exit_callback().
348 quick_exit(0);
351 static void
352 sigchld_handler(int dummy __unused)
356 * The only purpose of this handler is to make SIGCHLD
357 * interrupt the AUTOFSREQUEST ioctl(2), so we can call
358 * wait_for_children().
362 static void
363 register_sigchld(void)
365 struct sigaction sa;
366 int error;
368 bzero(&sa, sizeof(sa));
369 sa.sa_handler = sigchld_handler;
370 sigfillset(&sa.sa_mask);
371 error = sigaction(SIGCHLD, &sa, NULL);
372 if (error != 0)
373 log_err(1, "sigaction");
378 static int
379 wait_for_children(bool block)
381 pid_t pid;
382 int status;
383 int num = 0;
385 for (;;) {
387 * If "block" is true, wait for at least one process.
389 if (block && num == 0)
390 pid = wait4(-1, &status, 0, NULL);
391 else
392 pid = wait4(-1, &status, WNOHANG, NULL);
393 if (pid <= 0)
394 break;
395 if (WIFSIGNALED(status)) {
396 log_warnx("child process %d terminated with signal %d",
397 pid, WTERMSIG(status));
398 } else if (WEXITSTATUS(status) != 0) {
399 log_debugx("child process %d terminated with exit status %d",
400 pid, WEXITSTATUS(status));
401 } else {
402 log_debugx("child process %d terminated gracefully", pid);
404 num++;
407 return (num);
410 static void
411 usage_automountd(void)
414 fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
415 "[-o opts][-Tidv]\n");
416 exit(1);
420 main_automountd(int argc, char **argv)
422 struct pidfh *pidfh;
423 pid_t pid, otherpid;
424 const char *pidfile_path = AUTOMOUNTD_PIDFILE;
425 char *options = NULL;
426 struct autofs_daemon_request request;
427 int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
428 bool dont_daemonize = false, incomplete_hierarchy = false;
430 defined_init();
432 while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
433 switch (ch) {
434 case 'D':
435 defined_parse_and_add(optarg);
436 break;
437 case 'T':
439 * For compatibility with other implementations,
440 * such as OS X.
442 debug++;
443 break;
444 case 'd':
445 dont_daemonize = true;
446 debug++;
447 break;
448 case 'i':
449 incomplete_hierarchy = true;
450 break;
451 case 'm':
452 maxproc = atoi(optarg);
453 break;
454 case 'o':
455 options = concat(options, ',', optarg);
456 break;
457 case 'v':
458 debug++;
459 break;
460 case '?':
461 default:
462 usage_automountd();
465 argc -= optind;
466 if (argc != 0)
467 usage_automountd();
469 log_init(debug);
471 pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
472 if (pidfh == NULL) {
473 if (errno == EEXIST) {
474 log_errx(1, "daemon already running, pid: %jd.",
475 (intmax_t)otherpid);
477 log_err(1, "cannot open or create pidfile \"%s\"",
478 pidfile_path);
481 autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
482 if (autofs_fd < 0 && errno == ENOENT) {
483 saved_errno = errno;
484 retval = kldload("autofs");
485 if (retval != -1)
486 autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
487 else
488 errno = saved_errno;
490 if (autofs_fd < 0)
491 log_err(1, "failed to open %s", AUTOFS_PATH);
493 if (dont_daemonize == false) {
494 if (daemon(0, 0) == -1) {
495 log_warn("cannot daemonize");
496 pidfile_remove(pidfh);
497 exit(1);
499 } else {
500 lesser_daemon();
503 pidfile_write(pidfh);
505 register_sigchld();
507 for (;;) {
508 log_debugx("waiting for request from the kernel");
510 memset(&request, 0, sizeof(request));
511 error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
512 if (error != 0) {
513 if (errno == EINTR) {
514 nchildren -= wait_for_children(false);
515 assert(nchildren >= 0);
516 continue;
519 log_err(1, "AUTOFSREQUEST");
522 if (dont_daemonize) {
523 log_debugx("not forking due to -d flag; "
524 "will exit after servicing a single request");
525 } else {
526 nchildren -= wait_for_children(false);
527 assert(nchildren >= 0);
529 while (maxproc > 0 && nchildren >= maxproc) {
530 log_debugx("maxproc limit of %d child processes hit; "
531 "waiting for child process to exit", maxproc);
532 nchildren -= wait_for_children(true);
533 assert(nchildren >= 0);
535 log_debugx("got request; forking child process #%d",
536 nchildren);
537 nchildren++;
539 pid = fork();
540 if (pid < 0)
541 log_err(1, "fork");
542 if (pid > 0)
543 continue;
546 pidfile_close(pidfh);
547 handle_request(&request, options, incomplete_hierarchy);
550 pidfile_close(pidfh);
552 return (0);