repart: smaller iso9660 tweaks (#41477)

This commit is contained in:
Lennart Poettering
2026-04-04 10:12:39 +02:00
committed by GitHub
4 changed files with 109 additions and 47 deletions
-2
View File
@@ -1354,8 +1354,6 @@ Features:
* automatic boot assessment: add one more default success check that just waits
for a bit after boot, and blesses the boot if the system stayed up that long.
* systemd-repart: add support for generating ISO9660 images
* systemd-repart: in addition to the existing "factory reset" mode (which
simply empties existing partitions marked for that). add a mode where
partitions marked for it are entirely removed. Use case: remove secondary OS
+54 -16
View File
@@ -8,7 +8,7 @@
#include "string-util.h"
#include "time-util.h"
void no_iso9660_datetime(struct iso9660_datetime *ret) {
void iso9660_datetime_zero(struct iso9660_datetime *ret) {
assert(ret);
memcpy(ret->year, "0000", 4);
@@ -21,7 +21,27 @@ void no_iso9660_datetime(struct iso9660_datetime *ret) {
ret->zone = 0;
}
int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret) {
static int validate_tm(const struct tm *t) {
assert(t);
/* Safety checks on bounded fields of struct tm, ranges as per tm(3type). Mostly in place because
* ISO9660 date/time ranges and struct tm ranges differ. */
if (t->tm_mon < 0 || t->tm_mon > 11)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Month out of range.");
if (t->tm_mday < 1 || t->tm_mday > 31)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Day of month out of range.");
if (t->tm_hour < 0 || t->tm_hour > 23)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Hour out of range.");
if (t->tm_min < 0 || t->tm_min > 59)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Minute out of range.");
if (t->tm_sec < 0 || t->tm_sec > 60)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Seconds out of range.");
return 0;
}
int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret) {
struct tm t;
int r;
@@ -31,25 +51,33 @@ int time_to_iso9660_datetime(usec_t usec, bool utc, struct iso9660_datetime *ret
if (r < 0)
return r;
r = validate_tm(&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.");
long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */
if (offset < INT8_MIN || offset > INT8_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range.");
char buf[17];
assert_cc(sizeof(buf)-1 == offsetof(struct iso9660_datetime, zone));
/* 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);
ret->zone = offset;
return 0;
}
int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time *ret) {
int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret) {
struct tm t;
int r;
@@ -59,8 +87,16 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time
if (r < 0)
return r;
r = validate_tm(&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.");
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Year is incompatible with ISO9660.");
long offset = t.tm_gmtoff / (15*60); /* The time zone is encoded by 15 minutes increments */
if (offset < INT8_MIN || offset > INT8_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "GMT offset out of range.");
*ret = (struct iso9660_dir_time) {
.year = t.tm_year,
@@ -70,29 +106,31 @@ int time_to_iso9660_dir_datetime(usec_t usec, bool utc, struct iso9660_dir_time
.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),
.offset = offset,
};
return 0;
}
static bool valid_iso9660_string(const char *str, bool allow_a_chars) {
static bool iso9660_valid_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;
int iso9660_set_string(char target[], size_t len, const char *source, bool allow_a_chars) {
assert(target || len == 0);
if (source) {
if (!iso9660_valid_string(source, allow_a_chars))
return -EINVAL;
size_t slen = strlen(source);
if (slen > len)
return -EINVAL;
void *p = mempcpy(target, source, slen);
memset(p, ' ', len - slen);
memset(mempcpy(target, source, slen), ' ', len - slen);
} else
memset(target, ' ', len);
@@ -101,16 +139,16 @@ int set_iso9660_string(char target[], size_t len, const char *source, bool allow
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) &&
return iso9660_valid_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) &&
return iso9660_valid_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) &&
return iso9660_valid_string(name, /* allow_a_chars= */ true) &&
strlen(name) <= 128;
}
+8 -8
View File
@@ -127,14 +127,14 @@ struct _packed_ iso9660_primary_volume_descriptor {
uint8_t file_structure_version; /* 1 */
uint8_t unused_5;
char application_used[512];
char reserved[653];
uint8_t 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];
uint8_t reserved[2];
char id_string[24];
le16_t checksum;
uint8_t key_bytes[2];
@@ -158,13 +158,13 @@ struct _packed_ el_torito_section_header {
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);
void iso9660_datetime_zero(struct iso9660_datetime *ret);
int iso9660_datetime_from_usec(usec_t usec, bool utc, struct iso9660_datetime *ret);
int iso9660_dir_datetime_from_usec(usec_t usec, bool utc, struct iso9660_dir_time *ret);
int iso9660_set_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);
static inline void iso9660_set_const_string(char target[], size_t len, const char *source, bool allow_a_chars) {
assert_se(iso9660_set_string(target, len, source, allow_a_chars) == 0);
}
bool iso9660_volume_name_valid(const char *name);
+47 -21
View File
@@ -7741,6 +7741,8 @@ static int write_primary_descriptor(
const char *publisher_id) {
int r;
assert(fd >= 0);
struct iso9660_primary_volume_descriptor desc = {
.header = {
.type = 1,
@@ -7769,43 +7771,43 @@ static int write_primary_descriptor(
}
};
set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true);
iso9660_set_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);
r = iso9660_dir_datetime_from_usec(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);
r = iso9660_set_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);
r = iso9660_set_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);
iso9660_set_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);
r = iso9660_set_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);
iso9660_set_const_string(desc.data_preparer_identifier, sizeof(desc.data_preparer_identifier), NULL, /* allow_a_chars= */ true);
iso9660_set_const_string(desc.application_identifier, sizeof(desc.application_identifier), "SYSTEMD-REPART", /* allow_a_chars= */ true);
iso9660_set_const_string(desc.copyright_file_identifier, sizeof(desc.copyright_file_identifier), NULL, /* allow_a_chars= */ false);
iso9660_set_const_string(desc.abstract_file_identifier, sizeof(desc.abstract_file_identifier), NULL, /* allow_a_chars= */ false);
iso9660_set_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);
r = iso9660_datetime_from_usec(usec, utc, &desc.volume_creation_date);
if (r < 0)
return r;
r = time_to_iso9660_datetime(usec, utc, &desc.volume_modification_date);
r = iso9660_datetime_from_usec(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);
iso9660_datetime_zero(&desc.volume_expiration_date);
iso9660_datetime_zero(&desc.volume_effective_date);
ssize_t s = pwrite(fd, &desc, sizeof(desc), ISO9660_PRIMARY_DESCRIPTOR*ISO9660_BLOCK_SIZE);
if (s < 0)
@@ -7817,6 +7819,8 @@ static int write_primary_descriptor(
}
static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) {
assert(fd >= 0);
struct iso9660_eltorito_descriptor desc = {
.header = {
.type = 0,
@@ -7825,7 +7829,7 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) {
.boot_catalog_sector = htole32(catalog_sector),
};
set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true);
iso9660_set_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));
@@ -7839,6 +7843,8 @@ static int write_eltorito_descriptor(int fd, uint32_t catalog_sector) {
}
static int write_terminal_descriptor(int fd) {
assert(fd >= 0);
struct iso9660_terminal_descriptor desc = {
.header = {
.type = 255,
@@ -7846,7 +7852,7 @@ static int write_terminal_descriptor(int fd) {
},
};
set_iso9660_const_string(desc.header.identifier, sizeof(desc.header.identifier), "CD001", /* allow_a_chars= */ true);
iso9660_set_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)
@@ -7858,6 +7864,7 @@ static int write_terminal_descriptor(int fd) {
}
static uint16_t calculate_validation_entry_checksum(const void *p, size_t size) {
assert(p || size == 0);
assert(size % 2 == 0);
uint16_t checksum = 0;
@@ -7869,6 +7876,8 @@ static uint16_t calculate_validation_entry_checksum(const void *p, size_t size)
}
static int write_boot_catalog(int fd, uint32_t load_block) {
assert(fd >= 0);
struct el_torito_validation_entry ve = {
.header_indicator = 1,
.platform = 0xef, /* EFI */
@@ -7911,9 +7920,16 @@ static int write_boot_catalog(int fd, uint32_t load_block) {
return 0;
}
static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector) {
static int write_directories(
int fd,
usec_t usec,
bool utc,
uint32_t root_sector) {
int r;
assert(fd >= 0);
uint32_t dir_size = 2*sizeof(struct iso9660_directory_entry); /* 2 entries with ident size 1: . and .. */
struct iso9660_directory_entry self = {
@@ -7929,7 +7945,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector
.ident[0] = 0, /* special value for self */
};
r = time_to_iso9660_dir_datetime(usec, utc, &self.time);
r = iso9660_dir_datetime_from_usec(usec, utc, &self.time);
if (r < 0)
return r;
@@ -7948,7 +7964,7 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector
// 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);
r = iso9660_dir_datetime_from_usec(usec, utc, &parent.time);
if (r < 0)
return r;
@@ -7967,9 +7983,19 @@ static int write_directories(int fd, usec_t usec, bool utc, uint32_t root_sector
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) {
static int write_eltorito(
int fd,
usec_t usec,
bool utc,
uint32_t load_block, /* in iso9660 blocks */
const char *system_id,
const char *volume_id,
const char *publisher_id) {
int r;
assert(fd >= 0);
r = write_primary_descriptor(fd, ISO9660_ROOT_DIRECTORY, usec, utc, system_id, volume_id, publisher_id);
if (r < 0)
return r;