etc/systemd/zfs-mount-generator: serialise, handle keylocation=http[s]://

* etc/systemd/zfs-mount-generator: serialise

The wins for a relatively normal workload are rather slim:
	real	0.02119s/0.00985s=2.15029x
	user	0.02130s/0.00346s=6.15560x
	sys	0.03858s/0.00643s=6.00062x

	wall-total	0.014518s/0.005925s=2.45009x
	wall-init	0.014518s/0.002457s=5.90684x
	wall-real	0.014518s/0.003467s=4.18668x

But this is a big win on machines with a lot of datasets and expensive
forks.

For example, the gain on a VM on my work laptop with 900+ legacy-mount
Docker datasets, the original gains from the C rewrite were
only five-fold:
	real    0.516s/0.102s=5.05882x
	user    0.237s/0.143s=1.65734x
	sys     0.287s/0.100s=2.87x

And this serial variant gains this back there as well:
	real    0.102s/0.008s=12.75x
	user    0.143s/0.007s=20.42857
	sys     0.100s/0.001s=100x

	wall-total	0.09717s/0.00319s=30.40255x
	wall-init	0.00203s/0.00200s=1.015941x
	wall-real	0.09513s/0.00118s=80.02043x

For a total of
	real    0.516s/0.008s=64.5x
	user    0.237s/0.007s=33.85714x
	sys     0.287s/0.001s=287x

Suggested-by: Richard Laager <rlaager@wiktel.com>

* etc/systemd/zfs-mount-generator: pull in network for keylocation=https

Also simplify RequiresMountsFor= handling
Ref: #11956

Reviewed-by: Richard Laager <rlaager@wiktel.com>
Reviewed-by: Tony Nguyen <tony.nguyen@delphix.com>
Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Upstream-commit: 4325de09cd
Closes #12138
This commit is contained in:
наб 2021-11-30 17:29:50 +01:00 committed by Brian Behlendorf
parent 7fbb90feea
commit fe6f2651f5
2 changed files with 179 additions and 273 deletions

View File

@ -27,9 +27,6 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -44,25 +41,16 @@
#include <errno.h> #include <errno.h>
#include <libzfs.h> #include <libzfs.h>
#define STRCMP ((int(*)(const void *, const void *))&strcmp) /*
#define PID_T_CMP ((int(*)(const void *, const void *))&pid_t_cmp) * For debugging only.
*
static int * Free statics with trivial life-times,
pid_t_cmp(const pid_t *lhs, const pid_t *rhs) * but saved line filenames are replaced with a static string.
{
/*
* This is always valid, quoth sys_types.h(7posix):
* > blksize_t, pid_t, and ssize_t shall be signed integer types.
*/ */
return (*lhs - *rhs); #define FREE_STATICS false
}
#define EXIT_ENOMEM() \ #define nitems(arr) (sizeof (arr) / sizeof (*arr))
do { \ #define STRCMP ((int(*)(const void *, const void *))&strcmp)
fprintf(stderr, PROGNAME "[%d]: " \
"not enough memory (L%d)!\n", getpid(), __LINE__); \
_exit(1); \
} while (0)
#define PROGNAME "zfs-mount-generator" #define PROGNAME "zfs-mount-generator"
@ -80,20 +68,11 @@ pid_t_cmp(const pid_t *lhs, const pid_t *rhs)
#define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$" #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
static regex_t uri_regex; static regex_t uri_regex;
static char *argv0;
static const char *destdir = "/tmp"; static const char *destdir = "/tmp";
static int destdir_fd = -1; static int destdir_fd = -1;
static void *known_pools = NULL; /* tsearch() of C strings */ static void *known_pools = NULL; /* tsearch() of C strings */
static struct { static void *noauto_files = NULL; /* tsearch() of C strings */
sem_t noauto_not_on_sem;
sem_t noauto_names_sem;
size_t noauto_names_len;
size_t noauto_names_max;
char noauto_names[][NAME_MAX];
} *noauto_files;
static char * static char *
@ -103,8 +82,12 @@ systemd_escape(const char *input, const char *prepend, const char *append)
size_t applen = strlen(append); size_t applen = strlen(append);
size_t prelen = strlen(prepend); size_t prelen = strlen(prepend);
char *ret = malloc(4 * len + prelen + applen + 1); char *ret = malloc(4 * len + prelen + applen + 1);
if (!ret) if (!ret) {
EXIT_ENOMEM(); fprintf(stderr, PROGNAME "[%d]: "
"out of memory to escape \"%s%s%s\"!\n",
getpid(), prepend, input, append);
return (NULL);
}
memcpy(ret, prepend, prelen); memcpy(ret, prepend, prelen);
char *out = ret + prelen; char *out = ret + prelen;
@ -166,8 +149,12 @@ systemd_escape_path(char *input, const char *prepend, const char *append)
{ {
if (strcmp(input, "/") == 0) { if (strcmp(input, "/") == 0) {
char *ret; char *ret;
if (asprintf(&ret, "%s-%s", prepend, append) == -1) if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
EXIT_ENOMEM(); fprintf(stderr, PROGNAME "[%d]: "
"out of memory to escape \"%s%s%s\"!\n",
getpid(), prepend, input, append);
ret = NULL;
}
return (ret); return (ret);
} else { } else {
/* /*
@ -209,6 +196,10 @@ fopenat(int dirfd, const char *pathname, int flags,
static int static int
line_worker(char *line, const char *cachefile) line_worker(char *line, const char *cachefile)
{ {
int ret = 0;
void *tofree_all[8];
void **tofree = tofree_all;
char *toktmp; char *toktmp;
/* BEGIN CSTYLED */ /* BEGIN CSTYLED */
const char *dataset = strtok_r(line, "\t", &toktmp); const char *dataset = strtok_r(line, "\t", &toktmp);
@ -240,11 +231,9 @@ line_worker(char *line, const char *cachefile)
if (p_nbmand == NULL) { if (p_nbmand == NULL) {
fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n", fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
getpid(), dataset); getpid(), dataset);
return (1); goto err;
} }
strncpy(argv0, dataset, strlen(argv0));
/* Minimal pre-requisites to mount a ZFS dataset */ /* Minimal pre-requisites to mount a ZFS dataset */
const char *after = "zfs-import.target"; const char *after = "zfs-import.target";
const char *wants = "zfs-import.target"; const char *wants = "zfs-import.target";
@ -280,27 +269,30 @@ line_worker(char *line, const char *cachefile)
if (strcmp(p_encroot, "-") != 0) { if (strcmp(p_encroot, "-") != 0) {
char *keyloadunit = char *keyloadunit = *(tofree++) =
systemd_escape(p_encroot, "zfs-load-key@", ".service"); systemd_escape(p_encroot, "zfs-load-key@", ".service");
if (keyloadunit == NULL)
goto err;
if (strcmp(dataset, p_encroot) == 0) { if (strcmp(dataset, p_encroot) == 0) {
const char *keymountdep = NULL; const char *keymountdep = NULL;
bool is_prompt = false; bool is_prompt = false;
bool need_network = false;
regmatch_t uri_matches[3]; regmatch_t uri_matches[3];
if (regexec(&uri_regex, p_keyloc, if (regexec(&uri_regex, p_keyloc,
sizeof (uri_matches) / sizeof (*uri_matches), nitems(uri_matches), uri_matches, 0) == 0) {
uri_matches, 0) == 0) { p_keyloc[uri_matches[1].rm_eo] = '\0';
p_keyloc[uri_matches[2].rm_eo] = '\0'; p_keyloc[uri_matches[2].rm_eo] = '\0';
const char *scheme =
&p_keyloc[uri_matches[1].rm_so];
const char *path = const char *path =
&p_keyloc[uri_matches[2].rm_so]; &p_keyloc[uri_matches[2].rm_so];
/* if (strcmp(scheme, "https") == 0 ||
* Assumes all URI keylocations need strcmp(scheme, "http") == 0)
* the mount for their path; need_network = true;
* http://, for example, wouldn't else
* (but it'd need network-online.target et al.)
*/
keymountdep = path; keymountdep = path;
} else { } else {
if (strcmp(p_keyloc, "prompt") != 0) if (strcmp(p_keyloc, "prompt") != 0)
@ -321,7 +313,7 @@ line_worker(char *line, const char *cachefile)
"couldn't open %s under %s: %s\n", "couldn't open %s under %s: %s\n",
getpid(), dataset, keyloadunit, destdir, getpid(), dataset, keyloadunit, destdir,
strerror(errno)); strerror(errno));
return (1); goto err;
} }
fprintf(keyloadunit_f, fprintf(keyloadunit_f,
@ -335,20 +327,22 @@ line_worker(char *line, const char *cachefile)
"After=%s\n", "After=%s\n",
dataset, cachefile, wants, after); dataset, cachefile, wants, after);
if (need_network)
fprintf(keyloadunit_f,
"Wants=network-online.target\n"
"After=network-online.target\n");
if (p_systemd_requires) if (p_systemd_requires)
fprintf(keyloadunit_f, fprintf(keyloadunit_f,
"Requires=%s\n", p_systemd_requires); "Requires=%s\n", p_systemd_requires);
if (p_systemd_requiresmountsfor || keymountdep) {
fprintf(keyloadunit_f, "RequiresMountsFor=");
if (p_systemd_requiresmountsfor) if (p_systemd_requiresmountsfor)
fprintf(keyloadunit_f, fprintf(keyloadunit_f,
"%s ", p_systemd_requiresmountsfor); "RequiresMountsFor=%s\n",
p_systemd_requiresmountsfor);
if (keymountdep) if (keymountdep)
fprintf(keyloadunit_f, fprintf(keyloadunit_f,
"'%s'", keymountdep); "RequiresMountsFor='%s'\n", keymountdep);
fprintf(keyloadunit_f, "\n");
}
/* BEGIN CSTYLED */ /* BEGIN CSTYLED */
fprintf(keyloadunit_f, fprintf(keyloadunit_f,
@ -393,9 +387,13 @@ line_worker(char *line, const char *cachefile)
if (after[0] == '\0') if (after[0] == '\0')
after = keyloadunit; after = keyloadunit;
else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1) else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
after = toktmp; after = *(tofree++) = toktmp;
else else {
EXIT_ENOMEM(); fprintf(stderr, PROGNAME "[%d]: %s: "
"out of memory to generate after=\"%s %s\"!\n",
getpid(), dataset, after, keyloadunit);
goto err;
}
} }
@ -404,12 +402,12 @@ line_worker(char *line, const char *cachefile)
strcmp(p_systemd_ignore, "off") == 0) { strcmp(p_systemd_ignore, "off") == 0) {
/* ok */ /* ok */
} else if (strcmp(p_systemd_ignore, "on") == 0) } else if (strcmp(p_systemd_ignore, "on") == 0)
return (0); goto end;
else { else {
fprintf(stderr, PROGNAME "[%d]: %s: " fprintf(stderr, PROGNAME "[%d]: %s: "
"invalid org.openzfs.systemd:ignore=%s\n", "invalid org.openzfs.systemd:ignore=%s\n",
getpid(), dataset, p_systemd_ignore); getpid(), dataset, p_systemd_ignore);
return (1); goto err;
} }
/* Check for canmount */ /* Check for canmount */
@ -418,21 +416,21 @@ line_worker(char *line, const char *cachefile)
} else if (strcmp(p_canmount, "noauto") == 0) } else if (strcmp(p_canmount, "noauto") == 0)
noauto = true; noauto = true;
else if (strcmp(p_canmount, "off") == 0) else if (strcmp(p_canmount, "off") == 0)
return (0); goto end;
else { else {
fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n", fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
getpid(), dataset, p_canmount); getpid(), dataset, p_canmount);
return (1); goto err;
} }
/* Check for legacy and blank mountpoints */ /* Check for legacy and blank mountpoints */
if (strcmp(p_mountpoint, "legacy") == 0 || if (strcmp(p_mountpoint, "legacy") == 0 ||
strcmp(p_mountpoint, "none") == 0) strcmp(p_mountpoint, "none") == 0)
return (0); goto end;
else if (p_mountpoint[0] != '/') { else if (p_mountpoint[0] != '/') {
fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n", fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
getpid(), dataset, p_mountpoint); getpid(), dataset, p_mountpoint);
return (1); goto err;
} }
/* Escape the mountpoint per systemd policy */ /* Escape the mountpoint per systemd policy */
@ -442,7 +440,7 @@ line_worker(char *line, const char *cachefile)
fprintf(stderr, fprintf(stderr,
PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n", PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
getpid(), dataset, p_mountpoint); getpid(), dataset, p_mountpoint);
return (1); goto err;
} }
@ -552,8 +550,7 @@ line_worker(char *line, const char *cachefile)
* files if we're sure they were created by us. (see 5.) * files if we're sure they were created by us. (see 5.)
* 2. We handle files differently based on canmount. * 2. We handle files differently based on canmount.
* Units with canmount=on always have precedence over noauto. * Units with canmount=on always have precedence over noauto.
* This is enforced by the noauto_not_on_sem semaphore, * This is enforced by processing these units before all others.
* which is only unlocked when the last canmount=on process exits.
* It is important to use p_canmount and not noauto here, * It is important to use p_canmount and not noauto here,
* since we categorise by canmount while other properties, * since we categorise by canmount while other properties,
* e.g. org.openzfs.systemd:wanted-by, also modify noauto. * e.g. org.openzfs.systemd:wanted-by, also modify noauto.
@ -561,7 +558,7 @@ line_worker(char *line, const char *cachefile)
* Additionally, we use noauto_files to track the unit file names * Additionally, we use noauto_files to track the unit file names
* (which are the systemd-escaped mountpoints) of all (exclusively) * (which are the systemd-escaped mountpoints) of all (exclusively)
* noauto datasets that had a file created. * noauto datasets that had a file created.
* 4. If the file to be created is found in the tracking array, * 4. If the file to be created is found in the tracking tree,
* we do NOT create it. * we do NOT create it.
* 5. If a file exists for a noauto dataset, * 5. If a file exists for a noauto dataset,
* we check whether the file name is in the array. * we check whether the file name is in the array.
@ -571,29 +568,14 @@ line_worker(char *line, const char *cachefile)
* further noauto datasets creating a file for this path again. * further noauto datasets creating a file for this path again.
*/ */
{
sem_t *our_sem = (strcmp(p_canmount, "on") == 0) ?
&noauto_files->noauto_names_sem :
&noauto_files->noauto_not_on_sem;
while (sem_wait(our_sem) == -1 && errno == EINTR)
;
}
struct stat stbuf; struct stat stbuf;
bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0; bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
bool is_known = false; *(tofree++) = (void *)mountfile;
for (size_t i = 0; i < noauto_files->noauto_names_len; ++i) {
if (strncmp(
noauto_files->noauto_names[i], mountfile, NAME_MAX) == 0) {
is_known = true;
break;
}
}
if (already_exists) { if (already_exists) {
if (is_known) { if (is_known) {
/* If it's in $noauto_files, we must be noauto too */ /* If it's in noauto_files, we must be noauto too */
/* See 5 */ /* See 5 */
errno = 0; errno = 0;
@ -614,43 +596,31 @@ line_worker(char *line, const char *cachefile)
} }
/* File exists: skip current dataset */ /* File exists: skip current dataset */
if (strcmp(p_canmount, "on") == 0) goto end;
sem_post(&noauto_files->noauto_names_sem);
return (0);
} else { } else {
if (is_known) { if (is_known) {
/* See 4 */ /* See 4 */
if (strcmp(p_canmount, "on") == 0) goto end;
sem_post(&noauto_files->noauto_names_sem);
return (0);
} else if (strcmp(p_canmount, "noauto") == 0) { } else if (strcmp(p_canmount, "noauto") == 0) {
if (noauto_files->noauto_names_len == if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
noauto_files->noauto_names_max)
fprintf(stderr, PROGNAME "[%d]: %s: " fprintf(stderr, PROGNAME "[%d]: %s: "
"noauto dataset limit (%zu) reached! " "out of memory for noauto datasets! "
"Not tracking %s. Please report this to " "Not tracking %s.\n",
"https://github.com/openzfs/zfs\n", getpid(), dataset, mountfile);
getpid(), dataset, else
noauto_files->noauto_names_max, mountfile); /* mountfile escaped to noauto_files */
else { *(--tofree) = NULL;
strncpy(noauto_files->noauto_names[
noauto_files->noauto_names_len],
mountfile, NAME_MAX);
++noauto_files->noauto_names_len;
}
} }
} }
FILE *mountfile_f = fopenat(destdir_fd, mountfile, FILE *mountfile_f = fopenat(destdir_fd, mountfile,
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644); O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
if (strcmp(p_canmount, "on") == 0)
sem_post(&noauto_files->noauto_names_sem);
if (!mountfile_f) { if (!mountfile_f) {
fprintf(stderr, fprintf(stderr,
PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n", PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
getpid(), dataset, mountfile, destdir, strerror(errno)); getpid(), dataset, mountfile, destdir, strerror(errno));
return (1); goto err;
} }
fprintf(mountfile_f, fprintf(mountfile_f,
@ -699,12 +669,17 @@ line_worker(char *line, const char *cachefile)
(void) fclose(mountfile_f); (void) fclose(mountfile_f);
if (!requiredby && !wantedby) if (!requiredby && !wantedby)
return (0); goto end;
/* Finally, create the appropriate dependencies */ /* Finally, create the appropriate dependencies */
char *linktgt; char *linktgt;
if (asprintf(&linktgt, "../%s", mountfile) == -1) if (asprintf(&linktgt, "../%s", mountfile) == -1) {
EXIT_ENOMEM(); fprintf(stderr, PROGNAME "[%d]: %s: "
"out of memory for dependents of %s!\n",
getpid(), dataset, mountfile);
goto err;
}
*(tofree++) = linktgt;
char *dependencies[][2] = { char *dependencies[][2] = {
{"wants", wantedby}, {"wants", wantedby},
@ -719,8 +694,14 @@ line_worker(char *line, const char *cachefile)
reqby; reqby;
reqby = strtok_r(NULL, " ", &toktmp)) { reqby = strtok_r(NULL, " ", &toktmp)) {
char *depdir; char *depdir;
if (asprintf(&depdir, "%s.%s", reqby, (*dep)[0]) == -1) if (asprintf(
EXIT_ENOMEM(); &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); (void) mkdirat(destdir_fd, depdir, 0755);
int depdir_fd = openat(destdir_fd, depdir, int depdir_fd = openat(destdir_fd, depdir,
@ -746,7 +727,24 @@ line_worker(char *line, const char *cachefile)
} }
} }
return (0); 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;
} }
@ -780,12 +778,11 @@ main(int argc, char **argv)
if (kmfd >= 0) { if (kmfd >= 0) {
(void) dup2(kmfd, STDERR_FILENO); (void) dup2(kmfd, STDERR_FILENO);
(void) close(kmfd); (void) close(kmfd);
setlinebuf(stderr);
} }
} }
uint8_t debug = 0;
argv0 = argv[0];
switch (argc) { switch (argc) {
case 1: case 1:
/* Use default */ /* Use default */
@ -844,33 +841,9 @@ main(int argc, char **argv)
} }
} }
{ bool debug = false;
/*
* We could just get a gigabyte here and Not Care,
* but if vm.overcommit_memory=2, then MAP_NORESERVE is ignored
* and we'd try (and likely fail) to rip it out of swap
*/
noauto_files = mmap(NULL, 4 * 1024 * 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
if (noauto_files == MAP_FAILED) {
fprintf(stderr,
PROGNAME "[%d]: couldn't allocate IPC region: %s\n",
getpid(), strerror(errno));
_exit(1);
}
sem_init(&noauto_files->noauto_not_on_sem, true, 0);
sem_init(&noauto_files->noauto_names_sem, true, 1);
noauto_files->noauto_names_len = 0;
/* Works out to 16447ish, *well* enough */
noauto_files->noauto_names_max =
(4 * 1024 * 1024 - sizeof (*noauto_files)) / NAME_MAX;
}
char *line = NULL; char *line = NULL;
size_t linelen = 0; size_t linelen = 0;
struct timespec time_start = {};
{ {
const char *dbgenv = getenv("ZFS_DEBUG"); const char *dbgenv = getenv("ZFS_DEBUG");
if (dbgenv) if (dbgenv)
@ -879,7 +852,7 @@ main(int argc, char **argv)
FILE *cmdline = fopen("/proc/cmdline", "re"); FILE *cmdline = fopen("/proc/cmdline", "re");
if (cmdline != NULL) { if (cmdline != NULL) {
if (getline(&line, &linelen, cmdline) >= 0) if (getline(&line, &linelen, cmdline) >= 0)
debug = strstr(line, "debug") ? 2 : 0; debug = strstr(line, "debug");
(void) fclose(cmdline); (void) fclose(cmdline);
} }
} }
@ -888,19 +861,17 @@ main(int argc, char **argv)
dup2(STDERR_FILENO, STDOUT_FILENO); dup2(STDERR_FILENO, STDOUT_FILENO);
} }
size_t forked_canmount_on = 0; struct timespec time_start = {};
size_t forked_canmount_not_on = 0;
size_t canmount_on_pids_len = 128;
pid_t *canmount_on_pids =
malloc(canmount_on_pids_len * sizeof (*canmount_on_pids));
if (canmount_on_pids == NULL)
canmount_on_pids_len = 0;
if (debug) if (debug)
clock_gettime(CLOCK_MONOTONIC_RAW, &time_start); clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
ssize_t read; struct line {
pid_t pid; char *line;
const char *fname;
struct line *next;
} *lines_canmount_not_on = NULL;
int ret = 0;
struct dirent *cachent; struct dirent *cachent;
while ((cachent = readdir(fslist_dir)) != NULL) { while ((cachent = readdir(fslist_dir)) != NULL) {
if (strcmp(cachent->d_name, ".") == 0 || if (strcmp(cachent->d_name, ".") == 0 ||
@ -916,129 +887,67 @@ main(int argc, char **argv)
continue; continue;
} }
const char *filename = FREE_STATICS ? "(elided)" : NULL;
ssize_t read;
while ((read = getline(&line, &linelen, cachefile)) >= 0) { while ((read = getline(&line, &linelen, cachefile)) >= 0) {
line[read - 1] = '\0'; /* newline */ line[read - 1] = '\0'; /* newline */
switch (pid = fork()) { char *canmount = line;
case -1: canmount += strcspn(canmount, "\t");
fprintf(stderr, canmount += strspn(canmount, "\t");
PROGNAME "[%d]: couldn't fork for %s: %s\n", canmount += strcspn(canmount, "\t");
getpid(), line, strerror(errno)); canmount += strspn(canmount, "\t");
break; bool canmount_on = strncmp(canmount, "on", 2) == 0;
case 0: /* child */
_exit(line_worker(line, cachent->d_name));
default: { /* parent */
char *tmp;
char *dset = strtok_r(line, "\t", &tmp);
strtok_r(NULL, "\t", &tmp);
char *canmount = strtok_r(NULL, "\t", &tmp);
bool canmount_on =
canmount && strncmp(canmount, "on", 2) == 0;
if (debug >= 2) if (canmount_on)
printf(PROGNAME ": forked %d, " ret |= line_worker(line, cachent->d_name);
"canmount_on=%d, dataset=%s\n", else {
(int)pid, canmount_on, dset); if (filename == NULL)
filename =
strdup(cachent->d_name) ?: "(?)";
if (canmount_on && struct line *l = calloc(1, sizeof (*l));
forked_canmount_on == char *nl = strdup(line);
canmount_on_pids_len) { if (l == NULL || nl == NULL) {
size_t new_len = fprintf(stderr, PROGNAME "[%d]: "
(canmount_on_pids_len ?: 16) * 2; "out of memory for \"%s\" in %s\n",
void *new_pidlist = getpid(), line, cachent->d_name);
realloc(canmount_on_pids, free(l);
new_len * free(nl);
sizeof (*canmount_on_pids));
if (!new_pidlist) {
fprintf(stderr,
PROGNAME "[%d]: "
"out of memory! "
"Mount ordering may be "
"affected.\n", getpid());
continue; continue;
} }
l->line = nl;
canmount_on_pids = new_pidlist; l->fname = filename;
canmount_on_pids_len = new_len; l->next = lines_canmount_not_on;
} lines_canmount_not_on = l;
if (canmount_on) {
canmount_on_pids[forked_canmount_on] =
pid;
++forked_canmount_on;
} else
++forked_canmount_not_on;
break;
}
} }
} }
(void) fclose(cachefile); fclose(cachefile);
} }
free(line); free(line);
if (forked_canmount_on == 0) { while (lines_canmount_not_on) {
/* No canmount=on processes to finish, so don't deadlock here */ struct line *l = lines_canmount_not_on;
for (size_t i = 0; i < forked_canmount_not_on; ++i) lines_canmount_not_on = l->next;
sem_post(&noauto_files->noauto_not_on_sem);
} else { ret |= line_worker(l->line, l->fname);
/* Likely a no-op, since we got these from a narrow fork loop */ if (FREE_STATICS) {
qsort(canmount_on_pids, forked_canmount_on, free(l->line);
sizeof (*canmount_on_pids), PID_T_CMP); free(l);
} }
int status, ret = 0;
struct rusage usage;
size_t forked_canmount_on_max = forked_canmount_on;
while ((pid = wait4(-1, &status, 0, &usage)) != -1) {
ret |= WEXITSTATUS(status) | WTERMSIG(status);
if (forked_canmount_on != 0) {
if (bsearch(&pid, canmount_on_pids,
forked_canmount_on_max, sizeof (*canmount_on_pids),
PID_T_CMP))
--forked_canmount_on;
if (forked_canmount_on == 0) {
/*
* All canmount=on processes have finished,
* let all the lower-priority ones finish now
*/
for (size_t i = 0;
i < forked_canmount_not_on; ++i)
sem_post(
&noauto_files->noauto_not_on_sem);
}
}
if (debug >= 2)
printf(PROGNAME ": %d done, user=%llu.%06us, "
"system=%llu.%06us, maxrss=%ldB, ex=0x%x\n",
(int)pid,
(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, status);
} }
if (debug) { if (debug) {
struct timespec time_end = {}; struct timespec time_end = {};
clock_gettime(CLOCK_MONOTONIC_RAW, &time_end); clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
struct rusage usage;
getrusage(RUSAGE_SELF, &usage); getrusage(RUSAGE_SELF, &usage);
printf( printf(
"\n" "\n"
PROGNAME ": self : " 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);
getrusage(RUSAGE_CHILDREN, &usage);
printf(PROGNAME ": children: "
"user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n", "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
(unsigned long long) usage.ru_utime.tv_sec, (unsigned long long) usage.ru_utime.tv_sec,
(unsigned int) usage.ru_utime.tv_usec, (unsigned int) usage.ru_utime.tv_usec,
@ -1068,7 +977,7 @@ main(int argc, char **argv)
time_init.tv_nsec / 1000000000; time_init.tv_nsec / 1000000000;
time_init.tv_nsec %= 1000000000; time_init.tv_nsec %= 1000000000;
printf(PROGNAME ": wall : " printf(PROGNAME ": "
"total=%llu.%09llus = " "total=%llu.%09llus = "
"init=%llu.%09llus + real=%llu.%09llus\n", "init=%llu.%09llus + real=%llu.%09llus\n",
(unsigned long long) time_init.tv_sec, (unsigned long long) time_init.tv_sec,
@ -1077,7 +986,15 @@ main(int argc, char **argv)
(unsigned long long) time_start.tv_nsec, (unsigned long long) time_start.tv_nsec,
(unsigned long long) time_end.tv_sec, (unsigned long long) time_end.tv_sec,
(unsigned long long) time_end.tv_nsec); (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); _exit(ret);
} }

View File

@ -142,22 +142,11 @@ ZEDLET, if enabled
.Pq see Xr zed 8 . .Pq see Xr zed 8 .
. .
.Sh ENVIRONMENT .Sh ENVIRONMENT
The If the
.Sy ZFS_DEBUG .Sy ZFS_DEBUG
environment variable can either be environment variable is nonzero
.Sy 0 .Pq or unset and Pa /proc/cmdline No contains Qq Sy debug ,
(default), print summary accounting information at the end.
.Sy 1
(print summary accounting information at the end), or at least
.Sy 2
(print accounting information for each subprocess as it finishes).
.
If not present,
.Pa /proc/cmdline
is additionally checked for
.Qq debug ,
in which case the debug level is set to
.Sy 2 .
. .
.Sh EXAMPLES .Sh EXAMPLES
To begin, enable tracking for the pool: To begin, enable tracking for the pool: