ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.59
Committed: Thu Aug 22 09:09:45 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-1_4
Changes since 1.58: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #!/usr/bin/perl
2
3 #
4 # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de>
5 #
6 # SPDX-License-Identifier: GPL-3.0-or-later
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <https://www.gnu.org/licenses/>.
20 #
21
22 use 5.016; # numerous features need 5.14, __SUB__ needs 5.16
23
24 our $VERSION = '1.4';
25 our $JSON_VERSION = 3; # the version of the json objects generated by this program
26
27 our $CHANGELOG = <<EOF;
28
29 1.4 Thu Aug 22 10:48:22 CEST 2019
30 - new "create" subcommand.
31 - "create" and "edit" try to save and restore ownership/permissions
32 of bcd hives when writing the new file.
33 - editorial fixes to the documentation.
34 - add mininmal hive creation example.
35
36 1.3 Sat Aug 17 07:04:15 CEST 2019
37 - output of pbcdedit elements --json has changed, as it didn't
38 take the reorganisation by classes fully into account.
39 - json schema bumped to 3.
40 - new "bcd-device" and "bcd-legacy-device" subcommands.
41 - implement --json option for lsblk.
42
43 1.2 Fri Aug 16 00:20:41 CEST 2019
44 - bcd element names now depend on the bcd object type they are in,
45 also affects "elements" output.
46 - json schema bumped to 2.
47 - new version command.
48 - numerous minor bugfixes.
49
50 EOF
51
52 =head1 NAME
53
54 pbcdedit - portable boot configuration data (BCD) store editor
55
56 =head1 SYNOPSIS
57
58 pbcdedit help # output manual page
59 pbcdedit version # output version and changelog
60
61 pbcdedit export path/to/BCD # output BCD hive as JSON
62 pbcdedit import path/to/BCD # convert standard input to BCD hive
63 pbcdedit edit path/to/BCD edit-instructions...
64
65 pbcdedit objects # list all supported object aliases and types
66 pbcdedit elements # list all supported bcd element aliases
67
68 # Example: enable text-based boot menu.
69 pbcdedit edit /my/BCD set '{default}' bootmenupolicy 1
70
71 # Example change system device to first partition containing winload.
72 pbcdedit edit /my/BCD \
73 set '{default}' device 'locate=<null>,element,path' \
74 set '{default}' osdevice 'locate=<null>,element,path'
75
76
77 =head1 DESCRIPTION
78
79 This program allows you to create, read and modify Boot Configuration Data
80 (BCD) stores used by Windows Vista and newer versions of Windows.
81
82 At this point, it is in relatively early stages of development and has
83 received little to no real-world testing.
84
85 Compared to other BCD editing programs it offers the following unique
86 features:
87
88 =over
89
90 =item Can create BCD hives from scratch
91
92 Practically all other BCD editing programs force you to copy existing BCD
93 stores, which might or might not be copyrighted by Microsoft.
94
95 =item Does not rely on Windows
96
97 As the "portable" in the name implies, this program does not rely on
98 C<bcdedit> or other windows programs or libraries, it works on any system
99 that supports at least perl version 5.16.
100
101 =item Decodes and encodes BCD device elements
102
103 PBCDEDIT can concisely decode and encode BCD device element contents. This
104 is pretty unique, and offers a lot of potential that can't be realised
105 with C<bcdedit> or any programs relying on it.
106
107 =item Minimal files
108
109 BCD files written by PBCDEDIT are always "minimal", that is, they don't
110 contain unused data areas and therefore don't contain old and potentially
111 sensitive data.
112
113 =back
114
115 The target audience for this program is professionals and tinkerers who
116 are ready to invest time into learning how it works. It is not an easy
117 program to use and requires patience and a good understanding of BCD
118 stores.
119
120
121 =head1 SUBCOMMANDS
122
123 PBCDEDIT expects a subcommand as first argument that tells it what to
124 do. The following subcommands exist:
125
126 =over
127
128 =item C<help>
129
130 Displays the whole manual page (this document).
131
132 =item C<version>
133
134 This outputs the PBCDEDIT version, the JSON schema version it uses and the
135 full log of changes.
136
137 =item C<export> F<path>
138
139 Reads a BCD data store and writes a JSON representation of it to standard
140 output.
141
142 The format of the data is explained later in this document.
143
144 Example: read a BCD store, modify it with an external program, write it
145 again.
146
147 pbcdedit export BCD | modify-json-somehow | pbcdedit import BCD
148
149 =item C<import> F<path>
150
151 The reverse of C<export>: Reads a JSON representation of a BCD data store
152 from standard input, and creates or replaces the given BCD data store.
153
154 =item C<edit> F<path> I<instructions...>
155
156 Load a BCD data store, apply some instructions to it, and save it again.
157
158 See the section L<EDITING BCD STORES>, below, for more info.
159
160 =item C<parse> F<path> I<instructions...>
161
162 Same as C<edit>, above, except it doesn't save the data store again. Can
163 be useful to extract some data from it.
164
165 =item C<create> F<path> I<instructions...>
166
167 Same as C<edit>, above, except it creates a new data store from scratch if
168 needed. An existing store will be emptied completely.
169
170 =item C<lsblk> [C<--json>]
171
172 On a GNU/Linux system, you can get a list of partition device descriptors
173 using this command - the external C<lsblk> command is required, as well as
174 a mounted C</sys> file system.
175
176 The output will be a list of all partitions in the system and C<partition>
177 descriptors for GPT and both C<legacypartition> and C<partition>
178 descriptors for MBR partitions.
179
180 With C<--json> it will print similar information as C<lsblk --json>, but
181 with extra C<bcd_device> and C<bcd_legacy_device> attributes.
182
183 =item C<bcd-device> F<path>
184
185 Tries to find the BCD device element for the given device, which currently
186 must be a a partition of some kind. Prints the C<partition=> descriptor as
187 a result, or nothing. Exit status will be true on success, and false on
188 failure.
189
190 Like C<lsblk>, above, this likely only works on GNU/Linux systems.
191
192 Example: print the partition descriptor of tghe partition with label DATA.
193
194 $ pbcdedit bcd-device /dev/disk/by-label/DATA
195 partition=<null>,harddisk,mbr,47cbc08a,213579202560
196
197 =item C<bcd-legacy-device> F<path>
198
199 Like above, but uses a C<legacypartition> descriptor instead.
200
201 =item C<objects> [C<--json>]
202
203 Outputs two tables: a table listing all type aliases with their hex BCD
204 element ID, and all object name aliases with their GUID and default type
205 (if any).
206
207 With C<--json> it prints similar information as a JSON object, for easier parsing.
208
209 =item C<elements> [C<--json>]
210
211 Outputs a table of known element aliases with their hex ID and the format
212 type.
213
214 With C<--json> it prints similar information as a JSON object, for easier parsing.
215
216 =item C<export-regf> F<path>
217
218 This has nothing to do with BCD stores, but simply exposes PCBEDIT's
219 internal registry hive reader - it takes a registry hive file as argument
220 and outputs a JSON representation of it to standard output.
221
222 Hive versions 1.2 till 1.6 are supported.
223
224 =item C<import-regf> F<path>
225
226 The reverse of C<export-regf>: reads a JSON representation of a registry
227 hive from standard input and creates or replaces the registry hive file
228 given as argument.
229
230 The written hive will always be in a slightly modified version 1.3
231 format. It's not the format windows would generate, but it should be
232 understood by any conformant hive reader.
233
234 Note that the representation chosen by PBCDEDIT currently throws away
235 classname data (often used for feeble attempts at hiding stuff by
236 Microsoft) and security descriptors, so if you write anything other than
237 a BCD hive you will most likely destroy it.
238
239 =back
240
241
242 =head1 BCD STORE REPRESENTATION FORMAT
243
244 A BCD data store is represented as a JSON object with one special key,
245 C<meta>, and one key per BCD object. That is, each BCD object becomes
246 one key-value pair in the object, and an additional key called C<meta>
247 contains meta information.
248
249 Here is an abridged example of a real BCD store:
250
251 {
252 "meta" : {
253 "version" : 1
254 },
255 "{7ae02178-821d-11e7-8813-1c872c5f5ab0}" : {
256 "type" : "application::osloader",
257 "description" : "Windows 10",
258 "device" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
259 "osdevice" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
260 "path" : "\\Windows\\system32\\winload.exe",
261 "systemroot" : "\\Windows"
262 },
263 "{bootloadersettings}" : {
264 "inherit" : "{globalsettings} {hypervisorsettings}"
265 },
266 "{bootmgr}" : {
267 "description" : "Windows Boot Manager",
268 "device" : "partition=<null>,harddisk,mbr,ff3ba63b,1048576",
269 "displayorder" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
270 "inherit" : "{globalsettings}",
271 "displaybootmenu" : 0,
272 "timeout" : 30
273 },
274 "{globalsettings}" : {
275 "inherit" : "{dbgsettings} {emssettings} {badmemory}"
276 },
277 "{hypervisorsettings}" : {
278 "hypervisorbaudrate" : 115200,
279 "hypervisordebugport" : 1,
280 "hypervisordebugtype" : 0
281 },
282 # ...
283 }
284
285 =head2 Minimal BCD to boot windows
286
287 Experimentally I found the following BCD is the minimum required to
288 successfully boot any post-XP version of Windows (assuming suitable
289 C<device> and C<osdevice> values, of course, and assuming a BIOS boot -
290 for UEFI, you should use F<winload.efi> instead of F<winload.exe>):
291
292 {
293 "{bootmgr}" : {
294 "default" : "{45b547a7-8ca6-4417-9eb0-a257b61f35b4}"
295 },
296
297 "{45b547a7-8ca6-4417-9eb0-a257b61f35b1}" : {
298 "type" : "application::osloader",
299 "description" : "Windows Boot",
300 "device" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
301 "osdevice" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
302 "path" : "\\Windows\\system32\\winload.exe",
303 "systemroot" : "\\Windows"
304 },
305 }
306
307 Note that minimal doesn't mean recommended - Windows itself will add stuff
308 to this during or after boot, and you might or might not run into issues
309 when installing updates as it might not be able to find the F<bootmgr>.
310
311 This is how you would create a minimal hive with PBCDEDIT from within
312 GNU/Linux, assuming F</dev/sdc3> is the windows partition, using
313 a random GUID for the osloader and using C<partition> instead of
314 C<legacypartition>:
315
316 osldr="{$(uuidgen)}"
317 part=$(pbcdedit bcd-device /dev/sdc3)
318 pbcdedit create minimal.bcd \
319 set '{bootmgr}' default "$osldr" \
320 set "$osldr" type application::osloader \
321 set "$osldr" description 'Windows Boot' \
322 set "$osldr" device "$part" \
323 set "$osldr" osdevice "$part" \
324 set "$osldr" path '\Windows\system32\winload.exe' \
325 set "$osldr" systemroot '\Windows'
326
327 =head2 The C<meta> key
328
329 The C<meta> key is not stored in the BCD data store but is used only
330 by PBCDEDIT. It is always generated when exporting, and importing will
331 be refused when it exists and the version stored inside doesn't store
332 the JSON schema version of PBCDEDIT. This ensures that different and
333 incompatible versions of PBCDEDIT will not read and misinterpret each
334 others data.
335
336 =head2 The object keys
337
338 Every other key is a BCD object. There is usually a BCD object for the
339 boot manager, one for every boot option and a few others that store common
340 settings inherited by these.
341
342 Each BCD object is represented by a GUID wrapped in curly braces. These
343 are usually random GUIDs used only to distinguish BCD objects from each
344 other. When adding a new boot option, you can simply generate a new GUID.
345
346 Some of these GUIDs are fixed well known GUIDs which PBCDEDIT will decode
347 into human-readable strings such as C<{globalsettings}>, which is the same
348 as C<{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}>.
349
350 Each BCD, object has an associated type. For example,
351 C<application::osloader> for objects loading Windows via F<winload.exe>,
352 C<application::bootsector> for real mode applications and so on.
353
354 The type of a object is stored in the pseudo BCD element C<type> (see next
355 section).
356
357 Some well-known objects have a default type. If an object type matches
358 its default type, then the C<type> element will be omitted. Similarly, if
359 the C<type> element is missing and the BCD object has a default type, the
360 default type will be used when writing a BCD store.
361
362 Running F<pbcdedit objects> will give you a list of object types,
363 well-known object aliases and their default types.
364
365 If different string keys in a JSON BCD store map to the same BCD object
366 then a random one will "win" and the others will be discarded. To avoid
367 this, you should always use the "canonical" name of a BCD object, which is
368 the human-readable form (if it exists).
369
370 =head2 The object values - BCD elements
371
372 The value of each BCD object entry consists of key-value pairs called BCD
373 elements.
374
375 BCD elements are identified by a 32 bit number, but to make things
376 simpler PBCDEDIT will replace these with well-known strings such as
377 C<description>, C<device> or C<path>.
378
379 When PBCDEDIT does not know the BCD element, it will use
380 C<custom:HHHHHHHH>, where C<HHHHHHHH> is the 8-digit hex number of the
381 BCD element. For example, C<device> would be C<custom::11000001>. You can
382 get a list of all BCD elements known to PBCDEDIT by running F<pbcdedit
383 elements>.
384
385 What was said about duplicate keys mapping to the same object is true for
386 elements as well, so, again, you should always use the canonical name,
387 which is the human readable alias, if known.
388
389 =head3 BCD element types
390
391 Each BCD element has a type such as I<string> or I<boolean>. This type
392 determines how the value is interpreted, and most of them are pretty easy
393 to explain:
394
395 =over
396
397 =item string
398
399 This is simply a unicode string. For example, the C<description> and
400 C<systemroot> elements both are of this type, one storing a human-readable
401 name for this boot option, the other a file path to the windows root
402 directory:
403
404 "description" : "Windows 10",
405 "systemroot" : "\\Windows",
406
407 =item boolean
408
409 Almost as simple are booleans, which represent I<true>/I<false>,
410 I<on>/I<off> and similar values. In the JSON form, true is represented
411 by the number C<1>, and false is represented by the number C<0>. Other
412 values will be accepted, but PBCDEDIT doesn't guarantee how these are
413 interpreted.
414
415 For example, C<displaybootmenu> is a boolean that decides whether to
416 enable the C<F8> boot menu. In the example BCD store above, this is
417 disabled:
418
419 "displaybootmenu" : 0,
420
421 =item integer
422
423 Again, very simple, this is a 64 bit integer. It can be either specified
424 as a decimal number, as a hex number (by prefixing it with C<0x>) or as a
425 binary number (prefix C<0b>).
426
427 For example, the boot C<timeout> is an integer, specifying the automatic
428 boot delay in seconds:
429
430 "timeout" : 30,
431
432 =item integer list
433
434 This is a list of 64 bit integers separated by whitespace. It is not used
435 much, so here is a somewhat artificial and untested example of using
436 C<customactions> to specify a certain custom, eh, action to be executed
437 when pressing C<F10> at boot:
438
439 "customactions" : "0x1000044000001 0x54000001",
440
441 =item guid
442
443 This represents a single GUID value wrapped in curly braces. It is used a
444 lot to refer from one BCD object to other one.
445
446 For example, The C<{bootmgr}> object might refer to a resume boot option
447 using C<default>:
448
449 "default" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
450
451 Human readable aliases are used and allowed.
452
453 =item guid list
454
455 Similar to the GUID type, this represents a list of such GUIDs, separated
456 by whitespace from each other.
457
458 For example, many BCD objects can I<inherit> elements from other BCD
459 objects by specifying the GUIDs of those other objects in a GUID list
460 called surprisingly called C<inherit>:
461
462 "inherit" : "{dbgsettings} {emssettings} {badmemory}",
463
464 This example also shows how human readable aliases can be used.
465
466 =item device
467
468 This type is why I write I<most> are easy to explain earlier: This type
469 is the pinnacle of Microsoft-typical hacks layered on top of other
470 hacks. Understanding this type took more time than writing all the rest of
471 PBCDEDIT, and because it is so complex, this type has its own subsection
472 below.
473
474 =back
475
476 =head3 The BCD "device" element type
477
478 Device elements specify, well, devices. They are used for such diverse
479 purposes such as finding a TFTP network boot image, serial ports or VMBUS
480 devices, but most commonly they are used to specify the disk (harddisk,
481 cdrom, ramdisk, vhd...) to boot from.
482
483 The device element is kind of a mini-language in its own which is much
484 more versatile then the limited windows interface to it - BCDEDIT -
485 reveals.
486
487 While some information can be found on the BCD store and the windows
488 registry, there is pretty much no public information about the device
489 element, so almost everything known about it had to be researched first
490 in the process of writing this script, and consequently, support for BCD
491 device elements is partial only.
492
493 On the other hand, the expressive power of PBCDEDIT in specifying devices
494 is much greater than BCDEDIT and therefore more can be done with it. The
495 downside is that BCD device elements are much more complicated than what
496 you might think from reading the BCDEDIT documentation.
497
498 In other words, simple things are complicated, and complicated things are
499 possible.
500
501 Anyway, the general syntax of device elements is an optional GUID,
502 followed by a device type, optionally followed by hexadecimal flags in
503 angle brackets, optionally followed by C<=> and a comma-separated list of
504 arguments, some of which can be (and often are) in turn devices again.
505
506 [{GUID}]type[<flags>][=arg,arg...]
507
508 Here are some examples:
509
510 boot
511 {b097d29f-bc00-11e9-8a9a-525400123456}block=file,<boot>,\\EFI"
512 locate=<null>,element,systemroot
513 partition=<null>,harddisk,mbr,47cbc08a,1048576
514 partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
515 block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,68720525312>,0,0,0,\Recovery\b097d29e-bc00-11e9-8a9a-525400123456\Winre.wim
516 block=file,<partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,ee3a393a-f0de-4057-9946-88584245ed48>,\
517 binary=050000000000000048000000000000000000000000000000000000000000000000000000000000000
518
519 I hope you are suitably impressed. I was, too, when I realized decoding
520 these binary blobs is not as easy as I had assumed.
521
522 The optional prefixed GUID seems to refer to a device BCD object, which
523 can be used to specify more device-specific BCD elements (for example
524 C<ramdisksdidevice> and C<ramdisksdpath>).
525
526 The flags after the type are omitted when they are C<0>. The only known
527 flag is C<1>, which seems to indicate that the parent device is invalid. I
528 don't claim to fully understand it, but it seems to indicate that the
529 boot manager has to search the device itself. Why the device is specified
530 in the first place escapes me, but a lot of this device stuff seems to be
531 badly hacked together...
532
533 The types understood and used by PBCDEDIT are as follows (keep in mind
534 that not of all the following is necessarily supported in PBCDEDIT):
535
536 =over
537
538 =item C<binary=>I<hex...>
539
540 This type isn't actually a real BCD element type, but a fallback for those
541 cases where PBCDEDIT can't perfectly decode a device element (except for
542 the leading GUID, which it can always decode). In such cases, it will
543 convert the device into this type with a hexdump of the element data.
544
545 =item C<null>
546
547 This is another special type - sometimes, a device is all zero-filled,
548 which is not valid. This can mark the absence of a device or something
549 PBCDEDIT does not understand, so it decodes it into this special "all
550 zero" type called C<null>.
551
552 It's most commonly found in devices that can use an optional parent
553 device, when no parent device is used.
554
555 =item C<boot>
556
557 Another type without parameters, this refers to the device that was booted
558 from (nowadays typically the EFI system partition).
559
560 =item C<vmbus=>I<interfacetype>,I<interfaceinstance>
561
562 This specifies a VMBUS device with the given interface type and interface
563 instance, both of which are "naked" (no curly braces) GUIDs.
564
565 Made-up example (couldn't find a single example on the web):
566
567 vmbus=c376c1c3-d276-48d2-90a9-c04748072c60,12345678-a234-b234-c234-d2345678abcd
568
569 =item C<partition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
570
571 This designates a specific partition on a block device. I<parent> is an
572 optional parent device on which to search on, and is often C<null>. Note
573 that the angle brackets around I<parent> are part of the syntax.
574
575 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
576 C<file> or C<vhd>, where the first three should be self-explaining,
577 C<file> is usually used to locate a file to be used as a disk image,
578 and C<vhd> is used to treat files as virtual harddisks, i.e. F<vhd> and
579 F<vhdx> files.
580
581 The I<partitiontype> is either C<mbr>, C<gpt> or C<raw>, the latter being
582 used for devices without partitions, such as cdroms, where the "partition"
583 is usually the whole device.
584
585 The I<diskid> identifies the disk or device using a unique signature, and
586 the same is true for the I<partitionid>. How these are interpreted depends
587 on the I<partitiontype>:
588
589 =over
590
591 =item C<mbr>
592
593 The C<diskid> is the 32 bit disk signature stored at offset 0x1b8 in the
594 MBR, interpreted as a 32 bit unsigned little endian integer and written as
595 hex number. That is, the bytes C<01 02 03 04> would become C<04030201>.
596
597 Diskpart (using the C<DETAIL> command) and the C<lsblk> command typically
598 found on GNU/Linux systems (using e.g. C<lsblk -o NAME,PARTUUID>) can
599 display the I<diskid>.
600
601 The I<partitionid> is the byte offset(!) of the partition counting from
602 the beginning of the MBR.
603
604 Example, use the partition on the harddisk with I<diskid> C<47cbc08a>
605 starting at sector C<2048> (= 1048576 / 512).
606
607 partition=<null>,harddisk,mbr,47cbc08a,1048576
608
609 =item C<gpt>
610
611 The I<diskid> is the disk GUID/disk identifier GUID from the partition
612 table (as displayed e.g. by F<gdisk>), and the I<partitionid> is the
613 partition unique GUID (displayed using e.g. the F<gdisk> F<i> command).
614
615 Example: use the partition C<76d39e5f-ad1b-407e-9c05-c81eb83b57dd> on GPT
616 disk C<9742e468-9206-48a0-b4e4-c4e9745a356a>.
617
618 partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
619
620 =item C<raw>
621
622 Instead of I<diskid> and I<partitionid>, this type only accepts a decimal
623 disk number and signifies the whole disk. BCDEDIT cannot display the
624 resulting device, and I am doubtful whether it has a useful effect.
625
626 =back
627
628 =item C<legacypartition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
629
630 This is exactly the same as the C<partition> type, except for a tiny
631 detail: instead of using the partition start offset, this type uses the
632 partition number for MBR disks. Behaviour other partition types should be
633 the same.
634
635 The partition number starts at C<1> and skips unused partition, so if
636 there are two primary partitions and another partition inside the extended
637 partition, the primary partitions are number C<1> and C<2> and the
638 partition inside the extended partition is number C<3>, regardless of any
639 gaps.
640
641 =item C<locate=><I<parent>>,I<locatetype>,I<locatearg>
642
643 This device description will make the bootloader search for a partition
644 with a given path.
645
646 The I<parent> device is the device to search on (angle brackets are
647 still part of the syntax!) If it is C<null>, then C<locate> will
648 search all disks it can find.
649
650 I<locatetype> is either C<element> or C<path>, and merely distinguishes
651 between two different ways to specify the path to search for: C<element>
652 uses an element ID (either as hex or as name) as I<locatearg> and C<path>
653 uses a relative path as I<locatearg>.
654
655 Example: find any partition which has the F<magicfile.xxx> path in the
656 root.
657
658 locate=<null>,path,\magicfile.xxx
659
660 Example: find any partition which has the path specified in the
661 C<systemroot> element (typically F<\Windows>).
662
663 locate=<null>,element,systemroot
664
665 =item C<block=>I<devicetype>,I<args...>
666
667 Last not least, the most complex type, C<block>, which... specifies block
668 devices (which could be inside a F<vhdx> file for example).
669
670 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
671 C<file> or C<vhd> - the same as for C<partition=>.
672
673 The remaining arguments change depending on the I<devicetype>:
674
675 =over
676
677 =item C<block=file>,<I<parent>>,I<path>
678
679 Interprets the I<parent> device (typically a partition) as a
680 filesystem and specifies a file path inside.
681
682 =item C<block=vhd>,<I<parent>>
683
684 Pretty much just changes the interpretation of I<parent>, which is
685 usually a disk image (C<block=file,...)>) to be a F<vhd> or F<vhdx> file.
686
687 =item C<block=ramdisk>,<I<parent>>,I<base>,I<size>,I<offset>,I<path>
688
689 Interprets the I<parent> device as RAM disk, using the (decimal)
690 base address, byte size and byte offset inside a file specified by
691 I<path>. The numbers are usually all C<0> because they can be extracted
692 from the RAM disk image or other parameters.
693
694 This is most commonly used to boot C<wim> images.
695
696 =item C<block=floppy>,I<drivenum>
697
698 Refers to a removable drive identified by a number. BCDEDIT cannot display
699 the resulting device, and it is not clear what effect it will have.
700
701 =item C<block=cdrom>,I<drivenum>
702
703 Pretty much the same as C<floppy> but for CD-ROMs.
704
705 =item anything else
706
707 Probably not yet implemented. Tell me of your needs...
708
709 =back
710
711 =head4 Examples
712
713 This concludes the syntax overview for device elements, but probably
714 leaves many questions open. I can't help with most of them, as I also have
715 many questions, but I can walk you through some actual examples using more
716 complex aspects.
717
718 =item C<< locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path >>
719
720 Just like with C declarations, you best treat device descriptors as
721 instructions to find your device and work your way from the inside out:
722
723 locate=<null>,path,\disk.vhdx
724
725 First, the innermost device descriptor searches all partitions on the
726 system for a file called F<\disk.vhdx>:
727
728 block=file,<see above>,\disk.vhdx
729
730 Next, this takes the device locate has found and finds a file called
731 F<\disk.vhdx> on it. This is the same file locate was using, but that is
732 only because we find the device using the same path as finding the disk
733 image, so this is purely incidental, although quite common.
734
735 Next, this file will be opened as a virtual disk:
736
737 block=vhd,<see above>
738
739 And finally, inside this disk, another C<locate> will look for a partition
740 with a path as specified in the C<path> element, which most likely will be
741 F<\Windows\system32\winload.exe>:
742
743 locate=<see above>,element,path
744
745 As a result, this will boot the first Windows it finds on the first
746 F<disk.vhdx> disk image it can find anywhere.
747
748 =item C<< locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path >>
749
750 Pretty much the same as the previous case, but with a bit of
751 variance. First, look for a specific partition on an MBR-partitioned disk:
752
753 partition=<null>,harddisk,mbr,47cbc08a,242643632128
754
755 Then open the file F<\win10.vhdx> on that partition:
756
757 block=file,<see above>,\win10.vhdx
758
759 Then, again, the file is opened as a virtual disk image:
760
761 block=vhd,<see above>
762
763 And again the windows loader (or whatever is in C<path>) will be searched:
764
765 locate=<see above>,element,path
766
767 =item C<< {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim >>
768
769 This is quite different. First, it starts with a GUID. This GUID belongs
770 to a BCD object of type C<device>, which has additional parameters:
771
772 "{b097d2b2-bc00-11e9-8a9a-525400123456}" : {
773 "type" : "device",
774 "description" : "sdi file for ramdisk",
775 "ramdisksdidevice" : "partition=<null>,harddisk,mbr,47cbc08a,1048576",
776 "ramdisksdipath" : "\boot.sdi"
777 },
778
779 I will not go into many details, but this specifies a (presumably empty)
780 template ramdisk image (F<\boot.sdi>) that is used to initialize the
781 ramdisk. The F<\boot.wim> file is then extracted into it. As you can also
782 see, this F<.sdi> file resides on a different C<partition>.
783
784 Continuing, as always, from the inside out, first this device descriptor
785 finds a specific partition:
786
787 partition=<null>,harddisk,mbr,47cbc08a,242643632128
788
789 And then specifies a C<ramdisk> image on this partition:
790
791 block<1>=ramdisk,<see above>,0,0,0,\boot.wim
792
793 I don't know what the purpose of the C<< <1> >> flag value is, but it
794 seems to be always there on this kind of entry.
795
796 If you have some good examples to add here, feel free to mail me.
797
798
799 =head1 EDITING BCD STORES
800
801 The C<edit> and C<parse> subcommands allow you to read a BCD data store
802 and modify it or extract data from it. This is done by executing a series
803 of "editing instructions" which are explained here.
804
805 =over
806
807 =item C<get> I<object> I<element>
808
809 Reads the BCD element I<element> from the BCD object I<object> and writes
810 it to standard output, followed by a newline. The I<object> can be a GUID
811 or a human-readable alias, or the special string C<{default}>, which will
812 refer to the default BCD object.
813
814 Example: find description of the default BCD object.
815
816 pbcdedit parse BCD get "{default}" description
817
818 =item C<set> I<object> I<element> I<value>
819
820 Similar to C<get>, but sets the element to the given I<value> instead.
821
822 Example: change the bootmgr default too
823 C<{b097d2ad-bc00-11e9-8a9a-525400123456}>:
824
825 pbcdedit edit BCD set "{bootmgr}" default "{b097d2ad-bc00-11e9-8a9a-525400123456}"
826
827 =item C<eval> I<perlcode>
828
829 This takes the next argument, interprets it as Perl code and
830 evaluates it. This allows you to do more complicated modifications or
831 extractions.
832
833 The following variables are predefined for your use:
834
835 =over
836
837 =item C<$PATH>
838
839 The path to the BCD data store, as given to C<edit> or C<parse>.
840
841 =item C<$BCD>
842
843 The decoded BCD data store.
844
845 =item C<$DEFAULT>
846
847 The default BCD object name.
848
849 =back
850
851 The example given for C<get>, above, could be expressed like this with
852 C<eval>:
853
854 pbcdedit edit BCD eval 'say $BCD->{$DEFAULT}{description}'
855
856 The example given for C<set> could be expressed like this:
857
858 pbcdedit edit BCD eval '$BCD->{"{bootmgr}"{default} = "{b097d2ad-bc00-11e9-8a9a-525400123456}"'
859
860 =item C<do> I<path>
861
862 Similar to C<eval>, above, but instead of using the argument as perl code,
863 it loads the perl code from the given file and executes it. This makes it
864 easier to write more complicated or larger programs.
865
866 =back
867
868
869 =head1 SEE ALSO
870
871 For ideas on what you can do with BCD stores in
872 general, and some introductory material, try
873 L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
874
875 For good reference on which BCD objects and
876 elements exist, see Geoff Chappell's pages at
877 L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
878
879 =head1 AUTHOR
880
881 Written by Marc A. Lehmann L<pbcdedit@schmorp.de>.
882
883 =head1 REPORTING BUGS
884
885 Bugs can be reported directly the author at L<pcbedit@schmorp.de>.
886
887 =head1 BUGS AND SHORTCOMINGS
888
889 This should be a module. Of a series of modules, even.
890
891 Registry code should preserve classname and security descriptor data, and
892 whatever else is necessary to read and write any registry hive file.
893
894 I am also not happy with device descriptors being strings rather than a
895 data structure, but strings are probably better for command line usage. In
896 any case, device descriptors could be converted by simply "splitting" at
897 "=" and "," into an array reference, recursively.
898
899 =head1 HOMEPAGE
900
901 Original versions of this program can be found at
902 L<http://software.schmorp.de/pkg/pbcdedit>.
903
904 =head1 COPYRIGHT
905
906 Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
907 see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
908 free to change and redistribute it. There is NO WARRANTY, to the extent
909 permitted by law.
910
911 =cut
912
913 # common sense is optional, but recommended
914 BEGIN { eval { require "common/sense.pm"; } && common::sense->import }
915
916 no warnings 'portable'; # avoid 32 bit integer warnings
917
918 use Encode ();
919 use List::Util ();
920 use IO::Handle ();
921 use Time::HiRes ();
922
923 eval { unpack "Q", pack "Q", 1 }
924 or die "perl with 64 bit integer supported required.\n";
925
926 our $JSON = eval { require JSON::XS; JSON::XS:: }
927 // eval { require JSON::PP; JSON::PP:: }
928 // die "either JSON::XS or JSON::PP must be installed\n";
929
930 our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
931
932 # hack used for debugging
933 sub xxd($$) {
934 open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
935 syswrite $xxd, $_[1];
936 }
937
938 # get some meta info on a file (uid, gid, perms)
939 sub stat_get($) {
940 [(stat shift)[4, 5, 2]]
941 }
942
943 # set stat info on a file
944 sub stat_set($$) {
945 my ($fh_or_path, $stat) = @_;
946
947 return unless $stat;
948 chown $stat->[0], $stat->[1], $fh_or_path;
949 chmod +($stat->[2] & 07777), $fh_or_path;
950 }
951
952 sub file_load($) {
953 my ($path) = @_;
954
955 open my $fh, "<:raw", $path
956 or die "$path: $!\n";
957 my $size = -s $fh;
958 $size = read $fh, my $buf, $size
959 or die "$path: short read\n";
960
961 $buf
962 }
963
964 sub file_save($$;$) {
965 my ($path, $data, $stat) = @_;
966
967 open my $fh, ">:raw", "$path~"
968 or die "$path~: $!\n";
969 print $fh $data
970 or die "$path~: short write\n";
971 stat_set $fh, $stat;
972 $fh->sync;
973 close $fh;
974
975 rename "$path~", $path;
976 }
977
978 # sources and resources used for writing pbcdedit
979 #
980 # registry:
981 # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
982 # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
983 # bcd:
984 # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
985 # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
986 # bcd devices:
987 # reactos' boot/environ/include/bl.h
988 # windows .mof files
989
990 #############################################################################
991 # registry stuff
992
993 # we use a hardcoded securitya descriptor - full access for everyone
994 my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
995 my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
996 my $sacl = "";
997 my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
998 my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
999 # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
1000 1, 0x8004,
1001 20 + (length $sacl) + (length $dacl),
1002 20 + (length $sacl) + (length $dacl) + (length $sid),
1003 0, 20,
1004 $sacl, $dacl, $sid, $sid;
1005 my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
1006
1007 sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
1008
1009 sub KEY_HIVE_ENTRY() { 0x0004 }
1010 sub KEY_NO_DELETE () { 0x0008 }
1011 sub KEY_COMP_NAME () { 0x0020 }
1012
1013 sub VALUE_COMP_NAME() { 0x0001 }
1014
1015 my @regf_typename = qw(
1016 none sz expand_sz binary dword dword_be link multi_sz
1017 resource_list full_resource_descriptor resource_requirements_list
1018 qword qword_be
1019 );
1020
1021 my %regf_dec_type = (
1022 sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1023 expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1024 link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1025 multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
1026 dword => sub { unpack "L<", shift },
1027 dword_be => sub { unpack "L>", shift },
1028 qword => sub { unpack "Q<", shift },
1029 qword_be => sub { unpack "Q>", shift },
1030 );
1031
1032 my %regf_enc_type = (
1033 sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1034 expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1035 link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1036 multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
1037 dword => sub { pack "L<", shift },
1038 dword_be => sub { pack "L>", shift },
1039 qword => sub { pack "Q<", shift },
1040 qword_be => sub { pack "Q>", shift },
1041 );
1042
1043 # decode a registry hive
1044 sub regf_decode($) {
1045 my ($hive) = @_;
1046
1047 "regf" eq substr $hive, 0, 4
1048 or die "not a registry hive\n";
1049
1050 my ($major, $minor) = unpack "\@20 L< L<", $hive;
1051
1052 $major == 1
1053 or die "registry major version is not 1, but $major\n";
1054
1055 $minor >= 2 && $minor <= 6
1056 or die "registry minor version is $minor, only 2 .. 6 are supported\n";
1057
1058 my $bins = substr $hive, 4096;
1059
1060 my $decode_key = sub {
1061 my ($ofs) = @_;
1062
1063 my @res;
1064
1065 my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
1066
1067 $sze < 0
1068 or die "key node points to unallocated cell\n";
1069
1070 $sig eq "nk"
1071 or die "expected key node at $ofs, got '$sig'\n";
1072
1073 my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
1074
1075 my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
1076
1077 # classnames, security descriptors
1078 #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
1079 #if ($cofs != NO_OFS && $clen) {
1080 # #warn "cofs $cofs+$clen\n";
1081 # xxd substr $bins, $cofs, 16;
1082 #}
1083
1084 $kname = Encode::decode "UTF-16LE", $kname
1085 unless $flags & KEY_COMP_NAME;
1086
1087 if ($vnum && $vofs != NO_OFS) {
1088 for ($vofs += 4; $vnum--; $vofs += 4) {
1089 my $kofs = unpack "\@$vofs L<", $bins;
1090
1091 my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
1092
1093 $sig eq "vk"
1094 or die "key values list contains invalid node (expected vk got '$sig')\n";
1095
1096 my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
1097
1098 my $name = substr $bins, $kofs + 24, $nsze;
1099
1100 $name = Encode::decode "UTF-16LE", $name
1101 unless $flags & VALUE_COMP_NAME;
1102
1103 my $data;
1104 if ($dsze & 0x80000000) {
1105 $data = substr $bins, $kofs + 12, $dsze & 0x7;
1106 } elsif ($dsze > 16344 && $minor > 3) { # big data
1107 my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
1108
1109 for ($bofs += 4; $bnum--; $bofs += 4) {
1110 my $dofs = unpack "\@$bofs L<", $bins;
1111 my $dsze = unpack "\@$dofs l<", $bins;
1112 $data .= substr $bins, $dofs + 4, -$dsze - 4;
1113 }
1114 $data = substr $data, 0, $dsze; # cells might be longer than data
1115 } else {
1116 $data = substr $bins, $dofs + 4, $dsze;
1117 }
1118
1119 $type = $regf_typename[$type] if $type < @regf_typename;
1120
1121 $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
1122 ->($data);
1123
1124 $res[0]{$name} = [$type, $data];
1125 }
1126 }
1127
1128 if ($sofs != NO_OFS) {
1129 my $decode_key = __SUB__;
1130
1131 my $decode_subkeylist = sub {
1132 my ($sofs) = @_;
1133
1134 my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
1135
1136 if ($sig eq "ri") { # index root
1137 for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
1138 __SUB__->(unpack "\@$lofs L<", $bins);
1139 }
1140 } else {
1141 my $inc;
1142
1143 if ($sig eq "li") { # subkey list
1144 $inc = 4;
1145 } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
1146 $inc = 8;
1147 } else {
1148 die "expected subkey list at $sofs, found '$sig'\n";
1149 }
1150
1151 for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
1152 my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
1153 $res[1]{$name} = $data;
1154 }
1155 }
1156 };
1157
1158 $decode_subkeylist->($sofs);
1159 }
1160
1161 ($kname, \@res);
1162 };
1163
1164 my ($rootcell) = unpack "\@36 L<", $hive;
1165
1166 my ($rname, $root) = $decode_key->($rootcell);
1167
1168 [$rname, $root]
1169 }
1170
1171 # return a binary windows fILETIME struct
1172 sub filetime_now {
1173 my ($s, $ms) = Time::HiRes::gettimeofday;
1174
1175 pack "Q<", $s = ($s * 1_000_000 + $ms) * 10 + 116_444_736_000_000_000
1176 }
1177
1178 # encode a registry hive
1179 sub regf_encode($) {
1180 my ($hive) = @_;
1181
1182 my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
1183
1184 # the filetime is apparently used to verify log file validity,
1185 # so by generating a new timestamp the log files *should* automatically
1186 # become invalidated and windows would "self-heal" them.
1187 # (update: has been verified by reverse engineering)
1188 # possibly the fact that the two sequence numbes match might also
1189 # make windows think that the hive is not dirty and ignore logs.
1190 # (update: has been verified by reverse engineering)
1191
1192 my $now = filetime_now;
1193
1194 # we only create a single hbin
1195 my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
1196
1197 # append cell to $bind, return offset
1198 my $cell = sub {
1199 my ($cell) = @_;
1200
1201 my $res = length $bins;
1202
1203 $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
1204
1205 $bins .= pack "l<", -(4 + length $cell);
1206 $bins .= $cell;
1207
1208 $res
1209 };
1210
1211 my $sdofs = $cell->($sk); # add a dummy security descriptor
1212 my $sdref = 0; # refcount
1213 substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
1214 substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
1215
1216 my $encode_key = sub {
1217 my ($kname, $kdata, $flags) = @_;
1218 my ($values, $subkeys) = @$kdata;
1219
1220 if ($kname =~ /[^\x00-\xff]/) {
1221 $kname = Encode::encode "UTF-16LE", $kname;
1222 } else {
1223 $flags |= KEY_COMP_NAME;
1224 }
1225
1226 # encode subkeys
1227
1228 my @snames =
1229 map $_->[1],
1230 sort { $a->[0] cmp $b->[0] }
1231 map [(uc $_), $_],
1232 keys %$subkeys;
1233
1234 # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
1235 my $maxsname = 4 * List::Util::max map length, @snames;
1236
1237 my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
1238
1239 # encode values
1240 my $maxvname = 4 * List::Util::max map length, keys %$values;
1241 my @vofs;
1242 my $maxdsze = 0;
1243
1244 while (my ($vname, $v) = each %$values) {
1245 my $flags = 0;
1246
1247 if ($vname =~ /[^\x00-\xff]/) {
1248 $vname = Encode::encode "UTF-16LE", $kname;
1249 } else {
1250 $flags |= VALUE_COMP_NAME;
1251 }
1252
1253 my ($type, $data) = @$v;
1254
1255 $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
1256
1257 my $dsze;
1258 my $dofs;
1259
1260 if (length $data <= 4) {
1261 $dsze = 0x80000000 | length $data;
1262 $dofs = unpack "L<", pack "a4", $data;
1263 } else {
1264 $dsze = length $data;
1265 $dofs = $cell->($data);
1266 }
1267
1268 $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
1269
1270 push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
1271 vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
1272
1273 $maxdsze = $dsze if $maxdsze < $dsze;
1274 }
1275
1276 # encode key
1277
1278 my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
1279 my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1280
1281 my $kdata = pack "
1282 a2 S< a8 x4 x4
1283 L< L< L< L< L< L<
1284 L< L< L< L< L< L<
1285 x4 S< S< a*
1286 ",
1287 nk => $flags, $now,
1288 (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1289 $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1290 length $kname, 0, $kname;
1291 ++$sdref;
1292
1293 my $res = $cell->($kdata);
1294
1295 substr $bins, $_ + 16, 4, pack "L<", $res
1296 for @sofs;
1297
1298 $res
1299 };
1300
1301 my ($rname, $root) = @$hive;
1302
1303 my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1304
1305 if (my $pad = -(length $bins) & 4095) {
1306 $pad -= 4;
1307 $bins .= pack "l< x$pad", $pad + 4;
1308 }
1309
1310 substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1311 substr $bins, 8, 4, pack "L<", length $bins;
1312
1313 my $base = pack "
1314 a4 L< L< a8 L< L< L< L<
1315 L< L< L<
1316 a64
1317 x396
1318 ",
1319 regf => 1974, 1974, $now, 1, 3, 0, 1,
1320 $rofs, length $bins, 1,
1321 (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1322
1323 my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1324 $chksum = 0xfffffffe if $chksum == 0xffffffff;
1325 $chksum = 1 if $chksum == 0;
1326
1327 $base .= pack "L<", $chksum;
1328
1329 $base = pack "a* \@4095 x1", $base;
1330
1331 $base . $bins
1332 }
1333
1334 # load and parse registry from file
1335 sub regf_load($) {
1336 my ($path) = @_;
1337
1338 regf_decode file_load $path
1339 }
1340
1341 # encode and save registry to file
1342 sub regf_save($$;$) {
1343 my ($path, $hive, $stat) = @_;
1344
1345 $hive = regf_encode $hive;
1346
1347 file_save $path, $hive, $stat;
1348 }
1349
1350 #############################################################################
1351 # bcd stuff
1352
1353 # human-readable alises for GUID object identifiers
1354 our %bcd_objects = (
1355 '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1356 '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1357 '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1358 '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1359 '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1360 '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1361 '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1362 '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1363 '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1364 '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1365 '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1366 '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1367 '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1368 '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1369 '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1370 '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1371 '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1372 '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1373 '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1374 );
1375
1376 # default types
1377 our %bcd_object_types = (
1378 '{fwbootmgr}' => 0x10100001,
1379 '{bootmgr}' => 0x10100002,
1380 '{memdiag}' => 0x10200005,
1381 '{ntldr}' => 0x10300006,
1382 '{badmemory}' => 0x20100000,
1383 '{dbgsettings}' => 0x20100000,
1384 '{emssettings}' => 0x20100000,
1385 '{globalsettings}' => 0x20100000,
1386 '{bootloadersettings}' => 0x20200003,
1387 '{hypervisorsettings}' => 0x20200003,
1388 '{kerneldbgsettings}' => 0x20200003,
1389 '{resumeloadersettings}' => 0x20200004,
1390 '{ramdiskoptions}' => 0x30000000,
1391 );
1392
1393 # object types
1394 our %bcd_types = (
1395 0x10100001 => 'application::fwbootmgr',
1396 0x10100002 => 'application::bootmgr',
1397 0x10200003 => 'application::osloader',
1398 0x10200004 => 'application::resume',
1399 0x10100005 => 'application::memdiag',
1400 0x10100006 => 'application::ntldr',
1401 0x10100007 => 'application::setupldr',
1402 0x10400008 => 'application::bootsector',
1403 0x10400009 => 'application::startup',
1404 0x1020000a => 'application::bootapp',
1405 0x20100000 => 'settings',
1406 0x20200001 => 'inherit::fwbootmgr',
1407 0x20200002 => 'inherit::bootmgr',
1408 0x20200003 => 'inherit::osloader',
1409 0x20200004 => 'inherit::resume',
1410 0x20200005 => 'inherit::memdiag',
1411 0x20200006 => 'inherit::ntldr',
1412 0x20200007 => 'inherit::setupldr',
1413 0x20200008 => 'inherit::bootsector',
1414 0x20200009 => 'inherit::startup',
1415 0x20300000 => 'inherit::device',
1416 0x30000000 => 'device',
1417 );
1418
1419 our %rbcd_objects = reverse %bcd_objects;
1420
1421 our $RE_GUID = qr<([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})>i;
1422
1423 sub dec_guid($) {
1424 my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1425 sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1426 }
1427
1428 sub enc_guid($) {
1429 $_[0] =~ /^$RE_GUID\z/o
1430 or return;
1431
1432 pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1433 }
1434
1435 # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1436 sub dec_wguid($) {
1437 my $guid = "{" . (dec_guid shift) . "}";
1438
1439 $bcd_objects{$guid} // $guid
1440 }
1441
1442 sub enc_wguid($) {
1443 my ($guid) = @_;
1444
1445 if (my $alias = $rbcd_objects{$guid}) {
1446 $guid = $alias;
1447 }
1448
1449 $guid =~ /^\{($RE_GUID)\}\z/o
1450 or return;
1451
1452 enc_guid $1
1453 }
1454
1455 sub BCDE_CLASS () { 0xf0000000 }
1456 sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1457 sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1458 sub BCDE_CLASS_DEVICE () { 0x30000000 }
1459 sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1460
1461 sub BCDE_FORMAT () { 0x0f000000 }
1462 sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1463 sub BCDE_FORMAT_STRING () { 0x02000000 }
1464 sub BCDE_FORMAT_GUID () { 0x03000000 }
1465 sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1466 sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1467 sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1468 sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1469
1470 sub enc_integer($) {
1471 my $value = shift;
1472 $value = oct $value if $value =~ /^0[bBxX]/;
1473 unpack "H*", pack "Q<", $value
1474 }
1475
1476 sub enc_device($$);
1477 sub dec_device($$);
1478
1479 our %bcde_dec = (
1480 BCDE_FORMAT_DEVICE , \&dec_device,
1481 # # for round-trip verification
1482 # BCDE_FORMAT_DEVICE , sub {
1483 # my $dev = dec_device $_[0];
1484 # $_[0] eq enc_device $dev
1485 # or die "bcd device decoding does not round trip for $_[0]\n";
1486 # $dev
1487 # },
1488 BCDE_FORMAT_STRING , sub { shift },
1489 BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1490 BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1491 BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1492 BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1493 BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this cna be 4 bytes
1494 );
1495
1496 our %bcde_enc = (
1497 BCDE_FORMAT_DEVICE , sub { binary => enc_device $_[0], $_[1] },
1498 BCDE_FORMAT_STRING , sub { sz => shift },
1499 BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1500 BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1501 BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1502 BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1503 BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1504 );
1505
1506 # BCD Elements
1507 our %bcde_byclass = (
1508 any => {
1509 0x11000001 => 'device',
1510 0x12000002 => 'path',
1511 0x12000004 => 'description',
1512 0x12000005 => 'locale',
1513 0x14000006 => 'inherit',
1514 0x15000007 => 'truncatememory',
1515 0x14000008 => 'recoverysequence',
1516 0x16000009 => 'recoveryenabled',
1517 0x1700000a => 'badmemorylist',
1518 0x1600000b => 'badmemoryaccess',
1519 0x1500000c => 'firstmegabytepolicy',
1520 0x1500000d => 'relocatephysical',
1521 0x1500000e => 'avoidlowmemory',
1522 0x1600000f => 'traditionalkseg',
1523 0x16000010 => 'bootdebug',
1524 0x15000011 => 'debugtype',
1525 0x15000012 => 'debugaddress',
1526 0x15000013 => 'debugport',
1527 0x15000014 => 'baudrate',
1528 0x15000015 => 'channel',
1529 0x12000016 => 'targetname',
1530 0x16000017 => 'noumex',
1531 0x15000018 => 'debugstart',
1532 0x12000019 => 'busparams',
1533 0x1500001a => 'hostip',
1534 0x1500001b => 'port',
1535 0x1600001c => 'dhcp',
1536 0x1200001d => 'key',
1537 0x1600001e => 'vm',
1538 0x16000020 => 'bootems',
1539 0x15000022 => 'emsport',
1540 0x15000023 => 'emsbaudrate',
1541 0x12000030 => 'loadoptions',
1542 0x16000040 => 'advancedoptions',
1543 0x16000041 => 'optionsedit',
1544 0x15000042 => 'keyringaddress',
1545 0x11000043 => 'bootstatdevice',
1546 0x12000044 => 'bootstatfilepath',
1547 0x16000045 => 'preservebootstat',
1548 0x16000046 => 'graphicsmodedisabled',
1549 0x15000047 => 'configaccesspolicy',
1550 0x16000048 => 'nointegritychecks',
1551 0x16000049 => 'testsigning',
1552 0x1200004a => 'fontpath',
1553 0x1500004b => 'integrityservices',
1554 0x1500004c => 'volumebandid',
1555 0x16000050 => 'extendedinput',
1556 0x15000051 => 'initialconsoleinput',
1557 0x15000052 => 'graphicsresolution',
1558 0x16000053 => 'restartonfailure',
1559 0x16000054 => 'highestmode',
1560 0x16000060 => 'isolatedcontext',
1561 0x15000065 => 'displaymessage',
1562 0x15000066 => 'displaymessageoverride',
1563 0x16000068 => 'nobootuxtext',
1564 0x16000069 => 'nobootuxprogress',
1565 0x1600006a => 'nobootuxfade',
1566 0x1600006b => 'bootuxreservepooldebug',
1567 0x1600006c => 'bootuxdisabled',
1568 0x1500006d => 'bootuxfadeframes',
1569 0x1600006e => 'bootuxdumpstats',
1570 0x1600006f => 'bootuxshowstats',
1571 0x16000071 => 'multibootsystem',
1572 0x16000072 => 'nokeyboard',
1573 0x15000073 => 'aliaswindowskey',
1574 0x16000074 => 'bootshutdowndisabled',
1575 0x15000075 => 'performancefrequency',
1576 0x15000076 => 'securebootrawpolicy',
1577 0x17000077 => 'allowedinmemorysettings',
1578 0x15000079 => 'bootuxtransitiontime',
1579 0x1600007a => 'mobilegraphics',
1580 0x1600007b => 'forcefipscrypto',
1581 0x1500007d => 'booterrorux',
1582 0x1600007e => 'flightsigning',
1583 0x1500007f => 'measuredbootlogformat',
1584 0x15000080 => 'displayrotation',
1585 0x15000081 => 'logcontrol',
1586 0x16000082 => 'nofirmwaresync',
1587 0x11000084 => 'windowssyspart',
1588 0x16000087 => 'numlock',
1589 0x26000202 => 'skipffumode',
1590 0x26000203 => 'forceffumode',
1591 0x25000510 => 'chargethreshold',
1592 0x26000512 => 'offmodecharging',
1593 0x25000aaa => 'bootflow',
1594 0x45000001 => 'devicetype',
1595 0x42000002 => 'applicationrelativepath',
1596 0x42000003 => 'ramdiskdevicerelativepath',
1597 0x46000004 => 'omitosloaderelements',
1598 0x47000006 => 'elementstomigrate',
1599 0x46000010 => 'recoveryos',
1600 },
1601 bootapp => {
1602 0x26000145 => 'enablebootdebugpolicy',
1603 0x26000146 => 'enablebootorderclean',
1604 0x26000147 => 'enabledeviceid',
1605 0x26000148 => 'enableffuloader',
1606 0x26000149 => 'enableiuloader',
1607 0x2600014a => 'enablemassstorage',
1608 0x2600014b => 'enablerpmbprovisioning',
1609 0x2600014c => 'enablesecurebootpolicy',
1610 0x2600014d => 'enablestartcharge',
1611 0x2600014e => 'enableresettpm',
1612 },
1613 bootmgr => {
1614 0x24000001 => 'displayorder',
1615 0x24000002 => 'bootsequence',
1616 0x23000003 => 'default',
1617 0x25000004 => 'timeout',
1618 0x26000005 => 'resume',
1619 0x23000006 => 'resumeobject',
1620 0x24000007 => 'startupsequence',
1621 0x24000010 => 'toolsdisplayorder',
1622 0x26000020 => 'displaybootmenu',
1623 0x26000021 => 'noerrordisplay',
1624 0x21000022 => 'bcddevice',
1625 0x22000023 => 'bcdfilepath',
1626 0x26000024 => 'hormenabled',
1627 0x26000025 => 'hiberboot',
1628 0x22000026 => 'passwordoverride',
1629 0x22000027 => 'pinpassphraseoverride',
1630 0x26000028 => 'processcustomactionsfirst',
1631 0x27000030 => 'customactions',
1632 0x26000031 => 'persistbootsequence',
1633 0x26000032 => 'skipstartupsequence',
1634 0x22000040 => 'fverecoveryurl',
1635 0x22000041 => 'fverecoverymessage',
1636 },
1637 device => {
1638 0x35000001 => 'ramdiskimageoffset',
1639 0x35000002 => 'ramdisktftpclientport',
1640 0x31000003 => 'ramdisksdidevice',
1641 0x32000004 => 'ramdisksdipath',
1642 0x35000005 => 'ramdiskimagelength',
1643 0x36000006 => 'exportascd',
1644 0x35000007 => 'ramdisktftpblocksize',
1645 0x35000008 => 'ramdisktftpwindowsize',
1646 0x36000009 => 'ramdiskmcenabled',
1647 0x3600000a => 'ramdiskmctftpfallback',
1648 0x3600000b => 'ramdisktftpvarwindow',
1649 },
1650 memdiag => {
1651 0x25000001 => 'passcount',
1652 0x25000002 => 'testmix',
1653 0x25000003 => 'failurecount',
1654 0x26000003 => 'cacheenable',
1655 0x25000004 => 'testtofail',
1656 0x26000004 => 'failuresenabled',
1657 0x25000005 => 'stridefailcount',
1658 0x26000005 => 'cacheenable',
1659 0x25000006 => 'invcfailcount',
1660 0x25000007 => 'matsfailcount',
1661 0x25000008 => 'randfailcount',
1662 0x25000009 => 'chckrfailcount',
1663 },
1664 ntldr => {
1665 0x22000001 => 'bpbstring',
1666 },
1667 osloader => {
1668 0x21000001 => 'osdevice',
1669 0x22000002 => 'systemroot',
1670 0x23000003 => 'resumeobject',
1671 0x26000004 => 'stampdisks',
1672 0x26000010 => 'detecthal',
1673 0x22000011 => 'kernel',
1674 0x22000012 => 'hal',
1675 0x22000013 => 'dbgtransport',
1676 0x25000020 => 'nx',
1677 0x25000021 => 'pae',
1678 0x26000022 => 'winpe',
1679 0x26000024 => 'nocrashautoreboot',
1680 0x26000025 => 'lastknowngood',
1681 0x26000026 => 'oslnointegritychecks',
1682 0x26000027 => 'osltestsigning',
1683 0x26000030 => 'nolowmem',
1684 0x25000031 => 'removememory',
1685 0x25000032 => 'increaseuserva',
1686 0x25000033 => 'perfmem',
1687 0x26000040 => 'vga',
1688 0x26000041 => 'quietboot',
1689 0x26000042 => 'novesa',
1690 0x26000043 => 'novga',
1691 0x25000050 => 'clustermodeaddressing',
1692 0x26000051 => 'usephysicaldestination',
1693 0x25000052 => 'restrictapiccluster',
1694 0x22000053 => 'evstore',
1695 0x26000054 => 'uselegacyapicmode',
1696 0x26000060 => 'onecpu',
1697 0x25000061 => 'numproc',
1698 0x26000062 => 'maxproc',
1699 0x25000063 => 'configflags',
1700 0x26000064 => 'maxgroup',
1701 0x26000065 => 'groupaware',
1702 0x25000066 => 'groupsize',
1703 0x26000070 => 'usefirmwarepcisettings',
1704 0x25000071 => 'msi',
1705 0x25000072 => 'pciexpress',
1706 0x25000080 => 'safeboot',
1707 0x26000081 => 'safebootalternateshell',
1708 0x26000090 => 'bootlog',
1709 0x26000091 => 'sos',
1710 0x260000a0 => 'debug',
1711 0x260000a1 => 'halbreakpoint',
1712 0x260000a2 => 'useplatformclock',
1713 0x260000a3 => 'forcelegacyplatform',
1714 0x260000a4 => 'useplatformtick',
1715 0x260000a5 => 'disabledynamictick',
1716 0x250000a6 => 'tscsyncpolicy',
1717 0x260000b0 => 'ems',
1718 0x250000c0 => 'forcefailure',
1719 0x250000c1 => 'driverloadfailurepolicy',
1720 0x250000c2 => 'bootmenupolicy',
1721 0x260000c3 => 'onetimeadvancedoptions',
1722 0x260000c4 => 'onetimeoptionsedit',
1723 0x250000e0 => 'bootstatuspolicy',
1724 0x260000e1 => 'disableelamdrivers',
1725 0x250000f0 => 'hypervisorlaunchtype',
1726 0x220000f1 => 'hypervisorpath',
1727 0x260000f2 => 'hypervisordebug',
1728 0x250000f3 => 'hypervisordebugtype',
1729 0x250000f4 => 'hypervisordebugport',
1730 0x250000f5 => 'hypervisorbaudrate',
1731 0x250000f6 => 'hypervisorchannel',
1732 0x250000f7 => 'bootux',
1733 0x260000f8 => 'hypervisordisableslat',
1734 0x220000f9 => 'hypervisorbusparams',
1735 0x250000fa => 'hypervisornumproc',
1736 0x250000fb => 'hypervisorrootprocpernode',
1737 0x260000fc => 'hypervisoruselargevtlb',
1738 0x250000fd => 'hypervisorhostip',
1739 0x250000fe => 'hypervisorhostport',
1740 0x250000ff => 'hypervisordebugpages',
1741 0x25000100 => 'tpmbootentropy',
1742 0x22000110 => 'hypervisorusekey',
1743 0x22000112 => 'hypervisorproductskutype',
1744 0x25000113 => 'hypervisorrootproc',
1745 0x26000114 => 'hypervisordhcp',
1746 0x25000115 => 'hypervisoriommupolicy',
1747 0x26000116 => 'hypervisorusevapic',
1748 0x22000117 => 'hypervisorloadoptions',
1749 0x25000118 => 'hypervisormsrfilterpolicy',
1750 0x25000119 => 'hypervisormmionxpolicy',
1751 0x2500011a => 'hypervisorschedulertype',
1752 0x25000120 => 'xsavepolicy',
1753 0x25000121 => 'xsaveaddfeature0',
1754 0x25000122 => 'xsaveaddfeature1',
1755 0x25000123 => 'xsaveaddfeature2',
1756 0x25000124 => 'xsaveaddfeature3',
1757 0x25000125 => 'xsaveaddfeature4',
1758 0x25000126 => 'xsaveaddfeature5',
1759 0x25000127 => 'xsaveaddfeature6',
1760 0x25000128 => 'xsaveaddfeature7',
1761 0x25000129 => 'xsaveremovefeature',
1762 0x2500012a => 'xsaveprocessorsmask',
1763 0x2500012b => 'xsavedisable',
1764 0x2500012c => 'kerneldebugtype',
1765 0x2200012d => 'kernelbusparams',
1766 0x2500012e => 'kerneldebugaddress',
1767 0x2500012f => 'kerneldebugport',
1768 0x25000130 => 'claimedtpmcounter',
1769 0x25000131 => 'kernelchannel',
1770 0x22000132 => 'kerneltargetname',
1771 0x25000133 => 'kernelhostip',
1772 0x25000134 => 'kernelport',
1773 0x26000135 => 'kerneldhcp',
1774 0x22000136 => 'kernelkey',
1775 0x22000137 => 'imchivename',
1776 0x21000138 => 'imcdevice',
1777 0x25000139 => 'kernelbaudrate',
1778 0x22000140 => 'mfgmode',
1779 0x26000141 => 'event',
1780 0x25000142 => 'vsmlaunchtype',
1781 0x25000144 => 'hypervisorenforcedcodeintegrity',
1782 0x21000150 => 'systemdatadevice',
1783 0x21000151 => 'osarcdevice',
1784 0x21000153 => 'osdatadevice',
1785 0x21000154 => 'bspdevice',
1786 0x21000155 => 'bspfilepath',
1787 },
1788 resume => {
1789 0x21000001 => 'filedevice',
1790 0x22000002 => 'filepath',
1791 0x26000003 => 'customsettings',
1792 0x26000004 => 'pae',
1793 0x21000005 => 'associatedosdevice',
1794 0x26000006 => 'debugoptionenabled',
1795 0x25000007 => 'bootux',
1796 0x25000008 => 'bootmenupolicy',
1797 0x26000024 => 'hormenabled',
1798 },
1799 startup => {
1800 0x26000001 => 'pxesoftreboot',
1801 0x22000002 => 'applicationname',
1802 },
1803 );
1804
1805 # mask, value => class
1806 our @bcde_typeclass = (
1807 [0x00000000, 0x00000000, 'any'],
1808 [0xf00fffff, 0x1000000a, 'bootapp'],
1809 [0xf0ffffff, 0x2020000a, 'bootapp'],
1810 [0xf00fffff, 0x10000001, 'bootmgr'],
1811 [0xf00fffff, 0x10000002, 'bootmgr'],
1812 [0xf0ffffff, 0x20200001, 'bootmgr'],
1813 [0xf0ffffff, 0x20200002, 'bootmgr'],
1814 [0xf0f00000, 0x20300000, 'device'],
1815 [0xf0000000, 0x30000000, 'device'],
1816 [0xf00fffff, 0x10000005, 'memdiag'],
1817 [0xf0ffffff, 0x20200005, 'memdiag'],
1818 [0xf00fffff, 0x10000006, 'ntldr'],
1819 [0xf00fffff, 0x10000007, 'ntldr'],
1820 [0xf0ffffff, 0x20200006, 'ntldr'],
1821 [0xf0ffffff, 0x20200007, 'ntldr'],
1822 [0xf00fffff, 0x10000003, 'osloader'],
1823 [0xf0ffffff, 0x20200003, 'osloader'],
1824 [0xf00fffff, 0x10000004, 'resume'],
1825 [0xf0ffffff, 0x20200004, 'resume'],
1826 [0xf00fffff, 0x10000009, 'startup'],
1827 [0xf0ffffff, 0x20200009, 'startup'],
1828 );
1829
1830 our %rbcde_byclass;
1831
1832 while (my ($k, $v) = each %bcde_byclass) {
1833 $rbcde_byclass{$k} = { reverse %$v };
1834 }
1835
1836 # decodes (numerical elem, type) to name
1837 sub dec_bcde_id($$) {
1838 for my $class (@bcde_typeclass) {
1839 if (($_[1] & $class->[0]) == $class->[1]) {
1840 if (my $id = $bcde_byclass{$class->[2]}{$_[0]}) {
1841 return $id;
1842 }
1843 }
1844 }
1845
1846 sprintf "custom:%08x", $_[0]
1847 }
1848
1849 # encodes (elem as name, type)
1850 sub enc_bcde_id($$) {
1851 $_[0] =~ /^custom:(?:0x)?([0-9a-fA-F]{8}$)/
1852 and return hex $1;
1853
1854 for my $class (@bcde_typeclass) {
1855 if (($_[1] & $class->[0]) == $class->[1]) {
1856 if (my $value = $rbcde_byclass{$class->[2]}{$_[0]}) {
1857 return $value;
1858 }
1859 }
1860 }
1861
1862 undef
1863 }
1864
1865 # decode/encode bcd device element - the horror, no documentaion
1866 # whatsoever, supercomplex, superinconsistent.
1867
1868 our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1869 our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1870 our @part_type = qw(gpt mbr raw);
1871
1872 our $NULL_DEVICE = "\x00" x 16;
1873
1874 # biggest bitch to decode, ever
1875 # this decoded a device portion after the GUID
1876 sub dec_device_($$);
1877 sub dec_device_($$) {
1878 my ($device, $type) = @_;
1879
1880 my $res;
1881
1882 my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1883
1884 $pad == 0
1885 or die "non-zero reserved field in device descriptor\n";
1886
1887 if ($length == 0 && $type == 0 && $flags == 0) {
1888 return ("null", $device);
1889 }
1890
1891 $length >= 16
1892 or die "device element size too small ($length)\n";
1893
1894 $type = $dev_type[$type] // die "$type: unknown device type\n";
1895 #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1896
1897 $res .= $type;
1898 $res .= sprintf "<%x>", $flags if $flags;
1899
1900 my $tail = substr $device, $length - 4 * 4, 1e9, "";
1901
1902 $length == 4 * 4 + length $device
1903 or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1904
1905 my $dec_path = sub {
1906 my ($path, $error) = @_;
1907
1908 $path =~ /^((?:..)*)\x00\x00\z/s
1909 or die "$error\n";
1910
1911 $path = Encode::decode "UTF-16LE", $1;
1912
1913 $path
1914 };
1915
1916 if ($type eq "partition" or $type eq "legacypartition") {
1917 my $partdata = substr $device, 0, 16, "";
1918 my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1919
1920 $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1921 $parttype = $part_type[$parttype] // die "unknown partition type\n";
1922
1923 my $diskid = substr $device, 0, 16, "";
1924
1925 $diskid = $parttype eq "gpt"
1926 ? dec_guid substr $diskid, 0, 16
1927 : sprintf "%08x", unpack "V", $diskid;
1928
1929 my $partid = $parttype eq "gpt" ? dec_guid $partdata
1930 : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1931 : unpack "L<", $partdata; # partition number, one-based
1932
1933 (my $parent, $device) = dec_device_ $device, $type;
1934
1935 $res .= "=";
1936 $res .= "<$parent>";
1937 $res .= ",$blocktype,$parttype,$diskid,$partid";
1938
1939 # PartitionType (gpt, mbr, raw)
1940 # guid | partsig | disknumber
1941
1942 } elsif ($type eq "boot") {
1943 $device =~ s/^\x00{56}\z//
1944 or die "boot device type with extra data not supported\n";
1945
1946 } elsif ($type eq "block") {
1947 my $blocktype = unpack "V", substr $device, 0, 4, "";
1948
1949 $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1950
1951 # decode a "file path" structure
1952 my $dec_file = sub {
1953 my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1954
1955 my $path = substr $device, 0, $flen - 12, "";
1956
1957 $fver == 1
1958 or die "unsupported file descriptor version '$fver'\n";
1959
1960 $ftype == 5
1961 or die "unsupported file descriptor path type '$type'\n";
1962
1963 (my $parent, $path) = dec_device_ $path, $type;
1964
1965 $path = $dec_path->($path, "file device without path");
1966
1967 ($parent, $path)
1968 };
1969
1970 if ($blocktype eq "file") {
1971 my ($parent, $path) = $dec_file->();
1972
1973 $res .= "=file,<$parent>,$path";
1974
1975 } elsif ($blocktype eq "vhd") {
1976 $device =~ s/^\x00{20}//s
1977 or die "virtualdisk has non-zero fields I don't understand\n";
1978
1979 (my $parent, $device) = dec_device_ $device, $type;
1980
1981 $res .= "=vhd,<$parent>";
1982
1983 } elsif ($blocktype eq "ramdisk") {
1984 my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1985 my ($subdev, $path) = $dec_file->();
1986
1987 $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1988
1989 } else {
1990 die "unsupported block type '$blocktype'\n";
1991 }
1992
1993 } elsif ($type eq "locate") {
1994 # mode, bcde_id, unknown, string
1995 # we assume locate has _either_ an element id _or_ a path, but not both
1996
1997 my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
1998
1999 if ($parent) {
2000 # not sure why this is an offset - it must come after the path
2001 $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
2002 ($parent, my $tail) = dec_device_ $parent, $type;
2003 0 == length $tail
2004 or die "trailing data after locate device parent\n";
2005 } else {
2006 $parent = "null";
2007 }
2008
2009 my $path = $device; $device = "";
2010 $path = $dec_path->($path, "device locate mode without path");
2011
2012 $res .= "=<$parent>,";
2013
2014 if ($mode == 0) { # "Element"
2015 !length $path
2016 or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
2017
2018 $elem = dec_bcde_id $elem, $type;
2019 $res .= "element,$elem";
2020
2021 } elsif ($mode == 1) { # "String"
2022 !$elem
2023 or die "device locate mode 1 having non-zero element\n";
2024
2025 $res .= "path,$path";
2026 } else {
2027 # mode 2 maybe called "ElementChild" with element and parent device? example needed
2028 die "device locate mode '$mode' not supported\n";
2029 }
2030
2031 } elsif ($type eq "vmbus") {
2032 my $type = dec_guid substr $device, 0, 16, "";
2033 my $instance = dec_guid substr $device, 0, 16, "";
2034
2035 $device =~ s/^\x00{24}\z//
2036 or die "vmbus has non-zero fields I don't understand\n";
2037
2038 $res .= "=$type,$instance";
2039
2040 } else {
2041 die "unsupported device type '$type'\n";
2042 }
2043
2044 warn "unexpected trailing device data($res), " . unpack "H*",$device
2045 if length $device;
2046 #length $device
2047 # and die "unexpected trailing device data\n";
2048
2049 ($res, $tail)
2050 }
2051
2052 # decode a full binary BCD device descriptor
2053 sub dec_device($$) {
2054 my ($device, $type) = @_;
2055
2056 $device = pack "H*", $device;
2057
2058 my $guid = dec_guid substr $device, 0, 16, "";
2059 $guid = $guid eq "00000000-0000-0000-0000-000000000000"
2060 ? "" : "{$guid}";
2061
2062 eval {
2063 my ($dev, $tail) = dec_device_ $device, $type;
2064
2065 $tail eq ""
2066 or die "unsupported trailing data after device descriptor\n";
2067
2068 "$guid$dev"
2069 # } // scalar ((warn $@), "$guid$fallback")
2070 } // ($guid . "binary=" . unpack "H*", $device)
2071 }
2072
2073 sub indexof($@) {
2074 my $value = shift;
2075
2076 for (0 .. $#_) {
2077 $value eq $_[$_]
2078 and return $_;
2079 }
2080
2081 undef
2082 }
2083
2084 # encode the device portion after the GUID
2085 sub enc_device_($$);
2086 sub enc_device_($$) {
2087 my ($device, $type) = @_;
2088
2089 my $enc_path = sub {
2090 my $path = shift;
2091 $path =~ s/\//\\/g;
2092 (Encode::encode "UTF-16LE", $path) . "\x00\x00"
2093 };
2094
2095 my $enc_file = sub {
2096 my ($parent, $path) = @_; # parent and path must already be encoded
2097
2098 $path = $parent . $path;
2099
2100 # fver 1, ftype 5
2101 pack "VVVa*", 1, 12 + length $path, 5, $path
2102 };
2103
2104 my $parse_path = sub {
2105 s/^([\/\\][^<>"|?*\x00-\x1f]*)//
2106 or die "$_: invalid path\n";
2107
2108 $enc_path->($1)
2109 };
2110
2111 my $parse_parent = sub {
2112 my $parent;
2113
2114 if (s/^<//) {
2115 ($parent, $_) = enc_device_ $_, $type;
2116 s/^>//
2117 or die "$device: syntax error: parent device not followed by '>'\n";
2118 } else {
2119 $parent = $NULL_DEVICE;
2120 }
2121
2122 $parent
2123 };
2124
2125 for ($device) {
2126 s/^([a-z]+)//
2127 or die "$_: device does not start with type string\n";
2128
2129 my $type = $1;
2130 my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
2131 my $payload;
2132
2133 if ($type eq "binary") {
2134 s/^=([0-9a-fA-F]+)//
2135 or die "binary type must have a hex string argument\n";
2136
2137 $payload = pack "H*", $1;
2138
2139 } elsif ($type eq "null") {
2140 return ($NULL_DEVICE, $_);
2141
2142 } elsif ($type eq "boot") {
2143 $payload = "\x00" x 56;
2144
2145 } elsif ($type eq "partition" or $type eq "legacypartition") {
2146 s/^=//
2147 or die "$_: missing '=' after $type\n";
2148
2149 my $parent = $parse_parent->();
2150
2151 s/^,//
2152 or die "$_: comma missing after partition parent device\n";
2153
2154 s/^([a-z]+),//
2155 or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
2156 my $blocktype = $1;
2157
2158 s/^([a-z]+),//
2159 or die "$_: partition block type not followed by partiton type\n";
2160 my $parttype = $1;
2161
2162 my ($partdata, $diskdata);
2163
2164 if ($parttype eq "mbr") {
2165 s/^([0-9a-f]{8}),//i
2166 or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
2167 $diskdata = pack "Vx12", hex $1;
2168
2169 s/^([0-9]+)//
2170 or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
2171
2172 # the following works for both 64 bit offset and 32 bit partno
2173 $partdata = pack "Q< x8", $1;
2174
2175 } elsif ($parttype eq "gpt") {
2176 s/^($RE_GUID),//
2177 or die "$_: partition disk guid missing or malformed\n";
2178 $diskdata = enc_guid $1;
2179
2180 s/^($RE_GUID)//
2181 or die "$_: partition guid missing or malformed\n";
2182 $partdata = enc_guid $1;
2183
2184 } elsif ($parttype eq "raw") {
2185 s/^([0-9]+)//
2186 or die "$_: partition disk number missing or malformed (must be decimal)\n";
2187
2188 $partdata = pack "L< x12", $1;
2189
2190 } else {
2191 die "$parttype: partition type not supported\n";
2192 }
2193
2194 $payload = pack "a16 L< L< a16 a*",
2195 $partdata,
2196 (indexof $blocktype, @block_type),
2197 (indexof $parttype, @part_type),
2198 $diskdata,
2199 $parent;
2200
2201 } elsif ($type eq "locate") {
2202 s/^=//
2203 or die "$_: missing '=' after $type\n";
2204
2205 my ($mode, $elem, $path);
2206
2207 my $parent = $parse_parent->();
2208
2209 s/^,//
2210 or die "$_: missing comma after locate parent device\n";
2211
2212 if (s/^element,//) {
2213 s/^([0-9a-z:]+)//i
2214 or die "$_ locate element must be either name or 8-digit hex id\n";
2215 $elem = enc_bcde_id $1, $type;
2216 $mode = 0;
2217 $path = $enc_path->("");
2218
2219 } elsif (s/^path,//) {
2220 $mode = 1;
2221 $path = $parse_path->();
2222
2223 } else {
2224 die "$_ second locate argument must be subtype (either element or path)\n";
2225 }
2226
2227 if ($parent ne $NULL_DEVICE) {
2228 ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
2229 } else {
2230 $parent = 0;
2231 }
2232
2233 $payload = pack "VVVa*", $mode, $elem, $parent, $path;
2234
2235 } elsif ($type eq "block") {
2236 s/^=//
2237 or die "$_: missing '=' after $type\n";
2238
2239 s/^([a-z]+),//
2240 or die "$_: block device does not start with block type (e.g. disk)\n";
2241 my $blocktype = $1;
2242
2243 my $blockdata;
2244
2245 if ($blocktype eq "file") {
2246 my $parent = $parse_parent->();
2247 s/^,// or die "$_: comma missing after file block device parent\n";
2248 my $path = $parse_path->();
2249
2250 $blockdata = $enc_file->($parent, $path);
2251
2252 } elsif ($blocktype eq "vhd") {
2253 $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
2254 $blockdata .= $parse_parent->();
2255
2256 } elsif ($blocktype eq "ramdisk") {
2257 my $parent = $parse_parent->();
2258
2259 s/^,(\d+),(\d+),(\d+),//a
2260 or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
2261
2262 my ($base, $size, $offset) = ($1, $2, $3);
2263
2264 my $path = $parse_path->();
2265
2266 $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
2267
2268 } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
2269 # this is guesswork
2270 s/^(\d+)//a
2271 or die "$_: missing device number for cdrom\n";
2272 $blockdata = pack "V", $1;
2273
2274 } else {
2275 die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
2276 }
2277
2278 $payload = pack "Va*",
2279 (indexof $blocktype, @block_type),
2280 $blockdata;
2281
2282 } elsif ($type eq "vmbus") {
2283 s/^=($RE_GUID)//
2284 or die "$_: malformed or missing vmbus interface type guid\n";
2285 my $type = enc_guid $1;
2286 s/^,($RE_GUID)//
2287 or die "$_: malformed or missing vmbus interface instance guid\n";
2288 my $instance = enc_guid $1;
2289
2290 $payload = pack "a16a16x24", $type, $instance;
2291
2292 # } elsif ($type eq "udp") {
2293 # $payload = pack "Va16", 1, "12345678";
2294
2295 } else {
2296 die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
2297 }
2298
2299 return (
2300 (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
2301 $_
2302 );
2303 }
2304 }
2305
2306 # encode a full binary BCD device descriptor
2307 sub enc_device($$) {
2308 my ($device, $type) = @_;
2309
2310 my $guid = "\x00" x 16;
2311
2312 if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
2313 $guid = enc_guid $1
2314 or die "$device: does not start with valid guid\n";
2315 }
2316
2317 my ($descriptor, $tail) = enc_device_ $device, $type;
2318
2319 length $tail
2320 and die "$device: garbage after device descriptor\n";
2321
2322 unpack "H*", $guid . $descriptor
2323 }
2324
2325 # decode a registry hive into the BCD structure used by pbcdedit
2326 sub bcd_decode {
2327 my ($hive) = @_;
2328
2329 my %bcd;
2330
2331 my $objects = $hive->[1][1]{Objects}[1];
2332
2333 while (my ($k, $v) = each %$objects) {
2334 my %kv;
2335 $v = $v->[1];
2336
2337 $k = $bcd_objects{$k} // $k;
2338
2339 my $type = $v->{Description}[0]{Type}[1];
2340
2341 if ($type != $bcd_object_types{$k}) {
2342 $kv{type} = $bcd_types{$type} // sprintf "0x%08x", $type;
2343 }
2344
2345 my $elems = $v->{Elements}[1];
2346
2347 while (my ($k, $v) = each %$elems) {
2348 my $k = hex $k;
2349
2350 my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1], $type);
2351 my $k = dec_bcde_id $k, $type;
2352
2353 $kv{$k} = $v;
2354 }
2355
2356 $bcd{$k} = \%kv;
2357 }
2358
2359 $bcd{meta} = { version => $JSON_VERSION };
2360
2361 \%bcd
2362 }
2363
2364 # encode a pbcdedit structure into a registry hive
2365 sub bcd_encode {
2366 my ($bcd) = @_;
2367
2368 if (my $meta = $bcd->{meta}) {
2369 $meta->{version} eq $JSON_VERSION
2370 or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2371 }
2372
2373 my %objects;
2374 my %rbcd_types = reverse %bcd_types;
2375
2376 while (my ($k, $v) = each %$bcd) {
2377 my %kv;
2378
2379 next if $k eq "meta";
2380
2381 $k = lc $k; # I know you windows types!
2382
2383 my $type = $v->{type};
2384
2385 if ($type) {
2386 $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2387 ? hex $type
2388 : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2389 }
2390
2391 my $guid = enc_wguid $k
2392 or die "$k: invalid bcd object identifier\n";
2393
2394 # default type if not given
2395 $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2396
2397 my %elem;
2398
2399 while (my ($k, $v) = each %$v) {
2400 next if $k eq "type";
2401
2402 $k = (enc_bcde_id $k, $type) // die "$k: invalid bcde element name or id\n";
2403 $elem{sprintf "%08x", $k} = [{
2404 Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2405 }];
2406 }
2407
2408 $guid = dec_guid $guid;
2409
2410 $objects{"{$guid}"} = [undef, {
2411 Description => [{ Type => [dword => $type] }],
2412 Elements => [undef, \%elem],
2413 }];
2414 }
2415
2416 [NewStoreRoot => [undef, {
2417 Description => [{
2418 KeyName => [sz => "BCD00000001"],
2419 System => [dword => 1],
2420 pbcdedit => [sz => $VERSION],
2421 # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2422 }],
2423 Objects => [undef, \%objects],
2424 }]]
2425 }
2426
2427 #############################################################################
2428 # edit instructions
2429
2430 sub bcd_edit_eval {
2431 package pbcdedit;
2432
2433 our ($PATH, $BCD, $DEFAULT);
2434
2435 eval shift;
2436 die "$@" if $@;
2437 }
2438
2439 sub bcd_edit {
2440 my ($path, $bcd, @insns) = @_;
2441
2442 my $default = $bcd->{"{bootmgr}"}{default};
2443
2444 # prepare "officially visible" variables
2445 local $pbcdedit::PATH = $path;
2446 local $pbcdedit::BCD = $bcd;
2447 local $pbcdedit::DEFAULT = $default;
2448
2449 while (@insns) {
2450 my $insn = shift @insns;
2451
2452 if ($insn eq "get") {
2453 my $object = shift @insns;
2454 my $elem = shift @insns;
2455
2456 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2457
2458 print $bcd->{$object}{$elem}, "\n";
2459
2460 } elsif ($insn eq "set") {
2461 my $object = shift @insns;
2462 my $elem = shift @insns;
2463 my $value = shift @insns;
2464
2465 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2466
2467 $bcd->{$object}{$elem} = $value;
2468
2469 } elsif ($insn eq "eval") {
2470 my $perl = shift @insns;
2471 bcd_edit_eval "#line 1 'eval'\n$perl";
2472
2473 } elsif ($insn eq "do") {
2474 my $path = shift @insns;
2475 my $file = file_load $path;
2476 bcd_edit_eval "#line 1 '$path'\n$file";
2477
2478 } else {
2479 die "$insn: not a recognized instruction for edit/parse\n";
2480 }
2481 }
2482
2483 }
2484
2485 #############################################################################
2486 # other utilities
2487
2488 # json to stdout
2489 sub prjson($) {
2490 print $json_coder->encode ($_[0]);
2491 }
2492
2493 # json from stdin
2494 sub rdjson() {
2495 my $json;
2496 1 while read STDIN, $json, 65536, length $json;
2497 $json_coder->decode ($json)
2498 }
2499
2500 sub lsblk() {
2501 my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,MAJ:MIN,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2502
2503 for my $dev (@{ $lsblk->{blockdevices} }) {
2504 if ($dev->{type} eq "part") {
2505 if ($dev->{pttype} eq "gpt") {
2506 $dev->{bcd_device} = "partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}";
2507 } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2508 if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2509 my ($diskid, $partno) = ($1, hex $2);
2510 $dev->{bcd_legacy_device} = "legacypartition=<null>,harddisk,mbr,$diskid,$partno";
2511 if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2512 my $start = 512 * readline $fh;
2513 $dev->{bcd_device} = "partition=<null>,harddisk,mbr,$diskid,$start";
2514 }
2515 }
2516 }
2517 }
2518 }
2519
2520 $lsblk->{blockdevices}
2521 }
2522
2523 sub prdev($$) {
2524 my ($path, $attribute) = @_;
2525
2526 # rather than stat'ing and guessing how devices are encoded, we use lsblk for this
2527 # unfortunately, there doesn't seem to be a way to restrict lsblk to just oned evice,
2528 # so we always assume the first one is it.
2529 my $mm = $json_coder->decode (scalar qx<lsblk -o MAJ:MIN -J \Q$path\E>)->{blockdevices}[0]{"maj:min"};
2530
2531 my $lsblk = lsblk;
2532
2533 for my $dev (@$lsblk) {
2534 if ($dev->{"maj:min"} eq $mm && $dev->{$attribute}) {
2535 say $dev->{$attribute};
2536 exit 0;
2537 }
2538 }
2539
2540 exit 1;
2541 }
2542
2543 #############################################################################
2544 # command line parser
2545
2546 our %CMD = (
2547 help => sub {
2548 require Pod::Usage;
2549 Pod::Usage::pod2usage (-verbose => 2);
2550 },
2551
2552 objects => sub {
2553 my %rbcd_types = reverse %bcd_types;
2554 $_ = sprintf "%08x", $_ for values %rbcd_types;
2555
2556 if ($_[0] eq "--json") {
2557 my %default_type = %bcd_object_types;
2558 $_ = sprintf "%08x", $_ for values %default_type;
2559
2560 prjson {
2561 version => $JSON_VERSION,
2562 object_alias => \%bcd_objects,
2563 object_type => \%rbcd_types,
2564 object_default_type => \%default_type,
2565 };
2566 } else {
2567 my %rbcd_objects = reverse %bcd_objects;
2568
2569 print "\n";
2570
2571 printf "%-9s %s\n", "Type", "Alias";
2572 for my $tname (sort keys %rbcd_types) {
2573 printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2574 }
2575
2576 print "\n";
2577
2578 printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2579 for my $name (sort keys %rbcd_objects) {
2580 my $guid = $rbcd_objects{$name};
2581 my $type = $bcd_object_types{$name};
2582 my $tname = $bcd_types{$type};
2583
2584 $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2585
2586 printf "%-39s %-23s %s\n", $guid, $name, $type;
2587 }
2588
2589 print "\n";
2590 }
2591 },
2592
2593 elements => sub {
2594 my $json = $_[0] eq "--json";
2595
2596 my %format_name = (
2597 BCDE_FORMAT_DEVICE , "device",
2598 BCDE_FORMAT_STRING , "string",
2599 BCDE_FORMAT_GUID , "guid",
2600 BCDE_FORMAT_GUID_LIST , "guid list",
2601 BCDE_FORMAT_INTEGER , "integer",
2602 BCDE_FORMAT_BOOLEAN , "boolean",
2603 BCDE_FORMAT_INTEGER_LIST, "integer list",
2604 );
2605
2606 my @element;
2607
2608 for my $class (sort keys %rbcde_byclass) {
2609 my $rbcde = $rbcde_byclass{$class};
2610
2611 unless ($json) {
2612 print "\n";
2613 printf "Elements applicable to class(es): $class\n";
2614 printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2615 }
2616 for my $name (sort keys %$rbcde) {
2617 my $id = $rbcde->{$name};
2618 my $format = $format_name{$id & BCDE_FORMAT};
2619
2620 if ($json) {
2621 push @element, [$class, $id * 1, $format, $name];
2622 } else {
2623 $id = sprintf "%08x", $id;
2624 printf "%-9s %-12s %s\n", $id, $format, $name;
2625 }
2626 }
2627 }
2628 print "\n" unless $json;
2629
2630 prjson {
2631 version => $JSON_VERSION,
2632 element => \@element,
2633 class => \@bcde_typeclass,
2634 } if $json;
2635
2636 },
2637
2638 export => sub {
2639 prjson bcd_decode regf_load shift;
2640 },
2641
2642 import => sub {
2643 regf_save shift, bcd_encode rdjson;
2644 },
2645
2646 create => sub {
2647 my $path = shift;
2648 my $stat = stat_get $path; # should actually be done at file load time
2649 my $bcd = { };
2650 bcd_edit $path, $bcd, @_;
2651 regf_save $path, bcd_encode $bcd;
2652 stat_set $path, $stat;
2653 },
2654
2655 edit => sub {
2656 my $path = shift;
2657 my $stat = stat_get $path; # should actually be done at file load time
2658 my $bcd = bcd_decode regf_load $path;
2659 bcd_edit $path, $bcd, @_;
2660 regf_save $path, bcd_encode $bcd;
2661 stat_set $path, $stat;
2662 },
2663
2664 parse => sub {
2665 my $path = shift;
2666 my $bcd = bcd_decode regf_load $path;
2667 bcd_edit $path, $bcd, @_;
2668 },
2669
2670 "export-regf" => sub {
2671 prjson regf_load shift;
2672
2673 },
2674
2675 "import-regf" => sub {
2676 regf_save shift, rdjson;
2677 },
2678
2679 lsblk => sub {
2680 my $json = $_[0] eq "--json";
2681
2682 my $lsblk = lsblk;
2683
2684 if ($json) {
2685 prjson $lsblk;
2686 } else {
2687 printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2688 for my $dev (@$lsblk) {
2689 for my $bcd ($dev->{bcd_device}, $dev->{bcd_legacy_device}) {
2690 printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2691 $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $bcd
2692 if $bcd;
2693 }
2694 }
2695 }
2696 },
2697
2698 "bcd-device" => sub {
2699 prdev shift, "bcd_device";
2700 },
2701
2702 "bcd-legacy-device" => sub {
2703 prdev shift, "bcd_legacy_device";
2704 },
2705
2706 version => sub {
2707 print "\n",
2708 "PBCDEDIT version $VERSION, copyright 2019 Marc A. Lehmann <pbcdedit\@schmorp.de>.\n",
2709 "JSON schema version: $JSON_VERSION\n",
2710 "Licensed under the GNU General Public License Version 3.0, or any later version.\n",
2711 "\n",
2712 $CHANGELOG,
2713 "\n";
2714 },
2715 );
2716
2717 my $cmd = shift;
2718
2719 unless (exists $CMD{$cmd}) {
2720 warn "Usage: $0 subcommand args...\nTry $0 help\n";
2721 exit 126;
2722 }
2723
2724 $CMD{$cmd}->(@ARGV);
2725