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