xref: /freebsd/stand/efi/libefi/env.c (revision ca987d4641cdcd7f27e153db17c5bf064934faf5)
1 /*
2  * Copyright (c) 2015 Netflix, Inc. All Rights Reserved.
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 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include <sys/param.h>
30 #include <stand.h>
31 #include <string.h>
32 #include <efi.h>
33 #include <efilib.h>
34 #include <uuid.h>
35 #include <stdbool.h>
36 #include "bootstrap.h"
37 #ifdef BOOT_FORTH
38 #include "ficl.h"
39 #endif
40 
41 /*
42  * Simple wrappers to the underlying UEFI functions.
43  * See http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES
44  * for details.
45  */
46 EFI_STATUS
47 efi_get_next_variable_name(UINTN *variable_name_size, CHAR16 *variable_name,
48     EFI_GUID *vendor_guid)
49 {
50 	return (RS->GetNextVariableName(variable_name_size, variable_name,
51 	    vendor_guid));
52 }
53 
54 EFI_STATUS
55 efi_get_variable(CHAR16 *variable_name, EFI_GUID *vendor_guid,
56     UINT32 *attributes, UINTN *data_size, void *data)
57 {
58 	return (RS->GetVariable(variable_name, vendor_guid, attributes,
59 	    data_size, data));
60 }
61 
62 EFI_STATUS
63 efi_set_variable(CHAR16 *variable_name, EFI_GUID *vendor_guid,
64     UINT32 attributes, UINTN data_size, void *data)
65 {
66 	return (RS->SetVariable(variable_name, vendor_guid, attributes,
67 	    data_size, data));
68 }
69 
70 void
71 efi_init_environment(void)
72 {
73 	char var[128];
74 
75 	snprintf(var, sizeof(var), "%d.%02d", ST->Hdr.Revision >> 16,
76 	    ST->Hdr.Revision & 0xffff);
77 	env_setenv("efi-version", EV_VOLATILE, var, env_noset, env_nounset);
78 }
79 
80 COMMAND_SET(efishow, "efi-show", "print some or all EFI variables", command_efi_show);
81 
82 static int
83 efi_print_var(CHAR16 *varnamearg, EFI_GUID *matchguid, int lflag)
84 {
85 	UINTN		datasz, i;
86 	EFI_STATUS	status;
87 	UINT32		attr;
88 	CHAR16		*data;
89 	char		*str;
90 	uint32_t	uuid_status;
91 	int		is_ascii;
92 
93 	datasz = 0;
94 	status = RS->GetVariable(varnamearg, matchguid, &attr,
95 	    &datasz, NULL);
96 	if (status != EFI_BUFFER_TOO_SMALL) {
97 		printf("Can't get the variable: error %#lx\n",
98 		    EFI_ERROR_CODE(status));
99 		return (CMD_ERROR);
100 	}
101 	data = malloc(datasz);
102 	status = RS->GetVariable(varnamearg, matchguid, &attr,
103 	    &datasz, data);
104 	if (status != EFI_SUCCESS) {
105 		printf("Can't get the variable: error %#lx\n",
106 		    EFI_ERROR_CODE(status));
107 		return (CMD_ERROR);
108 	}
109 	uuid_to_string((uuid_t *)matchguid, &str, &uuid_status);
110 	if (lflag) {
111 		printf("%s 0x%x %S", str, attr, varnamearg);
112 	} else {
113 		printf("%s 0x%x %S=", str, attr, varnamearg);
114 		is_ascii = 1;
115 		free(str);
116 		str = (char *)data;
117 		for (i = 0; i < datasz - 1; i++) {
118 			/* Quick hack to see if this ascii-ish string printable range plus tab, cr and lf */
119 			if ((str[i] < 32 || str[i] > 126) && str[i] != 9 && str[i] != 10 && str[i] != 13) {
120 				is_ascii = 0;
121 				break;
122 			}
123 		}
124 		if (str[datasz - 1] != '\0')
125 			is_ascii = 0;
126 		if (is_ascii)
127 			printf("%s", str);
128 		else {
129 			for (i = 0; i < datasz / 2; i++) {
130 				if (isalnum(data[i]) || isspace(data[i]))
131 					printf("%c", data[i]);
132 				else
133 					printf("\\x%02x", data[i]);
134 			}
135 		}
136 	}
137 	free(data);
138 	if (pager_output("\n"))
139 		return (CMD_WARN);
140 	return (CMD_OK);
141 }
142 
143 static int
144 command_efi_show(int argc, char *argv[])
145 {
146 	/*
147 	 * efi-show [-a]
148 	 *	print all the env
149 	 * efi-show -u UUID
150 	 *	print all the env vars tagged with UUID
151 	 * efi-show -v var
152 	 *	search all the env vars and print the ones matching var
153 	 * eif-show -u UUID -v var
154 	 * eif-show UUID var
155 	 *	print all the env vars that match UUID and var
156 	 */
157 	/* NB: We assume EFI_GUID is the same as uuid_t */
158 	int		aflag = 0, gflag = 0, lflag = 0, vflag = 0;
159 	int		ch, rv;
160 	unsigned	i;
161 	EFI_STATUS	status;
162 	EFI_GUID	varguid = { 0,0,0,{0,0,0,0,0,0,0,0} };
163 	EFI_GUID	matchguid = { 0,0,0,{0,0,0,0,0,0,0,0} };
164 	uint32_t	uuid_status;
165 	CHAR16		*varname;
166 	CHAR16		*newnm;
167 	CHAR16		varnamearg[128];
168 	UINTN		varalloc;
169 	UINTN		varsz;
170 
171 	while ((ch = getopt(argc, argv, "ag:lv:")) != -1) {
172 		switch (ch) {
173 		case 'a':
174 			aflag = 1;
175 			break;
176 		case 'g':
177 			gflag = 1;
178 			uuid_from_string(optarg, (uuid_t *)&matchguid,
179 			    &uuid_status);
180 			if (uuid_status != uuid_s_ok) {
181 				printf("uid %s could not be parsed\n", optarg);
182 				return (CMD_ERROR);
183 			}
184 			break;
185 		case 'l':
186 			lflag = 1;
187 			break;
188 		case 'v':
189 			vflag = 1;
190 			if (strlen(optarg) >= nitems(varnamearg)) {
191 				printf("Variable %s is longer than %zd characters\n",
192 				    optarg, nitems(varnamearg));
193 				return (CMD_ERROR);
194 			}
195 			for (i = 0; i < strlen(optarg); i++)
196 				varnamearg[i] = optarg[i];
197 			varnamearg[i] = 0;
198 			break;
199 		default:
200 			printf("Invalid argument %c\n", ch);
201 			return (CMD_ERROR);
202 		}
203 	}
204 
205 	if (aflag && (gflag || vflag)) {
206 		printf("-a isn't compatible with -v or -u\n");
207 		return (CMD_ERROR);
208 	}
209 
210 	if (aflag && optind < argc) {
211 		printf("-a doesn't take any args\n");
212 		return (CMD_ERROR);
213 	}
214 
215 	if (optind == argc)
216 		aflag = 1;
217 
218 	argc -= optind;
219 	argv += optind;
220 
221 	pager_open();
222 	if (vflag && gflag) {
223 		rv = efi_print_var(varnamearg, &matchguid, lflag);
224 		pager_close();
225 		return (rv);
226 	}
227 
228 	if (argc == 2) {
229 		optarg = argv[0];
230 		if (strlen(optarg) >= nitems(varnamearg)) {
231 			printf("Variable %s is longer than %zd characters\n",
232 			    optarg, nitems(varnamearg));
233 			pager_close();
234 			return (CMD_ERROR);
235 		}
236 		for (i = 0; i < strlen(optarg); i++)
237 			varnamearg[i] = optarg[i];
238 		varnamearg[i] = 0;
239 		optarg = argv[1];
240 		uuid_from_string(optarg, (uuid_t *)&matchguid,
241 		    &uuid_status);
242 		if (uuid_status != uuid_s_ok) {
243 			printf("uid %s could not be parsed\n", optarg);
244 			pager_close();
245 			return (CMD_ERROR);
246 		}
247 		rv = efi_print_var(varnamearg, &matchguid, lflag);
248 		pager_close();
249 		return (rv);
250 	}
251 
252 	if (argc > 0) {
253 		printf("Too many args %d\n", argc);
254 		pager_close();
255 		return (CMD_ERROR);
256 	}
257 
258 	/*
259 	 * Initiate the search -- note the standard takes pain
260 	 * to specify the initial call must be a poiner to a NULL
261 	 * character.
262 	 */
263 	varalloc = 1024;
264 	varname = malloc(varalloc);
265 	if (varname == NULL) {
266 		printf("Can't allocate memory to get variables\n");
267 		pager_close();
268 		return (CMD_ERROR);
269 	}
270 	varname[0] = 0;
271 	while (1) {
272 		varsz = varalloc;
273 		status = RS->GetNextVariableName(&varsz, varname, &varguid);
274 		if (status == EFI_BUFFER_TOO_SMALL) {
275 			varalloc = varsz;
276 			newnm = realloc(varname, varalloc);
277 			if (newnm == NULL) {
278 				printf("Can't allocate memory to get variables\n");
279 				free(varname);
280 				pager_close();
281 				return (CMD_ERROR);
282 			}
283 			varname = newnm;
284 			continue; /* Try again with bigger buffer */
285 		}
286 		if (status != EFI_SUCCESS)
287 			break;
288 		if (aflag) {
289 			if (efi_print_var(varname, &varguid, lflag) != CMD_OK)
290 				break;
291 			continue;
292 		}
293 		if (vflag) {
294 			if (wcscmp(varnamearg, varname) == 0) {
295 				if (efi_print_var(varname, &varguid, lflag) != CMD_OK)
296 					break;
297 				continue;
298 			}
299 		}
300 		if (gflag) {
301 			if (memcmp(&varguid, &matchguid, sizeof(varguid)) == 0) {
302 				if (efi_print_var(varname, &varguid, lflag) != CMD_OK)
303 					break;
304 				continue;
305 			}
306 		}
307 	}
308 	free(varname);
309 	pager_close();
310 
311 	return (CMD_OK);
312 }
313 
314 COMMAND_SET(efiset, "efi-set", "set EFI variables", command_efi_set);
315 
316 static int
317 command_efi_set(int argc, char *argv[])
318 {
319 	char *uuid, *var, *val;
320 	CHAR16 wvar[128];
321 	EFI_GUID guid;
322 	uint32_t status;
323 	EFI_STATUS err;
324 
325 	if (argc != 4) {
326 		printf("efi-set uuid var new-value\n");
327 		return (CMD_ERROR);
328 	}
329 	uuid = argv[1];
330 	var = argv[2];
331 	val = argv[3];
332 	uuid_from_string(uuid, (uuid_t *)&guid, &status);
333 	if (status != uuid_s_ok) {
334 		printf("Invalid uuid %s %d\n", uuid, status);
335 		return (CMD_ERROR);
336 	}
337 	cpy8to16(var, wvar, sizeof(wvar));
338 	err = RS->SetVariable(wvar, &guid,
339 	    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS,
340 	    strlen(val) + 1, val);
341 	if (EFI_ERROR(err)) {
342 		printf("Failed to set variable: error %lu\n", EFI_ERROR_CODE(err));
343 		return (CMD_ERROR);
344 	}
345 	return (CMD_OK);
346 }
347 
348 COMMAND_SET(efiunset, "efi-unset", "delete / unset EFI variables", command_efi_unset);
349 
350 static int
351 command_efi_unset(int argc, char *argv[])
352 {
353 	char *uuid, *var;
354 	CHAR16 wvar[128];
355 	EFI_GUID guid;
356 	uint32_t status;
357 	EFI_STATUS err;
358 
359 	if (argc != 3) {
360 		printf("efi-unset uuid var\n");
361 		return (CMD_ERROR);
362 	}
363 	uuid = argv[1];
364 	var = argv[2];
365 	uuid_from_string(uuid, (uuid_t *)&guid, &status);
366 	if (status != uuid_s_ok) {
367 		printf("Invalid uuid %s\n", uuid);
368 		return (CMD_ERROR);
369 	}
370 	cpy8to16(var, wvar, sizeof(wvar));
371 	err = RS->SetVariable(wvar, &guid, 0, 0, NULL);
372 	if (EFI_ERROR(err)) {
373 		printf("Failed to unset variable: error %lu\n", EFI_ERROR_CODE(err));
374 		return (CMD_ERROR);
375 	}
376 	return (CMD_OK);
377 }
378 
379 #ifdef BOOT_FORTH
380 /*
381  * FreeBSD's loader interaction words and extras
382  *
383  *	efi-setenv  ( value n name n guid n attr -- 0 | -1)
384  * 	efi-getenv  ( guid n addr n -- addr' n' | -1 )
385  * 	efi-unsetenv ( name n guid n'' -- )
386  */
387 
388 /*
389  * efi-setenv
390  * 	efi-setenv  ( value n name n guid n attr -- 0 | -1)
391  *
392  * Set environment variables using the SetVariable EFI runtime service.
393  *
394  * Value and guid are passed through in binary form (so guid needs to be
395  * converted to binary form from its string form). Name is converted from
396  * ASCII to CHAR16. Since ficl doesn't have support for internationalization,
397  * there's no native CHAR16 interface provided.
398  *
399  * attr is an int in the bitmask of the following attributes for this variable.
400  *
401  *	1	Non volatile
402  *	2	Boot service access
403  *	4	Run time access
404  * (corresponding to the same bits in the UEFI spec).
405  */
406 static void
407 ficlEfiSetenv(FICL_VM *pVM)
408 {
409 	char	*value = NULL, *guid = NULL;
410 	CHAR16	*name = NULL;
411 	int	i;
412 	char	*namep, *valuep, *guidp;
413 	int	names, values, guids, attr;
414 	EFI_STATUS status;
415 	uuid_t	u;
416 	uint32_t ustatus;
417 	bool	error = true;
418 
419 #if FICL_ROBUST > 1
420 	vmCheckStack(pVM, 6, 0);
421 #endif
422 	attr = stackPopINT(pVM->pStack);
423 	guids = stackPopINT(pVM->pStack);
424 	guidp = (char*)stackPopPtr(pVM->pStack);
425 	names = stackPopINT(pVM->pStack);
426 	namep = (char*)stackPopPtr(pVM->pStack);
427 	values = stackPopINT(pVM->pStack);
428 	valuep = (char*)stackPopPtr(pVM->pStack);
429 
430 	guid = (char*)ficlMalloc(guids);
431 	if (guid == NULL)
432 		goto out;
433 	memcpy(guid, guidp, guids);
434 	uuid_from_string(guid, &u, &ustatus);
435 	if (ustatus != uuid_s_ok) {
436 		stackPushINT(pVM->pStack, -1);
437 		goto out;
438 	}
439 
440 	name = ficlMalloc((names + 1) * sizeof(CHAR16));
441 	if (name == NULL)
442 		goto out;
443 	for (i = 0; i < names; i++)
444 		name[i] = namep[i];
445 	name[names] = 0;
446 
447 	value = ficlMalloc(values + 1);
448 	if (value == NULL)
449 		goto out;
450 	memcpy(value, valuep, values);
451 
452 	status = efi_set_variable(name, (EFI_GUID *)&u, attr, values, value);
453 	if (status == EFI_SUCCESS)
454 		stackPushINT(pVM->pStack, 0);
455 	else
456 		stackPushINT(pVM->pStack, -1);
457 	error = false;
458 out:
459 	ficlFree(name);
460 	ficlFree(value);
461 	ficlFree(guid);
462 
463 	if (error == true)
464 		vmThrowErr(pVM, "Error: out of memory");
465 }
466 
467 static void
468 ficlEfiGetenv(FICL_VM *pVM)
469 {
470 	char	*name, *value;
471 	char	*namep;
472 	int	names;
473 
474 #if FICL_ROBUST > 1
475 	vmCheckStack(pVM, 2, 2);
476 #endif
477 	names = stackPopINT(pVM->pStack);
478 	namep = (char*) stackPopPtr(pVM->pStack);
479 
480 	name = (char*) ficlMalloc(names+1);
481 	if (name == NULL)
482 		vmThrowErr(pVM, "Error: out of memory");
483 	strncpy(name, namep, names);
484 	name[names] = '\0';
485 
486 	value = getenv(name);
487 	ficlFree(name);
488 
489 	if(value != NULL) {
490 		stackPushPtr(pVM->pStack, value);
491 		stackPushINT(pVM->pStack, strlen(value));
492 	} else
493 		stackPushINT(pVM->pStack, -1);
494 }
495 
496 static void
497 ficlEfiUnsetenv(FICL_VM *pVM)
498 {
499 	char	*name;
500 	char	*namep;
501 	int	names;
502 
503 #if FICL_ROBUST > 1
504 	vmCheckStack(pVM, 2, 0);
505 #endif
506 	names = stackPopINT(pVM->pStack);
507 	namep = (char*) stackPopPtr(pVM->pStack);
508 
509 	name = (char*) ficlMalloc(names+1);
510 	if (name == NULL)
511 		vmThrowErr(pVM, "Error: out of memory");
512 	strncpy(name, namep, names);
513 	name[names] = '\0';
514 
515 	unsetenv(name);
516 	ficlFree(name);
517 }
518 
519 /**************************************************************************
520 ** Add FreeBSD UEFI platform extensions into the system dictionary
521 **************************************************************************/
522 void ficlEfiCompilePlatform(FICL_SYSTEM *pSys)
523 {
524 	FICL_DICT *dp = pSys->dp;
525 	assert (dp);
526 
527 	dictAppendWord(dp, "efi-setenv",    ficlEfiSetenv,	FW_DEFAULT);
528 	dictAppendWord(dp, "efi-getenv",    ficlEfiGetenv,	FW_DEFAULT);
529 	dictAppendWord(dp, "efi-unsetenv",  ficlEfiUnsetenv,    FW_DEFAULT);
530 }
531 
532 FICL_COMPILE_SET(ficlEfiCompilePlatform);
533 
534 #endif	/* BOOT_FORTH */
535