1 /*
2 * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
3 * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
4 *
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:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 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.
23 */
24
25
26 #include <sys/resource.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/stat.h>
30 #include <stdbool.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <stdio.h>
34 #include <time.h>
35 #include <regex.h>
36 #include <search.h>
37 #include <dirent.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <limits.h>
41 #include <errno.h>
42 #include <libzfs.h>
43
44 /*
45 * For debugging only.
46 *
47 * Free statics with trivial life-times,
48 * but saved line filenames are replaced with a static string.
49 */
50 #define FREE_STATICS false
51
52 #define nitems(arr) (sizeof (arr) / sizeof (*arr))
53 #define STRCMP ((int(*)(const void *, const void *))&strcmp)
54
55
56 #define PROGNAME "zfs-mount-generator"
57 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
58 #define ZFS SBINDIR "/zfs"
59
60 #define OUTPUT_HEADER \
61 "# Automatically generated by " PROGNAME "\n" \
62 "\n"
63
64 /*
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
67 */
68 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
69 static regex_t uri_regex;
70
71 static const char *destdir = "/tmp";
72 static int destdir_fd = -1;
73
74 static void *known_pools = NULL; /* tsearch() of C strings */
75 static void *noauto_files = NULL; /* tsearch() of C strings */
76
77
78 static char *
systemd_escape(const char * input,const char * prepend,const char * append)79 systemd_escape(const char *input, const char *prepend, const char *append)
80 {
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);
85 if (!ret) {
86 fprintf(stderr, PROGNAME "[%d]: "
87 "out of memory to escape \"%s%s%s\"!\n",
88 getpid(), prepend, input, append);
89 return (NULL);
90 }
91
92 memcpy(ret, prepend, prelen);
93 char *out = ret + prelen;
94
95 const char *cur = input;
96 if (*cur == '.') {
97 memcpy(out, "\\x2e", 4);
98 out += 4;
99 ++cur;
100 }
101 for (; *cur; ++cur) {
102 if (*cur == '/')
103 *(out++) = '-';
104 else if (strchr(
105 "0123456789"
106 "abcdefghijklmnopqrstuvwxyz"
107 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
108 ":_.", *cur))
109 *(out++) = *cur;
110 else {
111 sprintf(out, "\\x%02x", (int)*cur);
112 out += 4;
113 }
114 }
115
116 memcpy(out, append, applen + 1);
117 return (ret);
118 }
119
120 static void
simplify_path(char * path)121 simplify_path(char *path)
122 {
123 char *out = path;
124 for (char *cur = path; *cur; ++cur) {
125 if (*cur == '/') {
126 while (*(cur + 1) == '/')
127 ++cur;
128 *(out++) = '/';
129 } else
130 *(out++) = *cur;
131 }
132
133 *(out++) = '\0';
134 }
135
136 static bool
strendswith(const char * what,const char * suff)137 strendswith(const char *what, const char *suff)
138 {
139 size_t what_l = strlen(what);
140 size_t suff_l = strlen(suff);
141
142 return ((what_l >= suff_l) &&
143 (strcmp(what + what_l - suff_l, suff) == 0));
144 }
145
146 /* Assumes already-simplified path, doesn't modify input */
147 static char *
systemd_escape_path(char * input,const char * prepend,const char * append)148 systemd_escape_path(char *input, const char *prepend, const char *append)
149 {
150 if (strcmp(input, "/") == 0) {
151 char *ret;
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);
156 ret = NULL;
157 }
158 return (ret);
159 } else {
160 /*
161 * path_is_normalized() (flattened for absolute paths here),
162 * required for proper escaping
163 */
164 if (strstr(input, "/./") || strstr(input, "/../") ||
165 strendswith(input, "/.") || strendswith(input, "/.."))
166 return (NULL);
167
168
169 if (input[0] == '/')
170 ++input;
171
172 char *back = &input[strlen(input) - 1];
173 bool deslash = *back == '/';
174 if (deslash)
175 *back = '\0';
176
177 char *ret = systemd_escape(input, prepend, append);
178
179 if (deslash)
180 *back = '/';
181 return (ret);
182 }
183 }
184
185 static FILE *
fopenat(int dirfd,const char * pathname,int flags,const char * stream_mode,mode_t mode)186 fopenat(int dirfd, const char *pathname, int flags,
187 const char *stream_mode, mode_t mode)
188 {
189 int fd = openat(dirfd, pathname, flags, mode);
190 if (fd < 0)
191 return (NULL);
192
193 return (fdopen(fd, stream_mode));
194 }
195
196 static int
line_worker(char * line,const char * cachefile)197 line_worker(char *line, const char *cachefile)
198 {
199 int ret = 0;
200 void *tofree_all[8];
201 void **tofree = tofree_all;
202
203 char *toktmp;
204 /* BEGIN CSTYLED */
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) ?: "-";
225 /* END CSTYLED */
226
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);
231
232 if (p_nbmand == NULL) {
233 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
234 getpid(), dataset);
235 goto err;
236 }
237
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;
244 bool noauto = false;
245 bool wantedby_append = true;
246
247 /*
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
256 */
257 if (tfind(pool, &known_pools, STRCMP)) {
258 after = "";
259 wants = "";
260 }
261
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;
270
271
272 if (strcmp(p_encroot, "-") != 0) {
273 char *keyloadunit = *(tofree++) =
274 systemd_escape(p_encroot, "zfs-load-key@", ".service");
275 if (keyloadunit == NULL)
276 goto err;
277
278 if (strcmp(dataset, p_encroot) == 0) {
279 const char *keymountdep = NULL;
280 bool is_prompt = false;
281 bool need_network = false;
282
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';
288 const char *scheme =
289 &p_keyloc[uri_matches[1].rm_so];
290 const char *path =
291 &p_keyloc[uri_matches[2].rm_so];
292
293 if (strcmp(scheme, "https") == 0 ||
294 strcmp(scheme, "http") == 0)
295 need_network = true;
296 else
297 keymountdep = path;
298 } else {
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);
303
304 is_prompt = true;
305 }
306
307
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",
311 0644);
312 if (!keyloadunit_f) {
313 fprintf(stderr, PROGNAME "[%d]: %s: "
314 "couldn't open %s under %s: %s\n",
315 getpid(), dataset, keyloadunit, destdir,
316 strerror(errno));
317 goto err;
318 }
319
320 fprintf(keyloadunit_f,
321 OUTPUT_HEADER
322 "[Unit]\n"
323 "Description=Load ZFS key for %s\n"
324 "SourcePath=" FSLIST "/%s\n"
325 "Documentation=man:zfs-mount-generator(8)\n"
326 "DefaultDependencies=no\n"
327 "Wants=%s\n"
328 "After=%s\n",
329 dataset, cachefile, wants, after);
330
331 if (need_network)
332 fprintf(keyloadunit_f,
333 "Wants=network-online.target\n"
334 "After=network-online.target\n");
335
336 if (p_systemd_requires)
337 fprintf(keyloadunit_f,
338 "Requires=%s\n", p_systemd_requires);
339
340 if (p_systemd_requiresmountsfor)
341 fprintf(keyloadunit_f,
342 "RequiresMountsFor=%s\n",
343 p_systemd_requiresmountsfor);
344 if (keymountdep)
345 fprintf(keyloadunit_f,
346 "RequiresMountsFor='%s'\n", keymountdep);
347
348 /* BEGIN CSTYLED */
349 fprintf(keyloadunit_f,
350 "\n"
351 "[Service]\n"
352 "Type=oneshot\n"
353 "RemainAfterExit=yes\n"
354 "# This avoids a dependency loop involving systemd-journald.socket if this\n"
355 "# dataset is a parent of the root filesystem.\n"
356 "StandardOutput=null\n"
357 "StandardError=null\n"
358 "ExecStart=/bin/sh -euc '"
359 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
360 dataset);
361 if (is_prompt)
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;"
366 "done;"
367 "exit 1",
368 dataset, dataset, dataset);
369 else
370 fprintf(keyloadunit_f,
371 "exec " ZFS " load-key \"%s\"",
372 dataset);
373
374 fprintf(keyloadunit_f,
375 "'\n"
376 "ExecStop=/bin/sh -euc '"
377 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
378 "exec " ZFS " unload-key \"%s\""
379 "'\n",
380 dataset, dataset);
381 /* END CSTYLED */
382
383 (void) fclose(keyloadunit_f);
384 }
385
386 /* Update dependencies for the mount file to want this */
387 bindsto = keyloadunit;
388 if (after[0] == '\0')
389 after = keyloadunit;
390 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
391 after = *(tofree++) = toktmp;
392 else {
393 fprintf(stderr, PROGNAME "[%d]: %s: "
394 "out of memory to generate after=\"%s %s\"!\n",
395 getpid(), dataset, after, keyloadunit);
396 goto err;
397 }
398 }
399
400
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) {
404 /* ok */
405 } else if (strcmp(p_systemd_ignore, "on") == 0)
406 goto end;
407 else {
408 fprintf(stderr, PROGNAME "[%d]: %s: "
409 "invalid org.openzfs.systemd:ignore=%s\n",
410 getpid(), dataset, p_systemd_ignore);
411 goto err;
412 }
413
414 /* Check for canmount */
415 if (strcmp(p_canmount, "on") == 0) {
416 /* ok */
417 } else if (strcmp(p_canmount, "noauto") == 0)
418 noauto = true;
419 else if (strcmp(p_canmount, "off") == 0)
420 goto end;
421 else {
422 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
423 getpid(), dataset, p_canmount);
424 goto err;
425 }
426
427 /* Check for legacy and blank mountpoints */
428 if (strcmp(p_mountpoint, "legacy") == 0 ||
429 strcmp(p_mountpoint, "none") == 0)
430 goto end;
431 else if (p_mountpoint[0] != '/') {
432 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
433 getpid(), dataset, p_mountpoint);
434 goto err;
435 }
436
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) {
441 fprintf(stderr,
442 PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
443 getpid(), dataset, p_mountpoint);
444 goto err;
445 }
446
447
448 /*
449 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
450 *
451 * The longest string achievable here is
452 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
453 */
454 char opts[64] = "";
455
456 /* atime */
457 if (strcmp(p_atime, "on") == 0) {
458 /* relatime */
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");
463 else
464 fprintf(stderr,
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");
469 } else
470 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
471 getpid(), dataset, p_atime);
472
473 /* devices */
474 if (strcmp(p_devices, "on") == 0)
475 strcat(opts, ",dev");
476 else if (strcmp(p_devices, "off") == 0)
477 strcat(opts, ",nodev");
478 else
479 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
480 getpid(), dataset, p_devices);
481
482 /* exec */
483 if (strcmp(p_exec, "on") == 0)
484 strcat(opts, ",exec");
485 else if (strcmp(p_exec, "off") == 0)
486 strcat(opts, ",noexec");
487 else
488 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
489 getpid(), dataset, p_exec);
490
491 /* readonly */
492 if (strcmp(p_readonly, "on") == 0)
493 strcat(opts, ",ro");
494 else if (strcmp(p_readonly, "off") == 0)
495 strcat(opts, ",rw");
496 else
497 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
498 getpid(), dataset, p_readonly);
499
500 /* setuid */
501 if (strcmp(p_setuid, "on") == 0)
502 strcat(opts, ",suid");
503 else if (strcmp(p_setuid, "off") == 0)
504 strcat(opts, ",nosuid");
505 else
506 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
507 getpid(), dataset, p_setuid);
508
509 /* nbmand */
510 if (strcmp(p_nbmand, "on") == 0)
511 strcat(opts, ",mand");
512 else if (strcmp(p_nbmand, "off") == 0)
513 strcat(opts, ",nomand");
514 else
515 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
516 getpid(), dataset, p_setuid);
517
518 if (strcmp(p_systemd_wantedby, "-") != 0) {
519 noauto = true;
520
521 if (strcmp(p_systemd_wantedby, "none") != 0)
522 wantedby = p_systemd_wantedby;
523 }
524
525 if (strcmp(p_systemd_requiredby, "-") != 0) {
526 noauto = true;
527
528 if (strcmp(p_systemd_requiredby, "none") != 0)
529 requiredby = p_systemd_requiredby;
530 }
531
532 /*
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.
538 */
539 if (!noauto) {
540 if (strcmp(p_systemd_nofail, "off") == 0)
541 requiredby = strdupa("local-fs.target");
542 else {
543 wantedby = strdupa("local-fs.target");
544 wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
545 }
546 }
547
548 /*
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.
570 */
571
572 struct stat stbuf;
573 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
574 bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
575
576 *(tofree++) = (void *)mountfile;
577 if (already_exists) {
578 if (is_known) {
579 /* If it's in noauto_files, we must be noauto too */
580
581 /* See 5 */
582 errno = 0;
583 (void) unlinkat(destdir_fd, mountfile, 0);
584
585 /* See 2 */
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));
591 } else {
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);
597 }
598
599 /* File exists: skip current dataset */
600 goto end;
601 } else {
602 if (is_known) {
603 /* See 4 */
604 goto end;
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);
611 else
612 /* mountfile escaped to noauto_files */
613 *(--tofree) = NULL;
614 }
615 }
616
617
618 FILE *mountfile_f = fopenat(destdir_fd, mountfile,
619 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
620 if (!mountfile_f) {
621 fprintf(stderr,
622 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
623 getpid(), dataset, mountfile, destdir, strerror(errno));
624 goto err;
625 }
626
627 fprintf(mountfile_f,
628 OUTPUT_HEADER
629 "[Unit]\n"
630 "SourcePath=" FSLIST "/%s\n"
631 "Documentation=man:zfs-mount-generator(8)\n"
632 "\n"
633 "Before=",
634 cachefile);
635
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 */
639 if (requiredby)
640 fprintf(mountfile_f, " %s", requiredby);
641 if (wantedby && wantedby_append)
642 fprintf(mountfile_f, " %s", wantedby);
643
644 fprintf(mountfile_f,
645 "\n"
646 "After=");
647 if (p_systemd_after)
648 fprintf(mountfile_f, "%s ", p_systemd_after);
649 fprintf(mountfile_f, "%s\n", after);
650
651 fprintf(mountfile_f, "Wants=%s\n", wants);
652
653 if (bindsto)
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)
658 fprintf(mountfile_f,
659 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
660
661 fprintf(mountfile_f,
662 "\n"
663 "[Mount]\n"
664 "Where=%s\n"
665 "What=%s\n"
666 "Type=zfs\n"
667 "Options=defaults%s,zfsutil\n",
668 p_mountpoint, dataset, opts);
669
670 (void) fclose(mountfile_f);
671
672 if (!requiredby && !wantedby)
673 goto end;
674
675 /* Finally, create the appropriate dependencies */
676 char *linktgt;
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);
681 goto err;
682 }
683 *(tofree++) = linktgt;
684
685 struct dep {
686 const char *type;
687 char *list;
688 } deps[] = {
689 {"wants", wantedby},
690 {"requires", requiredby},
691 {}
692 };
693 for (struct dep *dep = deps; dep->type; ++dep) {
694 if (!dep->list)
695 continue;
696
697 for (char *reqby = strtok_r(dep->list, " ", &toktmp);
698 reqby;
699 reqby = strtok_r(NULL, " ", &toktmp)) {
700 char *depdir;
701 if (asprintf(
702 &depdir, "%s.%s", reqby, dep->type) == -1) {
703 fprintf(stderr, PROGNAME "[%d]: %s: "
704 "out of memory for dependent dir name "
705 "\"%s.%s\"!\n",
706 getpid(), dataset, reqby, dep->type);
707 continue;
708 }
709
710 (void) mkdirat(destdir_fd, depdir, 0755);
711 int depdir_fd = openat(destdir_fd, depdir,
712 O_PATH | O_DIRECTORY | O_CLOEXEC);
713 if (depdir_fd < 0) {
714 fprintf(stderr, PROGNAME "[%d]: %s: "
715 "couldn't open %s under %s: %s\n",
716 getpid(), dataset, depdir, destdir,
717 strerror(errno));
718 free(depdir);
719 continue;
720 }
721
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));
728
729 (void) close(depdir_fd);
730 free(depdir);
731 }
732 }
733
734 end:
735 if (tofree >= tofree_all + nitems(tofree_all)) {
736 /*
737 * This won't happen as-is:
738 * we've got 8 slots and allocate 5 things at most.
739 */
740 fprintf(stderr,
741 PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
742 getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
743 ret = tofree - tofree_all;
744 }
745
746 while (tofree-- != tofree_all)
747 free(*tofree);
748 return (ret);
749 err:
750 ret = 1;
751 goto end;
752 }
753
754
755 static int
pool_enumerator(zpool_handle_t * pool,void * data)756 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
757 {
758 int ret = 0;
759
760 /*
761 * Pools are guaranteed-unique by the kernel,
762 * no risk of leaking dupes here
763 */
764 char *name = strdup(zpool_get_name(pool));
765 if (!name || !tsearch(name, &known_pools, STRCMP)) {
766 free(name);
767 ret = ENOMEM;
768 }
769
770 zpool_close(pool);
771 return (ret);
772 }
773
774 int
main(int argc,char ** argv)775 main(int argc, char **argv)
776 {
777 struct timespec time_init = {};
778 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
779
780 {
781 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
782 if (kmfd >= 0) {
783 (void) dup2(kmfd, STDERR_FILENO);
784 (void) close(kmfd);
785
786 setlinebuf(stderr);
787 }
788 }
789
790 switch (argc) {
791 case 1:
792 /* Use default */
793 break;
794 case 2:
795 case 4:
796 destdir = argv[1];
797 break;
798 default:
799 fprintf(stderr,
800 PROGNAME "[%d]: wrong argument count: %d\n",
801 getpid(), argc - 1);
802 _exit(1);
803 }
804
805 {
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));
811 _exit(1);
812 }
813 }
814
815 DIR *fslist_dir = opendir(FSLIST);
816 if (!fslist_dir) {
817 if (errno != ENOENT)
818 fprintf(stderr,
819 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
820 getpid(), strerror(errno));
821 _exit(0);
822 }
823
824 {
825 libzfs_handle_t *libzfs = libzfs_init();
826 if (libzfs) {
827 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
828 fprintf(stderr, PROGNAME "[%d]: "
829 "error listing pools, ignoring\n",
830 getpid());
831 libzfs_fini(libzfs);
832 } else
833 fprintf(stderr, PROGNAME "[%d]: "
834 "couldn't start libzfs, ignoring\n",
835 getpid());
836 }
837
838 {
839 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
840 if (regerr != 0) {
841 fprintf(stderr,
842 PROGNAME "[%d]: invalid regex: %d\n",
843 getpid(), regerr);
844 _exit(1);
845 }
846 }
847
848 bool debug = false;
849 char *line = NULL;
850 size_t linelen = 0;
851 {
852 const char *dbgenv = getenv("ZFS_DEBUG");
853 if (dbgenv)
854 debug = atoi(dbgenv);
855 else {
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);
861 }
862 }
863
864 if (debug && !isatty(STDOUT_FILENO))
865 dup2(STDERR_FILENO, STDOUT_FILENO);
866 }
867
868 struct timespec time_start = {};
869 if (debug)
870 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
871
872 struct line {
873 char *line;
874 const char *fname;
875 struct line *next;
876 } *lines_canmount_not_on = NULL;
877
878 int ret = 0;
879 struct dirent *cachent;
880 while ((cachent = readdir(fslist_dir)) != NULL) {
881 if (strcmp(cachent->d_name, ".") == 0 ||
882 strcmp(cachent->d_name, "..") == 0)
883 continue;
884
885 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
886 O_RDONLY | O_CLOEXEC, "r", 0);
887 if (!cachefile) {
888 fprintf(stderr, PROGNAME "[%d]: "
889 "couldn't open %s under " FSLIST ": %s\n",
890 getpid(), cachent->d_name, strerror(errno));
891 continue;
892 }
893
894 const char *filename = FREE_STATICS ? "(elided)" : NULL;
895
896 ssize_t read;
897 while ((read = getline(&line, &linelen, cachefile)) >= 0) {
898 line[read - 1] = '\0'; /* newline */
899
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;
906
907 if (canmount_on)
908 ret |= line_worker(line, cachent->d_name);
909 else {
910 if (filename == NULL)
911 filename =
912 strdup(cachent->d_name) ?: "(?)";
913
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);
920 free(l);
921 free(nl);
922 continue;
923 }
924 l->line = nl;
925 l->fname = filename;
926 l->next = lines_canmount_not_on;
927 lines_canmount_not_on = l;
928 }
929 }
930
931 fclose(cachefile);
932 }
933 free(line);
934
935 while (lines_canmount_not_on) {
936 struct line *l = lines_canmount_not_on;
937 lines_canmount_not_on = l->next;
938
939 ret |= line_worker(l->line, l->fname);
940 if (FREE_STATICS) {
941 free(l->line);
942 free(l);
943 }
944 }
945
946 if (debug) {
947 struct timespec time_end = {};
948 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
949
950 struct rusage usage;
951 getrusage(RUSAGE_SELF, &usage);
952 printf(
953 "\n"
954 PROGNAME ": "
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);
961
962 if (time_start.tv_nsec > time_end.tv_nsec) {
963 time_end.tv_nsec =
964 1000000000 + time_end.tv_nsec - time_start.tv_nsec;
965 time_end.tv_sec -= 1;
966 } else
967 time_end.tv_nsec -= time_start.tv_nsec;
968 time_end.tv_sec -= time_start.tv_sec;
969
970 if (time_init.tv_nsec > time_start.tv_nsec) {
971 time_start.tv_nsec =
972 1000000000 + time_start.tv_nsec - time_init.tv_nsec;
973 time_start.tv_sec -= 1;
974 } else
975 time_start.tv_nsec -= time_init.tv_nsec;
976 time_start.tv_sec -= time_init.tv_sec;
977
978 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
979 time_init.tv_sec =
980 time_start.tv_sec + time_end.tv_sec +
981 time_init.tv_nsec / 1000000000;
982 time_init.tv_nsec %= 1000000000;
983
984 printf(PROGNAME ": "
985 "total=%llu.%09llus = "
986 "init=%llu.%09llus + real=%llu.%09llus\n",
987 (unsigned long long) time_init.tv_sec,
988 (unsigned long long) time_init.tv_nsec,
989 (unsigned long long) time_start.tv_sec,
990 (unsigned long long) time_start.tv_nsec,
991 (unsigned long long) time_end.tv_sec,
992 (unsigned long long) time_end.tv_nsec);
993
994 fflush(stdout);
995 }
996
997 if (FREE_STATICS) {
998 closedir(fslist_dir);
999 tdestroy(noauto_files, free);
1000 tdestroy(known_pools, free);
1001 regfree(&uri_regex);
1002 }
1003 _exit(ret);
1004 }
1005