xref: /freebsd/sys/contrib/openzfs/etc/systemd/system-generators/zfs-mount-generator.c (revision efa8679e7f69c9cc225613827d9f75644cca5b3b)
1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
4  * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 
27 #include <sys/resource.h>
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <sys/stat.h>
31 #include <stdbool.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <stdio.h>
35 #include <time.h>
36 #include <regex.h>
37 #include <search.h>
38 #include <dirent.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include <errno.h>
43 #include <libzfs.h>
44 
45 /*
46  * For debugging only.
47  *
48  * Free statics with trivial life-times,
49  * but saved line filenames are replaced with a static string.
50  */
51 #define	FREE_STATICS false
52 
53 #define	nitems(arr) (sizeof (arr) / sizeof (*arr))
54 #define	STRCMP ((int(*)(const void *, const void *))&strcmp)
55 
56 
57 #define	PROGNAME "zfs-mount-generator"
58 #define	FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
59 #define	ZFS SBINDIR "/zfs"
60 
61 #define	OUTPUT_HEADER \
62 	"# Automatically generated by " PROGNAME "\n" \
63 	"\n"
64 
65 /*
66  * Starts like the one in libzfs_util.c but also matches "//"
67  * and captures until the end, since we actually use it for path extraxion
68  */
69 #define	URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
70 static regex_t uri_regex;
71 
72 static const char *destdir = "/tmp";
73 static int destdir_fd = -1;
74 
75 static void *known_pools = NULL; /* tsearch() of C strings */
76 static void *noauto_files = NULL; /* tsearch() of C strings */
77 
78 
79 static char *
systemd_escape(const char * input,const char * prepend,const char * append)80 systemd_escape(const char *input, const char *prepend, const char *append)
81 {
82 	size_t len = strlen(input);
83 	size_t applen = strlen(append);
84 	size_t prelen = strlen(prepend);
85 	char *ret = malloc(4 * len + prelen + applen + 1);
86 	if (!ret) {
87 		fprintf(stderr, PROGNAME "[%d]: "
88 		    "out of memory to escape \"%s%s%s\"!\n",
89 		    getpid(), prepend, input, append);
90 		return (NULL);
91 	}
92 
93 	memcpy(ret, prepend, prelen);
94 	char *out = ret + prelen;
95 
96 	const char *cur = input;
97 	if (*cur == '.') {
98 		memcpy(out, "\\x2e", 4);
99 		out += 4;
100 		++cur;
101 	}
102 	for (; *cur; ++cur) {
103 		if (*cur == '/')
104 			*(out++) = '-';
105 		else if (strchr(
106 		    "0123456789"
107 		    "abcdefghijklmnopqrstuvwxyz"
108 		    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
109 		    ":_.", *cur))
110 			*(out++) = *cur;
111 		else {
112 			sprintf(out, "\\x%02x", (int)*cur);
113 			out += 4;
114 		}
115 	}
116 
117 	memcpy(out, append, applen + 1);
118 	return (ret);
119 }
120 
121 static void
simplify_path(char * path)122 simplify_path(char *path)
123 {
124 	char *out = path;
125 	for (char *cur = path; *cur; ++cur) {
126 		if (*cur == '/') {
127 			while (*(cur + 1) == '/')
128 				++cur;
129 			*(out++) = '/';
130 		} else
131 			*(out++) = *cur;
132 	}
133 
134 	*(out++) = '\0';
135 }
136 
137 static bool
strendswith(const char * what,const char * suff)138 strendswith(const char *what, const char *suff)
139 {
140 	size_t what_l = strlen(what);
141 	size_t suff_l = strlen(suff);
142 
143 	return ((what_l >= suff_l) &&
144 	    (strcmp(what + what_l - suff_l, suff) == 0));
145 }
146 
147 /* Assumes already-simplified path, doesn't modify input */
148 static char *
systemd_escape_path(char * input,const char * prepend,const char * append)149 systemd_escape_path(char *input, const char *prepend, const char *append)
150 {
151 	if (strcmp(input, "/") == 0) {
152 		char *ret;
153 		if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
154 			fprintf(stderr, PROGNAME "[%d]: "
155 			    "out of memory to escape \"%s%s%s\"!\n",
156 			    getpid(), prepend, input, append);
157 			ret = NULL;
158 		}
159 		return (ret);
160 	} else {
161 		/*
162 		 * path_is_normalized() (flattened for absolute paths here),
163 		 * required for proper escaping
164 		 */
165 		if (strstr(input, "/./") || strstr(input, "/../") ||
166 		    strendswith(input, "/.") || strendswith(input, "/.."))
167 			return (NULL);
168 
169 
170 		if (input[0] == '/')
171 			++input;
172 
173 		char *back = &input[strlen(input) - 1];
174 		bool deslash = *back == '/';
175 		if (deslash)
176 			*back = '\0';
177 
178 		char *ret = systemd_escape(input, prepend, append);
179 
180 		if (deslash)
181 			*back = '/';
182 		return (ret);
183 	}
184 }
185 
186 static FILE *
fopenat(int dirfd,const char * pathname,int flags,const char * stream_mode,mode_t mode)187 fopenat(int dirfd, const char *pathname, int flags,
188     const char *stream_mode, mode_t mode)
189 {
190 	int fd = openat(dirfd, pathname, flags, mode);
191 	if (fd < 0)
192 		return (NULL);
193 
194 	return (fdopen(fd, stream_mode));
195 }
196 
197 static int
line_worker(char * line,const char * cachefile)198 line_worker(char *line, const char *cachefile)
199 {
200 	int ret = 0;
201 	void *tofree_all[8];
202 	void **tofree = tofree_all;
203 
204 	char *toktmp;
205 	const char *toktmp2;
206 	/* BEGIN CSTYLED */
207 	const char *dataset                     = strtok_r(line, "\t", &toktmp);
208 	      char *p_mountpoint                = strtok_r(NULL, "\t", &toktmp);
209 	const char *p_canmount                  = strtok_r(NULL, "\t", &toktmp);
210 	const char *p_atime                     = strtok_r(NULL, "\t", &toktmp);
211 	const char *p_relatime                  = strtok_r(NULL, "\t", &toktmp);
212 	const char *p_devices                   = strtok_r(NULL, "\t", &toktmp);
213 	const char *p_exec                      = strtok_r(NULL, "\t", &toktmp);
214 	const char *p_readonly                  = strtok_r(NULL, "\t", &toktmp);
215 	const char *p_setuid                    = strtok_r(NULL, "\t", &toktmp);
216 	const char *p_nbmand                    = strtok_r(NULL, "\t", &toktmp);
217 	const char *p_encroot                   = strtok_r(NULL, "\t", &toktmp) ?: "-";
218 	      char *p_keyloc                    = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
219 	const char *p_systemd_requires          = strtok_r(NULL, "\t", &toktmp) ?: "-";
220 	const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
221 	const char *p_systemd_before            = strtok_r(NULL, "\t", &toktmp) ?: "-";
222 	const char *p_systemd_after             = strtok_r(NULL, "\t", &toktmp) ?: "-";
223 	      char *p_systemd_wantedby          = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
224 	      char *p_systemd_requiredby        = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
225 	const char *p_systemd_nofail            = strtok_r(NULL, "\t", &toktmp) ?: "-";
226 	const char *p_systemd_ignore            = strtok_r(NULL, "\t", &toktmp) ?: "-";
227 	/* END CSTYLED */
228 
229 	size_t pool_len = strlen(dataset);
230 	if ((toktmp2 = strchr(dataset, '/')) != NULL)
231 		pool_len = toktmp2 - dataset;
232 	const char *pool = *(tofree++) = strndup(dataset, pool_len);
233 
234 	if (p_nbmand == NULL) {
235 		fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
236 		    getpid(), dataset);
237 		goto err;
238 	}
239 
240 	/* Minimal pre-requisites to mount a ZFS dataset */
241 	const char *after = "zfs-import.target";
242 	const char *wants = "zfs-import.target";
243 	const char *bindsto = NULL;
244 	char *wantedby = NULL;
245 	char *requiredby = NULL;
246 	bool noauto = false;
247 	bool wantedby_append = true;
248 
249 	/*
250 	 * zfs-import.target is not needed if the pool is already imported.
251 	 * This avoids a dependency loop on root-on-ZFS systems:
252 	 *   systemd-random-seed.service After (via RequiresMountsFor)
253 	 *   var-lib.mount After
254 	 *   zfs-import.target After
255 	 *   zfs-import-{cache,scan}.service After
256 	 *   cryptsetup.service After
257 	 *   systemd-random-seed.service
258 	 */
259 	if (tfind(pool, &known_pools, STRCMP)) {
260 		after = "";
261 		wants = "";
262 	}
263 
264 	if (strcmp(p_systemd_after, "-") == 0)
265 		p_systemd_after = NULL;
266 	if (strcmp(p_systemd_before, "-") == 0)
267 		p_systemd_before = NULL;
268 	if (strcmp(p_systemd_requires, "-") == 0)
269 		p_systemd_requires = NULL;
270 	if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
271 		p_systemd_requiresmountsfor = NULL;
272 
273 
274 	if (strcmp(p_encroot, "-") != 0) {
275 		char *keyloadunit = *(tofree++) =
276 		    systemd_escape(p_encroot, "zfs-load-key@", ".service");
277 		if (keyloadunit == NULL)
278 			goto err;
279 
280 		if (strcmp(dataset, p_encroot) == 0) {
281 			const char *keymountdep = NULL;
282 			bool is_prompt = false;
283 			bool need_network = false;
284 
285 			regmatch_t uri_matches[3];
286 			if (regexec(&uri_regex, p_keyloc,
287 			    nitems(uri_matches), uri_matches, 0) == 0) {
288 				p_keyloc[uri_matches[1].rm_eo] = '\0';
289 				p_keyloc[uri_matches[2].rm_eo] = '\0';
290 				const char *scheme =
291 				    &p_keyloc[uri_matches[1].rm_so];
292 				const char *path =
293 				    &p_keyloc[uri_matches[2].rm_so];
294 
295 				if (strcmp(scheme, "https") == 0 ||
296 				    strcmp(scheme, "http") == 0)
297 					need_network = true;
298 				else
299 					keymountdep = path;
300 			} else {
301 				if (strcmp(p_keyloc, "prompt") != 0)
302 					fprintf(stderr, PROGNAME "[%d]: %s: "
303 					    "unknown non-URI keylocation=%s\n",
304 					    getpid(), dataset, p_keyloc);
305 
306 				is_prompt = true;
307 			}
308 
309 
310 			/* Generate the key-load .service unit */
311 			FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
312 			    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
313 			    0644);
314 			if (!keyloadunit_f) {
315 				fprintf(stderr, PROGNAME "[%d]: %s: "
316 				    "couldn't open %s under %s: %s\n",
317 				    getpid(), dataset, keyloadunit, destdir,
318 				    strerror(errno));
319 				goto err;
320 			}
321 
322 			fprintf(keyloadunit_f,
323 			    OUTPUT_HEADER
324 			    "[Unit]\n"
325 			    "Description=Load ZFS key for %s\n"
326 			    "SourcePath=" FSLIST "/%s\n"
327 			    "Documentation=man:zfs-mount-generator(8)\n"
328 			    "DefaultDependencies=no\n"
329 			    "Wants=%s\n"
330 			    "After=%s\n",
331 			    dataset, cachefile, wants, after);
332 
333 			if (need_network)
334 				fprintf(keyloadunit_f,
335 				    "Wants=network-online.target\n"
336 				    "After=network-online.target\n");
337 
338 			if (p_systemd_requires)
339 				fprintf(keyloadunit_f,
340 				    "Requires=%s\n", p_systemd_requires);
341 
342 			if (p_systemd_requiresmountsfor)
343 				fprintf(keyloadunit_f,
344 				    "RequiresMountsFor=%s\n",
345 				    p_systemd_requiresmountsfor);
346 			if (keymountdep)
347 				fprintf(keyloadunit_f,
348 				    "RequiresMountsFor='%s'\n", keymountdep);
349 
350 			/* BEGIN CSTYLED */
351 			fprintf(keyloadunit_f,
352 			    "\n"
353 			    "[Service]\n"
354 			    "Type=oneshot\n"
355 			    "RemainAfterExit=yes\n"
356 			    "# This avoids a dependency loop involving systemd-journald.socket if this\n"
357 			    "# dataset is a parent of the root filesystem.\n"
358 			    "StandardOutput=null\n"
359 			    "StandardError=null\n"
360 			    "ExecStart=/bin/sh -euc '"
361 			        "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
362 			    dataset);
363 			if (is_prompt)
364 				fprintf(keyloadunit_f,
365 				    "for i in 1 2 3; do "
366 				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
367 				        "" ZFS " load-key \"%s\" && exit 0;"
368 				    "done;"
369 				    "exit 1",
370 				    dataset, dataset, dataset);
371 			else
372 				fprintf(keyloadunit_f,
373 				    "exec " ZFS " load-key \"%s\"",
374 				    dataset);
375 
376 			fprintf(keyloadunit_f,
377 				"'\n"
378 				"ExecStop=/bin/sh -euc '"
379 				    "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
380 				    "exec " ZFS " unload-key \"%s\""
381 				"'\n",
382 				dataset, dataset);
383 			/* END CSTYLED */
384 
385 			(void) fclose(keyloadunit_f);
386 		}
387 
388 		/* Update dependencies for the mount file to want this */
389 		bindsto = keyloadunit;
390 		if (after[0] == '\0')
391 			after = keyloadunit;
392 		else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
393 			after = *(tofree++) = toktmp;
394 		else {
395 			fprintf(stderr, PROGNAME "[%d]: %s: "
396 			    "out of memory to generate after=\"%s %s\"!\n",
397 			    getpid(), dataset, after, keyloadunit);
398 			goto err;
399 		}
400 	}
401 
402 
403 	/* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
404 	if (strcmp(p_systemd_ignore, "-") == 0 ||
405 	    strcmp(p_systemd_ignore, "off") == 0) {
406 		/* ok */
407 	} else if (strcmp(p_systemd_ignore, "on") == 0)
408 		goto end;
409 	else {
410 		fprintf(stderr, PROGNAME "[%d]: %s: "
411 		    "invalid org.openzfs.systemd:ignore=%s\n",
412 		    getpid(), dataset, p_systemd_ignore);
413 		goto err;
414 	}
415 
416 	/* Check for canmount */
417 	if (strcmp(p_canmount, "on") == 0) {
418 		/* ok */
419 	} else if (strcmp(p_canmount, "noauto") == 0)
420 		noauto = true;
421 	else if (strcmp(p_canmount, "off") == 0)
422 		goto end;
423 	else {
424 		fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
425 		    getpid(), dataset, p_canmount);
426 		goto err;
427 	}
428 
429 	/* Check for legacy and blank mountpoints */
430 	if (strcmp(p_mountpoint, "legacy") == 0 ||
431 	    strcmp(p_mountpoint, "none") == 0)
432 		goto end;
433 	else if (p_mountpoint[0] != '/') {
434 		fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
435 		    getpid(), dataset, p_mountpoint);
436 		goto err;
437 	}
438 
439 	/* Escape the mountpoint per systemd policy */
440 	simplify_path(p_mountpoint);
441 	const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
442 	if (mountfile == NULL) {
443 		fprintf(stderr,
444 		    PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
445 		    getpid(), dataset, p_mountpoint);
446 		goto err;
447 	}
448 
449 
450 	/*
451 	 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
452 	 *
453 	 * The longest string achievable here is
454 	 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
455 	 */
456 	char opts[64] = "";
457 
458 	/* atime */
459 	if (strcmp(p_atime, "on") == 0) {
460 		/* relatime */
461 		if (strcmp(p_relatime, "on") == 0)
462 			strcat(opts, ",atime,relatime");
463 		else if (strcmp(p_relatime, "off") == 0)
464 			strcat(opts, ",atime,strictatime");
465 		else
466 			fprintf(stderr,
467 			    PROGNAME "[%d]: %s: invalid relatime=%s\n",
468 			    getpid(), dataset, p_relatime);
469 	} else if (strcmp(p_atime, "off") == 0) {
470 		strcat(opts, ",noatime");
471 	} else
472 		fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
473 		    getpid(), dataset, p_atime);
474 
475 	/* devices */
476 	if (strcmp(p_devices, "on") == 0)
477 		strcat(opts, ",dev");
478 	else if (strcmp(p_devices, "off") == 0)
479 		strcat(opts, ",nodev");
480 	else
481 		fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
482 		    getpid(), dataset, p_devices);
483 
484 	/* exec */
485 	if (strcmp(p_exec, "on") == 0)
486 		strcat(opts, ",exec");
487 	else if (strcmp(p_exec, "off") == 0)
488 		strcat(opts, ",noexec");
489 	else
490 		fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
491 		    getpid(), dataset, p_exec);
492 
493 	/* readonly */
494 	if (strcmp(p_readonly, "on") == 0)
495 		strcat(opts, ",ro");
496 	else if (strcmp(p_readonly, "off") == 0)
497 		strcat(opts, ",rw");
498 	else
499 		fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
500 		    getpid(), dataset, p_readonly);
501 
502 	/* setuid */
503 	if (strcmp(p_setuid, "on") == 0)
504 		strcat(opts, ",suid");
505 	else if (strcmp(p_setuid, "off") == 0)
506 		strcat(opts, ",nosuid");
507 	else
508 		fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
509 		    getpid(), dataset, p_setuid);
510 
511 	/* nbmand */
512 	if (strcmp(p_nbmand, "on") == 0)
513 		strcat(opts, ",mand");
514 	else if (strcmp(p_nbmand, "off") == 0)
515 		strcat(opts, ",nomand");
516 	else
517 		fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
518 		    getpid(), dataset, p_setuid);
519 
520 	if (strcmp(p_systemd_wantedby, "-") != 0) {
521 		noauto = true;
522 
523 		if (strcmp(p_systemd_wantedby, "none") != 0)
524 			wantedby = p_systemd_wantedby;
525 	}
526 
527 	if (strcmp(p_systemd_requiredby, "-") != 0) {
528 		noauto = true;
529 
530 		if (strcmp(p_systemd_requiredby, "none") != 0)
531 			requiredby = p_systemd_requiredby;
532 	}
533 
534 	/*
535 	 * For datasets with canmount=on, a dependency is created for
536 	 * local-fs.target by default. To avoid regressions, this dependency
537 	 * is reduced to "wants" rather than "requires" when nofail!=off.
538 	 * **THIS MAY CHANGE**
539 	 * noauto=on disables this behavior completely.
540 	 */
541 	if (!noauto) {
542 		if (strcmp(p_systemd_nofail, "off") == 0)
543 			requiredby = strdupa("local-fs.target");
544 		else {
545 			wantedby = strdupa("local-fs.target");
546 			wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
547 		}
548 	}
549 
550 	/*
551 	 * Handle existing files:
552 	 * 1.	We never overwrite existing files, although we may delete
553 	 * 	files if we're sure they were created by us. (see 5.)
554 	 * 2.	We handle files differently based on canmount.
555 	 * 	Units with canmount=on always have precedence over noauto.
556 	 * 	This is enforced by processing these units before all others.
557 	 * 	It is important to use p_canmount and not noauto here,
558 	 * 	since we categorise by canmount while other properties,
559 	 * 	e.g. org.openzfs.systemd:wanted-by, also modify noauto.
560 	 * 3.	If no unit file exists for a noauto dataset, we create one.
561 	 * 	Additionally, we use noauto_files to track the unit file names
562 	 * 	(which are the systemd-escaped mountpoints) of all (exclusively)
563 	 * 	noauto datasets that had a file created.
564 	 * 4.	If the file to be created is found in the tracking tree,
565 	 * 	we do NOT create it.
566 	 * 5.	If a file exists for a noauto dataset,
567 	 * 	we check whether the file name is in the array.
568 	 * 	If it is, we have multiple noauto datasets for the same
569 	 * 	mountpoint. In such cases, we remove the file for safety.
570 	 * 	We leave the file name in the tracking array to avoid
571 	 * 	further noauto datasets creating a file for this path again.
572 	 */
573 
574 	struct stat stbuf;
575 	bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
576 	bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
577 
578 	*(tofree++) = (void *)mountfile;
579 	if (already_exists) {
580 		if (is_known) {
581 			/* If it's in noauto_files, we must be noauto too */
582 
583 			/* See 5 */
584 			errno = 0;
585 			(void) unlinkat(destdir_fd, mountfile, 0);
586 
587 			/* See 2 */
588 			fprintf(stderr, PROGNAME "[%d]: %s: "
589 			    "removing duplicate noauto unit %s%s%s\n",
590 			    getpid(), dataset, mountfile,
591 			    errno ? "" : " failed: ",
592 			    errno ? "" : strerror(errno));
593 		} else {
594 			/* Don't log for canmount=noauto */
595 			if (strcmp(p_canmount, "on") == 0)
596 				fprintf(stderr, PROGNAME "[%d]: %s: "
597 				    "%s already exists. Skipping.\n",
598 				    getpid(), dataset, mountfile);
599 		}
600 
601 		/* File exists: skip current dataset */
602 		goto end;
603 	} else {
604 		if (is_known) {
605 			/* See 4 */
606 			goto end;
607 		} else if (strcmp(p_canmount, "noauto") == 0) {
608 			if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
609 				fprintf(stderr, PROGNAME "[%d]: %s: "
610 				    "out of memory for noauto datasets! "
611 				    "Not tracking %s.\n",
612 				    getpid(), dataset, mountfile);
613 			else
614 				/* mountfile escaped to noauto_files */
615 				*(--tofree) = NULL;
616 		}
617 	}
618 
619 
620 	FILE *mountfile_f = fopenat(destdir_fd, mountfile,
621 	    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
622 	if (!mountfile_f) {
623 		fprintf(stderr,
624 		    PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
625 		    getpid(), dataset, mountfile, destdir, strerror(errno));
626 		goto err;
627 	}
628 
629 	fprintf(mountfile_f,
630 	    OUTPUT_HEADER
631 	    "[Unit]\n"
632 	    "SourcePath=" FSLIST "/%s\n"
633 	    "Documentation=man:zfs-mount-generator(8)\n"
634 	    "\n"
635 	    "Before=",
636 	    cachefile);
637 
638 	if (p_systemd_before)
639 		fprintf(mountfile_f, "%s ", p_systemd_before);
640 	fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
641 	if (requiredby)
642 		fprintf(mountfile_f, " %s", requiredby);
643 	if (wantedby && wantedby_append)
644 		fprintf(mountfile_f, " %s", wantedby);
645 
646 	fprintf(mountfile_f,
647 	    "\n"
648 	    "After=");
649 	if (p_systemd_after)
650 		fprintf(mountfile_f, "%s ", p_systemd_after);
651 	fprintf(mountfile_f, "%s\n", after);
652 
653 	fprintf(mountfile_f, "Wants=%s\n", wants);
654 
655 	if (bindsto)
656 		fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
657 	if (p_systemd_requires)
658 		fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
659 	if (p_systemd_requiresmountsfor)
660 		fprintf(mountfile_f,
661 		    "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
662 
663 	fprintf(mountfile_f,
664 	    "\n"
665 	    "[Mount]\n"
666 	    "Where=%s\n"
667 	    "What=%s\n"
668 	    "Type=zfs\n"
669 	    "Options=defaults%s,zfsutil\n",
670 	    p_mountpoint, dataset, opts);
671 
672 	(void) fclose(mountfile_f);
673 
674 	if (!requiredby && !wantedby)
675 		goto end;
676 
677 	/* Finally, create the appropriate dependencies */
678 	char *linktgt;
679 	if (asprintf(&linktgt, "../%s", mountfile) == -1) {
680 		fprintf(stderr, PROGNAME "[%d]: %s: "
681 		    "out of memory for dependents of %s!\n",
682 		    getpid(), dataset, mountfile);
683 		goto err;
684 	}
685 	*(tofree++) = linktgt;
686 
687 	struct dep {
688 		const char *type;
689 		char *list;
690 	} deps[] = {
691 		{"wants", wantedby},
692 		{"requires", requiredby},
693 		{}
694 	};
695 	for (struct dep *dep = deps; dep->type; ++dep) {
696 		if (!dep->list)
697 			continue;
698 
699 		for (char *reqby = strtok_r(dep->list, " ", &toktmp);
700 		    reqby;
701 		    reqby = strtok_r(NULL, " ", &toktmp)) {
702 			char *depdir;
703 			if (asprintf(
704 			    &depdir, "%s.%s", reqby, dep->type) == -1) {
705 				fprintf(stderr, PROGNAME "[%d]: %s: "
706 				    "out of memory for dependent dir name "
707 				    "\"%s.%s\"!\n",
708 				    getpid(), dataset, reqby, dep->type);
709 				continue;
710 			}
711 
712 			(void) mkdirat(destdir_fd, depdir, 0755);
713 			int depdir_fd = openat(destdir_fd, depdir,
714 			    O_PATH | O_DIRECTORY | O_CLOEXEC);
715 			if (depdir_fd < 0) {
716 				fprintf(stderr, PROGNAME "[%d]: %s: "
717 				    "couldn't open %s under %s: %s\n",
718 				    getpid(), dataset, depdir, destdir,
719 				    strerror(errno));
720 				free(depdir);
721 				continue;
722 			}
723 
724 			if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
725 				fprintf(stderr, PROGNAME "[%d]: %s: "
726 				    "couldn't symlink at "
727 				    "%s under %s under %s: %s\n",
728 				    getpid(), dataset, mountfile,
729 				    depdir, destdir, strerror(errno));
730 
731 			(void) close(depdir_fd);
732 			free(depdir);
733 		}
734 	}
735 
736 end:
737 	if (tofree >= tofree_all + nitems(tofree_all)) {
738 		/*
739 		 * This won't happen as-is:
740 		 * we've got 8 slots and allocate 5 things at most.
741 		 */
742 		fprintf(stderr,
743 		    PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
744 		    getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
745 		ret = tofree - tofree_all;
746 	}
747 
748 	while (tofree-- != tofree_all)
749 		free(*tofree);
750 	return (ret);
751 err:
752 	ret = 1;
753 	goto end;
754 }
755 
756 
757 static int
pool_enumerator(zpool_handle_t * pool,void * data)758 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
759 {
760 	int ret = 0;
761 
762 	/*
763 	 * Pools are guaranteed-unique by the kernel,
764 	 * no risk of leaking dupes here
765 	 */
766 	char *name = strdup(zpool_get_name(pool));
767 	if (!name || !tsearch(name, &known_pools, STRCMP)) {
768 		free(name);
769 		ret = ENOMEM;
770 	}
771 
772 	zpool_close(pool);
773 	return (ret);
774 }
775 
776 int
main(int argc,char ** argv)777 main(int argc, char **argv)
778 {
779 	struct timespec time_init = {};
780 	clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
781 
782 	{
783 		int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
784 		if (kmfd >= 0) {
785 			(void) dup2(kmfd, STDERR_FILENO);
786 			(void) close(kmfd);
787 
788 			setlinebuf(stderr);
789 		}
790 	}
791 
792 	switch (argc) {
793 	case 1:
794 		/* Use default */
795 		break;
796 	case 2:
797 	case 4:
798 		destdir = argv[1];
799 		break;
800 	default:
801 		fprintf(stderr,
802 		    PROGNAME "[%d]: wrong argument count: %d\n",
803 		    getpid(), argc - 1);
804 		_exit(1);
805 	}
806 
807 	{
808 		destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
809 		if (destdir_fd < 0) {
810 			fprintf(stderr, PROGNAME "[%d]: "
811 			    "can't open destination directory %s: %s\n",
812 			    getpid(), destdir, strerror(errno));
813 			_exit(1);
814 		}
815 	}
816 
817 	DIR *fslist_dir = opendir(FSLIST);
818 	if (!fslist_dir) {
819 		if (errno != ENOENT)
820 			fprintf(stderr,
821 			    PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
822 			    getpid(), strerror(errno));
823 		_exit(0);
824 	}
825 
826 	{
827 		libzfs_handle_t *libzfs = libzfs_init();
828 		if (libzfs) {
829 			if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
830 				fprintf(stderr, PROGNAME "[%d]: "
831 				    "error listing pools, ignoring\n",
832 				    getpid());
833 			libzfs_fini(libzfs);
834 		} else
835 			fprintf(stderr, PROGNAME "[%d]: "
836 			    "couldn't start libzfs, ignoring\n",
837 			    getpid());
838 	}
839 
840 	{
841 		int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
842 		if (regerr != 0) {
843 			fprintf(stderr,
844 			    PROGNAME "[%d]: invalid regex: %d\n",
845 			    getpid(), regerr);
846 			_exit(1);
847 		}
848 	}
849 
850 	bool debug = false;
851 	char *line = NULL;
852 	size_t linelen = 0;
853 	{
854 		const char *dbgenv = getenv("ZFS_DEBUG");
855 		if (dbgenv)
856 			debug = atoi(dbgenv);
857 		else {
858 			FILE *cmdline = fopen("/proc/cmdline", "re");
859 			if (cmdline != NULL) {
860 				if (getline(&line, &linelen, cmdline) >= 0)
861 					debug = strstr(line, "debug");
862 				(void) fclose(cmdline);
863 			}
864 		}
865 
866 		if (debug && !isatty(STDOUT_FILENO))
867 			dup2(STDERR_FILENO, STDOUT_FILENO);
868 	}
869 
870 	struct timespec time_start = {};
871 	if (debug)
872 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
873 
874 	struct line {
875 		char *line;
876 		const char *fname;
877 		struct line *next;
878 	} *lines_canmount_not_on = NULL;
879 
880 	int ret = 0;
881 	struct dirent *cachent;
882 	while ((cachent = readdir(fslist_dir)) != NULL) {
883 		if (strcmp(cachent->d_name, ".") == 0 ||
884 		    strcmp(cachent->d_name, "..") == 0)
885 			continue;
886 
887 		FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
888 		    O_RDONLY | O_CLOEXEC, "r", 0);
889 		if (!cachefile) {
890 			fprintf(stderr, PROGNAME "[%d]: "
891 			    "couldn't open %s under " FSLIST ": %s\n",
892 			    getpid(), cachent->d_name, strerror(errno));
893 			continue;
894 		}
895 
896 		const char *filename = FREE_STATICS ? "(elided)" : NULL;
897 
898 		ssize_t read;
899 		while ((read = getline(&line, &linelen, cachefile)) >= 0) {
900 			line[read - 1] = '\0'; /* newline */
901 
902 			char *canmount = line;
903 			canmount += strcspn(canmount, "\t");
904 			canmount += strspn(canmount, "\t");
905 			canmount += strcspn(canmount, "\t");
906 			canmount += strspn(canmount, "\t");
907 			bool canmount_on = strncmp(canmount, "on", 2) == 0;
908 
909 			if (canmount_on)
910 				ret |= line_worker(line, cachent->d_name);
911 			else {
912 				if (filename == NULL)
913 					filename =
914 					    strdup(cachent->d_name) ?: "(?)";
915 
916 				struct line *l = calloc(1, sizeof (*l));
917 				char *nl = strdup(line);
918 				if (l == NULL || nl == NULL) {
919 					fprintf(stderr, PROGNAME "[%d]: "
920 					    "out of memory for \"%s\" in %s\n",
921 					    getpid(), line, cachent->d_name);
922 					free(l);
923 					free(nl);
924 					continue;
925 				}
926 				l->line = nl;
927 				l->fname = filename;
928 				l->next = lines_canmount_not_on;
929 				lines_canmount_not_on = l;
930 			}
931 		}
932 
933 		fclose(cachefile);
934 	}
935 	free(line);
936 
937 	while (lines_canmount_not_on) {
938 		struct line *l = lines_canmount_not_on;
939 		lines_canmount_not_on = l->next;
940 
941 		ret |= line_worker(l->line, l->fname);
942 		if (FREE_STATICS) {
943 			free(l->line);
944 			free(l);
945 		}
946 	}
947 
948 	if (debug) {
949 		struct timespec time_end = {};
950 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
951 
952 		struct rusage usage;
953 		getrusage(RUSAGE_SELF, &usage);
954 		printf(
955 		    "\n"
956 		    PROGNAME ": "
957 		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
958 		    (unsigned long long) usage.ru_utime.tv_sec,
959 		    (unsigned int) usage.ru_utime.tv_usec,
960 		    (unsigned long long) usage.ru_stime.tv_sec,
961 		    (unsigned int) usage.ru_stime.tv_usec,
962 		    usage.ru_maxrss * 1024);
963 
964 		if (time_start.tv_nsec > time_end.tv_nsec) {
965 			time_end.tv_nsec =
966 			    1000000000 + time_end.tv_nsec - time_start.tv_nsec;
967 			time_end.tv_sec -= 1;
968 		} else
969 			time_end.tv_nsec -= time_start.tv_nsec;
970 		time_end.tv_sec -= time_start.tv_sec;
971 
972 		if (time_init.tv_nsec > time_start.tv_nsec) {
973 			time_start.tv_nsec =
974 			    1000000000 + time_start.tv_nsec - time_init.tv_nsec;
975 			time_start.tv_sec -= 1;
976 		} else
977 			time_start.tv_nsec -= time_init.tv_nsec;
978 		time_start.tv_sec -= time_init.tv_sec;
979 
980 		time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
981 		time_init.tv_sec =
982 		    time_start.tv_sec + time_end.tv_sec +
983 		    time_init.tv_nsec / 1000000000;
984 		time_init.tv_nsec %= 1000000000;
985 
986 		printf(PROGNAME ": "
987 		    "total=%llu.%09llus = "
988 		    "init=%llu.%09llus + real=%llu.%09llus\n",
989 		    (unsigned long long) time_init.tv_sec,
990 		    (unsigned long long) time_init.tv_nsec,
991 		    (unsigned long long) time_start.tv_sec,
992 		    (unsigned long long) time_start.tv_nsec,
993 		    (unsigned long long) time_end.tv_sec,
994 		    (unsigned long long) time_end.tv_nsec);
995 
996 		fflush(stdout);
997 	}
998 
999 	if (FREE_STATICS) {
1000 		closedir(fslist_dir);
1001 		tdestroy(noauto_files, free);
1002 		tdestroy(known_pools, free);
1003 		regfree(&uri_regex);
1004 	}
1005 	_exit(ret);
1006 }
1007