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(1, strlen(*optionsp) + 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 static int 378 wait_for_children(bool block) 379 { 380 pid_t pid; 381 int status; 382 int num = 0; 383 384 for (;;) { 385 /* 386 * If "block" is true, wait for at least one process. 387 */ 388 if (block && num == 0) 389 pid = wait4(-1, &status, 0, NULL); 390 else 391 pid = wait4(-1, &status, WNOHANG, NULL); 392 if (pid <= 0) 393 break; 394 if (WIFSIGNALED(status)) { 395 log_warnx("child process %d terminated with signal %d", 396 pid, WTERMSIG(status)); 397 } else if (WEXITSTATUS(status) != 0) { 398 log_debugx("child process %d terminated with exit status %d", 399 pid, WEXITSTATUS(status)); 400 } else { 401 log_debugx("child process %d terminated gracefully", pid); 402 } 403 num++; 404 } 405 406 return (num); 407 } 408 409 static void 410 usage_automountd(void) 411 { 412 413 fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]" 414 "[-o opts][-Tidv]\n"); 415 exit(1); 416 } 417 418 void 419 main_automountd(int argc, char **argv) 420 { 421 struct pidfh *pidfh; 422 pid_t pid, otherpid; 423 const char *pidfile_path = AUTOMOUNTD_PIDFILE; 424 char *options = NULL; 425 struct autofs_daemon_request request; 426 int ch, debug = 0, error, maxproc = 30, retval, saved_errno; 427 bool dont_daemonize = false, incomplete_hierarchy = false; 428 429 defined_init(); 430 431 while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) { 432 switch (ch) { 433 case 'D': 434 defined_parse_and_add(optarg); 435 break; 436 case 'T': 437 /* 438 * For compatibility with other implementations, 439 * such as OS X. 440 */ 441 debug++; 442 break; 443 case 'd': 444 dont_daemonize = true; 445 debug++; 446 break; 447 case 'i': 448 incomplete_hierarchy = true; 449 break; 450 case 'm': 451 maxproc = atoi(optarg); 452 break; 453 case 'o': 454 options = concat(options, ',', optarg); 455 break; 456 case 'v': 457 debug++; 458 break; 459 case '?': 460 default: 461 usage_automountd(); 462 } 463 } 464 argc -= optind; 465 if (argc != 0) 466 usage_automountd(); 467 468 log_init(debug); 469 470 pidfh = pidfile_open(pidfile_path, 0600, &otherpid); 471 if (pidfh == NULL) { 472 if (errno == EEXIST) { 473 log_errx(1, "daemon already running, pid: %jd.", 474 (intmax_t)otherpid); 475 } 476 log_err(1, "cannot open or create pidfile \"%s\"", 477 pidfile_path); 478 } 479 480 autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); 481 if (autofs_fd < 0 && errno == ENOENT) { 482 saved_errno = errno; 483 retval = kldload("autofs"); 484 if (retval != -1) 485 autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); 486 else 487 errno = saved_errno; 488 } 489 if (autofs_fd < 0) 490 log_err(1, "failed to open %s", AUTOFS_PATH); 491 492 if (dont_daemonize == false) { 493 if (daemon(0, 0) == -1) { 494 log_warn("cannot daemonize"); 495 pidfile_remove(pidfh); 496 exit(1); 497 } 498 } else { 499 lesser_daemon(); 500 } 501 502 pidfile_write(pidfh); 503 504 register_sigchld(); 505 506 for (;;) { 507 log_debugx("waiting for request from the kernel"); 508 509 memset(&request, 0, sizeof(request)); 510 error = ioctl(autofs_fd, AUTOFSREQUEST, &request); 511 if (error != 0) { 512 if (errno == EINTR) { 513 nchildren -= wait_for_children(false); 514 assert(nchildren >= 0); 515 continue; 516 } 517 518 log_err(1, "AUTOFSREQUEST"); 519 } 520 521 if (dont_daemonize) { 522 log_debugx("not forking due to -d flag; " 523 "will exit after servicing a single request"); 524 } else { 525 nchildren -= wait_for_children(false); 526 assert(nchildren >= 0); 527 528 while (maxproc > 0 && nchildren >= maxproc) { 529 log_debugx("maxproc limit of %d child processes hit; " 530 "waiting for child process to exit", maxproc); 531 nchildren -= wait_for_children(true); 532 assert(nchildren >= 0); 533 } 534 log_debugx("got request; forking child process #%d", 535 nchildren); 536 nchildren++; 537 538 pid = fork(); 539 if (pid < 0) 540 log_err(1, "fork"); 541 if (pid > 0) 542 continue; 543 } 544 545 pidfile_close(pidfh); 546 handle_request(&request, options, incomplete_hierarchy); 547 } 548 } 549