2 * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
3 * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 #include <sys/resource.h>
27 #include <sys/types.h>
47 * Free statics with trivial life-times,
48 * but saved line filenames are replaced with a static string.
50 #define FREE_STATICS false
52 #define nitems(arr) (sizeof (arr) / sizeof (*arr))
53 #define STRCMP ((int(*)(const void *, const void *))&strcmp)
56 #define PROGNAME "zfs-mount-generator"
57 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
58 #define ZFS SBINDIR "/zfs"
60 #define OUTPUT_HEADER \
61 "# Automatically generated by " PROGNAME "\n" \
65 * Starts like the one in libzfs_util.c but also matches "//"
66 * and captures until the end, since we actually use it for path extraxion
68 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
69 static regex_t uri_regex
;
71 static const char *destdir
= "/tmp";
72 static int destdir_fd
= -1;
74 static void *known_pools
= NULL
; /* tsearch() of C strings */
75 static void *noauto_files
= NULL
; /* tsearch() of C strings */
79 systemd_escape(const char *input
, const char *prepend
, const char *append
)
81 size_t len
= strlen(input
);
82 size_t applen
= strlen(append
);
83 size_t prelen
= strlen(prepend
);
84 char *ret
= malloc(4 * len
+ prelen
+ applen
+ 1);
86 fprintf(stderr
, PROGNAME
"[%d]: "
87 "out of memory to escape \"%s%s%s\"!\n",
88 getpid(), prepend
, input
, append
);
92 memcpy(ret
, prepend
, prelen
);
93 char *out
= ret
+ prelen
;
95 const char *cur
= input
;
97 memcpy(out
, "\\x2e", 4);
101 for (; *cur
; ++cur
) {
106 "abcdefghijklmnopqrstuvwxyz"
107 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
111 sprintf(out
, "\\x%02x", (int)*cur
);
116 memcpy(out
, append
, applen
+ 1);
121 simplify_path(char *path
)
124 for (char *cur
= path
; *cur
; ++cur
) {
126 while (*(cur
+ 1) == '/')
137 strendswith(const char *what
, const char *suff
)
139 size_t what_l
= strlen(what
);
140 size_t suff_l
= strlen(suff
);
142 return ((what_l
>= suff_l
) &&
143 (strcmp(what
+ what_l
- suff_l
, suff
) == 0));
146 /* Assumes already-simplified path, doesn't modify input */
148 systemd_escape_path(char *input
, const char *prepend
, const char *append
)
150 if (strcmp(input
, "/") == 0) {
152 if (asprintf(&ret
, "%s-%s", prepend
, append
) == -1) {
153 fprintf(stderr
, PROGNAME
"[%d]: "
154 "out of memory to escape \"%s%s%s\"!\n",
155 getpid(), prepend
, input
, append
);
161 * path_is_normalized() (flattened for absolute paths here),
162 * required for proper escaping
164 if (strstr(input
, "/./") || strstr(input
, "/../") ||
165 strendswith(input
, "/.") || strendswith(input
, "/.."))
172 char *back
= &input
[strlen(input
) - 1];
173 bool deslash
= *back
== '/';
177 char *ret
= systemd_escape(input
, prepend
, append
);
186 fopenat(int dirfd
, const char *pathname
, int flags
,
187 const char *stream_mode
, mode_t mode
)
189 int fd
= openat(dirfd
, pathname
, flags
, mode
);
193 return (fdopen(fd
, stream_mode
));
197 line_worker(char *line
, const char *cachefile
)
201 void **tofree
= tofree_all
;
205 const char *dataset
= strtok_r(line
, "\t", &toktmp
);
206 char *p_mountpoint
= strtok_r(NULL
, "\t", &toktmp
);
207 const char *p_canmount
= strtok_r(NULL
, "\t", &toktmp
);
208 const char *p_atime
= strtok_r(NULL
, "\t", &toktmp
);
209 const char *p_relatime
= strtok_r(NULL
, "\t", &toktmp
);
210 const char *p_devices
= strtok_r(NULL
, "\t", &toktmp
);
211 const char *p_exec
= strtok_r(NULL
, "\t", &toktmp
);
212 const char *p_readonly
= strtok_r(NULL
, "\t", &toktmp
);
213 const char *p_setuid
= strtok_r(NULL
, "\t", &toktmp
);
214 const char *p_nbmand
= strtok_r(NULL
, "\t", &toktmp
);
215 const char *p_encroot
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
216 char *p_keyloc
= strtok_r(NULL
, "\t", &toktmp
) ?: strdupa("none");
217 const char *p_systemd_requires
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
218 const char *p_systemd_requiresmountsfor
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
219 const char *p_systemd_before
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
220 const char *p_systemd_after
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
221 char *p_systemd_wantedby
= strtok_r(NULL
, "\t", &toktmp
) ?: strdupa("-");
222 char *p_systemd_requiredby
= strtok_r(NULL
, "\t", &toktmp
) ?: strdupa("-");
223 const char *p_systemd_nofail
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
224 const char *p_systemd_ignore
= strtok_r(NULL
, "\t", &toktmp
) ?: "-";
227 size_t pool_len
= strlen(dataset
);
228 if ((toktmp
= strchr(dataset
, '/')) != NULL
)
229 pool_len
= toktmp
- dataset
;
230 const char *pool
= *(tofree
++) = strndup(dataset
, pool_len
);
232 if (p_nbmand
== NULL
) {
233 fprintf(stderr
, PROGNAME
"[%d]: %s: not enough tokens!\n",
238 /* Minimal pre-requisites to mount a ZFS dataset */
239 const char *after
= "zfs-import.target";
240 const char *wants
= "zfs-import.target";
241 const char *bindsto
= NULL
;
242 char *wantedby
= NULL
;
243 char *requiredby
= NULL
;
245 bool wantedby_append
= true;
248 * zfs-import.target is not needed if the pool is already imported.
249 * This avoids a dependency loop on root-on-ZFS systems:
250 * systemd-random-seed.service After (via RequiresMountsFor)
251 * var-lib.mount After
252 * zfs-import.target After
253 * zfs-import-{cache,scan}.service After
254 * cryptsetup.service After
255 * systemd-random-seed.service
257 if (tfind(pool
, &known_pools
, STRCMP
)) {
262 if (strcmp(p_systemd_after
, "-") == 0)
263 p_systemd_after
= NULL
;
264 if (strcmp(p_systemd_before
, "-") == 0)
265 p_systemd_before
= NULL
;
266 if (strcmp(p_systemd_requires
, "-") == 0)
267 p_systemd_requires
= NULL
;
268 if (strcmp(p_systemd_requiresmountsfor
, "-") == 0)
269 p_systemd_requiresmountsfor
= NULL
;
272 if (strcmp(p_encroot
, "-") != 0) {
273 char *keyloadunit
= *(tofree
++) =
274 systemd_escape(p_encroot
, "zfs-load-key@", ".service");
275 if (keyloadunit
== NULL
)
278 if (strcmp(dataset
, p_encroot
) == 0) {
279 const char *keymountdep
= NULL
;
280 bool is_prompt
= false;
281 bool need_network
= false;
283 regmatch_t uri_matches
[3];
284 if (regexec(&uri_regex
, p_keyloc
,
285 nitems(uri_matches
), uri_matches
, 0) == 0) {
286 p_keyloc
[uri_matches
[1].rm_eo
] = '\0';
287 p_keyloc
[uri_matches
[2].rm_eo
] = '\0';
289 &p_keyloc
[uri_matches
[1].rm_so
];
291 &p_keyloc
[uri_matches
[2].rm_so
];
293 if (strcmp(scheme
, "https") == 0 ||
294 strcmp(scheme
, "http") == 0)
299 if (strcmp(p_keyloc
, "prompt") != 0)
300 fprintf(stderr
, PROGNAME
"[%d]: %s: "
301 "unknown non-URI keylocation=%s\n",
302 getpid(), dataset
, p_keyloc
);
308 /* Generate the key-load .service unit */
309 FILE *keyloadunit_f
= fopenat(destdir_fd
, keyloadunit
,
310 O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, "w",
312 if (!keyloadunit_f
) {
313 fprintf(stderr
, PROGNAME
"[%d]: %s: "
314 "couldn't open %s under %s: %s\n",
315 getpid(), dataset
, keyloadunit
, destdir
,
320 fprintf(keyloadunit_f
,
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"
329 dataset
, cachefile
, wants
, after
);
332 fprintf(keyloadunit_f
,
333 "Wants=network-online.target\n"
334 "After=network-online.target\n");
336 if (p_systemd_requires
)
337 fprintf(keyloadunit_f
,
338 "Requires=%s\n", p_systemd_requires
);
340 if (p_systemd_requiresmountsfor
)
341 fprintf(keyloadunit_f
,
342 "RequiresMountsFor=%s\n",
343 p_systemd_requiresmountsfor
);
345 fprintf(keyloadunit_f
,
346 "RequiresMountsFor='%s'\n", keymountdep
);
349 fprintf(keyloadunit_f
,
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;",
362 fprintf(keyloadunit_f
,
363 "for i in 1 2 3; do "
364 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
365 "" ZFS
" load-key \"%s\" && exit 0;"
368 dataset
, dataset
, dataset
);
370 fprintf(keyloadunit_f
,
371 "exec " ZFS
" load-key \"%s\"",
374 fprintf(keyloadunit_f
,
376 "ExecStop=/bin/sh -euc '"
377 "[ \"$$(" ZFS
" get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
378 "exec " ZFS
" unload-key \"%s\""
383 (void) fclose(keyloadunit_f
);
386 /* Update dependencies for the mount file to want this */
387 bindsto
= keyloadunit
;
388 if (after
[0] == '\0')
390 else if (asprintf(&toktmp
, "%s %s", after
, keyloadunit
) != -1)
391 after
= *(tofree
++) = toktmp
;
393 fprintf(stderr
, PROGNAME
"[%d]: %s: "
394 "out of memory to generate after=\"%s %s\"!\n",
395 getpid(), dataset
, after
, keyloadunit
);
401 /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
402 if (strcmp(p_systemd_ignore
, "-") == 0 ||
403 strcmp(p_systemd_ignore
, "off") == 0) {
405 } else if (strcmp(p_systemd_ignore
, "on") == 0)
408 fprintf(stderr
, PROGNAME
"[%d]: %s: "
409 "invalid org.openzfs.systemd:ignore=%s\n",
410 getpid(), dataset
, p_systemd_ignore
);
414 /* Check for canmount */
415 if (strcmp(p_canmount
, "on") == 0) {
417 } else if (strcmp(p_canmount
, "noauto") == 0)
419 else if (strcmp(p_canmount
, "off") == 0)
422 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid canmount=%s\n",
423 getpid(), dataset
, p_canmount
);
427 /* Check for legacy and blank mountpoints */
428 if (strcmp(p_mountpoint
, "legacy") == 0 ||
429 strcmp(p_mountpoint
, "none") == 0)
431 else if (p_mountpoint
[0] != '/') {
432 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid mountpoint=%s\n",
433 getpid(), dataset
, p_mountpoint
);
437 /* Escape the mountpoint per systemd policy */
438 simplify_path(p_mountpoint
);
439 const char *mountfile
= systemd_escape_path(p_mountpoint
, "", ".mount");
440 if (mountfile
== NULL
) {
442 PROGNAME
"[%d]: %s: abnormal simplified mountpoint: %s\n",
443 getpid(), dataset
, p_mountpoint
);
449 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
451 * The longest string achievable here is
452 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
457 if (strcmp(p_atime
, "on") == 0) {
459 if (strcmp(p_relatime
, "on") == 0)
460 strcat(opts
, ",atime,relatime");
461 else if (strcmp(p_relatime
, "off") == 0)
462 strcat(opts
, ",atime,strictatime");
465 PROGNAME
"[%d]: %s: invalid relatime=%s\n",
466 getpid(), dataset
, p_relatime
);
467 } else if (strcmp(p_atime
, "off") == 0) {
468 strcat(opts
, ",noatime");
470 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid atime=%s\n",
471 getpid(), dataset
, p_atime
);
474 if (strcmp(p_devices
, "on") == 0)
475 strcat(opts
, ",dev");
476 else if (strcmp(p_devices
, "off") == 0)
477 strcat(opts
, ",nodev");
479 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid devices=%s\n",
480 getpid(), dataset
, p_devices
);
483 if (strcmp(p_exec
, "on") == 0)
484 strcat(opts
, ",exec");
485 else if (strcmp(p_exec
, "off") == 0)
486 strcat(opts
, ",noexec");
488 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid exec=%s\n",
489 getpid(), dataset
, p_exec
);
492 if (strcmp(p_readonly
, "on") == 0)
494 else if (strcmp(p_readonly
, "off") == 0)
497 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid readonly=%s\n",
498 getpid(), dataset
, p_readonly
);
501 if (strcmp(p_setuid
, "on") == 0)
502 strcat(opts
, ",suid");
503 else if (strcmp(p_setuid
, "off") == 0)
504 strcat(opts
, ",nosuid");
506 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid setuid=%s\n",
507 getpid(), dataset
, p_setuid
);
510 if (strcmp(p_nbmand
, "on") == 0)
511 strcat(opts
, ",mand");
512 else if (strcmp(p_nbmand
, "off") == 0)
513 strcat(opts
, ",nomand");
515 fprintf(stderr
, PROGNAME
"[%d]: %s: invalid nbmand=%s\n",
516 getpid(), dataset
, p_setuid
);
518 if (strcmp(p_systemd_wantedby
, "-") != 0) {
521 if (strcmp(p_systemd_wantedby
, "none") != 0)
522 wantedby
= p_systemd_wantedby
;
525 if (strcmp(p_systemd_requiredby
, "-") != 0) {
528 if (strcmp(p_systemd_requiredby
, "none") != 0)
529 requiredby
= p_systemd_requiredby
;
533 * For datasets with canmount=on, a dependency is created for
534 * local-fs.target by default. To avoid regressions, this dependency
535 * is reduced to "wants" rather than "requires" when nofail!=off.
536 * **THIS MAY CHANGE**
537 * noauto=on disables this behavior completely.
540 if (strcmp(p_systemd_nofail
, "off") == 0)
541 requiredby
= strdupa("local-fs.target");
543 wantedby
= strdupa("local-fs.target");
544 wantedby_append
= strcmp(p_systemd_nofail
, "on") != 0;
549 * Handle existing files:
550 * 1. We never overwrite existing files, although we may delete
551 * files if we're sure they were created by us. (see 5.)
552 * 2. We handle files differently based on canmount.
553 * Units with canmount=on always have precedence over noauto.
554 * This is enforced by processing these units before all others.
555 * It is important to use p_canmount and not noauto here,
556 * since we categorise by canmount while other properties,
557 * e.g. org.openzfs.systemd:wanted-by, also modify noauto.
558 * 3. If no unit file exists for a noauto dataset, we create one.
559 * Additionally, we use noauto_files to track the unit file names
560 * (which are the systemd-escaped mountpoints) of all (exclusively)
561 * noauto datasets that had a file created.
562 * 4. If the file to be created is found in the tracking tree,
563 * we do NOT create it.
564 * 5. If a file exists for a noauto dataset,
565 * we check whether the file name is in the array.
566 * If it is, we have multiple noauto datasets for the same
567 * mountpoint. In such cases, we remove the file for safety.
568 * We leave the file name in the tracking array to avoid
569 * further noauto datasets creating a file for this path again.
573 bool already_exists
= fstatat(destdir_fd
, mountfile
, &stbuf
, 0) == 0;
574 bool is_known
= tfind(mountfile
, &noauto_files
, STRCMP
) != NULL
;
576 *(tofree
++) = (void *)mountfile
;
577 if (already_exists
) {
579 /* If it's in noauto_files, we must be noauto too */
583 (void) unlinkat(destdir_fd
, mountfile
, 0);
586 fprintf(stderr
, PROGNAME
"[%d]: %s: "
587 "removing duplicate noauto unit %s%s%s\n",
588 getpid(), dataset
, mountfile
,
589 errno
? "" : " failed: ",
590 errno
? "" : strerror(errno
));
592 /* Don't log for canmount=noauto */
593 if (strcmp(p_canmount
, "on") == 0)
594 fprintf(stderr
, PROGNAME
"[%d]: %s: "
595 "%s already exists. Skipping.\n",
596 getpid(), dataset
, mountfile
);
599 /* File exists: skip current dataset */
605 } else if (strcmp(p_canmount
, "noauto") == 0) {
606 if (tsearch(mountfile
, &noauto_files
, STRCMP
) == NULL
)
607 fprintf(stderr
, PROGNAME
"[%d]: %s: "
608 "out of memory for noauto datasets! "
609 "Not tracking %s.\n",
610 getpid(), dataset
, mountfile
);
612 /* mountfile escaped to noauto_files */
618 FILE *mountfile_f
= fopenat(destdir_fd
, mountfile
,
619 O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, "w", 0644);
622 PROGNAME
"[%d]: %s: couldn't open %s under %s: %s\n",
623 getpid(), dataset
, mountfile
, destdir
, strerror(errno
));
630 "SourcePath=" FSLIST
"/%s\n"
631 "Documentation=man:zfs-mount-generator(8)\n"
636 if (p_systemd_before
)
637 fprintf(mountfile_f
, "%s ", p_systemd_before
);
638 fprintf(mountfile_f
, "zfs-mount.service"); /* Ensures we don't race */
640 fprintf(mountfile_f
, " %s", requiredby
);
641 if (wantedby
&& wantedby_append
)
642 fprintf(mountfile_f
, " %s", wantedby
);
648 fprintf(mountfile_f
, "%s ", p_systemd_after
);
649 fprintf(mountfile_f
, "%s\n", after
);
651 fprintf(mountfile_f
, "Wants=%s\n", wants
);
654 fprintf(mountfile_f
, "BindsTo=%s\n", bindsto
);
655 if (p_systemd_requires
)
656 fprintf(mountfile_f
, "Requires=%s\n", p_systemd_requires
);
657 if (p_systemd_requiresmountsfor
)
659 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor
);
667 "Options=defaults%s,zfsutil\n",
668 p_mountpoint
, dataset
, opts
);
670 (void) fclose(mountfile_f
);
672 if (!requiredby
&& !wantedby
)
675 /* Finally, create the appropriate dependencies */
677 if (asprintf(&linktgt
, "../%s", mountfile
) == -1) {
678 fprintf(stderr
, PROGNAME
"[%d]: %s: "
679 "out of memory for dependents of %s!\n",
680 getpid(), dataset
, mountfile
);
683 *(tofree
++) = linktgt
;
690 {"requires", requiredby
},
693 for (struct dep
*dep
= deps
; dep
->type
; ++dep
) {
697 for (char *reqby
= strtok_r(dep
->list
, " ", &toktmp
);
699 reqby
= strtok_r(NULL
, " ", &toktmp
)) {
702 &depdir
, "%s.%s", reqby
, dep
->type
) == -1) {
703 fprintf(stderr
, PROGNAME
"[%d]: %s: "
704 "out of memory for dependent dir name "
706 getpid(), dataset
, reqby
, dep
->type
);
710 (void) mkdirat(destdir_fd
, depdir
, 0755);
711 int depdir_fd
= openat(destdir_fd
, depdir
,
712 O_PATH
| O_DIRECTORY
| O_CLOEXEC
);
714 fprintf(stderr
, PROGNAME
"[%d]: %s: "
715 "couldn't open %s under %s: %s\n",
716 getpid(), dataset
, depdir
, destdir
,
722 if (symlinkat(linktgt
, depdir_fd
, mountfile
) == -1)
723 fprintf(stderr
, PROGNAME
"[%d]: %s: "
724 "couldn't symlink at "
725 "%s under %s under %s: %s\n",
726 getpid(), dataset
, mountfile
,
727 depdir
, destdir
, strerror(errno
));
729 (void) close(depdir_fd
);
735 if (tofree
>= tofree_all
+ nitems(tofree_all
)) {
737 * This won't happen as-is:
738 * we've got 8 slots and allocate 5 things at most.
741 PROGNAME
"[%d]: %s: need to free %zu > %zu!\n",
742 getpid(), dataset
, tofree
- tofree_all
, nitems(tofree_all
));
743 ret
= tofree
- tofree_all
;
746 while (tofree
-- != tofree_all
)
756 pool_enumerator(zpool_handle_t
*pool
, void *data
__attribute__((unused
)))
761 * Pools are guaranteed-unique by the kernel,
762 * no risk of leaking dupes here
764 char *name
= strdup(zpool_get_name(pool
));
765 if (!name
|| !tsearch(name
, &known_pools
, STRCMP
)) {
775 main(int argc
, char **argv
)
777 struct timespec time_init
= {};
778 clock_gettime(CLOCK_MONOTONIC_RAW
, &time_init
);
781 int kmfd
= open("/dev/kmsg", O_WRONLY
| O_CLOEXEC
);
783 (void) dup2(kmfd
, STDERR_FILENO
);
800 PROGNAME
"[%d]: wrong argument count: %d\n",
806 destdir_fd
= open(destdir
, O_PATH
| O_DIRECTORY
| O_CLOEXEC
);
807 if (destdir_fd
< 0) {
808 fprintf(stderr
, PROGNAME
"[%d]: "
809 "can't open destination directory %s: %s\n",
810 getpid(), destdir
, strerror(errno
));
815 DIR *fslist_dir
= opendir(FSLIST
);
819 PROGNAME
"[%d]: couldn't open " FSLIST
": %s\n",
820 getpid(), strerror(errno
));
825 libzfs_handle_t
*libzfs
= libzfs_init();
827 if (zpool_iter(libzfs
, pool_enumerator
, NULL
) != 0)
828 fprintf(stderr
, PROGNAME
"[%d]: "
829 "error listing pools, ignoring\n",
833 fprintf(stderr
, PROGNAME
"[%d]: "
834 "couldn't start libzfs, ignoring\n",
839 int regerr
= regcomp(&uri_regex
, URI_REGEX_S
, 0);
842 PROGNAME
"[%d]: invalid regex: %d\n",
852 const char *dbgenv
= getenv("ZFS_DEBUG");
854 debug
= atoi(dbgenv
);
856 FILE *cmdline
= fopen("/proc/cmdline", "re");
857 if (cmdline
!= NULL
) {
858 if (getline(&line
, &linelen
, cmdline
) >= 0)
859 debug
= strstr(line
, "debug");
860 (void) fclose(cmdline
);
864 if (debug
&& !isatty(STDOUT_FILENO
))
865 dup2(STDERR_FILENO
, STDOUT_FILENO
);
868 struct timespec time_start
= {};
870 clock_gettime(CLOCK_MONOTONIC_RAW
, &time_start
);
876 } *lines_canmount_not_on
= NULL
;
879 struct dirent
*cachent
;
880 while ((cachent
= readdir(fslist_dir
)) != NULL
) {
881 if (strcmp(cachent
->d_name
, ".") == 0 ||
882 strcmp(cachent
->d_name
, "..") == 0)
885 FILE *cachefile
= fopenat(dirfd(fslist_dir
), cachent
->d_name
,
886 O_RDONLY
| O_CLOEXEC
, "r", 0);
888 fprintf(stderr
, PROGNAME
"[%d]: "
889 "couldn't open %s under " FSLIST
": %s\n",
890 getpid(), cachent
->d_name
, strerror(errno
));
894 const char *filename
= FREE_STATICS
? "(elided)" : NULL
;
897 while ((read
= getline(&line
, &linelen
, cachefile
)) >= 0) {
898 line
[read
- 1] = '\0'; /* newline */
900 char *canmount
= line
;
901 canmount
+= strcspn(canmount
, "\t");
902 canmount
+= strspn(canmount
, "\t");
903 canmount
+= strcspn(canmount
, "\t");
904 canmount
+= strspn(canmount
, "\t");
905 bool canmount_on
= strncmp(canmount
, "on", 2) == 0;
908 ret
|= line_worker(line
, cachent
->d_name
);
910 if (filename
== NULL
)
912 strdup(cachent
->d_name
) ?: "(?)";
914 struct line
*l
= calloc(1, sizeof (*l
));
915 char *nl
= strdup(line
);
916 if (l
== NULL
|| nl
== NULL
) {
917 fprintf(stderr
, PROGNAME
"[%d]: "
918 "out of memory for \"%s\" in %s\n",
919 getpid(), line
, cachent
->d_name
);
926 l
->next
= lines_canmount_not_on
;
927 lines_canmount_not_on
= l
;
935 while (lines_canmount_not_on
) {
936 struct line
*l
= lines_canmount_not_on
;
937 lines_canmount_not_on
= l
->next
;
939 ret
|= line_worker(l
->line
, l
->fname
);
947 struct timespec time_end
= {};
948 clock_gettime(CLOCK_MONOTONIC_RAW
, &time_end
);
951 getrusage(RUSAGE_SELF
, &usage
);
955 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
956 (unsigned long long) usage
.ru_utime
.tv_sec
,
957 (unsigned int) usage
.ru_utime
.tv_usec
,
958 (unsigned long long) usage
.ru_stime
.tv_sec
,
959 (unsigned int) usage
.ru_stime
.tv_usec
,
960 usage
.ru_maxrss
* 1024);
962 if (time_start
.tv_nsec
> time_end
.tv_nsec
) {
964 1000000000 + time_end
.tv_nsec
- time_start
.tv_nsec
;
965 time_end
.tv_sec
-= 1;
967 time_end
.tv_nsec
-= time_start
.tv_nsec
;
968 time_end
.tv_sec
-= time_start
.tv_sec
;
970 if (time_init
.tv_nsec
> time_start
.tv_nsec
) {
972 1000000000 + time_start
.tv_nsec
- time_init
.tv_nsec
;
973 time_start
.tv_sec
-= 1;
975 time_start
.tv_nsec
-= time_init
.tv_nsec
;
976 time_start
.tv_sec
-= time_init
.tv_sec
;
978 time_init
.tv_nsec
= time_start
.tv_nsec
+ time_end
.tv_nsec
;
980 time_start
.tv_sec
+ time_end
.tv_sec
+
981 time_init
.tv_nsec
/ 1000000000;
982 time_init
.tv_nsec
%= 1000000000;
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
);
998 closedir(fslist_dir
);
999 tdestroy(noauto_files
, free
);
1000 tdestroy(known_pools
, free
);
1001 regfree(&uri_regex
);