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 ```
This commit is contained in:
committed by
Daan De Meyer
parent
ea79ac6f36
commit
365361f490
@@ -718,6 +718,59 @@
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--el-torito=<replaceable>BOOL</replaceable></option></term>
|
||||
|
||||
<listitem><para>Write a minimal ISO9660 header with El Torito boot catalog. That will
|
||||
boot the ESP on EFI firmware.</para>
|
||||
|
||||
<para>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.</para>
|
||||
|
||||
<para>The disk requires at least one partition with <varname>Type=esp</varname>. The first one will
|
||||
be the one referenced in the boot catalog.</para>
|
||||
|
||||
<para>This option is available only when creating a new partition table, that is when
|
||||
<option>--empty=</option> has value <literal>require</literal>, <literal>force</literal> or
|
||||
<literal>create</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--el-torito-system=<replaceable>STRING</replaceable></option></term>
|
||||
|
||||
<listitem><para>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.</para>
|
||||
|
||||
<para>The value is limited to 32 characters.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--el-torito-volume=<replaceable>STRING</replaceable></option></term>
|
||||
|
||||
<listitem><para>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.</para>
|
||||
|
||||
<para>The value is limited to 32 characters.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--el-torito-publisher=<replaceable>STRING</replaceable></option></term>
|
||||
|
||||
<listitem><para>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.</para>
|
||||
|
||||
<para>The value is limited to 128 characters.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user