Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/openzfs/etc/systemd/system-generators/zfs-mount-generator.c
48521 views
1
// SPDX-License-Identifier: MIT
2
/*
3
* Copyright (c) 2017 Antonio Russo <[email protected]>
4
* Copyright (c) 2020 InsanePrawn <[email protected]>
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 *
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
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
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 *
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 *
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
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
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
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
1007