1/*-
2 * Copyright 2020 Toomas Soome <tsoome@me.com>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26/*
27 * Big Theory Statement.
28 *
29 * nvstore is abstraction layer to implement data read/write to different
30 * types of non-volatile storage.
31 *
32 * User interfaces:
33 * Provide mapping via environment: setenv/unsetenv/putenv. Access via
34 * environment functions/commands is available once nvstore has
35 * attached the backend and stored textual data is mapped to environment.
36 *
37 * Provide command "nvstore" to create new data instances.
38 *
39 * API: TBD.
40 * nvstore_init(): attach new backend and create the environment mapping.
41 * nvstore_fini: detach backend and unmap the related environment.
42 *
43 * The disk based storage, such as UFS file or ZFS bootenv label area, is
44 * only accessible after root file system is set. Root file system change
45 * will switch the back end storage.
46 */
47
48#include <sys/cdefs.h>
49__FBSDID("$FreeBSD$");
50
51#include <stdbool.h>
52#include <sys/queue.h>
53#include <bootstrap.h>
54#include "stand.h"
55
56typedef struct nvstore {
57	char		*nvs_name;
58	void		*nvs_data;
59	nvs_callbacks_t	*nvs_cb;
60	STAILQ_ENTRY(nvstore) nvs_next;
61} nvstore_t;
62
63typedef STAILQ_HEAD(store_list, nvstore) nvstore_list_t;
64
65nvstore_list_t stores = STAILQ_HEAD_INITIALIZER(stores);
66
67void *
68nvstore_get_store(const char *name)
69{
70	nvstore_t *st;
71
72	st = NULL;
73
74	STAILQ_FOREACH(st, &stores, nvs_next) {
75		if (strcmp(name, st->nvs_name) == 0)
76			break;
77	}
78
79	return (st);
80}
81
82int
83nvstore_init(const char *name, nvs_callbacks_t *cb, void *data)
84{
85	nvstore_t *st;
86
87	st = nvstore_get_store(name);
88	if (st != NULL)
89		return (EEXIST);
90
91	if ((st = malloc(sizeof (*st))) == NULL)
92		return (ENOMEM);
93
94	if ((st->nvs_name = strdup(name)) == NULL) {
95		free(st);
96		return (ENOMEM);
97	}
98
99	st->nvs_data = data;
100	st->nvs_cb = cb;
101
102	STAILQ_INSERT_TAIL(&stores, st, nvs_next);
103	return (0);
104}
105
106int
107nvstore_fini(const char *name)
108{
109	nvstore_t *st;
110
111	st = nvstore_get_store(name);
112	if (st == NULL)
113		return (ENOENT);
114
115	STAILQ_REMOVE(&stores, st, nvstore, nvs_next);
116
117	free(st->nvs_name);
118	free(st->nvs_data);
119	free(st);
120	return (0);
121}
122
123int
124nvstore_print(void *ptr)
125{
126	nvstore_t *st = ptr;
127
128	return (st->nvs_cb->nvs_iterate(st->nvs_data, st->nvs_cb->nvs_print));
129}
130
131int
132nvstore_get_var(void *ptr, const char *name, void **data)
133{
134	nvstore_t *st = ptr;
135
136	return (st->nvs_cb->nvs_getter(st->nvs_data, name, data));
137}
138
139int
140nvstore_set_var(void *ptr, int type, const char *name,
141    void *data, size_t size)
142{
143	nvstore_t *st = ptr;
144
145	return (st->nvs_cb->nvs_setter(st->nvs_data, type, name, data, size));
146}
147
148int
149nvstore_set_var_from_string(void *ptr, const char *type, const char *name,
150    const char *data)
151{
152	nvstore_t *st = ptr;
153
154	return (st->nvs_cb->nvs_setter_str(st->nvs_data, type, name, data));
155}
156
157int
158nvstore_unset_var(void *ptr, const char *name)
159{
160	nvstore_t *st = ptr;
161
162	return (st->nvs_cb->nvs_unset(st->nvs_data, name));
163}
164
165COMMAND_SET(nvstore, "nvstore", "manage non-volatile data", command_nvstore);
166
167static void
168nvstore_usage(const char *me)
169{
170	printf("Usage:\t%s -l\n", me);
171	printf("\t%s store -l\n", me);
172	printf("\t%s store [-t type] key value\n", me);
173	printf("\t%s store -g key\n", me);
174	printf("\t%s store -d key\n", me);
175}
176
177/*
178 * Usage: nvstore -l		# list stores
179 *	nvstore store -l	# list data in store
180 *	nvstore store [-t type] key value
181 *	nvstore store -g key	# get value
182 *	nvstore store -d key	# delete key
183 */
184static int
185command_nvstore(int argc, char *argv[])
186{
187	int c;
188	bool list, get, delete;
189	nvstore_t *st;
190	char *me, *name, *type;
191
192	me = argv[0];
193	optind = 1;
194	optreset = 1;
195
196	list = false;
197	while ((c = getopt(argc, argv, "l")) != -1) {
198		switch (c) {
199		case 'l':
200			list = true;
201			break;
202		case '?':
203		default:
204			return (CMD_ERROR);
205		}
206	}
207
208	argc -= optind;
209	argv += optind;
210
211	if (argc == 0) {
212		if (list) {
213			if (STAILQ_EMPTY(&stores)) {
214				printf("No configured nvstores\n");
215				return (CMD_OK);
216			}
217			printf("List of configured nvstores:\n");
218			STAILQ_FOREACH(st, &stores, nvs_next) {
219				printf("\t%s\n", st->nvs_name);
220			}
221			return (CMD_OK);
222		}
223		nvstore_usage(me);
224		return (CMD_ERROR);
225	}
226
227	if (argc == 0 || (argc != 0 && list)) {
228		nvstore_usage(me);
229		return (CMD_ERROR);
230	}
231
232	st = nvstore_get_store(argv[0]);
233	if (st == NULL) {
234		nvstore_usage(me);
235		return (CMD_ERROR);
236	}
237
238	optind = 1;
239	optreset = 1;
240	name = NULL;
241	type = NULL;
242	get = delete = false;
243
244	while ((c = getopt(argc, argv, "d:g:lt:")) != -1) {
245		switch (c) {
246		case 'd':
247			if (list || get) {
248				nvstore_usage(me);
249				return (CMD_ERROR);
250			}
251			name = optarg;
252			delete = true;
253			break;
254		case 'g':
255			if (delete || list) {
256				nvstore_usage(me);
257				return (CMD_ERROR);
258			}
259			name = optarg;
260			get = true;
261			break;
262		case 'l':
263			if (delete || get) {
264				nvstore_usage(me);
265				return (CMD_ERROR);
266			}
267			list = true;
268			break;
269		case 't':
270			type = optarg;
271			break;
272		case '?':
273		default:
274			return (CMD_ERROR);
275		}
276	}
277
278	argc -= optind;
279	argv += optind;
280
281	if (list) {
282		(void) nvstore_print(st);
283		return (CMD_OK);
284	}
285
286	if (delete && name != NULL) {
287		(void) nvstore_unset_var(st, name);
288		return (CMD_OK);
289	}
290
291	if (get && name != NULL) {
292		char *ptr = NULL;
293
294		if (nvstore_get_var(st, name, (void **)&ptr) == 0)
295			printf("%s = %s\n", name, ptr);
296		return (CMD_OK);
297	}
298
299	if (argc == 2) {
300		c = nvstore_set_var_from_string(st, type, argv[0], argv[1]);
301		if (c != 0) {
302			printf("error: %s\n", strerror(c));
303			return (CMD_ERROR);
304		}
305		return (CMD_OK);
306	}
307
308	nvstore_usage(me);
309	return (CMD_OK);
310}
311