1 // SPDX-License-Identifier: MIT
2 /*
3 * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
4 * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26
27 #include <sys/resource.h>
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <sys/stat.h>
31 #include <stdbool.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <stdio.h>
35 #include <time.h>
36 #include <regex.h>
37 #include <search.h>
38 #include <dirent.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include <errno.h>
43 #include <libzfs.h>
44
45 /*
46 * For debugging only.
47 *
48 * Free statics with trivial life-times,
49 * but saved line filenames are replaced with a static string.
50 */
51 #define FREE_STATICS false
52
53 #define nitems(arr) (sizeof (arr) / sizeof (*arr))
54 #define STRCMP ((int(*)(const void *, const void *))&strcmp)
55
56
57 #define PROGNAME "zfs-mount-generator"
58 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
59 #define ZFS SBINDIR "/zfs"
60
61 #define OUTPUT_HEADER \
62 "# Automatically generated by " PROGNAME "\n" \
63 "\n"
64
65 /*
66 * Starts like the one in libzfs_util.c but also matches "//"
67 * and captures until the end, since we actually use it for path extraxion
68 */
69 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
70 static regex_t uri_regex;
71
72 static const char *destdir = "/tmp";
73 static int destdir_fd = -1;
74
75 static void *known_pools = NULL; /* tsearch() of C strings */
76 static void *noauto_files = NULL; /* tsearch() of C strings */
77
78
79 static char *
systemd_escape(const char * input,const char * prepend,const char * append)80 systemd_escape(const char *input, const char *prepend, const char *append)
81 {
82 size_t len = strlen(input);
83 size_t applen = strlen(append);
84 size_t prelen = strlen(prepend);
85 char *ret = malloc(4 * len + prelen + applen + 1);
86 if (!ret) {
87 fprintf(stderr, PROGNAME "[%d]: "
88 "out of memory to escape \"%s%s%s\"!\n",
89 getpid(), prepend, input, append);
90 return (NULL);
91 }
92
93 memcpy(ret, prepend, prelen);
94 char *out = ret + prelen;
95
96 const char *cur = input;
97 if (*cur == '.') {
98 memcpy(out, "\\x2e", 4);
99 out += 4;
100 ++cur;
101 }
102 for (; *cur; ++cur) {
103 if (*cur == '/')
104 *(out++) = '-';
105 else if (strchr(
106 "0123456789"
107 "abcdefghijklmnopqrstuvwxyz"
108 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
109 ":_.", *cur))
110 *(out++) = *cur;
111 else {
112 sprintf(out, "\\x%02x", (int)*cur);
113 out += 4;
114 }
115 }
116
117 memcpy(out, append, applen + 1);
118 return (ret);
119 }
120
121 static void
simplify_path(char * path)122 simplify_path(char *path)
123 {
124 char *out = path;
125 for (char *cur = path; *cur; ++cur) {
126 if (*cur == '/') {
127 while (*(cur + 1) == '/')
128 ++cur;
129 *(out++) = '/';
130 } else
131 *(out++) = *cur;
132 }
133
134 *(out++) = '\0';
135 }
136
137 static bool
strendswith(const char * what,const char * suff)138 strendswith(const char *what, const char *suff)
139 {
140 size_t what_l = strlen(what);
141 size_t suff_l = strlen(suff);
142
143 return ((what_l >= suff_l) &&
144 (strcmp(what + what_l - suff_l, suff) == 0));
145 }
146
147 /* Assumes already-simplified path, doesn't modify input */
148 static char *
systemd_escape_path(char * input,const char * prepend,const char * append)149 systemd_escape_path(char *input, const char *prepend, const char *append)
150 {
151 if (strcmp(input, "/") == 0) {
152 char *ret;
153 if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
154 fprintf(stderr, PROGNAME "[%d]: "
155 "out of memory to escape \"%s%s%s\"!\n",
156 getpid(), prepend, input, append);
157 ret = NULL;
158 }
159 return (ret);
160 } else {
161 /*
162 * path_is_normalized() (flattened for absolute paths here),
163 * required for proper escaping
164 */
165 if (strstr(input, "/./") || strstr(input, "/../") ||
166 strendswith(input, "/.") || strendswith(input, "/.."))
167 return (NULL);
168
169
170 if (input[0] == '/')
171 ++input;
172
173 char *back = &input[strlen(input) - 1];
174 bool deslash = *back == '/';
175 if (deslash)
176 *back = '\0';
177
178 char *ret = systemd_escape(input, prepend, append);
179
180 if (deslash)
181 *back = '/';
182 return (ret);
183 }
184 }
185
186 static FILE *
fopenat(int dirfd,const char * pathname,int flags,const char * stream_mode,mode_t mode)187 fopenat(int dirfd, const char *pathname, int flags,
188 const char *stream_mode, mode_t mode)
189 {
190 int fd = openat(dirfd, pathname, flags, mode);
191 if (fd < 0)
192 return (NULL);
193
194 return (fdopen(fd, stream_mode));
195 }
196
197 static int
line_worker(char * line,const char * cachefile)198 line_worker(char *line, const char *cachefile)
199 {
200 int ret = 0;
201 void *tofree_all[8];
202 void **tofree = tofree_all;
203
204 char *toktmp;
205 /* BEGIN CSTYLED */
206 const char *dataset = strtok_r(line, "\t", &toktmp);
207 char *p_mountpoint = strtok_r(NULL, "\t", &toktmp);
208 const char *p_canmount = strtok_r(NULL, "\t", &toktmp);
209 const char *p_atime = strtok_r(NULL, "\t", &toktmp);
210 const char *p_relatime = strtok_r(NULL, "\t", &toktmp);
211 const char *p_devices = strtok_r(NULL, "\t", &toktmp);
212 const char *p_exec = strtok_r(NULL, "\t", &toktmp);
213 const char *p_readonly = strtok_r(NULL, "\t", &toktmp);
214 const char *p_setuid = strtok_r(NULL, "\t", &toktmp);
215 const char *p_nbmand = strtok_r(NULL, "\t", &toktmp);
216 const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-";
217 char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
218 const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-";
219 const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
220 const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-";
221 const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-";
222 char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
223 char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
224 const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-";
225 const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-";
226 /* END CSTYLED */
227
228 size_t pool_len = strlen(dataset);
229 if ((toktmp = strchr(dataset, '/')) != NULL)
230 pool_len = toktmp - dataset;
231 const char *pool = *(tofree++) = strndup(dataset, pool_len);
232
233 if (p_nbmand == NULL) {
234 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
235 getpid(), dataset);
236 goto err;
237 }
238
239 /* Minimal pre-requisites to mount a ZFS dataset */
240 const char *after = "zfs-import.target";
241 const char *wants = "zfs-import.target";
242 const char *bindsto = NULL;
243 char *wantedby = NULL;
244 char *requiredby = NULL;
245 bool noauto = false;
246 bool wantedby_append = true;
247
248 /*
249 * zfs-import.target is not needed if the pool is already imported.
250 * This avoids a dependency loop on root-on-ZFS systems:
251 * systemd-random-seed.service After (via RequiresMountsFor)
252 * var-lib.mount After
253 * zfs-import.target After
254 * zfs-import-{cache,scan}.service After
255 * cryptsetup.service After
256 * systemd-random-seed.service
257 */
258 if (tfind(pool, &known_pools, STRCMP)) {
259 after = "";
260 wants = "";
261 }
262
263 if (strcmp(p_systemd_after, "-") == 0)
264 p_systemd_after = NULL;
265 if (strcmp(p_systemd_before, "-") == 0)
266 p_systemd_before = NULL;
267 if (strcmp(p_systemd_requires, "-") == 0)
268 p_systemd_requires = NULL;
269 if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
270 p_systemd_requiresmountsfor = NULL;
271
272
273 if (strcmp(p_encroot, "-") != 0) {
274 char *keyloadunit = *(tofree++) =
275 systemd_escape(p_encroot, "zfs-load-key@", ".service");
276 if (keyloadunit == NULL)
277 goto err;
278
279 if (strcmp(dataset, p_encroot) == 0) {
280 const char *keymountdep = NULL;
281 bool is_prompt = false;
282 bool need_network = false;
283
284 regmatch_t uri_matches[3];
285 if (regexec(&uri_regex, p_keyloc,
286 nitems(uri_matches), uri_matches, 0) == 0) {
287 p_keyloc[uri_matches[1].rm_eo] = '\0';
288 p_keyloc[uri_matches[2].rm_eo] = '\0';
289 const char *scheme =
290 &p_keyloc[uri_matches[1].rm_so];
291 const char *path =
292 &p_keyloc[uri_matches[2].rm_so];
293
294 if (strcmp(scheme, "https") == 0 ||
295 strcmp(scheme, "http") == 0)
296 need_network = true;
297 else
298 keymountdep = path;
299 } else {
300 if (strcmp(p_keyloc, "prompt") != 0)
301 fprintf(stderr, PROGNAME "[%d]: %s: "
302 "unknown non-URI keylocation=%s\n",
303 getpid(), dataset, p_keyloc);
304
305 is_prompt = true;
306 }
307
308
309 /* Generate the key-load .service unit */
310 FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
311 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
312 0644);
313 if (!keyloadunit_f) {
314 fprintf(stderr, PROGNAME "[%d]: %s: "
315 "couldn't open %s under %s: %s\n",
316 getpid(), dataset, keyloadunit, destdir,
317 strerror(errno));
318 goto err;
319 }
320
321 fprintf(keyloadunit_f,
322 OUTPUT_HEADER
323 "[Unit]\n"
324 "Description=Load ZFS key for %s\n"
325 "SourcePath=" FSLIST "/%s\n"
326 "Documentation=man:zfs-mount-generator(8)\n"
327 "DefaultDependencies=no\n"
328 "Wants=%s\n"
329 "After=%s\n",
330 dataset, cachefile, wants, after);
331
332 if (need_network)
333 fprintf(keyloadunit_f,
334 "Wants=network-online.target\n"
335 "After=network-online.target\n");
336
337 if (p_systemd_requires)
338 fprintf(keyloadunit_f,
339 "Requires=%s\n", p_systemd_requires);
340
341 if (p_systemd_requiresmountsfor)
342 fprintf(keyloadunit_f,
343 "RequiresMountsFor=%s\n",
344 p_systemd_requiresmountsfor);
345 if (keymountdep)
346 fprintf(keyloadunit_f,
347 "RequiresMountsFor='%s'\n", keymountdep);
348
349 /* BEGIN CSTYLED */
350 fprintf(keyloadunit_f,
351 "\n"
352 "[Service]\n"
353 "Type=oneshot\n"
354 "RemainAfterExit=yes\n"
355 "# This avoids a dependency loop involving systemd-journald.socket if this\n"
356 "# dataset is a parent of the root filesystem.\n"
357 "StandardOutput=null\n"
358 "StandardError=null\n"
359 "ExecStart=/bin/sh -euc '"
360 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
361 dataset);
362 if (is_prompt)
363 fprintf(keyloadunit_f,
364 "for i in 1 2 3; do "
365 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
366 "" ZFS " load-key \"%s\" && exit 0;"
367 "done;"
368 "exit 1",
369 dataset, dataset, dataset);
370 else
371 fprintf(keyloadunit_f,
372 "exec " ZFS " load-key \"%s\"",
373 dataset);
374
375 fprintf(keyloadunit_f,
376 "'\n"
377 "ExecStop=/bin/sh -euc '"
378 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
379 "exec " ZFS " unload-key \"%s\""
380 "'\n",
381 dataset, dataset);
382 /* END CSTYLED */
383
384 (void) fclose(keyloadunit_f);
385 }
386
387 /* Update dependencies for the mount file to want this */
388 bindsto = keyloadunit;
389 if (after[0] == '\0')
390 after = keyloadunit;
391 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
392 after = *(tofree++) = toktmp;
393 else {
394 fprintf(stderr, PROGNAME "[%d]: %s: "
395 "out of memory to generate after=\"%s %s\"!\n",
396 getpid(), dataset, after, keyloadunit);
397 goto err;
398 }
399 }
400
401
402 /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
403 if (strcmp(p_systemd_ignore, "-") == 0 ||
404 strcmp(p_systemd_ignore, "off") == 0) {
405 /* ok */
406 } else if (strcmp(p_systemd_ignore, "on") == 0)
407 goto end;
408 else {
409 fprintf(stderr, PROGNAME "[%d]: %s: "
410 "invalid org.openzfs.systemd:ignore=%s\n",
411 getpid(), dataset, p_systemd_ignore);
412 goto err;
413 }
414
415 /* Check for canmount */
416 if (strcmp(p_canmount, "on") == 0) {
417 /* ok */
418 } else if (strcmp(p_canmount, "noauto") == 0)
419 noauto = true;
420 else if (strcmp(p_canmount, "off") == 0)
421 goto end;
422 else {
423 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
424 getpid(), dataset, p_canmount);
425 goto err;
426 }
427
428 /* Check for legacy and blank mountpoints */
429 if (strcmp(p_mountpoint, "legacy") == 0 ||
430 strcmp(p_mountpoint, "none") == 0)
431 goto end;
432 else if (p_mountpoint[0] != '/') {
433 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
434 getpid(), dataset, p_mountpoint);
435 goto err;
436 }
437
438 /* Escape the mountpoint per systemd policy */
439 simplify_path(p_mountpoint);
440 const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
441 if (mountfile == NULL) {
442 fprintf(stderr,
443 PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
444 getpid(), dataset, p_mountpoint);
445 goto err;
446 }
447
448
449 /*
450 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
451 *
452 * The longest string achievable here is
453 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
454 */
455 char opts[64] = "";
456
457 /* atime */
458 if (strcmp(p_atime, "on") == 0) {
459 /* relatime */
460 if (strcmp(p_relatime, "on") == 0)
461 strcat(opts, ",atime,relatime");
462 else if (strcmp(p_relatime, "off") == 0)
463 strcat(opts, ",atime,strictatime");
464 else
465 fprintf(stderr,
466 PROGNAME "[%d]: %s: invalid relatime=%s\n",
467 getpid(), dataset, p_relatime);
468 } else if (strcmp(p_atime, "off") == 0) {
469 strcat(opts, ",noatime");
470 } else
471 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
472 getpid(), dataset, p_atime);
473
474 /* devices */
475 if (strcmp(p_devices, "on") == 0)
476 strcat(opts, ",dev");
477 else if (strcmp(p_devices, "off") == 0)
478 strcat(opts, ",nodev");
479 else
480 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
481 getpid(), dataset, p_devices);
482
483 /* exec */
484 if (strcmp(p_exec, "on") == 0)
485 strcat(opts, ",exec");
486 else if (strcmp(p_exec, "off") == 0)
487 strcat(opts, ",noexec");
488 else
489 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
490 getpid(), dataset, p_exec);
491
492 /* readonly */
493 if (strcmp(p_readonly, "on") == 0)
494 strcat(opts, ",ro");
495 else if (strcmp(p_readonly, "off") == 0)
496 strcat(opts, ",rw");
497 else
498 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
499 getpid(), dataset, p_readonly);
500
501 /* setuid */
502 if (strcmp(p_setuid, "on") == 0)
503 strcat(opts, ",suid");
504 else if (strcmp(p_setuid, "off") == 0)
505 strcat(opts, ",nosuid");
506 else
507 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
508 getpid(), dataset, p_setuid);
509
510 /* nbmand */
511 if (strcmp(p_nbmand, "on") == 0)
512 strcat(opts, ",mand");
513 else if (strcmp(p_nbmand, "off") == 0)
514 strcat(opts, ",nomand");
515 else
516 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
517 getpid(), dataset, p_setuid);
518
519 if (strcmp(p_systemd_wantedby, "-") != 0) {
520 noauto = true;
521
522 if (strcmp(p_systemd_wantedby, "none") != 0)
523 wantedby = p_systemd_wantedby;
524 }
525
526 if (strcmp(p_systemd_requiredby, "-") != 0) {
527 noauto = true;
528
529 if (strcmp(p_systemd_requiredby, "none") != 0)
530 requiredby = p_systemd_requiredby;
531 }
532
533 /*
534 * For datasets with canmount=on, a dependency is created for
535 * local-fs.target by default. To avoid regressions, this dependency
536 * is reduced to "wants" rather than "requires" when nofail!=off.
537 * **THIS MAY CHANGE**
538 * noauto=on disables this behavior completely.
539 */
540 if (!noauto) {
541 if (strcmp(p_systemd_nofail, "off") == 0)
542 requiredby = strdupa("local-fs.target");
543 else {
544 wantedby = strdupa("local-fs.target");
545 wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
546 }
547 }
548
549 /*
550 * Handle existing files:
551 * 1. We never overwrite existing files, although we may delete
552 * files if we're sure they were created by us. (see 5.)
553 * 2. We handle files differently based on canmount.
554 * Units with canmount=on always have precedence over noauto.
555 * This is enforced by processing these units before all others.
556 * It is important to use p_canmount and not noauto here,
557 * since we categorise by canmount while other properties,
558 * e.g. org.openzfs.systemd:wanted-by, also modify noauto.
559 * 3. If no unit file exists for a noauto dataset, we create one.
560 * Additionally, we use noauto_files to track the unit file names
561 * (which are the systemd-escaped mountpoints) of all (exclusively)
562 * noauto datasets that had a file created.
563 * 4. If the file to be created is found in the tracking tree,
564 * we do NOT create it.
565 * 5. If a file exists for a noauto dataset,
566 * we check whether the file name is in the array.
567 * If it is, we have multiple noauto datasets for the same
568 * mountpoint. In such cases, we remove the file for safety.
569 * We leave the file name in the tracking array to avoid
570 * further noauto datasets creating a file for this path again.
571 */
572
573 struct stat stbuf;
574 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
575 bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
576
577 *(tofree++) = (void *)mountfile;
578 if (already_exists) {
579 if (is_known) {
580 /* If it's in noauto_files, we must be noauto too */
581
582 /* See 5 */
583 errno = 0;
584 (void) unlinkat(destdir_fd, mountfile, 0);
585
586 /* See 2 */
587 fprintf(stderr, PROGNAME "[%d]: %s: "
588 "removing duplicate noauto unit %s%s%s\n",
589 getpid(), dataset, mountfile,
590 errno ? "" : " failed: ",
591 errno ? "" : strerror(errno));
592 } else {
593 /* Don't log for canmount=noauto */
594 if (strcmp(p_canmount, "on") == 0)
595 fprintf(stderr, PROGNAME "[%d]: %s: "
596 "%s already exists. Skipping.\n",
597 getpid(), dataset, mountfile);
598 }
599
600 /* File exists: skip current dataset */
601 goto end;
602 } else {
603 if (is_known) {
604 /* See 4 */
605 goto end;
606 } else if (strcmp(p_canmount, "noauto") == 0) {
607 if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
608 fprintf(stderr, PROGNAME "[%d]: %s: "
609 "out of memory for noauto datasets! "
610 "Not tracking %s.\n",
611 getpid(), dataset, mountfile);
612 else
613 /* mountfile escaped to noauto_files */
614 *(--tofree) = NULL;
615 }
616 }
617
618
619 FILE *mountfile_f = fopenat(destdir_fd, mountfile,
620 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
621 if (!mountfile_f) {
622 fprintf(stderr,
623 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
624 getpid(), dataset, mountfile, destdir, strerror(errno));
625 goto err;
626 }
627
628 fprintf(mountfile_f,
629 OUTPUT_HEADER
630 "[Unit]\n"
631 "SourcePath=" FSLIST "/%s\n"
632 "Documentation=man:zfs-mount-generator(8)\n"
633 "\n"
634 "Before=",
635 cachefile);
636
637 if (p_systemd_before)
638 fprintf(mountfile_f, "%s ", p_systemd_before);
639 fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
640 if (requiredby)
641 fprintf(mountfile_f, " %s", requiredby);
642 if (wantedby && wantedby_append)
643 fprintf(mountfile_f, " %s", wantedby);
644
645 fprintf(mountfile_f,
646 "\n"
647 "After=");
648 if (p_systemd_after)
649 fprintf(mountfile_f, "%s ", p_systemd_after);
650 fprintf(mountfile_f, "%s\n", after);
651
652 fprintf(mountfile_f, "Wants=%s\n", wants);
653
654 if (bindsto)
655 fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
656 if (p_systemd_requires)
657 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
658 if (p_systemd_requiresmountsfor)
659 fprintf(mountfile_f,
660 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
661
662 fprintf(mountfile_f,
663 "\n"
664 "[Mount]\n"
665 "Where=%s\n"
666 "What=%s\n"
667 "Type=zfs\n"
668 "Options=defaults%s,zfsutil\n",
669 p_mountpoint, dataset, opts);
670
671 (void) fclose(mountfile_f);
672
673 if (!requiredby && !wantedby)
674 goto end;
675
676 /* Finally, create the appropriate dependencies */
677 char *linktgt;
678 if (asprintf(&linktgt, "../%s", mountfile) == -1) {
679 fprintf(stderr, PROGNAME "[%d]: %s: "
680 "out of memory for dependents of %s!\n",
681 getpid(), dataset, mountfile);
682 goto err;
683 }
684 *(tofree++) = linktgt;
685
686 struct dep {
687 const char *type;
688 char *list;
689 } deps[] = {
690 {"wants", wantedby},
691 {"requires", requiredby},
692 {}
693 };
694 for (struct dep *dep = deps; dep->type; ++dep) {
695 if (!dep->list)
696 continue;
697
698 for (char *reqby = strtok_r(dep->list, " ", &toktmp);
699 reqby;
700 reqby = strtok_r(NULL, " ", &toktmp)) {
701 char *depdir;
702 if (asprintf(
703 &depdir, "%s.%s", reqby, dep->type) == -1) {
704 fprintf(stderr, PROGNAME "[%d]: %s: "
705 "out of memory for dependent dir name "
706 "\"%s.%s\"!\n",
707 getpid(), dataset, reqby, dep->type);
708 continue;
709 }
710
711 (void) mkdirat(destdir_fd, depdir, 0755);
712 int depdir_fd = openat(destdir_fd, depdir,
713 O_PATH | O_DIRECTORY | O_CLOEXEC);
714 if (depdir_fd < 0) {
715 fprintf(stderr, PROGNAME "[%d]: %s: "
716 "couldn't open %s under %s: %s\n",
717 getpid(), dataset, depdir, destdir,
718 strerror(errno));
719 free(depdir);
720 continue;
721 }
722
723 if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
724 fprintf(stderr, PROGNAME "[%d]: %s: "
725 "couldn't symlink at "
726 "%s under %s under %s: %s\n",
727 getpid(), dataset, mountfile,
728 depdir, destdir, strerror(errno));
729
730 (void) close(depdir_fd);
731 free(depdir);
732 }
733 }
734
735 end:
736 if (tofree >= tofree_all + nitems(tofree_all)) {
737 /*
738 * This won't happen as-is:
739 * we've got 8 slots and allocate 5 things at most.
740 */
741 fprintf(stderr,
742 PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
743 getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
744 ret = tofree - tofree_all;
745 }
746
747 while (tofree-- != tofree_all)
748 free(*tofree);
749 return (ret);
750 err:
751 ret = 1;
752 goto end;
753 }
754
755
756 static int
pool_enumerator(zpool_handle_t * pool,void * data)757 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
758 {
759 int ret = 0;
760
761 /*
762 * Pools are guaranteed-unique by the kernel,
763 * no risk of leaking dupes here
764 */
765 char *name = strdup(zpool_get_name(pool));
766 if (!name || !tsearch(name, &known_pools, STRCMP)) {
767 free(name);
768 ret = ENOMEM;
769 }
770
771 zpool_close(pool);
772 return (ret);
773 }
774
775 int
main(int argc,char ** argv)776 main(int argc, char **argv)
777 {
778 struct timespec time_init = {};
779 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
780
781 {
782 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
783 if (kmfd >= 0) {
784 (void) dup2(kmfd, STDERR_FILENO);
785 (void) close(kmfd);
786
787 setlinebuf(stderr);
788 }
789 }
790
791 switch (argc) {
792 case 1:
793 /* Use default */
794 break;
795 case 2:
796 case 4:
797 destdir = argv[1];
798 break;
799 default:
800 fprintf(stderr,
801 PROGNAME "[%d]: wrong argument count: %d\n",
802 getpid(), argc - 1);
803 _exit(1);
804 }
805
806 {
807 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
808 if (destdir_fd < 0) {
809 fprintf(stderr, PROGNAME "[%d]: "
810 "can't open destination directory %s: %s\n",
811 getpid(), destdir, strerror(errno));
812 _exit(1);
813 }
814 }
815
816 DIR *fslist_dir = opendir(FSLIST);
817 if (!fslist_dir) {
818 if (errno != ENOENT)
819 fprintf(stderr,
820 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
821 getpid(), strerror(errno));
822 _exit(0);
823 }
824
825 {
826 libzfs_handle_t *libzfs = libzfs_init();
827 if (libzfs) {
828 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
829 fprintf(stderr, PROGNAME "[%d]: "
830 "error listing pools, ignoring\n",
831 getpid());
832 libzfs_fini(libzfs);
833 } else
834 fprintf(stderr, PROGNAME "[%d]: "
835 "couldn't start libzfs, ignoring\n",
836 getpid());
837 }
838
839 {
840 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
841 if (regerr != 0) {
842 fprintf(stderr,
843 PROGNAME "[%d]: invalid regex: %d\n",
844 getpid(), regerr);
845 _exit(1);
846 }
847 }
848
849 bool debug = false;
850 char *line = NULL;
851 size_t linelen = 0;
852 {
853 const char *dbgenv = getenv("ZFS_DEBUG");
854 if (dbgenv)
855 debug = atoi(dbgenv);
856 else {
857 FILE *cmdline = fopen("/proc/cmdline", "re");
858 if (cmdline != NULL) {
859 if (getline(&line, &linelen, cmdline) >= 0)
860 debug = strstr(line, "debug");
861 (void) fclose(cmdline);
862 }
863 }
864
865 if (debug && !isatty(STDOUT_FILENO))
866 dup2(STDERR_FILENO, STDOUT_FILENO);
867 }
868
869 struct timespec time_start = {};
870 if (debug)
871 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
872
873 struct line {
874 char *line;
875 const char *fname;
876 struct line *next;
877 } *lines_canmount_not_on = NULL;
878
879 int ret = 0;
880 struct dirent *cachent;
881 while ((cachent = readdir(fslist_dir)) != NULL) {
882 if (strcmp(cachent->d_name, ".") == 0 ||
883 strcmp(cachent->d_name, "..") == 0)
884 continue;
885
886 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
887 O_RDONLY | O_CLOEXEC, "r", 0);
888 if (!cachefile) {
889 fprintf(stderr, PROGNAME "[%d]: "
890 "couldn't open %s under " FSLIST ": %s\n",
891 getpid(), cachent->d_name, strerror(errno));
892 continue;
893 }
894
895 const char *filename = FREE_STATICS ? "(elided)" : NULL;
896
897 ssize_t read;
898 while ((read = getline(&line, &linelen, cachefile)) >= 0) {
899 line[read - 1] = '\0'; /* newline */
900
901 char *canmount = line;
902 canmount += strcspn(canmount, "\t");
903 canmount += strspn(canmount, "\t");
904 canmount += strcspn(canmount, "\t");
905 canmount += strspn(canmount, "\t");
906 bool canmount_on = strncmp(canmount, "on", 2) == 0;
907
908 if (canmount_on)
909 ret |= line_worker(line, cachent->d_name);
910 else {
911 if (filename == NULL)
912 filename =
913 strdup(cachent->d_name) ?: "(?)";
914
915 struct line *l = calloc(1, sizeof (*l));
916 char *nl = strdup(line);
917 if (l == NULL || nl == NULL) {
918 fprintf(stderr, PROGNAME "[%d]: "
919 "out of memory for \"%s\" in %s\n",
920 getpid(), line, cachent->d_name);
921 free(l);
922 free(nl);
923 continue;
924 }
925 l->line = nl;
926 l->fname = filename;
927 l->next = lines_canmount_not_on;
928 lines_canmount_not_on = l;
929 }
930 }
931
932 fclose(cachefile);
933 }
934 free(line);
935
936 while (lines_canmount_not_on) {
937 struct line *l = lines_canmount_not_on;
938 lines_canmount_not_on = l->next;
939
940 ret |= line_worker(l->line, l->fname);
941 if (FREE_STATICS) {
942 free(l->line);
943 free(l);
944 }
945 }
946
947 if (debug) {
948 struct timespec time_end = {};
949 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
950
951 struct rusage usage;
952 getrusage(RUSAGE_SELF, &usage);
953 printf(
954 "\n"
955 PROGNAME ": "
956 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
957 (unsigned long long) usage.ru_utime.tv_sec,
958 (unsigned int) usage.ru_utime.tv_usec,
959 (unsigned long long) usage.ru_stime.tv_sec,
960 (unsigned int) usage.ru_stime.tv_usec,
961 usage.ru_maxrss * 1024);
962
963 if (time_start.tv_nsec > time_end.tv_nsec) {
964 time_end.tv_nsec =
965 1000000000 + time_end.tv_nsec - time_start.tv_nsec;
966 time_end.tv_sec -= 1;
967 } else
968 time_end.tv_nsec -= time_start.tv_nsec;
969 time_end.tv_sec -= time_start.tv_sec;
970
971 if (time_init.tv_nsec > time_start.tv_nsec) {
972 time_start.tv_nsec =
973 1000000000 + time_start.tv_nsec - time_init.tv_nsec;
974 time_start.tv_sec -= 1;
975 } else
976 time_start.tv_nsec -= time_init.tv_nsec;
977 time_start.tv_sec -= time_init.tv_sec;
978
979 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
980 time_init.tv_sec =
981 time_start.tv_sec + time_end.tv_sec +
982 time_init.tv_nsec / 1000000000;
983 time_init.tv_nsec %= 1000000000;
984
985 printf(PROGNAME ": "
986 "total=%llu.%09llus = "
987 "init=%llu.%09llus + real=%llu.%09llus\n",
988 (unsigned long long) time_init.tv_sec,
989 (unsigned long long) time_init.tv_nsec,
990 (unsigned long long) time_start.tv_sec,
991 (unsigned long long) time_start.tv_nsec,
992 (unsigned long long) time_end.tv_sec,
993 (unsigned long long) time_end.tv_nsec);
994
995 fflush(stdout);
996 }
997
998 if (FREE_STATICS) {
999 closedir(fslist_dir);
1000 tdestroy(noauto_files, free);
1001 tdestroy(known_pools, free);
1002 regfree(&uri_regex);
1003 }
1004 _exit(ret);
1005 }
1006