Path: blob/main/sys/contrib/openzfs/etc/systemd/system-generators/zfs-mount-generator.c
48521 views
// SPDX-License-Identifier: MIT1/*2* Copyright (c) 2017 Antonio Russo <[email protected]>3* Copyright (c) 2020 InsanePrawn <[email protected]>4*5* Permission is hereby granted, free of charge, to any person obtaining6* a copy of this software and associated documentation files (the7* "Software"), to deal in the Software without restriction, including8* without limitation the rights to use, copy, modify, merge, publish,9* distribute, sublicense, and/or sell copies of the Software, and to10* permit persons to whom the Software is furnished to do so, subject to11* the following conditions:12*13* The above copyright notice and this permission notice shall be14* 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 OF18* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND19* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE20* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION21* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION22* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.23*/242526#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>4344/*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 false5152#define nitems(arr) (sizeof (arr) / sizeof (*arr))53#define STRCMP ((int(*)(const void *, const void *))&strcmp)545556#define PROGNAME "zfs-mount-generator"57#define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"58#define ZFS SBINDIR "/zfs"5960#define OUTPUT_HEADER \61"# Automatically generated by " PROGNAME "\n" \62"\n"6364/*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 extraxion67*/68#define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"69static regex_t uri_regex;7071static const char *destdir = "/tmp";72static int destdir_fd = -1;7374static void *known_pools = NULL; /* tsearch() of C strings */75static void *noauto_files = NULL; /* tsearch() of C strings */767778static char *79systemd_escape(const char *input, const char *prepend, const char *append)80{81size_t len = strlen(input);82size_t applen = strlen(append);83size_t prelen = strlen(prepend);84char *ret = malloc(4 * len + prelen + applen + 1);85if (!ret) {86fprintf(stderr, PROGNAME "[%d]: "87"out of memory to escape \"%s%s%s\"!\n",88getpid(), prepend, input, append);89return (NULL);90}9192memcpy(ret, prepend, prelen);93char *out = ret + prelen;9495const char *cur = input;96if (*cur == '.') {97memcpy(out, "\\x2e", 4);98out += 4;99++cur;100}101for (; *cur; ++cur) {102if (*cur == '/')103*(out++) = '-';104else if (strchr(105"0123456789"106"abcdefghijklmnopqrstuvwxyz"107"ABCDEFGHIJKLMNOPQRSTUVWXYZ"108":_.", *cur))109*(out++) = *cur;110else {111sprintf(out, "\\x%02x", (int)*cur);112out += 4;113}114}115116memcpy(out, append, applen + 1);117return (ret);118}119120static void121simplify_path(char *path)122{123char *out = path;124for (char *cur = path; *cur; ++cur) {125if (*cur == '/') {126while (*(cur + 1) == '/')127++cur;128*(out++) = '/';129} else130*(out++) = *cur;131}132133*(out++) = '\0';134}135136static bool137strendswith(const char *what, const char *suff)138{139size_t what_l = strlen(what);140size_t suff_l = strlen(suff);141142return ((what_l >= suff_l) &&143(strcmp(what + what_l - suff_l, suff) == 0));144}145146/* Assumes already-simplified path, doesn't modify input */147static char *148systemd_escape_path(char *input, const char *prepend, const char *append)149{150if (strcmp(input, "/") == 0) {151char *ret;152if (asprintf(&ret, "%s-%s", prepend, append) == -1) {153fprintf(stderr, PROGNAME "[%d]: "154"out of memory to escape \"%s%s%s\"!\n",155getpid(), prepend, input, append);156ret = NULL;157}158return (ret);159} else {160/*161* path_is_normalized() (flattened for absolute paths here),162* required for proper escaping163*/164if (strstr(input, "/./") || strstr(input, "/../") ||165strendswith(input, "/.") || strendswith(input, "/.."))166return (NULL);167168169if (input[0] == '/')170++input;171172char *back = &input[strlen(input) - 1];173bool deslash = *back == '/';174if (deslash)175*back = '\0';176177char *ret = systemd_escape(input, prepend, append);178179if (deslash)180*back = '/';181return (ret);182}183}184185static FILE *186fopenat(int dirfd, const char *pathname, int flags,187const char *stream_mode, mode_t mode)188{189int fd = openat(dirfd, pathname, flags, mode);190if (fd < 0)191return (NULL);192193return (fdopen(fd, stream_mode));194}195196static int197line_worker(char *line, const char *cachefile)198{199int ret = 0;200void *tofree_all[8];201void **tofree = tofree_all;202203char *toktmp;204/* BEGIN CSTYLED */205const char *dataset = strtok_r(line, "\t", &toktmp);206char *p_mountpoint = strtok_r(NULL, "\t", &toktmp);207const char *p_canmount = strtok_r(NULL, "\t", &toktmp);208const char *p_atime = strtok_r(NULL, "\t", &toktmp);209const char *p_relatime = strtok_r(NULL, "\t", &toktmp);210const char *p_devices = strtok_r(NULL, "\t", &toktmp);211const char *p_exec = strtok_r(NULL, "\t", &toktmp);212const char *p_readonly = strtok_r(NULL, "\t", &toktmp);213const char *p_setuid = strtok_r(NULL, "\t", &toktmp);214const char *p_nbmand = strtok_r(NULL, "\t", &toktmp);215const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-";216char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");217const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-";218const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";219const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-";220const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-";221char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");222char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");223const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-";224const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-";225/* END CSTYLED */226227size_t pool_len = strlen(dataset);228if ((toktmp = strchr(dataset, '/')) != NULL)229pool_len = toktmp - dataset;230const char *pool = *(tofree++) = strndup(dataset, pool_len);231232if (p_nbmand == NULL) {233fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",234getpid(), dataset);235goto err;236}237238/* Minimal pre-requisites to mount a ZFS dataset */239const char *after = "zfs-import.target";240const char *wants = "zfs-import.target";241const char *bindsto = NULL;242char *wantedby = NULL;243char *requiredby = NULL;244bool noauto = false;245bool wantedby_append = true;246247/*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 After252* zfs-import.target After253* zfs-import-{cache,scan}.service After254* cryptsetup.service After255* systemd-random-seed.service256*/257if (tfind(pool, &known_pools, STRCMP)) {258after = "";259wants = "";260}261262if (strcmp(p_systemd_after, "-") == 0)263p_systemd_after = NULL;264if (strcmp(p_systemd_before, "-") == 0)265p_systemd_before = NULL;266if (strcmp(p_systemd_requires, "-") == 0)267p_systemd_requires = NULL;268if (strcmp(p_systemd_requiresmountsfor, "-") == 0)269p_systemd_requiresmountsfor = NULL;270271272if (strcmp(p_encroot, "-") != 0) {273char *keyloadunit = *(tofree++) =274systemd_escape(p_encroot, "zfs-load-key@", ".service");275if (keyloadunit == NULL)276goto err;277278if (strcmp(dataset, p_encroot) == 0) {279const char *keymountdep = NULL;280bool is_prompt = false;281bool need_network = false;282283regmatch_t uri_matches[3];284if (regexec(&uri_regex, p_keyloc,285nitems(uri_matches), uri_matches, 0) == 0) {286p_keyloc[uri_matches[1].rm_eo] = '\0';287p_keyloc[uri_matches[2].rm_eo] = '\0';288const char *scheme =289&p_keyloc[uri_matches[1].rm_so];290const char *path =291&p_keyloc[uri_matches[2].rm_so];292293if (strcmp(scheme, "https") == 0 ||294strcmp(scheme, "http") == 0)295need_network = true;296else297keymountdep = path;298} else {299if (strcmp(p_keyloc, "prompt") != 0)300fprintf(stderr, PROGNAME "[%d]: %s: "301"unknown non-URI keylocation=%s\n",302getpid(), dataset, p_keyloc);303304is_prompt = true;305}306307308/* Generate the key-load .service unit */309FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,310O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",3110644);312if (!keyloadunit_f) {313fprintf(stderr, PROGNAME "[%d]: %s: "314"couldn't open %s under %s: %s\n",315getpid(), dataset, keyloadunit, destdir,316strerror(errno));317goto err;318}319320fprintf(keyloadunit_f,321OUTPUT_HEADER322"[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",329dataset, cachefile, wants, after);330331if (need_network)332fprintf(keyloadunit_f,333"Wants=network-online.target\n"334"After=network-online.target\n");335336if (p_systemd_requires)337fprintf(keyloadunit_f,338"Requires=%s\n", p_systemd_requires);339340if (p_systemd_requiresmountsfor)341fprintf(keyloadunit_f,342"RequiresMountsFor=%s\n",343p_systemd_requiresmountsfor);344if (keymountdep)345fprintf(keyloadunit_f,346"RequiresMountsFor='%s'\n", keymountdep);347348/* BEGIN CSTYLED */349fprintf(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;",360dataset);361if (is_prompt)362fprintf(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",368dataset, dataset, dataset);369else370fprintf(keyloadunit_f,371"exec " ZFS " load-key \"%s\"",372dataset);373374fprintf(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",380dataset, dataset);381/* END CSTYLED */382383(void) fclose(keyloadunit_f);384}385386/* Update dependencies for the mount file to want this */387bindsto = keyloadunit;388if (after[0] == '\0')389after = keyloadunit;390else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)391after = *(tofree++) = toktmp;392else {393fprintf(stderr, PROGNAME "[%d]: %s: "394"out of memory to generate after=\"%s %s\"!\n",395getpid(), dataset, after, keyloadunit);396goto err;397}398}399400401/* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */402if (strcmp(p_systemd_ignore, "-") == 0 ||403strcmp(p_systemd_ignore, "off") == 0) {404/* ok */405} else if (strcmp(p_systemd_ignore, "on") == 0)406goto end;407else {408fprintf(stderr, PROGNAME "[%d]: %s: "409"invalid org.openzfs.systemd:ignore=%s\n",410getpid(), dataset, p_systemd_ignore);411goto err;412}413414/* Check for canmount */415if (strcmp(p_canmount, "on") == 0) {416/* ok */417} else if (strcmp(p_canmount, "noauto") == 0)418noauto = true;419else if (strcmp(p_canmount, "off") == 0)420goto end;421else {422fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",423getpid(), dataset, p_canmount);424goto err;425}426427/* Check for legacy and blank mountpoints */428if (strcmp(p_mountpoint, "legacy") == 0 ||429strcmp(p_mountpoint, "none") == 0)430goto end;431else if (p_mountpoint[0] != '/') {432fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",433getpid(), dataset, p_mountpoint);434goto err;435}436437/* Escape the mountpoint per systemd policy */438simplify_path(p_mountpoint);439const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");440if (mountfile == NULL) {441fprintf(stderr,442PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",443getpid(), dataset, p_mountpoint);444goto err;445}446447448/*449* Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options450*451* The longest string achievable here is452* ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".453*/454char opts[64] = "";455456/* atime */457if (strcmp(p_atime, "on") == 0) {458/* relatime */459if (strcmp(p_relatime, "on") == 0)460strcat(opts, ",atime,relatime");461else if (strcmp(p_relatime, "off") == 0)462strcat(opts, ",atime,strictatime");463else464fprintf(stderr,465PROGNAME "[%d]: %s: invalid relatime=%s\n",466getpid(), dataset, p_relatime);467} else if (strcmp(p_atime, "off") == 0) {468strcat(opts, ",noatime");469} else470fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",471getpid(), dataset, p_atime);472473/* devices */474if (strcmp(p_devices, "on") == 0)475strcat(opts, ",dev");476else if (strcmp(p_devices, "off") == 0)477strcat(opts, ",nodev");478else479fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",480getpid(), dataset, p_devices);481482/* exec */483if (strcmp(p_exec, "on") == 0)484strcat(opts, ",exec");485else if (strcmp(p_exec, "off") == 0)486strcat(opts, ",noexec");487else488fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",489getpid(), dataset, p_exec);490491/* readonly */492if (strcmp(p_readonly, "on") == 0)493strcat(opts, ",ro");494else if (strcmp(p_readonly, "off") == 0)495strcat(opts, ",rw");496else497fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",498getpid(), dataset, p_readonly);499500/* setuid */501if (strcmp(p_setuid, "on") == 0)502strcat(opts, ",suid");503else if (strcmp(p_setuid, "off") == 0)504strcat(opts, ",nosuid");505else506fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",507getpid(), dataset, p_setuid);508509/* nbmand */510if (strcmp(p_nbmand, "on") == 0)511strcat(opts, ",mand");512else if (strcmp(p_nbmand, "off") == 0)513strcat(opts, ",nomand");514else515fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",516getpid(), dataset, p_setuid);517518if (strcmp(p_systemd_wantedby, "-") != 0) {519noauto = true;520521if (strcmp(p_systemd_wantedby, "none") != 0)522wantedby = p_systemd_wantedby;523}524525if (strcmp(p_systemd_requiredby, "-") != 0) {526noauto = true;527528if (strcmp(p_systemd_requiredby, "none") != 0)529requiredby = p_systemd_requiredby;530}531532/*533* For datasets with canmount=on, a dependency is created for534* local-fs.target by default. To avoid regressions, this dependency535* is reduced to "wants" rather than "requires" when nofail!=off.536* **THIS MAY CHANGE**537* noauto=on disables this behavior completely.538*/539if (!noauto) {540if (strcmp(p_systemd_nofail, "off") == 0)541requiredby = strdupa("local-fs.target");542else {543wantedby = strdupa("local-fs.target");544wantedby_append = strcmp(p_systemd_nofail, "on") != 0;545}546}547548/*549* Handle existing files:550* 1. We never overwrite existing files, although we may delete551* 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 names560* (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 same567* mountpoint. In such cases, we remove the file for safety.568* We leave the file name in the tracking array to avoid569* further noauto datasets creating a file for this path again.570*/571572struct stat stbuf;573bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;574bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;575576*(tofree++) = (void *)mountfile;577if (already_exists) {578if (is_known) {579/* If it's in noauto_files, we must be noauto too */580581/* See 5 */582errno = 0;583(void) unlinkat(destdir_fd, mountfile, 0);584585/* See 2 */586fprintf(stderr, PROGNAME "[%d]: %s: "587"removing duplicate noauto unit %s%s%s\n",588getpid(), dataset, mountfile,589errno ? "" : " failed: ",590errno ? "" : strerror(errno));591} else {592/* Don't log for canmount=noauto */593if (strcmp(p_canmount, "on") == 0)594fprintf(stderr, PROGNAME "[%d]: %s: "595"%s already exists. Skipping.\n",596getpid(), dataset, mountfile);597}598599/* File exists: skip current dataset */600goto end;601} else {602if (is_known) {603/* See 4 */604goto end;605} else if (strcmp(p_canmount, "noauto") == 0) {606if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)607fprintf(stderr, PROGNAME "[%d]: %s: "608"out of memory for noauto datasets! "609"Not tracking %s.\n",610getpid(), dataset, mountfile);611else612/* mountfile escaped to noauto_files */613*(--tofree) = NULL;614}615}616617618FILE *mountfile_f = fopenat(destdir_fd, mountfile,619O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);620if (!mountfile_f) {621fprintf(stderr,622PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",623getpid(), dataset, mountfile, destdir, strerror(errno));624goto err;625}626627fprintf(mountfile_f,628OUTPUT_HEADER629"[Unit]\n"630"SourcePath=" FSLIST "/%s\n"631"Documentation=man:zfs-mount-generator(8)\n"632"\n"633"Before=",634cachefile);635636if (p_systemd_before)637fprintf(mountfile_f, "%s ", p_systemd_before);638fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */639if (requiredby)640fprintf(mountfile_f, " %s", requiredby);641if (wantedby && wantedby_append)642fprintf(mountfile_f, " %s", wantedby);643644fprintf(mountfile_f,645"\n"646"After=");647if (p_systemd_after)648fprintf(mountfile_f, "%s ", p_systemd_after);649fprintf(mountfile_f, "%s\n", after);650651fprintf(mountfile_f, "Wants=%s\n", wants);652653if (bindsto)654fprintf(mountfile_f, "BindsTo=%s\n", bindsto);655if (p_systemd_requires)656fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);657if (p_systemd_requiresmountsfor)658fprintf(mountfile_f,659"RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);660661fprintf(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",668p_mountpoint, dataset, opts);669670(void) fclose(mountfile_f);671672if (!requiredby && !wantedby)673goto end;674675/* Finally, create the appropriate dependencies */676char *linktgt;677if (asprintf(&linktgt, "../%s", mountfile) == -1) {678fprintf(stderr, PROGNAME "[%d]: %s: "679"out of memory for dependents of %s!\n",680getpid(), dataset, mountfile);681goto err;682}683*(tofree++) = linktgt;684685struct dep {686const char *type;687char *list;688} deps[] = {689{"wants", wantedby},690{"requires", requiredby},691{}692};693for (struct dep *dep = deps; dep->type; ++dep) {694if (!dep->list)695continue;696697for (char *reqby = strtok_r(dep->list, " ", &toktmp);698reqby;699reqby = strtok_r(NULL, " ", &toktmp)) {700char *depdir;701if (asprintf(702&depdir, "%s.%s", reqby, dep->type) == -1) {703fprintf(stderr, PROGNAME "[%d]: %s: "704"out of memory for dependent dir name "705"\"%s.%s\"!\n",706getpid(), dataset, reqby, dep->type);707continue;708}709710(void) mkdirat(destdir_fd, depdir, 0755);711int depdir_fd = openat(destdir_fd, depdir,712O_PATH | O_DIRECTORY | O_CLOEXEC);713if (depdir_fd < 0) {714fprintf(stderr, PROGNAME "[%d]: %s: "715"couldn't open %s under %s: %s\n",716getpid(), dataset, depdir, destdir,717strerror(errno));718free(depdir);719continue;720}721722if (symlinkat(linktgt, depdir_fd, mountfile) == -1)723fprintf(stderr, PROGNAME "[%d]: %s: "724"couldn't symlink at "725"%s under %s under %s: %s\n",726getpid(), dataset, mountfile,727depdir, destdir, strerror(errno));728729(void) close(depdir_fd);730free(depdir);731}732}733734end:735if (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*/740fprintf(stderr,741PROGNAME "[%d]: %s: need to free %zu > %zu!\n",742getpid(), dataset, tofree - tofree_all, nitems(tofree_all));743ret = tofree - tofree_all;744}745746while (tofree-- != tofree_all)747free(*tofree);748return (ret);749err:750ret = 1;751goto end;752}753754755static int756pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))757{758int ret = 0;759760/*761* Pools are guaranteed-unique by the kernel,762* no risk of leaking dupes here763*/764char *name = strdup(zpool_get_name(pool));765if (!name || !tsearch(name, &known_pools, STRCMP)) {766free(name);767ret = ENOMEM;768}769770zpool_close(pool);771return (ret);772}773774int775main(int argc, char **argv)776{777struct timespec time_init = {};778clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);779780{781int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);782if (kmfd >= 0) {783(void) dup2(kmfd, STDERR_FILENO);784(void) close(kmfd);785786setlinebuf(stderr);787}788}789790switch (argc) {791case 1:792/* Use default */793break;794case 2:795case 4:796destdir = argv[1];797break;798default:799fprintf(stderr,800PROGNAME "[%d]: wrong argument count: %d\n",801getpid(), argc - 1);802_exit(1);803}804805{806destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);807if (destdir_fd < 0) {808fprintf(stderr, PROGNAME "[%d]: "809"can't open destination directory %s: %s\n",810getpid(), destdir, strerror(errno));811_exit(1);812}813}814815DIR *fslist_dir = opendir(FSLIST);816if (!fslist_dir) {817if (errno != ENOENT)818fprintf(stderr,819PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",820getpid(), strerror(errno));821_exit(0);822}823824{825libzfs_handle_t *libzfs = libzfs_init();826if (libzfs) {827if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)828fprintf(stderr, PROGNAME "[%d]: "829"error listing pools, ignoring\n",830getpid());831libzfs_fini(libzfs);832} else833fprintf(stderr, PROGNAME "[%d]: "834"couldn't start libzfs, ignoring\n",835getpid());836}837838{839int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);840if (regerr != 0) {841fprintf(stderr,842PROGNAME "[%d]: invalid regex: %d\n",843getpid(), regerr);844_exit(1);845}846}847848bool debug = false;849char *line = NULL;850size_t linelen = 0;851{852const char *dbgenv = getenv("ZFS_DEBUG");853if (dbgenv)854debug = atoi(dbgenv);855else {856FILE *cmdline = fopen("/proc/cmdline", "re");857if (cmdline != NULL) {858if (getline(&line, &linelen, cmdline) >= 0)859debug = strstr(line, "debug");860(void) fclose(cmdline);861}862}863864if (debug && !isatty(STDOUT_FILENO))865dup2(STDERR_FILENO, STDOUT_FILENO);866}867868struct timespec time_start = {};869if (debug)870clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);871872struct line {873char *line;874const char *fname;875struct line *next;876} *lines_canmount_not_on = NULL;877878int ret = 0;879struct dirent *cachent;880while ((cachent = readdir(fslist_dir)) != NULL) {881if (strcmp(cachent->d_name, ".") == 0 ||882strcmp(cachent->d_name, "..") == 0)883continue;884885FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,886O_RDONLY | O_CLOEXEC, "r", 0);887if (!cachefile) {888fprintf(stderr, PROGNAME "[%d]: "889"couldn't open %s under " FSLIST ": %s\n",890getpid(), cachent->d_name, strerror(errno));891continue;892}893894const char *filename = FREE_STATICS ? "(elided)" : NULL;895896ssize_t read;897while ((read = getline(&line, &linelen, cachefile)) >= 0) {898line[read - 1] = '\0'; /* newline */899900char *canmount = line;901canmount += strcspn(canmount, "\t");902canmount += strspn(canmount, "\t");903canmount += strcspn(canmount, "\t");904canmount += strspn(canmount, "\t");905bool canmount_on = strncmp(canmount, "on", 2) == 0;906907if (canmount_on)908ret |= line_worker(line, cachent->d_name);909else {910if (filename == NULL)911filename =912strdup(cachent->d_name) ?: "(?)";913914struct line *l = calloc(1, sizeof (*l));915char *nl = strdup(line);916if (l == NULL || nl == NULL) {917fprintf(stderr, PROGNAME "[%d]: "918"out of memory for \"%s\" in %s\n",919getpid(), line, cachent->d_name);920free(l);921free(nl);922continue;923}924l->line = nl;925l->fname = filename;926l->next = lines_canmount_not_on;927lines_canmount_not_on = l;928}929}930931fclose(cachefile);932}933free(line);934935while (lines_canmount_not_on) {936struct line *l = lines_canmount_not_on;937lines_canmount_not_on = l->next;938939ret |= line_worker(l->line, l->fname);940if (FREE_STATICS) {941free(l->line);942free(l);943}944}945946if (debug) {947struct timespec time_end = {};948clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);949950struct rusage usage;951getrusage(RUSAGE_SELF, &usage);952printf(953"\n"954PROGNAME ": "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,960usage.ru_maxrss * 1024);961962if (time_start.tv_nsec > time_end.tv_nsec) {963time_end.tv_nsec =9641000000000 + time_end.tv_nsec - time_start.tv_nsec;965time_end.tv_sec -= 1;966} else967time_end.tv_nsec -= time_start.tv_nsec;968time_end.tv_sec -= time_start.tv_sec;969970if (time_init.tv_nsec > time_start.tv_nsec) {971time_start.tv_nsec =9721000000000 + time_start.tv_nsec - time_init.tv_nsec;973time_start.tv_sec -= 1;974} else975time_start.tv_nsec -= time_init.tv_nsec;976time_start.tv_sec -= time_init.tv_sec;977978time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;979time_init.tv_sec =980time_start.tv_sec + time_end.tv_sec +981time_init.tv_nsec / 1000000000;982time_init.tv_nsec %= 1000000000;983984printf(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);993994fflush(stdout);995}996997if (FREE_STATICS) {998closedir(fslist_dir);999tdestroy(noauto_files, free);1000tdestroy(known_pools, free);1001regfree(&uri_regex);1002}1003_exit(ret);1004}100510061007