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