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