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/types.h>
27#include <errno.h>
28#include <limits.h>
29#include <inttypes.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <stdbool.h>
33#include <string.h>
34#include <kenv.h>
35#include <unistd.h>
36
37#include <libzfsbootenv.h>
38
39#ifndef ZFS_MAXNAMELEN
40#define	ZFS_MAXNAMELEN	256
41#endif
42
43static int
44add_pair(const char *name, const char *nvlist, const char *key,
45    const char *type, const char *value)
46{
47	void *data, *nv;
48	size_t size;
49	int rv;
50	char *end;
51
52	rv = lzbe_nvlist_get(name, nvlist, &nv);
53	if (rv != 0)
54		return (rv);
55
56	data = NULL;
57	rv = EINVAL;
58	if (strcmp(type, "DATA_TYPE_STRING") == 0) {
59		data = __DECONST(void *, value);
60		size = strlen(data) + 1;
61		rv = lzbe_add_pair(nv, key, type, data, size);
62	} else if (strcmp(type, "DATA_TYPE_UINT64") == 0) {
63		uint64_t v;
64
65		v = strtoull(value, &end, 0);
66		if (errno != 0 || *end != '\0')
67			goto done;
68		size = sizeof (v);
69		rv = lzbe_add_pair(nv, key, type, &v, size);
70	} else if (strcmp(type, "DATA_TYPE_INT64") == 0) {
71		int64_t v;
72
73		v = strtoll(value, &end, 0);
74		if (errno != 0 || *end != '\0')
75			goto done;
76		size = sizeof (v);
77		rv = lzbe_add_pair(nv, key, type, &v, size);
78	} else if (strcmp(type, "DATA_TYPE_UINT32") == 0) {
79		uint32_t v;
80
81		v = strtoul(value, &end, 0);
82		if (errno != 0 || *end != '\0')
83			goto done;
84		size = sizeof (v);
85		rv = lzbe_add_pair(nv, key, type, &v, size);
86	} else if (strcmp(type, "DATA_TYPE_INT32") == 0) {
87		int32_t v;
88
89		v = strtol(value, &end, 0);
90		if (errno != 0 || *end != '\0')
91			goto done;
92		size = sizeof (v);
93		rv = lzbe_add_pair(nv, key, type, &v, size);
94	} else if (strcmp(type, "DATA_TYPE_UINT16") == 0) {
95		uint16_t v;
96
97		v = strtoul(value, &end, 0);
98		if (errno != 0 || *end != '\0')
99			goto done;
100		size = sizeof (v);
101		rv = lzbe_add_pair(nv, key, type, &v, size);
102	} else if (strcmp(type, "DATA_TYPE_INT16") == 0) {
103		int16_t v;
104
105		v = strtol(value, &end, 0);
106		if (errno != 0 || *end != '\0')
107			goto done;
108		size = sizeof (v);
109		rv = lzbe_add_pair(nv, key, type, &v, size);
110	} else if (strcmp(type, "DATA_TYPE_UINT8") == 0) {
111		uint8_t v;
112
113		v = strtoul(value, &end, 0);
114		if (errno != 0 || *end != '\0')
115			goto done;
116		size = sizeof (v);
117		rv = lzbe_add_pair(nv, key, type, &v, size);
118	} else if (strcmp(type, "DATA_TYPE_INT8") == 0) {
119		int8_t v;
120
121		v = strtol(value, &end, 0);
122		if (errno != 0 || *end != '\0')
123			goto done;
124		size = sizeof (v);
125		rv = lzbe_add_pair(nv, key, type, &v, size);
126	} else if (strcmp(type, "DATA_TYPE_BYTE") == 0) {
127		uint8_t v;
128
129		v = strtoul(value, &end, 0);
130		if (errno != 0 || *end != '\0')
131			goto done;
132		size = sizeof (v);
133		rv = lzbe_add_pair(nv, key, type, &v, size);
134	} else if (strcmp(type, "DATA_TYPE_BOOLEAN_VALUE") == 0) {
135		int32_t v;
136
137		v = strtol(value, &end, 0);
138		if (errno != 0 || *end != '\0') {
139			if (strcasecmp(value, "YES") == 0)
140				v = 1;
141			else if (strcasecmp(value, "NO") == 0)
142				v = 0;
143			if (strcasecmp(value, "true") == 0)
144				v = 1;
145			else if (strcasecmp(value, "false") == 0)
146				v = 0;
147			else goto done;
148		}
149		size = sizeof (v);
150		rv = lzbe_add_pair(nv, key, type, &v, size);
151	}
152
153	if (rv == 0)
154		rv = lzbe_nvlist_set(name, nvlist, nv);
155
156done:
157	lzbe_nvlist_free(nv);
158	return (rv);
159}
160
161static int
162delete_pair(const char *name, const char *nvlist, const char *key)
163{
164	void *nv;
165	int rv;
166
167	rv = lzbe_nvlist_get(name, nvlist, &nv);
168	if (rv == 0) {
169		rv = lzbe_remove_pair(nv, key);
170	}
171	if (rv == 0)
172		rv = lzbe_nvlist_set(name, nvlist, nv);
173
174	lzbe_nvlist_free(nv);
175	return (rv);
176}
177
178/*
179 * Usage: zfsbootcfg [-z pool] [-d key] [-k key -t type -v value] [-p]
180 *	zfsbootcfg [-z pool] -n nvlist [-d key] [-k key -t type -v value] [-p]
181 *
182 * if nvlist is set, we will update nvlist in bootenv.
183 * if nvlist is not set, we update pairs in bootenv.
184 */
185int
186main(int argc, char * const *argv)
187{
188	char buf[ZFS_MAXNAMELEN], *name;
189	const char *key, *value, *type, *nvlist;
190	int rv;
191	bool print, delete;
192
193	nvlist = NULL;
194	name = NULL;
195	key = NULL;
196	type = NULL;
197	value = NULL;
198	print = delete = false;
199	while ((rv = getopt(argc, argv, "d:k:n:pt:v:z:")) != -1) {
200		switch (rv) {
201		case 'd':
202			delete = true;
203			key = optarg;
204			break;
205		case 'k':
206			key = optarg;
207			break;
208		case 'n':
209			nvlist = optarg;
210			break;
211		case 'p':
212			print = true;
213			break;
214		case 't':
215			type = optarg;
216			break;
217		case 'v':
218			value = optarg;
219			break;
220		case 'z':
221			name = optarg;
222			break;
223		}
224	}
225
226	argc -= optind;
227	argv += optind;
228
229	if (argc == 1)
230		value = argv[0];
231
232	if (argc > 1) {
233		fprintf(stderr, "usage: zfsbootcfg <boot.config(5) options>\n");
234		return (1);
235	}
236
237	if (name == NULL) {
238		rv = kenv(KENV_GET, "vfs.root.mountfrom", buf, sizeof(buf));
239		if (rv <= 0) {
240			perror("can't get vfs.root.mountfrom");
241			return (1);
242		}
243
244		if (strncmp(buf, "zfs:", 4) == 0) {
245			name = strchr(buf + 4, '/');
246			if (name != NULL)
247				*name = '\0';
248			name = buf + 4;
249		} else {
250			perror("not a zfs root");
251			return (1);
252		}
253	}
254
255	rv = 0;
256	if (key != NULL || value != NULL) {
257		if (type == NULL)
258			type = "DATA_TYPE_STRING";
259
260		if (delete)
261			rv = delete_pair(name, nvlist, key);
262		else if (key == NULL || strcmp(key, "command") == 0)
263			rv = lzbe_set_boot_device(name, lzbe_add, value);
264		else
265			rv = add_pair(name, nvlist, key, type, value);
266
267		if (rv == 0)
268			printf("zfs bootenv is successfully written\n");
269		else
270			printf("error: %d\n", rv);
271	} else if (!print) {
272		char *ptr;
273
274		if (lzbe_get_boot_device(name, &ptr) == 0) {
275			printf("zfs:%s:\n", ptr);
276			free(ptr);
277		}
278	}
279
280	if (print) {
281		rv = lzbe_bootenv_print(name, nvlist, stdout);
282	}
283
284	return (rv);
285}
286