mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-02-15 05:57:26 +03:00
Merge pull request #26941 from bluca/portable_version
portable: introduce SYSEXT_ fields to identify sysexts, and include more metadata in log messages via LogExtraFields=
This commit is contained in:
commit
a4d1d1f63d
@ -335,6 +335,45 @@ service data may be placed on the host file system. Use `StateDirectory=` in
|
||||
the unit files to enable such behaviour and add a local data directory to the
|
||||
services copied onto the host.
|
||||
|
||||
## Logging
|
||||
|
||||
Several fields are autotmatically added to log messages generated by a portable
|
||||
service (or about a portable service, e.g.: start/stop logs from systemd).
|
||||
The `PORTABLE=` field will refer to the name of the portable image where the unit
|
||||
was loaded from. In case extensions are used, additionally there will be a
|
||||
`PORTABLE_ROOT=` field, referring to the name of image used as the base layer
|
||||
(i.e.: `RootImage=` or `RootDirectory=`), and one `PORTABLE_EXTENSION=` field per
|
||||
each extension image used.
|
||||
|
||||
The `os-release` file from the portable image will be parsed and added as structured
|
||||
metadata to the journal log entries. The parsed fields will be the first ID field which
|
||||
is set from the set of `IMAGE_ID` and `ID` in this order of preference, and the first
|
||||
version field which is set from a set of `IMAGE_VERSION`, `VERSION_ID`, and `BUILD_ID`
|
||||
in this order of preference. The ID and version, if any, are concatenated with an
|
||||
underscore (`_`) as separator. If only either one is found, it will be used by itself.
|
||||
The field will be named `PORTABLE_NAME_AND_VERSION=`.
|
||||
|
||||
In case extensions are used, the same fields in the same order are, but prefixed by
|
||||
`SYSEXT_`, are parsed from each `extension-release` file, and are appended to the
|
||||
journal as log entries, using `PORTABLE_EXTENSION_NAME_AND_VERSION=` as the field
|
||||
name. The base layer's field will be named `PORTABLE_ROOT_NAME_AND_VERSION=` instead
|
||||
of `PORTABLE_NAME_AND_VERSION=` in this case.
|
||||
|
||||
For example, a portable service `app0` using two extensions `app0.raw` and
|
||||
`app1.raw` (with `SYSEXT_ID=app`, and `SYSEXT_VERSION_ID=` `0` and `1` in their
|
||||
respective extension-releases), and a base layer `base.raw` (with `VERSION_ID=10` and
|
||||
`ID=debian` in `os-release`), will create log entries with the following fields:
|
||||
|
||||
```
|
||||
PORTABLE=app0.raw
|
||||
PORTABLE_ROOT=base.raw
|
||||
PORTABLE_ROOT_NAME_AND_VERSION=debian_10
|
||||
PORTABLE_EXTENSION=app0.raw
|
||||
PORTABLE_EXTENSION_NAME_AND_VERSION=app_0
|
||||
PORTABLE_EXTENSION=app1.raw
|
||||
PORTABLE_EXTENSION_NAME_AND_VERSION=app_1
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
[`portablectl(1)`](https://www.freedesktop.org/software/systemd/man/portablectl.html)<br>
|
||||
|
@ -111,6 +111,11 @@
|
||||
<varname>VERSION_ID=</varname> exists and matches. This ensures ABI/API compatibility between the
|
||||
layers and prevents merging of an incompatible image in an overlay.</para>
|
||||
|
||||
<para>In order to identify the extension image itself, the same fields defined below can be added to the
|
||||
<filename>extension-release</filename> file with a <varname>SYSEXT_</varname> prefix (to disambiguate
|
||||
from fields used to match on the base image). E.g.: <varname>SYSEXT_ID=myext</varname>,
|
||||
<varname>SYSEXT_VERSION_ID=1.2.3</varname>.</para>
|
||||
|
||||
<para>In the <filename>extension-release.<replaceable>IMAGE</replaceable></filename> filename, the
|
||||
<replaceable>IMAGE</replaceable> part must exactly match the file name of the containing image with the
|
||||
suffix removed. In case it is not possible to guarantee that an image file name is stable and doesn't
|
||||
|
@ -478,6 +478,7 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
|
||||
int r;
|
||||
|
||||
assert(f || fname);
|
||||
assert(ret);
|
||||
|
||||
r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
|
||||
if (r < 0)
|
||||
@ -487,6 +488,19 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
r = fdopen_independent(fd, "re", &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return load_env_file_pairs(f, fname, ret);
|
||||
}
|
||||
|
||||
static int merge_env_file_push(
|
||||
const char *filename, unsigned line,
|
||||
const char *key, char *value,
|
||||
|
@ -14,6 +14,7 @@ int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
|
||||
#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
|
||||
int load_env_file(FILE *f, const char *fname, char ***ret);
|
||||
int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
|
||||
int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
|
||||
|
||||
int merge_env_file(char ***env, FILE *f, const char *fname);
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "extract-word.h"
|
||||
#include "fileio.h"
|
||||
@ -63,6 +64,16 @@ char* strv_find_startswith(char * const *l, const char *name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* strv_find_first_field(char * const *needles, char * const *haystack) {
|
||||
STRV_FOREACH(k, needles) {
|
||||
char *value = strv_env_pairs_get((char **)haystack, *k);
|
||||
if (value)
|
||||
return value;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char** strv_free(char **l) {
|
||||
STRV_FOREACH(k, l)
|
||||
free(*k);
|
||||
|
@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_;
|
||||
char* strv_find_case(char * const *l, const char *name) _pure_;
|
||||
char* strv_find_prefix(char * const *l, const char *name) _pure_;
|
||||
char* strv_find_startswith(char * const *l, const char *name) _pure_;
|
||||
/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
|
||||
* of the first key from the first vector that is found in the second vector. */
|
||||
char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
|
||||
|
||||
#define strv_contains(l, s) (!!strv_find((l), (s)))
|
||||
#define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
|
||||
|
@ -566,18 +566,13 @@ static int extract_image_and_extensions(
|
||||
* extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
|
||||
* the units are started. Also, collect valid portable prefixes if caller requested that. */
|
||||
if (validate_sysext || ret_valid_prefixes) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *prefixes = NULL;
|
||||
|
||||
r = take_fdopen_unlocked(&os_release->fd, "r", &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = parse_env_file(f, os_release->name,
|
||||
"ID", &id,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"PORTABLE_PREFIXES", &prefixes);
|
||||
r = parse_env_file_fd(os_release->fd, os_release->name,
|
||||
"ID", &id,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"PORTABLE_PREFIXES", &prefixes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (isempty(id))
|
||||
@ -594,7 +589,6 @@ static int extract_image_and_extensions(
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
|
||||
_cleanup_strv_free_ char **extension_release = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
const char *e;
|
||||
|
||||
r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, relax_extension_release_check, matches, &extension_release_meta, &extra_unit_files, error);
|
||||
@ -608,12 +602,7 @@ static int extract_image_and_extensions(
|
||||
if (!validate_sysext && !ret_valid_prefixes && !ret_extension_releases)
|
||||
continue;
|
||||
|
||||
/* We need to keep the fd valid, to return the PortableMetadata to the caller. */
|
||||
r = fdopen_independent(extension_release_meta->fd, "re", &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = load_env_file_pairs(f, extension_release_meta->name, &extension_release);
|
||||
r = load_env_file_pairs_fd(extension_release_meta->fd, extension_release_meta->name, &extension_release);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -951,11 +940,67 @@ static int make_marker_text(const char *image_path, OrderedHashmap *extension_im
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_release_log_fields(
|
||||
char **text,
|
||||
const PortableMetadata *release,
|
||||
ImageClass type,
|
||||
const char *field_name) {
|
||||
|
||||
static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
|
||||
[IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
|
||||
[IMAGE_EXTENSION] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
|
||||
};
|
||||
static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
|
||||
[IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
|
||||
[IMAGE_EXTENSION] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
|
||||
};
|
||||
_cleanup_strv_free_ char **fields = NULL;
|
||||
const char *id = NULL, *version = NULL;
|
||||
int r;
|
||||
|
||||
assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_EXTENSION));
|
||||
assert(!strv_isempty((char *const *)field_ids[type]));
|
||||
assert(!strv_isempty((char *const *)field_versions[type]));
|
||||
assert(field_name);
|
||||
assert(text);
|
||||
|
||||
if (!release)
|
||||
return 0; /* Nothing to do. */
|
||||
|
||||
r = load_env_file_pairs_fd(release->fd, release->name, &fields);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse '%s': %m", release->name);
|
||||
|
||||
/* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */
|
||||
id = strv_find_first_field((char *const *)field_ids[type], fields);
|
||||
|
||||
/* Then the version, same logic, prefer the more specific one */
|
||||
version = strv_find_first_field((char *const *)field_versions[type], fields);
|
||||
|
||||
/* If there's no valid version to be found, simply omit it. */
|
||||
if (!id && !version)
|
||||
return 0;
|
||||
|
||||
if (!strextend(text,
|
||||
"LogExtraFields=",
|
||||
field_name,
|
||||
"=",
|
||||
strempty(id),
|
||||
id && version ? "_" : "",
|
||||
strempty(version),
|
||||
"\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int install_chroot_dropin(
|
||||
const char *image_path,
|
||||
ImageType type,
|
||||
OrderedHashmap *extension_images,
|
||||
OrderedHashmap *extension_releases,
|
||||
const PortableMetadata *m,
|
||||
const PortableMetadata *os_release,
|
||||
const char *dropin_dir,
|
||||
PortableFlags flags,
|
||||
char **ret_dropin,
|
||||
@ -1005,19 +1050,67 @@ static int install_chroot_dropin(
|
||||
"LogExtraFields=PORTABLE=", base_name, "\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
/* If we have a single image then PORTABLE= will point to it, so we add
|
||||
* PORTABLE_NAME_AND_VERSION= with the os-release fields and we are done. But if we have
|
||||
* extensions, PORTABLE= will point to the image where the current unit was found in. So we
|
||||
* also list PORTABLE_ROOT= and PORTABLE_ROOT_NAME_AND_VERSION= for the base image, and
|
||||
* PORTABLE_EXTENSION= and PORTABLE_EXTENSION_NAME_AND_VERSION= for each extension, so that
|
||||
* all needed metadata is available. */
|
||||
if (ordered_hashmap_isempty(extension_images))
|
||||
r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_NAME_AND_VERSION");
|
||||
else {
|
||||
_cleanup_free_ char *root_base_name = NULL;
|
||||
|
||||
r = path_extract_filename(image_path, &root_base_name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract basename from '%s': %m", image_path);
|
||||
|
||||
if (!strextend(&text,
|
||||
"Environment=PORTABLE_ROOT=", root_base_name, "\n",
|
||||
"LogExtraFields=PORTABLE_ROOT=", root_base_name, "\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_ROOT_NAME_AND_VERSION");
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (m->image_path && !path_equal(m->image_path, image_path))
|
||||
ORDERED_HASHMAP_FOREACH(ext, extension_images)
|
||||
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
|
||||
_cleanup_free_ char *extension_base_name = NULL;
|
||||
|
||||
r = path_extract_filename(ext->path, &extension_base_name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path);
|
||||
|
||||
if (!strextend(&text,
|
||||
"\n",
|
||||
extension_setting_from_image(ext->type),
|
||||
ext->path,
|
||||
/* With --force tell PID1 to avoid enforcing that the image <name> and
|
||||
* extension-release.<name> have to match. */
|
||||
!IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) &&
|
||||
FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT) ?
|
||||
":x-systemd.relax-extension-release-check" :
|
||||
"",
|
||||
"\n"))
|
||||
":x-systemd.relax-extension-release-check\n" :
|
||||
"\n",
|
||||
/* In PORTABLE= we list the 'main' image name for this unit
|
||||
* (the image where the unit was extracted from), but we are
|
||||
* stacking multiple images, so list those too. */
|
||||
"LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
/* Look for image/version identifiers in the extension release files. We
|
||||
* look for all possible IDs, but typically only 1 or 2 will be set, so
|
||||
* the number of fields added shouldn't be too large. We prefix the DDI
|
||||
* name to the value, so that we can add the same field multiple times and
|
||||
* still be able to identify what applies to what. */
|
||||
r = append_release_log_fields(&text,
|
||||
ordered_hashmap_get(extension_releases, ext->name),
|
||||
IMAGE_EXTENSION,
|
||||
"PORTABLE_EXTENSION_NAME_AND_VERSION");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
|
||||
@ -1106,7 +1199,9 @@ static int attach_unit_file(
|
||||
const char *image_path,
|
||||
ImageType type,
|
||||
OrderedHashmap *extension_images,
|
||||
OrderedHashmap *extension_releases,
|
||||
const PortableMetadata *m,
|
||||
const PortableMetadata *os_release,
|
||||
const char *profile,
|
||||
PortableFlags flags,
|
||||
PortableChange **changes,
|
||||
@ -1150,7 +1245,7 @@ static int attach_unit_file(
|
||||
* is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
|
||||
* all for PID 1. */
|
||||
|
||||
r = install_chroot_dropin(image_path, type, extension_images, m, dropin_dir, flags, &chroot_dropin, changes, n_changes);
|
||||
r = install_chroot_dropin(image_path, type, extension_images, extension_releases, m, os_release, dropin_dir, flags, &chroot_dropin, changes, n_changes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1302,7 +1397,8 @@ int portable_attach(
|
||||
size_t *n_changes,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL;
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
|
||||
_cleanup_(lookup_paths_free) LookupPaths paths = {};
|
||||
_cleanup_strv_free_ char **valid_prefixes = NULL;
|
||||
@ -1318,8 +1414,8 @@ int portable_attach(
|
||||
/* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
|
||||
&image,
|
||||
&extension_images,
|
||||
/* extension_releases= */ NULL,
|
||||
/* os_release= */ NULL,
|
||||
&extension_releases,
|
||||
&os_release,
|
||||
&unit_files,
|
||||
&valid_prefixes,
|
||||
error);
|
||||
@ -1386,8 +1482,8 @@ int portable_attach(
|
||||
}
|
||||
|
||||
HASHMAP_FOREACH(item, unit_files) {
|
||||
r = attach_unit_file(&paths, image->path, image->type, extension_images,
|
||||
item, profile, flags, changes, n_changes);
|
||||
r = attach_unit_file(&paths, image->path, image->type, extension_images, extension_releases,
|
||||
item, os_release, profile, flags, changes, n_changes);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
|
||||
}
|
||||
|
@ -400,7 +400,8 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
|
||||
nl = true;
|
||||
} else {
|
||||
_cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL,
|
||||
*id = NULL, *version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL;
|
||||
*sysext_id = NULL, *sysext_version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL,
|
||||
*id = NULL, *version_id = NULL, *image_id = NULL, *image_version = NULL, *build_id = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
|
||||
f = fmemopen_unlocked((void*) data, sz, "r");
|
||||
@ -408,30 +409,42 @@ static int inspect_image(int argc, char *argv[], void *userdata) {
|
||||
return log_error_errno(errno, "Failed to open extension-release buffer: %m");
|
||||
|
||||
r = parse_env_file(f, name,
|
||||
"ID", &id,
|
||||
"VERSION_ID", &version_id,
|
||||
"SYSEXT_ID", &sysext_id,
|
||||
"SYSEXT_VERSION_ID", &sysext_version_id,
|
||||
"SYSEXT_BUILD_ID", &build_id,
|
||||
"SYSEXT_IMAGE_ID", &image_id,
|
||||
"SYSEXT_IMAGE_VERSION", &image_version,
|
||||
"SYSEXT_PRETTY_NAME", &pretty_os,
|
||||
"SYSEXT_SCOPE", &sysext_scope,
|
||||
"SYSEXT_LEVEL", &sysext_level,
|
||||
"ID", &id,
|
||||
"VERSION_ID", &version_id,
|
||||
"PORTABLE_PRETTY_NAME", &pretty_portable,
|
||||
"PORTABLE_PREFIXES", &portable_prefixes,
|
||||
"PRETTY_NAME", &pretty_os);
|
||||
"PORTABLE_PREFIXES", &portable_prefixes);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse extension release from '%s': %m", name);
|
||||
|
||||
printf("Extension:\n\t%s\n"
|
||||
"\tExtension Scope:\n\t\t%s\n"
|
||||
"\tExtension Compatibility Level:\n\t\t%s\n"
|
||||
"\tExtension Compatibility OS:\n\t\t%s\n"
|
||||
"\tExtension Compatibility OS Version:\n\t\t%s\n"
|
||||
"\tPortable Service:\n\t\t%s\n"
|
||||
"\tPortable Prefixes:\n\t\t%s\n"
|
||||
"\tOperating System:\n\t\t%s (%s %s)\n",
|
||||
"\tExtension Image:\n\t\t%s%s%s %s%s%s\n",
|
||||
name,
|
||||
strna(sysext_scope),
|
||||
strna(sysext_level),
|
||||
strna(id),
|
||||
strna(version_id),
|
||||
strna(pretty_portable),
|
||||
strna(portable_prefixes),
|
||||
strna(pretty_os),
|
||||
strna(id),
|
||||
strna(version_id));
|
||||
strempty(pretty_os),
|
||||
pretty_os ? " (" : "ID: ",
|
||||
strna(sysext_id ?: image_id),
|
||||
pretty_os ? "" : "Version: ",
|
||||
strna(sysext_version_id ?: image_version ?: build_id),
|
||||
pretty_os ? ")" : "");
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(reply);
|
||||
|
@ -509,6 +509,23 @@ TEST(write_string_file_verify) {
|
||||
assert_se(write_string_file("/proc/version", buf2, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_AVOID_NEWLINE) == 0);
|
||||
}
|
||||
|
||||
static void check_file_pairs_one(char **l) {
|
||||
assert_se(l);
|
||||
assert_se(strv_length(l) == 14);
|
||||
|
||||
STRV_FOREACH_PAIR(k, v, l) {
|
||||
assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
|
||||
printf("%s=%s\n", *k, *v);
|
||||
assert_se(!streq(*k, "NAME") || streq(*v, "Arch Linux"));
|
||||
assert_se(!streq(*k, "ID") || streq(*v, "arch"));
|
||||
assert_se(!streq(*k, "PRETTY_NAME") || streq(*v, "Arch Linux"));
|
||||
assert_se(!streq(*k, "ANSI_COLOR") || streq(*v, "0;36"));
|
||||
assert_se(!streq(*k, "HOME_URL") || streq(*v, "https://www.archlinux.org/"));
|
||||
assert_se(!streq(*k, "SUPPORT_URL") || streq(*v, "https://bbs.archlinux.org/"));
|
||||
assert_se(!streq(*k, "BUG_REPORT_URL") || streq(*v, "https://bugs.archlinux.org/"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(load_env_file_pairs) {
|
||||
_cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-load_env_file_pairs-XXXXXX";
|
||||
int fd, r;
|
||||
@ -529,24 +546,17 @@ TEST(load_env_file_pairs) {
|
||||
WRITE_STRING_FILE_CREATE);
|
||||
assert_se(r == 0);
|
||||
|
||||
r = load_env_file_pairs_fd(fd, fn, &l);
|
||||
assert_se(r >= 0);
|
||||
check_file_pairs_one(l);
|
||||
l = strv_free(l);
|
||||
|
||||
f = fdopen(fd, "r");
|
||||
assert_se(f);
|
||||
|
||||
r = load_env_file_pairs(f, fn, &l);
|
||||
assert_se(r >= 0);
|
||||
|
||||
assert_se(strv_length(l) == 14);
|
||||
STRV_FOREACH_PAIR(k, v, l) {
|
||||
assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL"));
|
||||
printf("%s=%s\n", *k, *v);
|
||||
if (streq(*k, "NAME")) assert_se(streq(*v, "Arch Linux"));
|
||||
if (streq(*k, "ID")) assert_se(streq(*v, "arch"));
|
||||
if (streq(*k, "PRETTY_NAME")) assert_se(streq(*v, "Arch Linux"));
|
||||
if (streq(*k, "ANSI_COLOR")) assert_se(streq(*v, "0;36"));
|
||||
if (streq(*k, "HOME_URL")) assert_se(streq(*v, "https://www.archlinux.org/"));
|
||||
if (streq(*k, "SUPPORT_URL")) assert_se(streq(*v, "https://bbs.archlinux.org/"));
|
||||
if (streq(*k, "BUG_REPORT_URL")) assert_se(streq(*v, "https://bugs.archlinux.org/"));
|
||||
}
|
||||
check_file_pairs_one(l);
|
||||
}
|
||||
|
||||
TEST(search_and_fopen) {
|
||||
|
@ -994,4 +994,16 @@ TEST(strv_copy_n) {
|
||||
assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e")));
|
||||
}
|
||||
|
||||
TEST(strv_find_first_field) {
|
||||
char **haystack = STRV_MAKE("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
|
||||
|
||||
assert_se(strv_find_first_field(NULL, NULL) == NULL);
|
||||
assert_se(strv_find_first_field(NULL, haystack) == NULL);
|
||||
assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), NULL) == NULL);
|
||||
assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), haystack) == NULL);
|
||||
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "a", "c"), haystack), "b"));
|
||||
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "c", "a"), haystack), "d"));
|
||||
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
@ -685,6 +685,8 @@ EOF
|
||||
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
( echo "${version_id}"
|
||||
echo "SYSEXT_IMAGE_ID=app" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app0"
|
||||
cat >"$initdir/usr/lib/systemd/system/app0.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
@ -710,6 +712,8 @@ EOF
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
( echo "${version_id}"
|
||||
echo "SYSEXT_SCOPE=portable"
|
||||
echo "SYSEXT_IMAGE_ID=app"
|
||||
echo "SYSEXT_IMAGE_VERSION=1"
|
||||
echo "PORTABLE_PREFIXES=app1" ) >>"$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
setfattr -n user.extension-release.strict -v false "$initdir/usr/lib/extension-release.d/extension-release.app2"
|
||||
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
|
||||
|
@ -96,12 +96,20 @@ systemctl is-active app0.service
|
||||
status="$(portablectl is-attached --extension app0 minimal_0)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
|
||||
timeout "$TIMEOUT" portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
systemctl is-active app0.service
|
||||
status="$(portablectl is-attached --extension app0 minimal_1)"
|
||||
[[ "${status}" == "running-runtime" ]]
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
|
||||
portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
|
||||
@ -189,6 +197,20 @@ portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 |
|
||||
portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/systemd/system/app1.service
|
||||
portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/systemd/system/app0.service
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
|
||||
grep -q -F "LogExtraFields=PORTABLE=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app1.service.d/20-portable.conf
|
||||
|
||||
portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
|
||||
|
||||
# Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
|
||||
|
Loading…
x
Reference in New Issue
Block a user