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