Created
May 28, 2018 12:35
-
-
Save JosephSalisbury/df86c53a0dcd5118fdb0b53b26bfb8c6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
# Script creating a disk img from a compiled ignition config read from stdin | |
# Based on https://github.com/coreos/vagrant-ignition/blob/master/lib/vagrant-ignition/action/IgnitionDiskGenerator.rb | |
# See https://github.com/coreos/bugs/issues/1573 | |
require 'zlib' | |
# Note: gpt and gpt2 are the primary and secondary headers respectively | |
class IgnitionDiskGenerator | |
# These are variables needed across both function | |
@@first_usable_lba | |
@@gpt_entrysize | |
@@gpt_max_entries | |
@@gpt_length | |
@@last_usable_lba | |
def self.create_disk(contents) | |
# Constants | |
@first_usable_lba = 2048 | |
@gpt_entrysize = 128 | |
@gpt_max_entries = 128 | |
gpt_lba = 1 | |
arr_lba = 2 | |
lba_size = 512 | |
pmbr_length = lba_size | |
@gpt_length = lba_size | |
arr_length = @gpt_max_entries * @gpt_entrysize | |
minimum_disk_size = 1536000 # ~1.5M; there are issues with udev not registering the device if it is less than this... | |
partitioner_reserved_space = (@first_usable_lba * lba_size) + (@gpt_length + arr_length) # this is space reserved by the partition tables and arrays | |
part_label = "Ignition Config Drive" | |
part_label_encoded = part_label.encode("utf-16le").bytes | |
file_size_unrounded = contents.bytesize | |
if file_size_unrounded < minimum_disk_size | |
file_size = minimum_disk_size - partitioner_reserved_space | |
elsif (file_size_unrounded % lba_size) != 0 | |
file_size = lba_size * ((file_size_unrounded / lba_size) + 1) | |
else | |
file_size = file_size_unrounded | |
end | |
drive_size = file_size + partitioner_reserved_space | |
# Calculated variables | |
drive_sectors = drive_size / lba_size | |
gpt2_lba = drive_sectors - 1 | |
arr2_lba = drive_sectors - 33 | |
# This is global as gen_gpt requires it | |
@last_usable_lba = drive_sectors - 34 | |
## START PMBR ## | |
pmbr = ("\0".bytes) * 446 # bootloader code | |
pmbr += "\x00".bytes # status code | |
pmbr += "\x00\x01\x00".bytes # first CHS (for a protective MBR, we can just use the min value) | |
pmbr += "\xee".bytes # partition type | |
pmbr += "\xfe\xff\xff".bytes # last CHS (for a protective MBR, we can just use the max value) | |
pmbr += "\x01\x00\x00\x00".bytes # first LBA of first partition | |
pmbr += [gpt2_lba].pack('L').bytes # last LBA of first partition | |
pmbr += ("\x00".bytes) * 48 # other partition data | |
pmbr += "\x55\xaa".bytes # MBR signature | |
## END PMBR ## | |
## START PART ## | |
# UUID format on disk according to cgpt: | |
# typedef struct { | |
# UINT32 Data1; | |
# UINT16 Data2; | |
# UINT16 Data3; | |
# UINT8 Data4[8]; | |
# } EFI_GUID; | |
# Partition Type GUID is 0FC63DAF-8483-4772-8E79-3D69D8477DE4 (Linux Filesystem) | |
part1 = [0x0FC63DAF].pack('L').bytes + [0x8483].pack('S').bytes + [0x4772].pack('S').bytes + [0x8E793D69D8477DE4].pack('Q').bytes.reverse! | |
# Partition GUID is 99570A8A-F826-4EB0-BA4E-9DD72D55EA13 | |
part1 += [0x99570A8A].pack('L').bytes + [0xF826].pack('S').bytes + [0x4EB0].pack('S').bytes + [0xBA4E9DD72D55EA13].pack('Q').bytes.reverse! | |
part1 += [@first_usable_lba].pack('Q').bytes # first LBA of first partition | |
part1 += [@last_usable_lba].pack('Q').bytes # last LBA of first partition | |
part1 += "\0".bytes * 8 # partition attributes | |
part1 += part_label_encoded + ("\0".bytes * (72 - part_label_encoded.length)) # partition name (length of 72 bytes, or 36 utf16le characters) | |
part_others = "\0".bytes * (arr_length - part1.length) # other partition data | |
part_arr = part1 + part_others | |
part_extra_space = "\0".bytes * ((@first_usable_lba * lba_size) - (pmbr_length + @gpt_length + arr_length)) # this adds a buffer so that our partition starts at sector 2048 | |
part_full = part_arr + part_extra_space | |
## END PART ## | |
## START GPT ## | |
gpt = gen_gpt(gpt_lba, gpt2_lba, arr_lba, part_arr) | |
## END GPT ## | |
## START GPT2 ## | |
gpt2 = gen_gpt(gpt2_lba, gpt_lba, arr2_lba, part_arr) | |
## END GPT2 ## | |
## START DATA ## | |
if file_size_unrounded < minimum_disk_size | |
data = contents.bytes + ("\x00".bytes * (file_size - contents.bytesize)) | |
else | |
data = contents.bytes + ("\x00".bytes * (lba_size - (file_size_unrounded % lba_size))) | |
end | |
## END DATA ## | |
device = pmbr + gpt + part_full + data + part1 + part_others + gpt2 | |
device.each do | byte | | |
print byte.chr | |
end | |
end | |
# The only thing calling this function should be self.create_disk | |
private_class_method def self.gen_gpt(curr_lba, other_lba, arr_lba, part_arr) | |
gpt_first = "\x45\x46\x49\x20\x50\x41\x52\x54".bytes # GPT signature | |
gpt_first += "\x00\x00\x01\x00".bytes # GPT version | |
gpt_first += "\x5c\x00\x00\x00".bytes # GPT header size | |
gpt_fakecrc = "\x00\x00\x00\x00".bytes # CRC for header must be zeroed during calculation | |
gpt_second = "\x00\x00\x00\x00".bytes # reserved zero bytes | |
gpt_second += [curr_lba].pack('Q').bytes # LBA of current table | |
gpt_second += [other_lba].pack('Q').bytes # LBA of backup/secondary table | |
gpt_second += [@first_usable_lba].pack('Q').bytes # first usable lba for partitions | |
gpt_second += [@last_usable_lba].pack('Q').bytes # last usable lba for partitions | |
# gpt uuid is C89E4452-AAF1-67D0-8299-E7651D2805A8 | |
gpt_second += [0xC89E4452].pack('L').bytes + [0xAAF1].pack('S').bytes + [0x67D0].pack('S').bytes + [0x8299E7651D2805A8].pack('Q').bytes.reverse! | |
gpt_second += [arr_lba].pack('Q').bytes # LBA of this table's partition array | |
gpt_second += [@gpt_max_entries].pack('L').bytes # max number of entries in partition array | |
gpt_second += [@gpt_entrysize].pack('L').bytes # size (in bytes) of each entry in partition array | |
gpt_second += [Zlib::crc32((part_arr).pack('C*'))].pack('L').bytes # CRC for the partition array | |
gpt_intermediate = gpt_first + gpt_fakecrc + gpt_second # this is used during crc32 calculation | |
gpt_crc = [Zlib::crc32(gpt_intermediate.pack('C*'))].pack('L').bytes | |
gpt_end = ("\x00".bytes) * (@gpt_length - gpt_intermediate.length) # padding to finish LBA | |
# Return finished gpt | |
gpt_first + gpt_crc + gpt_second + gpt_end | |
end | |
end | |
IgnitionDiskGenerator.create_disk(ARGF.read) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment