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 const char *toktmp2;
206 /* BEGIN CSTYLED */
207 const char *dataset = strtok_r(line, "\t", &toktmp);
208 char *p_mountpoint = strtok_r(NULL, "\t", &toktmp);
209 const char *p_canmount = strtok_r(NULL, "\t", &toktmp);
210 const char *p_atime = strtok_r(NULL, "\t", &toktmp);
211 const char *p_relatime = strtok_r(NULL, "\t", &toktmp);
212 const char *p_devices = strtok_r(NULL, "\t", &toktmp);
213 const char *p_exec = strtok_r(NULL, "\t", &toktmp);
214 const char *p_readonly = strtok_r(NULL, "\t", &toktmp);
215 const char *p_setuid = strtok_r(NULL, "\t", &toktmp);
216 const char *p_nbmand = strtok_r(NULL, "\t", &toktmp);
217 const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-";
218 char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
219 const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-";
220 const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
221 const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-";
222 const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-";
223 char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
224 char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
225 const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-";
226 const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-";
227 /* END CSTYLED */
228
229 size_t pool_len = strlen(dataset);
230 if ((toktmp2 = strchr(dataset, '/')) != NULL)
231 pool_len = toktmp2 - dataset;
232 const char *pool = *(tofree++) = strndup(dataset, pool_len);
233
234 if (p_nbmand == NULL) {
235 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
236 getpid(), dataset);
237 goto err;
238 }
239
240 /* Minimal pre-requisites to mount a ZFS dataset */
241 const char *after = "zfs-import.target";
242 const char *wants = "zfs-import.target";
243 const char *bindsto = NULL;
244 char *wantedby = NULL;
245 char *requiredby = NULL;
246 bool noauto = false;
247 bool wantedby_append = true;
248
249 /*
250 * zfs-import.target is not needed if the pool is already imported.
251 * This avoids a dependency loop on root-on-ZFS systems:
252 * systemd-random-seed.service After (via RequiresMountsFor)
253 * var-lib.mount After
254 * zfs-import.target After
255 * zfs-import-{cache,scan}.service After
256 * cryptsetup.service After
257 * systemd-random-seed.service
258 */
259 if (tfind(pool, &known_pools, STRCMP)) {
260 after = "";
261 wants = "";
262 }
263
264 if (strcmp(p_systemd_after, "-") == 0)
265 p_systemd_after = NULL;
266 if (strcmp(p_systemd_before, "-") == 0)
267 p_systemd_before = NULL;
268 if (strcmp(p_systemd_requires, "-") == 0)
269 p_systemd_requires = NULL;
270 if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
271 p_systemd_requiresmountsfor = NULL;
272
273
274 if (strcmp(p_encroot, "-") != 0) {
275 char *keyloadunit = *(tofree++) =
276 systemd_escape(p_encroot, "zfs-load-key@", ".service");
277 if (keyloadunit == NULL)
278 goto err;
279
280 if (strcmp(dataset, p_encroot) == 0) {
281 const char *keymountdep = NULL;
282 bool is_prompt = false;
283 bool need_network = false;
284
285 regmatch_t uri_matches[3];
286 if (regexec(&uri_regex, p_keyloc,
287 nitems(uri_matches), uri_matches, 0) == 0) {
288 p_keyloc[uri_matches[1].rm_eo] = '\0';
289 p_keyloc[uri_matches[2].rm_eo] = '\0';
290 const char *scheme =
291 &p_keyloc[uri_matches[1].rm_so];
292 const char *path =
293 &p_keyloc[uri_matches[2].rm_so];
294
295 if (strcmp(scheme, "https") == 0 ||
296 strcmp(scheme, "http") == 0)
297 need_network = true;
298 else
299 keymountdep = path;
300 } else {
301 if (strcmp(p_keyloc, "prompt") != 0)
302 fprintf(stderr, PROGNAME "[%d]: %s: "
303 "unknown non-URI keylocation=%s\n",
304 getpid(), dataset, p_keyloc);
305
306 is_prompt = true;
307 }
308
309
310 /* Generate the key-load .service unit */
311 FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
312 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
313 0644);
314 if (!keyloadunit_f) {
315 fprintf(stderr, PROGNAME "[%d]: %s: "
316 "couldn't open %s under %s: %s\n",
317 getpid(), dataset, keyloadunit, destdir,
318 strerror(errno));
319 goto err;
320 }
321
322 fprintf(keyloadunit_f,
323 OUTPUT_HEADER
324 "[Unit]\n"
325 "Description=Load ZFS key for %s\n"
326 "SourcePath=" FSLIST "/%s\n"
327 "Documentation=man:zfs-mount-generator(8)\n"
328 "DefaultDependencies=no\n"
329 "Wants=%s\n"
330 "After=%s\n",
331 dataset, cachefile, wants, after);
332
333 if (need_network)
334 fprintf(keyloadunit_f,
335 "Wants=network-online.target\n"
336 "After=network-online.target\n");
337
338 if (p_systemd_requires)
339 fprintf(keyloadunit_f,
340 "Requires=%s\n", p_systemd_requires);
341
342 if (p_systemd_requiresmountsfor)
343 fprintf(keyloadunit_f,
344 "RequiresMountsFor=%s\n",
345 p_systemd_requiresmountsfor);
346 if (keymountdep)
347 fprintf(keyloadunit_f,
348 "RequiresMountsFor='%s'\n", keymountdep);
349
350 /* BEGIN CSTYLED */
351 fprintf(keyloadunit_f,
352 "\n"
353 "[Service]\n"
354 "Type=oneshot\n"
355 "RemainAfterExit=yes\n"
356 "# This avoids a dependency loop involving systemd-journald.socket if this\n"
357 "# dataset is a parent of the root filesystem.\n"
358 "StandardOutput=null\n"
359 "StandardError=null\n"
360 "ExecStart=/bin/sh -euc '"
361 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
362 dataset);
363 if (is_prompt)
364 fprintf(keyloadunit_f,
365 "for i in 1 2 3; do "
366 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
367 "" ZFS " load-key \"%s\" && exit 0;"
368 "done;"
369 "exit 1",
370 dataset, dataset, dataset);
371 else
372 fprintf(keyloadunit_f,
373 "exec " ZFS " load-key \"%s\"",
374 dataset);
375
376 fprintf(keyloadunit_f,
377 "'\n"
378 "ExecStop=/bin/sh -euc '"
379 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
380 "exec " ZFS " unload-key \"%s\""
381 "'\n",
382 dataset, dataset);
383 /* END CSTYLED */
384
385 (void) fclose(keyloadunit_f);
386 }
387
388 /* Update dependencies for the mount file to want this */
389 bindsto = keyloadunit;
390 if (after[0] == '\0')
391 after = keyloadunit;
392 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
393 after = *(tofree++) = toktmp;
394 else {
395 fprintf(stderr, PROGNAME "[%d]: %s: "
396 "out of memory to generate after=\"%s %s\"!\n",
397 getpid(), dataset, after, keyloadunit);
398 goto err;
399 }
400 }
401
402
403 /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
404 if (strcmp(p_systemd_ignore, "-") == 0 ||
405 strcmp(p_systemd_ignore, "off") == 0) {
406 /* ok */
407 } else if (strcmp(p_systemd_ignore, "on") == 0)
408 goto end;
409 else {
410 fprintf(stderr, PROGNAME "[%d]: %s: "
411 "invalid org.openzfs.systemd:ignore=%s\n",
412 getpid(), dataset, p_systemd_ignore);
413 goto err;
414 }
415
416 /* Check for canmount */
417 if (strcmp(p_canmount, "on") == 0) {
418 /* ok */
419 } else if (strcmp(p_canmount, "noauto") == 0)
420 noauto = true;
421 else if (strcmp(p_canmount, "off") == 0)
422 goto end;
423 else {
424 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
425 getpid(), dataset, p_canmount);
426 goto err;
427 }
428
429 /* Check for legacy and blank mountpoints */
430 if (strcmp(p_mountpoint, "legacy") == 0 ||
431 strcmp(p_mountpoint, "none") == 0)
432 goto end;
433 else if (p_mountpoint[0] != '/') {
434 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
435 getpid(), dataset, p_mountpoint);
436 goto err;
437 }
438
439 /* Escape the mountpoint per systemd policy */
440 simplify_path(p_mountpoint);
441 const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
442 if (mountfile == NULL) {
443 fprintf(stderr,
444 PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
445 getpid(), dataset, p_mountpoint);
446 goto err;
447 }
448
449
450 /*
451 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
452 *
453 * The longest string achievable here is
454 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
455 */
456 char opts[64] = "";
457
458 /* atime */
459 if (strcmp(p_atime, "on") == 0) {
460 /* relatime */
461 if (strcmp(p_relatime, "on") == 0)
462 strcat(opts, ",atime,relatime");
463 else if (strcmp(p_relatime, "off") == 0)
464 strcat(opts, ",atime,strictatime");
465 else
466 fprintf(stderr,
467 PROGNAME "[%d]: %s: invalid relatime=%s\n",
468 getpid(), dataset, p_relatime);
469 } else if (strcmp(p_atime, "off") == 0) {
470 strcat(opts, ",noatime");
471 } else
472 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
473 getpid(), dataset, p_atime);
474
475 /* devices */
476 if (strcmp(p_devices, "on") == 0)
477 strcat(opts, ",dev");
478 else if (strcmp(p_devices, "off") == 0)
479 strcat(opts, ",nodev");
480 else
481 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
482 getpid(), dataset, p_devices);
483
484 /* exec */
485 if (strcmp(p_exec, "on") == 0)
486 strcat(opts, ",exec");
487 else if (strcmp(p_exec, "off") == 0)
488 strcat(opts, ",noexec");
489 else
490 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
491 getpid(), dataset, p_exec);
492
493 /* readonly */
494 if (strcmp(p_readonly, "on") == 0)
495 strcat(opts, ",ro");
496 else if (strcmp(p_readonly, "off") == 0)
497 strcat(opts, ",rw");
498 else
499 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
500 getpid(), dataset, p_readonly);
501
502 /* setuid */
503 if (strcmp(p_setuid, "on") == 0)
504 strcat(opts, ",suid");
505 else if (strcmp(p_setuid, "off") == 0)
506 strcat(opts, ",nosuid");
507 else
508 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
509 getpid(), dataset, p_setuid);
510
511 /* nbmand */
512 if (strcmp(p_nbmand, "on") == 0)
513 strcat(opts, ",mand");
514 else if (strcmp(p_nbmand, "off") == 0)
515 strcat(opts, ",nomand");
516 else
517 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
518 getpid(), dataset, p_setuid);
519
520 if (strcmp(p_systemd_wantedby, "-") != 0) {
521 noauto = true;
522
523 if (strcmp(p_systemd_wantedby, "none") != 0)
524 wantedby = p_systemd_wantedby;
525 }
526
527 if (strcmp(p_systemd_requiredby, "-") != 0) {
528 noauto = true;
529
530 if (strcmp(p_systemd_requiredby, "none") != 0)
531 requiredby = p_systemd_requiredby;
532 }
533
534 /*
535 * For datasets with canmount=on, a dependency is created for
536 * local-fs.target by default. To avoid regressions, this dependency
537 * is reduced to "wants" rather than "requires" when nofail!=off.
538 * **THIS MAY CHANGE**
539 * noauto=on disables this behavior completely.
540 */
541 if (!noauto) {
542 if (strcmp(p_systemd_nofail, "off") == 0)
543 requiredby = strdupa("local-fs.target");
544 else {
545 wantedby = strdupa("local-fs.target");
546 wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
547 }
548 }
549
550 /*
551 * Handle existing files:
552 * 1. We never overwrite existing files, although we may delete
553 * files if we're sure they were created by us. (see 5.)
554 * 2. We handle files differently based on canmount.
555 * Units with canmount=on always have precedence over noauto.
556 * This is enforced by processing these units before all others.
557 * It is important to use p_canmount and not noauto here,
558 * since we categorise by canmount while other properties,
559 * e.g. org.openzfs.systemd:wanted-by, also modify noauto.
560 * 3. If no unit file exists for a noauto dataset, we create one.
561 * Additionally, we use noauto_files to track the unit file names
562 * (which are the systemd-escaped mountpoints) of all (exclusively)
563 * noauto datasets that had a file created.
564 * 4. If the file to be created is found in the tracking tree,
565 * we do NOT create it.
566 * 5. If a file exists for a noauto dataset,
567 * we check whether the file name is in the array.
568 * If it is, we have multiple noauto datasets for the same
569 * mountpoint. In such cases, we remove the file for safety.
570 * We leave the file name in the tracking array to avoid
571 * further noauto datasets creating a file for this path again.
572 */
573
574 struct stat stbuf;
575 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
576 bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
577
578 *(tofree++) = (void *)mountfile;
579 if (already_exists) {
580 if (is_known) {
581 /* If it's in noauto_files, we must be noauto too */
582
583 /* See 5 */
584 errno = 0;
585 (void) unlinkat(destdir_fd, mountfile, 0);
586
587 /* See 2 */
588 fprintf(stderr, PROGNAME "[%d]: %s: "
589 "removing duplicate noauto unit %s%s%s\n",
590 getpid(), dataset, mountfile,
591 errno ? "" : " failed: ",
592 errno ? "" : strerror(errno));
593 } else {
594 /* Don't log for canmount=noauto */
595 if (strcmp(p_canmount, "on") == 0)
596 fprintf(stderr, PROGNAME "[%d]: %s: "
597 "%s already exists. Skipping.\n",
598 getpid(), dataset, mountfile);
599 }
600
601 /* File exists: skip current dataset */
602 goto end;
603 } else {
604 if (is_known) {
605 /* See 4 */
606 goto end;
607 } else if (strcmp(p_canmount, "noauto") == 0) {
608 if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
609 fprintf(stderr, PROGNAME "[%d]: %s: "
610 "out of memory for noauto datasets! "
611 "Not tracking %s.\n",
612 getpid(), dataset, mountfile);
613 else
614 /* mountfile escaped to noauto_files */
615 *(--tofree) = NULL;
616 }
617 }
618
619
620 FILE *mountfile_f = fopenat(destdir_fd, mountfile,
621 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
622 if (!mountfile_f) {
623 fprintf(stderr,
624 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
625 getpid(), dataset, mountfile, destdir, strerror(errno));
626 goto err;
627 }
628
629 fprintf(mountfile_f,
630 OUTPUT_HEADER
631 "[Unit]\n"
632 "SourcePath=" FSLIST "/%s\n"
633 "Documentation=man:zfs-mount-generator(8)\n"
634 "\n"
635 "Before=",
636 cachefile);
637
638 if (p_systemd_before)
639 fprintf(mountfile_f, "%s ", p_systemd_before);
640 fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
641 if (requiredby)
642 fprintf(mountfile_f, " %s", requiredby);
643 if (wantedby && wantedby_append)
644 fprintf(mountfile_f, " %s", wantedby);
645
646 fprintf(mountfile_f,
647 "\n"
648 "After=");
649 if (p_systemd_after)
650 fprintf(mountfile_f, "%s ", p_systemd_after);
651 fprintf(mountfile_f, "%s\n", after);
652
653 fprintf(mountfile_f, "Wants=%s\n", wants);
654
655 if (bindsto)
656 fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
657 if (p_systemd_requires)
658 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
659 if (p_systemd_requiresmountsfor)
660 fprintf(mountfile_f,
661 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
662
663 fprintf(mountfile_f,
664 "\n"
665 "[Mount]\n"
666 "Where=%s\n"
667 "What=%s\n"
668 "Type=zfs\n"
669 "Options=defaults%s,zfsutil\n",
670 p_mountpoint, dataset, opts);
671
672 (void) fclose(mountfile_f);
673
674 if (!requiredby && !wantedby)
675 goto end;
676
677 /* Finally, create the appropriate dependencies */
678 char *linktgt;
679 if (asprintf(&linktgt, "../%s", mountfile) == -1) {
680 fprintf(stderr, PROGNAME "[%d]: %s: "
681 "out of memory for dependents of %s!\n",
682 getpid(), dataset, mountfile);
683 goto err;
684 }
685 *(tofree++) = linktgt;
686
687 struct dep {
688 const char *type;
689 char *list;
690 } deps[] = {
691 {"wants", wantedby},
692 {"requires", requiredby},
693 {}
694 };
695 for (struct dep *dep = deps; dep->type; ++dep) {
696 if (!dep->list)
697 continue;
698
699 for (char *reqby = strtok_r(dep->list, " ", &toktmp);
700 reqby;
701 reqby = strtok_r(NULL, " ", &toktmp)) {
702 char *depdir;
703 if (asprintf(
704 &depdir, "%s.%s", reqby, dep->type) == -1) {
705 fprintf(stderr, PROGNAME "[%d]: %s: "
706 "out of memory for dependent dir name "
707 "\"%s.%s\"!\n",
708 getpid(), dataset, reqby, dep->type);
709 continue;
710 }
711
712 (void) mkdirat(destdir_fd, depdir, 0755);
713 int depdir_fd = openat(destdir_fd, depdir,
714 O_PATH | O_DIRECTORY | O_CLOEXEC);
715 if (depdir_fd < 0) {
716 fprintf(stderr, PROGNAME "[%d]: %s: "
717 "couldn't open %s under %s: %s\n",
718 getpid(), dataset, depdir, destdir,
719 strerror(errno));
720 free(depdir);
721 continue;
722 }
723
724 if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
725 fprintf(stderr, PROGNAME "[%d]: %s: "
726 "couldn't symlink at "
727 "%s under %s under %s: %s\n",
728 getpid(), dataset, mountfile,
729 depdir, destdir, strerror(errno));
730
731 (void) close(depdir_fd);
732 free(depdir);
733 }
734 }
735
736 end:
737 if (tofree >= tofree_all + nitems(tofree_all)) {
738 /*
739 * This won't happen as-is:
740 * we've got 8 slots and allocate 5 things at most.
741 */
742 fprintf(stderr,
743 PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
744 getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
745 ret = tofree - tofree_all;
746 }
747
748 while (tofree-- != tofree_all)
749 free(*tofree);
750 return (ret);
751 err:
752 ret = 1;
753 goto end;
754 }
755
756
757 static int
pool_enumerator(zpool_handle_t * pool,void * data)758 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
759 {
760 int ret = 0;
761
762 /*
763 * Pools are guaranteed-unique by the kernel,
764 * no risk of leaking dupes here
765 */
766 char *name = strdup(zpool_get_name(pool));
767 if (!name || !tsearch(name, &known_pools, STRCMP)) {
768 free(name);
769 ret = ENOMEM;
770 }
771
772 zpool_close(pool);
773 return (ret);
774 }
775
776 int
main(int argc,char ** argv)777 main(int argc, char **argv)
778 {
779 struct timespec time_init = {};
780 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
781
782 {
783 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
784 if (kmfd >= 0) {
785 (void) dup2(kmfd, STDERR_FILENO);
786 (void) close(kmfd);
787
788 setlinebuf(stderr);
789 }
790 }
791
792 switch (argc) {
793 case 1:
794 /* Use default */
795 break;
796 case 2:
797 case 4:
798 destdir = argv[1];
799 break;
800 default:
801 fprintf(stderr,
802 PROGNAME "[%d]: wrong argument count: %d\n",
803 getpid(), argc - 1);
804 _exit(1);
805 }
806
807 {
808 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
809 if (destdir_fd < 0) {
810 fprintf(stderr, PROGNAME "[%d]: "
811 "can't open destination directory %s: %s\n",
812 getpid(), destdir, strerror(errno));
813 _exit(1);
814 }
815 }
816
817 DIR *fslist_dir = opendir(FSLIST);
818 if (!fslist_dir) {
819 if (errno != ENOENT)
820 fprintf(stderr,
821 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
822 getpid(), strerror(errno));
823 _exit(0);
824 }
825
826 {
827 libzfs_handle_t *libzfs = libzfs_init();
828 if (libzfs) {
829 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
830 fprintf(stderr, PROGNAME "[%d]: "
831 "error listing pools, ignoring\n",
832 getpid());
833 libzfs_fini(libzfs);
834 } else
835 fprintf(stderr, PROGNAME "[%d]: "
836 "couldn't start libzfs, ignoring\n",
837 getpid());
838 }
839
840 {
841 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
842 if (regerr != 0) {
843 fprintf(stderr,
844 PROGNAME "[%d]: invalid regex: %d\n",
845 getpid(), regerr);
846 _exit(1);
847 }
848 }
849
850 bool debug = false;
851 char *line = NULL;
852 size_t linelen = 0;
853 {
854 const char *dbgenv = getenv("ZFS_DEBUG");
855 if (dbgenv)
856 debug = atoi(dbgenv);
857 else {
858 FILE *cmdline = fopen("/proc/cmdline", "re");
859 if (cmdline != NULL) {
860 if (getline(&line, &linelen, cmdline) >= 0)
861 debug = strstr(line, "debug");
862 (void) fclose(cmdline);
863 }
864 }
865
866 if (debug && !isatty(STDOUT_FILENO))
867 dup2(STDERR_FILENO, STDOUT_FILENO);
868 }
869
870 struct timespec time_start = {};
871 if (debug)
872 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
873
874 struct line {
875 char *line;
876 const char *fname;
877 struct line *next;
878 } *lines_canmount_not_on = NULL;
879
880 int ret = 0;
881 struct dirent *cachent;
882 while ((cachent = readdir(fslist_dir)) != NULL) {
883 if (strcmp(cachent->d_name, ".") == 0 ||
884 strcmp(cachent->d_name, "..") == 0)
885 continue;
886
887 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
888 O_RDONLY | O_CLOEXEC, "r", 0);
889 if (!cachefile) {
890 fprintf(stderr, PROGNAME "[%d]: "
891 "couldn't open %s under " FSLIST ": %s\n",
892 getpid(), cachent->d_name, strerror(errno));
893 continue;
894 }
895
896 const char *filename = FREE_STATICS ? "(elided)" : NULL;
897
898 ssize_t read;
899 while ((read = getline(&line, &linelen, cachefile)) >= 0) {
900 line[read - 1] = '\0'; /* newline */
901
902 char *canmount = line;
903 canmount += strcspn(canmount, "\t");
904 canmount += strspn(canmount, "\t");
905 canmount += strcspn(canmount, "\t");
906 canmount += strspn(canmount, "\t");
907 bool canmount_on = strncmp(canmount, "on", 2) == 0;
908
909 if (canmount_on)
910 ret |= line_worker(line, cachent->d_name);
911 else {
912 if (filename == NULL)
913 filename =
914 strdup(cachent->d_name) ?: "(?)";
915
916 struct line *l = calloc(1, sizeof (*l));
917 char *nl = strdup(line);
918 if (l == NULL || nl == NULL) {
919 fprintf(stderr, PROGNAME "[%d]: "
920 "out of memory for \"%s\" in %s\n",
921 getpid(), line, cachent->d_name);
922 free(l);
923 free(nl);
924 continue;
925 }
926 l->line = nl;
927 l->fname = filename;
928 l->next = lines_canmount_not_on;
929 lines_canmount_not_on = l;
930 }
931 }
932
933 fclose(cachefile);
934 }
935 free(line);
936
937 while (lines_canmount_not_on) {
938 struct line *l = lines_canmount_not_on;
939 lines_canmount_not_on = l->next;
940
941 ret |= line_worker(l->line, l->fname);
942 if (FREE_STATICS) {
943 free(l->line);
944 free(l);
945 }
946 }
947
948 if (debug) {
949 struct timespec time_end = {};
950 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
951
952 struct rusage usage;
953 getrusage(RUSAGE_SELF, &usage);
954 printf(
955 "\n"
956 PROGNAME ": "
957 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
958 (unsigned long long) usage.ru_utime.tv_sec,
959 (unsigned int) usage.ru_utime.tv_usec,
960 (unsigned long long) usage.ru_stime.tv_sec,
961 (unsigned int) usage.ru_stime.tv_usec,
962 usage.ru_maxrss * 1024);
963
964 if (time_start.tv_nsec > time_end.tv_nsec) {
965 time_end.tv_nsec =
966 1000000000 + time_end.tv_nsec - time_start.tv_nsec;
967 time_end.tv_sec -= 1;
968 } else
969 time_end.tv_nsec -= time_start.tv_nsec;
970 time_end.tv_sec -= time_start.tv_sec;
971
972 if (time_init.tv_nsec > time_start.tv_nsec) {
973 time_start.tv_nsec =
974 1000000000 + time_start.tv_nsec - time_init.tv_nsec;
975 time_start.tv_sec -= 1;
976 } else
977 time_start.tv_nsec -= time_init.tv_nsec;
978 time_start.tv_sec -= time_init.tv_sec;
979
980 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
981 time_init.tv_sec =
982 time_start.tv_sec + time_end.tv_sec +
983 time_init.tv_nsec / 1000000000;
984 time_init.tv_nsec %= 1000000000;
985
986 printf(PROGNAME ": "
987 "total=%llu.%09llus = "
988 "init=%llu.%09llus + real=%llu.%09llus\n",
989 (unsigned long long) time_init.tv_sec,
990 (unsigned long long) time_init.tv_nsec,
991 (unsigned long long) time_start.tv_sec,
992 (unsigned long long) time_start.tv_nsec,
993 (unsigned long long) time_end.tv_sec,
994 (unsigned long long) time_end.tv_nsec);
995
996 fflush(stdout);
997 }
998
999 if (FREE_STATICS) {
1000 closedir(fslist_dir);
1001 tdestroy(noauto_files, free);
1002 tdestroy(known_pools, free);
1003 regfree(&uri_regex);
1004 }
1005 _exit(ret);
1006 }
1007