In mkosi, I want to add a sysupdate verb to wrap systemd-sysupdate. The definitions will be picked up from mkosi.sysupdate/ and passed to systemd-sysupdate. I want users to be able to write transfer definitions that are independent of the output directory used by mkosi. To make this possible, it should be possible to specify the directory that transfer sources should be looked up in on the sysupdate command line. Let's allow this via a new --transfer-source= option. Additionally, transfer sources that want to take advantage of this feature should specify PathRelativeTo=directory to indicate the configured Path= is interpreted relative to the tranfer source directory specified on the CLI. This allows for the following transfer definition to be put in mkosi.sysupdate: """ [Transfer] ProtectVersion=%A [Source] Type=regular-file Path=/ PathRelativeTo=directory MatchPattern=ParticleOS_@v.usr-%a.@u.raw [Target] Type=partition Path=auto MatchPattern=ParticleOS_@v MatchPartitionType=usr PartitionFlags=0 ReadOnly=1 """
729 lines
28 KiB
C
729 lines
28 KiB
C
/* 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 "build-path.h"
|
|
#include "chase.h"
|
|
#include "device-util.h"
|
|
#include "devnum-util.h"
|
|
#include "dirent-util.h"
|
|
#include "env-util.h"
|
|
#include "fd-util.h"
|
|
#include "fileio.h"
|
|
#include "find-esp.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 "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_recursive(
|
|
Resource *rr,
|
|
DIR* d,
|
|
const char* relpath,
|
|
mode_t m) {
|
|
int r;
|
|
|
|
for (;;) {
|
|
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
|
_cleanup_free_ char *joined = NULL, *rel_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 (!IN_SET(m, S_IFDIR, S_IFREG))
|
|
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 (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
|
|
continue;
|
|
|
|
rel_joined = path_join(relpath, de->d_name);
|
|
if (!rel_joined)
|
|
return log_oom();
|
|
|
|
r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
|
|
if (r == PATTERN_MATCH_RETRY) {
|
|
_cleanup_closedir_ DIR *subdir = NULL;
|
|
|
|
subdir = xopendirat(dirfd(d), rel_joined, 0);
|
|
if (!subdir)
|
|
continue;
|
|
|
|
r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0)
|
|
continue;
|
|
}
|
|
else if (r < 0)
|
|
return log_error_errno(r, "Failed to match pattern: %m");
|
|
else if (r == PATTERN_MATCH_NO)
|
|
continue;
|
|
|
|
if (de->d_type == DT_DIR && m != S_IFDIR)
|
|
continue;
|
|
|
|
joined = path_join(rr->path, rel_joined);
|
|
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_directory(
|
|
Resource *rr,
|
|
mode_t m) {
|
|
_cleanup_closedir_ DIR *d = NULL;
|
|
|
|
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_errno(errno, "Directory %s does not exist, not loading any resources: %m", rr->path);
|
|
return 0;
|
|
}
|
|
|
|
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
|
|
}
|
|
|
|
return resource_load_from_directory_recursive(rr, d, NULL, m);
|
|
}
|
|
|
|
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);
|
|
|
|
r = fdisk_new_context_at(AT_FDCWD, rr->path, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to create fdisk context from '%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.uuid))
|
|
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 (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
|
|
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_pair_ int pfd[2] = EBADF_PAIR;
|
|
_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%s", special_glyph(SPECIAL_GLYPH_DOWNLOAD),
|
|
suffixed_url, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
r = safe_fork_full("(sd-pull)",
|
|
(int[]) { -EBADF, pfd[1], STDERR_FILENO },
|
|
NULL, 0,
|
|
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG,
|
|
&pid);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0) {
|
|
/* Child */
|
|
|
|
const char *cmdline[] = {
|
|
SYSTEMD_PULL_PATH,
|
|
"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
|
|
};
|
|
|
|
r = invoke_callout_binary(SYSTEMD_PULL_PATH, (char *const*) cmdline);
|
|
log_error_errno(r, "Failed to execute %s tool: %m", SYSTEMD_PULL_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_full(p, 64, /* secure = */ false, &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 == PATTERN_MATCH_YES) {
|
|
_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;
|
|
}
|
|
|
|
static int instance_version_match(Instance *const*a, Instance *const*b) {
|
|
assert(a);
|
|
assert(b);
|
|
assert(*a);
|
|
assert(*b);
|
|
assert((*a)->metadata.version);
|
|
assert((*b)->metadata.version);
|
|
|
|
/* List is sorted newest-to-oldest */
|
|
return -strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
|
|
}
|
|
|
|
Instance* resource_find_instance(Resource *rr, const char *version) {
|
|
Instance key = {
|
|
.metadata.version = (char*) version,
|
|
}, *k = &key;
|
|
|
|
Instance **found;
|
|
found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_version_match);
|
|
if (!found)
|
|
return NULL;
|
|
|
|
return *found;
|
|
}
|
|
|
|
int resource_resolve_path(
|
|
Resource *rr,
|
|
const char *root,
|
|
const char *relative_to_directory,
|
|
const char *node) {
|
|
|
|
_cleanup_free_ char *p = NULL;
|
|
dev_t d;
|
|
int r;
|
|
|
|
assert(rr);
|
|
|
|
if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_ESP, PATH_RELATIVE_TO_XBOOTLDR, PATH_RELATIVE_TO_BOOT) &&
|
|
!IN_SET(rr->type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY))
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Paths relative to %s are only allowed for regular-file or directory resources.",
|
|
path_relative_to_to_string(rr->path_relative_to));
|
|
|
|
if (rr->path_auto) {
|
|
struct stat orig_root_stats;
|
|
|
|
/* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
|
|
* the original root block device node is symlinked in /run/systemd/volatile-root. Let's
|
|
* follow that link here. If that doesn't exist, we check the backing device of "/usr". We
|
|
* don't actually check the backing device of the root fs "/", 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 = stat("/run/systemd/volatile-root", &orig_root_stats);
|
|
if (r < 0) {
|
|
if (errno == ENOENT) /* volatile-root not found */
|
|
r = get_block_device_harder("/usr/", &d);
|
|
else
|
|
return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");
|
|
} else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
|
|
else /* symlink was present and a block device */
|
|
d = orig_root_stats.st_rdev;
|
|
|
|
} else if (rr->type == RESOURCE_PARTITION) {
|
|
_cleanup_close_ int fd = -EBADF, real_fd = -EBADF;
|
|
_cleanup_free_ char *resolved = NULL;
|
|
struct stat st;
|
|
|
|
r = chase(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(errno, "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)) {
|
|
_cleanup_free_ char *resolved = NULL, *relative_to = NULL;
|
|
ChaseFlags chase_flags = CHASE_PREFIX_ROOT;
|
|
|
|
if (rr->path_relative_to == PATH_RELATIVE_TO_EXPLICIT) {
|
|
assert(relative_to_directory);
|
|
|
|
relative_to = strdup(relative_to_directory);
|
|
if (!relative_to)
|
|
return log_oom();
|
|
} else if (rr->path_relative_to == PATH_RELATIVE_TO_ROOT) {
|
|
relative_to = strdup(empty_to_root(root));
|
|
if (!relative_to)
|
|
return log_oom();
|
|
} else { /* boot, esp, or xbootldr */
|
|
r = 0;
|
|
if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR))
|
|
r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL);
|
|
if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP)
|
|
r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to resolve $BOOT: %m");
|
|
log_debug("Resolved $BOOT to '%s'", relative_to);
|
|
|
|
/* Since this partition is read from EFI, there should be no symlinks */
|
|
chase_flags |= CHASE_PROHIBIT_SYMLINKS;
|
|
}
|
|
|
|
r = chase(rr->path, relative_to, chase_flags, &resolved, NULL);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to resolve '%s' (relative to '%s'): %m", rr->path, relative_to);
|
|
|
|
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 = devname_from_devnum(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);
|
|
|
|
static const char *path_relative_to_table[_PATH_RELATIVE_TO_MAX] = {
|
|
[PATH_RELATIVE_TO_ROOT] = "root",
|
|
[PATH_RELATIVE_TO_ESP] = "esp",
|
|
[PATH_RELATIVE_TO_XBOOTLDR] = "xbootldr",
|
|
[PATH_RELATIVE_TO_BOOT] = "boot",
|
|
[PATH_RELATIVE_TO_EXPLICIT] = "explicit",
|
|
};
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(path_relative_to, PathRelativeTo);
|