xref: /freebsd/sys/contrib/openzfs/etc/systemd/system-generators/zfs-mount-generator.c (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
1*61145dc2SMartin Matuska // SPDX-License-Identifier: MIT
216038816SMartin Matuska /*
316038816SMartin Matuska  * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
416038816SMartin Matuska  * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
516038816SMartin Matuska  *
616038816SMartin Matuska  * Permission is hereby granted, free of charge, to any person obtaining
716038816SMartin Matuska  * a copy of this software and associated documentation files (the
816038816SMartin Matuska  * "Software"), to deal in the Software without restriction, including
916038816SMartin Matuska  * without limitation the rights to use, copy, modify, merge, publish,
1016038816SMartin Matuska  * distribute, sublicense, and/or sell copies of the Software, and to
1116038816SMartin Matuska  * permit persons to whom the Software is furnished to do so, subject to
1216038816SMartin Matuska  * the following conditions:
1316038816SMartin Matuska  *
1416038816SMartin Matuska  * The above copyright notice and this permission notice shall be
1516038816SMartin Matuska  * included in all copies or substantial portions of the Software.
1616038816SMartin Matuska  *
1716038816SMartin Matuska  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1816038816SMartin Matuska  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1916038816SMartin Matuska  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2016038816SMartin Matuska  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2116038816SMartin Matuska  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2216038816SMartin Matuska  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2316038816SMartin Matuska  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2416038816SMartin Matuska  */
2516038816SMartin Matuska 
2616038816SMartin Matuska 
2716038816SMartin Matuska #include <sys/resource.h>
2816038816SMartin Matuska #include <sys/types.h>
2916038816SMartin Matuska #include <sys/time.h>
3016038816SMartin Matuska #include <sys/stat.h>
3116038816SMartin Matuska #include <stdbool.h>
3216038816SMartin Matuska #include <unistd.h>
3316038816SMartin Matuska #include <fcntl.h>
3416038816SMartin Matuska #include <stdio.h>
3516038816SMartin Matuska #include <time.h>
3616038816SMartin Matuska #include <regex.h>
3716038816SMartin Matuska #include <search.h>
3816038816SMartin Matuska #include <dirent.h>
3916038816SMartin Matuska #include <string.h>
4016038816SMartin Matuska #include <stdlib.h>
4116038816SMartin Matuska #include <limits.h>
4216038816SMartin Matuska #include <errno.h>
4316038816SMartin Matuska #include <libzfs.h>
4416038816SMartin Matuska 
4516038816SMartin Matuska /*
46681ce946SMartin Matuska  * For debugging only.
47681ce946SMartin Matuska  *
48681ce946SMartin Matuska  * Free statics with trivial life-times,
49681ce946SMartin Matuska  * but saved line filenames are replaced with a static string.
5016038816SMartin Matuska  */
51681ce946SMartin Matuska #define	FREE_STATICS false
5216038816SMartin Matuska 
53681ce946SMartin Matuska #define	nitems(arr) (sizeof (arr) / sizeof (*arr))
54681ce946SMartin Matuska #define	STRCMP ((int(*)(const void *, const void *))&strcmp)
5516038816SMartin Matuska 
5616038816SMartin Matuska 
5716038816SMartin Matuska #define	PROGNAME "zfs-mount-generator"
5816038816SMartin Matuska #define	FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
5916038816SMartin Matuska #define	ZFS SBINDIR "/zfs"
6016038816SMartin Matuska 
6116038816SMartin Matuska #define	OUTPUT_HEADER \
6216038816SMartin Matuska 	"# Automatically generated by " PROGNAME "\n" \
6316038816SMartin Matuska 	"\n"
6416038816SMartin Matuska 
6516038816SMartin Matuska /*
6616038816SMartin Matuska  * Starts like the one in libzfs_util.c but also matches "//"
6716038816SMartin Matuska  * and captures until the end, since we actually use it for path extraxion
6816038816SMartin Matuska  */
6916038816SMartin Matuska #define	URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
7016038816SMartin Matuska static regex_t uri_regex;
7116038816SMartin Matuska 
7216038816SMartin Matuska static const char *destdir = "/tmp";
7316038816SMartin Matuska static int destdir_fd = -1;
7416038816SMartin Matuska 
7516038816SMartin Matuska static void *known_pools = NULL; /* tsearch() of C strings */
76681ce946SMartin Matuska static void *noauto_files = NULL; /* tsearch() of C strings */
7716038816SMartin Matuska 
7816038816SMartin Matuska 
7916038816SMartin Matuska static char *
systemd_escape(const char * input,const char * prepend,const char * append)8016038816SMartin Matuska systemd_escape(const char *input, const char *prepend, const char *append)
8116038816SMartin Matuska {
8216038816SMartin Matuska 	size_t len = strlen(input);
8316038816SMartin Matuska 	size_t applen = strlen(append);
8416038816SMartin Matuska 	size_t prelen = strlen(prepend);
8516038816SMartin Matuska 	char *ret = malloc(4 * len + prelen + applen + 1);
86681ce946SMartin Matuska 	if (!ret) {
87681ce946SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: "
88681ce946SMartin Matuska 		    "out of memory to escape \"%s%s%s\"!\n",
89681ce946SMartin Matuska 		    getpid(), prepend, input, append);
90681ce946SMartin Matuska 		return (NULL);
91681ce946SMartin Matuska 	}
9216038816SMartin Matuska 
9316038816SMartin Matuska 	memcpy(ret, prepend, prelen);
9416038816SMartin Matuska 	char *out = ret + prelen;
9516038816SMartin Matuska 
9616038816SMartin Matuska 	const char *cur = input;
9716038816SMartin Matuska 	if (*cur == '.') {
9816038816SMartin Matuska 		memcpy(out, "\\x2e", 4);
9916038816SMartin Matuska 		out += 4;
10016038816SMartin Matuska 		++cur;
10116038816SMartin Matuska 	}
10216038816SMartin Matuska 	for (; *cur; ++cur) {
10316038816SMartin Matuska 		if (*cur == '/')
10416038816SMartin Matuska 			*(out++) = '-';
10516038816SMartin Matuska 		else if (strchr(
10616038816SMartin Matuska 		    "0123456789"
10716038816SMartin Matuska 		    "abcdefghijklmnopqrstuvwxyz"
10816038816SMartin Matuska 		    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
10916038816SMartin Matuska 		    ":_.", *cur))
11016038816SMartin Matuska 			*(out++) = *cur;
11116038816SMartin Matuska 		else {
11216038816SMartin Matuska 			sprintf(out, "\\x%02x", (int)*cur);
11316038816SMartin Matuska 			out += 4;
11416038816SMartin Matuska 		}
11516038816SMartin Matuska 	}
11616038816SMartin Matuska 
11716038816SMartin Matuska 	memcpy(out, append, applen + 1);
11816038816SMartin Matuska 	return (ret);
11916038816SMartin Matuska }
12016038816SMartin Matuska 
12116038816SMartin Matuska static void
simplify_path(char * path)12216038816SMartin Matuska simplify_path(char *path)
12316038816SMartin Matuska {
12416038816SMartin Matuska 	char *out = path;
12516038816SMartin Matuska 	for (char *cur = path; *cur; ++cur) {
12616038816SMartin Matuska 		if (*cur == '/') {
12716038816SMartin Matuska 			while (*(cur + 1) == '/')
12816038816SMartin Matuska 				++cur;
12916038816SMartin Matuska 			*(out++) = '/';
13016038816SMartin Matuska 		} else
13116038816SMartin Matuska 			*(out++) = *cur;
13216038816SMartin Matuska 	}
13316038816SMartin Matuska 
13416038816SMartin Matuska 	*(out++) = '\0';
13516038816SMartin Matuska }
13616038816SMartin Matuska 
13716038816SMartin Matuska static bool
strendswith(const char * what,const char * suff)13816038816SMartin Matuska strendswith(const char *what, const char *suff)
13916038816SMartin Matuska {
14016038816SMartin Matuska 	size_t what_l = strlen(what);
14116038816SMartin Matuska 	size_t suff_l = strlen(suff);
14216038816SMartin Matuska 
14316038816SMartin Matuska 	return ((what_l >= suff_l) &&
14416038816SMartin Matuska 	    (strcmp(what + what_l - suff_l, suff) == 0));
14516038816SMartin Matuska }
14616038816SMartin Matuska 
14716038816SMartin Matuska /* Assumes already-simplified path, doesn't modify input */
14816038816SMartin Matuska static char *
systemd_escape_path(char * input,const char * prepend,const char * append)14916038816SMartin Matuska systemd_escape_path(char *input, const char *prepend, const char *append)
15016038816SMartin Matuska {
15116038816SMartin Matuska 	if (strcmp(input, "/") == 0) {
15216038816SMartin Matuska 		char *ret;
153681ce946SMartin Matuska 		if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
154681ce946SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: "
155681ce946SMartin Matuska 			    "out of memory to escape \"%s%s%s\"!\n",
156681ce946SMartin Matuska 			    getpid(), prepend, input, append);
157681ce946SMartin Matuska 			ret = NULL;
158681ce946SMartin Matuska 		}
15916038816SMartin Matuska 		return (ret);
16016038816SMartin Matuska 	} else {
16116038816SMartin Matuska 		/*
16216038816SMartin Matuska 		 * path_is_normalized() (flattened for absolute paths here),
16316038816SMartin Matuska 		 * required for proper escaping
16416038816SMartin Matuska 		 */
16516038816SMartin Matuska 		if (strstr(input, "/./") || strstr(input, "/../") ||
16616038816SMartin Matuska 		    strendswith(input, "/.") || strendswith(input, "/.."))
16716038816SMartin Matuska 			return (NULL);
16816038816SMartin Matuska 
16916038816SMartin Matuska 
17016038816SMartin Matuska 		if (input[0] == '/')
17116038816SMartin Matuska 			++input;
17216038816SMartin Matuska 
17316038816SMartin Matuska 		char *back = &input[strlen(input) - 1];
17416038816SMartin Matuska 		bool deslash = *back == '/';
17516038816SMartin Matuska 		if (deslash)
17616038816SMartin Matuska 			*back = '\0';
17716038816SMartin Matuska 
17816038816SMartin Matuska 		char *ret = systemd_escape(input, prepend, append);
17916038816SMartin Matuska 
18016038816SMartin Matuska 		if (deslash)
18116038816SMartin Matuska 			*back = '/';
18216038816SMartin Matuska 		return (ret);
18316038816SMartin Matuska 	}
18416038816SMartin Matuska }
18516038816SMartin Matuska 
18616038816SMartin Matuska static FILE *
fopenat(int dirfd,const char * pathname,int flags,const char * stream_mode,mode_t mode)18716038816SMartin Matuska fopenat(int dirfd, const char *pathname, int flags,
18816038816SMartin Matuska     const char *stream_mode, mode_t mode)
18916038816SMartin Matuska {
19016038816SMartin Matuska 	int fd = openat(dirfd, pathname, flags, mode);
19116038816SMartin Matuska 	if (fd < 0)
19216038816SMartin Matuska 		return (NULL);
19316038816SMartin Matuska 
19416038816SMartin Matuska 	return (fdopen(fd, stream_mode));
19516038816SMartin Matuska }
19616038816SMartin Matuska 
19716038816SMartin Matuska static int
line_worker(char * line,const char * cachefile)19816038816SMartin Matuska line_worker(char *line, const char *cachefile)
19916038816SMartin Matuska {
200681ce946SMartin Matuska 	int ret = 0;
201681ce946SMartin Matuska 	void *tofree_all[8];
202681ce946SMartin Matuska 	void **tofree = tofree_all;
203681ce946SMartin Matuska 
20416038816SMartin Matuska 	char *toktmp;
20516038816SMartin Matuska 	/* BEGIN CSTYLED */
20616038816SMartin Matuska 	const char *dataset                     = strtok_r(line, "\t", &toktmp);
20716038816SMartin Matuska 	      char *p_mountpoint                = strtok_r(NULL, "\t", &toktmp);
20816038816SMartin Matuska 	const char *p_canmount                  = strtok_r(NULL, "\t", &toktmp);
20916038816SMartin Matuska 	const char *p_atime                     = strtok_r(NULL, "\t", &toktmp);
21016038816SMartin Matuska 	const char *p_relatime                  = strtok_r(NULL, "\t", &toktmp);
21116038816SMartin Matuska 	const char *p_devices                   = strtok_r(NULL, "\t", &toktmp);
21216038816SMartin Matuska 	const char *p_exec                      = strtok_r(NULL, "\t", &toktmp);
21316038816SMartin Matuska 	const char *p_readonly                  = strtok_r(NULL, "\t", &toktmp);
21416038816SMartin Matuska 	const char *p_setuid                    = strtok_r(NULL, "\t", &toktmp);
21516038816SMartin Matuska 	const char *p_nbmand                    = strtok_r(NULL, "\t", &toktmp);
21616038816SMartin Matuska 	const char *p_encroot                   = strtok_r(NULL, "\t", &toktmp) ?: "-";
21716038816SMartin Matuska 	      char *p_keyloc                    = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
21816038816SMartin Matuska 	const char *p_systemd_requires          = strtok_r(NULL, "\t", &toktmp) ?: "-";
21916038816SMartin Matuska 	const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
22016038816SMartin Matuska 	const char *p_systemd_before            = strtok_r(NULL, "\t", &toktmp) ?: "-";
22116038816SMartin Matuska 	const char *p_systemd_after             = strtok_r(NULL, "\t", &toktmp) ?: "-";
22216038816SMartin Matuska 	      char *p_systemd_wantedby          = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
22316038816SMartin Matuska 	      char *p_systemd_requiredby        = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
22416038816SMartin Matuska 	const char *p_systemd_nofail            = strtok_r(NULL, "\t", &toktmp) ?: "-";
22516038816SMartin Matuska 	const char *p_systemd_ignore            = strtok_r(NULL, "\t", &toktmp) ?: "-";
22616038816SMartin Matuska 	/* END CSTYLED */
22716038816SMartin Matuska 
22815f0b8c3SMartin Matuska 	size_t pool_len = strlen(dataset);
22915f0b8c3SMartin Matuska 	if ((toktmp = strchr(dataset, '/')) != NULL)
23015f0b8c3SMartin Matuska 		pool_len = toktmp - dataset;
23115f0b8c3SMartin Matuska 	const char *pool = *(tofree++) = strndup(dataset, pool_len);
23216038816SMartin Matuska 
23316038816SMartin Matuska 	if (p_nbmand == NULL) {
23416038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
23516038816SMartin Matuska 		    getpid(), dataset);
236681ce946SMartin Matuska 		goto err;
23716038816SMartin Matuska 	}
23816038816SMartin Matuska 
23916038816SMartin Matuska 	/* Minimal pre-requisites to mount a ZFS dataset */
24016038816SMartin Matuska 	const char *after = "zfs-import.target";
24116038816SMartin Matuska 	const char *wants = "zfs-import.target";
24216038816SMartin Matuska 	const char *bindsto = NULL;
24316038816SMartin Matuska 	char *wantedby = NULL;
24416038816SMartin Matuska 	char *requiredby = NULL;
24516038816SMartin Matuska 	bool noauto = false;
24616038816SMartin Matuska 	bool wantedby_append = true;
24716038816SMartin Matuska 
24816038816SMartin Matuska 	/*
24916038816SMartin Matuska 	 * zfs-import.target is not needed if the pool is already imported.
25016038816SMartin Matuska 	 * This avoids a dependency loop on root-on-ZFS systems:
25116038816SMartin Matuska 	 *   systemd-random-seed.service After (via RequiresMountsFor)
25216038816SMartin Matuska 	 *   var-lib.mount After
25316038816SMartin Matuska 	 *   zfs-import.target After
25416038816SMartin Matuska 	 *   zfs-import-{cache,scan}.service After
25516038816SMartin Matuska 	 *   cryptsetup.service After
25616038816SMartin Matuska 	 *   systemd-random-seed.service
25716038816SMartin Matuska 	 */
25816038816SMartin Matuska 	if (tfind(pool, &known_pools, STRCMP)) {
25916038816SMartin Matuska 		after = "";
26016038816SMartin Matuska 		wants = "";
26116038816SMartin Matuska 	}
26216038816SMartin Matuska 
26316038816SMartin Matuska 	if (strcmp(p_systemd_after, "-") == 0)
26416038816SMartin Matuska 		p_systemd_after = NULL;
26516038816SMartin Matuska 	if (strcmp(p_systemd_before, "-") == 0)
26616038816SMartin Matuska 		p_systemd_before = NULL;
26716038816SMartin Matuska 	if (strcmp(p_systemd_requires, "-") == 0)
26816038816SMartin Matuska 		p_systemd_requires = NULL;
26916038816SMartin Matuska 	if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
27016038816SMartin Matuska 		p_systemd_requiresmountsfor = NULL;
27116038816SMartin Matuska 
27216038816SMartin Matuska 
27316038816SMartin Matuska 	if (strcmp(p_encroot, "-") != 0) {
274681ce946SMartin Matuska 		char *keyloadunit = *(tofree++) =
27516038816SMartin Matuska 		    systemd_escape(p_encroot, "zfs-load-key@", ".service");
276681ce946SMartin Matuska 		if (keyloadunit == NULL)
277681ce946SMartin Matuska 			goto err;
27816038816SMartin Matuska 
27916038816SMartin Matuska 		if (strcmp(dataset, p_encroot) == 0) {
28016038816SMartin Matuska 			const char *keymountdep = NULL;
28116038816SMartin Matuska 			bool is_prompt = false;
282681ce946SMartin Matuska 			bool need_network = false;
28316038816SMartin Matuska 
28416038816SMartin Matuska 			regmatch_t uri_matches[3];
28516038816SMartin Matuska 			if (regexec(&uri_regex, p_keyloc,
286681ce946SMartin Matuska 			    nitems(uri_matches), uri_matches, 0) == 0) {
287681ce946SMartin Matuska 				p_keyloc[uri_matches[1].rm_eo] = '\0';
28816038816SMartin Matuska 				p_keyloc[uri_matches[2].rm_eo] = '\0';
289681ce946SMartin Matuska 				const char *scheme =
290681ce946SMartin Matuska 				    &p_keyloc[uri_matches[1].rm_so];
29116038816SMartin Matuska 				const char *path =
29216038816SMartin Matuska 				    &p_keyloc[uri_matches[2].rm_so];
29316038816SMartin Matuska 
294681ce946SMartin Matuska 				if (strcmp(scheme, "https") == 0 ||
295681ce946SMartin Matuska 				    strcmp(scheme, "http") == 0)
296681ce946SMartin Matuska 					need_network = true;
297681ce946SMartin Matuska 				else
29816038816SMartin Matuska 					keymountdep = path;
29916038816SMartin Matuska 			} else {
30016038816SMartin Matuska 				if (strcmp(p_keyloc, "prompt") != 0)
30116038816SMartin Matuska 					fprintf(stderr, PROGNAME "[%d]: %s: "
30216038816SMartin Matuska 					    "unknown non-URI keylocation=%s\n",
30316038816SMartin Matuska 					    getpid(), dataset, p_keyloc);
30416038816SMartin Matuska 
30516038816SMartin Matuska 				is_prompt = true;
30616038816SMartin Matuska 			}
30716038816SMartin Matuska 
30816038816SMartin Matuska 
30916038816SMartin Matuska 			/* Generate the key-load .service unit */
31016038816SMartin Matuska 			FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
31116038816SMartin Matuska 			    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
31216038816SMartin Matuska 			    0644);
31316038816SMartin Matuska 			if (!keyloadunit_f) {
31416038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
31516038816SMartin Matuska 				    "couldn't open %s under %s: %s\n",
31616038816SMartin Matuska 				    getpid(), dataset, keyloadunit, destdir,
31716038816SMartin Matuska 				    strerror(errno));
318681ce946SMartin Matuska 				goto err;
31916038816SMartin Matuska 			}
32016038816SMartin Matuska 
32116038816SMartin Matuska 			fprintf(keyloadunit_f,
32216038816SMartin Matuska 			    OUTPUT_HEADER
32316038816SMartin Matuska 			    "[Unit]\n"
32416038816SMartin Matuska 			    "Description=Load ZFS key for %s\n"
32516038816SMartin Matuska 			    "SourcePath=" FSLIST "/%s\n"
32616038816SMartin Matuska 			    "Documentation=man:zfs-mount-generator(8)\n"
32716038816SMartin Matuska 			    "DefaultDependencies=no\n"
32816038816SMartin Matuska 			    "Wants=%s\n"
32916038816SMartin Matuska 			    "After=%s\n",
33016038816SMartin Matuska 			    dataset, cachefile, wants, after);
33116038816SMartin Matuska 
332681ce946SMartin Matuska 			if (need_network)
333681ce946SMartin Matuska 				fprintf(keyloadunit_f,
334681ce946SMartin Matuska 				    "Wants=network-online.target\n"
335681ce946SMartin Matuska 				    "After=network-online.target\n");
336681ce946SMartin Matuska 
33716038816SMartin Matuska 			if (p_systemd_requires)
33816038816SMartin Matuska 				fprintf(keyloadunit_f,
33916038816SMartin Matuska 				    "Requires=%s\n", p_systemd_requires);
34016038816SMartin Matuska 
34116038816SMartin Matuska 			if (p_systemd_requiresmountsfor)
34216038816SMartin Matuska 				fprintf(keyloadunit_f,
343681ce946SMartin Matuska 				    "RequiresMountsFor=%s\n",
344681ce946SMartin Matuska 				    p_systemd_requiresmountsfor);
34516038816SMartin Matuska 			if (keymountdep)
34616038816SMartin Matuska 				fprintf(keyloadunit_f,
347681ce946SMartin Matuska 				    "RequiresMountsFor='%s'\n", keymountdep);
34816038816SMartin Matuska 
34916038816SMartin Matuska 			/* BEGIN CSTYLED */
35016038816SMartin Matuska 			fprintf(keyloadunit_f,
35116038816SMartin Matuska 			    "\n"
35216038816SMartin Matuska 			    "[Service]\n"
35316038816SMartin Matuska 			    "Type=oneshot\n"
35416038816SMartin Matuska 			    "RemainAfterExit=yes\n"
35516038816SMartin Matuska 			    "# This avoids a dependency loop involving systemd-journald.socket if this\n"
35616038816SMartin Matuska 			    "# dataset is a parent of the root filesystem.\n"
35716038816SMartin Matuska 			    "StandardOutput=null\n"
35816038816SMartin Matuska 			    "StandardError=null\n"
35916038816SMartin Matuska 			    "ExecStart=/bin/sh -euc '"
36016038816SMartin Matuska 			        "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
36116038816SMartin Matuska 			    dataset);
36216038816SMartin Matuska 			if (is_prompt)
36316038816SMartin Matuska 				fprintf(keyloadunit_f,
36416038816SMartin Matuska 				    "for i in 1 2 3; do "
36516038816SMartin Matuska 				        "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
36616038816SMartin Matuska 				        "" ZFS " load-key \"%s\" && exit 0;"
36716038816SMartin Matuska 				    "done;"
36816038816SMartin Matuska 				    "exit 1",
36916038816SMartin Matuska 				    dataset, dataset, dataset);
37016038816SMartin Matuska 			else
37116038816SMartin Matuska 				fprintf(keyloadunit_f,
37216038816SMartin Matuska 				    "exec " ZFS " load-key \"%s\"",
37316038816SMartin Matuska 				    dataset);
37416038816SMartin Matuska 
37516038816SMartin Matuska 			fprintf(keyloadunit_f,
37616038816SMartin Matuska 				"'\n"
37716038816SMartin Matuska 				"ExecStop=/bin/sh -euc '"
37816038816SMartin Matuska 				    "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
37916038816SMartin Matuska 				    "exec " ZFS " unload-key \"%s\""
38016038816SMartin Matuska 				"'\n",
38116038816SMartin Matuska 				dataset, dataset);
38216038816SMartin Matuska 			/* END CSTYLED */
38316038816SMartin Matuska 
38416038816SMartin Matuska 			(void) fclose(keyloadunit_f);
38516038816SMartin Matuska 		}
38616038816SMartin Matuska 
38716038816SMartin Matuska 		/* Update dependencies for the mount file to want this */
38816038816SMartin Matuska 		bindsto = keyloadunit;
38916038816SMartin Matuska 		if (after[0] == '\0')
39016038816SMartin Matuska 			after = keyloadunit;
39116038816SMartin Matuska 		else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
392681ce946SMartin Matuska 			after = *(tofree++) = toktmp;
393681ce946SMartin Matuska 		else {
394681ce946SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: %s: "
395681ce946SMartin Matuska 			    "out of memory to generate after=\"%s %s\"!\n",
396681ce946SMartin Matuska 			    getpid(), dataset, after, keyloadunit);
397681ce946SMartin Matuska 			goto err;
398681ce946SMartin Matuska 		}
39916038816SMartin Matuska 	}
40016038816SMartin Matuska 
40116038816SMartin Matuska 
40216038816SMartin Matuska 	/* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
40316038816SMartin Matuska 	if (strcmp(p_systemd_ignore, "-") == 0 ||
40416038816SMartin Matuska 	    strcmp(p_systemd_ignore, "off") == 0) {
40516038816SMartin Matuska 		/* ok */
40616038816SMartin Matuska 	} else if (strcmp(p_systemd_ignore, "on") == 0)
407681ce946SMartin Matuska 		goto end;
40816038816SMartin Matuska 	else {
40916038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: "
41016038816SMartin Matuska 		    "invalid org.openzfs.systemd:ignore=%s\n",
41116038816SMartin Matuska 		    getpid(), dataset, p_systemd_ignore);
412681ce946SMartin Matuska 		goto err;
41316038816SMartin Matuska 	}
41416038816SMartin Matuska 
41516038816SMartin Matuska 	/* Check for canmount */
41616038816SMartin Matuska 	if (strcmp(p_canmount, "on") == 0) {
41716038816SMartin Matuska 		/* ok */
41816038816SMartin Matuska 	} else if (strcmp(p_canmount, "noauto") == 0)
41916038816SMartin Matuska 		noauto = true;
42016038816SMartin Matuska 	else if (strcmp(p_canmount, "off") == 0)
421681ce946SMartin Matuska 		goto end;
42216038816SMartin Matuska 	else {
42316038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
42416038816SMartin Matuska 		    getpid(), dataset, p_canmount);
425681ce946SMartin Matuska 		goto err;
42616038816SMartin Matuska 	}
42716038816SMartin Matuska 
42816038816SMartin Matuska 	/* Check for legacy and blank mountpoints */
42916038816SMartin Matuska 	if (strcmp(p_mountpoint, "legacy") == 0 ||
43016038816SMartin Matuska 	    strcmp(p_mountpoint, "none") == 0)
431681ce946SMartin Matuska 		goto end;
43216038816SMartin Matuska 	else if (p_mountpoint[0] != '/') {
43316038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
43416038816SMartin Matuska 		    getpid(), dataset, p_mountpoint);
435681ce946SMartin Matuska 		goto err;
43616038816SMartin Matuska 	}
43716038816SMartin Matuska 
43816038816SMartin Matuska 	/* Escape the mountpoint per systemd policy */
43916038816SMartin Matuska 	simplify_path(p_mountpoint);
44016038816SMartin Matuska 	const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
44116038816SMartin Matuska 	if (mountfile == NULL) {
44216038816SMartin Matuska 		fprintf(stderr,
44316038816SMartin Matuska 		    PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
44416038816SMartin Matuska 		    getpid(), dataset, p_mountpoint);
445681ce946SMartin Matuska 		goto err;
44616038816SMartin Matuska 	}
44716038816SMartin Matuska 
44816038816SMartin Matuska 
44916038816SMartin Matuska 	/*
45016038816SMartin Matuska 	 * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
45116038816SMartin Matuska 	 *
45216038816SMartin Matuska 	 * The longest string achievable here is
45316038816SMartin Matuska 	 * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
45416038816SMartin Matuska 	 */
45516038816SMartin Matuska 	char opts[64] = "";
45616038816SMartin Matuska 
45716038816SMartin Matuska 	/* atime */
45816038816SMartin Matuska 	if (strcmp(p_atime, "on") == 0) {
45916038816SMartin Matuska 		/* relatime */
46016038816SMartin Matuska 		if (strcmp(p_relatime, "on") == 0)
46116038816SMartin Matuska 			strcat(opts, ",atime,relatime");
46216038816SMartin Matuska 		else if (strcmp(p_relatime, "off") == 0)
46316038816SMartin Matuska 			strcat(opts, ",atime,strictatime");
46416038816SMartin Matuska 		else
46516038816SMartin Matuska 			fprintf(stderr,
46616038816SMartin Matuska 			    PROGNAME "[%d]: %s: invalid relatime=%s\n",
46716038816SMartin Matuska 			    getpid(), dataset, p_relatime);
46816038816SMartin Matuska 	} else if (strcmp(p_atime, "off") == 0) {
46916038816SMartin Matuska 		strcat(opts, ",noatime");
47016038816SMartin Matuska 	} else
47116038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
47216038816SMartin Matuska 		    getpid(), dataset, p_atime);
47316038816SMartin Matuska 
47416038816SMartin Matuska 	/* devices */
47516038816SMartin Matuska 	if (strcmp(p_devices, "on") == 0)
47616038816SMartin Matuska 		strcat(opts, ",dev");
47716038816SMartin Matuska 	else if (strcmp(p_devices, "off") == 0)
47816038816SMartin Matuska 		strcat(opts, ",nodev");
47916038816SMartin Matuska 	else
48016038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
48116038816SMartin Matuska 		    getpid(), dataset, p_devices);
48216038816SMartin Matuska 
48316038816SMartin Matuska 	/* exec */
48416038816SMartin Matuska 	if (strcmp(p_exec, "on") == 0)
48516038816SMartin Matuska 		strcat(opts, ",exec");
48616038816SMartin Matuska 	else if (strcmp(p_exec, "off") == 0)
48716038816SMartin Matuska 		strcat(opts, ",noexec");
48816038816SMartin Matuska 	else
48916038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
49016038816SMartin Matuska 		    getpid(), dataset, p_exec);
49116038816SMartin Matuska 
49216038816SMartin Matuska 	/* readonly */
49316038816SMartin Matuska 	if (strcmp(p_readonly, "on") == 0)
49416038816SMartin Matuska 		strcat(opts, ",ro");
49516038816SMartin Matuska 	else if (strcmp(p_readonly, "off") == 0)
49616038816SMartin Matuska 		strcat(opts, ",rw");
49716038816SMartin Matuska 	else
49816038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
49916038816SMartin Matuska 		    getpid(), dataset, p_readonly);
50016038816SMartin Matuska 
50116038816SMartin Matuska 	/* setuid */
50216038816SMartin Matuska 	if (strcmp(p_setuid, "on") == 0)
50316038816SMartin Matuska 		strcat(opts, ",suid");
50416038816SMartin Matuska 	else if (strcmp(p_setuid, "off") == 0)
50516038816SMartin Matuska 		strcat(opts, ",nosuid");
50616038816SMartin Matuska 	else
50716038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
50816038816SMartin Matuska 		    getpid(), dataset, p_setuid);
50916038816SMartin Matuska 
51016038816SMartin Matuska 	/* nbmand */
51116038816SMartin Matuska 	if (strcmp(p_nbmand, "on") == 0)
51216038816SMartin Matuska 		strcat(opts, ",mand");
51316038816SMartin Matuska 	else if (strcmp(p_nbmand, "off") == 0)
51416038816SMartin Matuska 		strcat(opts, ",nomand");
51516038816SMartin Matuska 	else
51616038816SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
51716038816SMartin Matuska 		    getpid(), dataset, p_setuid);
51816038816SMartin Matuska 
51916038816SMartin Matuska 	if (strcmp(p_systemd_wantedby, "-") != 0) {
52016038816SMartin Matuska 		noauto = true;
52116038816SMartin Matuska 
52216038816SMartin Matuska 		if (strcmp(p_systemd_wantedby, "none") != 0)
52316038816SMartin Matuska 			wantedby = p_systemd_wantedby;
52416038816SMartin Matuska 	}
52516038816SMartin Matuska 
52616038816SMartin Matuska 	if (strcmp(p_systemd_requiredby, "-") != 0) {
52716038816SMartin Matuska 		noauto = true;
52816038816SMartin Matuska 
52916038816SMartin Matuska 		if (strcmp(p_systemd_requiredby, "none") != 0)
53016038816SMartin Matuska 			requiredby = p_systemd_requiredby;
53116038816SMartin Matuska 	}
53216038816SMartin Matuska 
53316038816SMartin Matuska 	/*
53416038816SMartin Matuska 	 * For datasets with canmount=on, a dependency is created for
53516038816SMartin Matuska 	 * local-fs.target by default. To avoid regressions, this dependency
53616038816SMartin Matuska 	 * is reduced to "wants" rather than "requires" when nofail!=off.
53716038816SMartin Matuska 	 * **THIS MAY CHANGE**
53816038816SMartin Matuska 	 * noauto=on disables this behavior completely.
53916038816SMartin Matuska 	 */
54016038816SMartin Matuska 	if (!noauto) {
54116038816SMartin Matuska 		if (strcmp(p_systemd_nofail, "off") == 0)
54216038816SMartin Matuska 			requiredby = strdupa("local-fs.target");
54316038816SMartin Matuska 		else {
54416038816SMartin Matuska 			wantedby = strdupa("local-fs.target");
54516038816SMartin Matuska 			wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
54616038816SMartin Matuska 		}
54716038816SMartin Matuska 	}
54816038816SMartin Matuska 
54916038816SMartin Matuska 	/*
55016038816SMartin Matuska 	 * Handle existing files:
55116038816SMartin Matuska 	 * 1.	We never overwrite existing files, although we may delete
55216038816SMartin Matuska 	 * 	files if we're sure they were created by us. (see 5.)
55316038816SMartin Matuska 	 * 2.	We handle files differently based on canmount.
55416038816SMartin Matuska 	 * 	Units with canmount=on always have precedence over noauto.
555681ce946SMartin Matuska 	 * 	This is enforced by processing these units before all others.
55616038816SMartin Matuska 	 * 	It is important to use p_canmount and not noauto here,
55716038816SMartin Matuska 	 * 	since we categorise by canmount while other properties,
55816038816SMartin Matuska 	 * 	e.g. org.openzfs.systemd:wanted-by, also modify noauto.
55916038816SMartin Matuska 	 * 3.	If no unit file exists for a noauto dataset, we create one.
56016038816SMartin Matuska 	 * 	Additionally, we use noauto_files to track the unit file names
56116038816SMartin Matuska 	 * 	(which are the systemd-escaped mountpoints) of all (exclusively)
56216038816SMartin Matuska 	 * 	noauto datasets that had a file created.
563681ce946SMartin Matuska 	 * 4.	If the file to be created is found in the tracking tree,
56416038816SMartin Matuska 	 * 	we do NOT create it.
56516038816SMartin Matuska 	 * 5.	If a file exists for a noauto dataset,
56616038816SMartin Matuska 	 * 	we check whether the file name is in the array.
56716038816SMartin Matuska 	 * 	If it is, we have multiple noauto datasets for the same
56816038816SMartin Matuska 	 * 	mountpoint. In such cases, we remove the file for safety.
56916038816SMartin Matuska 	 * 	We leave the file name in the tracking array to avoid
57016038816SMartin Matuska 	 * 	further noauto datasets creating a file for this path again.
57116038816SMartin Matuska 	 */
57216038816SMartin Matuska 
57316038816SMartin Matuska 	struct stat stbuf;
57416038816SMartin Matuska 	bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
575681ce946SMartin Matuska 	bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
57616038816SMartin Matuska 
577681ce946SMartin Matuska 	*(tofree++) = (void *)mountfile;
57816038816SMartin Matuska 	if (already_exists) {
57916038816SMartin Matuska 		if (is_known) {
580681ce946SMartin Matuska 			/* If it's in noauto_files, we must be noauto too */
58116038816SMartin Matuska 
58216038816SMartin Matuska 			/* See 5 */
58316038816SMartin Matuska 			errno = 0;
58416038816SMartin Matuska 			(void) unlinkat(destdir_fd, mountfile, 0);
58516038816SMartin Matuska 
58616038816SMartin Matuska 			/* See 2 */
58716038816SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: %s: "
58816038816SMartin Matuska 			    "removing duplicate noauto unit %s%s%s\n",
58916038816SMartin Matuska 			    getpid(), dataset, mountfile,
59016038816SMartin Matuska 			    errno ? "" : " failed: ",
59116038816SMartin Matuska 			    errno ? "" : strerror(errno));
59216038816SMartin Matuska 		} else {
59316038816SMartin Matuska 			/* Don't log for canmount=noauto */
59416038816SMartin Matuska 			if (strcmp(p_canmount, "on") == 0)
59516038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
59616038816SMartin Matuska 				    "%s already exists. Skipping.\n",
59716038816SMartin Matuska 				    getpid(), dataset, mountfile);
59816038816SMartin Matuska 		}
59916038816SMartin Matuska 
60016038816SMartin Matuska 		/* File exists: skip current dataset */
601681ce946SMartin Matuska 		goto end;
60216038816SMartin Matuska 	} else {
60316038816SMartin Matuska 		if (is_known) {
60416038816SMartin Matuska 			/* See 4 */
605681ce946SMartin Matuska 			goto end;
60616038816SMartin Matuska 		} else if (strcmp(p_canmount, "noauto") == 0) {
607681ce946SMartin Matuska 			if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
60816038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
609681ce946SMartin Matuska 				    "out of memory for noauto datasets! "
610681ce946SMartin Matuska 				    "Not tracking %s.\n",
611681ce946SMartin Matuska 				    getpid(), dataset, mountfile);
612681ce946SMartin Matuska 			else
613681ce946SMartin Matuska 				/* mountfile escaped to noauto_files */
614681ce946SMartin Matuska 				*(--tofree) = NULL;
61516038816SMartin Matuska 		}
61616038816SMartin Matuska 	}
61716038816SMartin Matuska 
61816038816SMartin Matuska 
61916038816SMartin Matuska 	FILE *mountfile_f = fopenat(destdir_fd, mountfile,
62016038816SMartin Matuska 	    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
62116038816SMartin Matuska 	if (!mountfile_f) {
62216038816SMartin Matuska 		fprintf(stderr,
62316038816SMartin Matuska 		    PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
62416038816SMartin Matuska 		    getpid(), dataset, mountfile, destdir, strerror(errno));
625681ce946SMartin Matuska 		goto err;
62616038816SMartin Matuska 	}
62716038816SMartin Matuska 
62816038816SMartin Matuska 	fprintf(mountfile_f,
62916038816SMartin Matuska 	    OUTPUT_HEADER
63016038816SMartin Matuska 	    "[Unit]\n"
63116038816SMartin Matuska 	    "SourcePath=" FSLIST "/%s\n"
63216038816SMartin Matuska 	    "Documentation=man:zfs-mount-generator(8)\n"
63316038816SMartin Matuska 	    "\n"
63416038816SMartin Matuska 	    "Before=",
63516038816SMartin Matuska 	    cachefile);
63616038816SMartin Matuska 
63716038816SMartin Matuska 	if (p_systemd_before)
63816038816SMartin Matuska 		fprintf(mountfile_f, "%s ", p_systemd_before);
63916038816SMartin Matuska 	fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
64016038816SMartin Matuska 	if (requiredby)
64116038816SMartin Matuska 		fprintf(mountfile_f, " %s", requiredby);
64216038816SMartin Matuska 	if (wantedby && wantedby_append)
64316038816SMartin Matuska 		fprintf(mountfile_f, " %s", wantedby);
64416038816SMartin Matuska 
64516038816SMartin Matuska 	fprintf(mountfile_f,
64616038816SMartin Matuska 	    "\n"
64716038816SMartin Matuska 	    "After=");
64816038816SMartin Matuska 	if (p_systemd_after)
64916038816SMartin Matuska 		fprintf(mountfile_f, "%s ", p_systemd_after);
65016038816SMartin Matuska 	fprintf(mountfile_f, "%s\n", after);
65116038816SMartin Matuska 
65216038816SMartin Matuska 	fprintf(mountfile_f, "Wants=%s\n", wants);
65316038816SMartin Matuska 
65416038816SMartin Matuska 	if (bindsto)
65516038816SMartin Matuska 		fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
65616038816SMartin Matuska 	if (p_systemd_requires)
65716038816SMartin Matuska 		fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
65816038816SMartin Matuska 	if (p_systemd_requiresmountsfor)
65916038816SMartin Matuska 		fprintf(mountfile_f,
66016038816SMartin Matuska 		    "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
66116038816SMartin Matuska 
66216038816SMartin Matuska 	fprintf(mountfile_f,
66316038816SMartin Matuska 	    "\n"
66416038816SMartin Matuska 	    "[Mount]\n"
66516038816SMartin Matuska 	    "Where=%s\n"
66616038816SMartin Matuska 	    "What=%s\n"
66716038816SMartin Matuska 	    "Type=zfs\n"
66816038816SMartin Matuska 	    "Options=defaults%s,zfsutil\n",
66916038816SMartin Matuska 	    p_mountpoint, dataset, opts);
67016038816SMartin Matuska 
67116038816SMartin Matuska 	(void) fclose(mountfile_f);
67216038816SMartin Matuska 
67316038816SMartin Matuska 	if (!requiredby && !wantedby)
674681ce946SMartin Matuska 		goto end;
67516038816SMartin Matuska 
67616038816SMartin Matuska 	/* Finally, create the appropriate dependencies */
67716038816SMartin Matuska 	char *linktgt;
678681ce946SMartin Matuska 	if (asprintf(&linktgt, "../%s", mountfile) == -1) {
679681ce946SMartin Matuska 		fprintf(stderr, PROGNAME "[%d]: %s: "
680681ce946SMartin Matuska 		    "out of memory for dependents of %s!\n",
681681ce946SMartin Matuska 		    getpid(), dataset, mountfile);
682681ce946SMartin Matuska 		goto err;
683681ce946SMartin Matuska 	}
684681ce946SMartin Matuska 	*(tofree++) = linktgt;
68516038816SMartin Matuska 
686a0b956f5SMartin Matuska 	struct dep {
687a0b956f5SMartin Matuska 		const char *type;
688a0b956f5SMartin Matuska 		char *list;
689a0b956f5SMartin Matuska 	} deps[] = {
69016038816SMartin Matuska 		{"wants", wantedby},
69116038816SMartin Matuska 		{"requires", requiredby},
69216038816SMartin Matuska 		{}
69316038816SMartin Matuska 	};
694a0b956f5SMartin Matuska 	for (struct dep *dep = deps; dep->type; ++dep) {
695a0b956f5SMartin Matuska 		if (!dep->list)
69616038816SMartin Matuska 			continue;
69716038816SMartin Matuska 
698a0b956f5SMartin Matuska 		for (char *reqby = strtok_r(dep->list, " ", &toktmp);
69916038816SMartin Matuska 		    reqby;
70016038816SMartin Matuska 		    reqby = strtok_r(NULL, " ", &toktmp)) {
70116038816SMartin Matuska 			char *depdir;
702681ce946SMartin Matuska 			if (asprintf(
703a0b956f5SMartin Matuska 			    &depdir, "%s.%s", reqby, dep->type) == -1) {
704681ce946SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
705681ce946SMartin Matuska 				    "out of memory for dependent dir name "
706681ce946SMartin Matuska 				    "\"%s.%s\"!\n",
707a0b956f5SMartin Matuska 				    getpid(), dataset, reqby, dep->type);
708681ce946SMartin Matuska 				continue;
709681ce946SMartin Matuska 			}
71016038816SMartin Matuska 
71116038816SMartin Matuska 			(void) mkdirat(destdir_fd, depdir, 0755);
71216038816SMartin Matuska 			int depdir_fd = openat(destdir_fd, depdir,
71316038816SMartin Matuska 			    O_PATH | O_DIRECTORY | O_CLOEXEC);
71416038816SMartin Matuska 			if (depdir_fd < 0) {
71516038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
71616038816SMartin Matuska 				    "couldn't open %s under %s: %s\n",
71716038816SMartin Matuska 				    getpid(), dataset, depdir, destdir,
71816038816SMartin Matuska 				    strerror(errno));
71916038816SMartin Matuska 				free(depdir);
72016038816SMartin Matuska 				continue;
72116038816SMartin Matuska 			}
72216038816SMartin Matuska 
72316038816SMartin Matuska 			if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
72416038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: %s: "
72516038816SMartin Matuska 				    "couldn't symlink at "
72616038816SMartin Matuska 				    "%s under %s under %s: %s\n",
72716038816SMartin Matuska 				    getpid(), dataset, mountfile,
72816038816SMartin Matuska 				    depdir, destdir, strerror(errno));
72916038816SMartin Matuska 
73016038816SMartin Matuska 			(void) close(depdir_fd);
73116038816SMartin Matuska 			free(depdir);
73216038816SMartin Matuska 		}
73316038816SMartin Matuska 	}
73416038816SMartin Matuska 
735681ce946SMartin Matuska end:
736681ce946SMartin Matuska 	if (tofree >= tofree_all + nitems(tofree_all)) {
737681ce946SMartin Matuska 		/*
738681ce946SMartin Matuska 		 * This won't happen as-is:
73915f0b8c3SMartin Matuska 		 * we've got 8 slots and allocate 5 things at most.
740681ce946SMartin Matuska 		 */
741681ce946SMartin Matuska 		fprintf(stderr,
742681ce946SMartin Matuska 		    PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
743681ce946SMartin Matuska 		    getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
744681ce946SMartin Matuska 		ret = tofree - tofree_all;
745681ce946SMartin Matuska 	}
746681ce946SMartin Matuska 
747681ce946SMartin Matuska 	while (tofree-- != tofree_all)
748681ce946SMartin Matuska 		free(*tofree);
749681ce946SMartin Matuska 	return (ret);
750681ce946SMartin Matuska err:
751681ce946SMartin Matuska 	ret = 1;
752681ce946SMartin Matuska 	goto end;
75316038816SMartin Matuska }
75416038816SMartin Matuska 
75516038816SMartin Matuska 
75616038816SMartin Matuska static int
pool_enumerator(zpool_handle_t * pool,void * data)75716038816SMartin Matuska pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
75816038816SMartin Matuska {
75916038816SMartin Matuska 	int ret = 0;
76016038816SMartin Matuska 
76116038816SMartin Matuska 	/*
76216038816SMartin Matuska 	 * Pools are guaranteed-unique by the kernel,
76316038816SMartin Matuska 	 * no risk of leaking dupes here
76416038816SMartin Matuska 	 */
76516038816SMartin Matuska 	char *name = strdup(zpool_get_name(pool));
76616038816SMartin Matuska 	if (!name || !tsearch(name, &known_pools, STRCMP)) {
76716038816SMartin Matuska 		free(name);
76816038816SMartin Matuska 		ret = ENOMEM;
76916038816SMartin Matuska 	}
77016038816SMartin Matuska 
77116038816SMartin Matuska 	zpool_close(pool);
77216038816SMartin Matuska 	return (ret);
77316038816SMartin Matuska }
77416038816SMartin Matuska 
77516038816SMartin Matuska int
main(int argc,char ** argv)77616038816SMartin Matuska main(int argc, char **argv)
77716038816SMartin Matuska {
77816038816SMartin Matuska 	struct timespec time_init = {};
77916038816SMartin Matuska 	clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
78016038816SMartin Matuska 
78116038816SMartin Matuska 	{
78216038816SMartin Matuska 		int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
78316038816SMartin Matuska 		if (kmfd >= 0) {
78416038816SMartin Matuska 			(void) dup2(kmfd, STDERR_FILENO);
78516038816SMartin Matuska 			(void) close(kmfd);
786681ce946SMartin Matuska 
787681ce946SMartin Matuska 			setlinebuf(stderr);
78816038816SMartin Matuska 		}
78916038816SMartin Matuska 	}
79016038816SMartin Matuska 
79116038816SMartin Matuska 	switch (argc) {
79216038816SMartin Matuska 	case 1:
79316038816SMartin Matuska 		/* Use default */
79416038816SMartin Matuska 		break;
79516038816SMartin Matuska 	case 2:
79616038816SMartin Matuska 	case 4:
79716038816SMartin Matuska 		destdir = argv[1];
79816038816SMartin Matuska 		break;
79916038816SMartin Matuska 	default:
80016038816SMartin Matuska 		fprintf(stderr,
80116038816SMartin Matuska 		    PROGNAME "[%d]: wrong argument count: %d\n",
80216038816SMartin Matuska 		    getpid(), argc - 1);
80316038816SMartin Matuska 		_exit(1);
80416038816SMartin Matuska 	}
80516038816SMartin Matuska 
80616038816SMartin Matuska 	{
80716038816SMartin Matuska 		destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
80816038816SMartin Matuska 		if (destdir_fd < 0) {
80916038816SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: "
81016038816SMartin Matuska 			    "can't open destination directory %s: %s\n",
81116038816SMartin Matuska 			    getpid(), destdir, strerror(errno));
81216038816SMartin Matuska 			_exit(1);
81316038816SMartin Matuska 		}
81416038816SMartin Matuska 	}
81516038816SMartin Matuska 
81616038816SMartin Matuska 	DIR *fslist_dir = opendir(FSLIST);
81716038816SMartin Matuska 	if (!fslist_dir) {
81816038816SMartin Matuska 		if (errno != ENOENT)
81916038816SMartin Matuska 			fprintf(stderr,
82016038816SMartin Matuska 			    PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
82116038816SMartin Matuska 			    getpid(), strerror(errno));
82216038816SMartin Matuska 		_exit(0);
82316038816SMartin Matuska 	}
82416038816SMartin Matuska 
82516038816SMartin Matuska 	{
82616038816SMartin Matuska 		libzfs_handle_t *libzfs = libzfs_init();
82716038816SMartin Matuska 		if (libzfs) {
82816038816SMartin Matuska 			if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
82916038816SMartin Matuska 				fprintf(stderr, PROGNAME "[%d]: "
83016038816SMartin Matuska 				    "error listing pools, ignoring\n",
83116038816SMartin Matuska 				    getpid());
83216038816SMartin Matuska 			libzfs_fini(libzfs);
83316038816SMartin Matuska 		} else
83416038816SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: "
83516038816SMartin Matuska 			    "couldn't start libzfs, ignoring\n",
83616038816SMartin Matuska 			    getpid());
83716038816SMartin Matuska 	}
83816038816SMartin Matuska 
83916038816SMartin Matuska 	{
84016038816SMartin Matuska 		int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
84116038816SMartin Matuska 		if (regerr != 0) {
84216038816SMartin Matuska 			fprintf(stderr,
84316038816SMartin Matuska 			    PROGNAME "[%d]: invalid regex: %d\n",
84416038816SMartin Matuska 			    getpid(), regerr);
84516038816SMartin Matuska 			_exit(1);
84616038816SMartin Matuska 		}
84716038816SMartin Matuska 	}
84816038816SMartin Matuska 
849681ce946SMartin Matuska 	bool debug = false;
85016038816SMartin Matuska 	char *line = NULL;
85116038816SMartin Matuska 	size_t linelen = 0;
85216038816SMartin Matuska 	{
85316038816SMartin Matuska 		const char *dbgenv = getenv("ZFS_DEBUG");
85416038816SMartin Matuska 		if (dbgenv)
85516038816SMartin Matuska 			debug = atoi(dbgenv);
85616038816SMartin Matuska 		else {
85716038816SMartin Matuska 			FILE *cmdline = fopen("/proc/cmdline", "re");
85816038816SMartin Matuska 			if (cmdline != NULL) {
85916038816SMartin Matuska 				if (getline(&line, &linelen, cmdline) >= 0)
860681ce946SMartin Matuska 					debug = strstr(line, "debug");
86116038816SMartin Matuska 				(void) fclose(cmdline);
86216038816SMartin Matuska 			}
86316038816SMartin Matuska 		}
86416038816SMartin Matuska 
86516038816SMartin Matuska 		if (debug && !isatty(STDOUT_FILENO))
86616038816SMartin Matuska 			dup2(STDERR_FILENO, STDOUT_FILENO);
86716038816SMartin Matuska 	}
86816038816SMartin Matuska 
869681ce946SMartin Matuska 	struct timespec time_start = {};
87016038816SMartin Matuska 	if (debug)
87116038816SMartin Matuska 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
87216038816SMartin Matuska 
873681ce946SMartin Matuska 	struct line {
874681ce946SMartin Matuska 		char *line;
875681ce946SMartin Matuska 		const char *fname;
876681ce946SMartin Matuska 		struct line *next;
877681ce946SMartin Matuska 	} *lines_canmount_not_on = NULL;
878681ce946SMartin Matuska 
879681ce946SMartin Matuska 	int ret = 0;
88016038816SMartin Matuska 	struct dirent *cachent;
88116038816SMartin Matuska 	while ((cachent = readdir(fslist_dir)) != NULL) {
88216038816SMartin Matuska 		if (strcmp(cachent->d_name, ".") == 0 ||
88316038816SMartin Matuska 		    strcmp(cachent->d_name, "..") == 0)
88416038816SMartin Matuska 			continue;
88516038816SMartin Matuska 
88616038816SMartin Matuska 		FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
88716038816SMartin Matuska 		    O_RDONLY | O_CLOEXEC, "r", 0);
88816038816SMartin Matuska 		if (!cachefile) {
88916038816SMartin Matuska 			fprintf(stderr, PROGNAME "[%d]: "
89016038816SMartin Matuska 			    "couldn't open %s under " FSLIST ": %s\n",
89116038816SMartin Matuska 			    getpid(), cachent->d_name, strerror(errno));
89216038816SMartin Matuska 			continue;
89316038816SMartin Matuska 		}
89416038816SMartin Matuska 
895681ce946SMartin Matuska 		const char *filename = FREE_STATICS ? "(elided)" : NULL;
896681ce946SMartin Matuska 
897681ce946SMartin Matuska 		ssize_t read;
89816038816SMartin Matuska 		while ((read = getline(&line, &linelen, cachefile)) >= 0) {
89916038816SMartin Matuska 			line[read - 1] = '\0'; /* newline */
90016038816SMartin Matuska 
901681ce946SMartin Matuska 			char *canmount = line;
902681ce946SMartin Matuska 			canmount += strcspn(canmount, "\t");
903681ce946SMartin Matuska 			canmount += strspn(canmount, "\t");
904681ce946SMartin Matuska 			canmount += strcspn(canmount, "\t");
905681ce946SMartin Matuska 			canmount += strspn(canmount, "\t");
906681ce946SMartin Matuska 			bool canmount_on = strncmp(canmount, "on", 2) == 0;
90716038816SMartin Matuska 
908681ce946SMartin Matuska 			if (canmount_on)
909681ce946SMartin Matuska 				ret |= line_worker(line, cachent->d_name);
910681ce946SMartin Matuska 			else {
911681ce946SMartin Matuska 				if (filename == NULL)
912681ce946SMartin Matuska 					filename =
913681ce946SMartin Matuska 					    strdup(cachent->d_name) ?: "(?)";
91416038816SMartin Matuska 
915681ce946SMartin Matuska 				struct line *l = calloc(1, sizeof (*l));
916681ce946SMartin Matuska 				char *nl = strdup(line);
917681ce946SMartin Matuska 				if (l == NULL || nl == NULL) {
918681ce946SMartin Matuska 					fprintf(stderr, PROGNAME "[%d]: "
919681ce946SMartin Matuska 					    "out of memory for \"%s\" in %s\n",
920681ce946SMartin Matuska 					    getpid(), line, cachent->d_name);
921681ce946SMartin Matuska 					free(l);
922681ce946SMartin Matuska 					free(nl);
92316038816SMartin Matuska 					continue;
92416038816SMartin Matuska 				}
925681ce946SMartin Matuska 				l->line = nl;
926681ce946SMartin Matuska 				l->fname = filename;
927681ce946SMartin Matuska 				l->next = lines_canmount_not_on;
928681ce946SMartin Matuska 				lines_canmount_not_on = l;
92916038816SMartin Matuska 			}
93016038816SMartin Matuska 		}
93116038816SMartin Matuska 
932681ce946SMartin Matuska 		fclose(cachefile);
93316038816SMartin Matuska 	}
93416038816SMartin Matuska 	free(line);
93516038816SMartin Matuska 
936681ce946SMartin Matuska 	while (lines_canmount_not_on) {
937681ce946SMartin Matuska 		struct line *l = lines_canmount_not_on;
938681ce946SMartin Matuska 		lines_canmount_not_on = l->next;
939681ce946SMartin Matuska 
940681ce946SMartin Matuska 		ret |= line_worker(l->line, l->fname);
941681ce946SMartin Matuska 		if (FREE_STATICS) {
942681ce946SMartin Matuska 			free(l->line);
943681ce946SMartin Matuska 			free(l);
94416038816SMartin Matuska 		}
94516038816SMartin Matuska 	}
94616038816SMartin Matuska 
94716038816SMartin Matuska 	if (debug) {
94816038816SMartin Matuska 		struct timespec time_end = {};
94916038816SMartin Matuska 		clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
95016038816SMartin Matuska 
951681ce946SMartin Matuska 		struct rusage usage;
95216038816SMartin Matuska 		getrusage(RUSAGE_SELF, &usage);
95316038816SMartin Matuska 		printf(
95416038816SMartin Matuska 		    "\n"
955681ce946SMartin Matuska 		    PROGNAME ": "
95616038816SMartin Matuska 		    "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
95716038816SMartin Matuska 		    (unsigned long long) usage.ru_utime.tv_sec,
95816038816SMartin Matuska 		    (unsigned int) usage.ru_utime.tv_usec,
95916038816SMartin Matuska 		    (unsigned long long) usage.ru_stime.tv_sec,
96016038816SMartin Matuska 		    (unsigned int) usage.ru_stime.tv_usec,
96116038816SMartin Matuska 		    usage.ru_maxrss * 1024);
96216038816SMartin Matuska 
96316038816SMartin Matuska 		if (time_start.tv_nsec > time_end.tv_nsec) {
96416038816SMartin Matuska 			time_end.tv_nsec =
96516038816SMartin Matuska 			    1000000000 + time_end.tv_nsec - time_start.tv_nsec;
96616038816SMartin Matuska 			time_end.tv_sec -= 1;
96716038816SMartin Matuska 		} else
96816038816SMartin Matuska 			time_end.tv_nsec -= time_start.tv_nsec;
96916038816SMartin Matuska 		time_end.tv_sec -= time_start.tv_sec;
97016038816SMartin Matuska 
97116038816SMartin Matuska 		if (time_init.tv_nsec > time_start.tv_nsec) {
97216038816SMartin Matuska 			time_start.tv_nsec =
97316038816SMartin Matuska 			    1000000000 + time_start.tv_nsec - time_init.tv_nsec;
97416038816SMartin Matuska 			time_start.tv_sec -= 1;
97516038816SMartin Matuska 		} else
97616038816SMartin Matuska 			time_start.tv_nsec -= time_init.tv_nsec;
97716038816SMartin Matuska 		time_start.tv_sec -= time_init.tv_sec;
97816038816SMartin Matuska 
97916038816SMartin Matuska 		time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
98016038816SMartin Matuska 		time_init.tv_sec =
98116038816SMartin Matuska 		    time_start.tv_sec + time_end.tv_sec +
98216038816SMartin Matuska 		    time_init.tv_nsec / 1000000000;
98316038816SMartin Matuska 		time_init.tv_nsec %= 1000000000;
98416038816SMartin Matuska 
985681ce946SMartin Matuska 		printf(PROGNAME ": "
98616038816SMartin Matuska 		    "total=%llu.%09llus = "
98716038816SMartin Matuska 		    "init=%llu.%09llus + real=%llu.%09llus\n",
98816038816SMartin Matuska 		    (unsigned long long) time_init.tv_sec,
98916038816SMartin Matuska 		    (unsigned long long) time_init.tv_nsec,
99016038816SMartin Matuska 		    (unsigned long long) time_start.tv_sec,
99116038816SMartin Matuska 		    (unsigned long long) time_start.tv_nsec,
99216038816SMartin Matuska 		    (unsigned long long) time_end.tv_sec,
99316038816SMartin Matuska 		    (unsigned long long) time_end.tv_nsec);
994681ce946SMartin Matuska 
995681ce946SMartin Matuska 		fflush(stdout);
99616038816SMartin Matuska 	}
99716038816SMartin Matuska 
998681ce946SMartin Matuska 	if (FREE_STATICS) {
999681ce946SMartin Matuska 		closedir(fslist_dir);
1000681ce946SMartin Matuska 		tdestroy(noauto_files, free);
1001681ce946SMartin Matuska 		tdestroy(known_pools, free);
1002681ce946SMartin Matuska 		regfree(&uri_regex);
1003681ce946SMartin Matuska 	}
100416038816SMartin Matuska 	_exit(ret);
100516038816SMartin Matuska }
1006