4 * Copyright 2021 Sylvain BERTRAND <sylvain.bertrand@legeek.net>
5 * LICENSE: GNU AFFERO GPLV3
8 * quick and dirty, ultra minimal, source oriented, UEFI GPT partition creator
22 * how to get pertinent device information from sysfs for GPT partitioning
24 * for a /dev/sdX block device:
25 * /sys/block/sdX/size = size of the device in blocks of 512 bytes (hardcoded)
26 * /sys/block/sdX/queue/logical_block_size = size in bytes of a logical block
27 * which is the size used by LBA (Logical Block Access) offsets used
29 * /sys/block/sdX/queue/physical_block_size = size in bytes of a physical block
32 #define X64_UTF8_BYTES_MAX (sizeof("18446744073709551615") - 1)
34 #define strtou64 strtoul
41 /******************************************************************************/
42 /* stolen and cosmetized, not validated on big endian */
43 /* http://home.thep.lu.se/~bjorn/crc/ */
44 static u32
le_crc32_for_byte(u32 r
)
52 r
= (r
& 1 ? 0 : (u32
)0xedb88320) ^ r
>> 1;
55 return r
^ (u32
)0xff000000;
57 static u32 le_crc32_tbl
[0x100];
58 static void le_crc32_tbl_gen(void)
66 le_crc32_tbl
[i
] = le_crc32_for_byte(i
);
70 static void le_crc32_update(void *data
, u64 bytes_n
, u32
* crc
)
78 *crc
= le_crc32_tbl
[(u8
)*crc
^ ((u8
*)data
)[i
]] ^ *crc
>> 8;
82 /******************************************************************************/
83 /*----------------------------------------------------------------------------*/
92 /*----------------------------------------------------------------------------*/
93 static u8
*protective_mbr
;
95 #define BOOT_SIGNATURE_0X55 0x1fe
96 #define BOOT_SIGNATURE_0XAA 0x1ff
98 /*----------------------------------------------------------------------------*/
117 /* utf-16 names? really?... */
119 #define ENTRIES_ARRAY_MIN_BYTES_N 16384 /* specs: minimum to book on the disk */
120 #define ENTRY_BYTES_N 0x80
121 /* make it at least 128MB */
122 static struct entry_t entry_efi_system
=
124 .type
.blk_0
= 0xc12a7328,
125 .type
.blk_1
= 0xf81f,
126 .type
.blk_2
= 0x11d2,
127 .type
.blk_3
= 0xba4b,
128 .type
.blk_4
= {0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b},
130 .uniq
.blk_0
= 0xdeadbeef,
131 .uniq
.blk_1
= 0x0000,
132 .uniq
.blk_2
= 0x0000,
133 .uniq
.blk_3
= 0x0000,
134 .uniq
.blk_4
= {0x00, 0x0, 0x00, 0x00, 0x00, 0x01},
136 .first
= 0x0000000000000022,
137 .last
= 0x000000000004ffff,
141 static struct entry_t entry_root
=
143 /* linux root x86_64 GUID */
144 .type
.blk_0
= 0x4f68bce3,
145 .type
.blk_1
= 0xe8cd,
146 .type
.blk_2
= 0x4db1,
147 .type
.blk_3
= 0x96e7,
148 .type
.blk_4
= {0xfb, 0xca, 0xf9, 0x84, 0xb7, 0x09},
150 .uniq
.blk_0
= 0xdeadbeef,
151 .uniq
.blk_1
= 0x0000,
152 .uniq
.blk_2
= 0x0000,
153 .uniq
.blk_3
= 0x0000,
154 .uniq
.blk_4
= {0x00, 0x0, 0x00, 0x00, 0x00, 0x02},
156 /* everything else */
157 .first
= 0x0000000000050000,
158 .last
= 0x0, /* generated later */
162 static struct entry_t
*entries
[2] = {
166 static u8 entries_n
= 2;
167 static u8 entries_lbas_n
;
168 static u8
*entries_array
;
169 static u32 entries_array_crc32
;
170 /*----------------------------------------------------------------------------*/
171 struct hdrs_t
{ /* there are 2 headers, each must fit in a logical block */
172 u64 first_usable_lba
;
176 static struct hdrs_t hdrs
= {
177 .disk
.blk_0
= 0xdeadbeef,
178 .disk
.blk_1
= 0x0000,
179 .disk
.blk_2
= 0x0000,
180 .disk
.blk_3
= 0x0000,
181 .disk
.blk_4
= {0x00, 0x0, 0x00, 0x00, 0x00, 0x00},
183 static u8 hdr_signature
[8] = "EFI PART";
184 #define HDR_REVISION 0x00010000 /* 1.0 */
185 #define HDR_BYTES_N 0x5c /* 92 bytes */
186 static u8
*hdr_primary
;
187 static u8
*hdr_secondary
;
188 /*----------------------------------------------------------------------------*/
189 static utf8
*pf(utf8
*fmt
,...)
196 r
= vsnprintf(0, 0, fmt
, ap
);
199 r_str
= malloc(r
+ 1); /* we want a terminating 0 */
202 vsnprintf(r_str
, r
+ 1, fmt
, ap
); /* has room for the terminating 0 */
206 /* brain damaged mixed-endian guid */
207 static void guid_write(void *dest
, struct guid_t
*src
)
216 *p32
= htole32(src
->blk_0
); /* little endian */
219 *p16
= htole16(src
->blk_1
); /* little endian */
222 *p16
= htole16(src
->blk_2
); /* little endian */
225 *p16
= htobe16(src
->blk_3
); /* big endian */
227 d
[0] = src
->blk_4
[0];
228 d
[1] = src
->blk_4
[1];
229 d
[2] = src
->blk_4
[2];
230 d
[3] = src
->blk_4
[3];
231 d
[4] = src
->blk_4
[4];
232 d
[5] = src
->blk_4
[5];
234 static void sysfs_infos_get(void)
238 utf8 val_str
[X64_UTF8_BYTES_MAX
+ 1]; /* 0 terminating char */
241 utf8
*logical_blk_sz_path
;
242 utf8
*physical_blk_sz_path
;
244 blk_dev_name
= strrchr(blk_dev
.path
, '/');
246 printf("%s:device name is %s\n", blk_dev
.path
, blk_dev_name
);
247 sz_512_n_path
= pf("/sys/block/%s/size", blk_dev_name
);
248 printf("%s:reading %s\n", blk_dev
.path
, sz_512_n_path
);
249 fd
= open(sz_512_n_path
, O_RDONLY
);
251 dprintf(2, "%s:unable to open %s\n", blk_dev
.path
, sz_512_n_path
);
255 /* reads are supposed to be atomic from sysfs... I guess */
256 r
= read(fd
, val_str
, sizeof(val_str
));
257 val_str
[r
- 1] = 0; /* remove the terminating '\n' */
258 blk_dev
.sz_512_n
= strtou64(val_str
, 0, 10);
259 printf("%s:size is %"PRIu64
" blocks of 512 bytes\n", blk_dev
.path
, blk_dev
.sz_512_n
);
262 logical_blk_sz_path
= pf("/sys/block/%s/queue/logical_block_size", blk_dev_name
);
263 printf("%s:reading %s\n", blk_dev
.path
, logical_blk_sz_path
);
264 fd
= open(logical_blk_sz_path
, O_RDONLY
);
266 dprintf(2, "%s:unable to open %s\n", blk_dev
.path
, logical_blk_sz_path
);
269 free(logical_blk_sz_path
);
270 /* reads are supposed to be atomic from sysfs... I guess */
271 r
= read(fd
, val_str
, sizeof(val_str
));
272 val_str
[r
- 1] = 0; /* remove the terminating '\n' */
273 blk_dev
.logical_blk_sz
= strtou64(val_str
, 0, 10);
274 printf("%s:logical block size is %"PRIu64
" bytes\n", blk_dev
.path
, blk_dev
.logical_blk_sz
);
277 physical_blk_sz_path
= pf("/sys/block/%s/queue/physical_block_size", blk_dev_name
);
278 printf("%s:reading %s\n", blk_dev
.path
, physical_blk_sz_path
);
279 fd
= open(physical_blk_sz_path
, O_RDONLY
);
281 dprintf(2, "%s:unable to open %s\n", blk_dev
.path
, physical_blk_sz_path
);
284 free(physical_blk_sz_path
);
285 /* reads are supposed to be atomic from sysfs... I guess */
286 r
= read(fd
, val_str
, sizeof(val_str
));
287 val_str
[r
- 1] = 0; /* remove the terminating '\n' */
288 blk_dev
.physical_blk_sz
= strtou64(val_str
, 0, 10);
289 printf("%s:physical block size is %"PRIu64
" bytes\n", blk_dev
.path
, blk_dev
.physical_blk_sz
);
292 static void protective_mbr_gen(void)
294 u32
*le32_whole_dev_logical_blks_n
;
295 u64 whole_dev_bytes_n
;
296 u64 whole_dev_logical_blks_n
;
298 if (blk_dev
.logical_blk_sz
< 512) {
299 dprintf(2, "%s: something is wrong, the logical block size is %"PRIu64
", below 512/0x200 bytes", blk_dev
.path
, blk_dev
.logical_blk_sz
);
302 protective_mbr
= calloc(1, blk_dev
.logical_blk_sz
);
304 protective_mbr
[PART_0
+ 0x02] = 0x02; /* first CHS */
305 protective_mbr
[PART_0
+ 0x04] = 0xee; /* partition type */
306 protective_mbr
[PART_0
+ 0x05] = 0xff; /* last head */
307 protective_mbr
[PART_0
+ 0x06] = 0xff; /* last cylinder MSBs + last sector */
308 protective_mbr
[PART_0
+ 0x07] = 0xff; /* last cylinder LSBs */
309 protective_mbr
[PART_0
+ 0x08] = 0x1; /* little endian */
311 whole_dev_bytes_n
= blk_dev
.sz_512_n
* 512;
312 whole_dev_logical_blks_n
= whole_dev_bytes_n
/ blk_dev
.logical_blk_sz
;
313 /* cap to max, remove the MBR in LBA 0 */
314 if (whole_dev_logical_blks_n
> 0x100000000)
315 whole_dev_logical_blks_n
= 0xffffffff;
316 le32_whole_dev_logical_blks_n
= (u32
*)&protective_mbr
[PART_0
+ 0x0c];
317 *le32_whole_dev_logical_blks_n
= htole32( /* remove mbr */
318 (u32
)whole_dev_logical_blks_n
- 1);
320 protective_mbr
[BOOT_SIGNATURE_0X55
] = 0x55;
321 protective_mbr
[BOOT_SIGNATURE_0XAA
] = 0xaa;
323 static void protective_mbr_write(void)
326 size_t written_bytes_n
;
328 r0
= lseek(blk_dev
.fd
, 0, SEEK_SET
);
330 dprintf(2, "%s:unable to reach the start of the device\n", blk_dev
.path
);
339 r1
= write(blk_dev
.fd
, protective_mbr
+ written_bytes_n
, blk_dev
.logical_blk_sz
- written_bytes_n
);
343 dprintf(2, "%s:unable to write the protective master boot record (mbr), device mbr is now probably corrupted\n", blk_dev
.path
);
346 written_bytes_n
+= (size_t)r1
;
347 if (written_bytes_n
== (size_t)(blk_dev
.logical_blk_sz
))
350 printf("%s:protective master boot record (mbr) written, %"PRIu64
" bytes\n", blk_dev
.path
, blk_dev
.logical_blk_sz
);
352 static u8
entries_array_gen_entry(u8 entry_idx
)
357 entry
= entries_array
+ ENTRY_BYTES_N
* entry_idx
;
358 guid_write(&entry
[0x00], &entries
[entry_idx
]->type
);
359 guid_write(&entry
[0x10], &entries
[entry_idx
]->uniq
);
360 p64
= (u64
*)&entry
[0x20];
361 *p64
= htole64(entries
[entry_idx
]->first
);
362 p64
= (u64
*)&entry
[0x28];
363 *p64
= htole64(entries
[entry_idx
]->last
);
365 static void entries_array_gen(void)
369 entries_array
= calloc(1, entries_lbas_n
* blk_dev
.logical_blk_sz
);
372 if (entry_idx
== entries_n
)
374 entries_array_gen_entry(entry_idx
);
378 static void hdr_primary_gen(void)
385 hdr_primary
= calloc(1, blk_dev
.logical_blk_sz
);
387 memcpy(hdr_primary
, hdr_signature
, 8);
389 p32
= (u32
*)&hdr_primary
[0x08];
390 *p32
= htole32(HDR_REVISION
);
392 p32
=(u32
*)&hdr_primary
[0x0c];
393 *p32
= htole32(HDR_BYTES_N
);
395 /* the CRC32 will go there, 0 for its calculation */
397 p64
= (u64
*)&hdr_primary
[0x18];
398 *p64
= htole64(0x00000001); /* lba of this hdr */
400 p64
= (u64
*)&hdr_primary
[0x20];
401 *p64
= htole64(blk_dev
.last_lba
); /* the hdr at the end */
403 p64
= (u64
*)&hdr_primary
[0x28];
404 *p64
= htole64(hdrs
.first_usable_lba
);
406 p64
= (u64
*)&hdr_primary
[0x30];
407 *p64
= htole64(hdrs
.last_usable_lba
);
409 guid_write(&hdr_primary
[0x38], &hdrs
.disk
);
411 p64
= (u64
*)&hdr_primary
[0x48];
412 *p64
= htole64(2); /* skip mbr and hdr */
414 p32
= (u32
*)&hdr_primary
[0x50];
415 *p32
= htole32((u32
)(ENTRIES_ARRAY_MIN_BYTES_N
/ENTRY_BYTES_N
));
417 p32
= (u32
*)&hdr_primary
[0x54];
418 *p32
= htole32(ENTRY_BYTES_N
);
420 p32
= (u32
*)&hdr_primary
[0x58];
421 *p32
= htole32(entries_array_crc32
);
423 /* crc32 on exactly the header size */
425 le_crc32_update(hdr_primary
, HDR_BYTES_N
, &le_crc32
);
426 printf("%s:primary hdr crc32 is 0x%"PRIx32
"\n", blk_dev
.path
, le_crc32
);
427 p32
= (u32
*)&hdr_primary
[0x10];
430 static void hdr_secondary_gen(void)
437 hdr_secondary
= calloc(1, blk_dev
.logical_blk_sz
);
439 memcpy(hdr_secondary
, hdr_signature
, 8);
441 p32
= (u32
*)&hdr_secondary
[0x08];
442 *p32
= htole32(HDR_REVISION
);
444 p32
=(u32
*)&hdr_secondary
[0x0c];
445 *p32
= htole32(HDR_BYTES_N
);
447 /* the CRC32 will go there, 0 for its calculation */
449 p64
= (u64
*)&hdr_secondary
[0x18];
450 *p64
= htole64(blk_dev
.last_lba
); /* this hdr */
452 p64
= (u64
*)&hdr_secondary
[0x20];
453 *p64
= htole64(1); /* the hdr at the beginning */
455 p64
= (u64
*)&hdr_secondary
[0x28];
456 *p64
= htole64(hdrs
.first_usable_lba
);
458 p64
= (u64
*)&hdr_secondary
[0x30];
459 *p64
= htole64(hdrs
.last_usable_lba
);
461 guid_write(&hdr_secondary
[0x38], &hdrs
.disk
);
463 p64
= (u64
*)&hdr_secondary
[0x48];
464 *p64
= htole64(blk_dev
.last_lba
- entries_lbas_n
);
466 p32
= (u32
*)&hdr_secondary
[0x50];
467 *p32
= htole32((u32
)(ENTRIES_ARRAY_MIN_BYTES_N
/ENTRY_BYTES_N
));
469 p32
= (u32
*)&hdr_secondary
[0x54];
470 *p32
= htole32(ENTRY_BYTES_N
);
472 p32
= (u32
*)&hdr_secondary
[0x58];
473 *p32
= htole32(entries_array_crc32
);
475 /* crc32 on exactly the header size */
477 le_crc32_update(hdr_secondary
, HDR_BYTES_N
, &le_crc32
);
478 printf("%s:secondary hdr crc32 is 0x%"PRIx32
"\n", blk_dev
.path
, le_crc32
);
479 p32
= (u32
*)&hdr_secondary
[0x10];
482 static void primary_hdr_write(void)
486 size_t written_bytes_n
;
487 size_t bytes_to_write_n
;
490 start
= (off_t
)blk_dev
.logical_blk_sz
;
491 r0
= lseek(blk_dev
.fd
, start
, SEEK_SET
);
493 dprintf(2, "%s:unable to reach the first lba of the device\n", blk_dev
.path
);
496 bytes_to_write_n
= (size_t)(blk_dev
.logical_blk_sz
);
503 r1
= write(blk_dev
.fd
, hdr_primary
+ written_bytes_n
, bytes_to_write_n
- written_bytes_n
);
507 dprintf(2, "%s:unable to write the primary GPT header, block device is now probably corrupted\n", blk_dev
.path
);
510 written_bytes_n
+= (size_t)r1
;
511 if (written_bytes_n
== bytes_to_write_n
)
514 printf("%s:primary GPT header written, %"PRIu64
" bytes\n", blk_dev
.path
, bytes_to_write_n
);
516 static void secondary_hdr_write(void)
520 size_t written_bytes_n
;
521 size_t bytes_to_write_n
;
523 start
= (off_t
)(blk_dev
.last_lba
* blk_dev
.logical_blk_sz
);
524 r0
= lseek(blk_dev
.fd
, start
, SEEK_SET
);
526 dprintf(2, "%s:unable to reach the lba of the secondary header\n", blk_dev
.path
);
529 bytes_to_write_n
= (size_t)(blk_dev
.logical_blk_sz
);
536 r1
= write(blk_dev
.fd
, hdr_secondary
+ written_bytes_n
, bytes_to_write_n
- written_bytes_n
);
540 dprintf(2, "%s:unable to write the primary GPT header, block device is now probably corrupted\n", blk_dev
.path
);
543 written_bytes_n
+= (size_t)r1
;
544 if (written_bytes_n
== bytes_to_write_n
)
547 printf("%s:secondary GPT header written, %"PRIu64
" bytes\n", blk_dev
.path
, bytes_to_write_n
);
549 static void primary_entries_write(void)
552 size_t written_bytes_n
;
554 size_t bytes_to_write_n
;
556 /* skip the mbr and the primary hdr */
557 start
= (off_t
)(blk_dev
.logical_blk_sz
* 2);
558 r0
= lseek(blk_dev
.fd
, start
, SEEK_SET
);
560 dprintf(2, "%s:unable to reach the first lba for the primary entries\n", blk_dev
.path
);
563 bytes_to_write_n
= (size_t)(blk_dev
.logical_blk_sz
* entries_lbas_n
);
570 r1
= write(blk_dev
.fd
, entries_array
+ written_bytes_n
, bytes_to_write_n
- written_bytes_n
);
574 dprintf(2, "%s:unable to write the primary entries, block device is now probably corrupted\n", blk_dev
.path
);
577 written_bytes_n
+= (size_t)r1
;
578 if (written_bytes_n
== bytes_to_write_n
)
581 printf("%s:primary entries written, %"PRIu64
" bytes\n", blk_dev
.path
, bytes_to_write_n
);
583 static void secondary_entries_write(void)
586 size_t written_bytes_n
;
588 size_t bytes_to_write_n
;
590 /* skip the secondary hdr, offset arithmetic */
591 start
= (off_t
)(blk_dev
.logical_blk_sz
* (blk_dev
.last_lba
593 r0
= lseek(blk_dev
.fd
, start
, SEEK_SET
);
595 dprintf(2, "%s:unable to reach the first lba for the secondary entries\n", blk_dev
.path
);
598 bytes_to_write_n
= (size_t)(blk_dev
.logical_blk_sz
* entries_lbas_n
);
605 r1
= write(blk_dev
.fd
, entries_array
+ written_bytes_n
, bytes_to_write_n
- written_bytes_n
);
609 dprintf(2, "%s:unable to write the secondary entries, block device is now probably corrupted\n", blk_dev
.path
);
612 written_bytes_n
+= (size_t)r1
;
613 if (written_bytes_n
== bytes_to_write_n
)
616 printf("%s:secondary entries written, %"PRIu64
" bytes\n", blk_dev
.path
, bytes_to_write_n
);
618 int main(int argc
, utf8
**argv
)
620 u64 whole_dev_bytes_n
;
624 dprintf(2, "missing block device path\n");
627 blk_dev
.path
= argv
[1];
628 printf("block device path is %s\n", blk_dev
.path
);
629 blk_dev
.fd
= open(blk_dev
.path
, O_RDWR
| O_SYNC
);
630 if (blk_dev
.fd
== -1) {
631 dprintf(2, "%s:unable to open\n", blk_dev
.path
);
634 printf("%s:opened\n", blk_dev
.path
);
637 whole_dev_bytes_n
= blk_dev
.sz_512_n
* 512;
638 if ((whole_dev_bytes_n
% blk_dev
.logical_blk_sz
) != 0) {
639 dprintf(2, "%s: the whole device size %"PRIu64
" is not a multiple of the logical block size %"PRIu64
" bytes\n", whole_dev_bytes_n
, blk_dev
.logical_blk_sz
);
642 /* the total number of lba, -1 to get the offset of the last one */
643 blk_dev
.last_lba
= blk_dev
.sz_512_n
* 512 / blk_dev
.logical_blk_sz
- 1;
644 printf("%s:last lba is %"PRIu64
"\n", blk_dev
.path
, blk_dev
.last_lba
);
646 entries_bytes_n
= ENTRY_BYTES_N
* entries_n
;
647 /* we handle entries array size of ENTRIES_ARRAY_MIN_BYTES_N */
648 if (entries_bytes_n
> ENTRIES_ARRAY_MIN_BYTES_N
) {
649 dprintf(2, "%s:sorry,you have too many partition entries, %u, for this tool to handle\n", blk_dev
.path
, entries_n
);
652 entries_lbas_n
= ENTRIES_ARRAY_MIN_BYTES_N
/ blk_dev
.logical_blk_sz
653 + ((ENTRIES_ARRAY_MIN_BYTES_N
% blk_dev
.logical_blk_sz
) == 0
655 /* protective mbr, hdr, entries */
656 hdrs
.first_usable_lba
= 0 + 2 + entries_lbas_n
;
658 hdrs
.last_usable_lba
= blk_dev
.last_lba
- 1 - entries_lbas_n
;
659 printf("%s:lbas for partitions:first usable is %"PRIu64
", last usable is %"PRIu64
"\n", blk_dev
.path
, hdrs
.first_usable_lba
, hdrs
.last_usable_lba
);
661 entry_root
.last
= hdrs
.last_usable_lba
;
666 /* crc32 on exactly the entries array size, not lba bounded */
667 entries_array_crc32
= 0;
668 le_crc32_update(entries_array
, (ENTRIES_ARRAY_MIN_BYTES_N
/ENTRY_BYTES_N
)
669 * ENTRY_BYTES_N
, &entries_array_crc32
);
670 printf("%s:entries array crc32 is 0x%"PRIx32
"\n", blk_dev
.path
, entries_array_crc32
);
673 protective_mbr_gen();
675 protective_mbr_write();
677 secondary_hdr_write();
678 primary_entries_write();
679 secondary_entries_write();