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