mirror of
https://github.com/systemd/systemd
synced 2026-03-25 16:25:04 +01:00
Compare commits
No commits in common. "f6e40037a0c6f027fd30e141092dd4982cc56050" and "7a6abbe93762fe23d415144ae7a040df3266bb5f" have entirely different histories.
f6e40037a0
...
7a6abbe937
@ -12,7 +12,7 @@ traditional system services, making two specific facets of container management
|
||||
available to system services more readily. Specifically:
|
||||
|
||||
1. The bundling of applications, i.e. packing up multiple services, their
|
||||
binaries and all their dependencies in an image, and running them
|
||||
binaries and all their dependencies in a single image, and running them
|
||||
directly from it.
|
||||
|
||||
2. Stricter default security policies, i.e. sand-boxing of applications.
|
||||
@ -29,13 +29,12 @@ of use-cases in a nicer way.
|
||||
## So, what *is* a "Portable Service"?
|
||||
|
||||
A portable service is ultimately just an OS tree, either inside of a directory
|
||||
tree, or inside a raw disk image (or a set of images that get layered, see
|
||||
[Layered Images](#layered-images)) containing a Linux file system. This tree is called the
|
||||
"image". It can be "attached" or "detached" from the system. When "attached"
|
||||
specific systemd units from the image are made available on the host system,
|
||||
then behaving pretty much exactly like locally installed system services. When
|
||||
"detached" these units are removed again from the host, leaving no artifacts
|
||||
around (except maybe messages they might have logged).
|
||||
tree, or inside a raw disk image containing a Linux file system. This tree is
|
||||
called the "image". It can be "attached" or "detached" from the system. When
|
||||
"attached" specific systemd units from the image are made available on the host
|
||||
system, then behaving pretty much exactly like locally installed system
|
||||
services. When "detached" these units are removed again from the host, leaving
|
||||
no artifacts around (except maybe messages they might have logged).
|
||||
|
||||
The OS tree/image can be created with any tool of your choice. For example, you
|
||||
can use `dnf --installroot=` if you like, or `debootstrap`, the image format is
|
||||
@ -146,14 +145,8 @@ the drop-ins and the unit files associated with the image, and removes them
|
||||
again.
|
||||
|
||||
Note that `portablectl attach` won't enable or start any of the units it copies
|
||||
out by default, but `--enable` and `--now` parameter are available as shortcuts.
|
||||
The same is true for the opposite `detach` operation.
|
||||
|
||||
A `portablectl reattach` command is made available to combine a `detach` with an
|
||||
`attach`, and it is useful in case an image gets upgraded, as it allows a to
|
||||
perform a `restart` operation on the unit(s) instead of `stop` plus `start`,
|
||||
thus providing lower downtime and avoiding losing runtime state associated with
|
||||
the unit such as the file descriptor store.
|
||||
out. This still has to take place in a second, separate step. (That said We
|
||||
might add options to do this automatically later on.).
|
||||
|
||||
## Requirements on Images
|
||||
|
||||
@ -250,34 +243,6 @@ image. To facility 3 and 4 you also need to include a boot loader in the
|
||||
image. As mentioned `mkosi -b` takes care of all of that for you, but any other
|
||||
image generator should work too.
|
||||
|
||||
## Extension Images
|
||||
|
||||
Portable services can be delivered as one or multiple images that extend the base
|
||||
image, and are combined with OverlayFS at runtime, when they are attached. This
|
||||
enables a workflow that splits the base 'runtime' from the daemon, so that multiple
|
||||
portable services can share the same 'runtime' image (libraries, tools) without
|
||||
having to include everything each time, with the layering happening only at runtime.
|
||||
The `--extension` parameter of `portablectl` can be used to specify as many upper
|
||||
layers as desired. On top of the requirements listed in the previous section, the
|
||||
following must be also be observed.
|
||||
|
||||
1. The base/OS image must contain an os-release file, either in `/etc/os-release` or
|
||||
`/usr/lib/os-release`. The file should follow the standard format.
|
||||
|
||||
2. The upper extension(s) image(s) must contain an extension-release file in
|
||||
`/usr/lib/extension-release.d/`, with an `ID=` and `SYSEXT_LEVEL=`/`VERSION_ID=`
|
||||
matching the base image.
|
||||
|
||||
3. The base/OS image does not need to have any unit files.
|
||||
|
||||
4. The upper extension(s) image(s) must at least contain one matching unit file each,
|
||||
with the right name prefix and suffix (see above).
|
||||
|
||||
```
|
||||
# /usr/lib/systemd/portablectl attach --extension foobar_0.7.23.raw debian-runtime_11.1.raw foobar
|
||||
# /usr/lib/systemd/portablectl attach --extension barbaz_7.0.23.raw debian-runtime_11.1.raw barbaz
|
||||
```
|
||||
|
||||
## Execution Environment
|
||||
|
||||
Note that the code in portable service images is run exactly like regular
|
||||
|
||||
@ -58,44 +58,7 @@
|
||||
<listitem><para>A glob pattern to select the default entry. The default entry
|
||||
may be changed in the boot menu itself, in which case the name of the
|
||||
selected entry will be stored as an EFI variable, overriding this option.
|
||||
</para>
|
||||
|
||||
<table>
|
||||
<title>Automatically detected entries will use the following names:</title>
|
||||
|
||||
<tgroup cols='2'>
|
||||
<colspec colname='name' />
|
||||
<colspec colname='expl' />
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Name</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>auto-efi-default</entry>
|
||||
<entry>EFI Default Loader</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>auto-efi-shell</entry>
|
||||
<entry>EFI Shell</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>auto-osx</entry>
|
||||
<entry>macOS</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>auto-reboot-to-firmware-setup</entry>
|
||||
<entry>Reboot Into Firmware Interface</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>auto-windows</entry>
|
||||
<entry>Windows Boot Manager</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table></listitem>
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
<refnamediv>
|
||||
<refname>os-release</refname>
|
||||
<refname>initrd-release</refname>
|
||||
<refname>extension-release</refname>
|
||||
<refpurpose>Operating system identification</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
@ -25,7 +24,6 @@
|
||||
<para><filename>/etc/os-release</filename></para>
|
||||
<para><filename>/usr/lib/os-release</filename></para>
|
||||
<para><filename>/etc/initrd-release</filename></para>
|
||||
<para><filename>/usr/lib/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
@ -96,28 +94,6 @@
|
||||
above) work correctly. The rest of this document that talks about <filename>os-release</filename>
|
||||
should be understood to apply to <filename>initrd-release</filename> too.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title><filename>/usr/lib/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename></title>
|
||||
|
||||
<para><filename>/usr/lib/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>
|
||||
for extension images plays the same role as <filename>os-release</filename> in the main system, and follows the
|
||||
same syntax and rules as described in the <ulink url="https://systemd.io/PORTABLE_SERVICES">Portable Services Documentation</ulink>.
|
||||
The purpose of this file is to allow the operating system to correctly match an extension image
|
||||
to a base OS image, This is typically implemented by first checking that the <varname>ID=</varname>
|
||||
options match, and if they do either <varname>SYSEXT_LEVEL=</varname> has to match too (preferred), or
|
||||
as a fallback if that is not present <varname>VERSION_ID=</varname> is checked. This ensures that ABI/API
|
||||
between the layers matches and no incompatible images are merged in an overlay.
|
||||
It is preferred that the <filename>extension-release.<replaceable>IMAGE</replaceable></filename> filename is suffixed
|
||||
with the exact file name of the image that contains it, so that all such files in every layer of an overlay are visible.
|
||||
But for the purpose of parsing metadata, in case it is not possible to guarantee that an image file name is stable
|
||||
and doesn't change between the build and the deployment phases, the first and only file which name starts with
|
||||
<filename>extension-release.</filename>, is located in the same directory and is tagged with a
|
||||
<varname>user.extension-release.strict</varname> <citerefentry><refentrytitle>xattr</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
set to the string <literal>0</literal>, will be parsed instead, if the one with the expected name cannot be found.
|
||||
The rest of this document that talks about <filename>os-release</filename> should be understood to apply to
|
||||
<filename>extension-release</filename> too.</para>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@ -397,8 +373,7 @@
|
||||
|
||||
<listitem><para>A lower-case string (mostly numeric, no spaces or other characters outside of 0–9,
|
||||
a–z, ".", "_" and "-") identifying the operating system extensions support level, to indicate which
|
||||
extension images are supported. See <filename>/usr/lib/extension-release.d/extension-release.<replaceable>IMAGE</replaceable></filename>,
|
||||
<ulink url="https://www.kernel.org/doc/html/latest/admin-guide/initrd.html">initrd</ulink> and
|
||||
extension images are supported. See
|
||||
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
|
||||
for more information.</para>
|
||||
|
||||
@ -461,13 +436,6 @@ VARIANT="Workstation Edition"
|
||||
VARIANT_ID=workstation</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title><filename>extension-release</filename> file for an extension for Fedora Workstation 32</title>
|
||||
|
||||
<programlisting>ID=fedora
|
||||
VERSION_ID=32</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Reading <filename>os-release</filename> in
|
||||
<citerefentry><refentrytitle>sh</refentrytitle><manvolnum>1</manvolnum></citerefentry></title>
|
||||
|
||||
@ -359,11 +359,7 @@
|
||||
top of <replaceable>IMAGE</replaceable> when attaching/detaching. This argument can be specified
|
||||
multiple times, in which case the order in which images are laid down follows the rules specified in
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for the <varname>ExtensionImages=</varname> directive. The image(s) must contain an
|
||||
<filename>extension-release</filename> file with metadata that matches what is defined in the
|
||||
<filename>os-release</filename> of <replaceable>IMAGE</replaceable>. See:
|
||||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||
</para>
|
||||
for the <varname>ExtensionImages=</varname> directive.</para>
|
||||
|
||||
<para>Note that the same extensions have to be specified, in the same order, when attaching
|
||||
and detaching.</para></listitem>
|
||||
|
||||
@ -108,8 +108,6 @@
|
||||
<title>Key bindings</title>
|
||||
<para>The following keys may be used in the boot menu:</para>
|
||||
|
||||
<!-- Developer commands Q/v/Ctrl+l deliberately not advertised. -->
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><keycap>↑</keycap> (Up)</term>
|
||||
@ -152,16 +150,31 @@
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><keycap>p</keycap></term>
|
||||
<term><keycap>v</keycap></term>
|
||||
<listitem><para>Show systemd-boot, UEFI, and firmware versions</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><keycap>P</keycap></term>
|
||||
<listitem><para>Print status</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><keycap>Q</keycap></term>
|
||||
<listitem><para>Quit</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><keycap>h</keycap></term>
|
||||
<term><keycap>?</keycap></term>
|
||||
<term><keycap>F1</keycap></term>
|
||||
<listitem><para>Show a help screen</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><keycombo><keycap>Ctrl</keycap><keycap>l</keycap></keycombo></term>
|
||||
<listitem><para>Reprint the screen</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>The following keys may be pressed during bootup or in the boot menu to directly boot a specific
|
||||
|
||||
@ -428,11 +428,6 @@
|
||||
paths. If the empty string is assigned, the entire list of mount paths defined prior to this is
|
||||
reset.</para>
|
||||
|
||||
<para>Each image must carry a <filename>/usr/lib/extension-release.d/extension-release.IMAGE</filename>
|
||||
file, with the appropriate metadata which matches <varname>RootImage=</varname>/<varname>RootDirectory=</varname>
|
||||
or the host. See:
|
||||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>When <varname>DevicePolicy=</varname> is set to <literal>closed</literal> or
|
||||
<literal>strict</literal>, or set to <literal>auto</literal> and <varname>DeviceAllow=</varname> is
|
||||
set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode,
|
||||
|
||||
@ -430,14 +430,6 @@ option('sbat-distro-version', type : 'string',
|
||||
description : 'SBAT distribution package version, e.g. 248-7.fc34')
|
||||
option('sbat-distro-url', type : 'string',
|
||||
description : 'SBAT distribution URL, e.g. https://src.fedoraproject.org/rpms/systemd')
|
||||
option('efi-color-normal', type : 'string', value : 'lightgray,black',
|
||||
description : 'general boot loader color in "foreground,background" form, see constants from eficon.h')
|
||||
option('efi-color-entry', type : 'string', value : 'lightgray,black',
|
||||
description : 'boot loader color for entries')
|
||||
option('efi-color-highlight', type : 'string', value : 'black,lightgray',
|
||||
description : 'boot loader color for selected entries')
|
||||
option('efi-color-edit', type : 'string', value : 'black,lightgray',
|
||||
description : 'boot loader color for option line edit')
|
||||
|
||||
option('bashcompletiondir', type : 'string',
|
||||
description : 'directory for bash completion scripts ["no" disables]')
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "env-file.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
@ -9,13 +8,10 @@
|
||||
#include "fs-util.h"
|
||||
#include "macro.h"
|
||||
#include "os-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "utf8.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
bool image_name_is_valid(const char *s) {
|
||||
if (!filename_is_valid(s))
|
||||
@ -45,8 +41,8 @@ int path_is_extension_tree(const char *path, const char *extension) {
|
||||
if (laccess(path, F_OK) < 0)
|
||||
return -errno;
|
||||
|
||||
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
|
||||
* and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
|
||||
/* We use /usr/lib/extension-release.d/extension-release.NAME as flag file if something is a system extension,
|
||||
* and {/etc|/usr/lib}/os-release as flag file if something is an OS (in case extension == NULL) */
|
||||
r = open_extension_release(path, extension, NULL, NULL);
|
||||
if (r == -ENOENT) /* We got nothing */
|
||||
return 0;
|
||||
@ -71,91 +67,6 @@ int open_extension_release(const char *root, const char *extension, char **ret_p
|
||||
r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
|
||||
ret_path ? &q : NULL,
|
||||
ret_fd ? &fd : NULL);
|
||||
/* Cannot find the expected extension-release file? The image filename might have been
|
||||
* mangled on deployment, so fallback to checking for any file in the extension-release.d
|
||||
* directory, and return the first one with a user.extension-release xattr instead.
|
||||
* The user.extension-release.strict xattr is checked to ensure the author of the image
|
||||
* considers it OK if names do not match. */
|
||||
if (r == -ENOENT) {
|
||||
_cleanup_free_ char *extension_release_dir_path = NULL;
|
||||
_cleanup_closedir_ DIR *extension_release_dir = NULL;
|
||||
|
||||
r = chase_symlinks_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
|
||||
&extension_release_dir_path, &extension_release_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = -ENOENT;
|
||||
struct dirent *de;
|
||||
FOREACH_DIRENT(de, extension_release_dir, return -errno) {
|
||||
int k;
|
||||
|
||||
if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
|
||||
continue;
|
||||
|
||||
const char *image_name = startswith(de->d_name, "extension-release.");
|
||||
if (!image_name)
|
||||
continue;
|
||||
|
||||
if (!image_name_is_valid(image_name))
|
||||
continue;
|
||||
|
||||
/* We already chased the directory, and checked that
|
||||
* this is a real file, so we shouldn't fail to open it. */
|
||||
_cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
|
||||
de->d_name,
|
||||
O_PATH|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (extension_release_fd < 0)
|
||||
return log_debug_errno(errno,
|
||||
"Failed to open extension-release file %s/%s: %m",
|
||||
extension_release_dir_path,
|
||||
de->d_name);
|
||||
|
||||
/* Really ensure it is a regular file after we open it. */
|
||||
if (fd_verify_regular(extension_release_fd) < 0)
|
||||
continue;
|
||||
|
||||
/* No xattr or cannot parse it? Then skip this. */
|
||||
_cleanup_free_ char *extension_release_xattr = NULL;
|
||||
k = fgetxattrat_fake_malloc(extension_release_fd, NULL, "user.extension-release.strict", AT_EMPTY_PATH, &extension_release_xattr);
|
||||
if (k < 0 && !ERRNO_IS_NOT_SUPPORTED(k) && k != -ENODATA)
|
||||
log_debug_errno(k,
|
||||
"Failed to read 'user.extension-release.strict' extended attribute from extension-release file %s/%s: %m",
|
||||
extension_release_dir_path,
|
||||
de->d_name);
|
||||
if (k < 0)
|
||||
continue;
|
||||
|
||||
/* Explicitly set to request strict matching? Skip it. */
|
||||
k = parse_boolean(extension_release_xattr);
|
||||
if (k < 0)
|
||||
log_debug_errno(k,
|
||||
"Failed to parse 'user.extension-release.strict' extended attribute value from extension-release file %s/%s: %m",
|
||||
extension_release_dir_path,
|
||||
de->d_name);
|
||||
if (k < 0 || k > 0)
|
||||
continue;
|
||||
|
||||
/* We already found what we were looking for, but there's another candidate?
|
||||
* We treat this as an error, as we want to enforce that there are no ambiguities
|
||||
* in case we are in the fallback path.*/
|
||||
if (r == 0) {
|
||||
r = -ENOTUNIQ;
|
||||
break;
|
||||
}
|
||||
|
||||
r = 0; /* Found it! */
|
||||
|
||||
if (ret_fd)
|
||||
fd = TAKE_FD(extension_release_fd);
|
||||
|
||||
if (ret_path) {
|
||||
q = path_join(extension_release_dir_path, de->d_name);
|
||||
if (!q)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const char *p;
|
||||
|
||||
|
||||
@ -103,43 +103,6 @@ int fgetxattr_malloc(
|
||||
}
|
||||
}
|
||||
|
||||
/* Note: ret_fn should already be allocated for the usual xsprintf and /proc/self/fd/%i pattern. */
|
||||
static int getxattrat_fake_prepare(
|
||||
int dirfd,
|
||||
const char *filename,
|
||||
int flags,
|
||||
char ret_fn[static STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1],
|
||||
int *ret_fd) {
|
||||
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
assert(ret_fn);
|
||||
assert(ret_fd);
|
||||
|
||||
/* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
|
||||
|
||||
if (flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH))
|
||||
return -EINVAL;
|
||||
|
||||
if (isempty(filename)) {
|
||||
if (!(flags & AT_EMPTY_PATH))
|
||||
return -EINVAL;
|
||||
|
||||
snprintf(ret_fn, STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1, "/proc/self/fd/%i", dirfd);
|
||||
} else {
|
||||
fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
snprintf(ret_fn, STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1, "/proc/self/fd/%i", fd);
|
||||
}
|
||||
|
||||
/* Pass the FD to the caller, since in case we do openat() the filename depends on it. */
|
||||
*ret_fd = TAKE_FD(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fgetxattrat_fake(
|
||||
int dirfd,
|
||||
const char *filename,
|
||||
@ -151,11 +114,24 @@ int fgetxattrat_fake(
|
||||
char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
|
||||
_cleanup_close_ int fd = -1;
|
||||
ssize_t l;
|
||||
int r;
|
||||
|
||||
r = getxattrat_fake_prepare(dirfd, filename, flags, fn, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
|
||||
|
||||
if (flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH))
|
||||
return -EINVAL;
|
||||
|
||||
if (isempty(filename)) {
|
||||
if (!(flags & AT_EMPTY_PATH))
|
||||
return -EINVAL;
|
||||
|
||||
xsprintf(fn, "/proc/self/fd/%i", dirfd);
|
||||
} else {
|
||||
fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
xsprintf(fn, "/proc/self/fd/%i", fd);
|
||||
}
|
||||
|
||||
l = getxattr(fn, attribute, value, size);
|
||||
if (l < 0)
|
||||
@ -165,24 +141,6 @@ int fgetxattrat_fake(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fgetxattrat_fake_malloc(
|
||||
int dirfd,
|
||||
const char *filename,
|
||||
const char *attribute,
|
||||
int flags,
|
||||
char **value) {
|
||||
|
||||
char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r;
|
||||
|
||||
r = getxattrat_fake_prepare(dirfd, filename, flags, fn, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return getxattr_malloc(fn, attribute, value, false);
|
||||
}
|
||||
|
||||
static int parse_crtime(le64_t le, usec_t *usec) {
|
||||
uint64_t u;
|
||||
|
||||
|
||||
@ -17,12 +17,6 @@ int fgetxattrat_fake(
|
||||
void *value, size_t size,
|
||||
int flags,
|
||||
size_t *ret_size);
|
||||
int fgetxattrat_fake_malloc(
|
||||
int dirfd,
|
||||
const char *filename,
|
||||
const char *attribute,
|
||||
int flags,
|
||||
char **value);
|
||||
|
||||
int fd_setcrtime(int fd, usec_t usec);
|
||||
|
||||
|
||||
@ -21,8 +21,6 @@
|
||||
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
|
||||
#endif
|
||||
|
||||
#define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
|
||||
|
||||
/* magic string to find in the binary image */
|
||||
static const char _used_ _section_(".sdmagic") magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
|
||||
|
||||
@ -118,6 +116,8 @@ static BOOLEAN line_edit(
|
||||
len = StrLen(line);
|
||||
print = AllocatePool((x_max+1) * sizeof(CHAR16));
|
||||
|
||||
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
|
||||
|
||||
first = 0;
|
||||
cursor = 0;
|
||||
clear = 0;
|
||||
@ -127,29 +127,24 @@ static BOOLEAN line_edit(
|
||||
EFI_STATUS err;
|
||||
UINT64 key;
|
||||
UINTN j;
|
||||
UINTN cursor_color = TEXT_ATTR_SWAP(COLOR_EDIT);
|
||||
|
||||
j = MIN(len - first, x_max);
|
||||
j = len - first;
|
||||
if (j >= x_max-1)
|
||||
j = x_max-1;
|
||||
CopyMem(print, line + first, j * sizeof(CHAR16));
|
||||
while (clear > 0 && j < x_max) {
|
||||
while (clear > 0 && j < x_max-1) {
|
||||
clear--;
|
||||
print[j++] = ' ';
|
||||
}
|
||||
print[j] = '\0';
|
||||
|
||||
/* See comment at edit_line() call site for why we start at 1. */
|
||||
print_at(1, y_pos, COLOR_EDIT, print);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
|
||||
if (!print[cursor])
|
||||
print[cursor] = ' ';
|
||||
print[cursor+1] = '\0';
|
||||
do {
|
||||
print_at(cursor + 1, y_pos, cursor_color, print + cursor);
|
||||
cursor_color = TEXT_ATTR_SWAP(cursor_color);
|
||||
|
||||
err = console_key_read(&key, 750 * 1000);
|
||||
print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor);
|
||||
} while (EFI_ERROR(err));
|
||||
err = console_key_read(&key, 0);
|
||||
if (EFI_ERROR(err))
|
||||
continue;
|
||||
|
||||
switch (key) {
|
||||
case KEYPRESS(0, SCAN_ESC, 0):
|
||||
@ -187,6 +182,7 @@ static BOOLEAN line_edit(
|
||||
cursor_right(&cursor, &first, x_max, len);
|
||||
while (line[first + cursor] && line[first + cursor] != ' ')
|
||||
cursor_right(&cursor, &first, x_max, len);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
continue;
|
||||
|
||||
case KEYPRESS(0, SCAN_UP, 0):
|
||||
@ -200,6 +196,7 @@ static BOOLEAN line_edit(
|
||||
}
|
||||
while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
|
||||
cursor_left(&cursor, &first);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
continue;
|
||||
|
||||
case KEYPRESS(0, SCAN_RIGHT, 0):
|
||||
@ -209,6 +206,7 @@ static BOOLEAN line_edit(
|
||||
if (first + cursor == len)
|
||||
continue;
|
||||
cursor_right(&cursor, &first, x_max, len);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
continue;
|
||||
|
||||
case KEYPRESS(0, SCAN_LEFT, 0):
|
||||
@ -216,6 +214,7 @@ static BOOLEAN line_edit(
|
||||
case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
|
||||
/* backward-char */
|
||||
cursor_left(&cursor, &first);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
continue;
|
||||
|
||||
case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
|
||||
@ -251,6 +250,7 @@ static BOOLEAN line_edit(
|
||||
cursor_left(&cursor, &first);
|
||||
clear++;
|
||||
}
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
|
||||
|
||||
for (UINTN i = first + cursor; i + clear < len; i++)
|
||||
line[i] = line[i + clear];
|
||||
@ -335,6 +335,7 @@ static BOOLEAN line_edit(
|
||||
}
|
||||
}
|
||||
|
||||
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
|
||||
return enter;
|
||||
}
|
||||
|
||||
@ -374,7 +375,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
|
||||
assert(config);
|
||||
assert(loaded_image_path);
|
||||
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
|
||||
|
||||
Print(L"systemd-boot version: " GIT_VERSION "\n");
|
||||
@ -511,20 +512,19 @@ static BOOLEAN menu_run(
|
||||
|
||||
EFI_STATUS err;
|
||||
UINTN visible_max;
|
||||
UINTN idx_highlight = config->idx_default;
|
||||
UINTN idx_highlight_prev = 0;
|
||||
UINTN idx_highlight;
|
||||
UINTN idx_highlight_prev;
|
||||
UINTN idx_first;
|
||||
UINTN idx_last;
|
||||
BOOLEAN refresh = TRUE;
|
||||
BOOLEAN highlight = FALSE;
|
||||
UINTN line_width = 0;
|
||||
UINTN entry_padding = 3;
|
||||
BOOLEAN refresh;
|
||||
BOOLEAN highlight;
|
||||
UINTN line_width;
|
||||
CHAR16 **lines;
|
||||
UINTN x_start;
|
||||
UINTN y_start;
|
||||
UINTN x_max;
|
||||
UINTN y_max;
|
||||
CHAR16 *status = NULL;
|
||||
CHAR16 *status;
|
||||
CHAR16 *clearline;
|
||||
UINTN timeout_remain = config->timeout_sec;
|
||||
INT16 idx;
|
||||
@ -534,10 +534,10 @@ static BOOLEAN menu_run(
|
||||
graphics_mode(FALSE);
|
||||
uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
|
||||
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
|
||||
/* draw a single character to make ClearScreen work on some firmware */
|
||||
Print(L" ");
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*) L" ");
|
||||
|
||||
if (config->console_mode_change != CONSOLE_MODE_KEEP) {
|
||||
err = console_set_mode(&config->console_mode, config->console_mode_change);
|
||||
@ -554,24 +554,32 @@ static BOOLEAN menu_run(
|
||||
y_max = 25;
|
||||
}
|
||||
|
||||
idx_highlight = config->idx_default;
|
||||
idx_highlight_prev = 0;
|
||||
|
||||
visible_max = y_max - 2;
|
||||
|
||||
/* Drawing entries starts at idx_first until idx_last. We want to make
|
||||
* sure that idx_highlight is centered, but not if we are close to the
|
||||
* beginning/end of the entry list. Otherwise we would have a half-empty
|
||||
* screen. */
|
||||
if (config->entry_count <= visible_max || idx_highlight <= visible_max / 2)
|
||||
idx_first = 0;
|
||||
else if (idx_highlight >= config->entry_count - (visible_max / 2))
|
||||
idx_first = config->entry_count - visible_max;
|
||||
if ((UINTN)config->idx_default >= visible_max)
|
||||
idx_first = config->idx_default-1;
|
||||
else
|
||||
idx_first = idx_highlight - (visible_max / 2);
|
||||
idx_last = idx_first + visible_max - 1;
|
||||
idx_first = 0;
|
||||
|
||||
idx_last = idx_first + visible_max-1;
|
||||
|
||||
refresh = TRUE;
|
||||
highlight = FALSE;
|
||||
|
||||
/* length of the longest entry */
|
||||
for (UINTN i = 0; i < config->entry_count; i++)
|
||||
line_width = MAX(line_width, StrLen(config->entries[i]->title_show));
|
||||
line_width = MIN(line_width + 2 * entry_padding, x_max);
|
||||
line_width = 5;
|
||||
for (UINTN i = 0; i < config->entry_count; i++) {
|
||||
UINTN entry_len;
|
||||
|
||||
entry_len = StrLen(config->entries[i]->title_show);
|
||||
if (line_width < entry_len)
|
||||
line_width = entry_len;
|
||||
}
|
||||
if (line_width > x_max-6)
|
||||
line_width = x_max-6;
|
||||
|
||||
/* offsets to center the entries on the screen */
|
||||
x_start = (x_max - (line_width)) / 2;
|
||||
@ -585,20 +593,19 @@ static BOOLEAN menu_run(
|
||||
for (UINTN i = 0; i < config->entry_count; i++) {
|
||||
UINTN j;
|
||||
|
||||
lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16)));
|
||||
UINTN padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2;
|
||||
|
||||
for (j = 0; j < padding; j++)
|
||||
lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16)));
|
||||
for (j = 0; j < x_start; j++)
|
||||
lines[i][j] = ' ';
|
||||
|
||||
for (UINTN k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
|
||||
for (UINTN k = 0; config->entries[i]->title_show[k] != '\0' && j < x_max; j++, k++)
|
||||
lines[i][j] = config->entries[i]->title_show[k];
|
||||
|
||||
for (; j < line_width; j++)
|
||||
for (; j < x_max; j++)
|
||||
lines[i][j] = ' ';
|
||||
lines[i][line_width] = '\0';
|
||||
lines[i][x_max] = '\0';
|
||||
}
|
||||
|
||||
status = NULL;
|
||||
clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
|
||||
for (UINTN i = 0; i < x_max; i++)
|
||||
clearline[i] = ' ';
|
||||
@ -611,22 +618,36 @@ static BOOLEAN menu_run(
|
||||
for (UINTN i = 0; i < config->entry_count; i++) {
|
||||
if (i < idx_first || i > idx_last)
|
||||
continue;
|
||||
print_at(x_start, y_start + i - idx_first,
|
||||
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
|
||||
lines[i]);
|
||||
if ((INTN)i == config->idx_default_efivar)
|
||||
print_at(x_start, y_start + i - idx_first,
|
||||
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
|
||||
(CHAR16*) L"=>");
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first);
|
||||
if (i == idx_highlight)
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
|
||||
EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
|
||||
else
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
|
||||
EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[i]);
|
||||
if ((INTN)i == config->idx_default_efivar) {
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + i - idx_first);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*) L"=>");
|
||||
}
|
||||
}
|
||||
refresh = FALSE;
|
||||
} else if (highlight) {
|
||||
print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]);
|
||||
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]);
|
||||
if ((INTN)idx_highlight_prev == config->idx_default_efivar)
|
||||
print_at(x_start , y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, (CHAR16*) L"=>");
|
||||
if ((INTN)idx_highlight == config->idx_default_efivar)
|
||||
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, (CHAR16*) L"=>");
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
|
||||
if ((INTN)idx_highlight_prev == config->idx_default_efivar) {
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*) L"=>");
|
||||
}
|
||||
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight - idx_first);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight]);
|
||||
if ((INTN)idx_highlight == config->idx_default_efivar) {
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight - idx_first);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*) L"=>");
|
||||
}
|
||||
highlight = FALSE;
|
||||
}
|
||||
|
||||
@ -646,7 +667,9 @@ static BOOLEAN menu_run(
|
||||
x = (x_max - len) / 2;
|
||||
else
|
||||
x = 0;
|
||||
print_at(0, y_max - 1, COLOR_NORMAL, clearline + (x_max - x));
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline + (x_max - x));
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, status);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
|
||||
}
|
||||
@ -668,7 +691,9 @@ static BOOLEAN menu_run(
|
||||
if (status) {
|
||||
FreePool(status);
|
||||
status = NULL;
|
||||
print_at(0, y_max - 1, COLOR_NORMAL, clearline + 1);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
|
||||
}
|
||||
|
||||
idx_highlight_prev = idx_highlight;
|
||||
@ -676,14 +701,12 @@ static BOOLEAN menu_run(
|
||||
switch (key) {
|
||||
case KEYPRESS(0, SCAN_UP, 0):
|
||||
case KEYPRESS(0, 0, 'k'):
|
||||
case KEYPRESS(0, 0, 'K'):
|
||||
if (idx_highlight > 0)
|
||||
idx_highlight--;
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, SCAN_DOWN, 0):
|
||||
case KEYPRESS(0, 0, 'j'):
|
||||
case KEYPRESS(0, 0, 'J'):
|
||||
if (idx_highlight < config->entry_count-1)
|
||||
idx_highlight++;
|
||||
break;
|
||||
@ -727,10 +750,8 @@ static BOOLEAN menu_run(
|
||||
|
||||
case KEYPRESS(0, SCAN_F1, 0):
|
||||
case KEYPRESS(0, 0, 'h'):
|
||||
case KEYPRESS(0, 0, 'H'):
|
||||
case KEYPRESS(0, 0, '?'):
|
||||
/* This must stay below 80 characters! Q/v/Ctrl+l deliberately not advertised. */
|
||||
status = StrDuplicate(L"(d)efault (t/T)timeout (e)dit (p)rint (h)elp");
|
||||
status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, 0, 'Q'):
|
||||
@ -739,7 +760,6 @@ static BOOLEAN menu_run(
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, 0, 'd'):
|
||||
case KEYPRESS(0, 0, 'D'):
|
||||
if (config->idx_default_efivar != (INTN)idx_highlight) {
|
||||
/* store the selected entry in a persistent EFI variable */
|
||||
efivar_set(
|
||||
@ -801,18 +821,16 @@ static BOOLEAN menu_run(
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, 0, 'e'):
|
||||
case KEYPRESS(0, 0, 'E'):
|
||||
/* only the options of configured entries can be edited */
|
||||
if (!config->editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED)
|
||||
break;
|
||||
/* The edit line may end up on the last line of the screen. And even though we're
|
||||
* not telling the firmware to advance the line, it still does in this one case,
|
||||
* causing a scroll to happen that screws with our beautiful boot loader output.
|
||||
* Since we cannot paint the last character of the edit line, we simply start
|
||||
* at x-offset 1 for symmetry. */
|
||||
print_at(1, y_max - 1, COLOR_EDIT, clearline + 2);
|
||||
exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-2, y_max-1);
|
||||
print_at(1, y_max - 1, COLOR_NORMAL, clearline + 2);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
|
||||
if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1))
|
||||
exit = TRUE;
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, 0, 'v'):
|
||||
@ -821,7 +839,6 @@ static BOOLEAN menu_run(
|
||||
ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
|
||||
break;
|
||||
|
||||
case KEYPRESS(0, 0, 'p'):
|
||||
case KEYPRESS(0, 0, 'P'):
|
||||
print_status(config, loaded_image_path);
|
||||
refresh = TRUE;
|
||||
@ -862,7 +879,7 @@ static BOOLEAN menu_run(
|
||||
FreePool(lines);
|
||||
FreePool(clearline);
|
||||
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, COLOR_NORMAL);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_WHITE|EFI_BACKGROUND_BLACK);
|
||||
uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
|
||||
return run;
|
||||
}
|
||||
@ -1410,7 +1427,6 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
|
||||
.auto_entries = TRUE,
|
||||
.auto_firmware = TRUE,
|
||||
.random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
|
||||
.idx_default_efivar = -1,
|
||||
};
|
||||
|
||||
err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
|
||||
@ -1550,7 +1566,7 @@ static INTN config_entry_find(Config *config, CHAR16 *id) {
|
||||
}
|
||||
|
||||
static VOID config_default_entry_select(Config *config) {
|
||||
_cleanup_freepool_ CHAR16 *entry_default = NULL;
|
||||
_cleanup_freepool_ CHAR16 *entry_oneshot = NULL, *entry_default = NULL;
|
||||
EFI_STATUS err;
|
||||
INTN i;
|
||||
|
||||
@ -1560,11 +1576,13 @@ static VOID config_default_entry_select(Config *config) {
|
||||
* The EFI variable to specify a boot entry for the next, and only the
|
||||
* next reboot. The variable is always cleared directly after it is read.
|
||||
*/
|
||||
err = efivar_get(LOADER_GUID, L"LoaderEntryOneShot", &config->entry_oneshot);
|
||||
err = efivar_get(LOADER_GUID, L"LoaderEntryOneShot", &entry_oneshot);
|
||||
if (!EFI_ERROR(err)) {
|
||||
|
||||
config->entry_oneshot = StrDuplicate(entry_oneshot);
|
||||
efivar_set(LOADER_GUID, L"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
|
||||
|
||||
i = config_entry_find(config, config->entry_oneshot);
|
||||
i = config_entry_find(config, entry_oneshot);
|
||||
if (i >= 0) {
|
||||
config->idx_default = i;
|
||||
return;
|
||||
@ -1574,10 +1592,12 @@ static VOID config_default_entry_select(Config *config) {
|
||||
/*
|
||||
* The EFI variable to select the default boot entry overrides the
|
||||
* configured pattern. The variable can be set and cleared by pressing
|
||||
* the 'd' key in the loader selection menu.
|
||||
* the 'd' key in the loader selection menu, the entry is marked with
|
||||
* an '*'.
|
||||
*/
|
||||
err = efivar_get(LOADER_GUID, L"LoaderEntryDefault", &entry_default);
|
||||
if (!EFI_ERROR(err)) {
|
||||
|
||||
i = config_entry_find(config, entry_default);
|
||||
if (i >= 0) {
|
||||
config->idx_default = i;
|
||||
@ -1585,6 +1605,7 @@ static VOID config_default_entry_select(Config *config) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
config->idx_default_efivar = -1;
|
||||
|
||||
if (config->entry_count == 0)
|
||||
return;
|
||||
@ -1596,6 +1617,8 @@ static VOID config_default_entry_select(Config *config) {
|
||||
if (config->entry_default_pattern) {
|
||||
i = config->entry_count;
|
||||
while (i--) {
|
||||
if (config->entries[i]->no_autoselect)
|
||||
continue;
|
||||
if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) {
|
||||
config->idx_default = i;
|
||||
return;
|
||||
|
||||
@ -103,13 +103,6 @@ if have_gnu_efi
|
||||
efi_conf.set10('ENABLE_TPM', get_option('tpm'))
|
||||
efi_conf.set('SD_TPM_PCR', get_option('tpm-pcrindex'))
|
||||
|
||||
foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit']
|
||||
c = get_option('efi-' + ctype).split(',')
|
||||
fg = 'EFI_' + c[0].strip().underscorify().to_upper()
|
||||
bg = 'EFI_BACKGROUND_' + c[1].strip().underscorify().to_upper()
|
||||
efi_conf.set(ctype.underscorify().to_upper(), '(' + fg + '|' + bg + ')')
|
||||
endforeach
|
||||
|
||||
if get_option('sbat-distro') != ''
|
||||
efi_conf.set_quoted('SBAT_PROJECT', meson.project_name())
|
||||
efi_conf.set_quoted('PROJECT_VERSION', meson.project_version())
|
||||
|
||||
@ -515,10 +515,3 @@ VOID *memmem_safe(const VOID *haystack, UINTN haystack_len, const VOID *needle,
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VOID print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str) {
|
||||
assert(str);
|
||||
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x, y);
|
||||
uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, attr);
|
||||
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*)str);
|
||||
}
|
||||
|
||||
@ -91,5 +91,3 @@ static inline VOID *mempmem_safe(const VOID *haystack, UINTN haystack_len, const
|
||||
CHAR8 *p = memmem_safe(haystack, haystack_len, needle, needle_len);
|
||||
return p ? p + needle_len : NULL;
|
||||
}
|
||||
|
||||
VOID print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str);
|
||||
|
||||
@ -2538,13 +2538,13 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
|
||||
_META_MAX,
|
||||
};
|
||||
|
||||
static const char *const paths[_META_MAX] = {
|
||||
static const char *paths[_META_MAX] = {
|
||||
[META_HOSTNAME] = "/etc/hostname\0",
|
||||
[META_MACHINE_ID] = "/etc/machine-id\0",
|
||||
[META_MACHINE_INFO] = "/etc/machine-info\0",
|
||||
[META_OS_RELEASE] = ("/etc/os-release\0"
|
||||
"/usr/lib/os-release\0"),
|
||||
[META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
|
||||
[META_OS_RELEASE] = "/etc/os-release\0"
|
||||
"/usr/lib/os-release\0",
|
||||
[META_EXTENSION_RELEASE] = NULL,
|
||||
};
|
||||
|
||||
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
|
||||
@ -2561,6 +2561,17 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
|
||||
|
||||
assert(m);
|
||||
|
||||
/* As per the os-release spec, if the image is an extension it will have a file
|
||||
* named after the image name in extension-release.d/ */
|
||||
if (m->image_name) {
|
||||
char *ext;
|
||||
|
||||
ext = strjoina("/usr/lib/extension-release.d/extension-release.", m->image_name, "0");
|
||||
ext[strlen(ext) - 1] = '\0'; /* Extra \0 for NULSTR_FOREACH using placeholder from above */
|
||||
paths[META_EXTENSION_RELEASE] = ext;
|
||||
} else
|
||||
log_debug("No image name available, will skip extension-release metadata");
|
||||
|
||||
for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) {
|
||||
if (!paths[n_meta_initialized]) {
|
||||
fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -1;
|
||||
@ -2614,20 +2625,6 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
|
||||
|
||||
fds[2*k] = safe_close(fds[2*k]);
|
||||
|
||||
if (k == META_EXTENSION_RELEASE) {
|
||||
/* As per the os-release spec, if the image is an extension it will have a file
|
||||
* named after the image name in extension-release.d/ - we use the image name
|
||||
* and try to resolve it with the extension-release helpers, as sometimes
|
||||
* the image names are mangled on deployment and do not match anymore.
|
||||
* Unlike other paths this is not fixed, and the image name
|
||||
* can be mangled on deployment, so by calling into the helper
|
||||
* we allow a fallback that matches on the first extension-release
|
||||
* file found in the directory, if one named after the image cannot
|
||||
* be found first. */
|
||||
r = open_extension_release(t, m->image_name, NULL, &fd);
|
||||
if (r < 0)
|
||||
fd = r; /* Propagate the error. */
|
||||
} else
|
||||
NULSTR_FOREACH(p, paths[k]) {
|
||||
fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
|
||||
if (fd >= 0)
|
||||
|
||||
@ -17,15 +17,12 @@
|
||||
|
||||
static void test_fgetxattrat_fake(void) {
|
||||
char t[] = "/var/tmp/xattrtestXXXXXX";
|
||||
_cleanup_free_ char *value = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
const char *x;
|
||||
char v[3];
|
||||
int r;
|
||||
size_t size;
|
||||
|
||||
log_info("/* %s */", __func__);
|
||||
|
||||
assert_se(mkdtemp(t));
|
||||
x = strjoina(t, "/test");
|
||||
assert_se(touch(x) >= 0);
|
||||
@ -48,13 +45,6 @@ static void test_fgetxattrat_fake(void) {
|
||||
r = fgetxattrat_fake(fd, "usr", "user.idontexist", v, 3, 0, &size);
|
||||
assert_se(r == -ENODATA || ERRNO_IS_NOT_SUPPORTED(r));
|
||||
|
||||
safe_close(fd);
|
||||
fd = open(x, O_PATH|O_CLOEXEC);
|
||||
assert_se(fd >= 0);
|
||||
r = fgetxattrat_fake_malloc(fd, NULL, "user.foo", AT_EMPTY_PATH, &value);
|
||||
assert_se(r == 3);
|
||||
assert_se(streq(value, "bar"));
|
||||
|
||||
cleanup:
|
||||
assert_se(unlink(x) >= 0);
|
||||
assert_se(rmdir(t) >= 0);
|
||||
@ -66,8 +56,6 @@ static void test_getcrtime(void) {
|
||||
usec_t usec, k;
|
||||
int r;
|
||||
|
||||
log_info("/* %s */", __func__);
|
||||
|
||||
assert_se(tmp_dir(&vt) >= 0);
|
||||
|
||||
fd = open_tmpfile_unlinkable(vt, O_RDWR);
|
||||
|
||||
@ -626,9 +626,8 @@ EOF
|
||||
|
||||
export initdir="$TESTDIR/app1"
|
||||
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.app2"
|
||||
echo "${version_id}" >>"$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"
|
||||
grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app1"
|
||||
echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app1"
|
||||
cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
|
||||
[Service]
|
||||
Type=oneshot
|
||||
@ -639,7 +638,7 @@ EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
test -e /usr/lib/os-release
|
||||
cat /usr/lib/extension-release.d/extension-release.app2
|
||||
cat /usr/lib/extension-release.d/extension-release.app1
|
||||
EOF
|
||||
chmod +x "$initdir/opt/script1.sh"
|
||||
echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"
|
||||
|
||||
@ -5,11 +5,6 @@ set -eux
|
||||
set -o pipefail
|
||||
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
mkdir -p /run/systemd/system/systemd-portabled.service.d/
|
||||
cat <<EOF >/run/systemd/system/systemd-portabled.service.d/override.conf
|
||||
[Service]
|
||||
Environment=SYSTEMD_LOG_LEVEL=debug
|
||||
EOF
|
||||
|
||||
portablectl attach --now --runtime /usr/share/minimal_0.raw app0
|
||||
|
||||
@ -68,31 +63,24 @@ portablectl detach --now --enable --runtime /tmp/minimal_1 app0
|
||||
|
||||
portablectl list | grep -q -F "No images."
|
||||
|
||||
portablectl attach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
|
||||
root="/usr/share/minimal_0.raw"
|
||||
app1="/usr/share/app1.raw"
|
||||
|
||||
systemctl is-active app0.service
|
||||
|
||||
portablectl reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
systemctl is-active app0.service
|
||||
|
||||
portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
portablectl attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1
|
||||
portablectl attach --now --runtime --extension ${app1} ${root} app1
|
||||
|
||||
systemctl is-active app1.service
|
||||
|
||||
portablectl reattach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
|
||||
portablectl reattach --now --runtime --extension ${app1} ${root} app1
|
||||
|
||||
systemctl is-active app1.service
|
||||
|
||||
portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1
|
||||
portablectl detach --now --runtime --extension ${app1} ${root} app1
|
||||
|
||||
# portablectl also works with directory paths rather than images
|
||||
|
||||
mkdir /tmp/rootdir /tmp/app1 /tmp/overlay
|
||||
mount /usr/share/app1.raw /tmp/app1
|
||||
mount /usr/share/minimal_0.raw /tmp/rootdir
|
||||
mount ${app1} /tmp/app1
|
||||
mount ${root} /tmp/rootdir
|
||||
mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay
|
||||
|
||||
portablectl attach --copy=symlink --now --runtime /tmp/overlay app1
|
||||
|
||||
@ -235,7 +235,7 @@ systemd-run -P --property ExtensionImages=/usr/share/app0.raw --property RootIma
|
||||
systemd-run -P --property ExtensionImages=/usr/share/app0.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
|
||||
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script0.sh | grep -q -F "extension-release.app0"
|
||||
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
|
||||
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
|
||||
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app1"
|
||||
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
|
||||
cat >/run/systemd/system/testservice-50e.service <<EOF
|
||||
[Service]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user