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