1/*-
2 * Copyright (c) 2016 Andriy Gapon <avg@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/cdefs.h>
27__FBSDID("$FreeBSD$");
28
29#include <sys/types.h>
30#include <errno.h>
31#include <limits.h>
32#include <inttypes.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <stdbool.h>
36#include <string.h>
37#include <kenv.h>
38#include <unistd.h>
39
40#include <libzfsbootenv.h>
41
42#ifndef ZFS_MAXNAMELEN
43#define	ZFS_MAXNAMELEN	256
44#endif
45
46static int
47add_pair(const char *name, const char *nvlist, const char *key,
48    const char *type, const char *value)
49{
50	void *data, *nv;
51	size_t size;
52	int rv;
53	char *end;
54
55	rv = lzbe_nvlist_get(name, nvlist, &nv);
56	if (rv != 0)
57		return (rv);
58
59	data = NULL;
60	rv = EINVAL;
61	if (strcmp(type, "DATA_TYPE_STRING") == 0) {
62		data = __DECONST(void *, value);
63		size = strlen(data) + 1;
64		rv = lzbe_add_pair(nv, key, type, data, size);
65	} else if (strcmp(type, "DATA_TYPE_UINT64") == 0) {
66		uint64_t v;
67
68		v = strtoull(value, &end, 0);
69		if (errno != 0 || *end != '\0')
70			goto done;
71		size = sizeof (v);
72		rv = lzbe_add_pair(nv, key, type, &v, size);
73	} else if (strcmp(type, "DATA_TYPE_INT64") == 0) {
74		int64_t v;
75
76		v = strtoll(value, &end, 0);
77		if (errno != 0 || *end != '\0')
78			goto done;
79		size = sizeof (v);
80		rv = lzbe_add_pair(nv, key, type, &v, size);
81	} else if (strcmp(type, "DATA_TYPE_UINT32") == 0) {
82		uint32_t v;
83
84		v = strtoul(value, &end, 0);
85		if (errno != 0 || *end != '\0')
86			goto done;
87		size = sizeof (v);
88		rv = lzbe_add_pair(nv, key, type, &v, size);
89	} else if (strcmp(type, "DATA_TYPE_INT32") == 0) {
90		int32_t v;
91
92		v = strtol(value, &end, 0);
93		if (errno != 0 || *end != '\0')
94			goto done;
95		size = sizeof (v);
96		rv = lzbe_add_pair(nv, key, type, &v, size);
97	} else if (strcmp(type, "DATA_TYPE_UINT16") == 0) {
98		uint16_t v;
99
100		v = strtoul(value, &end, 0);
101		if (errno != 0 || *end != '\0')
102			goto done;
103		size = sizeof (v);
104		rv = lzbe_add_pair(nv, key, type, &v, size);
105	} else if (strcmp(type, "DATA_TYPE_INT16") == 0) {
106		int16_t v;
107
108		v = strtol(value, &end, 0);
109		if (errno != 0 || *end != '\0')
110			goto done;
111		size = sizeof (v);
112		rv = lzbe_add_pair(nv, key, type, &v, size);
113	} else if (strcmp(type, "DATA_TYPE_UINT8") == 0) {
114		uint8_t v;
115
116		v = strtoul(value, &end, 0);
117		if (errno != 0 || *end != '\0')
118			goto done;
119		size = sizeof (v);
120		rv = lzbe_add_pair(nv, key, type, &v, size);
121	} else if (strcmp(type, "DATA_TYPE_INT8") == 0) {
122		int8_t v;
123
124		v = strtol(value, &end, 0);
125		if (errno != 0 || *end != '\0')
126			goto done;
127		size = sizeof (v);
128		rv = lzbe_add_pair(nv, key, type, &v, size);
129	} else if (strcmp(type, "DATA_TYPE_BYTE") == 0) {
130		uint8_t v;
131
132		v = strtoul(value, &end, 0);
133		if (errno != 0 || *end != '\0')
134			goto done;
135		size = sizeof (v);
136		rv = lzbe_add_pair(nv, key, type, &v, size);
137	} else if (strcmp(type, "DATA_TYPE_BOOLEAN_VALUE") == 0) {
138		int32_t v;
139
140		v = strtol(value, &end, 0);
141		if (errno != 0 || *end != '\0') {
142			if (strcasecmp(value, "YES") == 0)
143				v = 1;
144			else if (strcasecmp(value, "NO") == 0)
145				v = 0;
146			if (strcasecmp(value, "true") == 0)
147				v = 1;
148			else if (strcasecmp(value, "false") == 0)
149				v = 0;
150			else goto done;
151		}
152		size = sizeof (v);
153		rv = lzbe_add_pair(nv, key, type, &v, size);
154	}
155
156	if (rv == 0)
157		rv = lzbe_nvlist_set(name, nvlist, nv);
158
159done:
160	lzbe_nvlist_free(nv);
161	return (rv);
162}
163
164static int
165delete_pair(const char *name, const char *nvlist, const char *key)
166{
167	void *nv;
168	int rv;
169
170	rv = lzbe_nvlist_get(name, nvlist, &nv);
171	if (rv == 0) {
172		rv = lzbe_remove_pair(nv, key);
173	}
174	if (rv == 0)
175		rv = lzbe_nvlist_set(name, nvlist, nv);
176
177	lzbe_nvlist_free(nv);
178	return (rv);
179}
180
181/*
182 * Usage: zfsbootcfg [-z pool] [-d key] [-k key -t type -v value] [-p]
183 *	zfsbootcfg [-z pool] -n nvlist [-d key] [-k key -t type -v value] [-p]
184 *
185 * if nvlist is set, we will update nvlist in bootenv.
186 * if nvlist is not set, we update pairs in bootenv.
187 */
188int
189main(int argc, char * const *argv)
190{
191	char buf[ZFS_MAXNAMELEN], *name;
192	const char *key, *value, *type, *nvlist;
193	int rv;
194	bool print, delete;
195
196	nvlist = NULL;
197	name = NULL;
198	key = NULL;
199	type = NULL;
200	value = NULL;
201	print = delete = false;
202	while ((rv = getopt(argc, argv, "d:k:n:pt:v:z:")) != -1) {
203		switch (rv) {
204		case 'd':
205			delete = true;
206			key = optarg;
207			break;
208		case 'k':
209			key = optarg;
210			break;
211		case 'n':
212			nvlist = optarg;
213			break;
214		case 'p':
215			print = true;
216			break;
217		case 't':
218			type = optarg;
219			break;
220		case 'v':
221			value = optarg;
222			break;
223		case 'z':
224			name = optarg;
225			break;
226		}
227	}
228
229	argc -= optind;
230	argv += optind;
231
232	if (argc == 1)
233		value = argv[0];
234
235	if (argc > 1) {
236		fprintf(stderr, "usage: zfsbootcfg <boot.config(5) options>\n");
237		return (1);
238	}
239
240	if (name == NULL) {
241		rv = kenv(KENV_GET, "vfs.root.mountfrom", buf, sizeof(buf));
242		if (rv <= 0) {
243			perror("can't get vfs.root.mountfrom");
244			return (1);
245		}
246
247		if (strncmp(buf, "zfs:", 4) == 0) {
248			name = strchr(buf + 4, '/');
249			if (name != NULL)
250				*name = '\0';
251			name = buf + 4;
252		} else {
253			perror("not a zfs root");
254			return (1);
255		}
256	}
257
258	rv = 0;
259	if (key != NULL || value != NULL) {
260		if (type == NULL)
261			type = "DATA_TYPE_STRING";
262
263		if (delete)
264			rv = delete_pair(name, nvlist, key);
265		else if (key == NULL || strcmp(key, "command") == 0)
266			rv = lzbe_set_boot_device(name, lzbe_add, value);
267		else
268			rv = add_pair(name, nvlist, key, type, value);
269
270		if (rv == 0)
271			printf("zfs bootenv is successfully written\n");
272		else
273			printf("error: %d\n", rv);
274	} else if (!print) {
275		char *ptr;
276
277		if (lzbe_get_boot_device(name, &ptr) == 0) {
278			printf("zfs:%s:\n", ptr);
279			free(ptr);
280		}
281	}
282
283	if (print) {
284		rv = lzbe_bootenv_print(name, nvlist, stdout);
285	}
286
287	return (rv);
288}
289