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