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 char *dependencies[][2] = { 685 {"wants", wantedby}, 686 {"requires", requiredby}, 687 {} 688 }; 689 for (__typeof__(&*dependencies) dep = &*dependencies; **dep; ++dep) { 690 if (!(*dep)[1]) 691 continue; 692 693 for (char *reqby = strtok_r((*dep)[1], " ", &toktmp); 694 reqby; 695 reqby = strtok_r(NULL, " ", &toktmp)) { 696 char *depdir; 697 if (asprintf( 698 &depdir, "%s.%s", reqby, (*dep)[0]) == -1) { 699 fprintf(stderr, PROGNAME "[%d]: %s: " 700 "out of memory for dependent dir name " 701 "\"%s.%s\"!\n", 702 getpid(), dataset, reqby, (*dep)[0]); 703 continue; 704 } 705 706 (void) mkdirat(destdir_fd, depdir, 0755); 707 int depdir_fd = openat(destdir_fd, depdir, 708 O_PATH | O_DIRECTORY | O_CLOEXEC); 709 if (depdir_fd < 0) { 710 fprintf(stderr, PROGNAME "[%d]: %s: " 711 "couldn't open %s under %s: %s\n", 712 getpid(), dataset, depdir, destdir, 713 strerror(errno)); 714 free(depdir); 715 continue; 716 } 717 718 if (symlinkat(linktgt, depdir_fd, mountfile) == -1) 719 fprintf(stderr, PROGNAME "[%d]: %s: " 720 "couldn't symlink at " 721 "%s under %s under %s: %s\n", 722 getpid(), dataset, mountfile, 723 depdir, destdir, strerror(errno)); 724 725 (void) close(depdir_fd); 726 free(depdir); 727 } 728 } 729 730 end: 731 if (tofree >= tofree_all + nitems(tofree_all)) { 732 /* 733 * This won't happen as-is: 734 * we've got 8 slots and allocate 4 things at most. 735 */ 736 fprintf(stderr, 737 PROGNAME "[%d]: %s: need to free %zu > %zu!\n", 738 getpid(), dataset, tofree - tofree_all, nitems(tofree_all)); 739 ret = tofree - tofree_all; 740 } 741 742 while (tofree-- != tofree_all) 743 free(*tofree); 744 return (ret); 745 err: 746 ret = 1; 747 goto end; 748 } 749 750 751 static int 752 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused))) 753 { 754 int ret = 0; 755 756 /* 757 * Pools are guaranteed-unique by the kernel, 758 * no risk of leaking dupes here 759 */ 760 char *name = strdup(zpool_get_name(pool)); 761 if (!name || !tsearch(name, &known_pools, STRCMP)) { 762 free(name); 763 ret = ENOMEM; 764 } 765 766 zpool_close(pool); 767 return (ret); 768 } 769 770 int 771 main(int argc, char **argv) 772 { 773 struct timespec time_init = {}; 774 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init); 775 776 { 777 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); 778 if (kmfd >= 0) { 779 (void) dup2(kmfd, STDERR_FILENO); 780 (void) close(kmfd); 781 782 setlinebuf(stderr); 783 } 784 } 785 786 switch (argc) { 787 case 1: 788 /* Use default */ 789 break; 790 case 2: 791 case 4: 792 destdir = argv[1]; 793 break; 794 default: 795 fprintf(stderr, 796 PROGNAME "[%d]: wrong argument count: %d\n", 797 getpid(), argc - 1); 798 _exit(1); 799 } 800 801 { 802 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC); 803 if (destdir_fd < 0) { 804 fprintf(stderr, PROGNAME "[%d]: " 805 "can't open destination directory %s: %s\n", 806 getpid(), destdir, strerror(errno)); 807 _exit(1); 808 } 809 } 810 811 DIR *fslist_dir = opendir(FSLIST); 812 if (!fslist_dir) { 813 if (errno != ENOENT) 814 fprintf(stderr, 815 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n", 816 getpid(), strerror(errno)); 817 _exit(0); 818 } 819 820 { 821 libzfs_handle_t *libzfs = libzfs_init(); 822 if (libzfs) { 823 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0) 824 fprintf(stderr, PROGNAME "[%d]: " 825 "error listing pools, ignoring\n", 826 getpid()); 827 libzfs_fini(libzfs); 828 } else 829 fprintf(stderr, PROGNAME "[%d]: " 830 "couldn't start libzfs, ignoring\n", 831 getpid()); 832 } 833 834 { 835 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0); 836 if (regerr != 0) { 837 fprintf(stderr, 838 PROGNAME "[%d]: invalid regex: %d\n", 839 getpid(), regerr); 840 _exit(1); 841 } 842 } 843 844 bool debug = false; 845 char *line = NULL; 846 size_t linelen = 0; 847 { 848 const char *dbgenv = getenv("ZFS_DEBUG"); 849 if (dbgenv) 850 debug = atoi(dbgenv); 851 else { 852 FILE *cmdline = fopen("/proc/cmdline", "re"); 853 if (cmdline != NULL) { 854 if (getline(&line, &linelen, cmdline) >= 0) 855 debug = strstr(line, "debug"); 856 (void) fclose(cmdline); 857 } 858 } 859 860 if (debug && !isatty(STDOUT_FILENO)) 861 dup2(STDERR_FILENO, STDOUT_FILENO); 862 } 863 864 struct timespec time_start = {}; 865 if (debug) 866 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start); 867 868 struct line { 869 char *line; 870 const char *fname; 871 struct line *next; 872 } *lines_canmount_not_on = NULL; 873 874 int ret = 0; 875 struct dirent *cachent; 876 while ((cachent = readdir(fslist_dir)) != NULL) { 877 if (strcmp(cachent->d_name, ".") == 0 || 878 strcmp(cachent->d_name, "..") == 0) 879 continue; 880 881 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name, 882 O_RDONLY | O_CLOEXEC, "r", 0); 883 if (!cachefile) { 884 fprintf(stderr, PROGNAME "[%d]: " 885 "couldn't open %s under " FSLIST ": %s\n", 886 getpid(), cachent->d_name, strerror(errno)); 887 continue; 888 } 889 890 const char *filename = FREE_STATICS ? "(elided)" : NULL; 891 892 ssize_t read; 893 while ((read = getline(&line, &linelen, cachefile)) >= 0) { 894 line[read - 1] = '\0'; /* newline */ 895 896 char *canmount = line; 897 canmount += strcspn(canmount, "\t"); 898 canmount += strspn(canmount, "\t"); 899 canmount += strcspn(canmount, "\t"); 900 canmount += strspn(canmount, "\t"); 901 bool canmount_on = strncmp(canmount, "on", 2) == 0; 902 903 if (canmount_on) 904 ret |= line_worker(line, cachent->d_name); 905 else { 906 if (filename == NULL) 907 filename = 908 strdup(cachent->d_name) ?: "(?)"; 909 910 struct line *l = calloc(1, sizeof (*l)); 911 char *nl = strdup(line); 912 if (l == NULL || nl == NULL) { 913 fprintf(stderr, PROGNAME "[%d]: " 914 "out of memory for \"%s\" in %s\n", 915 getpid(), line, cachent->d_name); 916 free(l); 917 free(nl); 918 continue; 919 } 920 l->line = nl; 921 l->fname = filename; 922 l->next = lines_canmount_not_on; 923 lines_canmount_not_on = l; 924 } 925 } 926 927 fclose(cachefile); 928 } 929 free(line); 930 931 while (lines_canmount_not_on) { 932 struct line *l = lines_canmount_not_on; 933 lines_canmount_not_on = l->next; 934 935 ret |= line_worker(l->line, l->fname); 936 if (FREE_STATICS) { 937 free(l->line); 938 free(l); 939 } 940 } 941 942 if (debug) { 943 struct timespec time_end = {}; 944 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end); 945 946 struct rusage usage; 947 getrusage(RUSAGE_SELF, &usage); 948 printf( 949 "\n" 950 PROGNAME ": " 951 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n", 952 (unsigned long long) usage.ru_utime.tv_sec, 953 (unsigned int) usage.ru_utime.tv_usec, 954 (unsigned long long) usage.ru_stime.tv_sec, 955 (unsigned int) usage.ru_stime.tv_usec, 956 usage.ru_maxrss * 1024); 957 958 if (time_start.tv_nsec > time_end.tv_nsec) { 959 time_end.tv_nsec = 960 1000000000 + time_end.tv_nsec - time_start.tv_nsec; 961 time_end.tv_sec -= 1; 962 } else 963 time_end.tv_nsec -= time_start.tv_nsec; 964 time_end.tv_sec -= time_start.tv_sec; 965 966 if (time_init.tv_nsec > time_start.tv_nsec) { 967 time_start.tv_nsec = 968 1000000000 + time_start.tv_nsec - time_init.tv_nsec; 969 time_start.tv_sec -= 1; 970 } else 971 time_start.tv_nsec -= time_init.tv_nsec; 972 time_start.tv_sec -= time_init.tv_sec; 973 974 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec; 975 time_init.tv_sec = 976 time_start.tv_sec + time_end.tv_sec + 977 time_init.tv_nsec / 1000000000; 978 time_init.tv_nsec %= 1000000000; 979 980 printf(PROGNAME ": " 981 "total=%llu.%09llus = " 982 "init=%llu.%09llus + real=%llu.%09llus\n", 983 (unsigned long long) time_init.tv_sec, 984 (unsigned long long) time_init.tv_nsec, 985 (unsigned long long) time_start.tv_sec, 986 (unsigned long long) time_start.tv_nsec, 987 (unsigned long long) time_end.tv_sec, 988 (unsigned long long) time_end.tv_nsec); 989 990 fflush(stdout); 991 } 992 993 if (FREE_STATICS) { 994 closedir(fslist_dir); 995 tdestroy(noauto_files, free); 996 tdestroy(known_pools, free); 997 regfree(&uri_regex); 998 } 999 _exit(ret); 1000 } 1001