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 size_t pool_len = strlen(dataset); 228 if ((toktmp = strchr(dataset, '/')) != NULL) 229 pool_len = toktmp - dataset; 230 const char *pool = *(tofree++) = strndup(dataset, pool_len); 231 232 if (p_nbmand == NULL) { 233 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n", 234 getpid(), dataset); 235 goto err; 236 } 237 238 /* Minimal pre-requisites to mount a ZFS dataset */ 239 const char *after = "zfs-import.target"; 240 const char *wants = "zfs-import.target"; 241 const char *bindsto = NULL; 242 char *wantedby = NULL; 243 char *requiredby = NULL; 244 bool noauto = false; 245 bool wantedby_append = true; 246 247 /* 248 * zfs-import.target is not needed if the pool is already imported. 249 * This avoids a dependency loop on root-on-ZFS systems: 250 * systemd-random-seed.service After (via RequiresMountsFor) 251 * var-lib.mount After 252 * zfs-import.target After 253 * zfs-import-{cache,scan}.service After 254 * cryptsetup.service After 255 * systemd-random-seed.service 256 */ 257 if (tfind(pool, &known_pools, STRCMP)) { 258 after = ""; 259 wants = ""; 260 } 261 262 if (strcmp(p_systemd_after, "-") == 0) 263 p_systemd_after = NULL; 264 if (strcmp(p_systemd_before, "-") == 0) 265 p_systemd_before = NULL; 266 if (strcmp(p_systemd_requires, "-") == 0) 267 p_systemd_requires = NULL; 268 if (strcmp(p_systemd_requiresmountsfor, "-") == 0) 269 p_systemd_requiresmountsfor = NULL; 270 271 272 if (strcmp(p_encroot, "-") != 0) { 273 char *keyloadunit = *(tofree++) = 274 systemd_escape(p_encroot, "zfs-load-key@", ".service"); 275 if (keyloadunit == NULL) 276 goto err; 277 278 if (strcmp(dataset, p_encroot) == 0) { 279 const char *keymountdep = NULL; 280 bool is_prompt = false; 281 bool need_network = false; 282 283 regmatch_t uri_matches[3]; 284 if (regexec(&uri_regex, p_keyloc, 285 nitems(uri_matches), uri_matches, 0) == 0) { 286 p_keyloc[uri_matches[1].rm_eo] = '\0'; 287 p_keyloc[uri_matches[2].rm_eo] = '\0'; 288 const char *scheme = 289 &p_keyloc[uri_matches[1].rm_so]; 290 const char *path = 291 &p_keyloc[uri_matches[2].rm_so]; 292 293 if (strcmp(scheme, "https") == 0 || 294 strcmp(scheme, "http") == 0) 295 need_network = true; 296 else 297 keymountdep = path; 298 } else { 299 if (strcmp(p_keyloc, "prompt") != 0) 300 fprintf(stderr, PROGNAME "[%d]: %s: " 301 "unknown non-URI keylocation=%s\n", 302 getpid(), dataset, p_keyloc); 303 304 is_prompt = true; 305 } 306 307 308 /* Generate the key-load .service unit */ 309 FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit, 310 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 311 0644); 312 if (!keyloadunit_f) { 313 fprintf(stderr, PROGNAME "[%d]: %s: " 314 "couldn't open %s under %s: %s\n", 315 getpid(), dataset, keyloadunit, destdir, 316 strerror(errno)); 317 goto err; 318 } 319 320 fprintf(keyloadunit_f, 321 OUTPUT_HEADER 322 "[Unit]\n" 323 "Description=Load ZFS key for %s\n" 324 "SourcePath=" FSLIST "/%s\n" 325 "Documentation=man:zfs-mount-generator(8)\n" 326 "DefaultDependencies=no\n" 327 "Wants=%s\n" 328 "After=%s\n", 329 dataset, cachefile, wants, after); 330 331 if (need_network) 332 fprintf(keyloadunit_f, 333 "Wants=network-online.target\n" 334 "After=network-online.target\n"); 335 336 if (p_systemd_requires) 337 fprintf(keyloadunit_f, 338 "Requires=%s\n", p_systemd_requires); 339 340 if (p_systemd_requiresmountsfor) 341 fprintf(keyloadunit_f, 342 "RequiresMountsFor=%s\n", 343 p_systemd_requiresmountsfor); 344 if (keymountdep) 345 fprintf(keyloadunit_f, 346 "RequiresMountsFor='%s'\n", keymountdep); 347 348 /* BEGIN CSTYLED */ 349 fprintf(keyloadunit_f, 350 "\n" 351 "[Service]\n" 352 "Type=oneshot\n" 353 "RemainAfterExit=yes\n" 354 "# This avoids a dependency loop involving systemd-journald.socket if this\n" 355 "# dataset is a parent of the root filesystem.\n" 356 "StandardOutput=null\n" 357 "StandardError=null\n" 358 "ExecStart=/bin/sh -euc '" 359 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;", 360 dataset); 361 if (is_prompt) 362 fprintf(keyloadunit_f, 363 "for i in 1 2 3; do " 364 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |" 365 "" ZFS " load-key \"%s\" && exit 0;" 366 "done;" 367 "exit 1", 368 dataset, dataset, dataset); 369 else 370 fprintf(keyloadunit_f, 371 "exec " ZFS " load-key \"%s\"", 372 dataset); 373 374 fprintf(keyloadunit_f, 375 "'\n" 376 "ExecStop=/bin/sh -euc '" 377 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;" 378 "exec " ZFS " unload-key \"%s\"" 379 "'\n", 380 dataset, dataset); 381 /* END CSTYLED */ 382 383 (void) fclose(keyloadunit_f); 384 } 385 386 /* Update dependencies for the mount file to want this */ 387 bindsto = keyloadunit; 388 if (after[0] == '\0') 389 after = keyloadunit; 390 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1) 391 after = *(tofree++) = toktmp; 392 else { 393 fprintf(stderr, PROGNAME "[%d]: %s: " 394 "out of memory to generate after=\"%s %s\"!\n", 395 getpid(), dataset, after, keyloadunit); 396 goto err; 397 } 398 } 399 400 401 /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */ 402 if (strcmp(p_systemd_ignore, "-") == 0 || 403 strcmp(p_systemd_ignore, "off") == 0) { 404 /* ok */ 405 } else if (strcmp(p_systemd_ignore, "on") == 0) 406 goto end; 407 else { 408 fprintf(stderr, PROGNAME "[%d]: %s: " 409 "invalid org.openzfs.systemd:ignore=%s\n", 410 getpid(), dataset, p_systemd_ignore); 411 goto err; 412 } 413 414 /* Check for canmount */ 415 if (strcmp(p_canmount, "on") == 0) { 416 /* ok */ 417 } else if (strcmp(p_canmount, "noauto") == 0) 418 noauto = true; 419 else if (strcmp(p_canmount, "off") == 0) 420 goto end; 421 else { 422 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n", 423 getpid(), dataset, p_canmount); 424 goto err; 425 } 426 427 /* Check for legacy and blank mountpoints */ 428 if (strcmp(p_mountpoint, "legacy") == 0 || 429 strcmp(p_mountpoint, "none") == 0) 430 goto end; 431 else if (p_mountpoint[0] != '/') { 432 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n", 433 getpid(), dataset, p_mountpoint); 434 goto err; 435 } 436 437 /* Escape the mountpoint per systemd policy */ 438 simplify_path(p_mountpoint); 439 const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount"); 440 if (mountfile == NULL) { 441 fprintf(stderr, 442 PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n", 443 getpid(), dataset, p_mountpoint); 444 goto err; 445 } 446 447 448 /* 449 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options 450 * 451 * The longest string achievable here is 452 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand". 453 */ 454 char opts[64] = ""; 455 456 /* atime */ 457 if (strcmp(p_atime, "on") == 0) { 458 /* relatime */ 459 if (strcmp(p_relatime, "on") == 0) 460 strcat(opts, ",atime,relatime"); 461 else if (strcmp(p_relatime, "off") == 0) 462 strcat(opts, ",atime,strictatime"); 463 else 464 fprintf(stderr, 465 PROGNAME "[%d]: %s: invalid relatime=%s\n", 466 getpid(), dataset, p_relatime); 467 } else if (strcmp(p_atime, "off") == 0) { 468 strcat(opts, ",noatime"); 469 } else 470 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n", 471 getpid(), dataset, p_atime); 472 473 /* devices */ 474 if (strcmp(p_devices, "on") == 0) 475 strcat(opts, ",dev"); 476 else if (strcmp(p_devices, "off") == 0) 477 strcat(opts, ",nodev"); 478 else 479 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n", 480 getpid(), dataset, p_devices); 481 482 /* exec */ 483 if (strcmp(p_exec, "on") == 0) 484 strcat(opts, ",exec"); 485 else if (strcmp(p_exec, "off") == 0) 486 strcat(opts, ",noexec"); 487 else 488 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n", 489 getpid(), dataset, p_exec); 490 491 /* readonly */ 492 if (strcmp(p_readonly, "on") == 0) 493 strcat(opts, ",ro"); 494 else if (strcmp(p_readonly, "off") == 0) 495 strcat(opts, ",rw"); 496 else 497 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n", 498 getpid(), dataset, p_readonly); 499 500 /* setuid */ 501 if (strcmp(p_setuid, "on") == 0) 502 strcat(opts, ",suid"); 503 else if (strcmp(p_setuid, "off") == 0) 504 strcat(opts, ",nosuid"); 505 else 506 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n", 507 getpid(), dataset, p_setuid); 508 509 /* nbmand */ 510 if (strcmp(p_nbmand, "on") == 0) 511 strcat(opts, ",mand"); 512 else if (strcmp(p_nbmand, "off") == 0) 513 strcat(opts, ",nomand"); 514 else 515 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n", 516 getpid(), dataset, p_setuid); 517 518 if (strcmp(p_systemd_wantedby, "-") != 0) { 519 noauto = true; 520 521 if (strcmp(p_systemd_wantedby, "none") != 0) 522 wantedby = p_systemd_wantedby; 523 } 524 525 if (strcmp(p_systemd_requiredby, "-") != 0) { 526 noauto = true; 527 528 if (strcmp(p_systemd_requiredby, "none") != 0) 529 requiredby = p_systemd_requiredby; 530 } 531 532 /* 533 * For datasets with canmount=on, a dependency is created for 534 * local-fs.target by default. To avoid regressions, this dependency 535 * is reduced to "wants" rather than "requires" when nofail!=off. 536 * **THIS MAY CHANGE** 537 * noauto=on disables this behavior completely. 538 */ 539 if (!noauto) { 540 if (strcmp(p_systemd_nofail, "off") == 0) 541 requiredby = strdupa("local-fs.target"); 542 else { 543 wantedby = strdupa("local-fs.target"); 544 wantedby_append = strcmp(p_systemd_nofail, "on") != 0; 545 } 546 } 547 548 /* 549 * Handle existing files: 550 * 1. We never overwrite existing files, although we may delete 551 * files if we're sure they were created by us. (see 5.) 552 * 2. We handle files differently based on canmount. 553 * Units with canmount=on always have precedence over noauto. 554 * This is enforced by processing these units before all others. 555 * It is important to use p_canmount and not noauto here, 556 * since we categorise by canmount while other properties, 557 * e.g. org.openzfs.systemd:wanted-by, also modify noauto. 558 * 3. If no unit file exists for a noauto dataset, we create one. 559 * Additionally, we use noauto_files to track the unit file names 560 * (which are the systemd-escaped mountpoints) of all (exclusively) 561 * noauto datasets that had a file created. 562 * 4. If the file to be created is found in the tracking tree, 563 * we do NOT create it. 564 * 5. If a file exists for a noauto dataset, 565 * we check whether the file name is in the array. 566 * If it is, we have multiple noauto datasets for the same 567 * mountpoint. In such cases, we remove the file for safety. 568 * We leave the file name in the tracking array to avoid 569 * further noauto datasets creating a file for this path again. 570 */ 571 572 struct stat stbuf; 573 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0; 574 bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL; 575 576 *(tofree++) = (void *)mountfile; 577 if (already_exists) { 578 if (is_known) { 579 /* If it's in noauto_files, we must be noauto too */ 580 581 /* See 5 */ 582 errno = 0; 583 (void) unlinkat(destdir_fd, mountfile, 0); 584 585 /* See 2 */ 586 fprintf(stderr, PROGNAME "[%d]: %s: " 587 "removing duplicate noauto unit %s%s%s\n", 588 getpid(), dataset, mountfile, 589 errno ? "" : " failed: ", 590 errno ? "" : strerror(errno)); 591 } else { 592 /* Don't log for canmount=noauto */ 593 if (strcmp(p_canmount, "on") == 0) 594 fprintf(stderr, PROGNAME "[%d]: %s: " 595 "%s already exists. Skipping.\n", 596 getpid(), dataset, mountfile); 597 } 598 599 /* File exists: skip current dataset */ 600 goto end; 601 } else { 602 if (is_known) { 603 /* See 4 */ 604 goto end; 605 } else if (strcmp(p_canmount, "noauto") == 0) { 606 if (tsearch(mountfile, &noauto_files, STRCMP) == NULL) 607 fprintf(stderr, PROGNAME "[%d]: %s: " 608 "out of memory for noauto datasets! " 609 "Not tracking %s.\n", 610 getpid(), dataset, mountfile); 611 else 612 /* mountfile escaped to noauto_files */ 613 *(--tofree) = NULL; 614 } 615 } 616 617 618 FILE *mountfile_f = fopenat(destdir_fd, mountfile, 619 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644); 620 if (!mountfile_f) { 621 fprintf(stderr, 622 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n", 623 getpid(), dataset, mountfile, destdir, strerror(errno)); 624 goto err; 625 } 626 627 fprintf(mountfile_f, 628 OUTPUT_HEADER 629 "[Unit]\n" 630 "SourcePath=" FSLIST "/%s\n" 631 "Documentation=man:zfs-mount-generator(8)\n" 632 "\n" 633 "Before=", 634 cachefile); 635 636 if (p_systemd_before) 637 fprintf(mountfile_f, "%s ", p_systemd_before); 638 fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */ 639 if (requiredby) 640 fprintf(mountfile_f, " %s", requiredby); 641 if (wantedby && wantedby_append) 642 fprintf(mountfile_f, " %s", wantedby); 643 644 fprintf(mountfile_f, 645 "\n" 646 "After="); 647 if (p_systemd_after) 648 fprintf(mountfile_f, "%s ", p_systemd_after); 649 fprintf(mountfile_f, "%s\n", after); 650 651 fprintf(mountfile_f, "Wants=%s\n", wants); 652 653 if (bindsto) 654 fprintf(mountfile_f, "BindsTo=%s\n", bindsto); 655 if (p_systemd_requires) 656 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires); 657 if (p_systemd_requiresmountsfor) 658 fprintf(mountfile_f, 659 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor); 660 661 fprintf(mountfile_f, 662 "\n" 663 "[Mount]\n" 664 "Where=%s\n" 665 "What=%s\n" 666 "Type=zfs\n" 667 "Options=defaults%s,zfsutil\n", 668 p_mountpoint, dataset, opts); 669 670 (void) fclose(mountfile_f); 671 672 if (!requiredby && !wantedby) 673 goto end; 674 675 /* Finally, create the appropriate dependencies */ 676 char *linktgt; 677 if (asprintf(&linktgt, "../%s", mountfile) == -1) { 678 fprintf(stderr, PROGNAME "[%d]: %s: " 679 "out of memory for dependents of %s!\n", 680 getpid(), dataset, mountfile); 681 goto err; 682 } 683 *(tofree++) = linktgt; 684 685 struct dep { 686 const char *type; 687 char *list; 688 } deps[] = { 689 {"wants", wantedby}, 690 {"requires", requiredby}, 691 {} 692 }; 693 for (struct dep *dep = deps; dep->type; ++dep) { 694 if (!dep->list) 695 continue; 696 697 for (char *reqby = strtok_r(dep->list, " ", &toktmp); 698 reqby; 699 reqby = strtok_r(NULL, " ", &toktmp)) { 700 char *depdir; 701 if (asprintf( 702 &depdir, "%s.%s", reqby, dep->type) == -1) { 703 fprintf(stderr, PROGNAME "[%d]: %s: " 704 "out of memory for dependent dir name " 705 "\"%s.%s\"!\n", 706 getpid(), dataset, reqby, dep->type); 707 continue; 708 } 709 710 (void) mkdirat(destdir_fd, depdir, 0755); 711 int depdir_fd = openat(destdir_fd, depdir, 712 O_PATH | O_DIRECTORY | O_CLOEXEC); 713 if (depdir_fd < 0) { 714 fprintf(stderr, PROGNAME "[%d]: %s: " 715 "couldn't open %s under %s: %s\n", 716 getpid(), dataset, depdir, destdir, 717 strerror(errno)); 718 free(depdir); 719 continue; 720 } 721 722 if (symlinkat(linktgt, depdir_fd, mountfile) == -1) 723 fprintf(stderr, PROGNAME "[%d]: %s: " 724 "couldn't symlink at " 725 "%s under %s under %s: %s\n", 726 getpid(), dataset, mountfile, 727 depdir, destdir, strerror(errno)); 728 729 (void) close(depdir_fd); 730 free(depdir); 731 } 732 } 733 734 end: 735 if (tofree >= tofree_all + nitems(tofree_all)) { 736 /* 737 * This won't happen as-is: 738 * we've got 8 slots and allocate 5 things at most. 739 */ 740 fprintf(stderr, 741 PROGNAME "[%d]: %s: need to free %zu > %zu!\n", 742 getpid(), dataset, tofree - tofree_all, nitems(tofree_all)); 743 ret = tofree - tofree_all; 744 } 745 746 while (tofree-- != tofree_all) 747 free(*tofree); 748 return (ret); 749 err: 750 ret = 1; 751 goto end; 752 } 753 754 755 static int 756 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused))) 757 { 758 int ret = 0; 759 760 /* 761 * Pools are guaranteed-unique by the kernel, 762 * no risk of leaking dupes here 763 */ 764 char *name = strdup(zpool_get_name(pool)); 765 if (!name || !tsearch(name, &known_pools, STRCMP)) { 766 free(name); 767 ret = ENOMEM; 768 } 769 770 zpool_close(pool); 771 return (ret); 772 } 773 774 int 775 main(int argc, char **argv) 776 { 777 struct timespec time_init = {}; 778 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init); 779 780 { 781 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); 782 if (kmfd >= 0) { 783 (void) dup2(kmfd, STDERR_FILENO); 784 (void) close(kmfd); 785 786 setlinebuf(stderr); 787 } 788 } 789 790 switch (argc) { 791 case 1: 792 /* Use default */ 793 break; 794 case 2: 795 case 4: 796 destdir = argv[1]; 797 break; 798 default: 799 fprintf(stderr, 800 PROGNAME "[%d]: wrong argument count: %d\n", 801 getpid(), argc - 1); 802 _exit(1); 803 } 804 805 { 806 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC); 807 if (destdir_fd < 0) { 808 fprintf(stderr, PROGNAME "[%d]: " 809 "can't open destination directory %s: %s\n", 810 getpid(), destdir, strerror(errno)); 811 _exit(1); 812 } 813 } 814 815 DIR *fslist_dir = opendir(FSLIST); 816 if (!fslist_dir) { 817 if (errno != ENOENT) 818 fprintf(stderr, 819 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n", 820 getpid(), strerror(errno)); 821 _exit(0); 822 } 823 824 { 825 libzfs_handle_t *libzfs = libzfs_init(); 826 if (libzfs) { 827 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0) 828 fprintf(stderr, PROGNAME "[%d]: " 829 "error listing pools, ignoring\n", 830 getpid()); 831 libzfs_fini(libzfs); 832 } else 833 fprintf(stderr, PROGNAME "[%d]: " 834 "couldn't start libzfs, ignoring\n", 835 getpid()); 836 } 837 838 { 839 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0); 840 if (regerr != 0) { 841 fprintf(stderr, 842 PROGNAME "[%d]: invalid regex: %d\n", 843 getpid(), regerr); 844 _exit(1); 845 } 846 } 847 848 bool debug = false; 849 char *line = NULL; 850 size_t linelen = 0; 851 { 852 const char *dbgenv = getenv("ZFS_DEBUG"); 853 if (dbgenv) 854 debug = atoi(dbgenv); 855 else { 856 FILE *cmdline = fopen("/proc/cmdline", "re"); 857 if (cmdline != NULL) { 858 if (getline(&line, &linelen, cmdline) >= 0) 859 debug = strstr(line, "debug"); 860 (void) fclose(cmdline); 861 } 862 } 863 864 if (debug && !isatty(STDOUT_FILENO)) 865 dup2(STDERR_FILENO, STDOUT_FILENO); 866 } 867 868 struct timespec time_start = {}; 869 if (debug) 870 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start); 871 872 struct line { 873 char *line; 874 const char *fname; 875 struct line *next; 876 } *lines_canmount_not_on = NULL; 877 878 int ret = 0; 879 struct dirent *cachent; 880 while ((cachent = readdir(fslist_dir)) != NULL) { 881 if (strcmp(cachent->d_name, ".") == 0 || 882 strcmp(cachent->d_name, "..") == 0) 883 continue; 884 885 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name, 886 O_RDONLY | O_CLOEXEC, "r", 0); 887 if (!cachefile) { 888 fprintf(stderr, PROGNAME "[%d]: " 889 "couldn't open %s under " FSLIST ": %s\n", 890 getpid(), cachent->d_name, strerror(errno)); 891 continue; 892 } 893 894 const char *filename = FREE_STATICS ? "(elided)" : NULL; 895 896 ssize_t read; 897 while ((read = getline(&line, &linelen, cachefile)) >= 0) { 898 line[read - 1] = '\0'; /* newline */ 899 900 char *canmount = line; 901 canmount += strcspn(canmount, "\t"); 902 canmount += strspn(canmount, "\t"); 903 canmount += strcspn(canmount, "\t"); 904 canmount += strspn(canmount, "\t"); 905 bool canmount_on = strncmp(canmount, "on", 2) == 0; 906 907 if (canmount_on) 908 ret |= line_worker(line, cachent->d_name); 909 else { 910 if (filename == NULL) 911 filename = 912 strdup(cachent->d_name) ?: "(?)"; 913 914 struct line *l = calloc(1, sizeof (*l)); 915 char *nl = strdup(line); 916 if (l == NULL || nl == NULL) { 917 fprintf(stderr, PROGNAME "[%d]: " 918 "out of memory for \"%s\" in %s\n", 919 getpid(), line, cachent->d_name); 920 free(l); 921 free(nl); 922 continue; 923 } 924 l->line = nl; 925 l->fname = filename; 926 l->next = lines_canmount_not_on; 927 lines_canmount_not_on = l; 928 } 929 } 930 931 fclose(cachefile); 932 } 933 free(line); 934 935 while (lines_canmount_not_on) { 936 struct line *l = lines_canmount_not_on; 937 lines_canmount_not_on = l->next; 938 939 ret |= line_worker(l->line, l->fname); 940 if (FREE_STATICS) { 941 free(l->line); 942 free(l); 943 } 944 } 945 946 if (debug) { 947 struct timespec time_end = {}; 948 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end); 949 950 struct rusage usage; 951 getrusage(RUSAGE_SELF, &usage); 952 printf( 953 "\n" 954 PROGNAME ": " 955 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n", 956 (unsigned long long) usage.ru_utime.tv_sec, 957 (unsigned int) usage.ru_utime.tv_usec, 958 (unsigned long long) usage.ru_stime.tv_sec, 959 (unsigned int) usage.ru_stime.tv_usec, 960 usage.ru_maxrss * 1024); 961 962 if (time_start.tv_nsec > time_end.tv_nsec) { 963 time_end.tv_nsec = 964 1000000000 + time_end.tv_nsec - time_start.tv_nsec; 965 time_end.tv_sec -= 1; 966 } else 967 time_end.tv_nsec -= time_start.tv_nsec; 968 time_end.tv_sec -= time_start.tv_sec; 969 970 if (time_init.tv_nsec > time_start.tv_nsec) { 971 time_start.tv_nsec = 972 1000000000 + time_start.tv_nsec - time_init.tv_nsec; 973 time_start.tv_sec -= 1; 974 } else 975 time_start.tv_nsec -= time_init.tv_nsec; 976 time_start.tv_sec -= time_init.tv_sec; 977 978 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec; 979 time_init.tv_sec = 980 time_start.tv_sec + time_end.tv_sec + 981 time_init.tv_nsec / 1000000000; 982 time_init.tv_nsec %= 1000000000; 983 984 printf(PROGNAME ": " 985 "total=%llu.%09llus = " 986 "init=%llu.%09llus + real=%llu.%09llus\n", 987 (unsigned long long) time_init.tv_sec, 988 (unsigned long long) time_init.tv_nsec, 989 (unsigned long long) time_start.tv_sec, 990 (unsigned long long) time_start.tv_nsec, 991 (unsigned long long) time_end.tv_sec, 992 (unsigned long long) time_end.tv_nsec); 993 994 fflush(stdout); 995 } 996 997 if (FREE_STATICS) { 998 closedir(fslist_dir); 999 tdestroy(noauto_files, free); 1000 tdestroy(known_pools, free); 1001 regfree(&uri_regex); 1002 } 1003 _exit(ret); 1004 } 1005