1 /* 2 * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com> 3 * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com> 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 */ 24 25 26 #include <sys/resource.h> 27 #include <sys/types.h> 28 #include <sys/time.h> 29 #include <sys/stat.h> 30 #include <stdbool.h> 31 #include <unistd.h> 32 #include <fcntl.h> 33 #include <stdio.h> 34 #include <time.h> 35 #include <regex.h> 36 #include <search.h> 37 #include <dirent.h> 38 #include <string.h> 39 #include <stdlib.h> 40 #include <limits.h> 41 #include <errno.h> 42 #include <libzfs.h> 43 44 /* 45 * For debugging only. 46 * 47 * Free statics with trivial life-times, 48 * but saved line filenames are replaced with a static string. 49 */ 50 #define FREE_STATICS false 51 52 #define nitems(arr) (sizeof (arr) / sizeof (*arr)) 53 #define STRCMP ((int(*)(const void *, const void *))&strcmp) 54 55 56 #define PROGNAME "zfs-mount-generator" 57 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache" 58 #define ZFS SBINDIR "/zfs" 59 60 #define OUTPUT_HEADER \ 61 "# Automatically generated by " PROGNAME "\n" \ 62 "\n" 63 64 /* 65 * Starts like the one in libzfs_util.c but also matches "//" 66 * and captures until the end, since we actually use it for path extraxion 67 */ 68 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$" 69 static regex_t uri_regex; 70 71 static const char *destdir = "/tmp"; 72 static int destdir_fd = -1; 73 74 static void *known_pools = NULL; /* tsearch() of C strings */ 75 static void *noauto_files = NULL; /* tsearch() of C strings */ 76 77 78 static char * 79 systemd_escape(const char *input, const char *prepend, const char *append) 80 { 81 size_t len = strlen(input); 82 size_t applen = strlen(append); 83 size_t prelen = strlen(prepend); 84 char *ret = malloc(4 * len + prelen + applen + 1); 85 if (!ret) { 86 fprintf(stderr, PROGNAME "[%d]: " 87 "out of memory to escape \"%s%s%s\"!\n", 88 getpid(), prepend, input, append); 89 return (NULL); 90 } 91 92 memcpy(ret, prepend, prelen); 93 char *out = ret + prelen; 94 95 const char *cur = input; 96 if (*cur == '.') { 97 memcpy(out, "\\x2e", 4); 98 out += 4; 99 ++cur; 100 } 101 for (; *cur; ++cur) { 102 if (*cur == '/') 103 *(out++) = '-'; 104 else if (strchr( 105 "0123456789" 106 "abcdefghijklmnopqrstuvwxyz" 107 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 108 ":_.", *cur)) 109 *(out++) = *cur; 110 else { 111 sprintf(out, "\\x%02x", (int)*cur); 112 out += 4; 113 } 114 } 115 116 memcpy(out, append, applen + 1); 117 return (ret); 118 } 119 120 static void 121 simplify_path(char *path) 122 { 123 char *out = path; 124 for (char *cur = path; *cur; ++cur) { 125 if (*cur == '/') { 126 while (*(cur + 1) == '/') 127 ++cur; 128 *(out++) = '/'; 129 } else 130 *(out++) = *cur; 131 } 132 133 *(out++) = '\0'; 134 } 135 136 static bool 137 strendswith(const char *what, const char *suff) 138 { 139 size_t what_l = strlen(what); 140 size_t suff_l = strlen(suff); 141 142 return ((what_l >= suff_l) && 143 (strcmp(what + what_l - suff_l, suff) == 0)); 144 } 145 146 /* Assumes already-simplified path, doesn't modify input */ 147 static char * 148 systemd_escape_path(char *input, const char *prepend, const char *append) 149 { 150 if (strcmp(input, "/") == 0) { 151 char *ret; 152 if (asprintf(&ret, "%s-%s", prepend, append) == -1) { 153 fprintf(stderr, PROGNAME "[%d]: " 154 "out of memory to escape \"%s%s%s\"!\n", 155 getpid(), prepend, input, append); 156 ret = NULL; 157 } 158 return (ret); 159 } else { 160 /* 161 * path_is_normalized() (flattened for absolute paths here), 162 * required for proper escaping 163 */ 164 if (strstr(input, "/./") || strstr(input, "/../") || 165 strendswith(input, "/.") || strendswith(input, "/..")) 166 return (NULL); 167 168 169 if (input[0] == '/') 170 ++input; 171 172 char *back = &input[strlen(input) - 1]; 173 bool deslash = *back == '/'; 174 if (deslash) 175 *back = '\0'; 176 177 char *ret = systemd_escape(input, prepend, append); 178 179 if (deslash) 180 *back = '/'; 181 return (ret); 182 } 183 } 184 185 static FILE * 186 fopenat(int dirfd, const char *pathname, int flags, 187 const char *stream_mode, mode_t mode) 188 { 189 int fd = openat(dirfd, pathname, flags, mode); 190 if (fd < 0) 191 return (NULL); 192 193 return (fdopen(fd, stream_mode)); 194 } 195 196 static int 197 line_worker(char *line, const char *cachefile) 198 { 199 int ret = 0; 200 void *tofree_all[8]; 201 void **tofree = tofree_all; 202 203 char *toktmp; 204 /* BEGIN CSTYLED */ 205 const char *dataset = strtok_r(line, "\t", &toktmp); 206 char *p_mountpoint = strtok_r(NULL, "\t", &toktmp); 207 const char *p_canmount = strtok_r(NULL, "\t", &toktmp); 208 const char *p_atime = strtok_r(NULL, "\t", &toktmp); 209 const char *p_relatime = strtok_r(NULL, "\t", &toktmp); 210 const char *p_devices = strtok_r(NULL, "\t", &toktmp); 211 const char *p_exec = strtok_r(NULL, "\t", &toktmp); 212 const char *p_readonly = strtok_r(NULL, "\t", &toktmp); 213 const char *p_setuid = strtok_r(NULL, "\t", &toktmp); 214 const char *p_nbmand = strtok_r(NULL, "\t", &toktmp); 215 const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-"; 216 char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none"); 217 const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-"; 218 const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-"; 219 const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-"; 220 const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-"; 221 char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-"); 222 char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-"); 223 const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-"; 224 const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-"; 225 /* END CSTYLED */ 226 227 const char *pool = dataset; 228 if ((toktmp = strchr(pool, '/')) != NULL) 229 pool = strndupa(pool, toktmp - pool); 230 231 if (p_nbmand == NULL) { 232 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n", 233 getpid(), dataset); 234 goto err; 235 } 236 237 /* Minimal pre-requisites to mount a ZFS dataset */ 238 const char *after = "zfs-import.target"; 239 const char *wants = "zfs-import.target"; 240 const char *bindsto = NULL; 241 char *wantedby = NULL; 242 char *requiredby = NULL; 243 bool noauto = false; 244 bool wantedby_append = true; 245 246 /* 247 * zfs-import.target is not needed if the pool is already imported. 248 * This avoids a dependency loop on root-on-ZFS systems: 249 * systemd-random-seed.service After (via RequiresMountsFor) 250 * var-lib.mount After 251 * zfs-import.target After 252 * zfs-import-{cache,scan}.service After 253 * cryptsetup.service After 254 * systemd-random-seed.service 255 */ 256 if (tfind(pool, &known_pools, STRCMP)) { 257 after = ""; 258 wants = ""; 259 } 260 261 if (strcmp(p_systemd_after, "-") == 0) 262 p_systemd_after = NULL; 263 if (strcmp(p_systemd_before, "-") == 0) 264 p_systemd_before = NULL; 265 if (strcmp(p_systemd_requires, "-") == 0) 266 p_systemd_requires = NULL; 267 if (strcmp(p_systemd_requiresmountsfor, "-") == 0) 268 p_systemd_requiresmountsfor = NULL; 269 270 271 if (strcmp(p_encroot, "-") != 0) { 272 char *keyloadunit = *(tofree++) = 273 systemd_escape(p_encroot, "zfs-load-key@", ".service"); 274 if (keyloadunit == NULL) 275 goto err; 276 277 if (strcmp(dataset, p_encroot) == 0) { 278 const char *keymountdep = NULL; 279 bool is_prompt = false; 280 bool need_network = false; 281 282 regmatch_t uri_matches[3]; 283 if (regexec(&uri_regex, p_keyloc, 284 nitems(uri_matches), uri_matches, 0) == 0) { 285 p_keyloc[uri_matches[1].rm_eo] = '\0'; 286 p_keyloc[uri_matches[2].rm_eo] = '\0'; 287 const char *scheme = 288 &p_keyloc[uri_matches[1].rm_so]; 289 const char *path = 290 &p_keyloc[uri_matches[2].rm_so]; 291 292 if (strcmp(scheme, "https") == 0 || 293 strcmp(scheme, "http") == 0) 294 need_network = true; 295 else 296 keymountdep = path; 297 } else { 298 if (strcmp(p_keyloc, "prompt") != 0) 299 fprintf(stderr, PROGNAME "[%d]: %s: " 300 "unknown non-URI keylocation=%s\n", 301 getpid(), dataset, p_keyloc); 302 303 is_prompt = true; 304 } 305 306 307 /* Generate the key-load .service unit */ 308 FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit, 309 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 310 0644); 311 if (!keyloadunit_f) { 312 fprintf(stderr, PROGNAME "[%d]: %s: " 313 "couldn't open %s under %s: %s\n", 314 getpid(), dataset, keyloadunit, destdir, 315 strerror(errno)); 316 goto err; 317 } 318 319 fprintf(keyloadunit_f, 320 OUTPUT_HEADER 321 "[Unit]\n" 322 "Description=Load ZFS key for %s\n" 323 "SourcePath=" FSLIST "/%s\n" 324 "Documentation=man:zfs-mount-generator(8)\n" 325 "DefaultDependencies=no\n" 326 "Wants=%s\n" 327 "After=%s\n", 328 dataset, cachefile, wants, after); 329 330 if (need_network) 331 fprintf(keyloadunit_f, 332 "Wants=network-online.target\n" 333 "After=network-online.target\n"); 334 335 if (p_systemd_requires) 336 fprintf(keyloadunit_f, 337 "Requires=%s\n", p_systemd_requires); 338 339 if (p_systemd_requiresmountsfor) 340 fprintf(keyloadunit_f, 341 "RequiresMountsFor=%s\n", 342 p_systemd_requiresmountsfor); 343 if (keymountdep) 344 fprintf(keyloadunit_f, 345 "RequiresMountsFor='%s'\n", keymountdep); 346 347 /* BEGIN CSTYLED */ 348 fprintf(keyloadunit_f, 349 "\n" 350 "[Service]\n" 351 "Type=oneshot\n" 352 "RemainAfterExit=yes\n" 353 "# This avoids a dependency loop involving systemd-journald.socket if this\n" 354 "# dataset is a parent of the root filesystem.\n" 355 "StandardOutput=null\n" 356 "StandardError=null\n" 357 "ExecStart=/bin/sh -euc '" 358 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;", 359 dataset); 360 if (is_prompt) 361 fprintf(keyloadunit_f, 362 "for i in 1 2 3; do " 363 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |" 364 "" ZFS " load-key \"%s\" && exit 0;" 365 "done;" 366 "exit 1", 367 dataset, dataset, dataset); 368 else 369 fprintf(keyloadunit_f, 370 "exec " ZFS " load-key \"%s\"", 371 dataset); 372 373 fprintf(keyloadunit_f, 374 "'\n" 375 "ExecStop=/bin/sh -euc '" 376 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;" 377 "exec " ZFS " unload-key \"%s\"" 378 "'\n", 379 dataset, dataset); 380 /* END CSTYLED */ 381 382 (void) fclose(keyloadunit_f); 383 } 384 385 /* Update dependencies for the mount file to want this */ 386 bindsto = keyloadunit; 387 if (after[0] == '\0') 388 after = keyloadunit; 389 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1) 390 after = *(tofree++) = toktmp; 391 else { 392 fprintf(stderr, PROGNAME "[%d]: %s: " 393 "out of memory to generate after=\"%s %s\"!\n", 394 getpid(), dataset, after, keyloadunit); 395 goto err; 396 } 397 } 398 399 400 /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */ 401 if (strcmp(p_systemd_ignore, "-") == 0 || 402 strcmp(p_systemd_ignore, "off") == 0) { 403 /* ok */ 404 } else if (strcmp(p_systemd_ignore, "on") == 0) 405 goto end; 406 else { 407 fprintf(stderr, PROGNAME "[%d]: %s: " 408 "invalid org.openzfs.systemd:ignore=%s\n", 409 getpid(), dataset, p_systemd_ignore); 410 goto err; 411 } 412 413 /* Check for canmount */ 414 if (strcmp(p_canmount, "on") == 0) { 415 /* ok */ 416 } else if (strcmp(p_canmount, "noauto") == 0) 417 noauto = true; 418 else if (strcmp(p_canmount, "off") == 0) 419 goto end; 420 else { 421 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n", 422 getpid(), dataset, p_canmount); 423 goto err; 424 } 425 426 /* Check for legacy and blank mountpoints */ 427 if (strcmp(p_mountpoint, "legacy") == 0 || 428 strcmp(p_mountpoint, "none") == 0) 429 goto end; 430 else if (p_mountpoint[0] != '/') { 431 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n", 432 getpid(), dataset, p_mountpoint); 433 goto err; 434 } 435 436 /* Escape the mountpoint per systemd policy */ 437 simplify_path(p_mountpoint); 438 const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount"); 439 if (mountfile == NULL) { 440 fprintf(stderr, 441 PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n", 442 getpid(), dataset, p_mountpoint); 443 goto err; 444 } 445 446 447 /* 448 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options 449 * 450 * The longest string achievable here is 451 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand". 452 */ 453 char opts[64] = ""; 454 455 /* atime */ 456 if (strcmp(p_atime, "on") == 0) { 457 /* relatime */ 458 if (strcmp(p_relatime, "on") == 0) 459 strcat(opts, ",atime,relatime"); 460 else if (strcmp(p_relatime, "off") == 0) 461 strcat(opts, ",atime,strictatime"); 462 else 463 fprintf(stderr, 464 PROGNAME "[%d]: %s: invalid relatime=%s\n", 465 getpid(), dataset, p_relatime); 466 } else if (strcmp(p_atime, "off") == 0) { 467 strcat(opts, ",noatime"); 468 } else 469 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n", 470 getpid(), dataset, p_atime); 471 472 /* devices */ 473 if (strcmp(p_devices, "on") == 0) 474 strcat(opts, ",dev"); 475 else if (strcmp(p_devices, "off") == 0) 476 strcat(opts, ",nodev"); 477 else 478 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n", 479 getpid(), dataset, p_devices); 480 481 /* exec */ 482 if (strcmp(p_exec, "on") == 0) 483 strcat(opts, ",exec"); 484 else if (strcmp(p_exec, "off") == 0) 485 strcat(opts, ",noexec"); 486 else 487 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n", 488 getpid(), dataset, p_exec); 489 490 /* readonly */ 491 if (strcmp(p_readonly, "on") == 0) 492 strcat(opts, ",ro"); 493 else if (strcmp(p_readonly, "off") == 0) 494 strcat(opts, ",rw"); 495 else 496 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n", 497 getpid(), dataset, p_readonly); 498 499 /* setuid */ 500 if (strcmp(p_setuid, "on") == 0) 501 strcat(opts, ",suid"); 502 else if (strcmp(p_setuid, "off") == 0) 503 strcat(opts, ",nosuid"); 504 else 505 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n", 506 getpid(), dataset, p_setuid); 507 508 /* nbmand */ 509 if (strcmp(p_nbmand, "on") == 0) 510 strcat(opts, ",mand"); 511 else if (strcmp(p_nbmand, "off") == 0) 512 strcat(opts, ",nomand"); 513 else 514 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n", 515 getpid(), dataset, p_setuid); 516 517 if (strcmp(p_systemd_wantedby, "-") != 0) { 518 noauto = true; 519 520 if (strcmp(p_systemd_wantedby, "none") != 0) 521 wantedby = p_systemd_wantedby; 522 } 523 524 if (strcmp(p_systemd_requiredby, "-") != 0) { 525 noauto = true; 526 527 if (strcmp(p_systemd_requiredby, "none") != 0) 528 requiredby = p_systemd_requiredby; 529 } 530 531 /* 532 * For datasets with canmount=on, a dependency is created for 533 * local-fs.target by default. To avoid regressions, this dependency 534 * is reduced to "wants" rather than "requires" when nofail!=off. 535 * **THIS MAY CHANGE** 536 * noauto=on disables this behavior completely. 537 */ 538 if (!noauto) { 539 if (strcmp(p_systemd_nofail, "off") == 0) 540 requiredby = strdupa("local-fs.target"); 541 else { 542 wantedby = strdupa("local-fs.target"); 543 wantedby_append = strcmp(p_systemd_nofail, "on") != 0; 544 } 545 } 546 547 /* 548 * Handle existing files: 549 * 1. We never overwrite existing files, although we may delete 550 * files if we're sure they were created by us. (see 5.) 551 * 2. We handle files differently based on canmount. 552 * Units with canmount=on always have precedence over noauto. 553 * This is enforced by processing these units before all others. 554 * It is important to use p_canmount and not noauto here, 555 * since we categorise by canmount while other properties, 556 * e.g. org.openzfs.systemd:wanted-by, also modify noauto. 557 * 3. If no unit file exists for a noauto dataset, we create one. 558 * Additionally, we use noauto_files to track the unit file names 559 * (which are the systemd-escaped mountpoints) of all (exclusively) 560 * noauto datasets that had a file created. 561 * 4. If the file to be created is found in the tracking tree, 562 * we do NOT create it. 563 * 5. If a file exists for a noauto dataset, 564 * we check whether the file name is in the array. 565 * If it is, we have multiple noauto datasets for the same 566 * mountpoint. In such cases, we remove the file for safety. 567 * We leave the file name in the tracking array to avoid 568 * further noauto datasets creating a file for this path again. 569 */ 570 571 struct stat stbuf; 572 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0; 573 bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL; 574 575 *(tofree++) = (void *)mountfile; 576 if (already_exists) { 577 if (is_known) { 578 /* If it's in noauto_files, we must be noauto too */ 579 580 /* See 5 */ 581 errno = 0; 582 (void) unlinkat(destdir_fd, mountfile, 0); 583 584 /* See 2 */ 585 fprintf(stderr, PROGNAME "[%d]: %s: " 586 "removing duplicate noauto unit %s%s%s\n", 587 getpid(), dataset, mountfile, 588 errno ? "" : " failed: ", 589 errno ? "" : strerror(errno)); 590 } else { 591 /* Don't log for canmount=noauto */ 592 if (strcmp(p_canmount, "on") == 0) 593 fprintf(stderr, PROGNAME "[%d]: %s: " 594 "%s already exists. Skipping.\n", 595 getpid(), dataset, mountfile); 596 } 597 598 /* File exists: skip current dataset */ 599 goto end; 600 } else { 601 if (is_known) { 602 /* See 4 */ 603 goto end; 604 } else if (strcmp(p_canmount, "noauto") == 0) { 605 if (tsearch(mountfile, &noauto_files, STRCMP) == NULL) 606 fprintf(stderr, PROGNAME "[%d]: %s: " 607 "out of memory for noauto datasets! " 608 "Not tracking %s.\n", 609 getpid(), dataset, mountfile); 610 else 611 /* mountfile escaped to noauto_files */ 612 *(--tofree) = NULL; 613 } 614 } 615 616 617 FILE *mountfile_f = fopenat(destdir_fd, mountfile, 618 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644); 619 if (!mountfile_f) { 620 fprintf(stderr, 621 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n", 622 getpid(), dataset, mountfile, destdir, strerror(errno)); 623 goto err; 624 } 625 626 fprintf(mountfile_f, 627 OUTPUT_HEADER 628 "[Unit]\n" 629 "SourcePath=" FSLIST "/%s\n" 630 "Documentation=man:zfs-mount-generator(8)\n" 631 "\n" 632 "Before=", 633 cachefile); 634 635 if (p_systemd_before) 636 fprintf(mountfile_f, "%s ", p_systemd_before); 637 fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */ 638 if (requiredby) 639 fprintf(mountfile_f, " %s", requiredby); 640 if (wantedby && wantedby_append) 641 fprintf(mountfile_f, " %s", wantedby); 642 643 fprintf(mountfile_f, 644 "\n" 645 "After="); 646 if (p_systemd_after) 647 fprintf(mountfile_f, "%s ", p_systemd_after); 648 fprintf(mountfile_f, "%s\n", after); 649 650 fprintf(mountfile_f, "Wants=%s\n", wants); 651 652 if (bindsto) 653 fprintf(mountfile_f, "BindsTo=%s\n", bindsto); 654 if (p_systemd_requires) 655 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires); 656 if (p_systemd_requiresmountsfor) 657 fprintf(mountfile_f, 658 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor); 659 660 fprintf(mountfile_f, 661 "\n" 662 "[Mount]\n" 663 "Where=%s\n" 664 "What=%s\n" 665 "Type=zfs\n" 666 "Options=defaults%s,zfsutil\n", 667 p_mountpoint, dataset, opts); 668 669 (void) fclose(mountfile_f); 670 671 if (!requiredby && !wantedby) 672 goto end; 673 674 /* Finally, create the appropriate dependencies */ 675 char *linktgt; 676 if (asprintf(&linktgt, "../%s", mountfile) == -1) { 677 fprintf(stderr, PROGNAME "[%d]: %s: " 678 "out of memory for dependents of %s!\n", 679 getpid(), dataset, mountfile); 680 goto err; 681 } 682 *(tofree++) = linktgt; 683 684 struct dep { 685 const char *type; 686 char *list; 687 } deps[] = { 688 {"wants", wantedby}, 689 {"requires", requiredby}, 690 {} 691 }; 692 for (struct dep *dep = deps; dep->type; ++dep) { 693 if (!dep->list) 694 continue; 695 696 for (char *reqby = strtok_r(dep->list, " ", &toktmp); 697 reqby; 698 reqby = strtok_r(NULL, " ", &toktmp)) { 699 char *depdir; 700 if (asprintf( 701 &depdir, "%s.%s", reqby, dep->type) == -1) { 702 fprintf(stderr, PROGNAME "[%d]: %s: " 703 "out of memory for dependent dir name " 704 "\"%s.%s\"!\n", 705 getpid(), dataset, reqby, dep->type); 706 continue; 707 } 708 709 (void) mkdirat(destdir_fd, depdir, 0755); 710 int depdir_fd = openat(destdir_fd, depdir, 711 O_PATH | O_DIRECTORY | O_CLOEXEC); 712 if (depdir_fd < 0) { 713 fprintf(stderr, PROGNAME "[%d]: %s: " 714 "couldn't open %s under %s: %s\n", 715 getpid(), dataset, depdir, destdir, 716 strerror(errno)); 717 free(depdir); 718 continue; 719 } 720 721 if (symlinkat(linktgt, depdir_fd, mountfile) == -1) 722 fprintf(stderr, PROGNAME "[%d]: %s: " 723 "couldn't symlink at " 724 "%s under %s under %s: %s\n", 725 getpid(), dataset, mountfile, 726 depdir, destdir, strerror(errno)); 727 728 (void) close(depdir_fd); 729 free(depdir); 730 } 731 } 732 733 end: 734 if (tofree >= tofree_all + nitems(tofree_all)) { 735 /* 736 * This won't happen as-is: 737 * we've got 8 slots and allocate 4 things at most. 738 */ 739 fprintf(stderr, 740 PROGNAME "[%d]: %s: need to free %zu > %zu!\n", 741 getpid(), dataset, tofree - tofree_all, nitems(tofree_all)); 742 ret = tofree - tofree_all; 743 } 744 745 while (tofree-- != tofree_all) 746 free(*tofree); 747 return (ret); 748 err: 749 ret = 1; 750 goto end; 751 } 752 753 754 static int 755 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused))) 756 { 757 int ret = 0; 758 759 /* 760 * Pools are guaranteed-unique by the kernel, 761 * no risk of leaking dupes here 762 */ 763 char *name = strdup(zpool_get_name(pool)); 764 if (!name || !tsearch(name, &known_pools, STRCMP)) { 765 free(name); 766 ret = ENOMEM; 767 } 768 769 zpool_close(pool); 770 return (ret); 771 } 772 773 int 774 main(int argc, char **argv) 775 { 776 struct timespec time_init = {}; 777 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init); 778 779 { 780 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); 781 if (kmfd >= 0) { 782 (void) dup2(kmfd, STDERR_FILENO); 783 (void) close(kmfd); 784 785 setlinebuf(stderr); 786 } 787 } 788 789 switch (argc) { 790 case 1: 791 /* Use default */ 792 break; 793 case 2: 794 case 4: 795 destdir = argv[1]; 796 break; 797 default: 798 fprintf(stderr, 799 PROGNAME "[%d]: wrong argument count: %d\n", 800 getpid(), argc - 1); 801 _exit(1); 802 } 803 804 { 805 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC); 806 if (destdir_fd < 0) { 807 fprintf(stderr, PROGNAME "[%d]: " 808 "can't open destination directory %s: %s\n", 809 getpid(), destdir, strerror(errno)); 810 _exit(1); 811 } 812 } 813 814 DIR *fslist_dir = opendir(FSLIST); 815 if (!fslist_dir) { 816 if (errno != ENOENT) 817 fprintf(stderr, 818 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n", 819 getpid(), strerror(errno)); 820 _exit(0); 821 } 822 823 { 824 libzfs_handle_t *libzfs = libzfs_init(); 825 if (libzfs) { 826 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0) 827 fprintf(stderr, PROGNAME "[%d]: " 828 "error listing pools, ignoring\n", 829 getpid()); 830 libzfs_fini(libzfs); 831 } else 832 fprintf(stderr, PROGNAME "[%d]: " 833 "couldn't start libzfs, ignoring\n", 834 getpid()); 835 } 836 837 { 838 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0); 839 if (regerr != 0) { 840 fprintf(stderr, 841 PROGNAME "[%d]: invalid regex: %d\n", 842 getpid(), regerr); 843 _exit(1); 844 } 845 } 846 847 bool debug = false; 848 char *line = NULL; 849 size_t linelen = 0; 850 { 851 const char *dbgenv = getenv("ZFS_DEBUG"); 852 if (dbgenv) 853 debug = atoi(dbgenv); 854 else { 855 FILE *cmdline = fopen("/proc/cmdline", "re"); 856 if (cmdline != NULL) { 857 if (getline(&line, &linelen, cmdline) >= 0) 858 debug = strstr(line, "debug"); 859 (void) fclose(cmdline); 860 } 861 } 862 863 if (debug && !isatty(STDOUT_FILENO)) 864 dup2(STDERR_FILENO, STDOUT_FILENO); 865 } 866 867 struct timespec time_start = {}; 868 if (debug) 869 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start); 870 871 struct line { 872 char *line; 873 const char *fname; 874 struct line *next; 875 } *lines_canmount_not_on = NULL; 876 877 int ret = 0; 878 struct dirent *cachent; 879 while ((cachent = readdir(fslist_dir)) != NULL) { 880 if (strcmp(cachent->d_name, ".") == 0 || 881 strcmp(cachent->d_name, "..") == 0) 882 continue; 883 884 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name, 885 O_RDONLY | O_CLOEXEC, "r", 0); 886 if (!cachefile) { 887 fprintf(stderr, PROGNAME "[%d]: " 888 "couldn't open %s under " FSLIST ": %s\n", 889 getpid(), cachent->d_name, strerror(errno)); 890 continue; 891 } 892 893 const char *filename = FREE_STATICS ? "(elided)" : NULL; 894 895 ssize_t read; 896 while ((read = getline(&line, &linelen, cachefile)) >= 0) { 897 line[read - 1] = '\0'; /* newline */ 898 899 char *canmount = line; 900 canmount += strcspn(canmount, "\t"); 901 canmount += strspn(canmount, "\t"); 902 canmount += strcspn(canmount, "\t"); 903 canmount += strspn(canmount, "\t"); 904 bool canmount_on = strncmp(canmount, "on", 2) == 0; 905 906 if (canmount_on) 907 ret |= line_worker(line, cachent->d_name); 908 else { 909 if (filename == NULL) 910 filename = 911 strdup(cachent->d_name) ?: "(?)"; 912 913 struct line *l = calloc(1, sizeof (*l)); 914 char *nl = strdup(line); 915 if (l == NULL || nl == NULL) { 916 fprintf(stderr, PROGNAME "[%d]: " 917 "out of memory for \"%s\" in %s\n", 918 getpid(), line, cachent->d_name); 919 free(l); 920 free(nl); 921 continue; 922 } 923 l->line = nl; 924 l->fname = filename; 925 l->next = lines_canmount_not_on; 926 lines_canmount_not_on = l; 927 } 928 } 929 930 fclose(cachefile); 931 } 932 free(line); 933 934 while (lines_canmount_not_on) { 935 struct line *l = lines_canmount_not_on; 936 lines_canmount_not_on = l->next; 937 938 ret |= line_worker(l->line, l->fname); 939 if (FREE_STATICS) { 940 free(l->line); 941 free(l); 942 } 943 } 944 945 if (debug) { 946 struct timespec time_end = {}; 947 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end); 948 949 struct rusage usage; 950 getrusage(RUSAGE_SELF, &usage); 951 printf( 952 "\n" 953 PROGNAME ": " 954 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n", 955 (unsigned long long) usage.ru_utime.tv_sec, 956 (unsigned int) usage.ru_utime.tv_usec, 957 (unsigned long long) usage.ru_stime.tv_sec, 958 (unsigned int) usage.ru_stime.tv_usec, 959 usage.ru_maxrss * 1024); 960 961 if (time_start.tv_nsec > time_end.tv_nsec) { 962 time_end.tv_nsec = 963 1000000000 + time_end.tv_nsec - time_start.tv_nsec; 964 time_end.tv_sec -= 1; 965 } else 966 time_end.tv_nsec -= time_start.tv_nsec; 967 time_end.tv_sec -= time_start.tv_sec; 968 969 if (time_init.tv_nsec > time_start.tv_nsec) { 970 time_start.tv_nsec = 971 1000000000 + time_start.tv_nsec - time_init.tv_nsec; 972 time_start.tv_sec -= 1; 973 } else 974 time_start.tv_nsec -= time_init.tv_nsec; 975 time_start.tv_sec -= time_init.tv_sec; 976 977 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec; 978 time_init.tv_sec = 979 time_start.tv_sec + time_end.tv_sec + 980 time_init.tv_nsec / 1000000000; 981 time_init.tv_nsec %= 1000000000; 982 983 printf(PROGNAME ": " 984 "total=%llu.%09llus = " 985 "init=%llu.%09llus + real=%llu.%09llus\n", 986 (unsigned long long) time_init.tv_sec, 987 (unsigned long long) time_init.tv_nsec, 988 (unsigned long long) time_start.tv_sec, 989 (unsigned long long) time_start.tv_nsec, 990 (unsigned long long) time_end.tv_sec, 991 (unsigned long long) time_end.tv_nsec); 992 993 fflush(stdout); 994 } 995 996 if (FREE_STATICS) { 997 closedir(fslist_dir); 998 tdestroy(noauto_files, free); 999 tdestroy(known_pools, free); 1000 regfree(&uri_regex); 1001 } 1002 _exit(ret); 1003 } 1004