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+.\\-]*\\):\\/\\/\\(.*\\)$"
69static regex_t uri_regex;
70
71static const char *destdir = "/tmp";
72static int destdir_fd = -1;
73
74static void *known_pools = NULL; /* tsearch() of C strings */
75static void *noauto_files = NULL; /* tsearch() of C strings */
76
77
78static char *
79systemd_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
120static void
121simplify_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
136static bool
137strendswith(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 */
147static char *
148systemd_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
185static FILE *
186fopenat(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
196static int
197line_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
734end:
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);
749err:
750	ret = 1;
751	goto end;
752}
753
754
755static int
756pool_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
774int
775main(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