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 <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
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 *
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
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
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 *
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 *
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
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
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
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