main.c revision 1.2 1 /* $NetBSD: main.c,v 1.2 2025/02/27 17:26:56 christos 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.2 2025/02/27 17:26:56 christos Exp $");
29 #endif /* not lint */
30
31 #include <sys/efiio.h>
32 #include <sys/queue.h>
33
34 #include <assert.h>
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <getopt.h>
40 #include <inttypes.h>
41 #include <limits.h>
42 #include <regex.h>
43 #include <stdbool.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <time.h>
48 #include <util.h>
49
50 #include "efiio.h"
51 #include "defs.h"
52 #include "bootvar.h"
53 #include "devpath.h"
54 #include "getvars.h"
55 #include "gptsubr.h"
56 #include "map.h"
57 #include "setvar.h"
58 #include "showvar.h"
59 #include "utils.h"
60
61 /*
62 * The UEFI spec is quite clear that it is intended for little endian
63 * machines only. As a result (and lazyness), byte ordering is
64 * ignored in this code and we build/run only on little endian
65 * machines.
66 */
67 __CTASSERT(_BYTE_ORDER == _LITTLE_ENDIAN);
68
69 #define _PATH_EFI "/dev/efi" /* XXX: should be in <paths.h> */
70
71 #define DEFAULT_PARTITION 1
72 #define DEFAULT_LABEL "NetBSD"
73 #define DEFAULT_LOADER "\\EFI\\NetBSD\\grubx64.efi"
74 #define DEFAULT_DEVICE parent_of_fname(".")
75
76 /****************************************/
77
78 static uint __used
79 get_max_namelen(efi_var_t **var_array, size_t var_cnt)
80 {
81 uint max_len = 0;
82
83 for (size_t i = 0; i < var_cnt; i++) {
84 uint len = (uint)strlen(var_array[i]->name);
85 if (len > max_len)
86 max_len = len;
87 }
88 return max_len;
89 }
90
91 /************************************************************************/
92
93 enum {
94 OPT_BRIEF = UCHAR_MAX + 1,
95 OPT_DEBUG = UCHAR_MAX + 2,
96 };
97
98 #define OPTIONS "@:A::a::B::b:CcDd:FfG::hL:l:Nn:Oo:p:qR:rTt:Vvw::X:x:y"
99 #define OPTION_LIST \
100 _X("brief", _NA, OPT_BRIEF, "set brief mode") \
101 _X("debug", _OA, OPT_DEBUG, "raise or set the debug level") \
102 _X("append-binary-args",_RA, '@', "Append extra variable args from file") \
103 _X("inactive", _OA, 'A', "clear active bit on '-b' variable") \
104 _X("active", _OA, 'a', "set active bit on '-b' variable") \
105 _X("delete-bootnum", _OA, 'B', "delete '-b' variable or arg") \
106 _X("bootnum", _RA, 'b', "specify a boot number") \
107 _X("create-only", _NA, 'C', "create a new boot variable") \
108 _X("create", _NA, 'c', "create a new boot variable and prefix BootOrder") \
109 _X("remove-dups", _NA, 'D', "remove duplicates in BootOrder") \
110 _X("disk", _RA, 'd', "specify disk device") \
111 _X("no-reconnect", _NA, 'F', "do not force driver reconnect after loading (requires '-r')") \
112 _X("reconnect", _NA, 'f', "force driver reconnect after loading (requires '-r')") \
113 _X("show-gpt", _OA, 'G', "show GPT for device") \
114 _X("help", _NA, 'h', "this help") \
115 _X("label", _RA, 'L', "specify boot label") \
116 _X("loader", _RA, 'l', "specify boot loader on EFI partition") \
117 _X("delete-bootnext", _NA, 'N', "delete NextBoot variable") \
118 _X("bootnext", _RA, 'n', "set NextBoot variable") \
119 _X("delete-bootorder", _NA, 'O', "delete BootOrder entirely") \
120 _X("bootorder", _RA, 'o', "set BootOrder") \
121 _X("part", _RA, 'p', "specify partition number") \
122 _X("quiet", _NA, 'q', "quiet mode") \
123 _X("regexp", _RA, 'R', "regular expression for variable search (default: '^Boot')") \
124 _X("driver", _NA, 'r', "operate on Driver#### instead of Boot####") \
125 _X("delete-timeout", _NA, 'T', "delete timeout") \
126 _X("timeout", _RA, 't', "set timeout to argument, in seconds") \
127 _X("version", _NA, 'V', "show Version") \
128 _X("verbose", _NA, 'v', "increment verboseness") \
129 _X("write-signature", _OA, 'w', "write MBR signature") \
130 _X("remove-bootorder", _RA, 'X', "remove argument from BootOrder") \
131 _X("prefix-bootorder", _RA, 'x', "prefix argument to BootOrder") \
132 _X("sysprep", _NA, 'y', "operate on SysPrep#### instead of Boot####")
133
134 #define TARGET_BOOT "Boot"
135 #define TARGET_DRIVER "Driver"
136 #define TARGET_SYSPREP "SysPrep"
137
138 #define IS_TARGET_DRIVER(opt) ((opt).target[0] == TARGET_DRIVER[0])
139
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
198 static int __used
199 append_optional_data(const char *fname, efi_var_ioc_t *ev)
200 {
201 char *buf, *cp;
202 size_t cnt;
203
204 buf = read_file(fname, &cnt);
205
206 ev->data = erealloc(ev->data, ev->datasize + cnt);
207 cp = ev->data;
208 cp += ev->datasize;
209 memcpy(cp, buf, cnt);
210 ev->datasize += cnt;
211
212 return 0;
213 }
214
215 typedef enum {
216 MBR_SIG_WRITE_NEVER = 0,
217 MBR_SIG_WRITE_MAYBE,
218 MBR_SIG_WRITE_FORCE,
219 } mbr_sig_write_t;
220
221 #define OPT_LIST \
222 _X(bool, active, false ) \
223 _X(bool, b_flag, false ) \
224 _X(bool, brief, false ) \
225 _X(bool, prefix_bootorder, false ) \
226 _X(bool, quiet, false ) \
227 _X(bool, reconnect, false ) \
228 _X(char *, bootorder, NULL ) \
229 _X(char *, csus, NULL ) \
230 _X(char *, device, NULL ) \
231 _X(char *, opt_fname, NULL ) \
232 _X(char *, regexp, NULL ) \
233 _X(const char *, label, DEFAULT_LABEL ) \
234 _X(const char *, loader, DEFAULT_LOADER ) \
235 _X(const char *, target, NULL ) \
236 _X(int, verbose, 0 ) \
237 _X(int, debug, 0 ) \
238 _X(mbr_sig_write_t, mbr_sig_write, MBR_SIG_WRITE_NEVER ) \
239 _X(uint16_t, bootnum, 0 ) \
240 _X(uint16_t, partnum, DEFAULT_PARTITION ) \
241 _X(uint16_t, timeout, 0 ) \
242 _X(uint32_t, mbr_sig, 0 )
243
244 #define IS_MBR_SIG_FORCE(o) ((o).mbr_sig_write == MBR_SIG_WRITE_FORCE)
245
246 static struct options { /* setable options */
247 #define _X(t,n,v) t n;
248 OPT_LIST
249 #undef _X
250 } opt = {
251 #define _X(t,n,v) .n = v,
252 OPT_LIST
253 #undef _X
254 };
255
256 static inline void
257 get_bootnum(struct options *op, const char *oarg)
258 {
259
260 op->b_flag = true;
261 op->bootnum = strtous(oarg, NULL, 16);
262 }
263
264 int
265 main(int argc, char **argv)
266 {
267 static struct option longopts[] = {
268 #define _NA no_argument
269 #define _OA optional_argument
270 #define _RA required_argument
271 #define _X(n,a,o,m) { n, a, NULL, o },
272 OPTION_LIST
273 { NULL, 0, NULL, 0 },
274 #undef _X
275 #undef _RA
276 #undef _OA
277 #undef _NA
278 };
279 enum {
280 act_create,
281 act_set_active,
282 act_del_variable,
283 act_del_bootnext,
284 act_set_bootnext,
285 act_del_bootorder,
286 act_set_bootorder,
287 act_prefix_bootorder,
288 act_remove_bootorder,
289 act_del_bootorder_dups,
290 act_set_timeout,
291 act_del_timeout,
292 act_show,
293 act_show_gpt,
294 } action = act_show;
295 efi_var_t **var_array;
296 void *var_hdl;
297 char *fname = NULL;
298 size_t i, var_cnt;
299 int ch, efi_fd;
300
301 union { /* Just in case the above __CTASSERT() is ignored ... */
302 uint32_t val;
303 uint8_t b[4];
304 } byte_order = { .val = 0x01020304, };
305 if (byte_order.b[0] != 4 ||
306 byte_order.b[1] != 3 ||
307 byte_order.b[2] != 2 ||
308 byte_order.b[3] != 1) {
309 errx(EXIT_FAILURE, "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, 0, 0xffffffff);
465 }
466 else {
467 opt.mbr_sig_write = MBR_SIG_WRITE_MAYBE;
468 srandom((uint)time(NULL));
469 opt.mbr_sig = (uint32_t)random();
470 }
471 break;
472
473 case 'X':
474 action = act_remove_bootorder;
475 if (opt.csus != NULL) {
476 usage("Comma Separated Hex list already specified!\n");
477 }
478 opt.csus = estrdup(optarg);
479 break;
480
481 case 'x':
482 action = act_prefix_bootorder;
483 if (opt.csus != NULL) {
484 usage("Comma Separated Hex list already specified!\n");
485 }
486 opt.csus = estrdup(optarg);
487 break;
488
489 case 'y':
490 if (opt.target != NULL)
491 errx(EXIT_FAILURE,
492 "only one of '-r' or '-y' are allowed");
493 opt.target = TARGET_SYSPREP;
494 break;
495
496 case 'h':
497 usage(NULL);
498 default:
499 usage("unknown option: '%c'\n", ch);
500 }
501 }
502 if (opt.target == NULL)
503 opt.target = TARGET_BOOT;
504
505 argv += optind;
506 argc -= optind;
507
508 if (argc != 0)
509 usage(NULL);
510
511 /*
512 * Check some option requirements/overrides here.
513 */
514 if (opt.quiet)
515 opt.verbose = 0;
516
517 switch (action) {
518 case act_create:
519 if (opt.regexp != NULL) {/* override any previous setting */
520 printf("Ignoring specified regexp: '%s'\n",
521 opt.regexp);
522 free(opt.regexp);
523 }
524 break;
525
526 case act_show_gpt:
527 return show_gpt(fname, opt.verbose);
528
529 case act_set_active:
530 case act_del_variable:
531 if (!opt.b_flag)
532 usage("please specify a boot number\n");
533 /*FALLTHROUGH*/
534 default:
535 if (opt.mbr_sig_write) {
536 /*
537 * This overrides all but act_create and
538 * act_show_gpt.
539 */
540 return mbr_sig_write(opt.device, opt.mbr_sig,
541 IS_MBR_SIG_FORCE(opt), opt.verbose);
542 }
543 break;
544 }
545
546 efi_fd = open(_PATH_EFI, O_RDONLY);
547 if (efi_fd == -1)
548 err(EXIT_FAILURE, "open");
549
550 switch (action) {
551 case act_del_bootorder_dups: return del_bootorder_dups(efi_fd, opt.target);
552 case act_del_bootorder: return del_bootorder(efi_fd, opt.target);
553 case act_del_bootnext: return del_bootnext(efi_fd);
554 case act_del_timeout: return del_timeout(efi_fd);
555 case act_del_variable: return del_variable(efi_fd, opt.target, opt.bootnum);
556
557 case act_set_active: return set_active(efi_fd, opt.target, opt.bootnum, opt.active);
558 case act_set_bootnext: return set_bootnext(efi_fd, opt.bootnum);
559 case act_set_bootorder: return set_bootorder(efi_fd, opt.target, opt.bootorder);
560 case act_set_timeout: return set_timeout(efi_fd, opt.timeout);
561
562 case act_remove_bootorder: return remove_bootorder(efi_fd, opt.target, opt.csus, 0);
563 case act_prefix_bootorder: return prefix_bootorder(efi_fd, opt.target, opt.csus, 0);
564
565 case act_show_gpt: assert(0); break; /* handled above */
566 default: break;
567 }
568
569 /*
570 * The following actions are handled below and require a call
571 * to get_variables() using a regexp. Setup the regexp here.
572 * XXX: merge with above switch()?
573 */
574 switch (action) {
575 case act_create:
576 easprintf(&opt.regexp, "^%s[0-9,A-F]{4}$", opt.target);
577 break;
578
579 case act_show:
580 default:
581 if (opt.regexp != NULL)
582 break;
583
584 if (opt.b_flag)
585 easprintf(&opt.regexp, "^%s%04X$", opt.target, opt.bootnum);
586 else
587 easprintf(&opt.regexp, "^%s", opt.target);
588 break;
589 }
590
591 var_hdl = get_variables(efi_fd, opt.regexp, &var_array, &var_cnt);
592
593 free(opt.regexp);
594 opt.regexp = NULL;
595
596 /*
597 * preform the remaining actions.
598 */
599 switch (action) {
600 case act_create: {
601 uint16_t bootnum;
602 efi_var_t v;
603 uint32_t attrib;
604 int rv;
605
606 if (opt.device == NULL) {
607 opt.device = DEFAULT_DEVICE;
608 if (opt.device == NULL)
609 errx(EXIT_FAILURE, "specify disk with '-d'");
610 }
611 attrib = LOAD_OPTION_ACTIVE;
612 if (opt.reconnect && IS_TARGET_DRIVER(opt))
613 attrib |= LOAD_OPTION_FORCE_RECONNECT;
614
615 /*
616 * Get a new variable name
617 */
618 bootnum = (uint16_t)find_new_bootvar(var_array, var_cnt, opt.target);
619 easprintf(&v.name, "%s%04X", opt.target, bootnum);
620
621 if (!opt.quiet)
622 printf("creating: %s\n", v.name);
623
624 /*
625 * Initialize efi_ioc structure.
626 */
627 efi_var_init(&v.ev, v.name,
628 &EFI_GLOBAL_VARIABLE,
629 EFI_VARIABLE_NON_VOLATILE |
630 EFI_VARIABLE_BOOTSERVICE_ACCESS |
631 EFI_VARIABLE_RUNTIME_ACCESS);
632
633 /*
634 * Setup the efi_ioc data section
635 */
636 v.ev.data = make_bootvar_data(opt.device, opt.partnum,
637 attrib, opt.label, opt.loader, opt.opt_fname, &v.ev.datasize);
638 #if 1
639 if (!opt.quiet) {
640 /*
641 * Prompt user for confirmation.
642 * XXX: Should this go away?
643 */
644 opt.debug &= (int)~DEBUG_BRIEF_BIT;
645 opt.debug |= DEBUG_VERBOSE_BIT;
646 show_variable(&v, opt.debug, 0);
647
648 printf("are you sure? [y/n] ");
649 if (getchar() != 'y')
650 goto done;
651 }
652 #endif
653 /*
654 * Write the variable.
655 */
656 rv = set_variable(efi_fd, &v.ev);
657 if (rv == -1)
658 err(EXIT_FAILURE, "set_variable");
659
660 /*
661 * Prefix the boot order if required.
662 */
663 if (opt.prefix_bootorder)
664 rv = prefix_bootorder(efi_fd, opt.target, NULL,
665 bootnum);
666
667 /*
668 * Possibly write the MBR signature.
669 * XXX: do we really want this here?
670 */
671 if (opt.mbr_sig_write) {
672 assert(opt.device != NULL);
673 mbr_sig_write(opt.device, opt.mbr_sig,
674 IS_MBR_SIG_FORCE(opt), opt.verbose);
675 }
676 break;
677 }
678
679 case act_show: {
680 uint max_namelen = get_max_namelen(var_array, var_cnt);
681 int flags = opt.debug;
682
683 if (opt.verbose)
684 flags |= DEBUG_VERBOSE_BIT;
685
686 if (opt.brief)
687 flags |= DEBUG_BRIEF_BIT;
688
689 if (max_namelen > 32)
690 max_namelen = 32;
691
692 for (i = 0; i < var_cnt; i++) {
693 if (opt.brief)
694 show_generic_data(var_array[i], max_namelen);
695 else
696 show_variable(var_array[i], flags, 0);
697 }
698 break;
699 }
700
701 default:
702 assert(0);
703 break;
704 }
705
706 done:
707 free_variables(var_hdl);
708 close(efi_fd);
709
710 return 0;
711 }
712