1 /* $NetBSD: main.c,v 1.6 2026/01/06 10:54:41 nia Exp $ */ 2 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 ``AS IS'' AND ANY EXPRESS 14 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 * 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 OR 19 * 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 #ifndef lint 28 __RCSID("$NetBSD: main.c,v 1.6 2026/01/06 10:54:41 nia Exp $"); 29 #endif /* not lint */ 30 31 #include <sys/efiio.h> 32 #include <sys/queue.h> 33 #include <sys/endian.h> 34 35 #include <assert.h> 36 #include <ctype.h> 37 #include <err.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <getopt.h> 41 #include <inttypes.h> 42 #include <limits.h> 43 #include <regex.h> 44 #include <stdbool.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <time.h> 49 #include <util.h> 50 51 #include "efiio.h" 52 #include "defs.h" 53 #include "bootvar.h" 54 #include "devpath.h" 55 #include "getvars.h" 56 #include "gptsubr.h" 57 #include "map.h" 58 #include "setvar.h" 59 #include "showvar.h" 60 #include "utils.h" 61 62 /* 63 * The UEFI spec is quite clear that it is intended for little endian 64 * machines only. As a result (and lazyness), byte ordering is 65 * ignored in this code and we build/run only on little endian 66 * machines. 67 */ 68 __CTASSERT(BYTE_ORDER == LITTLE_ENDIAN); 69 70 #define _PATH_EFI "/dev/efi" /* XXX: should be in <paths.h> */ 71 72 #define DEFAULT_PARTITION 1 73 #define DEFAULT_LABEL "NetBSD" 74 #define DEFAULT_LOADER "\\EFI\\NetBSD\\grubx64.efi" 75 #define DEFAULT_DEVICE parent_of_fname(".") 76 77 /****************************************/ 78 79 static uint __used 80 get_max_namelen(efi_var_t **var_array, size_t var_cnt) 81 { 82 uint max_len = 0; 83 84 for (size_t i = 0; i < var_cnt; i++) { 85 uint len = (uint)strlen(var_array[i]->name); 86 if (len > max_len) 87 max_len = len; 88 } 89 return max_len; 90 } 91 92 /************************************************************************/ 93 94 enum { 95 OPT_BRIEF = UCHAR_MAX + 1, 96 OPT_DEBUG = UCHAR_MAX + 2, 97 }; 98 99 #define OPTIONS "@:A::a::B::b:CcDd:FfG::hL:l:Nn:Oo:p:qR:rTt:Vvw::X:x:y" 100 #define OPTION_LIST \ 101 _X("brief", _NA, OPT_BRIEF, "set brief mode") \ 102 _X("debug", _OA, OPT_DEBUG, "raise or set the debug level") \ 103 _X("append-binary-args",_RA, '@', "Append extra variable args from file") \ 104 _X("inactive", _OA, 'A', "clear active bit on '-b' variable") \ 105 _X("active", _OA, 'a', "set active bit on '-b' variable") \ 106 _X("delete-bootnum", _OA, 'B', "delete '-b' variable or arg") \ 107 _X("bootnum", _RA, 'b', "specify a boot number") \ 108 _X("create-only", _NA, 'C', "create a new boot variable") \ 109 _X("create", _NA, 'c', "create a new boot variable and prefix BootOrder") \ 110 _X("remove-dups", _NA, 'D', "remove duplicates in BootOrder") \ 111 _X("disk", _RA, 'd', "specify disk device") \ 112 _X("no-reconnect", _NA, 'F', "do not force driver reconnect after loading (requires '-r')") \ 113 _X("reconnect", _NA, 'f', "force driver reconnect after loading (requires '-r')") \ 114 _X("show-gpt", _OA, 'G', "show GPT for device") \ 115 _X("help", _NA, 'h', "this help") \ 116 _X("label", _RA, 'L', "specify boot label") \ 117 _X("loader", _RA, 'l', "specify boot loader on EFI partition") \ 118 _X("delete-bootnext", _NA, 'N', "delete NextBoot variable") \ 119 _X("bootnext", _RA, 'n', "set NextBoot variable") \ 120 _X("delete-bootorder", _NA, 'O', "delete BootOrder entirely") \ 121 _X("bootorder", _RA, 'o', "set BootOrder") \ 122 _X("part", _RA, 'p', "specify partition number") \ 123 _X("quiet", _NA, 'q', "quiet mode") \ 124 _X("regexp", _RA, 'R', "regular expression for variable search (default: '^Boot')") \ 125 _X("driver", _NA, 'r', "operate on Driver#### instead of Boot####") \ 126 _X("delete-timeout", _NA, 'T', "delete timeout") \ 127 _X("timeout", _RA, 't', "set timeout to argument, in seconds") \ 128 _X("version", _NA, 'V', "show Version") \ 129 _X("verbose", _NA, 'v', "increment verboseness") \ 130 _X("write-signature", _OA, 'w', "write MBR signature") \ 131 _X("remove-bootorder", _RA, 'X', "remove argument from BootOrder") \ 132 _X("prefix-bootorder", _RA, 'x', "prefix argument to BootOrder") \ 133 _X("sysprep", _NA, 'y', "operate on SysPrep#### instead of Boot####") 134 135 #define TARGET_BOOT "Boot" 136 #define TARGET_DRIVER "Driver" 137 #define TARGET_SYSPREP "SysPrep" 138 139 #define IS_TARGET_DRIVER(opt) ((opt).target[0] == TARGET_DRIVER[0]) 140 141 #define ACTION_LIST \ 142 _X(active, NULL) \ 143 _X(create, NULL) \ 144 _X(delete, NULL) \ 145 _X(del_bootnext, del_bootnext) \ 146 _X(set_bootnext, set_bootnext) \ 147 _X(del_bootorder, del_bootorder) \ 148 _X(set_bootorder, set_bootorder) \ 149 _X(prefix_bootorder, prefix_bootorder) \ 150 _X(remove_bootorder, remove_bootorder) \ 151 _X(del_bootorder_dups, del_bootorder_dups) \ 152 _X(set_timeout, set_timeout) \ 153 _X(show, NULL) \ 154 _X(show_gpt, show_gpt) 155 156 static void __dead __printflike(1, 2) 157 usage(const char *fmt, ...) 158 { 159 const char *progname = getprogname(); 160 struct { 161 const char *help; 162 const char *name; 163 int opt; 164 } tbl[] = { 165 #define _NA "" 166 #define _OA "[=arg]" 167 #define _RA "=<arg>" 168 #define _X(n,a,o,m) { .name = n a, .opt = o, .help = m, }, 169 OPTION_LIST 170 #undef _X 171 #undef _RA 172 #undef _OA 173 #undef _NA 174 }; 175 176 if (fmt != NULL) { 177 va_list ap; 178 179 va_start(ap, fmt); 180 vfprintf(stderr, fmt, ap); 181 va_end(ap); 182 } 183 184 printf("%s version %u\n", progname, VERSION); 185 printf("Usage: %s [options]\n", progname); 186 for (size_t i = 0; i < __arraycount(tbl); i++) { 187 int n; 188 if (isprint(tbl[i].opt)) 189 n = printf("-%c | --%s", tbl[i].opt, tbl[i].name); 190 else 191 n = printf(" --%s", tbl[i].name); 192 printf("%*s %s\n", 32 - n, "", tbl[i].help); 193 } 194 exit(EXIT_SUCCESS); 195 } 196 197 static int __used 198 append_optional_data(const char *fname, efi_var_ioc_t *ev) 199 { 200 char *buf, *cp; 201 size_t cnt; 202 203 buf = read_file(fname, &cnt); 204 205 ev->data = erealloc(ev->data, ev->datasize + cnt); 206 cp = ev->data; 207 cp += ev->datasize; 208 memcpy(cp, buf, cnt); 209 ev->datasize += cnt; 210 211 return 0; 212 } 213 214 typedef enum { 215 MBR_SIG_WRITE_NEVER = 0, 216 MBR_SIG_WRITE_MAYBE, 217 MBR_SIG_WRITE_FORCE, 218 } mbr_sig_write_t; 219 220 #define OPT_LIST \ 221 _X(bool, active, false ) \ 222 _X(bool, b_flag, false ) \ 223 _X(bool, brief, false ) \ 224 _X(bool, prefix_bootorder, false ) \ 225 _X(bool, quiet, false ) \ 226 _X(bool, reconnect, false ) \ 227 _X(char *, bootorder, NULL ) \ 228 _X(char *, csus, NULL ) \ 229 _X(char *, device, NULL ) \ 230 _X(char *, opt_fname, NULL ) \ 231 _X(char *, regexp, NULL ) \ 232 _X(const char *, label, DEFAULT_LABEL ) \ 233 _X(const char *, loader, DEFAULT_LOADER ) \ 234 _X(const char *, target, NULL ) \ 235 _X(int, verbose, 0 ) \ 236 _X(uint, debug, 0 ) \ 237 _X(mbr_sig_write_t, mbr_sig_write, MBR_SIG_WRITE_NEVER ) \ 238 _X(uint16_t, bootnum, 0 ) \ 239 _X(uint16_t, partnum, DEFAULT_PARTITION ) \ 240 _X(uint16_t, timeout, 0 ) \ 241 _X(uint32_t, mbr_sig, 0 ) 242 243 #define IS_MBR_SIG_FORCE(o) ((o).mbr_sig_write == MBR_SIG_WRITE_FORCE) 244 245 static struct options { /* setable options */ 246 #define _X(t,n,v) t n; 247 OPT_LIST 248 #undef _X 249 } opt = { 250 #define _X(t,n,v) .n = v, 251 OPT_LIST 252 #undef _X 253 }; 254 255 static inline void 256 get_bootnum(struct options *op, const char *oarg) 257 { 258 259 op->b_flag = true; 260 op->bootnum = strtous(oarg, NULL, 16); 261 } 262 263 int 264 main(int argc, char **argv) 265 { 266 static struct option longopts[] = { 267 #define _NA no_argument 268 #define _OA optional_argument 269 #define _RA required_argument 270 #define _X(n,a,o,m) { n, a, NULL, o }, 271 OPTION_LIST 272 { NULL, 0, NULL, 0 }, 273 #undef _X 274 #undef _RA 275 #undef _OA 276 #undef _NA 277 }; 278 enum { 279 act_create, 280 act_set_active, 281 act_del_variable, 282 act_del_bootnext, 283 act_set_bootnext, 284 act_del_bootorder, 285 act_set_bootorder, 286 act_prefix_bootorder, 287 act_remove_bootorder, 288 act_del_bootorder_dups, 289 act_set_timeout, 290 act_del_timeout, 291 act_show, 292 act_show_gpt, 293 } action = act_show; 294 efi_var_t **var_array; 295 void *var_hdl; 296 char *fname = NULL; 297 size_t i, var_cnt; 298 int ch, efi_fd; 299 300 union { /* Just in case the above __CTASSERT() is ignored ... */ 301 uint32_t val; 302 uint8_t b[4]; 303 } byte_order = { .val = 0x01020304, }; 304 if (byte_order.b[0] != 4 || 305 byte_order.b[1] != 3 || 306 byte_order.b[2] != 2 || 307 byte_order.b[3] != 1) { 308 errx(EXIT_FAILURE, 309 "sorry: %s only runs on little-endian machines!", 310 getprogname()); 311 } 312 313 setprogname(argv[0]); 314 315 optreset = 1; 316 optind = 1; 317 opterr = 1; 318 while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) { 319 switch (ch) { 320 case OPT_BRIEF: 321 opt.brief = true; 322 break; 323 324 case OPT_DEBUG: 325 if (optarg) 326 opt.debug = strtous(optarg, NULL, 0); 327 else 328 opt.debug++; 329 opt.debug &= DEBUG_MASK; 330 break; 331 332 case '@': 333 opt.opt_fname = estrdup(optarg); 334 break; 335 336 case 'A': 337 if (optarg) 338 get_bootnum(&opt, optarg); 339 340 opt.active = false; 341 action = act_set_active; 342 break; 343 344 case 'a': 345 if (optarg) 346 get_bootnum(&opt, optarg); 347 348 opt.active = true; 349 action = act_set_active; 350 break; 351 352 case 'B': 353 if (optarg) 354 get_bootnum(&opt, optarg); 355 356 action = act_del_variable; 357 break; 358 359 case 'b': 360 get_bootnum(&opt, optarg); 361 break; 362 363 case 'C': 364 opt.prefix_bootorder = false; 365 action = act_create; 366 break; 367 368 case 'c': 369 opt.prefix_bootorder = true; 370 action = act_create; 371 break; 372 373 case 'D': 374 action = act_del_bootorder_dups; 375 break; 376 377 case 'd': 378 opt.device = estrdup(optarg); 379 break; 380 381 case 'F': 382 opt.reconnect = false; 383 break; 384 385 case 'f': 386 opt.reconnect = true; 387 break; 388 389 case 'G': 390 fname = estrdup(optarg ? optarg : "."); 391 action = act_show_gpt; 392 break; 393 394 case 'L': 395 opt.label = estrdup(optarg); 396 break; 397 398 case 'l': 399 opt.loader = estrdup(optarg); 400 break; 401 402 case 'N': 403 action = act_del_bootnext; 404 break; 405 406 case 'n': 407 opt.b_flag = true; 408 opt.bootnum = strtous(optarg, NULL, 16); 409 action = act_set_bootnext; 410 break; 411 412 case 'O': 413 action = act_del_bootorder; 414 break; 415 416 case 'o': 417 opt.bootorder = estrdup(optarg); 418 action = act_set_bootorder; 419 break; 420 421 case 'p': 422 opt.partnum = strtous(optarg, NULL, 0); 423 break; 424 425 case 'R': 426 if (opt.regexp != NULL) 427 free(opt.regexp); 428 opt.regexp = estrdup(optarg); 429 break; 430 431 case 'r': 432 if (opt.target != NULL) 433 errx(EXIT_FAILURE, 434 "only one of '-r' or '-y' are allowed"); 435 opt.target = TARGET_DRIVER; 436 break; 437 438 case 'T': 439 action = act_del_timeout; 440 break; 441 442 case 't': 443 opt.timeout = strtous(optarg, NULL, 0); 444 action = act_set_timeout; 445 break; 446 447 case 'q': 448 opt.quiet = true; 449 opt.verbose = 0; 450 break; 451 452 case 'V': 453 printf("version: %u\n", VERSION); 454 exit(EXIT_SUCCESS); 455 456 case 'v': 457 opt.verbose++; 458 opt.brief = false; 459 break; 460 461 case 'w': 462 if (optarg != NULL) { 463 opt.mbr_sig_write = MBR_SIG_WRITE_FORCE; 464 opt.mbr_sig = (uint32_t)estrtou(optarg, 0, 465 0, 0xffffffff); 466 } 467 else { 468 opt.mbr_sig_write = MBR_SIG_WRITE_MAYBE; 469 srandom((uint)time(NULL)); 470 opt.mbr_sig = (uint32_t)random(); 471 } 472 break; 473 474 case 'X': 475 action = act_remove_bootorder; 476 if (opt.csus != NULL) { 477 usage("Comma Separated Hex list" 478 " already specified!\n"); 479 } 480 opt.csus = estrdup(optarg); 481 break; 482 483 case 'x': 484 action = act_prefix_bootorder; 485 if (opt.csus != NULL) { 486 usage("Comma Separated Hex list" 487 " already specified!\n"); 488 } 489 opt.csus = estrdup(optarg); 490 break; 491 492 case 'y': 493 if (opt.target != NULL) 494 errx(EXIT_FAILURE, 495 "only one of '-r' or '-y' are allowed"); 496 opt.target = TARGET_SYSPREP; 497 break; 498 499 case 'h': 500 usage(NULL); 501 default: 502 usage("unknown option: '%c'\n", ch); 503 } 504 } 505 if (opt.target == NULL) 506 opt.target = TARGET_BOOT; 507 508 argv += optind; 509 argc -= optind; 510 511 if (argc != 0) 512 usage(NULL); 513 514 /* 515 * Check some option requirements/overrides here. 516 */ 517 if (opt.quiet) 518 opt.verbose = 0; 519 520 switch (action) { 521 case act_create: 522 if (opt.regexp != NULL) {/* override any previous setting */ 523 printf("Ignoring specified regexp: '%s'\n", 524 opt.regexp); 525 free(opt.regexp); 526 } 527 break; 528 529 case act_show_gpt: 530 return show_gpt(fname, opt.verbose); 531 532 case act_set_active: 533 case act_del_variable: 534 if (!opt.b_flag) 535 usage("please specify a boot number\n"); 536 /*FALLTHROUGH*/ 537 default: 538 if (opt.mbr_sig_write) { 539 /* 540 * This overrides all but act_create and 541 * act_show_gpt. 542 */ 543 return mbr_sig_write(opt.device, opt.mbr_sig, 544 IS_MBR_SIG_FORCE(opt), opt.verbose); 545 } 546 break; 547 } 548 549 efi_fd = open(_PATH_EFI, O_RDONLY); 550 if (efi_fd == -1) 551 err(EXIT_FAILURE, "open"); 552 553 switch (action) { 554 case act_del_bootorder_dups: return del_bootorder_dups(efi_fd, opt.target); 555 case act_del_bootorder: return del_bootorder(efi_fd, opt.target); 556 case act_del_bootnext: return del_bootnext(efi_fd); 557 case act_del_timeout: return del_timeout(efi_fd); 558 case act_del_variable: return del_variable(efi_fd, opt.target, opt.bootnum); 559 560 case act_set_active: return set_active(efi_fd, opt.target, opt.bootnum, opt.active); 561 case act_set_bootnext: return set_bootnext(efi_fd, opt.bootnum); 562 case act_set_bootorder: return set_bootorder(efi_fd, opt.target, opt.bootorder); 563 case act_set_timeout: return set_timeout(efi_fd, opt.timeout); 564 565 case act_remove_bootorder: return remove_bootorder(efi_fd, opt.target, opt.csus, 0); 566 case act_prefix_bootorder: return prefix_bootorder(efi_fd, opt.target, opt.csus, 0); 567 568 case act_show_gpt: assert(0); break; /* handled above */ 569 default: break; 570 } 571 572 /* 573 * The following actions are handled below and require a call 574 * to get_variables() using a regexp. Setup the regexp here. 575 * XXX: merge with above switch()? 576 */ 577 switch (action) { 578 case act_create: 579 easprintf(&opt.regexp, "^%s[0-9,A-F]{4}$", opt.target); 580 break; 581 582 case act_show: 583 default: 584 if (opt.regexp != NULL) 585 break; 586 587 if (opt.b_flag) { 588 easprintf(&opt.regexp, "^%s%04X$", 589 opt.target, opt.bootnum); 590 } else { 591 easprintf(&opt.regexp, "^%s", opt.target); 592 } 593 break; 594 } 595 596 var_hdl = get_variables(efi_fd, opt.regexp, &var_array, &var_cnt); 597 598 free(opt.regexp); 599 opt.regexp = NULL; 600 601 /* 602 * preform the remaining actions. 603 */ 604 switch (action) { 605 case act_create: { 606 uint16_t bootnum; 607 efi_var_t v; 608 uint32_t attrib; 609 int rv; 610 611 if (opt.device == NULL) { 612 opt.device = DEFAULT_DEVICE; 613 if (opt.device == NULL) 614 errx(EXIT_FAILURE, "specify disk with '-d'"); 615 } 616 attrib = LOAD_OPTION_ACTIVE; 617 if (opt.reconnect && IS_TARGET_DRIVER(opt)) 618 attrib |= LOAD_OPTION_FORCE_RECONNECT; 619 620 /* 621 * Get a new variable name 622 */ 623 bootnum = (uint16_t)find_new_bootvar(var_array, var_cnt, 624 opt.target); 625 easprintf(&v.name, "%s%04X", opt.target, bootnum); 626 627 if (!opt.quiet) 628 printf("creating: %s\n", v.name); 629 630 /* 631 * Initialize efi_ioc structure. 632 */ 633 efi_var_init(&v.ev, v.name, 634 &EFI_GLOBAL_VARIABLE, 635 EFI_VARIABLE_NON_VOLATILE | 636 EFI_VARIABLE_BOOTSERVICE_ACCESS | 637 EFI_VARIABLE_RUNTIME_ACCESS); 638 639 /* 640 * Setup the efi_ioc data section 641 */ 642 v.ev.data = make_bootvar_data(opt.device, opt.partnum, 643 attrib, opt.label, opt.loader, opt.opt_fname, 644 &v.ev.datasize); 645 #if 1 646 if (!opt.quiet) { 647 /* 648 * Prompt user for confirmation. 649 * XXX: Should this go away? 650 */ 651 opt.debug &= (uint)~DEBUG_BRIEF_BIT; 652 opt.debug |= DEBUG_VERBOSE_BIT; 653 show_variable(&v, opt.debug, 0); 654 655 printf("are you sure? [y/n] "); 656 if (getchar() != 'y') 657 goto done; 658 } 659 #endif 660 /* 661 * Write the variable. 662 */ 663 rv = set_variable(efi_fd, &v.ev); 664 if (rv == -1) 665 err(EXIT_FAILURE, "set_variable"); 666 667 /* 668 * Prefix the boot order if required. 669 */ 670 if (opt.prefix_bootorder) 671 rv = prefix_bootorder(efi_fd, opt.target, NULL, 672 bootnum); 673 674 /* 675 * Possibly write the MBR signature. 676 * XXX: do we really want this here? 677 */ 678 if (opt.mbr_sig_write) { 679 assert(opt.device != NULL); 680 mbr_sig_write(opt.device, opt.mbr_sig, 681 IS_MBR_SIG_FORCE(opt), opt.verbose); 682 } 683 break; 684 } 685 686 case act_show: { 687 uint max_namelen = get_max_namelen(var_array, var_cnt); 688 uint flags = opt.debug; 689 690 if (opt.verbose) 691 flags |= DEBUG_VERBOSE_BIT; 692 693 if (opt.brief) 694 flags |= DEBUG_BRIEF_BIT; 695 696 if (max_namelen > 32) 697 max_namelen = 32; 698 699 for (i = 0; i < var_cnt; i++) { 700 if (opt.brief) 701 show_generic_data(var_array[i], max_namelen); 702 else 703 show_variable(var_array[i], flags, 0); 704 } 705 break; 706 } 707 708 default: 709 assert(0); 710 break; 711 } 712 713 done: 714 free_variables(var_hdl); 715 close(efi_fd); 716 717 return 0; 718 } 719