1 /*
2 * Copyright (c) 2017 Antonio Russo <[email protected]>
3 * Copyright (c) 2020 InsanePrawn <[email protected]>
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 <sys/wait.h>
31 #include <sys/mman.h>
32 #include <semaphore.h>
33 #include <stdbool.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <stdio.h>
37 #include <time.h>
38 #include <regex.h>
39 #include <search.h>
40 #include <dirent.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <limits.h>
44 #include <errno.h>
45 #include <libzfs.h>
46
47 #define STRCMP ((int(*)(const void *, const void *))&strcmp)
48 #define PID_T_CMP ((int(*)(const void *, const void *))&pid_t_cmp)
49
50 static int
pid_t_cmp(const pid_t * lhs,const pid_t * rhs)51 pid_t_cmp(const pid_t *lhs, const pid_t *rhs)
52 {
53 /*
54 * This is always valid, quoth sys_types.h(7posix):
55 * > blksize_t, pid_t, and ssize_t shall be signed integer types.
56 */
57 return (*lhs - *rhs);
58 }
59
60 #define EXIT_ENOMEM() \
61 do { \
62 fprintf(stderr, PROGNAME "[%d]: " \
63 "not enough memory (L%d)!\n", getpid(), __LINE__); \
64 _exit(1); \
65 } while (0)
66
67
68 #define PROGNAME "zfs-mount-generator"
69 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
70 #define ZFS SBINDIR "/zfs"
71
72 #define OUTPUT_HEADER \
73 "# Automatically generated by " PROGNAME "\n" \
74 "\n"
75
76 /*
77 * Starts like the one in libzfs_util.c but also matches "//"
78 * and captures until the end, since we actually use it for path extraxion
79 */
80 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
81 static regex_t uri_regex;
82
83 static char *argv0;
84
85 static const char *destdir = "/tmp";
86 static int destdir_fd = -1;
87
88 static void *known_pools = NULL; /* tsearch() of C strings */
89 static struct {
90 sem_t noauto_not_on_sem;
91
92 sem_t noauto_names_sem;
93 size_t noauto_names_len;
94 size_t noauto_names_max;
95 char noauto_names[][NAME_MAX];
96 } *noauto_files;
97
98
99 static char *
systemd_escape(const char * input,const char * prepend,const char * append)100 systemd_escape(const char *input, const char *prepend, const char *append)
101 {
102 size_t len = strlen(input);
103 size_t applen = strlen(append);
104 size_t prelen = strlen(prepend);
105 char *ret = malloc(4 * len + prelen + applen + 1);
106 if (!ret)
107 EXIT_ENOMEM();
108
109 memcpy(ret, prepend, prelen);
110 char *out = ret + prelen;
111
112 const char *cur = input;
113 if (*cur == '.') {
114 memcpy(out, "\\x2e", 4);
115 out += 4;
116 ++cur;
117 }
118 for (; *cur; ++cur) {
119 if (*cur == '/')
120 *(out++) = '-';
121 else if (strchr(
122 "0123456789"
123 "abcdefghijklmnopqrstuvwxyz"
124 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
125 ":_.", *cur))
126 *(out++) = *cur;
127 else {
128 sprintf(out, "\\x%02x", (int)*cur);
129 out += 4;
130 }
131 }
132
133 memcpy(out, append, applen + 1);
134 return (ret);
135 }
136
137 static void
simplify_path(char * path)138 simplify_path(char *path)
139 {
140 char *out = path;
141 for (char *cur = path; *cur; ++cur) {
142 if (*cur == '/') {
143 while (*(cur + 1) == '/')
144 ++cur;
145 *(out++) = '/';
146 } else
147 *(out++) = *cur;
148 }
149
150 *(out++) = '\0';
151 }
152
153 static bool
strendswith(const char * what,const char * suff)154 strendswith(const char *what, const char *suff)
155 {
156 size_t what_l = strlen(what);
157 size_t suff_l = strlen(suff);
158
159 return ((what_l >= suff_l) &&
160 (strcmp(what + what_l - suff_l, suff) == 0));
161 }
162
163 /* Assumes already-simplified path, doesn't modify input */
164 static char *
systemd_escape_path(char * input,const char * prepend,const char * append)165 systemd_escape_path(char *input, const char *prepend, const char *append)
166 {
167 if (strcmp(input, "/") == 0) {
168 char *ret;
169 if (asprintf(&ret, "%s-%s", prepend, append) == -1)
170 EXIT_ENOMEM();
171 return (ret);
172 } else {
173 /*
174 * path_is_normalized() (flattened for absolute paths here),
175 * required for proper escaping
176 */
177 if (strstr(input, "/./") || strstr(input, "/../") ||
178 strendswith(input, "/.") || strendswith(input, "/.."))
179 return (NULL);
180
181
182 if (input[0] == '/')
183 ++input;
184
185 char *back = &input[strlen(input) - 1];
186 bool deslash = *back == '/';
187 if (deslash)
188 *back = '\0';
189
190 char *ret = systemd_escape(input, prepend, append);
191
192 if (deslash)
193 *back = '/';
194 return (ret);
195 }
196 }
197
198 static FILE *
fopenat(int dirfd,const char * pathname,int flags,const char * stream_mode,mode_t mode)199 fopenat(int dirfd, const char *pathname, int flags,
200 const char *stream_mode, mode_t mode)
201 {
202 int fd = openat(dirfd, pathname, flags, mode);
203 if (fd < 0)
204 return (NULL);
205
206 return (fdopen(fd, stream_mode));
207 }
208
209 static int
line_worker(char * line,const char * cachefile)210 line_worker(char *line, const char *cachefile)
211 {
212 char *toktmp;
213 /* BEGIN CSTYLED */
214 const char *dataset = strtok_r(line, "\t", &toktmp);
215 char *p_mountpoint = strtok_r(NULL, "\t", &toktmp);
216 const char *p_canmount = strtok_r(NULL, "\t", &toktmp);
217 const char *p_atime = strtok_r(NULL, "\t", &toktmp);
218 const char *p_relatime = strtok_r(NULL, "\t", &toktmp);
219 const char *p_devices = strtok_r(NULL, "\t", &toktmp);
220 const char *p_exec = strtok_r(NULL, "\t", &toktmp);
221 const char *p_readonly = strtok_r(NULL, "\t", &toktmp);
222 const char *p_setuid = strtok_r(NULL, "\t", &toktmp);
223 const char *p_nbmand = strtok_r(NULL, "\t", &toktmp);
224 const char *p_encroot = strtok_r(NULL, "\t", &toktmp) ?: "-";
225 char *p_keyloc = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
226 const char *p_systemd_requires = strtok_r(NULL, "\t", &toktmp) ?: "-";
227 const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
228 const char *p_systemd_before = strtok_r(NULL, "\t", &toktmp) ?: "-";
229 const char *p_systemd_after = strtok_r(NULL, "\t", &toktmp) ?: "-";
230 char *p_systemd_wantedby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
231 char *p_systemd_requiredby = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
232 const char *p_systemd_nofail = strtok_r(NULL, "\t", &toktmp) ?: "-";
233 const char *p_systemd_ignore = strtok_r(NULL, "\t", &toktmp) ?: "-";
234 /* END CSTYLED */
235
236 const char *pool = dataset;
237 if ((toktmp = strchr(pool, '/')) != NULL)
238 pool = strndupa(pool, toktmp - pool);
239
240 if (p_nbmand == NULL) {
241 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
242 getpid(), dataset);
243 return (1);
244 }
245
246 strncpy(argv0, dataset, strlen(argv0));
247
248 /* Minimal pre-requisites to mount a ZFS dataset */
249 const char *after = "zfs-import.target";
250 const char *wants = "zfs-import.target";
251 const char *bindsto = NULL;
252 char *wantedby = NULL;
253 char *requiredby = NULL;
254 bool noauto = false;
255 bool wantedby_append = true;
256
257 /*
258 * zfs-import.target is not needed if the pool is already imported.
259 * This avoids a dependency loop on root-on-ZFS systems:
260 * systemd-random-seed.service After (via RequiresMountsFor)
261 * var-lib.mount After
262 * zfs-import.target After
263 * zfs-import-{cache,scan}.service After
264 * cryptsetup.service After
265 * systemd-random-seed.service
266 */
267 if (tfind(pool, &known_pools, STRCMP)) {
268 after = "";
269 wants = "";
270 }
271
272 if (strcmp(p_systemd_after, "-") == 0)
273 p_systemd_after = NULL;
274 if (strcmp(p_systemd_before, "-") == 0)
275 p_systemd_before = NULL;
276 if (strcmp(p_systemd_requires, "-") == 0)
277 p_systemd_requires = NULL;
278 if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
279 p_systemd_requiresmountsfor = NULL;
280
281
282 if (strcmp(p_encroot, "-") != 0) {
283 char *keyloadunit =
284 systemd_escape(p_encroot, "zfs-load-key@", ".service");
285
286 if (strcmp(dataset, p_encroot) == 0) {
287 const char *keymountdep = NULL;
288 bool is_prompt = false;
289
290 regmatch_t uri_matches[3];
291 if (regexec(&uri_regex, p_keyloc,
292 sizeof (uri_matches) / sizeof (*uri_matches),
293 uri_matches, 0) == 0) {
294 p_keyloc[uri_matches[2].rm_eo] = '\0';
295 const char *path =
296 &p_keyloc[uri_matches[2].rm_so];
297
298 /*
299 * Assumes all URI keylocations need
300 * the mount for their path;
301 * http://, for example, wouldn't
302 * (but it'd need network-online.target et al.)
303 */
304 keymountdep = path;
305 } else {
306 if (strcmp(p_keyloc, "prompt") != 0)
307 fprintf(stderr, PROGNAME "[%d]: %s: "
308 "unknown non-URI keylocation=%s\n",
309 getpid(), dataset, p_keyloc);
310
311 is_prompt = true;
312 }
313
314
315 /* Generate the key-load .service unit */
316 FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
317 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
318 0644);
319 if (!keyloadunit_f) {
320 fprintf(stderr, PROGNAME "[%d]: %s: "
321 "couldn't open %s under %s: %s\n",
322 getpid(), dataset, keyloadunit, destdir,
323 strerror(errno));
324 return (1);
325 }
326
327 fprintf(keyloadunit_f,
328 OUTPUT_HEADER
329 "[Unit]\n"
330 "Description=Load ZFS key for %s\n"
331 "SourcePath=" FSLIST "/%s\n"
332 "Documentation=man:zfs-mount-generator(8)\n"
333 "DefaultDependencies=no\n"
334 "Wants=%s\n"
335 "After=%s\n",
336 dataset, cachefile, wants, after);
337
338 if (p_systemd_requires)
339 fprintf(keyloadunit_f,
340 "Requires=%s\n", p_systemd_requires);
341
342 if (p_systemd_requiresmountsfor || keymountdep) {
343 fprintf(keyloadunit_f, "RequiresMountsFor=");
344 if (p_systemd_requiresmountsfor)
345 fprintf(keyloadunit_f,
346 "%s ", p_systemd_requiresmountsfor);
347 if (keymountdep)
348 fprintf(keyloadunit_f,
349 "'%s'", keymountdep);
350 fprintf(keyloadunit_f, "\n");
351 }
352
353 /* BEGIN CSTYLED */
354 fprintf(keyloadunit_f,
355 "\n"
356 "[Service]\n"
357 "Type=oneshot\n"
358 "RemainAfterExit=yes\n"
359 "# This avoids a dependency loop involving systemd-journald.socket if this\n"
360 "# dataset is a parent of the root filesystem.\n"
361 "StandardOutput=null\n"
362 "StandardError=null\n"
363 "ExecStart=/bin/sh -euc '"
364 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
365 dataset);
366 if (is_prompt)
367 fprintf(keyloadunit_f,
368 "for i in 1 2 3; do "
369 "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
370 "" ZFS " load-key \"%s\" && exit 0;"
371 "done;"
372 "exit 1",
373 dataset, dataset, dataset);
374 else
375 fprintf(keyloadunit_f,
376 "exec " ZFS " load-key \"%s\"",
377 dataset);
378
379 fprintf(keyloadunit_f,
380 "'\n"
381 "ExecStop=/bin/sh -euc '"
382 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
383 "exec " ZFS " unload-key \"%s\""
384 "'\n",
385 dataset, dataset);
386 /* END CSTYLED */
387
388 (void) fclose(keyloadunit_f);
389 }
390
391 /* Update dependencies for the mount file to want this */
392 bindsto = keyloadunit;
393 if (after[0] == '\0')
394 after = keyloadunit;
395 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
396 after = toktmp;
397 else
398 EXIT_ENOMEM();
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 return (0);
408 else {
409 fprintf(stderr, PROGNAME "[%d]: %s: "
410 "invalid org.openzfs.systemd:ignore=%s\n",
411 getpid(), dataset, p_systemd_ignore);
412 return (1);
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 return (0);
422 else {
423 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
424 getpid(), dataset, p_canmount);
425 return (1);
426 }
427
428 /* Check for legacy and blank mountpoints */
429 if (strcmp(p_mountpoint, "legacy") == 0 ||
430 strcmp(p_mountpoint, "none") == 0)
431 return (0);
432 else if (p_mountpoint[0] != '/') {
433 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
434 getpid(), dataset, p_mountpoint);
435 return (1);
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 return (1);
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 the noauto_not_on_sem semaphore,
556 * which is only unlocked when the last canmount=on process exits.
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 array,
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 {
575 sem_t *our_sem = (strcmp(p_canmount, "on") == 0) ?
576 &noauto_files->noauto_names_sem :
577 &noauto_files->noauto_not_on_sem;
578 while (sem_wait(our_sem) == -1 && errno == EINTR)
579 ;
580 }
581
582 struct stat stbuf;
583 bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
584
585 bool is_known = false;
586 for (size_t i = 0; i < noauto_files->noauto_names_len; ++i) {
587 if (strncmp(
588 noauto_files->noauto_names[i], mountfile, NAME_MAX) == 0) {
589 is_known = true;
590 break;
591 }
592 }
593
594 if (already_exists) {
595 if (is_known) {
596 /* If it's in $noauto_files, we must be noauto too */
597
598 /* See 5 */
599 errno = 0;
600 (void) unlinkat(destdir_fd, mountfile, 0);
601
602 /* See 2 */
603 fprintf(stderr, PROGNAME "[%d]: %s: "
604 "removing duplicate noauto unit %s%s%s\n",
605 getpid(), dataset, mountfile,
606 errno ? "" : " failed: ",
607 errno ? "" : strerror(errno));
608 } else {
609 /* Don't log for canmount=noauto */
610 if (strcmp(p_canmount, "on") == 0)
611 fprintf(stderr, PROGNAME "[%d]: %s: "
612 "%s already exists. Skipping.\n",
613 getpid(), dataset, mountfile);
614 }
615
616 /* File exists: skip current dataset */
617 if (strcmp(p_canmount, "on") == 0)
618 sem_post(&noauto_files->noauto_names_sem);
619 return (0);
620 } else {
621 if (is_known) {
622 /* See 4 */
623 if (strcmp(p_canmount, "on") == 0)
624 sem_post(&noauto_files->noauto_names_sem);
625 return (0);
626 } else if (strcmp(p_canmount, "noauto") == 0) {
627 if (noauto_files->noauto_names_len ==
628 noauto_files->noauto_names_max)
629 fprintf(stderr, PROGNAME "[%d]: %s: "
630 "noauto dataset limit (%zu) reached! "
631 "Not tracking %s. Please report this to "
632 "https://github.com/openzfs/zfs\n",
633 getpid(), dataset,
634 noauto_files->noauto_names_max, mountfile);
635 else {
636 strncpy(noauto_files->noauto_names[
637 noauto_files->noauto_names_len],
638 mountfile, NAME_MAX);
639 ++noauto_files->noauto_names_len;
640 }
641 }
642 }
643
644
645 FILE *mountfile_f = fopenat(destdir_fd, mountfile,
646 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
647 if (strcmp(p_canmount, "on") == 0)
648 sem_post(&noauto_files->noauto_names_sem);
649 if (!mountfile_f) {
650 fprintf(stderr,
651 PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
652 getpid(), dataset, mountfile, destdir, strerror(errno));
653 return (1);
654 }
655
656 fprintf(mountfile_f,
657 OUTPUT_HEADER
658 "[Unit]\n"
659 "SourcePath=" FSLIST "/%s\n"
660 "Documentation=man:zfs-mount-generator(8)\n"
661 "\n"
662 "Before=",
663 cachefile);
664
665 if (p_systemd_before)
666 fprintf(mountfile_f, "%s ", p_systemd_before);
667 fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
668 if (requiredby)
669 fprintf(mountfile_f, " %s", requiredby);
670 if (wantedby && wantedby_append)
671 fprintf(mountfile_f, " %s", wantedby);
672
673 fprintf(mountfile_f,
674 "\n"
675 "After=");
676 if (p_systemd_after)
677 fprintf(mountfile_f, "%s ", p_systemd_after);
678 fprintf(mountfile_f, "%s\n", after);
679
680 fprintf(mountfile_f, "Wants=%s\n", wants);
681
682 if (bindsto)
683 fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
684 if (p_systemd_requires)
685 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
686 if (p_systemd_requiresmountsfor)
687 fprintf(mountfile_f,
688 "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
689
690 fprintf(mountfile_f,
691 "\n"
692 "[Mount]\n"
693 "Where=%s\n"
694 "What=%s\n"
695 "Type=zfs\n"
696 "Options=defaults%s,zfsutil\n",
697 p_mountpoint, dataset, opts);
698
699 (void) fclose(mountfile_f);
700
701 if (!requiredby && !wantedby)
702 return (0);
703
704 /* Finally, create the appropriate dependencies */
705 char *linktgt;
706 if (asprintf(&linktgt, "../%s", mountfile) == -1)
707 EXIT_ENOMEM();
708
709 char *dependencies[][2] = {
710 {"wants", wantedby},
711 {"requires", requiredby},
712 {}
713 };
714 for (__typeof__(&*dependencies) dep = &*dependencies; **dep; ++dep) {
715 if (!(*dep)[1])
716 continue;
717
718 for (char *reqby = strtok_r((*dep)[1], " ", &toktmp);
719 reqby;
720 reqby = strtok_r(NULL, " ", &toktmp)) {
721 char *depdir;
722 if (asprintf(&depdir, "%s.%s", reqby, (*dep)[0]) == -1)
723 EXIT_ENOMEM();
724
725 (void) mkdirat(destdir_fd, depdir, 0755);
726 int depdir_fd = openat(destdir_fd, depdir,
727 O_PATH | O_DIRECTORY | O_CLOEXEC);
728 if (depdir_fd < 0) {
729 fprintf(stderr, PROGNAME "[%d]: %s: "
730 "couldn't open %s under %s: %s\n",
731 getpid(), dataset, depdir, destdir,
732 strerror(errno));
733 free(depdir);
734 continue;
735 }
736
737 if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
738 fprintf(stderr, PROGNAME "[%d]: %s: "
739 "couldn't symlink at "
740 "%s under %s under %s: %s\n",
741 getpid(), dataset, mountfile,
742 depdir, destdir, strerror(errno));
743
744 (void) close(depdir_fd);
745 free(depdir);
746 }
747 }
748
749 return (0);
750 }
751
752
753 static int
pool_enumerator(zpool_handle_t * pool,void * data)754 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
755 {
756 int ret = 0;
757
758 /*
759 * Pools are guaranteed-unique by the kernel,
760 * no risk of leaking dupes here
761 */
762 char *name = strdup(zpool_get_name(pool));
763 if (!name || !tsearch(name, &known_pools, STRCMP)) {
764 free(name);
765 ret = ENOMEM;
766 }
767
768 zpool_close(pool);
769 return (ret);
770 }
771
772 int
main(int argc,char ** argv)773 main(int argc, char **argv)
774 {
775 struct timespec time_init = {};
776 clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
777
778 {
779 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
780 if (kmfd >= 0) {
781 (void) dup2(kmfd, STDERR_FILENO);
782 (void) close(kmfd);
783 }
784 }
785
786 uint8_t debug = 0;
787
788 argv0 = argv[0];
789 switch (argc) {
790 case 1:
791 /* Use default */
792 break;
793 case 2:
794 case 4:
795 destdir = argv[1];
796 break;
797 default:
798 fprintf(stderr,
799 PROGNAME "[%d]: wrong argument count: %d\n",
800 getpid(), argc - 1);
801 _exit(1);
802 }
803
804 {
805 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
806 if (destdir_fd < 0) {
807 fprintf(stderr, PROGNAME "[%d]: "
808 "can't open destination directory %s: %s\n",
809 getpid(), destdir, strerror(errno));
810 _exit(1);
811 }
812 }
813
814 DIR *fslist_dir = opendir(FSLIST);
815 if (!fslist_dir) {
816 if (errno != ENOENT)
817 fprintf(stderr,
818 PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
819 getpid(), strerror(errno));
820 _exit(0);
821 }
822
823 {
824 libzfs_handle_t *libzfs = libzfs_init();
825 if (libzfs) {
826 if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
827 fprintf(stderr, PROGNAME "[%d]: "
828 "error listing pools, ignoring\n",
829 getpid());
830 libzfs_fini(libzfs);
831 } else
832 fprintf(stderr, PROGNAME "[%d]: "
833 "couldn't start libzfs, ignoring\n",
834 getpid());
835 }
836
837 {
838 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
839 if (regerr != 0) {
840 fprintf(stderr,
841 PROGNAME "[%d]: invalid regex: %d\n",
842 getpid(), regerr);
843 _exit(1);
844 }
845 }
846
847 {
848 /*
849 * We could just get a gigabyte here and Not Care,
850 * but if vm.overcommit_memory=2, then MAP_NORESERVE is ignored
851 * and we'd try (and likely fail) to rip it out of swap
852 */
853 noauto_files = mmap(NULL, 4 * 1024 * 1024,
854 PROT_READ | PROT_WRITE,
855 MAP_SHARED | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
856 if (noauto_files == MAP_FAILED) {
857 fprintf(stderr,
858 PROGNAME "[%d]: couldn't allocate IPC region: %s\n",
859 getpid(), strerror(errno));
860 _exit(1);
861 }
862
863 sem_init(&noauto_files->noauto_not_on_sem, true, 0);
864 sem_init(&noauto_files->noauto_names_sem, true, 1);
865 noauto_files->noauto_names_len = 0;
866 /* Works out to 16447ish, *well* enough */
867 noauto_files->noauto_names_max =
868 (4 * 1024 * 1024 - sizeof (*noauto_files)) / NAME_MAX;
869 }
870
871 char *line = NULL;
872 size_t linelen = 0;
873 struct timespec time_start = {};
874 {
875 const char *dbgenv = getenv("ZFS_DEBUG");
876 if (dbgenv)
877 debug = atoi(dbgenv);
878 else {
879 FILE *cmdline = fopen("/proc/cmdline", "re");
880 if (cmdline != NULL) {
881 if (getline(&line, &linelen, cmdline) >= 0)
882 debug = strstr(line, "debug") ? 2 : 0;
883 (void) fclose(cmdline);
884 }
885 }
886
887 if (debug && !isatty(STDOUT_FILENO))
888 dup2(STDERR_FILENO, STDOUT_FILENO);
889 }
890
891 size_t forked_canmount_on = 0;
892 size_t forked_canmount_not_on = 0;
893 size_t canmount_on_pids_len = 128;
894 pid_t *canmount_on_pids =
895 malloc(canmount_on_pids_len * sizeof (*canmount_on_pids));
896 if (canmount_on_pids == NULL)
897 canmount_on_pids_len = 0;
898
899 if (debug)
900 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
901
902 ssize_t read;
903 pid_t pid;
904 struct dirent *cachent;
905 while ((cachent = readdir(fslist_dir)) != NULL) {
906 if (strcmp(cachent->d_name, ".") == 0 ||
907 strcmp(cachent->d_name, "..") == 0)
908 continue;
909
910 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
911 O_RDONLY | O_CLOEXEC, "r", 0);
912 if (!cachefile) {
913 fprintf(stderr, PROGNAME "[%d]: "
914 "couldn't open %s under " FSLIST ": %s\n",
915 getpid(), cachent->d_name, strerror(errno));
916 continue;
917 }
918
919 while ((read = getline(&line, &linelen, cachefile)) >= 0) {
920 line[read - 1] = '\0'; /* newline */
921
922 switch (pid = fork()) {
923 case -1:
924 fprintf(stderr,
925 PROGNAME "[%d]: couldn't fork for %s: %s\n",
926 getpid(), line, strerror(errno));
927 break;
928 case 0: /* child */
929 _exit(line_worker(line, cachent->d_name));
930 default: { /* parent */
931 char *tmp;
932 char *dset = strtok_r(line, "\t", &tmp);
933 strtok_r(NULL, "\t", &tmp);
934 char *canmount = strtok_r(NULL, "\t", &tmp);
935 bool canmount_on =
936 canmount && strncmp(canmount, "on", 2) == 0;
937
938 if (debug >= 2)
939 printf(PROGNAME ": forked %d, "
940 "canmount_on=%d, dataset=%s\n",
941 (int)pid, canmount_on, dset);
942
943 if (canmount_on &&
944 forked_canmount_on ==
945 canmount_on_pids_len) {
946 size_t new_len =
947 (canmount_on_pids_len ?: 16) * 2;
948 void *new_pidlist =
949 realloc(canmount_on_pids,
950 new_len *
951 sizeof (*canmount_on_pids));
952 if (!new_pidlist) {
953 fprintf(stderr,
954 PROGNAME "[%d]: "
955 "out of memory! "
956 "Mount ordering may be "
957 "affected.\n", getpid());
958 continue;
959 }
960
961 canmount_on_pids = new_pidlist;
962 canmount_on_pids_len = new_len;
963 }
964
965 if (canmount_on) {
966 canmount_on_pids[forked_canmount_on] =
967 pid;
968 ++forked_canmount_on;
969 } else
970 ++forked_canmount_not_on;
971 break;
972 }
973 }
974 }
975
976 (void) fclose(cachefile);
977 }
978 free(line);
979
980 if (forked_canmount_on == 0) {
981 /* No canmount=on processes to finish, so don't deadlock here */
982 for (size_t i = 0; i < forked_canmount_not_on; ++i)
983 sem_post(&noauto_files->noauto_not_on_sem);
984 } else {
985 /* Likely a no-op, since we got these from a narrow fork loop */
986 qsort(canmount_on_pids, forked_canmount_on,
987 sizeof (*canmount_on_pids), PID_T_CMP);
988 }
989
990 int status, ret = 0;
991 struct rusage usage;
992 size_t forked_canmount_on_max = forked_canmount_on;
993 while ((pid = wait4(-1, &status, 0, &usage)) != -1) {
994 ret |= WEXITSTATUS(status) | WTERMSIG(status);
995
996 if (forked_canmount_on != 0) {
997 if (bsearch(&pid, canmount_on_pids,
998 forked_canmount_on_max, sizeof (*canmount_on_pids),
999 PID_T_CMP))
1000 --forked_canmount_on;
1001
1002 if (forked_canmount_on == 0) {
1003 /*
1004 * All canmount=on processes have finished,
1005 * let all the lower-priority ones finish now
1006 */
1007 for (size_t i = 0;
1008 i < forked_canmount_not_on; ++i)
1009 sem_post(
1010 &noauto_files->noauto_not_on_sem);
1011 }
1012 }
1013
1014 if (debug >= 2)
1015 printf(PROGNAME ": %d done, user=%llu.%06us, "
1016 "system=%llu.%06us, maxrss=%ldB, ex=0x%x\n",
1017 (int)pid,
1018 (unsigned long long) usage.ru_utime.tv_sec,
1019 (unsigned int) usage.ru_utime.tv_usec,
1020 (unsigned long long) usage.ru_stime.tv_sec,
1021 (unsigned int) usage.ru_stime.tv_usec,
1022 usage.ru_maxrss * 1024, status);
1023 }
1024
1025 if (debug) {
1026 struct timespec time_end = {};
1027 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
1028
1029 getrusage(RUSAGE_SELF, &usage);
1030 printf(
1031 "\n"
1032 PROGNAME ": self : "
1033 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
1034 (unsigned long long) usage.ru_utime.tv_sec,
1035 (unsigned int) usage.ru_utime.tv_usec,
1036 (unsigned long long) usage.ru_stime.tv_sec,
1037 (unsigned int) usage.ru_stime.tv_usec,
1038 usage.ru_maxrss * 1024);
1039
1040 getrusage(RUSAGE_CHILDREN, &usage);
1041 printf(PROGNAME ": children: "
1042 "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
1043 (unsigned long long) usage.ru_utime.tv_sec,
1044 (unsigned int) usage.ru_utime.tv_usec,
1045 (unsigned long long) usage.ru_stime.tv_sec,
1046 (unsigned int) usage.ru_stime.tv_usec,
1047 usage.ru_maxrss * 1024);
1048
1049 if (time_start.tv_nsec > time_end.tv_nsec) {
1050 time_end.tv_nsec =
1051 1000000000 + time_end.tv_nsec - time_start.tv_nsec;
1052 time_end.tv_sec -= 1;
1053 } else
1054 time_end.tv_nsec -= time_start.tv_nsec;
1055 time_end.tv_sec -= time_start.tv_sec;
1056
1057 if (time_init.tv_nsec > time_start.tv_nsec) {
1058 time_start.tv_nsec =
1059 1000000000 + time_start.tv_nsec - time_init.tv_nsec;
1060 time_start.tv_sec -= 1;
1061 } else
1062 time_start.tv_nsec -= time_init.tv_nsec;
1063 time_start.tv_sec -= time_init.tv_sec;
1064
1065 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
1066 time_init.tv_sec =
1067 time_start.tv_sec + time_end.tv_sec +
1068 time_init.tv_nsec / 1000000000;
1069 time_init.tv_nsec %= 1000000000;
1070
1071 printf(PROGNAME ": wall : "
1072 "total=%llu.%09llus = "
1073 "init=%llu.%09llus + real=%llu.%09llus\n",
1074 (unsigned long long) time_init.tv_sec,
1075 (unsigned long long) time_init.tv_nsec,
1076 (unsigned long long) time_start.tv_sec,
1077 (unsigned long long) time_start.tv_nsec,
1078 (unsigned long long) time_end.tv_sec,
1079 (unsigned long long) time_end.tv_nsec);
1080 }
1081
1082 _exit(ret);
1083 }
1084