From: Valentin David Date: Sat, 21 Mar 2026 14:42:13 +0000 (+0100) Subject: repart: Optionally write minimal an El Torito boot catalog for EFI X-Git-Tag: v261-rc1~690 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=365361f4908d9cfa9a371cd2c52a19c2414cb554;p=thirdparty%2Fsystemd.git repart: Optionally write minimal an El Torito boot catalog for EFI This only points the firmware to the ESP. The ISO9660 is empty. The initramfs should create a loop device to change block size and enable GPT partitions. This was tested using OVMF on qemu, with: `-drive if=pflash,file=OVMF_CODE.fd,readonly=on,format=raw -drive if=pflash,file=OVMF_VARS.fd,format=raw -drive if=none,id=live-disk,file=dick.iso,media=cdrom,format=raw,readonly=on -device virtio-scsi-pci,id=scsi -device scsi-cd,drive=live-disk` And a simple definition: ``` [Partition] Type=esp Format=vfat CopyFiles=/usr/lib/systemd/boot/efi/systemd-bootx64.efi:/EFI/BOOT/BOOTX64.EFI ``` --- diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 18e127be464..0cb14b69913 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -718,6 +718,59 @@ + + + + Write a minimal ISO9660 header with El Torito boot catalog. That will + boot the ESP on EFI firmware. + + The ISO9660 filesystem created by it will be empty. The initramfs is expected to create a + partitionable loop device on top of the device to change the block size and enable GPT + partitions. + + The disk requires at least one partition with Type=esp. The first one will + be the one referenced in the boot catalog. + + This option is available only when creating a new partition table, that is when + has value require, force or + create. + + + + + + + + When creating an ISO9660 header, this value will be used as the system identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 32 characters. + + + + + + + + When creating an ISO9660 header, this value will be used as volume identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 32 characters. + + + + + + + + When creating an ISO9660 header, this value will be used as publisher identifier. + This is useful for the media to be matched against osinfo db. + + The value is limited to 128 characters. + + + + diff --git a/src/repart/iso9660.c b/src/repart/iso9660.c new file mode 100644 index 00000000000..5bc9588e689 --- /dev/null +++ b/src/repart/iso9660.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iso9660.h" +#include "log.h" +#include "stdio-util.h" +#include "string-util.h" +#include "time-util.h" + +void no_iso9660_datetime(struct iso9660_datetime *ret) { + assert(ret); + + memcpy(ret->year, "0000", 4); + memcpy(ret->month, "00", 2); + memcpy(ret->day, "00", 2); + memcpy(ret->hour, "00", 2); + memcpy(ret->minute, "00", 2); + memcpy(ret->second, "00", 2); + memcpy(ret->deci, "00", 2); + ret->zone = 0; +} + +int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + if (t.tm_year >= 10000 - 1900) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year has more than 4 digits and is incompatible with ISO9660."); + if (t.tm_year + 1900 < 0) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is negative and is incompatible with ISO9660."); + + char buf[17]; + /* Ignore leap seconds, no real hope for hardware. Deci-seconds always zero. */ + xsprintf(buf, "%04d%02d%02d%02d%02d%02d00", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, MIN(t.tm_sec, 59)); + memcpy(ret, buf, sizeof(buf)-1); + + /* The time zone is encoded by 15 minutes increments */ + ret->zone = t.tm_gmtoff / (15*60); + + return 0; +} + +int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret) { + struct tm t; + int r; + + assert(ret); + + r = localtime_or_gmtime_usec(usec, utc, &t); + if (r < 0) + return r; + + if (t.tm_year < 0 || t.tm_year > UINT8_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Year is incompatible with ISO9660."); + + *ret = (struct iso9660_dir_time) { + .year = t.tm_year, + .month = t.tm_mon + 1, + .day = t.tm_mday, + .hour = t.tm_hour, + .minute = t.tm_min, + .second = MIN(t.tm_sec, 59), + /* The time zone is encoded by 15 minutes increments */ + .offset = t.tm_gmtoff / (15*60), + }; + + return 0; +} + +static bool valid_iso9660_string(const char *str, bool allow_a_chars) { + /* note that a-chars are not supposed to accept lower case letters, but it looks like common practice + * to use them + */ + return in_charset(str, allow_a_chars ? UPPERCASE_LETTERS LOWERCASE_LETTERS DIGITS " _!\"%&'()*+,-./:;<=>?" : UPPERCASE_LETTERS DIGITS "_"); +} + +int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars) { + if (source && !valid_iso9660_string(source, allow_a_chars)) + return -EINVAL; + + if (source) { + size_t slen = strlen(source); + if (slen > len) + return -EINVAL; + void *p = mempcpy(target, source, slen); + memset(p, ' ', len - slen); + } else + memset(target, ' ', len); + + return 0; +} + +bool iso9660_volume_name_valid(const char *name) { + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_system_name_valid(const char *name) { + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 32; +} + +bool iso9660_publisher_name_valid(const char *name) { + return valid_iso9660_string(name, /* allow_a_chars= */ true) && + strlen(name) <= 128; +} diff --git a/src/repart/iso9660.h b/src/repart/iso9660.h new file mode 100644 index 00000000000..7a51928fd60 --- /dev/null +++ b/src/repart/iso9660.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "sparse-endian.h" + +/* ISO9660 is 5 blocks: + * - Primary descriptor + * - El torito descriptor + * - Terminal descriptor + * - El Torito boot catalog + * - Root directory + */ +#define ISO9660_BLOCK_SIZE 2048U +#define ISO9660_START 16U +#define ISO9660_PRIMARY_DESCRIPTOR (ISO9660_START+0U) +#define ISO9660_ELTORITO_DESCRIPTOR (ISO9660_START+1U) +#define ISO9660_TERMINAL_DESCRIPTOR (ISO9660_START+2U) +#define ISO9660_BOOT_CATALOG (ISO9660_START+3U) +#define ISO9660_ROOT_DIRECTORY (ISO9660_START+4U) +#define ISO9660_SIZE 5U + +struct _packed_ iso9660_volume_descriptor_header { + uint8_t type; + char identifier[5]; + uint8_t version; +}; + +struct _packed_ iso9660_terminal_descriptor { + struct iso9660_volume_descriptor_header header; + uint8_t data[2041]; +}; +assert_cc(sizeof(struct iso9660_terminal_descriptor) == 2048); + +struct _packed_ iso9660_datetime { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char deci[2]; + int8_t zone; +}; + +struct _packed_ iso9660_eltorito_descriptor { + struct iso9660_volume_descriptor_header header; + + char boot_system_identifier[32]; + uint8_t unused_1[32]; + le32_t boot_catalog_sector; + uint8_t unused_2[1973]; +}; + +assert_cc(sizeof(struct iso9660_eltorito_descriptor) == 2048); + +struct _packed_ iso9660_dir_time { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + int8_t offset; +}; + +struct _packed_ iso9660_directory_entry { + uint8_t len; + uint8_t xattr_len; + le32_t extent_loc_little; + be32_t extent_loc_big; + le32_t data_len_little; + be32_t data_len_big; + struct iso9660_dir_time time; + uint8_t flags; + uint8_t unit_size; + uint8_t gap_size; + le16_t volume_seq_num_little; + be16_t volume_seq_num_big; + uint8_t ident_len; + char ident[1]; /* variable */ +}; + +struct _packed_ iso9660_primary_volume_descriptor { + struct iso9660_volume_descriptor_header header; + + uint8_t unused_1; + char system_identifier[32]; + char volume_identifier[32]; + uint8_t unused_2[8]; + le32_t volume_space_size_little; + be32_t volume_space_size_big; + uint8_t unused_3[32]; + + le16_t volume_set_size_little; + be16_t volume_set_size_big; + le16_t volume_sequence_number_little; + be16_t volume_sequence_number_big; + le16_t logical_block_size_little; + be16_t logical_block_size_big; + + le32_t path_table_size_little; + be32_t path_table_size_big; + + le32_t path_table_little; + le32_t opt_path_table_little; + + be32_t path_table_big; + be32_t opt_path_table_big; + + struct iso9660_directory_entry root_directory_entry; + + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + + char copyright_file_identifier[37]; + char abstract_file_identifier[37]; + char bibliographic_file_identifier[37]; + + struct iso9660_datetime volume_creation_date; + struct iso9660_datetime volume_modification_date; + struct iso9660_datetime volume_expiration_date; + struct iso9660_datetime volume_effective_date; + + uint8_t file_structure_version; /* 1 */ + uint8_t unused_5; + char application_used[512]; + char reserved[653]; +}; +assert_cc(sizeof(struct iso9660_primary_volume_descriptor) == 2048); + +struct _packed_ el_torito_validation_entry { + uint8_t header_indicator; + uint8_t platform; + char reserved[2]; + char id_string[24]; + le16_t checksum; + uint8_t key_bytes[2]; +}; + +struct _packed_ el_torito_initial_entry { + uint8_t boot_indicator; + uint8_t boot_media_type; + le16_t load_segment; + uint8_t system_type; + uint8_t unused_1[1]; + le16_t sector_count; + le32_t load_rba; + uint8_t unused_2[20]; +}; + +struct _packed_ el_torito_section_header { + uint8_t header_indicator; + uint8_t platform; + le16_t nentries; + char id_string[28]; +}; + +void no_iso9660_datetime(struct iso9660_datetime *ret); +int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret); +int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret); +int set_iso9660_string(char target[], size_t len, const char *source, bool allow_a_chars); + +static inline void set_iso9660_const_string(char target[], size_t len, const char *source, bool allow_a_chars) { + assert_se(set_iso9660_string(target, len, source, allow_a_chars) == 0); +} + +bool iso9660_volume_name_valid(const char *name); +bool iso9660_system_name_valid(const char *name); +bool iso9660_publisher_name_valid(const char *name); diff --git a/src/repart/meson.build b/src/repart/meson.build index e6e32f54c7a..92c7d37da5a 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -8,7 +8,10 @@ executables += [ executable_template + { 'name' : 'systemd-repart', 'public' : true, - 'extract' : files('repart.c'), + 'extract' : files( + 'repart.c', + 'iso9660.c', + ), 'link_with' : [ libshared, libshared_fdisk, diff --git a/src/repart/repart.c b/src/repart/repart.c index bae376fc0ee..5c36bad0758 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -49,6 +49,7 @@ #include "initrd-util.h" #include "install-file.h" #include "io-util.h" +#include "iso9660.h" #include "json-util.h" #include "libmount-util.h" #include "list.h" @@ -213,6 +214,10 @@ static char *arg_generate_crypttab = NULL; static Set *arg_verity_settings = NULL; static bool arg_relax_copy_block_security = false; static bool arg_varlink = false; +static bool arg_eltorito = false; +static char *arg_eltorito_system = NULL; +static char *arg_eltorito_volume = NULL; +static char *arg_eltorito_publisher = NULL; STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -237,6 +242,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_system, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_volume, freep); +STATIC_DESTRUCTOR_REGISTER(arg_eltorito_publisher, freep); typedef enum ProgressPhase { PROGRESS_LOADING_DEFINITIONS, @@ -7651,6 +7659,321 @@ static int context_split(Context *context) { return 0; } +static int write_primary_descriptor( + int fd, + uint32_t root_sector, + usec_t usec, + bool utc, + const char *system_id, + const char *volume_id, + const char *publisher_id) { + int r; + + struct iso9660_primary_volume_descriptor desc = { + .header = { + .type = 1, + .version = 1, + }, + .volume_space_size_little = htole32(ISO9660_START + ISO9660_SIZE), + .volume_space_size_big = htobe32(ISO9660_START + ISO9660_SIZE), + .volume_set_size_little = htole16(1), + .volume_set_size_big = htobe16(1), + .volume_sequence_number_little = htole16(1), + .volume_sequence_number_big = htobe16(1), + .logical_block_size_little = htole16(ISO9660_BLOCK_SIZE), + .logical_block_size_big = htobe16(ISO9660_BLOCK_SIZE), + .file_structure_version = 1, + .root_directory_entry = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .data_len_big = htobe32(2*sizeof(struct iso9660_directory_entry)), /* 2 entries with ident size 1: . and .. */ + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for root */ + } + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + r = time_to_iso9660_dir_datetime(usec, utc, &desc.root_directory_entry.time); + if (r < 0) + return r; + + r = set_iso9660_string(desc.system_identifier, sizeof(desc.system_identifier), system_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + /* In theory the volume identifier should be d-chars, but in practice, a-chars are allowed */ + r = set_iso9660_string(desc.volume_identifier, sizeof(desc.volume_identifier), volume_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + set_iso9660_const_string(desc.volume_set_identifier, sizeof(desc.volume_set_identifier), NULL, /* allow_a_chars= */ false); + + r = set_iso9660_string(desc.publisher_identifier, sizeof(desc.publisher_identifier), publisher_id, /* allow_a_chars= */ true); + if (r < 0) + return r; + + set_iso9660_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true); + set_iso9660_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true); + set_iso9660_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false); + set_iso9660_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false); + set_iso9660_const_string(desc.bibliographic_file_identifier, sizeof(desc.bibliographic_file_identifier), NULL, /* allow_a_chars= */ false); + + r = time_to_iso9660_datetime(usec, utc, &desc.volume_creation_date); + if (r < 0) + return r; + + r = time_to_iso9660_datetime(usec, utc, &desc.volume_modification_date); + if (r < 0) + return r; + + no_iso9660_datetime(&desc.volume_expiration_date); + no_iso9660_datetime(&desc.volume_effective_date); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 primary descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 primary descriptor"); + + return 0; +} + +static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) { + struct iso9660_eltorito_descriptor desc = { + .header = { + .type = 0, + .version = 1, + }, + .boot_catalog_sector = htole32(catalog_sector), + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + strncpy(desc.boot_system_identifier, "EL TORITO SPECIFICATION", sizeof(desc.boot_system_identifier)); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_ELTORITO_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 El-Torito descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 El-Torito descriptor"); + + return 0; +} + +static int write_terminal_descriptor(int fd) { + struct iso9660_terminal_descriptor desc = { + .header = { + .type = 255, + .version = 1, + }, + }; + + set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true); + + ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_TERMINAL_DESCRIPTOR*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 terminal descriptor: %m"); + if (s != sizeof(desc)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 terminal descriptor"); + + return 0; +} + +static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) { + assert(size % 2 == 0); + + uint16_t checksum = 0; + + for (size_t i = 0; i < (size/2); i++) + checksum -= le16toh(((const le16_t*)p)[i]); + + return checksum; +} + +static int write_boot_catalog(int fd, uint32_t load_block) { + struct el_torito_validation_entry ve = { + .header_indicator = 1, + .platform = 0xef, /* EFI */ + .key_bytes = {0x55, 0xaa}, + }; + + ve.checksum = htole16(calculate_validation_entry_checksum(&ve, sizeof(ve))); + + struct el_torito_initial_entry ie = { + .boot_indicator = 0x88, /* bootable */ + .boot_media_type = 0, /* no emul */ + /* From UEFI specification: + * > If the value of Sector Count is set to 0 or 1, EFI will assume the system partition + * > consumes the space from the beginning of the “no emulation” image to the end of the + * > CD-ROM. + */ + .sector_count = htole16(0), + .load_rba = htole32(load_block), + + }; + + struct el_torito_section_header sh = { + .header_indicator = 0x91, /* final header */ + .nentries = htole16(0), /* no more entries */ + }; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &ve, sizeof(ve)); + p = mempcpy(p, &ie, sizeof(ie)); + p = mempcpy(p, &sh, sizeof(sh)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_BOOT_CATALOG*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write El-Torito boot catalog: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write El-Torito boot catalog"); + + return 0; +} + +static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) { + int r; + + uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */ + + struct iso9660_directory_entry self = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 0, /* special value for self */ + }; + + r = time_to_iso9660_dir_datetime(usec, utc, &self.time); + if (r < 0) + return r; + + struct iso9660_directory_entry parent = { + .len = sizeof(struct iso9660_directory_entry), + .extent_loc_little = htole32(root_sector), + .extent_loc_big = htobe32(root_sector), + .data_len_little = htole32(dir_size), + .data_len_big = htobe32(dir_size), + .flags = 2, /* directory */ + .volume_seq_num_little = htole16(1), + .volume_seq_num_big = htobe16(1), + .ident_len = 1, + .ident[0] = 1, /* special value for parent */ + }; + + // TODO: we should probably add some text file explaining there is no content through ISO9660 + + r = time_to_iso9660_dir_datetime(usec, utc, &parent.time); + if (r < 0) + return r; + + uint8_t sector[ISO9660_BLOCK_SIZE] = {}; + uint8_t *p = sector; + p = mempcpy(p, &self, sizeof(self)); + p = mempcpy(p, &parent, sizeof(parent)); + assert((size_t) (p - sector) <= sizeof(sector)); + + ssize_t s = pwrite(fd, §or, sizeof(sector), ISO9660_ROOT_DIRECTORY*ISO9660_BLOCK_SIZE); + if (s < 0) + return log_error_errno(errno, "Failed to write ISO9660 root directory: %m"); + if (s != sizeof(sector)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to fully write ISO9660 root directory"); + + return 0; +} + +static int write_eltorito(int fd, usec_t usec, bool utc, uint32_t load_block, const char *system_id, const char *volume_id, const char *publisher_id) { + int r; + + r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id); + if (r < 0) + return r; + + r = write_eltorito_descriptor(fd, ISO9660_BOOT_CATALOG); + if (r < 0) + return r; + + r = write_terminal_descriptor(fd); + if (r < 0) + return r; + + r = write_boot_catalog(fd, load_block); + if (r < 0) + return r; + + r = write_directories(fd, usec, utc, ISO9660_ROOT_DIRECTORY); + if (r < 0) + return r; + + return 0; +} + +static int context_verify_eltorito_overlap(Context *context) { + /* before writing the partition table, we check if we have collision with ISO9660 */ + assert(context); + + if (!arg_eltorito) + return 0; + + /* Check how many GPT partition entries can be stored. */ + size_t nents = fdisk_get_npartitions(context->fdisk_context); + /* The GPT contains + * - 1 unused block (protective MBR) + * - GPT header + * - N entries of 128 bytes each. + */ + size_t first_free_offset = 2*context->sector_size + round_up_size(nents*128, context->sector_size); + + if (first_free_offset > ISO9660_START*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The partition table is overlapping with the El Torito boot catalog."); + + /* The first lba is the first block where a partition could exist. Even if there is no partition + * there, we should still not overlap with it since a partition could be added later. + * It is unexpected for tools to change the first lba in the GPT header. So this should be safe. + */ + if (fdisk_get_first_lba(context->fdisk_context) * context->sector_size < (ISO9660_START+ISO9660_SIZE)*ISO9660_BLOCK_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito is overlapping with the first partition block."); + + return 0; +} + +static int context_find_esp_offset(Context *context, uint64_t *ret) { + assert(ret); + + uint64_t esp_offset = UINT64_MAX; + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped || PARTITION_IS_FOREIGN(p)) + continue; + if (p->type.designator == PARTITION_ESP) { + esp_offset = p->offset; + break; + } + } + + if (esp_offset == UINT64_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "El Torito boot catalog requires an ESP."); + if (esp_offset / ISO9660_BLOCK_SIZE > UINT32_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset is farther than El Torito boot catalog can support."); + if (esp_offset % ISO9660_BLOCK_SIZE != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP offset not aligned on 2K blocks."); + + *ret = esp_offset; + return 0; +} + static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; int capable, r; @@ -7717,6 +8040,10 @@ static int context_write_partition_table(Context *context) { (void) context_notify(context, PROGRESS_WRITING_TABLE, /* object= */ NULL, UINT_MAX); + r = context_verify_eltorito_overlap(context); + if (r < 0) + return r; + r = fdisk_write_disklabel(context->fdisk_context); if (r < 0) return log_error_errno(r, "Failed to write partition table: %m"); @@ -7736,6 +8063,24 @@ static int context_write_partition_table(Context *context) { } else log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices."); + if (arg_eltorito) { + bool utc = true; + usec_t usec = parse_source_date_epoch(); + if (usec == USEC_INFINITY) { + usec = now(CLOCK_REALTIME); + utc = false; + } + + uint64_t esp_offset; + r = context_find_esp_offset(context, &esp_offset); + if (r < 0) + return r; + + r = write_eltorito(fdisk_get_devfd(context->fdisk_context), usec, utc, esp_offset / ISO9660_BLOCK_SIZE, arg_eltorito_system, arg_eltorito_volume, arg_eltorito_publisher); + if (r < 0) + return log_error_errno(r, "Failed to write El Torito boot catalog: %m"); + } + log_info("All done."); return 0; @@ -9205,6 +9550,14 @@ static int help(void) { " Write fstab configuration to the given path\n" " --generate-crypttab=PATH\n" " Write crypttab configuration to the given path\n" + "\n%3$sEl Torito boot catalog:%4$s\n" + " --el-torito=BOOL Whether to add a boot catalog to boot the ESP\n" + " --el-torito-system=STRING\n" + " Set the system identifier in the ISO9660 descriptor\n" + " --el-torito-volume=STRING\n" + " Set the volume identifier in the ISO9660 descriptor\n" + " --el-torito-publisher=STRING\n" + " Set the publisher identifier in the ISO9660 descriptor\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -9264,6 +9617,10 @@ static int parse_argv(int argc, char *argv[]) { ARG_GENERATE_CRYPTTAB, ARG_LIST_DEVICES, ARG_JOIN_SIGNATURE, + ARG_ELTORITO, + ARG_ELTORITO_SYSTEM, + ARG_ELTORITO_VOLUME, + ARG_ELTORITO_PUBLISHER, }; static const struct option options[] = { @@ -9314,6 +9671,10 @@ static int parse_argv(int argc, char *argv[]) { { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, { "join-signature", required_argument, NULL, ARG_JOIN_SIGNATURE }, + { "el-torito", required_argument, NULL, ARG_ELTORITO }, + { "el-torito-system", required_argument, NULL, ARG_ELTORITO_SYSTEM }, + { "el-torito-volume", required_argument, NULL, ARG_ELTORITO_VOLUME }, + { "el-torito-publisher", required_argument, NULL, ARG_ELTORITO_PUBLISHER }, {} }; @@ -9738,6 +10099,43 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_ELTORITO: + r = parse_boolean_argument("--el-torito=", optarg, &arg_eltorito); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_SYSTEM: + if (!iso9660_system_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_system, optarg); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_VOLUME: + if (!iso9660_volume_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_volume, optarg); + if (r < 0) + return r; + + break; + + case ARG_ELTORITO_PUBLISHER: + if (!iso9660_publisher_name_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", optarg); + + r = free_and_strdup_warn(&arg_eltorito_publisher, optarg); + if (r < 0) + return r; + + break; + case '?': return -EINVAL; @@ -9882,6 +10280,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + if (arg_eltorito && !IN_SET(arg_empty, EMPTY_REQUIRE, EMPTY_FORCE, EMPTY_CREATE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--el-torito=yes requires --empty= to be either require, force or create."); + return 1; }