zfs/etc/systemd/system-generators/zfs-mount-generator.c

1005 lines
27 KiB
C
Raw Normal View History

/*
* 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 */
size_t pool_len = strlen(dataset);
if ((toktmp = strchr(dataset, '/')) != NULL)
pool_len = toktmp - dataset;
const char *pool = *(tofree++) = strndup(dataset, pool_len);
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;
struct dep {
const char *type;
char *list;
} deps[] = {
{"wants", wantedby},
{"requires", requiredby},
{}
};
for (struct dep *dep = deps; dep->type; ++dep) {
if (!dep->list)
continue;
for (char *reqby = strtok_r(dep->list, " ", &toktmp);
reqby;
reqby = strtok_r(NULL, " ", &toktmp)) {
char *depdir;
if (asprintf(
&depdir, "%s.%s", reqby, dep->type) == -1) {
fprintf(stderr, PROGNAME "[%d]: %s: "
"out of memory for dependent dir name "
"\"%s.%s\"!\n",
getpid(), dataset, reqby, dep->type);
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 5 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);
}