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 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 * 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 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 160 exit_callback(void) 161 { 162 163 done(EIO, true); 164 } 165 166 __dead static void 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 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 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 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 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 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 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