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