1 /*- 2 * Copyright (c) 2016 The DragonFly Project 3 * Copyright (c) 2014 The FreeBSD Foundation 4 * All rights reserved. 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/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> 46 47 #include "common.h" 48 49 #define AUTOMOUNTD_PIDFILE "/var/run/automountd.pid" 50 51 static int nchildren = 0; 52 static int autofs_fd; 53 static int request_id; 54 55 static void 56 done(int request_error, bool wildcards) 57 { 58 struct autofs_daemon_done add; 59 int error; 60 61 memset(&add, 0, sizeof(add)); 62 add.add_id = request_id; 63 add.add_wildcards = wildcards; 64 add.add_error = request_error; 65 66 log_debugx("completing request %d with error %d", 67 request_id, request_error); 68 69 error = ioctl(autofs_fd, AUTOFSDONE, &add); 70 if (error != 0) 71 log_warn("AUTOFSDONE"); 72 } 73 74 /* 75 * Remove "fstype=whatever" from optionsp and return the "whatever" part. 76 */ 77 static char * 78 pick_option(const char *option, char **optionsp) 79 { 80 char *tofree, *pair, *newoptions; 81 char *picked = NULL; 82 bool first = true; 83 84 tofree = *optionsp; 85 86 newoptions = calloc(strlen(*optionsp) + 1, 1); 87 if (newoptions == NULL) 88 log_err(1, "calloc"); 89 90 while ((pair = strsep(optionsp, ",")) != NULL) { 91 /* 92 * XXX: strncasecmp(3) perhaps? 93 */ 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); 102 } 103 } 104 105 free(tofree); 106 *optionsp = newoptions; 107 108 return (picked); 109 } 110 111 static void 112 create_subtree(const struct node *node, bool incomplete) 113 { 114 const struct node *child; 115 char *path; 116 bool wildcard_found = false; 117 118 /* 119 * Skip wildcard nodes. 120 */ 121 if (strcmp(node->n_key, "*") == 0) 122 return; 123 124 path = node_path(node); 125 log_debugx("creating subtree at %s", path); 126 create_directory(path); 127 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; 133 } 134 } 135 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; 142 } 143 } 144 145 free(path); 146 147 TAILQ_FOREACH(child, &node->n_children, n_next) 148 create_subtree(child, incomplete); 149 } 150 151 static void 152 exit_callback(void) 153 { 154 155 done(EIO, true); 156 } 157 158 static void 159 handle_request(const struct autofs_daemon_request *adr, char *cmdline_options, 160 bool incomplete_hierarchy) 161 { 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; 168 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); 172 173 /* 174 * Try to notify the kernel about any problems. 175 */ 176 request_id = adr->adr_id; 177 atexit(exit_callback); 178 179 if (strncmp(adr->adr_from, "map ", 4) != 0) { 180 log_errx(1, "invalid mountfrom \"%s\"; failing request", 181 adr->adr_from); 182 } 183 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) { 187 /* 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. 191 */ 192 parent = root; 193 key = checked_strdup(adr->adr_path); 194 } else { 195 /* 196 * Indirect map. 197 */ 198 parent = node_new_map(root, checked_strdup(adr->adr_prefix), 199 NULL, checked_strdup(map), 200 checked_strdup("[kernel request]"), lineno); 201 202 if (adr->adr_key[0] == '\0') 203 key = NULL; 204 else 205 key = checked_strdup(adr->adr_key); 206 } 207 208 /* 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. 214 */ 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"); 222 223 if (key != NULL) 224 node_expand_wildcard(root, key); 225 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); 230 } 231 232 options = node_options(node); 233 234 /* 235 * Append options from auto_master. 236 */ 237 options = concat(options, ',', adr->adr_options); 238 239 /* 240 * Prepend options passed via automountd(8) command line. 241 */ 242 options = concat(cmdline_options, ',', options); 243 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); 247 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); 253 254 /* 255 * Exit without calling exit_callback(). 256 */ 257 quick_exit(0); 258 } 259 260 /* 261 * Not a mountpoint; create directories in the autofs mount 262 * and complete the request. 263 */ 264 create_subtree(node, incomplete_hierarchy); 265 266 if (incomplete_hierarchy && key != NULL) { 267 /* 268 * We still need to create the single subdirectory 269 * user is trying to access. 270 */ 271 tmp = concat(adr->adr_path, '/', key); 272 node = node_find(root, tmp); 273 if (node != NULL) 274 create_subtree(node, false); 275 } 276 277 log_debugx("nothing to mount; exiting"); 278 done(0, wildcards); 279 280 /* 281 * Exit without calling exit_callback(). 282 */ 283 quick_exit(0); 284 } 285 286 log_debugx("found node defined at %s:%d; it is a mountpoint", 287 node->n_config_file, node->n_config_line); 288 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); 295 } 296 297 /* 298 * Append "automounted". 299 */ 300 options = concat(options, ',', "automounted"); 301 302 /* 303 * Remove "nobrowse", mount(8) doesn't understand it. 304 */ 305 pick_option("nobrowse", &options); 306 307 /* 308 * Figure out fstype. 309 */ 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"); 315 } 316 317 if (strcmp(fstype, "nfs") == 0) { 318 /* 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. 323 */ 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)); 332 } 333 } 334 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"); 341 342 log_debugx("mount done; exiting"); 343 done(0, wildcards); 344 345 /* 346 * Exit without calling exit_callback(). 347 */ 348 quick_exit(0); 349 } 350 351 static void 352 sigchld_handler(int dummy __unused) 353 { 354 355 /* 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(). 359 */ 360 } 361 362 static void 363 register_sigchld(void) 364 { 365 struct sigaction sa; 366 int error; 367 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"); 374 375 } 376 377 378 static int 379 wait_for_children(bool block) 380 { 381 pid_t pid; 382 int status; 383 int num = 0; 384 385 for (;;) { 386 /* 387 * If "block" is true, wait for at least one process. 388 */ 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); 403 } 404 num++; 405 } 406 407 return (num); 408 } 409 410 static void 411 usage_automountd(void) 412 { 413 414 fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]" 415 "[-o opts][-Tidv]\n"); 416 exit(1); 417 } 418 419 int 420 main_automountd(int argc, char **argv) 421 { 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; 429 430 defined_init(); 431 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': 438 /* 439 * For compatibility with other implementations, 440 * such as OS X. 441 */ 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(); 463 } 464 } 465 argc -= optind; 466 if (argc != 0) 467 usage_automountd(); 468 469 log_init(debug); 470 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); 476 } 477 log_err(1, "cannot open or create pidfile \"%s\"", 478 pidfile_path); 479 } 480 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; 489 } 490 if (autofs_fd < 0) 491 log_err(1, "failed to open %s", AUTOFS_PATH); 492 493 if (dont_daemonize == false) { 494 if (daemon(0, 0) == -1) { 495 log_warn("cannot daemonize"); 496 pidfile_remove(pidfh); 497 exit(1); 498 } 499 } else { 500 lesser_daemon(); 501 } 502 503 pidfile_write(pidfh); 504 505 register_sigchld(); 506 507 for (;;) { 508 log_debugx("waiting for request from the kernel"); 509 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; 517 } 518 519 log_err(1, "AUTOFSREQUEST"); 520 } 521 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); 528 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); 534 } 535 log_debugx("got request; forking child process #%d", 536 nchildren); 537 nchildren++; 538 539 pid = fork(); 540 if (pid < 0) 541 log_err(1, "fork"); 542 if (pid > 0) 543 continue; 544 } 545 546 pidfile_close(pidfh); 547 handle_request(&request, options, incomplete_hierarchy); 548 } 549 550 pidfile_close(pidfh); 551 552 return (0); 553 } 554 555