1
0
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.

19 changed files with 185 additions and 451 deletions

View File

@ -12,7 +12,7 @@ traditional system services, making two specific facets of container management
available to system services more readily. Specifically: available to system services more readily. Specifically:
1. The bundling of applications, i.e. packing up multiple services, their 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. directly from it.
2. Stricter default security policies, i.e. sand-boxing of applications. 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"? ## So, what *is* a "Portable Service"?
A portable service is ultimately just an OS tree, either inside of a directory 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 tree, or inside a raw disk image containing a Linux file system. This tree is
[Layered Images](#layered-images)) containing a Linux file system. This tree is called the called the "image". It can be "attached" or "detached" from the system. When
"image". It can be "attached" or "detached" from the system. When "attached" "attached" specific systemd units from the image are made available on the host
specific systemd units from the image are made available on the host system, system, then behaving pretty much exactly like locally installed system
then behaving pretty much exactly like locally installed system services. When services. When "detached" these units are removed again from the host, leaving
"detached" these units are removed again from the host, leaving no artifacts no artifacts around (except maybe messages they might have logged).
around (except maybe messages they might have logged).
The OS tree/image can be created with any tool of your choice. For example, you 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 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. again.
Note that `portablectl attach` won't enable or start any of the units it copies 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. out. This still has to take place in a second, separate step. (That said We
The same is true for the opposite `detach` operation. might add options to do this automatically later on.).
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.
## Requirements on Images ## 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. As mentioned `mkosi -b` takes care of all of that for you, but any other
image generator should work too. 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 ## Execution Environment
Note that the code in portable service images is run exactly like regular Note that the code in portable service images is run exactly like regular

View File

@ -58,44 +58,7 @@
<listitem><para>A glob pattern to select the default entry. The default entry <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 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. selected entry will be stored as an EFI variable, overriding this option.
</para> </para></listitem>
<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>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>

View File

@ -17,7 +17,6 @@
<refnamediv> <refnamediv>
<refname>os-release</refname> <refname>os-release</refname>
<refname>initrd-release</refname> <refname>initrd-release</refname>
<refname>extension-release</refname>
<refpurpose>Operating system identification</refpurpose> <refpurpose>Operating system identification</refpurpose>
</refnamediv> </refnamediv>
@ -25,7 +24,6 @@
<para><filename>/etc/os-release</filename></para> <para><filename>/etc/os-release</filename></para>
<para><filename>/usr/lib/os-release</filename></para> <para><filename>/usr/lib/os-release</filename></para>
<para><filename>/etc/initrd-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> </refsynopsisdiv>
<refsect1> <refsect1>
@ -96,28 +94,6 @@
above) work correctly. The rest of this document that talks about <filename>os-release</filename> 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> should be understood to apply to <filename>initrd-release</filename> too.</para>
</refsect2> </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>
<refsect1> <refsect1>
@ -397,8 +373,7 @@
<listitem><para>A lower-case string (mostly numeric, no spaces or other characters outside of 09, <listitem><para>A lower-case string (mostly numeric, no spaces or other characters outside of 09,
az, ".", "_" and "-") identifying the operating system extensions support level, to indicate which az, ".", "_" 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>, extension images are supported. See
<ulink url="https://www.kernel.org/doc/html/latest/admin-guide/initrd.html">initrd</ulink> and
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>) <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
for more information.</para> for more information.</para>
@ -461,13 +436,6 @@ VARIANT="Workstation Edition"
VARIANT_ID=workstation</programlisting> VARIANT_ID=workstation</programlisting>
</example> </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> <example>
<title>Reading <filename>os-release</filename> in <title>Reading <filename>os-release</filename> in
<citerefentry><refentrytitle>sh</refentrytitle><manvolnum>1</manvolnum></citerefentry></title> <citerefentry><refentrytitle>sh</refentrytitle><manvolnum>1</manvolnum></citerefentry></title>

View File

@ -359,11 +359,7 @@
top of <replaceable>IMAGE</replaceable> when attaching/detaching. This argument can be specified 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 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> <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for the <varname>ExtensionImages=</varname> directive. The image(s) must contain an for the <varname>ExtensionImages=</varname> directive.</para>
<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>
<para>Note that the same extensions have to be specified, in the same order, when attaching <para>Note that the same extensions have to be specified, in the same order, when attaching
and detaching.</para></listitem> and detaching.</para></listitem>

View File

@ -108,8 +108,6 @@
<title>Key bindings</title> <title>Key bindings</title>
<para>The following keys may be used in the boot menu:</para> <para>The following keys may be used in the boot menu:</para>
<!-- Developer commands Q/v/Ctrl+l deliberately not advertised. -->
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><keycap></keycap> (Up)</term> <term><keycap></keycap> (Up)</term>
@ -152,16 +150,31 @@
</varlistentry> </varlistentry>
<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> <listitem><para>Print status</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><keycap>Q</keycap></term>
<listitem><para>Quit</para></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><keycap>h</keycap></term> <term><keycap>h</keycap></term>
<term><keycap>?</keycap></term> <term><keycap>?</keycap></term>
<term><keycap>F1</keycap></term> <term><keycap>F1</keycap></term>
<listitem><para>Show a help screen</para></listitem> <listitem><para>Show a help screen</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><keycombo><keycap>Ctrl</keycap><keycap>l</keycap></keycombo></term>
<listitem><para>Reprint the screen</para></listitem>
</varlistentry>
</variablelist> </variablelist>
<para>The following keys may be pressed during bootup or in the boot menu to directly boot a specific <para>The following keys may be pressed during bootup or in the boot menu to directly boot a specific

View File

@ -428,11 +428,6 @@
paths. If the empty string is assigned, the entire list of mount paths defined prior to this is paths. If the empty string is assigned, the entire list of mount paths defined prior to this is
reset.</para> 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 <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 <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, set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode,

View File

@ -430,14 +430,6 @@ option('sbat-distro-version', type : 'string',
description : 'SBAT distribution package version, e.g. 248-7.fc34') description : 'SBAT distribution package version, e.g. 248-7.fc34')
option('sbat-distro-url', type : 'string', option('sbat-distro-url', type : 'string',
description : 'SBAT distribution URL, e.g. https://src.fedoraproject.org/rpms/systemd') 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', option('bashcompletiondir', type : 'string',
description : 'directory for bash completion scripts ["no" disables]') description : 'directory for bash completion scripts ["no" disables]')

View File

@ -1,7 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h" #include "alloc-util.h"
#include "dirent-util.h"
#include "env-file.h" #include "env-file.h"
#include "env-util.h" #include "env-util.h"
#include "fd-util.h" #include "fd-util.h"
@ -9,13 +8,10 @@
#include "fs-util.h" #include "fs-util.h"
#include "macro.h" #include "macro.h"
#include "os-util.h" #include "os-util.h"
#include "parse-util.h"
#include "path-util.h" #include "path-util.h"
#include "stat-util.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "utf8.h" #include "utf8.h"
#include "xattr-util.h"
bool image_name_is_valid(const char *s) { bool image_name_is_valid(const char *s) {
if (!filename_is_valid(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) if (laccess(path, F_OK) < 0)
return -errno; return -errno;
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system 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 a flag for something being an OS (when not an 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); r = open_extension_release(path, extension, NULL, NULL);
if (r == -ENOENT) /* We got nothing */ if (r == -ENOENT) /* We got nothing */
return 0; 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, r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
ret_path ? &q : NULL, ret_path ? &q : NULL,
ret_fd ? &fd : 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 { } else {
const char *p; const char *p;

View File

@ -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 fgetxattrat_fake(
int dirfd, int dirfd,
const char *filename, const char *filename,
@ -151,11 +114,24 @@ int fgetxattrat_fake(
char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
_cleanup_close_ int fd = -1; _cleanup_close_ int fd = -1;
ssize_t l; ssize_t l;
int r;
r = getxattrat_fake_prepare(dirfd, filename, flags, fn, &fd); /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
if (r < 0)
return r; 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); l = getxattr(fn, attribute, value, size);
if (l < 0) if (l < 0)
@ -165,24 +141,6 @@ int fgetxattrat_fake(
return 0; 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) { static int parse_crtime(le64_t le, usec_t *usec) {
uint64_t u; uint64_t u;

View File

@ -17,12 +17,6 @@ int fgetxattrat_fake(
void *value, size_t size, void *value, size_t size,
int flags, int flags,
size_t *ret_size); 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); int fd_setcrtime(int fd, usec_t usec);

View File

@ -21,8 +21,6 @@
#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
#endif #endif
#define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
/* magic string to find in the binary image */ /* magic string to find in the binary image */
static const char _used_ _section_(".sdmagic") magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####"; static const char _used_ _section_(".sdmagic") magic[] = "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
@ -118,6 +116,8 @@ static BOOLEAN line_edit(
len = StrLen(line); len = StrLen(line);
print = AllocatePool((x_max+1) * sizeof(CHAR16)); print = AllocatePool((x_max+1) * sizeof(CHAR16));
uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE);
first = 0; first = 0;
cursor = 0; cursor = 0;
clear = 0; clear = 0;
@ -127,29 +127,24 @@ static BOOLEAN line_edit(
EFI_STATUS err; EFI_STATUS err;
UINT64 key; UINT64 key;
UINTN j; 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)); CopyMem(print, line + first, j * sizeof(CHAR16));
while (clear > 0 && j < x_max) { while (clear > 0 && j < x_max-1) {
clear--; clear--;
print[j++] = ' '; print[j++] = ' ';
} }
print[j] = '\0'; print[j] = '\0';
/* See comment at edit_line() call site for why we start at 1. */ uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
print_at(1, y_pos, COLOR_EDIT, print); 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]) err = console_key_read(&key, 0);
print[cursor] = ' '; if (EFI_ERROR(err))
print[cursor+1] = '\0'; continue;
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));
switch (key) { switch (key) {
case KEYPRESS(0, SCAN_ESC, 0): case KEYPRESS(0, SCAN_ESC, 0):
@ -187,6 +182,7 @@ static BOOLEAN line_edit(
cursor_right(&cursor, &first, x_max, len); cursor_right(&cursor, &first, x_max, len);
while (line[first + cursor] && line[first + cursor] != ' ') while (line[first + cursor] && line[first + cursor] != ' ')
cursor_right(&cursor, &first, x_max, len); cursor_right(&cursor, &first, x_max, len);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue; continue;
case KEYPRESS(0, SCAN_UP, 0): case KEYPRESS(0, SCAN_UP, 0):
@ -200,6 +196,7 @@ static BOOLEAN line_edit(
} }
while ((first + cursor) > 0 && line[first + cursor-1] != ' ') while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
cursor_left(&cursor, &first); cursor_left(&cursor, &first);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue; continue;
case KEYPRESS(0, SCAN_RIGHT, 0): case KEYPRESS(0, SCAN_RIGHT, 0):
@ -209,6 +206,7 @@ static BOOLEAN line_edit(
if (first + cursor == len) if (first + cursor == len)
continue; continue;
cursor_right(&cursor, &first, x_max, len); cursor_right(&cursor, &first, x_max, len);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue; continue;
case KEYPRESS(0, SCAN_LEFT, 0): case KEYPRESS(0, SCAN_LEFT, 0):
@ -216,6 +214,7 @@ static BOOLEAN line_edit(
case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
/* backward-char */ /* backward-char */
cursor_left(&cursor, &first); cursor_left(&cursor, &first);
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
continue; continue;
case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
@ -251,6 +250,7 @@ static BOOLEAN line_edit(
cursor_left(&cursor, &first); cursor_left(&cursor, &first);
clear++; clear++;
} }
uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
for (UINTN i = first + cursor; i + clear < len; i++) for (UINTN i = first + cursor; i + clear < len; i++)
line[i] = line[i + clear]; 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; return enter;
} }
@ -374,7 +375,7 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
assert(config); assert(config);
assert(loaded_image_path); 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); uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
Print(L"systemd-boot version: " GIT_VERSION "\n"); Print(L"systemd-boot version: " GIT_VERSION "\n");
@ -511,20 +512,19 @@ static BOOLEAN menu_run(
EFI_STATUS err; EFI_STATUS err;
UINTN visible_max; UINTN visible_max;
UINTN idx_highlight = config->idx_default; UINTN idx_highlight;
UINTN idx_highlight_prev = 0; UINTN idx_highlight_prev;
UINTN idx_first; UINTN idx_first;
UINTN idx_last; UINTN idx_last;
BOOLEAN refresh = TRUE; BOOLEAN refresh;
BOOLEAN highlight = FALSE; BOOLEAN highlight;
UINTN line_width = 0; UINTN line_width;
UINTN entry_padding = 3;
CHAR16 **lines; CHAR16 **lines;
UINTN x_start; UINTN x_start;
UINTN y_start; UINTN y_start;
UINTN x_max; UINTN x_max;
UINTN y_max; UINTN y_max;
CHAR16 *status = NULL; CHAR16 *status;
CHAR16 *clearline; CHAR16 *clearline;
UINTN timeout_remain = config->timeout_sec; UINTN timeout_remain = config->timeout_sec;
INT16 idx; INT16 idx;
@ -534,10 +534,10 @@ static BOOLEAN menu_run(
graphics_mode(FALSE); graphics_mode(FALSE);
uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, 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->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 */ /* 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) { if (config->console_mode_change != CONSOLE_MODE_KEEP) {
err = console_set_mode(&config->console_mode, config->console_mode_change); err = console_set_mode(&config->console_mode, config->console_mode_change);
@ -554,24 +554,32 @@ static BOOLEAN menu_run(
y_max = 25; y_max = 25;
} }
idx_highlight = config->idx_default;
idx_highlight_prev = 0;
visible_max = y_max - 2; visible_max = y_max - 2;
/* Drawing entries starts at idx_first until idx_last. We want to make if ((UINTN)config->idx_default >= visible_max)
* sure that idx_highlight is centered, but not if we are close to the idx_first = config->idx_default-1;
* 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;
else else
idx_first = idx_highlight - (visible_max / 2); idx_first = 0;
idx_last = idx_first + visible_max-1; idx_last = idx_first + visible_max-1;
refresh = TRUE;
highlight = FALSE;
/* length of the longest entry */ /* length of the longest entry */
for (UINTN i = 0; i < config->entry_count; i++) line_width = 5;
line_width = MAX(line_width, StrLen(config->entries[i]->title_show)); for (UINTN i = 0; i < config->entry_count; i++) {
line_width = MIN(line_width + 2 * entry_padding, x_max); 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 */ /* offsets to center the entries on the screen */
x_start = (x_max - (line_width)) / 2; x_start = (x_max - (line_width)) / 2;
@ -585,20 +593,19 @@ static BOOLEAN menu_run(
for (UINTN i = 0; i < config->entry_count; i++) { for (UINTN i = 0; i < config->entry_count; i++) {
UINTN j; UINTN j;
lines[i] = AllocatePool(((line_width + 1) * sizeof(CHAR16))); lines[i] = AllocatePool(((x_max+1) * sizeof(CHAR16)));
UINTN padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2; for (j = 0; j < x_start; j++)
for (j = 0; j < padding; j++)
lines[i][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]; lines[i][j] = config->entries[i]->title_show[k];
for (; j < line_width; j++) for (; j < x_max; j++)
lines[i][j] = ' '; lines[i][j] = ' ';
lines[i][line_width] = '\0'; lines[i][x_max] = '\0';
} }
status = NULL;
clearline = AllocatePool((x_max+1) * sizeof(CHAR16)); clearline = AllocatePool((x_max+1) * sizeof(CHAR16));
for (UINTN i = 0; i < x_max; i++) for (UINTN i = 0; i < x_max; i++)
clearline[i] = ' '; clearline[i] = ' ';
@ -611,22 +618,36 @@ static BOOLEAN menu_run(
for (UINTN i = 0; i < config->entry_count; i++) { for (UINTN i = 0; i < config->entry_count; i++) {
if (i < idx_first || i > idx_last) if (i < idx_first || i > idx_last)
continue; continue;
print_at(x_start, y_start + i - idx_first, uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + i - idx_first);
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY, if (i == idx_highlight)
lines[i]); uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
if ((INTN)i == config->idx_default_efivar) EFI_BLACK|EFI_BACKGROUND_LIGHTGRAY);
print_at(x_start, y_start + i - idx_first, else
(i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY, uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut,
(CHAR16*) L"=>"); 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; refresh = FALSE;
} else if (highlight) { } else if (highlight) {
print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]); uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_start + idx_highlight_prev - idx_first);
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]); uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
if ((INTN)idx_highlight_prev == config->idx_default_efivar) uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, lines[idx_highlight_prev]);
print_at(x_start , y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, (CHAR16*) L"=>"); if ((INTN)idx_highlight_prev == config->idx_default_efivar) {
if ((INTN)idx_highlight == config->idx_default_efivar) uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, x_start-3, y_start + idx_highlight_prev - idx_first);
print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, (CHAR16*) L"=>"); 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; highlight = FALSE;
} }
@ -646,7 +667,9 @@ static BOOLEAN menu_run(
x = (x_max - len) / 2; x = (x_max - len) / 2;
else else
x = 0; 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, status);
uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len); uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
} }
@ -668,7 +691,9 @@ static BOOLEAN menu_run(
if (status) { if (status) {
FreePool(status); FreePool(status);
status = NULL; 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; idx_highlight_prev = idx_highlight;
@ -676,14 +701,12 @@ static BOOLEAN menu_run(
switch (key) { switch (key) {
case KEYPRESS(0, SCAN_UP, 0): case KEYPRESS(0, SCAN_UP, 0):
case KEYPRESS(0, 0, 'k'): case KEYPRESS(0, 0, 'k'):
case KEYPRESS(0, 0, 'K'):
if (idx_highlight > 0) if (idx_highlight > 0)
idx_highlight--; idx_highlight--;
break; break;
case KEYPRESS(0, SCAN_DOWN, 0): case KEYPRESS(0, SCAN_DOWN, 0):
case KEYPRESS(0, 0, 'j'): case KEYPRESS(0, 0, 'j'):
case KEYPRESS(0, 0, 'J'):
if (idx_highlight < config->entry_count-1) if (idx_highlight < config->entry_count-1)
idx_highlight++; idx_highlight++;
break; break;
@ -727,10 +750,8 @@ static BOOLEAN menu_run(
case KEYPRESS(0, SCAN_F1, 0): case KEYPRESS(0, SCAN_F1, 0):
case KEYPRESS(0, 0, 'h'): case KEYPRESS(0, 0, 'h'):
case KEYPRESS(0, 0, 'H'):
case KEYPRESS(0, 0, '?'): 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, (v)ersion (Q)uit (P)rint (h)elp");
status = StrDuplicate(L"(d)efault (t/T)timeout (e)dit (p)rint (h)elp");
break; break;
case KEYPRESS(0, 0, 'Q'): case KEYPRESS(0, 0, 'Q'):
@ -739,7 +760,6 @@ static BOOLEAN menu_run(
break; break;
case KEYPRESS(0, 0, 'd'): case KEYPRESS(0, 0, 'd'):
case KEYPRESS(0, 0, 'D'):
if (config->idx_default_efivar != (INTN)idx_highlight) { if (config->idx_default_efivar != (INTN)idx_highlight) {
/* store the selected entry in a persistent EFI variable */ /* store the selected entry in a persistent EFI variable */
efivar_set( efivar_set(
@ -801,18 +821,16 @@ static BOOLEAN menu_run(
break; break;
case KEYPRESS(0, 0, 'e'): case KEYPRESS(0, 0, 'e'):
case KEYPRESS(0, 0, 'E'):
/* only the options of configured entries can be edited */ /* only the options of configured entries can be edited */
if (!config->editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED) if (!config->editor || config->entries[idx_highlight]->type == LOADER_UNDEFINED)
break; break;
/* The edit line may end up on the last line of the screen. And even though we're uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
* not telling the firmware to advance the line, it still does in this one case, uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
* causing a scroll to happen that screws with our beautiful boot loader output. uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
* Since we cannot paint the last character of the edit line, we simply start if (line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-1, y_max-1))
* at x-offset 1 for symmetry. */ exit = TRUE;
print_at(1, y_max - 1, COLOR_EDIT, clearline + 2); uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max-2, y_max-1); uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
print_at(1, y_max - 1, COLOR_NORMAL, clearline + 2);
break; break;
case KEYPRESS(0, 0, 'v'): case KEYPRESS(0, 0, 'v'):
@ -821,7 +839,6 @@ static BOOLEAN menu_run(
ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
break; break;
case KEYPRESS(0, 0, 'p'):
case KEYPRESS(0, 0, 'P'): case KEYPRESS(0, 0, 'P'):
print_status(config, loaded_image_path); print_status(config, loaded_image_path);
refresh = TRUE; refresh = TRUE;
@ -862,7 +879,7 @@ static BOOLEAN menu_run(
FreePool(lines); FreePool(lines);
FreePool(clearline); 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); uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
return run; return run;
} }
@ -1410,7 +1427,6 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
.auto_entries = TRUE, .auto_entries = TRUE,
.auto_firmware = TRUE, .auto_firmware = TRUE,
.random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN, .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); 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) { 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; EFI_STATUS err;
INTN i; 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 * 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. * 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)) { if (!EFI_ERROR(err)) {
config->entry_oneshot = StrDuplicate(entry_oneshot);
efivar_set(LOADER_GUID, L"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE); 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) { if (i >= 0) {
config->idx_default = i; config->idx_default = i;
return; return;
@ -1574,10 +1592,12 @@ static VOID config_default_entry_select(Config *config) {
/* /*
* The EFI variable to select the default boot entry overrides the * The EFI variable to select the default boot entry overrides the
* configured pattern. The variable can be set and cleared by pressing * 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); err = efivar_get(LOADER_GUID, L"LoaderEntryDefault", &entry_default);
if (!EFI_ERROR(err)) { if (!EFI_ERROR(err)) {
i = config_entry_find(config, entry_default); i = config_entry_find(config, entry_default);
if (i >= 0) { if (i >= 0) {
config->idx_default = i; config->idx_default = i;
@ -1585,6 +1605,7 @@ static VOID config_default_entry_select(Config *config) {
return; return;
} }
} }
config->idx_default_efivar = -1;
if (config->entry_count == 0) if (config->entry_count == 0)
return; return;
@ -1596,6 +1617,8 @@ static VOID config_default_entry_select(Config *config) {
if (config->entry_default_pattern) { if (config->entry_default_pattern) {
i = config->entry_count; i = config->entry_count;
while (i--) { while (i--) {
if (config->entries[i]->no_autoselect)
continue;
if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) { if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) {
config->idx_default = i; config->idx_default = i;
return; return;

View File

@ -103,13 +103,6 @@ if have_gnu_efi
efi_conf.set10('ENABLE_TPM', get_option('tpm')) efi_conf.set10('ENABLE_TPM', get_option('tpm'))
efi_conf.set('SD_TPM_PCR', get_option('tpm-pcrindex')) 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') != '' if get_option('sbat-distro') != ''
efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) efi_conf.set_quoted('SBAT_PROJECT', meson.project_name())
efi_conf.set_quoted('PROJECT_VERSION', meson.project_version()) efi_conf.set_quoted('PROJECT_VERSION', meson.project_version())

View File

@ -515,10 +515,3 @@ VOID *memmem_safe(const VOID *haystack, UINTN haystack_len, const VOID *needle,
return NULL; 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);
}

View File

@ -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); CHAR8 *p = memmem_safe(haystack, haystack_len, needle, needle_len);
return p ? p + needle_len : NULL; return p ? p + needle_len : NULL;
} }
VOID print_at(UINTN x, UINTN y, UINTN attr, const CHAR16 *str);

View File

@ -2538,13 +2538,13 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
_META_MAX, _META_MAX,
}; };
static const char *const paths[_META_MAX] = { static const char *paths[_META_MAX] = {
[META_HOSTNAME] = "/etc/hostname\0", [META_HOSTNAME] = "/etc/hostname\0",
[META_MACHINE_ID] = "/etc/machine-id\0", [META_MACHINE_ID] = "/etc/machine-id\0",
[META_MACHINE_INFO] = "/etc/machine-info\0", [META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0" [META_OS_RELEASE] = "/etc/os-release\0"
"/usr/lib/os-release\0"), "/usr/lib/os-release\0",
[META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */ [META_EXTENSION_RELEASE] = NULL,
}; };
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **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); 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 ++) { for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) {
if (!paths[n_meta_initialized]) { if (!paths[n_meta_initialized]) {
fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -1; 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]); 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]) { NULSTR_FOREACH(p, paths[k]) {
fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd >= 0) if (fd >= 0)

View File

@ -17,15 +17,12 @@
static void test_fgetxattrat_fake(void) { static void test_fgetxattrat_fake(void) {
char t[] = "/var/tmp/xattrtestXXXXXX"; char t[] = "/var/tmp/xattrtestXXXXXX";
_cleanup_free_ char *value = NULL;
_cleanup_close_ int fd = -1; _cleanup_close_ int fd = -1;
const char *x; const char *x;
char v[3]; char v[3];
int r; int r;
size_t size; size_t size;
log_info("/* %s */", __func__);
assert_se(mkdtemp(t)); assert_se(mkdtemp(t));
x = strjoina(t, "/test"); x = strjoina(t, "/test");
assert_se(touch(x) >= 0); 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); r = fgetxattrat_fake(fd, "usr", "user.idontexist", v, 3, 0, &size);
assert_se(r == -ENODATA || ERRNO_IS_NOT_SUPPORTED(r)); 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: cleanup:
assert_se(unlink(x) >= 0); assert_se(unlink(x) >= 0);
assert_se(rmdir(t) >= 0); assert_se(rmdir(t) >= 0);
@ -66,8 +56,6 @@ static void test_getcrtime(void) {
usec_t usec, k; usec_t usec, k;
int r; int r;
log_info("/* %s */", __func__);
assert_se(tmp_dir(&vt) >= 0); assert_se(tmp_dir(&vt) >= 0);
fd = open_tmpfile_unlinkable(vt, O_RDWR); fd = open_tmpfile_unlinkable(vt, O_RDWR);

View File

@ -626,9 +626,8 @@ EOF
export initdir="$TESTDIR/app1" export initdir="$TESTDIR/app1"
mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt" 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" grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app1"
echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app2" echo "${version_id}" >>"$initdir/usr/lib/extension-release.d/extension-release.app1"
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 cat >"$initdir/usr/lib/systemd/system/app1.service" <<EOF
[Service] [Service]
Type=oneshot Type=oneshot
@ -639,7 +638,7 @@ EOF
#!/bin/bash #!/bin/bash
set -e set -e
test -e /usr/lib/os-release 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 EOF
chmod +x "$initdir/opt/script1.sh" chmod +x "$initdir/opt/script1.sh"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file" echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"

View File

@ -5,11 +5,6 @@ set -eux
set -o pipefail set -o pipefail
export SYSTEMD_LOG_LEVEL=debug 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 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 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 attach --now --runtime --extension ${app1} ${root} app1
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
systemctl is-active app1.service 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 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 # portablectl also works with directory paths rather than images
mkdir /tmp/rootdir /tmp/app1 /tmp/overlay mkdir /tmp/rootdir /tmp/app1 /tmp/overlay
mount /usr/share/app1.raw /tmp/app1 mount ${app1} /tmp/app1
mount /usr/share/minimal_0.raw /tmp/rootdir mount ${root} /tmp/rootdir
mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay
portablectl attach --copy=symlink --now --runtime /tmp/overlay app1 portablectl attach --copy=symlink --now --runtime /tmp/overlay app1

View File

@ -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 --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 /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 /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" 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 cat >/run/systemd/system/testservice-50e.service <<EOF
[Service] [Service]