| /* |
| * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com> |
| * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| |
| #include <sys/resource.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <sys/stat.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <time.h> |
| #include <regex.h> |
| #include <search.h> |
| #include <dirent.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <errno.h> |
| #include <libzfs.h> |
| |
| /* |
| * For debugging only. |
| * |
| * Free statics with trivial life-times, |
| * but saved line filenames are replaced with a static string. |
| */ |
| #define FREE_STATICS false |
| |
| #define nitems(arr) (sizeof (arr) / sizeof (*arr)) |
| #define STRCMP ((int(*)(const void *, const void *))&strcmp) |
| |
| |
| #define PROGNAME "zfs-mount-generator" |
| #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache" |
| #define ZFS SBINDIR "/zfs" |
| |
| #define OUTPUT_HEADER \ |
| "# Automatically generated by " PROGNAME "\n" \ |
| "\n" |
| |
| /* |
| * Starts like the one in libzfs_util.c but also matches "//" |
| * and captures until the end, since we actually use it for path extraxion |
| */ |
| #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$" |
| static regex_t uri_regex; |
| |
| static const char *destdir = "/tmp"; |
| static int destdir_fd = -1; |
| |
| static void *known_pools = NULL; /* tsearch() of C strings */ |
| static void *noauto_files = NULL; /* tsearch() of C strings */ |
| |
| |
| static char * |
| systemd_escape(const char *input, const char *prepend, const char *append) |
| { |
| size_t len = strlen(input); |
| size_t applen = strlen(append); |
| size_t prelen = strlen(prepend); |
| char *ret = malloc(4 * len + prelen + applen + 1); |
| if (!ret) { |
| fprintf(stderr, PROGNAME "[%d]: " |
| "out of memory to escape \"%s%s%s\"!\n", |
| getpid(), prepend, input, append); |
| return (NULL); |
| } |
| |
| memcpy(ret, prepend, prelen); |
| char *out = ret + prelen; |
| |
| const char *cur = input; |
| if (*cur == '.') { |
| memcpy(out, "\\x2e", 4); |
| out += 4; |
| ++cur; |
| } |
| for (; *cur; ++cur) { |
| if (*cur == '/') |
| *(out++) = '-'; |
| else if (strchr( |
| "0123456789" |
| "abcdefghijklmnopqrstuvwxyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| ":_.", *cur)) |
| *(out++) = *cur; |
| else { |
| sprintf(out, "\\x%02x", (int)*cur); |
| out += 4; |
| } |
| } |
| |
| memcpy(out, append, applen + 1); |
| return (ret); |
| } |
| |
| static void |
| simplify_path(char *path) |
| { |
| char *out = path; |
| for (char *cur = path; *cur; ++cur) { |
| if (*cur == '/') { |
| while (*(cur + 1) == '/') |
| ++cur; |
| *(out++) = '/'; |
| } else |
| *(out++) = *cur; |
| } |
| |
| *(out++) = '\0'; |
| } |
| |
| static bool |
| strendswith(const char *what, const char *suff) |
| { |
| size_t what_l = strlen(what); |
| size_t suff_l = strlen(suff); |
| |
| return ((what_l >= suff_l) && |
| (strcmp(what + what_l - suff_l, suff) == 0)); |
| } |
| |
| /* Assumes already-simplified path, doesn't modify input */ |
| static char * |
| systemd_escape_path(char *input, const char *prepend, const char *append) |
| { |
| if (strcmp(input, "/") == 0) { |
| char *ret; |
| if (asprintf(&ret, "%s-%s", prepend, append) == -1) { |
| fprintf(stderr, PROGNAME "[%d]: " |
| "out of memory to escape \"%s%s%s\"!\n", |
| getpid(), prepend, input, append); |
| ret = NULL; |
| } |
| return (ret); |
| } else { |
| /* |
| * path_is_normalized() (flattened for absolute paths here), |
| * required for proper escaping |
| */ |
| if (strstr(input, "/./") || strstr(input, "/../") || |
| strendswith(input, "/.") || strendswith(input, "/..")) |
| return (NULL); |
| |
| |
| if (input[0] == '/') |
| ++input; |
| |
| char *back = &input[strlen(input) - 1]; |
| bool deslash = *back == '/'; |
| if (deslash) |
| *back = '\0'; |
| |
| char *ret = systemd_escape(input, prepend, append); |
| |
| if (deslash) |
| *back = '/'; |
| return (ret); |
| } |
| } |
| |
| static FILE * |
| fopenat(int dirfd, const char *pathname, int flags, |
| const char *stream_mode, mode_t mode) |
| { |
| int fd = openat(dirfd, pathname, flags, mode); |
| if (fd < 0) |
| return (NULL); |
| |
| return (fdopen(fd, stream_mode)); |
| } |
| |
| static int |
| line_worker(char *line, const char *cachefile) |
| { |
| int ret = 0; |
| void *tofree_all[8]; |
| void **tofree = tofree_all; |
| |
| char *toktmp; |
| /* BEGIN CSTYLED */ |
| const char *dataset = strtok_r(line, "\t", &toktmp); |
| char *p_mountpoint = strtok_r(NULL, "\t", &toktmp); |
| const char *p_canmount = strtok_r(NULL, "\t", &toktmp); |
| const char *p_atime = strtok_r(NULL, "\t", &toktmp); |
| const char *p_relatime = strtok_r(NULL, "\t", &toktmp); |
| const char *p_devices = strtok_r(NULL, "\t", &toktmp); |
| const char *p_exec = strtok_r(NULL, "\t", &toktmp); |
| const char *p_readonly = strtok_r(NULL, "\t", &toktmp); |
| const char *p_setuid = strtok_r(NULL, "\t", &toktmp); |
| const char *p_nbmand = strtok_r(NULL, "\t", &toktmp); |
| const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none"); |
| const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-"); |
| char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-"); |
| const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-"; |
| /* END CSTYLED */ |
| |
| const char *pool = dataset; |
| if ((toktmp = strchr(pool, '/')) != NULL) |
| pool = strndupa(pool, toktmp - pool); |
| |
| if (p_nbmand == NULL) { |
| fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n", |
| getpid(), dataset); |
| goto err; |
| } |
| |
| /* Minimal pre-requisites to mount a ZFS dataset */ |
| const char *after = "zfs-import.target"; |
| const char *wants = "zfs-import.target"; |
| const char *bindsto = NULL; |
| char *wantedby = NULL; |
| char *requiredby = NULL; |
| bool noauto = false; |
| bool wantedby_append = true; |
| |
| /* |
| * zfs-import.target is not needed if the pool is already imported. |
| * This avoids a dependency loop on root-on-ZFS systems: |
| * systemd-random-seed.service After (via RequiresMountsFor) |
| * var-lib.mount After |
| * zfs-import.target After |
| * zfs-import-{cache,scan}.service After |
| * cryptsetup.service After |
| * systemd-random-seed.service |
| */ |
| if (tfind(pool, &known_pools, STRCMP)) { |
| after = ""; |
| wants = ""; |
| } |
| |
| if (strcmp(p_systemd_after, "-") == 0) |
| p_systemd_after = NULL; |
| if (strcmp(p_systemd_before, "-") == 0) |
| p_systemd_before = NULL; |
| if (strcmp(p_systemd_requires, "-") == 0) |
| p_systemd_requires = NULL; |
| if (strcmp(p_systemd_requiresmountsfor, "-") == 0) |
| p_systemd_requiresmountsfor = NULL; |
| |
| |
| if (strcmp(p_encroot, "-") != 0) { |
| char *keyloadunit = *(tofree++) = |
| systemd_escape(p_encroot, "zfs-load-key@", ".service"); |
| if (keyloadunit == NULL) |
| goto err; |
| |
| if (strcmp(dataset, p_encroot) == 0) { |
| const char *keymountdep = NULL; |
| bool is_prompt = false; |
| bool need_network = false; |
| |
| regmatch_t uri_matches[3]; |
| if (regexec(&uri_regex, p_keyloc, |
| nitems(uri_matches), uri_matches, 0) == 0) { |
| p_keyloc[uri_matches[1].rm_eo] = '\0'; |
| p_keyloc[uri_matches[2].rm_eo] = '\0'; |
| const char *scheme = |
| &p_keyloc[uri_matches[1].rm_so]; |
| const char *path = |
| &p_keyloc[uri_matches[2].rm_so]; |
| |
| if (strcmp(scheme, "https") == 0 || |
| strcmp(scheme, "http") == 0) |
| need_network = true; |
| else |
| keymountdep = path; |
| } else { |
| if (strcmp(p_keyloc, "prompt") != 0) |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "unknown non-URI keylocation=%s\n", |
| getpid(), dataset, p_keyloc); |
| |
| is_prompt = true; |
| } |
| |
| |
| /* Generate the key-load .service unit */ |
| FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit, |
| O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", |
| 0644); |
| if (!keyloadunit_f) { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "couldn't open %s under %s: %s\n", |
| getpid(), dataset, keyloadunit, destdir, |
| strerror(errno)); |
| goto err; |
| } |
| |
| fprintf(keyloadunit_f, |
| OUTPUT_HEADER |
| "[Unit]\n" |
| "Description=Load ZFS key for %s\n" |
| "SourcePath=" FSLIST "/%s\n" |
| "Documentation=man:zfs-mount-generator(8)\n" |
| "DefaultDependencies=no\n" |
| "Wants=%s\n" |
| "After=%s\n", |
| dataset, cachefile, wants, after); |
| |
| if (need_network) |
| fprintf(keyloadunit_f, |
| "Wants=network-online.target\n" |
| "After=network-online.target\n"); |
| |
| if (p_systemd_requires) |
| fprintf(keyloadunit_f, |
| "Requires=%s\n", p_systemd_requires); |
| |
| if (p_systemd_requiresmountsfor) |
| fprintf(keyloadunit_f, |
| "RequiresMountsFor=%s\n", |
| p_systemd_requiresmountsfor); |
| if (keymountdep) |
| fprintf(keyloadunit_f, |
| "RequiresMountsFor='%s'\n", keymountdep); |
| |
| /* BEGIN CSTYLED */ |
| fprintf(keyloadunit_f, |
| "\n" |
| "[Service]\n" |
| "Type=oneshot\n" |
| "RemainAfterExit=yes\n" |
| "# This avoids a dependency loop involving systemd-journald.socket if this\n" |
| "# dataset is a parent of the root filesystem.\n" |
| "StandardOutput=null\n" |
| "StandardError=null\n" |
| "ExecStart=/bin/sh -euc '" |
| "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;", |
| dataset); |
| if (is_prompt) |
| fprintf(keyloadunit_f, |
| "for i in 1 2 3; do " |
| "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |" |
| "" ZFS " load-key \"%s\" && exit 0;" |
| "done;" |
| "exit 1", |
| dataset, dataset, dataset); |
| else |
| fprintf(keyloadunit_f, |
| "exec " ZFS " load-key \"%s\"", |
| dataset); |
| |
| fprintf(keyloadunit_f, |
| "'\n" |
| "ExecStop=/bin/sh -euc '" |
| "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;" |
| "exec " ZFS " unload-key \"%s\"" |
| "'\n", |
| dataset, dataset); |
| /* END CSTYLED */ |
| |
| (void) fclose(keyloadunit_f); |
| } |
| |
| /* Update dependencies for the mount file to want this */ |
| bindsto = keyloadunit; |
| if (after[0] == '\0') |
| after = keyloadunit; |
| else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1) |
| after = *(tofree++) = toktmp; |
| else { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "out of memory to generate after=\"%s %s\"!\n", |
| getpid(), dataset, after, keyloadunit); |
| goto err; |
| } |
| } |
| |
| |
| /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */ |
| if (strcmp(p_systemd_ignore, "-") == 0 || |
| strcmp(p_systemd_ignore, "off") == 0) { |
| /* ok */ |
| } else if (strcmp(p_systemd_ignore, "on") == 0) |
| goto end; |
| else { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "invalid org.openzfs.systemd:ignore=%s\n", |
| getpid(), dataset, p_systemd_ignore); |
| goto err; |
| } |
| |
| /* Check for canmount */ |
| if (strcmp(p_canmount, "on") == 0) { |
| /* ok */ |
| } else if (strcmp(p_canmount, "noauto") == 0) |
| noauto = true; |
| else if (strcmp(p_canmount, "off") == 0) |
| goto end; |
| else { |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n", |
| getpid(), dataset, p_canmount); |
| goto err; |
| } |
| |
| /* Check for legacy and blank mountpoints */ |
| if (strcmp(p_mountpoint, "legacy") == 0 || |
| strcmp(p_mountpoint, "none") == 0) |
| goto end; |
| else if (p_mountpoint[0] != '/') { |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n", |
| getpid(), dataset, p_mountpoint); |
| goto err; |
| } |
| |
| /* Escape the mountpoint per systemd policy */ |
| simplify_path(p_mountpoint); |
| const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount"); |
| if (mountfile == NULL) { |
| fprintf(stderr, |
| PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n", |
| getpid(), dataset, p_mountpoint); |
| goto err; |
| } |
| |
| |
| /* |
| * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options |
| * |
| * The longest string achievable here is |
| * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand". |
| */ |
| char opts[64] = ""; |
| |
| /* atime */ |
| if (strcmp(p_atime, "on") == 0) { |
| /* relatime */ |
| if (strcmp(p_relatime, "on") == 0) |
| strcat(opts, ",atime,relatime"); |
| else if (strcmp(p_relatime, "off") == 0) |
| strcat(opts, ",atime,strictatime"); |
| else |
| fprintf(stderr, |
| PROGNAME "[%d]: %s: invalid relatime=%s\n", |
| getpid(), dataset, p_relatime); |
| } else if (strcmp(p_atime, "off") == 0) { |
| strcat(opts, ",noatime"); |
| } else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n", |
| getpid(), dataset, p_atime); |
| |
| /* devices */ |
| if (strcmp(p_devices, "on") == 0) |
| strcat(opts, ",dev"); |
| else if (strcmp(p_devices, "off") == 0) |
| strcat(opts, ",nodev"); |
| else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n", |
| getpid(), dataset, p_devices); |
| |
| /* exec */ |
| if (strcmp(p_exec, "on") == 0) |
| strcat(opts, ",exec"); |
| else if (strcmp(p_exec, "off") == 0) |
| strcat(opts, ",noexec"); |
| else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n", |
| getpid(), dataset, p_exec); |
| |
| /* readonly */ |
| if (strcmp(p_readonly, "on") == 0) |
| strcat(opts, ",ro"); |
| else if (strcmp(p_readonly, "off") == 0) |
| strcat(opts, ",rw"); |
| else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n", |
| getpid(), dataset, p_readonly); |
| |
| /* setuid */ |
| if (strcmp(p_setuid, "on") == 0) |
| strcat(opts, ",suid"); |
| else if (strcmp(p_setuid, "off") == 0) |
| strcat(opts, ",nosuid"); |
| else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n", |
| getpid(), dataset, p_setuid); |
| |
| /* nbmand */ |
| if (strcmp(p_nbmand, "on") == 0) |
| strcat(opts, ",mand"); |
| else if (strcmp(p_nbmand, "off") == 0) |
| strcat(opts, ",nomand"); |
| else |
| fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n", |
| getpid(), dataset, p_setuid); |
| |
| if (strcmp(p_systemd_wantedby, "-") != 0) { |
| noauto = true; |
| |
| if (strcmp(p_systemd_wantedby, "none") != 0) |
| wantedby = p_systemd_wantedby; |
| } |
| |
| if (strcmp(p_systemd_requiredby, "-") != 0) { |
| noauto = true; |
| |
| if (strcmp(p_systemd_requiredby, "none") != 0) |
| requiredby = p_systemd_requiredby; |
| } |
| |
| /* |
| * For datasets with canmount=on, a dependency is created for |
| * local-fs.target by default. To avoid regressions, this dependency |
| * is reduced to "wants" rather than "requires" when nofail!=off. |
| * **THIS MAY CHANGE** |
| * noauto=on disables this behavior completely. |
| */ |
| if (!noauto) { |
| if (strcmp(p_systemd_nofail, "off") == 0) |
| requiredby = strdupa("local-fs.target"); |
| else { |
| wantedby = strdupa("local-fs.target"); |
| wantedby_append = strcmp(p_systemd_nofail, "on") != 0; |
| } |
| } |
| |
| /* |
| * Handle existing files: |
| * 1. We never overwrite existing files, although we may delete |
| * files if we're sure they were created by us. (see 5.) |
| * 2. We handle files differently based on canmount. |
| * Units with canmount=on always have precedence over noauto. |
| * This is enforced by processing these units before all others. |
| * It is important to use p_canmount and not noauto here, |
| * since we categorise by canmount while other properties, |
| * e.g. org.openzfs.systemd:wanted-by, also modify noauto. |
| * 3. If no unit file exists for a noauto dataset, we create one. |
| * Additionally, we use noauto_files to track the unit file names |
| * (which are the systemd-escaped mountpoints) of all (exclusively) |
| * noauto datasets that had a file created. |
| * 4. If the file to be created is found in the tracking tree, |
| * we do NOT create it. |
| * 5. If a file exists for a noauto dataset, |
| * we check whether the file name is in the array. |
| * If it is, we have multiple noauto datasets for the same |
| * mountpoint. In such cases, we remove the file for safety. |
| * We leave the file name in the tracking array to avoid |
| * further noauto datasets creating a file for this path again. |
| */ |
| |
| struct stat stbuf; |
| bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0; |
| bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL; |
| |
| *(tofree++) = (void *)mountfile; |
| if (already_exists) { |
| if (is_known) { |
| /* If it's in noauto_files, we must be noauto too */ |
| |
| /* See 5 */ |
| errno = 0; |
| (void) unlinkat(destdir_fd, mountfile, 0); |
| |
| /* See 2 */ |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "removing duplicate noauto unit %s%s%s\n", |
| getpid(), dataset, mountfile, |
| errno ? "" : " failed: ", |
| errno ? "" : strerror(errno)); |
| } else { |
| /* Don't log for canmount=noauto */ |
| if (strcmp(p_canmount, "on") == 0) |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "%s already exists. Skipping.\n", |
| getpid(), dataset, mountfile); |
| } |
| |
| /* File exists: skip current dataset */ |
| goto end; |
| } else { |
| if (is_known) { |
| /* See 4 */ |
| goto end; |
| } else if (strcmp(p_canmount, "noauto") == 0) { |
| if (tsearch(mountfile, &noauto_files, STRCMP) == NULL) |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "out of memory for noauto datasets! " |
| "Not tracking %s.\n", |
| getpid(), dataset, mountfile); |
| else |
| /* mountfile escaped to noauto_files */ |
| *(--tofree) = NULL; |
| } |
| } |
| |
| |
| FILE *mountfile_f = fopenat(destdir_fd, mountfile, |
| O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644); |
| if (!mountfile_f) { |
| fprintf(stderr, |
| PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n", |
| getpid(), dataset, mountfile, destdir, strerror(errno)); |
| goto err; |
| } |
| |
| fprintf(mountfile_f, |
| OUTPUT_HEADER |
| "[Unit]\n" |
| "SourcePath=" FSLIST "/%s\n" |
| "Documentation=man:zfs-mount-generator(8)\n" |
| "\n" |
| "Before=", |
| cachefile); |
| |
| if (p_systemd_before) |
| fprintf(mountfile_f, "%s ", p_systemd_before); |
| fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */ |
| if (requiredby) |
| fprintf(mountfile_f, " %s", requiredby); |
| if (wantedby && wantedby_append) |
| fprintf(mountfile_f, " %s", wantedby); |
| |
| fprintf(mountfile_f, |
| "\n" |
| "After="); |
| if (p_systemd_after) |
| fprintf(mountfile_f, "%s ", p_systemd_after); |
| fprintf(mountfile_f, "%s\n", after); |
| |
| fprintf(mountfile_f, "Wants=%s\n", wants); |
| |
| if (bindsto) |
| fprintf(mountfile_f, "BindsTo=%s\n", bindsto); |
| if (p_systemd_requires) |
| fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires); |
| if (p_systemd_requiresmountsfor) |
| fprintf(mountfile_f, |
| "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor); |
| |
| fprintf(mountfile_f, |
| "\n" |
| "[Mount]\n" |
| "Where=%s\n" |
| "What=%s\n" |
| "Type=zfs\n" |
| "Options=defaults%s,zfsutil\n", |
| p_mountpoint, dataset, opts); |
| |
| (void) fclose(mountfile_f); |
| |
| if (!requiredby && !wantedby) |
| goto end; |
| |
| /* Finally, create the appropriate dependencies */ |
| char *linktgt; |
| if (asprintf(&linktgt, "../%s", mountfile) == -1) { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "out of memory for dependents of %s!\n", |
| getpid(), dataset, mountfile); |
| goto err; |
| } |
| *(tofree++) = linktgt; |
| |
| char *dependencies[][2] = { |
| {"wants", wantedby}, |
| {"requires", requiredby}, |
| {} |
| }; |
| for (__typeof__(&*dependencies) dep = &*dependencies; **dep; ++dep) { |
| if (!(*dep)[1]) |
| continue; |
| |
| for (char *reqby = strtok_r((*dep)[1], " ", &toktmp); |
| reqby; |
| reqby = strtok_r(NULL, " ", &toktmp)) { |
| char *depdir; |
| if (asprintf( |
| &depdir, "%s.%s", reqby, (*dep)[0]) == -1) { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "out of memory for dependent dir name " |
| "\"%s.%s\"!\n", |
| getpid(), dataset, reqby, (*dep)[0]); |
| continue; |
| } |
| |
| (void) mkdirat(destdir_fd, depdir, 0755); |
| int depdir_fd = openat(destdir_fd, depdir, |
| O_PATH | O_DIRECTORY | O_CLOEXEC); |
| if (depdir_fd < 0) { |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "couldn't open %s under %s: %s\n", |
| getpid(), dataset, depdir, destdir, |
| strerror(errno)); |
| free(depdir); |
| continue; |
| } |
| |
| if (symlinkat(linktgt, depdir_fd, mountfile) == -1) |
| fprintf(stderr, PROGNAME "[%d]: %s: " |
| "couldn't symlink at " |
| "%s under %s under %s: %s\n", |
| getpid(), dataset, mountfile, |
| depdir, destdir, strerror(errno)); |
| |
| (void) close(depdir_fd); |
| free(depdir); |
| } |
| } |
| |
| end: |
| if (tofree >= tofree_all + nitems(tofree_all)) { |
| /* |
| * This won't happen as-is: |
| * we've got 8 slots and allocate 4 things at most. |
| */ |
| fprintf(stderr, |
| PROGNAME "[%d]: %s: need to free %zu > %zu!\n", |
| getpid(), dataset, tofree - tofree_all, nitems(tofree_all)); |
| ret = tofree - tofree_all; |
| } |
| |
| while (tofree-- != tofree_all) |
| free(*tofree); |
| return (ret); |
| err: |
| ret = 1; |
| goto end; |
| } |
| |
| |
| static int |
| pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused))) |
| { |
| int ret = 0; |
| |
| /* |
| * Pools are guaranteed-unique by the kernel, |
| * no risk of leaking dupes here |
| */ |
| char *name = strdup(zpool_get_name(pool)); |
| if (!name || !tsearch(name, &known_pools, STRCMP)) { |
| free(name); |
| ret = ENOMEM; |
| } |
| |
| zpool_close(pool); |
| return (ret); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| struct timespec time_init = {}; |
| clock_gettime(CLOCK_MONOTONIC_RAW, &time_init); |
| |
| { |
| int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); |
| if (kmfd >= 0) { |
| (void) dup2(kmfd, STDERR_FILENO); |
| (void) close(kmfd); |
| |
| setlinebuf(stderr); |
| } |
| } |
| |
| switch (argc) { |
| case 1: |
| /* Use default */ |
| break; |
| case 2: |
| case 4: |
| destdir = argv[1]; |
| break; |
| default: |
| fprintf(stderr, |
| PROGNAME "[%d]: wrong argument count: %d\n", |
| getpid(), argc - 1); |
| _exit(1); |
| } |
| |
| { |
| destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC); |
| if (destdir_fd < 0) { |
| fprintf(stderr, PROGNAME "[%d]: " |
| "can't open destination directory %s: %s\n", |
| getpid(), destdir, strerror(errno)); |
| _exit(1); |
| } |
| } |
| |
| DIR *fslist_dir = opendir(FSLIST); |
| if (!fslist_dir) { |
| if (errno != ENOENT) |
| fprintf(stderr, |
| PROGNAME "[%d]: couldn't open " FSLIST ": %s\n", |
| getpid(), strerror(errno)); |
| _exit(0); |
| } |
| |
| { |
| libzfs_handle_t *libzfs = libzfs_init(); |
| if (libzfs) { |
| if (zpool_iter(libzfs, pool_enumerator, NULL) != 0) |
| fprintf(stderr, PROGNAME "[%d]: " |
| "error listing pools, ignoring\n", |
| getpid()); |
| libzfs_fini(libzfs); |
| } else |
| fprintf(stderr, PROGNAME "[%d]: " |
| "couldn't start libzfs, ignoring\n", |
| getpid()); |
| } |
| |
| { |
| int regerr = regcomp(&uri_regex, URI_REGEX_S, 0); |
| if (regerr != 0) { |
| fprintf(stderr, |
| PROGNAME "[%d]: invalid regex: %d\n", |
| getpid(), regerr); |
| _exit(1); |
| } |
| } |
| |
| bool debug = false; |
| char *line = NULL; |
| size_t linelen = 0; |
| { |
| const char *dbgenv = getenv("ZFS_DEBUG"); |
| if (dbgenv) |
| debug = atoi(dbgenv); |
| else { |
| FILE *cmdline = fopen("/proc/cmdline", "re"); |
| if (cmdline != NULL) { |
| if (getline(&line, &linelen, cmdline) >= 0) |
| debug = strstr(line, "debug"); |
| (void) fclose(cmdline); |
| } |
| } |
| |
| if (debug && !isatty(STDOUT_FILENO)) |
| dup2(STDERR_FILENO, STDOUT_FILENO); |
| } |
| |
| struct timespec time_start = {}; |
| if (debug) |
| clock_gettime(CLOCK_MONOTONIC_RAW, &time_start); |
| |
| struct line { |
| char *line; |
| const char *fname; |
| struct line *next; |
| } *lines_canmount_not_on = NULL; |
| |
| int ret = 0; |
| struct dirent *cachent; |
| while ((cachent = readdir(fslist_dir)) != NULL) { |
| if (strcmp(cachent->d_name, ".") == 0 || |
| strcmp(cachent->d_name, "..") == 0) |
| continue; |
| |
| FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name, |
| O_RDONLY | O_CLOEXEC, "r", 0); |
| if (!cachefile) { |
| fprintf(stderr, PROGNAME "[%d]: " |
| "couldn't open %s under " FSLIST ": %s\n", |
| getpid(), cachent->d_name, strerror(errno)); |
| continue; |
| } |
| |
| const char *filename = FREE_STATICS ? "(elided)" : NULL; |
| |
| ssize_t read; |
| while ((read = getline(&line, &linelen, cachefile)) >= 0) { |
| line[read - 1] = '\0'; /* newline */ |
| |
| char *canmount = line; |
| canmount += strcspn(canmount, "\t"); |
| canmount += strspn(canmount, "\t"); |
| canmount += strcspn(canmount, "\t"); |
| canmount += strspn(canmount, "\t"); |
| bool canmount_on = strncmp(canmount, "on", 2) == 0; |
| |
| if (canmount_on) |
| ret |= line_worker(line, cachent->d_name); |
| else { |
| if (filename == NULL) |
| filename = |
| strdup(cachent->d_name) ?: "(?)"; |
| |
| struct line *l = calloc(1, sizeof (*l)); |
| char *nl = strdup(line); |
| if (l == NULL || nl == NULL) { |
| fprintf(stderr, PROGNAME "[%d]: " |
| "out of memory for \"%s\" in %s\n", |
| getpid(), line, cachent->d_name); |
| free(l); |
| free(nl); |
| continue; |
| } |
| l->line = nl; |
| l->fname = filename; |
| l->next = lines_canmount_not_on; |
| lines_canmount_not_on = l; |
| } |
| } |
| |
| fclose(cachefile); |
| } |
| free(line); |
| |
| while (lines_canmount_not_on) { |
| struct line *l = lines_canmount_not_on; |
| lines_canmount_not_on = l->next; |
| |
| ret |= line_worker(l->line, l->fname); |
| if (FREE_STATICS) { |
| free(l->line); |
| free(l); |
| } |
| } |
| |
| if (debug) { |
| struct timespec time_end = {}; |
| clock_gettime(CLOCK_MONOTONIC_RAW, &time_end); |
| |
| struct rusage usage; |
| getrusage(RUSAGE_SELF, &usage); |
| printf( |
| "\n" |
| PROGNAME ": " |
| "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n", |
| (unsigned long long) usage.ru_utime.tv_sec, |
| (unsigned int) usage.ru_utime.tv_usec, |
| (unsigned long long) usage.ru_stime.tv_sec, |
| (unsigned int) usage.ru_stime.tv_usec, |
| usage.ru_maxrss * 1024); |
| |
| if (time_start.tv_nsec > time_end.tv_nsec) { |
| time_end.tv_nsec = |
| 1000000000 + time_end.tv_nsec - time_start.tv_nsec; |
| time_end.tv_sec -= 1; |
| } else |
| time_end.tv_nsec -= time_start.tv_nsec; |
| time_end.tv_sec -= time_start.tv_sec; |
| |
| if (time_init.tv_nsec > time_start.tv_nsec) { |
| time_start.tv_nsec = |
| 1000000000 + time_start.tv_nsec - time_init.tv_nsec; |
| time_start.tv_sec -= 1; |
| } else |
| time_start.tv_nsec -= time_init.tv_nsec; |
| time_start.tv_sec -= time_init.tv_sec; |
| |
| time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec; |
| time_init.tv_sec = |
| time_start.tv_sec + time_end.tv_sec + |
| time_init.tv_nsec / 1000000000; |
| time_init.tv_nsec %= 1000000000; |
| |
| printf(PROGNAME ": " |
| "total=%llu.%09llus = " |
| "init=%llu.%09llus + real=%llu.%09llus\n", |
| (unsigned long long) time_init.tv_sec, |
| (unsigned long long) time_init.tv_nsec, |
| (unsigned long long) time_start.tv_sec, |
| (unsigned long long) time_start.tv_nsec, |
| (unsigned long long) time_end.tv_sec, |
| (unsigned long long) time_end.tv_nsec); |
| |
| fflush(stdout); |
| } |
| |
| if (FREE_STATICS) { |
| closedir(fslist_dir); |
| tdestroy(noauto_files, free); |
| tdestroy(known_pools, free); |
| regfree(&uri_regex); |
| } |
| _exit(ret); |
| } |