sysupdate: add new component "sysupdate"

This commit is contained in:
Lennart Poettering 2020-12-28 15:17:54 +01:00
parent 40f35786b0
commit 43cc7a3ef4
21 changed files with 4925 additions and 0 deletions

View file

@ -1644,6 +1644,18 @@ conf.set('DEFAULT_DNSSEC_MODE',
'DNSSEC_' + default_dnssec.underscorify().to_upper())
conf.set_quoted('DEFAULT_DNSSEC_MODE_STR', default_dnssec)
want_sysupdate = get_option('sysupdate')
if want_sysupdate != 'false'
have = (conf.get('HAVE_OPENSSL') == 1 and
conf.get('HAVE_LIBFDISK') == 1)
if want_sysupdate == 'true' and not have
error('sysupdate support was requested, but dependencies are not available')
endif
else
have = false
endif
conf.set10('ENABLE_SYSUPDATE', have)
want_importd = get_option('importd')
if want_importd != 'false'
have = (conf.get('HAVE_LIBCURL') == 1 and
@ -2006,6 +2018,7 @@ subdir('src/rpm')
subdir('src/shutdown')
subdir('src/sysext')
subdir('src/systemctl')
subdir('src/sysupdate')
subdir('src/timedate')
subdir('src/timesync')
subdir('src/tmpfiles')
@ -3074,6 +3087,22 @@ if conf.get('ENABLE_REPART') == 1
endif
endif
if conf.get('ENABLE_SYSUPDATE') == 1
exe = executable(
'systemd-sysupdate',
systemd_sysupdate_sources,
include_directories : includes,
link_with : [libshared],
dependencies : [threads,
libblkid,
libfdisk,
libopenssl],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
public_programs += exe
endif
if conf.get('ENABLE_VCONSOLE') == 1
executable(
'systemd-vconsole-setup',
@ -4117,6 +4146,7 @@ foreach tuple : [
['rfkill'],
['sysext'],
['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1],
['sysupdate'],
['sysusers'],
['timedated'],
['timesyncd'],

View file

@ -100,6 +100,8 @@ option('binfmt', type : 'boolean',
description : 'support for custom binary formats')
option('repart', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'install the systemd-repart tool')
option('sysupdate', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'install the systemd-sysupdate tool')
option('coredump', type : 'boolean',
description : 'install the coredump handler')
option('pstore', type : 'boolean',

22
src/sysupdate/meson.build Normal file
View file

@ -0,0 +1,22 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
systemd_sysupdate_sources = files('''
sysupdate-instance.c
sysupdate-instance.h
sysupdate-partition.c
sysupdate-partition.h
sysupdate-pattern.c
sysupdate-pattern.h
sysupdate-resource.c
sysupdate-resource.h
sysupdate-transfer.c
sysupdate-transfer.h
sysupdate-update-set.c
sysupdate-update-set.h
sysupdate-util.c
sysupdate-util.h
sysupdate-cache.c
sysupdate-cache.h
sysupdate.c
sysupdate.h
'''.split())

View file

@ -0,0 +1,88 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "memory-util.h"
#include "sysupdate-cache.h"
#define WEB_CACHE_ENTRIES_MAX 64U
#define WEB_CACHE_ITEM_SIZE_MAX (64U*1024U*1024U)
static WebCacheItem* web_cache_item_free(WebCacheItem *i) {
if (!i)
return NULL;
free(i->url);
return mfree(i);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(WebCacheItem*, web_cache_item_free);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(web_cache_hash_ops, char, string_hash_func, string_compare_func, WebCacheItem, web_cache_item_free);
int web_cache_add_item(
Hashmap **web_cache,
const char *url,
bool verified,
const void *data,
size_t size) {
_cleanup_(web_cache_item_freep) WebCacheItem *item = NULL;
_cleanup_free_ char *u = NULL;
int r;
assert(web_cache);
assert(url);
assert(data || size == 0);
if (size > WEB_CACHE_ITEM_SIZE_MAX)
return -E2BIG;
item = web_cache_get_item(*web_cache, url, verified);
if (item && memcmp_nn(item->data, item->size, data, size) == 0)
return 0;
if (hashmap_size(*web_cache) >= (size_t) (WEB_CACHE_ENTRIES_MAX + !!hashmap_get(*web_cache, url)))
return -ENOSPC;
r = hashmap_ensure_allocated(web_cache, &web_cache_hash_ops);
if (r < 0)
return r;
u = strdup(url);
if (!u)
return -ENOMEM;
item = malloc(offsetof(WebCacheItem, data) + size + 1);
if (!item)
return -ENOMEM;
*item = (WebCacheItem) {
.url = TAKE_PTR(u),
.size = size,
.verified = verified,
};
/* Just to be extra paranoid, let's NUL terminate the downloaded buffer */
*(uint8_t*) mempcpy(item->data, data, size) = 0;
web_cache_item_free(hashmap_remove(*web_cache, url));
r = hashmap_put(*web_cache, item->url, item);
if (r < 0)
return r;
TAKE_PTR(item);
return 1;
}
WebCacheItem* web_cache_get_item(Hashmap *web_cache, const char *url, bool verified) {
WebCacheItem *i;
i = hashmap_get(web_cache, url);
if (!i)
return NULL;
if (i->verified != verified)
return NULL;
return i;
}

View file

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "hashmap.h"
typedef struct WebCacheItem {
char *url;
bool verified;
size_t size;
uint8_t data[];
} WebCacheItem;
/* A simple in-memory cache for downloaded manifests. Very likely multiple transfers will use the same
* manifest URLs, hence let's make sure we only download them once within each sysupdate invocation. */
int web_cache_add_item(Hashmap **cache, const char *url, bool verified, const void *data, size_t size);
WebCacheItem* web_cache_get_item(Hashmap *cache, const char *url, bool verified);

View file

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/stat.h>
#include "sysupdate-instance.h"
void instance_metadata_destroy(InstanceMetadata *m) {
assert(m);
free(m->version);
}
int instance_new(
Resource *rr,
const char *path,
const InstanceMetadata *f,
Instance **ret) {
_cleanup_(instance_freep) Instance *i = NULL;
_cleanup_free_ char *p = NULL, *v = NULL;
assert(rr);
assert(path);
assert(f);
assert(f->version);
assert(ret);
p = strdup(path);
if (!p)
return log_oom();
v = strdup(f->version);
if (!v)
return log_oom();
i = new(Instance, 1);
if (!i)
return log_oom();
*i = (Instance) {
.resource = rr,
.metadata = *f,
.path = TAKE_PTR(p),
.partition_info = PARTITION_INFO_NULL,
};
i->metadata.version = TAKE_PTR(v);
*ret = TAKE_PTR(i);
return 0;
}
Instance *instance_free(Instance *i) {
if (!i)
return NULL;
instance_metadata_destroy(&i->metadata);
free(i->path);
partition_info_destroy(&i->partition_info);
return mfree(i);
}

View file

@ -0,0 +1,67 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-id128.h"
#include "fs-util.h"
#include "time-util.h"
typedef struct InstanceMetadata InstanceMetadata;
typedef struct Instance Instance;
#include "sysupdate-resource.h"
#include "sysupdate-partition.h"
struct InstanceMetadata {
/* Various bits of metadata for each instance, that is either derived from the filename/GPT label or
* from metadata of the file/partition itself */
char *version;
sd_id128_t partition_uuid;
bool partition_uuid_set;
uint64_t partition_flags; /* GPT partition flags */
bool partition_flags_set;
usec_t mtime;
mode_t mode;
uint64_t size; /* uncompressed size of the file */
uint64_t tries_done, tries_left; /* for boot assessment counters */
int no_auto;
int read_only;
int growfs;
uint8_t sha256sum[32]; /* SHA256 sum of the download (i.e. compressed) file */
bool sha256sum_set;
};
#define INSTANCE_METADATA_NULL \
{ \
.mtime = USEC_INFINITY, \
.mode = MODE_INVALID, \
.size = UINT64_MAX, \
.tries_done = UINT64_MAX, \
.tries_left = UINT64_MAX, \
.no_auto = -1, \
.read_only = -1, \
.growfs = -1, \
}
struct Instance {
/* A pointer back to the resource this belongs to */
Resource *resource;
/* Metadata of this version */
InstanceMetadata metadata;
/* Where we found the instance */
char *path;
PartitionInfo partition_info;
};
void instance_metadata_destroy(InstanceMetadata *m);
int instance_new(Resource *rr, const char *path, const InstanceMetadata *f, Instance **ret);
Instance *instance_free(Instance *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(Instance*, instance_free);

View file

@ -0,0 +1,379 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/file.h>
#include "alloc-util.h"
#include "extract-word.h"
#include "gpt.h"
#include "id128-util.h"
#include "parse-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "sysupdate-partition.h"
#include "util.h"
void partition_info_destroy(PartitionInfo *p) {
assert(p);
p->label = mfree(p->label);
p->device = mfree(p->device);
}
static int fdisk_partition_get_attrs_as_uint64(
struct fdisk_partition *pa,
uint64_t *ret) {
uint64_t flags = 0;
const char *a;
int r;
assert(pa);
assert(ret);
/* Retrieve current flags as uint64_t mask */
a = fdisk_partition_get_attrs(pa);
if (!a) {
*ret = 0;
return 0;
}
for (;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&a, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
if (streq(word, "RequiredPartition"))
flags |= GPT_FLAG_REQUIRED_PARTITION;
else if (streq(word, "NoBlockIOProtocol"))
flags |= GPT_FLAG_NO_BLOCK_IO_PROTOCOL;
else if (streq(word, "LegacyBIOSBootable"))
flags |= GPT_FLAG_LEGACY_BIOS_BOOTABLE;
else {
const char *e;
unsigned u;
/* Drop "GUID" prefix if specified */
e = startswith(word, "GUID:") ?: word;
if (safe_atou(e, &u) < 0) {
log_debug("Unknown partition flag '%s', ignoring.", word);
continue;
}
if (u >= sizeof(flags)*8) { /* partition flags on GPT are 64bit. Let's ignore any further
bits should libfdisk report them */
log_debug("Partition flag above bit 63 (%s), ignoring.", word);
continue;
}
flags |= UINT64_C(1) << u;
}
}
*ret = flags;
return 0;
}
static int fdisk_partition_set_attrs_as_uint64(
struct fdisk_partition *pa,
uint64_t flags) {
_cleanup_free_ char *attrs = NULL;
int r;
assert(pa);
for (unsigned i = 0; i < sizeof(flags) * 8; i++) {
if (!FLAGS_SET(flags, UINT64_C(1) << i))
continue;
r = strextendf_with_separator(&attrs, ",", "%u", i);
if (r < 0)
return r;
}
return fdisk_partition_set_attrs(pa, strempty(attrs));
}
int read_partition_info(
struct fdisk_context *c,
struct fdisk_table *t,
size_t i,
PartitionInfo *ret) {
_cleanup_free_ char *label_copy = NULL, *device = NULL;
const char *pts, *ids, *label;
struct fdisk_partition *p;
struct fdisk_parttype *pt;
uint64_t start, size, flags;
sd_id128_t ptid, id;
size_t partno;
int r;
assert(c);
assert(t);
assert(ret);
p = fdisk_table_get_partition(t, i);
if (!p)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
if (fdisk_partition_is_used(p) <= 0) {
*ret = (PartitionInfo) PARTITION_INFO_NULL;
return 0; /* not found! */
}
if (fdisk_partition_has_partno(p) <= 0 ||
fdisk_partition_has_start(p) <= 0 ||
fdisk_partition_has_size(p) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size.");
partno = fdisk_partition_get_partno(p);
start = fdisk_partition_get_start(p);
assert(start <= UINT64_MAX / 512U);
start *= 512U;
size = fdisk_partition_get_size(p);
assert(size <= UINT64_MAX / 512U);
size *= 512U;
label = fdisk_partition_get_name(p);
if (!label)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label.");
pt = fdisk_partition_get_type(p);
if (!pt)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition: %m");
pts = fdisk_parttype_get_string(pt);
if (!pts)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition as string: %m");
r = sd_id128_from_string(pts, &ptid);
if (r < 0)
return log_error_errno(r, "Failed to parse partition type UUID %s: %m", pts);
ids = fdisk_partition_get_uuid(p);
if (!ids)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a UUID.");
r = sd_id128_from_string(ids, &id);
if (r < 0)
return log_error_errno(r, "Failed to parse partition UUID %s: %m", ids);
r = fdisk_partition_get_attrs_as_uint64(p, &flags);
if (r < 0)
return log_error_errno(r, "Failed to get partition flags: %m");
r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device);
if (r != 0)
return log_error_errno(r, "Failed to get partition device name: %m");
label_copy = strdup(label);
if (!label_copy)
return log_oom();
*ret = (PartitionInfo) {
.partno = partno,
.start = start,
.size = size,
.flags = flags,
.type = ptid,
.uuid = id,
.label = TAKE_PTR(label_copy),
.device = TAKE_PTR(device),
.no_auto = FLAGS_SET(flags, GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(ptid),
.read_only = FLAGS_SET(flags, GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(ptid),
.growfs = FLAGS_SET(flags, GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(ptid),
};
return 1; /* found! */
}
int find_suitable_partition(
const char *device,
uint64_t space,
sd_id128_t *partition_type,
PartitionInfo *ret) {
_cleanup_(partition_info_destroy) PartitionInfo smallest = PARTITION_INFO_NULL;
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
size_t n_partitions;
int r;
assert(device);
assert(ret);
c = fdisk_new_context();
if (!c)
return log_oom();
r = fdisk_assign_device(c, device, /* readonly= */ true);
if (r < 0)
return log_error_errno(r, "Failed to open device '%s': %m", device);
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
r = fdisk_get_partitions(c, &t);
if (r < 0)
return log_error_errno(r, "Failed to acquire partition table: %m");
n_partitions = fdisk_table_get_nents(t);
for (size_t i = 0; i < n_partitions; i++) {
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
r = read_partition_info(c, t, i, &pinfo);
if (r < 0)
return r;
if (r == 0) /* not assigned */
continue;
/* Filter out non-matching partition types */
if (partition_type && !sd_id128_equal(pinfo.type, *partition_type))
continue;
if (!streq_ptr(pinfo.label, "_empty")) /* used */
continue;
if (space != UINT64_MAX && pinfo.size < space) /* too small */
continue;
if (smallest.partno != SIZE_MAX && smallest.size <= pinfo.size) /* already found smaller */
continue;
smallest = pinfo;
pinfo = (PartitionInfo) PARTITION_INFO_NULL;
}
if (smallest.partno == SIZE_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "No available partition of a suitable size found.");
*ret = smallest;
smallest = (PartitionInfo) PARTITION_INFO_NULL;
return 0;
}
int patch_partition(
const char *device,
const PartitionInfo *info,
PartitionChange change) {
_cleanup_(fdisk_unref_partitionp) struct fdisk_partition *pa = NULL;
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
bool tweak_no_auto, tweak_read_only, tweak_growfs;
int r, fd;
assert(device);
assert(info);
assert(change <= _PARTITION_CHANGE_MAX);
if (change == 0) /* Nothing to do */
return 0;
c = fdisk_new_context();
if (!c)
return log_oom();
r = fdisk_assign_device(c, device, /* readonly= */ false);
if (r < 0)
return log_error_errno(r, "Failed to open device '%s': %m", device);
assert_se((fd = fdisk_get_devfd(c)) >= 0);
/* Make sure udev doesn't read the device while we make changes (this lock is released automatically
* by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit
* unlock by us here anywhere.) */
if (flock(fd, LOCK_EX) < 0)
return log_error_errno(errno, "Failed to lock block device '%s': %m", device);
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
r = fdisk_get_partition(c, info->partno, &pa);
if (r < 0)
return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device);
if (change & PARTITION_LABEL) {
r = fdisk_partition_set_name(pa, info->label);
if (r < 0)
return log_error_errno(r, "Failed to update partition label: %m");
}
if (change & PARTITION_UUID) {
r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid));
if (r < 0)
return log_error_errno(r, "Failed to update partition UUID: %m");
}
/* Tweak the read-only flag, but only if supported by the partition type */
tweak_no_auto =
FLAGS_SET(change, PARTITION_NO_AUTO) &&
gpt_partition_type_knows_no_auto(info->type);
tweak_read_only =
FLAGS_SET(change, PARTITION_READ_ONLY) &&
gpt_partition_type_knows_read_only(info->type);
tweak_growfs =
FLAGS_SET(change, PARTITION_GROWFS) &&
gpt_partition_type_knows_growfs(info->type);
if (change & PARTITION_FLAGS) {
uint64_t flags;
/* Update the full flags parameter, and import the read-only flag into it */
flags = info->flags;
if (tweak_no_auto)
SET_FLAG(flags, GPT_FLAG_NO_AUTO, info->no_auto);
if (tweak_read_only)
SET_FLAG(flags, GPT_FLAG_READ_ONLY, info->read_only);
if (tweak_growfs)
SET_FLAG(flags, GPT_FLAG_GROWFS, info->growfs);
r = fdisk_partition_set_attrs_as_uint64(pa, flags);
if (r < 0)
return log_error_errno(r, "Failed to update partition flags: %m");
} else if (tweak_no_auto || tweak_read_only || tweak_growfs) {
uint64_t old_flags, new_flags;
/* So we aren't supposed to update the full flags parameter, but we are supposed to update
* the RO flag of it. */
r = fdisk_partition_get_attrs_as_uint64(pa, &old_flags);
if (r < 0)
return log_error_errno(r, "Failed to get old partition flags: %m");
new_flags = old_flags;
if (tweak_no_auto)
SET_FLAG(new_flags, GPT_FLAG_NO_AUTO, info->no_auto);
if (tweak_read_only)
SET_FLAG(new_flags, GPT_FLAG_READ_ONLY, info->read_only);
if (tweak_growfs)
SET_FLAG(new_flags, GPT_FLAG_GROWFS, info->growfs);
if (new_flags != old_flags) {
r = fdisk_partition_set_attrs_as_uint64(pa, new_flags);
if (r < 0)
return log_error_errno(r, "Failed to update partition flags: %m");
}
}
r = fdisk_set_partition(c, info->partno, pa);
if (r < 0)
return log_error_errno(r, "Failed to update partition: %m");
r = fdisk_write_disklabel(c);
if (r < 0)
return log_error_errno(r, "Failed to write updated partition table: %m");
return 0;
}

View file

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <sys/types.h>
#include "sd-id128.h"
#include "fdisk-util.h"
#include "macro.h"
typedef struct PartitionInfo PartitionInfo;
typedef enum PartitionChange {
PARTITION_FLAGS = 1 << 0,
PARTITION_NO_AUTO = 1 << 1,
PARTITION_READ_ONLY = 1 << 2,
PARTITION_GROWFS = 1 << 3,
PARTITION_UUID = 1 << 4,
PARTITION_LABEL = 1 << 5,
_PARTITION_CHANGE_MAX = (1 << 6) - 1, /* all of the above */
_PARTITION_CHANGE_INVALID = -EINVAL,
} PartitionChange;
struct PartitionInfo {
size_t partno;
uint64_t start, size;
uint64_t flags;
sd_id128_t type, uuid;
char *label;
char *device; /* Note that this might point to some non-existing path in case we operate on a loopback file */
bool no_auto:1;
bool read_only:1;
bool growfs:1;
};
#define PARTITION_INFO_NULL \
{ \
.partno = SIZE_MAX, \
.start = UINT64_MAX, \
.size = UINT64_MAX, \
}
void partition_info_destroy(PartitionInfo *p);
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
int find_suitable_partition(const char *device, uint64_t space, sd_id128_t *partition_type, PartitionInfo *ret);
int patch_partition(const char *device, const PartitionInfo *info, PartitionChange change);

View file

@ -0,0 +1,605 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "hexdecoct.h"
#include "list.h"
#include "parse-util.h"
#include "path-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "sysupdate-pattern.h"
#include "sysupdate-util.h"
typedef enum PatternElementType {
PATTERN_LITERAL,
PATTERN_VERSION,
PATTERN_PARTITION_UUID,
PATTERN_PARTITION_FLAGS,
PATTERN_MTIME,
PATTERN_MODE,
PATTERN_SIZE,
PATTERN_TRIES_DONE,
PATTERN_TRIES_LEFT,
PATTERN_NO_AUTO,
PATTERN_READ_ONLY,
PATTERN_GROWFS,
PATTERN_SHA256SUM,
_PATTERN_ELEMENT_TYPE_MAX,
_PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
} PatternElementType;
typedef struct PatternElement PatternElement;
struct PatternElement {
PatternElementType type;
LIST_FIELDS(PatternElement, elements);
char literal[];
};
static PatternElement *pattern_element_free_all(PatternElement *e) {
PatternElement *p;
while ((p = LIST_POP(elements, e)))
free(p);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
static PatternElementType pattern_element_type_from_char(char c) {
switch (c) {
case 'v':
return PATTERN_VERSION;
case 'u':
return PATTERN_PARTITION_UUID;
case 'f':
return PATTERN_PARTITION_FLAGS;
case 't':
return PATTERN_MTIME;
case 'm':
return PATTERN_MODE;
case 's':
return PATTERN_SIZE;
case 'd':
return PATTERN_TRIES_DONE;
case 'l':
return PATTERN_TRIES_LEFT;
case 'a':
return PATTERN_NO_AUTO;
case 'r':
return PATTERN_READ_ONLY;
case 'g':
return PATTERN_GROWFS;
case 'h':
return PATTERN_SHA256SUM;
default:
return _PATTERN_ELEMENT_TYPE_INVALID;
}
}
static bool valid_char(char x) {
/* Let's refuse control characters here, and let's reserve some characters typically used in pattern
* languages so that we can use them later, possibly. */
if ((unsigned) x < ' ' || x >= 127)
return false;
return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '/', '|');
}
static int pattern_split(
const char *pattern,
PatternElement **ret) {
_cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
bool at = false, last_literal = true;
PatternElement *last = NULL;
uint64_t mask_found = 0;
size_t l, k = 0;
assert(pattern);
l = strlen(pattern);
for (const char *e = pattern; *e != 0; e++) {
if (*e == '@') {
if (!at) {
at = true;
continue;
}
/* Two at signs in a sequence, write out one */
at = false;
} else if (at) {
PatternElementType t;
uint64_t bit;
t = pattern_element_type_from_char(*e);
if (t < 0)
return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
bit = UINT64_C(1) << t;
if (mask_found & bit)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
/* We insist that two pattern field markers are separated by some literal string that
* we can use to separate the fields when parsing. */
if (!last_literal)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
if (ret) {
PatternElement *z;
z = malloc(offsetof(PatternElement, literal));
if (!z)
return -ENOMEM;
z->type = t;
LIST_INSERT_AFTER(elements, first, last, z);
last = z;
}
mask_found |= bit;
last_literal = at = false;
continue;
}
if (!valid_char(*e))
return log_debug_errno(SYNTHETIC_ERRNO(EBADRQC), "Invalid character 0x%0x in pattern, refusing.", *e);
last_literal = true;
if (!ret)
continue;
if (!last || last->type != PATTERN_LITERAL) {
PatternElement *z;
z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
if (!z)
return -ENOMEM;
z->type = PATTERN_LITERAL;
k = 0;
LIST_INSERT_AFTER(elements, first, last, z);
last = z;
}
assert(last);
assert(last->type == PATTERN_LITERAL);
last->literal[k++] = *e;
}
if (at)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
if (ret)
*ret = TAKE_PTR(first);
return 0;
}
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
PatternElement *e;
const char *p;
int r;
assert(pattern);
assert(s);
r = pattern_split(pattern, &elements);
if (r < 0)
return r;
p = s;
LIST_FOREACH(elements, e, elements) {
_cleanup_free_ char *t = NULL;
const char *n;
if (e->type == PATTERN_LITERAL) {
const char *k;
/* Skip literal fields */
k = startswith(p, e->literal);
if (!k)
goto nope;
p = k;
continue;
}
if (e->elements_next) {
/* The next element must be literal, as we use it to determine where to split */
assert(e->elements_next->type == PATTERN_LITERAL);
n = strstr(p, e->elements_next->literal);
if (!n)
goto nope;
} else
/* End of the string */
assert_se(n = strchr(p, 0));
t = strndup(p, n - p);
if (!t)
return -ENOMEM;
switch (e->type) {
case PATTERN_VERSION:
if (!version_is_valid(t)) {
log_debug("Version string is not valid, refusing: %s", t);
goto nope;
}
assert(!found.version);
found.version = TAKE_PTR(t);
break;
case PATTERN_PARTITION_UUID: {
sd_id128_t id;
if (sd_id128_from_string(t, &id) < 0)
goto nope;
assert(!found.partition_uuid_set);
found.partition_uuid = id;
found.partition_uuid_set = true;
break;
}
case PATTERN_PARTITION_FLAGS: {
uint64_t f;
if (safe_atoux64(t, &f) < 0)
goto nope;
if (found.partition_flags_set && found.partition_flags != f)
goto nope;
assert(!found.partition_flags_set);
found.partition_flags = f;
found.partition_flags_set = true;
break;
}
case PATTERN_MTIME: {
uint64_t v;
if (safe_atou64(t, &v) < 0)
goto nope;
if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
goto nope;
if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
goto nope;
assert(found.mtime == USEC_INFINITY);
found.mtime = v;
break;
}
case PATTERN_MODE: {
mode_t m;
r = parse_mode(t, &m);
if (r < 0)
goto nope;
if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
goto nope;
assert(found.mode == MODE_INVALID);
found.mode = m;
break;
}
case PATTERN_SIZE: {
uint64_t u;
r = safe_atou64(t, &u);
if (r < 0)
goto nope;
if (u == UINT64_MAX)
goto nope;
assert(found.size == UINT64_MAX);
found.size = u;
break;
}
case PATTERN_TRIES_DONE: {
uint64_t u;
r = safe_atou64(t, &u);
if (r < 0)
goto nope;
if (u == UINT64_MAX)
goto nope;
assert(found.tries_done == UINT64_MAX);
found.tries_done = u;
break;
}
case PATTERN_TRIES_LEFT: {
uint64_t u;
r = safe_atou64(t, &u);
if (r < 0)
goto nope;
if (u == UINT64_MAX)
goto nope;
assert(found.tries_left == UINT64_MAX);
found.tries_left = u;
break;
}
case PATTERN_NO_AUTO:
r = parse_boolean(t);
if (r < 0)
goto nope;
assert(found.no_auto < 0);
found.no_auto = r;
break;
case PATTERN_READ_ONLY:
r = parse_boolean(t);
if (r < 0)
goto nope;
assert(found.read_only < 0);
found.read_only = r;
break;
case PATTERN_GROWFS:
r = parse_boolean(t);
if (r < 0)
goto nope;
assert(found.growfs < 0);
found.growfs = r;
break;
case PATTERN_SHA256SUM: {
_cleanup_free_ void *d = NULL;
size_t l;
if (strlen(t) != sizeof(found.sha256sum) * 2)
goto nope;
r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
if (r == -ENOMEM)
return r;
if (r < 0)
goto nope;
assert(!found.sha256sum_set);
assert(l == sizeof(found.sha256sum));
memcpy(found.sha256sum, d, l);
found.sha256sum_set = true;
break;
}
default:
assert_se("unexpected pattern element");
}
p = n;
}
if (ret) {
*ret = found;
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
}
return true;
nope:
if (ret)
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
return false;
}
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
char **p;
int r;
STRV_FOREACH(p, patterns) {
r = pattern_match(*p, s, &found);
if (r < 0)
return r;
if (r > 0) {
if (ret) {
*ret = found;
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
}
return true;
}
}
if (ret)
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
return false;
}
int pattern_valid(const char *pattern) {
int r;
r = pattern_split(pattern, NULL);
if (r == -EINVAL)
return false;
if (r < 0)
return r;
return true;
}
int pattern_format(
const char *pattern,
const InstanceMetadata *fields,
char **ret) {
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
_cleanup_free_ char *j = NULL;
PatternElement *e;
int r;
assert(pattern);
assert(fields);
assert(ret);
r = pattern_split(pattern, &elements);
if (r < 0)
return r;
LIST_FOREACH(elements, e, elements) {
switch (e->type) {
case PATTERN_LITERAL:
if (!strextend(&j, e->literal))
return -ENOMEM;
break;
case PATTERN_VERSION:
if (!fields->version)
return -ENXIO;
if (!strextend(&j, fields->version))
return -ENOMEM;
break;
case PATTERN_PARTITION_UUID: {
char formatted[SD_ID128_STRING_MAX];
if (!fields->partition_uuid_set)
return -ENXIO;
if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
return -ENOMEM;
break;
}
case PATTERN_PARTITION_FLAGS:
if (!fields->partition_flags_set)
return -ENXIO;
r = strextendf(&j, "%" PRIx64, fields->partition_flags);
if (r < 0)
return r;
break;
case PATTERN_MTIME:
if (fields->mtime == USEC_INFINITY)
return -ENXIO;
r = strextendf(&j, "%" PRIu64, fields->mtime);
if (r < 0)
return r;
break;
case PATTERN_MODE:
if (fields->mode == MODE_INVALID)
return -ENXIO;
r = strextendf(&j, "%03o", fields->mode);
if (r < 0)
return r;
break;
case PATTERN_SIZE:
if (fields->size == UINT64_MAX)
return -ENXIO;
r = strextendf(&j, "%" PRIu64, fields->size);
if (r < 0)
return r;
break;
case PATTERN_TRIES_DONE:
if (fields->tries_done == UINT64_MAX)
return -ENXIO;
r = strextendf(&j, "%" PRIu64, fields->tries_done);
if (r < 0)
return r;
break;
case PATTERN_TRIES_LEFT:
if (fields->tries_left == UINT64_MAX)
return -ENXIO;
r = strextendf(&j, "%" PRIu64, fields->tries_left);
if (r < 0)
return r;
break;
case PATTERN_NO_AUTO:
if (fields->no_auto < 0)
return -ENXIO;
if (!strextend(&j, one_zero(fields->no_auto)))
return -ENOMEM;
break;
case PATTERN_READ_ONLY:
if (fields->read_only < 0)
return -ENXIO;
if (!strextend(&j, one_zero(fields->read_only)))
return -ENOMEM;
break;
case PATTERN_GROWFS:
if (fields->growfs < 0)
return -ENXIO;
if (!strextend(&j, one_zero(fields->growfs)))
return -ENOMEM;
break;
case PATTERN_SHA256SUM: {
_cleanup_free_ char *h = NULL;
if (!fields->sha256sum_set)
return -ENXIO;
h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
if (!h)
return -ENOMEM;
if (!strextend(&j, h))
return -ENOMEM;
break;
}
default:
assert_not_reached();
}
}
*ret = TAKE_PTR(j);
return 0;
}

View file

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "sysupdate-instance.h"
#include "time-util.h"
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret);
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret);
int pattern_valid(const char *pattern);
int pattern_format(const char *pattern, const InstanceMetadata *fields, char **ret);

View file

@ -0,0 +1,633 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "blockdev-util.h"
#include "chase-symlinks.h"
#include "dirent-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "glyph-util.h"
#include "gpt.h"
#include "hexdecoct.h"
#include "import-util.h"
#include "macro.h"
#include "process-util.h"
#include "sort-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "sysupdate-cache.h"
#include "sysupdate-instance.h"
#include "sysupdate-pattern.h"
#include "sysupdate-resource.h"
#include "sysupdate.h"
#include "utf8.h"
void resource_destroy(Resource *rr) {
assert(rr);
free(rr->path);
strv_free(rr->patterns);
for (size_t i = 0; i < rr->n_instances; i++)
instance_free(rr->instances[i]);
free(rr->instances);
}
static int resource_add_instance(
Resource *rr,
const char *path,
const InstanceMetadata *f,
Instance **ret) {
Instance *i;
int r;
assert(rr);
assert(path);
assert(f);
assert(f->version);
if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1))
return log_oom();
r = instance_new(rr, path, f, &i);
if (r < 0)
return r;
rr->instances[rr->n_instances++] = i;
if (ret)
*ret = i;
return 0;
}
static int resource_load_from_directory(
Resource *rr,
mode_t m) {
_cleanup_(closedirp) DIR *d = NULL;
int r;
assert(rr);
assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
assert(IN_SET(m, S_IFREG, S_IFDIR));
d = opendir(rr->path);
if (!d) {
if (errno == ENOENT) {
log_debug("Directory %s does not exist, not loading any resources.", rr->path);
return 0;
}
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
}
for (;;) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_free_ char *joined = NULL;
Instance *instance;
struct dirent *de;
struct stat st;
errno = 0;
de = readdir_no_dot(d);
if (!de) {
if (errno != 0)
return log_error_errno(errno, "Failed to read directory '%s': %m", rr->path);
break;
}
switch (de->d_type) {
case DT_UNKNOWN:
break;
case DT_DIR:
if (m != S_IFDIR)
continue;
break;
case DT_REG:
if (m != S_IFREG)
continue;
break;
default:
continue;
}
if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT) < 0) {
if (errno == ENOENT) /* Gone by now? */
continue;
return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name);
}
if ((st.st_mode & S_IFMT) != m)
continue;
r = pattern_match_many(rr->patterns, de->d_name, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r == 0)
continue;
joined = path_join(rr->path, de->d_name);
if (!joined)
return log_oom();
r = resource_add_instance(rr, joined, &extracted_fields, &instance);
if (r < 0)
return r;
/* Inherit these from the source, if not explicitly overwritten */
if (instance->metadata.mtime == USEC_INFINITY)
instance->metadata.mtime = timespec_load(&st.st_mtim) ?: USEC_INFINITY;
if (instance->metadata.mode == MODE_INVALID)
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
}
return 0;
}
static int resource_load_from_blockdev(Resource *rr) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
size_t n_partitions;
int r;
assert(rr);
c = fdisk_new_context();
if (!c)
return log_oom();
r = fdisk_assign_device(c, rr->path, /* readonly= */ true);
if (r < 0)
return log_error_errno(r, "Failed to open device '%s': %m", rr->path);
if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path);
r = fdisk_get_partitions(c, &t);
if (r < 0)
return log_error_errno(r, "Failed to acquire partition table: %m");
n_partitions = fdisk_table_get_nents(t);
for (size_t i = 0; i < n_partitions; i++) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
Instance *instance;
r = read_partition_info(c, t, i, &pinfo);
if (r < 0)
return r;
if (r == 0) /* not assigned */
continue;
/* Check if partition type matches */
if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type))
continue;
/* A label of "_empty" means "not used so far" for us */
if (streq_ptr(pinfo.label, "_empty")) {
rr->n_empty++;
continue;
}
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r == 0)
continue;
r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
if (r < 0)
return r;
instance->partition_info = pinfo;
pinfo = (PartitionInfo) PARTITION_INFO_NULL;
/* Inherit data from source if not configured explicitly */
if (!instance->metadata.partition_uuid_set) {
instance->metadata.partition_uuid = instance->partition_info.uuid;
instance->metadata.partition_uuid_set = true;
}
if (!instance->metadata.partition_flags_set) {
instance->metadata.partition_flags = instance->partition_info.flags;
instance->metadata.partition_flags_set = true;
}
if (instance->metadata.read_only < 0)
instance->metadata.read_only = instance->partition_info.read_only;
}
return 0;
}
static int download_manifest(
const char *url,
bool verify_signature,
char **ret_buffer,
size_t *ret_size) {
_cleanup_free_ char *buffer = NULL, *suffixed_url = NULL;
_cleanup_(close_pairp) int pfd[2] = { -1, -1 };
_cleanup_fclose_ FILE *manifest = NULL;
size_t size = 0;
pid_t pid;
int r;
assert(url);
assert(ret_buffer);
assert(ret_size);
/* Download a SHA256SUMS file as manifest */
r = import_url_append_component(url, "SHA256SUMS", &suffixed_url);
if (r < 0)
return log_error_errno(r, "Failed to append SHA256SUMS to URL: %m");
if (pipe2(pfd, O_CLOEXEC) < 0)
return log_error_errno(errno, "Failed to allocate pipe: %m");
log_info("%s Acquiring manifest file %s…", special_glyph(SPECIAL_GLYPH_DOWNLOAD), suffixed_url);
r = safe_fork("(sd-pull)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
const char *cmdline[] = {
"systemd-pull",
"raw",
"--direct", /* just download the specified URL, don't download anything else */
"--verify", verify_signature ? "signature" : "no", /* verify the manifest file */
suffixed_url,
"-", /* write to stdout */
NULL
};
pfd[0] = safe_close(pfd[0]);
r = rearrange_stdio(-1, pfd[1], STDERR_FILENO);
if (r < 0) {
log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
_exit(EXIT_FAILURE);
}
(void) unsetenv("NOTIFY_SOCKET");
execv(pull_binary_path(), (char *const*) cmdline);
log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path());
_exit(EXIT_FAILURE);
};
pfd[1] = safe_close(pfd[1]);
/* We'll first load the entire manifest into memory before parsing it. That's because the
* systemd-pull tool can validate the download only after its completion, but still pass the data to
* us as it runs. We thus need to check the return value of the process *before* parsing, to be
* reasonably safe. */
manifest = fdopen(pfd[0], "r");
if (!manifest)
return log_error_errno(errno, "Failed allocate FILE object for manifest file: %m");
TAKE_FD(pfd[0]);
r = read_full_stream(manifest, &buffer, &size);
if (r < 0)
return log_error_errno(r, "Failed to read manifest file from child: %m");
manifest = safe_fclose(manifest);
r = wait_for_terminate_and_check("(sd-pull)", pid, WAIT_LOG);
if (r < 0)
return r;
if (r != 0)
return -EPROTO;
*ret_buffer = TAKE_PTR(buffer);
*ret_size = size;
return 0;
}
static int resource_load_from_web(
Resource *rr,
bool verify,
Hashmap **web_cache) {
size_t manifest_size = 0, left = 0;
_cleanup_free_ char *buf = NULL;
const char *manifest, *p;
size_t line_nr = 1;
WebCacheItem *ci;
int r;
assert(rr);
ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL;
if (ci) {
log_debug("Manifest web cache hit for %s.", rr->path);
manifest = (char*) ci->data;
manifest_size = ci->size;
} else {
log_debug("Manifest web cache miss for %s.", rr->path);
r = download_manifest(rr->path, verify, &buf, &manifest_size);
if (r < 0)
return r;
manifest = buf;
}
if (memchr(manifest, 0, manifest_size))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing.");
if (!utf8_is_valid_n(manifest, manifest_size))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing.");
p = manifest;
left = manifest_size;
while (left > 0) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_free_ char *fn = NULL;
_cleanup_free_ void *h = NULL;
Instance *instance;
const char *e;
size_t hlen;
/* 64 character hash + separator + filename + newline */
if (left < 67)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr);
if (p[0] == '\\')
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
r = unhexmem(p, 64, &h, &hlen);
if (r < 0)
return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
p += 64, left -= 64;
if (*p != ' ')
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr);
p++, left--;
if (!IN_SET(*p, '*', ' '))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr);
p++, left--;
e = memchr(p, '\n', left);
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr);
if (e == p)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr);
fn = strndup(p, e - p);
if (!fn)
return log_oom();
if (!filename_is_valid(fn))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr);
if (string_has_cc(fn, NULL))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr);
r = pattern_match_many(rr->patterns, fn, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r > 0) {
_cleanup_free_ char *path = NULL;
r = import_url_append_component(rr->path, fn, &path);
if (r < 0)
return log_error_errno(r, "Failed to build instance URL: %m");
r = resource_add_instance(rr, path, &extracted_fields, &instance);
if (r < 0)
return r;
assert(hlen == sizeof(instance->metadata.sha256sum));
if (instance->metadata.sha256sum_set) {
if (memcmp(instance->metadata.sha256sum, h, hlen) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr);
} else {
memcpy(instance->metadata.sha256sum, h, hlen);
instance->metadata.sha256sum_set = true;
}
}
left -= (e - p) + 1;
p = e + 1;
line_nr++;
}
if (!ci && web_cache) {
r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size);
if (r < 0)
log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path);
else
log_debug("Added manifest '%s' to cache.", rr->path);
}
return 0;
}
static int instance_cmp(Instance *const*a, Instance *const*b) {
int r;
assert(a);
assert(b);
assert(*a);
assert(*b);
assert((*a)->metadata.version);
assert((*b)->metadata.version);
/* Newest version at the beginning */
r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
if (r != 0)
return -r;
/* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all,
* and since we allow multiple matching patterns not even in directories they are unique). Hence
* let's order by path as secondary ordering key. */
return path_compare((*a)->path, (*b)->path);
}
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
int r;
assert(rr);
switch (rr->type) {
case RESOURCE_TAR:
case RESOURCE_REGULAR_FILE:
r = resource_load_from_directory(rr, S_IFREG);
break;
case RESOURCE_DIRECTORY:
case RESOURCE_SUBVOLUME:
r = resource_load_from_directory(rr, S_IFDIR);
break;
case RESOURCE_PARTITION:
r = resource_load_from_blockdev(rr);
break;
case RESOURCE_URL_FILE:
case RESOURCE_URL_TAR:
r = resource_load_from_web(rr, verify, web_cache);
break;
default:
assert_not_reached();
}
if (r < 0)
return r;
typesafe_qsort(rr->instances, rr->n_instances, instance_cmp);
return 0;
}
Instance* resource_find_instance(Resource *rr, const char *version) {
Instance key = {
.metadata.version = (char*) version,
}, *k = &key;
return typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_cmp);
}
int resource_resolve_path(
Resource *rr,
const char *root,
const char *node) {
_cleanup_free_ char *p = NULL;
dev_t d;
int r;
assert(rr);
if (rr->path_auto) {
/* NB: we don't actually check the backing device of the root fs "/", but of "/usr", in order
* to support environments where the root fs is a tmpfs, and the OS itself placed exclusively
* in /usr/. */
if (rr->type != RESOURCE_PARTITION)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Automatic root path discovery only supported for partition resources.");
if (node) { /* If --image= is specified, directly use the loopback device */
r = free_and_strdup_warn(&rr->path, node);
if (r < 0)
return r;
return 0;
}
if (root)
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Block device is not allowed when using --root= mode.");
r = get_block_device_harder("/usr/", &d);
} else if (rr->type == RESOURCE_PARTITION) {
_cleanup_close_ int fd = -1, real_fd = -1;
_cleanup_free_ char *resolved = NULL;
struct stat st;
r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd);
if (r < 0)
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
if (fstat(fd, &st) < 0)
return log_error_errno(r, "Failed to stat '%s': %m", resolved);
if (S_ISBLK(st.st_mode) && root)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited.");
if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) {
/* Not a directory, hence no need to find backing block device for the path */
free_and_replace(rr->path, resolved);
return 0;
}
if (!S_ISDIR(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Target path '%s' does not refer to regular file, directory or block device, refusing.", rr->path);
if (node) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
r = free_and_strdup_warn(&rr->path, node);
if (r < 0)
return r;
return 0;
}
real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (real_fd < 0)
return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path);
r = get_block_device_harder_fd(fd, &d);
} else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) {
_cleanup_free_ char *resolved = NULL;
r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
if (r < 0)
return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
free_and_replace(rr->path, resolved);
return 0;
} else
return 0; /* Otherwise assume there's nothing to resolve */
if (r < 0)
return log_error_errno(r, "Failed to determine block device of file system: %m");
r = block_get_whole_disk(d, &d);
if (r < 0)
return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
r = device_path_make_canonical(S_IFBLK, d, &p);
if (r < 0)
return r;
if (rr->path)
log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path);
else
log_info("Automatically discovered root block device '%s'.", p);
free_and_replace(rr->path, p);
return 1;
}
static const char *resource_type_table[_RESOURCE_TYPE_MAX] = {
[RESOURCE_URL_FILE] = "url-file",
[RESOURCE_URL_TAR] = "url-tar",
[RESOURCE_TAR] = "tar",
[RESOURCE_PARTITION] = "partition",
[RESOURCE_REGULAR_FILE] = "regular-file",
[RESOURCE_DIRECTORY] = "directory",
[RESOURCE_SUBVOLUME] = "subvolume",
};
DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType);

View file

@ -0,0 +1,97 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-id128.h"
#include "hashmap.h"
#include "macro.h"
/* Forward declare this type so that the headers below can use it */
typedef struct Resource Resource;
#include "sysupdate-instance.h"
typedef enum ResourceType {
RESOURCE_URL_FILE,
RESOURCE_URL_TAR,
RESOURCE_TAR,
RESOURCE_PARTITION,
RESOURCE_REGULAR_FILE,
RESOURCE_DIRECTORY,
RESOURCE_SUBVOLUME,
_RESOURCE_TYPE_MAX,
_RESOURCE_TYPE_INVALID = -EINVAL,
} ResourceType;
static inline bool RESOURCE_IS_SOURCE(ResourceType t) {
return IN_SET(t,
RESOURCE_URL_FILE,
RESOURCE_URL_TAR,
RESOURCE_TAR,
RESOURCE_REGULAR_FILE,
RESOURCE_DIRECTORY,
RESOURCE_SUBVOLUME);
}
static inline bool RESOURCE_IS_TARGET(ResourceType t) {
return IN_SET(t,
RESOURCE_PARTITION,
RESOURCE_REGULAR_FILE,
RESOURCE_DIRECTORY,
RESOURCE_SUBVOLUME);
}
/* Returns true for all resources that deal with file system objects, i.e. where we operate on top of the
* file system layer, instead of below. */
static inline bool RESOURCE_IS_FILESYSTEM(ResourceType t) {
return IN_SET(t,
RESOURCE_TAR,
RESOURCE_REGULAR_FILE,
RESOURCE_DIRECTORY,
RESOURCE_SUBVOLUME);
}
static inline bool RESOURCE_IS_TAR(ResourceType t) {
return IN_SET(t,
RESOURCE_TAR,
RESOURCE_URL_TAR);
}
static inline bool RESOURCE_IS_URL(ResourceType t) {
return IN_SET(t,
RESOURCE_URL_TAR,
RESOURCE_URL_FILE);
}
struct Resource {
ResourceType type;
/* Where to look for instances, and what to match precisely */
char *path;
bool path_auto; /* automatically find root path (only available if target resource, not source resource) */
char **patterns;
sd_id128_t partition_type;
bool partition_type_set;
/* All instances of this resource we found */
Instance **instances;
size_t n_instances;
/* If this is a partition resource (RESOURCE_PARTITION), then how many partition slots are currently unassigned, that we can use */
size_t n_empty;
};
void resource_destroy(Resource *rr);
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache);
Instance* resource_find_instance(Resource *rr, const char *version);
int resource_resolve_path(Resource *rr, const char *root, const char *node);
ResourceType resource_type_from_string(const char *s) _pure_;
const char *resource_type_to_string(ResourceType t) _const_;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-id128.h"
/* Forward declare this type so that the headers below can use it */
typedef struct Transfer Transfer;
#include "sysupdate-partition.h"
#include "sysupdate-resource.h"
struct Transfer {
char *definition_path;
char *min_version;
char **protected_versions;
char *current_symlink;
bool verify;
Resource source, target;
uint64_t instances_max;
bool remove_temporary;
/* When creating a new partition/file, optionally override these attributes explicitly */
sd_id128_t partition_uuid;
bool partition_uuid_set;
uint64_t partition_flags;
bool partition_flags_set;
mode_t mode;
uint64_t tries_left, tries_done;
int no_auto;
int read_only;
int growfs;
/* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */
char *temporary_path;
char *final_path;
int install_read_only;
/* If we write to a partition in a partition table, the metrics of it */
PartitionInfo partition_info;
PartitionChange partition_change;
};
Transfer *transfer_new(void);
Transfer *transfer_free(Transfer *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_free);
int transfer_read_definition(Transfer *t, const char *path);
int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
int transfer_acquire_instance(Transfer *t, Instance *i);
int transfer_install_instance(Transfer *t, Instance *i, const char *root);

View file

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "glyph-util.h"
#include "string-util.h"
#include "sysupdate-update-set.h"
#include "terminal-util.h"
UpdateSet *update_set_free(UpdateSet *us) {
if (!us)
return NULL;
free(us->version);
free(us->instances); /* The objects referenced by this array are freed via resource_free(), not us */
return mfree(us);
}
int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b) {
assert(a);
assert(b);
assert(*a);
assert(*b);
assert((*a)->version);
assert((*b)->version);
/* Newest version at the beginning */
return -strverscmp_improved((*a)->version, (*b)->version);
}
const char *update_set_flags_to_color(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return ansi_highlight();
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return ansi_highlight_magenta();
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return ansi_highlight_green();
return NULL;
}
const char *update_set_flags_to_glyph(UpdateSetFlags flags) {
if (flags == 0 || (flags & UPDATE_OBSOLETE))
return special_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE);
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_PROTECTED))
return special_glyph(SPECIAL_GLYPH_WHITE_CIRCLE);
if ((flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_OBSOLETE)) == (UPDATE_AVAILABLE|UPDATE_NEWEST))
return special_glyph(SPECIAL_GLYPH_CIRCLE_ARROW);
return " ";
}

View file

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
typedef struct UpdateSet UpdateSet;
#include "sysupdate-instance.h"
typedef enum UpdateSetFlags {
UPDATE_NEWEST = 1 << 0,
UPDATE_AVAILABLE = 1 << 1,
UPDATE_INSTALLED = 1 << 2,
UPDATE_OBSOLETE = 1 << 3,
UPDATE_PROTECTED = 1 << 4,
} UpdateSetFlags;
struct UpdateSet {
UpdateSetFlags flags;
char *version;
Instance **instances;
size_t n_instances;
};
UpdateSet *update_set_free(UpdateSet *us);
int update_set_cmp(UpdateSet *const*a, UpdateSet *const*b);
const char *update_set_flags_to_color(UpdateSetFlags flags);
const char *update_set_flags_to_glyph(UpdateSetFlags flags);

View file

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "path-util.h"
#include "sysupdate-util.h"
bool version_is_valid(const char *s) {
if (isempty(s))
return false;
if (!filename_is_valid(s))
return false;
if (!in_charset(s, ALPHANUMERICAL ".,_-+"))
return false;
return true;
}

View file

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
bool version_is_valid(const char *s);

1412
src/sysupdate/sysupdate.c Normal file

File diff suppressed because it is too large Load diff

21
src/sysupdate/sysupdate.h Normal file
View file

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
extern bool arg_sync;
extern uint64_t arg_instances_max;
extern char *arg_root;
static inline const char* import_binary_path(void) {
return secure_getenv("SYSTEMD_IMPORT_PATH") ?: SYSTEMD_IMPORT_PATH;
}
static inline const char* import_fs_binary_path(void) {
return secure_getenv("SYSTEMD_IMPORT_FS_PATH") ?: SYSTEMD_IMPORT_FS_PATH;
}
static inline const char *pull_binary_path(void) {
return secure_getenv("SYSTEMD_PULL_PATH") ?: SYSTEMD_PULL_PATH;
}