mirror of
https://github.com/systemd/systemd.git
synced 2024-10-26 08:55:40 +03:00
Compare commits
26 Commits
a939d46000
...
5568950c0d
Author | SHA1 | Date | |
---|---|---|---|
|
5568950c0d | ||
|
f7078de515 | ||
|
6d6048b4cb | ||
|
10a48938ef | ||
|
b58b13f1c6 | ||
|
c8f59296bf | ||
|
d585085f57 | ||
|
ff4b6a1915 | ||
|
d9f68f48f7 | ||
|
0310b2a60b | ||
|
115fac3c29 | ||
|
9d8f5e22f8 | ||
|
6fb0c52295 | ||
|
edd10ab29c | ||
|
988053eac3 | ||
|
a586f57eb2 | ||
|
8cd7970274 | ||
|
9042d5b272 | ||
|
f87873abe3 | ||
|
e1d098e5af | ||
|
dddae856c4 | ||
|
6c123747e1 | ||
|
e40dd25a6c | ||
|
8333de90a9 | ||
|
62625d7f7b | ||
|
10dcd8e909 |
@ -69,6 +69,9 @@ The following exceptions apply:
|
||||
* the following sources are under **Public Domain** (LicenseRef-alg-sha1-public-domain):
|
||||
- src/fundamental/sha1-fundamental.c
|
||||
- src/fundamental/sha1-fundamental.h
|
||||
* the following files are licensed under **BSD-3-Clause** license:
|
||||
- src/boot/efi/chid.c
|
||||
- src/boot/efi/chid.h
|
||||
* Heebo fonts under docs/fonts/ are licensed under the **SIL Open Font License 1.1**,
|
||||
* any files under test/ without an explicit license we assume non-copyrightable
|
||||
(eg: computer-generated fuzzer data)
|
||||
|
8
TODO
8
TODO
@ -129,6 +129,11 @@ Deprecations and removals:
|
||||
|
||||
Features:
|
||||
|
||||
* $LISTEN_PID, $MAINPID and $SYSTEMD_EXECPID env vars that the service manager
|
||||
sets should be augmented with $LISTEN_PIDFDID, $MAINPIDFDID and
|
||||
$SYSTEMD_EXECPIDFD (and similar for other env vars we might send). Also,
|
||||
MAINPID= in sd_notify() should be augmented with MAINPIDFDID=, and so on.
|
||||
|
||||
* port copy.c over to use LabelOps for all labelling.
|
||||
|
||||
* port remaining getmntent() users over to libmount. There are subtle
|
||||
@ -157,9 +162,6 @@ Features:
|
||||
sd_event_add_child(), and then get rid of many more explicit sigprocmask()
|
||||
calls.
|
||||
|
||||
* maybe set shell.prompt.prefix credential in run0 to some warning emoji,
|
||||
i.e. ⚠️ or ☢️ or ⚡ or 👊 or 🧑🔧 or so.
|
||||
|
||||
* introduce new structure Tpm2CombinedPolicy, that combines the various TPm2
|
||||
policy bits into one structure, i.e. public key info, pcr masks, pcrlock
|
||||
stuff, pin and so on. Then pass that around in tpm2_seal() and tpm2_unseal().
|
||||
|
52
man/run0.xml
52
man/run0.xml
@ -192,6 +192,35 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pty</option></term>
|
||||
<term><option>--pipe</option></term>
|
||||
|
||||
<listitem><para>Request allocation of a pseudo TTY for the <command>run0</command> session (in case
|
||||
of <option>--pty</option>), or request passing the caller's STDIO file descriptors directly through
|
||||
(in case of <option>--pipe</option>). If neither switch is specified, or if both switches are
|
||||
specified, the mode will be picked automatically: if standard input, standard output and standard
|
||||
error output are all connected to a TTY then a pseudo TTY is allocated, otherwise the relevant file
|
||||
descriptors are passed through directly.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--shell-prompt-prefix=<replaceable>STRING</replaceable></option></term>
|
||||
|
||||
<listitem><para>Set a shell prompt prefix string. This ultimately controls the
|
||||
<varname>$SHELL_PROMPT_PREFIX</varname> environment variable for the invoked program, which is
|
||||
typically imported into the shell prompt. By default – if emojis are supported – a superhero emoji is
|
||||
shown (🦸). This default may also be changed (or turned off) by passing the
|
||||
<varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname> environment variable to <varname>run0</varname>,
|
||||
see below. Set to an empty string to disable shell prompt prefixing.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--machine=</option></term>
|
||||
|
||||
@ -256,7 +285,30 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>$SHELL_PROMPT_PREFIX</varname></term>
|
||||
<listitem><para>By default set to the superhero emoji (if supported), but may be overriden with the
|
||||
<varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname> environment variable (see below), or the
|
||||
<option>--shell-prompt-prefix=</option> switch (see above).</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>The following variables may be passed to <command>run0</command>:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>$SYSTEMD_RUN_SHELL_PROMPT_PREFIX</varname></term>
|
||||
<listitem><para>If set, overrides the default shell prompt prefix that <command>run0</command> sets
|
||||
for the invoked shell (the superhero emoji). Set to an empty string to disable shell prompt
|
||||
prefixing.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@ -77,7 +77,7 @@
|
||||
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
|
||||
<option>--ucode=</option>, <option>--splash=</option>, <option>--dtb=</option>,
|
||||
<option>--uname=</option>, <option>--sbat=</option>, <option>--pcrpkey=</option>,
|
||||
<option>--profile=</option>, see below. Only <option>--linux=</option> is mandatory. (Alternatively,
|
||||
<option>--profile=</option>, <option>--dtbauto=</option>, <option>--hwids=</option>, see below. Only <option>--linux=</option> is mandatory. (Alternatively,
|
||||
specify <option>--current</option> to use the current values of PCR register 11 instead.)</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
@ -125,6 +125,8 @@
|
||||
<term><option>--sbat=<replaceable>PATH</replaceable></option></term>
|
||||
<term><option>--pcrpkey=<replaceable>PATH</replaceable></option></term>
|
||||
<term><option>--profile=<replaceable>PATH</replaceable></option></term>
|
||||
<term><option>--dtbauto=<replaceable>PATH</replaceable></option></term>
|
||||
<term><option>--hwids=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
|
||||
configures the files to read the unified kernel image components from. Each option corresponds with
|
||||
@ -134,7 +136,7 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
|
||||
<para id="v257">With the exception of <option>--profile=</option>, which has been added in version
|
||||
<para id="v257">With the exception of <option>--profile=</option>, <option>--dtbauto=</option> and <option>--hwids=</option>, which have been added in version
|
||||
257.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
@ -79,6 +79,20 @@
|
||||
|
||||
<listitem><para>A <literal>.dtb</literal> section with a compiled binary DeviceTree.</para></listitem>
|
||||
|
||||
<listitem><para>Zero or more <literal>.dtbauto</literal> sections. Stub will always try to find first matching one.
|
||||
Matching process extracts first <varname>compatible</varname> string from <literal>.dtbauto</literal>
|
||||
section and compares it with the first Devicetree's <varname>compatible</varname> string supplied by
|
||||
the firmware in configuration tables. If firmware does not provide Devicetree, matching with
|
||||
<varname>.hwids</varname> section will be used instead. Stub will use SMBIOS data to calculate hardware
|
||||
IDs of the machine (as per <ulink url="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/specifying-hardware-ids-for-a-computer">specification</ulink>),
|
||||
then it will proceed to trying to find any of them in <literal>.hwids</literal> section and will use first
|
||||
matching entry's <varname>compatible</varname> as a search key among the <literal>.dtbauto</literal>
|
||||
entries, in a similar fashion as the use of <varname>compatible</varname> string read from the firmware
|
||||
provided Devicetree was described before. First matching <literal>.dtbauto</literal> section will be
|
||||
loaded and will override <varname>.dtb</varname> if present.</para></listitem>
|
||||
|
||||
<listitem><para>A <literal>.hwids</literal> section with hardware IDs of the machines to match Devicetrees (refer to <literal>.dtbauto</literal> section description).</para></listitem>
|
||||
|
||||
<listitem><para>A <literal>.uname</literal> section with the kernel version information, i.e. the
|
||||
output of <command>uname -r</command> for the kernel included in the <literal>.linux</literal>
|
||||
section.</para></listitem>
|
||||
|
@ -80,6 +80,7 @@ const char* special_glyph_full(SpecialGlyph code, bool force_utf) {
|
||||
[SPECIAL_GLYPH_YELLOW_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_BLUE_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_GREEN_CIRCLE] = "o",
|
||||
[SPECIAL_GLYPH_SUPERHERO] = "S",
|
||||
},
|
||||
|
||||
/* UTF-8 */
|
||||
@ -149,6 +150,7 @@ const char* special_glyph_full(SpecialGlyph code, bool force_utf) {
|
||||
[SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡",
|
||||
[SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵",
|
||||
[SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢",
|
||||
[SPECIAL_GLYPH_SUPERHERO] = u8"🦸",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -55,6 +55,7 @@ typedef enum SpecialGlyph {
|
||||
SPECIAL_GLYPH_YELLOW_CIRCLE,
|
||||
SPECIAL_GLYPH_BLUE_CIRCLE,
|
||||
SPECIAL_GLYPH_GREEN_CIRCLE,
|
||||
SPECIAL_GLYPH_SUPERHERO,
|
||||
_SPECIAL_GLYPH_MAX,
|
||||
_SPECIAL_GLYPH_INVALID = -EINVAL,
|
||||
} SpecialGlyph;
|
||||
|
130
src/boot/efi/chid.c
Normal file
130
src/boot/efi/chid.c
Normal file
@ -0,0 +1,130 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
/*
|
||||
* Based on Nikita Travkin's dtbloader implementation.
|
||||
* Copyright (c) 2024 Nikita Travkin <nikita@trvn.ru>
|
||||
*
|
||||
* https://github.com/TravMurav/dtbloader/blob/main/src/chid.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* Based on Linaro dtbloader implementation.
|
||||
* Copyright (c) 2019, Linaro. All rights reserved.
|
||||
*
|
||||
* https://github.com/aarch64-laptops/edk2/blob/dtbloader-app/EmbeddedPkg/Application/ConfigTableLoader/CHID.c
|
||||
*/
|
||||
|
||||
#include "chid.h"
|
||||
#include "chid-fundamental.h"
|
||||
#include "efi.h"
|
||||
#include "sha1-fundamental.h"
|
||||
#include "smbios.h"
|
||||
#include "util.h"
|
||||
|
||||
/**
|
||||
* smbios_to_hashable_string() - Convert ascii smbios string to stripped char16_t.
|
||||
*/
|
||||
static char16_t *smbios_to_hashable_string(const char *str) {
|
||||
if (!str) {
|
||||
/* User of this function is expected to free the result. */
|
||||
return xnew0(char16_t, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to strip leading and trailing spaces, leading zeroes.
|
||||
* See fwupd/libfwupdplugin/fu-hwids-smbios.c
|
||||
*/
|
||||
while (*str == ' ')
|
||||
str++;
|
||||
|
||||
while (*str == '0')
|
||||
str++;
|
||||
|
||||
size_t len = strlen8(str);
|
||||
|
||||
while (len > 0 && str[len - 1] == ' ')
|
||||
len--;
|
||||
|
||||
return xstrn8_to_16(str, len);
|
||||
}
|
||||
|
||||
/* This has to be in a struct due to _cleanup_ in populate_board_chids */
|
||||
typedef struct SmbiosInfo {
|
||||
const char16_t *smbios_fields[_CHID_SMBIOS_FIELDS_MAX];
|
||||
} SmbiosInfo;
|
||||
|
||||
static void smbios_info_populate(SmbiosInfo *ret_info) {
|
||||
static RawSmbiosInfo raw = {};
|
||||
static bool raw_info_populated = false;
|
||||
|
||||
if (!raw_info_populated) {
|
||||
smbios_raw_info_populate(&raw);
|
||||
raw_info_populated = true;
|
||||
}
|
||||
|
||||
ret_info->smbios_fields[CHID_SMBIOS_MANUFACTURER] = smbios_to_hashable_string(raw.manufacturer);
|
||||
ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_NAME] = smbios_to_hashable_string(raw.product_name);
|
||||
ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_SKU] = smbios_to_hashable_string(raw.product_sku);
|
||||
ret_info->smbios_fields[CHID_SMBIOS_FAMILY] = smbios_to_hashable_string(raw.family);
|
||||
ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_PRODUCT] = smbios_to_hashable_string(raw.baseboard_product);
|
||||
ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_MANUFACTURER] = smbios_to_hashable_string(raw.baseboard_manufacturer);
|
||||
}
|
||||
|
||||
static void smbios_info_done(SmbiosInfo *info) {
|
||||
FOREACH_ELEMENT(i, info->smbios_fields)
|
||||
free(i);
|
||||
}
|
||||
|
||||
static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX]) {
|
||||
_cleanup_(smbios_info_done) SmbiosInfo info = {};
|
||||
|
||||
if (!ret_chids)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
smbios_info_populate(&info);
|
||||
chid_calculate(info.smbios_fields, ret_chids);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device **ret_device) {
|
||||
EFI_STATUS status;
|
||||
|
||||
if ((uintptr_t) hwid_buffer % alignof(Device) != 0)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
const Device *devices = ASSERT_PTR(hwid_buffer);
|
||||
|
||||
EFI_GUID chids[CHID_TYPES_MAX] = {};
|
||||
static const size_t priority[] = { 3, 6, 8, 10, 4, 5, 7, 9, 11 }; /* From most to least specific. */
|
||||
|
||||
status = populate_board_chids(chids);
|
||||
if (EFI_STATUS_IS_ERROR(status))
|
||||
return log_error_status(status, "Failed to populate board CHIDs: %m");
|
||||
|
||||
size_t n_devices = 0;
|
||||
|
||||
/* Count devices and check validity */
|
||||
for (; (n_devices + 1) * sizeof(*devices) < hwid_length;) {
|
||||
if (devices[n_devices].struct_size == 0)
|
||||
break;
|
||||
if (devices[n_devices].struct_size != sizeof(*devices))
|
||||
return EFI_UNSUPPORTED;
|
||||
n_devices++;
|
||||
}
|
||||
|
||||
if (n_devices == 0)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
FOREACH_ELEMENT(i, priority)
|
||||
FOREACH_ARRAY(dev, devices, n_devices) {
|
||||
/* Can't take a pointer to a packed struct member, so copy to a local variable */
|
||||
EFI_GUID chid = dev->chid;
|
||||
if (efi_guid_equal(&chids[*i], &chid)) {
|
||||
*ret_device = dev;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return EFI_NOT_FOUND;
|
||||
}
|
23
src/boot/efi/chid.h
Normal file
23
src/boot/efi/chid.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
#pragma once
|
||||
|
||||
#include "efi.h"
|
||||
|
||||
#include "chid-fundamental.h"
|
||||
|
||||
typedef struct Device {
|
||||
uint32_t struct_size; /* = sizeof(struct Device), or 0 for EOL */
|
||||
uint32_t name_offset; /* nul-terminated string or 0 if not present */
|
||||
uint32_t compatible_offset; /* nul-terminated string or 0 if not present */
|
||||
EFI_GUID chid;
|
||||
} _packed_ Device;
|
||||
|
||||
static inline const char* device_get_name(const void *base, const Device *device) {
|
||||
return device->name_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->name_offset);
|
||||
}
|
||||
|
||||
static inline const char* device_get_compatible(const void *base, const Device *device) {
|
||||
return device->compatible_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->compatible_offset);
|
||||
}
|
||||
|
||||
EFI_STATUS chid_match(const void *chids_buffer, size_t chids_length, const Device **ret_device);
|
@ -106,6 +106,125 @@ EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir
|
||||
MAKE_GUID_PTR(EFI_DTB_TABLE), PHYSICAL_ADDRESS_TO_POINTER(state->addr));
|
||||
}
|
||||
|
||||
static const char* devicetree_get_compatible(const void *dtb) {
|
||||
if ((uintptr_t) dtb % alignof(FdtHeader) != 0)
|
||||
return NULL;
|
||||
|
||||
const FdtHeader *dt_header = ASSERT_PTR(dtb);
|
||||
|
||||
if (be32toh(dt_header->magic) != UINT32_C(0xd00dfeed))
|
||||
return NULL;
|
||||
|
||||
uint32_t dt_size = be32toh(dt_header->total_size);
|
||||
uint32_t struct_off = be32toh(dt_header->off_dt_struct);
|
||||
uint32_t struct_size = be32toh(dt_header->size_dt_struct);
|
||||
uint32_t strings_off = be32toh(dt_header->off_dt_strings);
|
||||
uint32_t strings_size = be32toh(dt_header->size_dt_strings);
|
||||
uint32_t end;
|
||||
|
||||
if (PTR_TO_SIZE(dtb) > SIZE_MAX - dt_size)
|
||||
return NULL;
|
||||
|
||||
if (!ADD_SAFE(&end, strings_off, strings_size) || end > dt_size)
|
||||
return NULL;
|
||||
const char *strings_block = (const char *) ((const uint8_t *) dt_header + strings_off);
|
||||
|
||||
if (struct_off % sizeof(uint32_t) != 0)
|
||||
return NULL;
|
||||
if (struct_size % sizeof(uint32_t) != 0 ||
|
||||
!ADD_SAFE(&end, struct_off, struct_size) ||
|
||||
end > strings_off)
|
||||
return NULL;
|
||||
const uint32_t *cursor = (const uint32_t *) ((const uint8_t *) dt_header + struct_off);
|
||||
|
||||
size_t size_words = struct_size / sizeof(uint32_t);
|
||||
size_t len, name_off, len_words, s;
|
||||
|
||||
for (size_t i = 0; i < end; i++) {
|
||||
switch (be32toh(cursor[i])) {
|
||||
case FDT_BEGIN_NODE:
|
||||
if (i >= size_words || cursor[++i] != 0)
|
||||
return NULL;
|
||||
break;
|
||||
case FDT_NOP:
|
||||
break;
|
||||
case FDT_PROP:
|
||||
/* At least 3 words should present: len, name_off, c (nul-terminated string always has non-zero length) */
|
||||
if (i + 3 >= size_words || cursor[++i] != 0)
|
||||
return NULL;
|
||||
len = be32toh(cursor[++i]);
|
||||
name_off = be32toh(cursor[++i]);
|
||||
len_words = DIV_ROUND_UP(len, sizeof(uint32_t));
|
||||
|
||||
if (ADD_SAFE(&s, name_off, STRLEN("compatible")) &&
|
||||
s < strings_size && memcmp(strings_block + name_off, "compatible", STRLEN("compatible"))) {
|
||||
const char *c = (const char *) &cursor[++i];
|
||||
if (len == 0 || i + len_words > size_words || c[len - 1] != '\0')
|
||||
c = NULL;
|
||||
|
||||
return c;
|
||||
}
|
||||
i += len_words;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This function checks if the firmware provided Devicetree
|
||||
* and a UKI provided Devicetree contain the same first entry
|
||||
* on their respective "compatible" fields (which usually defines
|
||||
* the actual device model). More specifically, given the FW/UKI
|
||||
* "compatible" property pair:
|
||||
*
|
||||
* compatible = "string1", "string2";
|
||||
* compatible = "string1", "string3";
|
||||
*
|
||||
* the function reports a match, while for
|
||||
*
|
||||
* compatible = "string1", "string3";
|
||||
* compatible = "string2", "string1";
|
||||
*
|
||||
* it reports a mismatch.
|
||||
*
|
||||
* Other entries might refer to SoC and therefore can't be used for matching
|
||||
*/
|
||||
EFI_STATUS devicetree_match(const void *uki_dtb, size_t uki_dtb_length) {
|
||||
const void *fw_dtb = find_configuration_table(MAKE_GUID_PTR(EFI_DTB_TABLE));
|
||||
if (!fw_dtb)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
const char *fw_compat = devicetree_get_compatible(fw_dtb);
|
||||
if (!fw_compat)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
return devicetree_match_by_compatible(uki_dtb, uki_dtb_length, fw_compat);
|
||||
}
|
||||
|
||||
EFI_STATUS devicetree_match_by_compatible(const void *uki_dtb, size_t uki_dtb_length, const char *compat) {
|
||||
if ((uintptr_t) uki_dtb % alignof(FdtHeader) != 0)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
const FdtHeader *dt_header = ASSERT_PTR(uki_dtb);
|
||||
|
||||
if (uki_dtb_length < sizeof(FdtHeader) ||
|
||||
uki_dtb_length < be32toh(dt_header->total_size))
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
if (!compat)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
const char *dt_compat = devicetree_get_compatible(uki_dtb);
|
||||
if (!dt_compat)
|
||||
return EFI_INVALID_PARAMETER;
|
||||
|
||||
/* Only matches the first compatible string from each DT */
|
||||
return streq8(dt_compat, compat) ? EFI_SUCCESS : EFI_NOT_FOUND;
|
||||
}
|
||||
|
||||
EFI_STATUS devicetree_install_from_memory(
|
||||
struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length) {
|
||||
|
||||
|
@ -9,6 +9,29 @@ struct devicetree_state {
|
||||
void *orig;
|
||||
};
|
||||
|
||||
enum {
|
||||
FDT_BEGIN_NODE = 1,
|
||||
FDT_END_NODE = 2,
|
||||
FDT_PROP = 3,
|
||||
FDT_NOP = 4,
|
||||
FDT_END = 9,
|
||||
};
|
||||
|
||||
typedef struct FdtHeader {
|
||||
uint32_t magic;
|
||||
uint32_t total_size;
|
||||
uint32_t off_dt_struct;
|
||||
uint32_t off_dt_strings;
|
||||
uint32_t off_mem_rsv_map;
|
||||
uint32_t version;
|
||||
uint32_t last_comp_version;
|
||||
uint32_t boot_cpuid_phys;
|
||||
uint32_t size_dt_strings;
|
||||
uint32_t size_dt_struct;
|
||||
} FdtHeader;
|
||||
|
||||
EFI_STATUS devicetree_match(const void *uki_dtb, size_t uki_dtb_length);
|
||||
EFI_STATUS devicetree_match_by_compatible(const void *uki_dtb, size_t uki_dtb_length, const char *compat);
|
||||
EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, char16_t *name);
|
||||
EFI_STATUS devicetree_install_from_memory(
|
||||
struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length);
|
||||
|
@ -254,6 +254,7 @@ endif
|
||||
############################################################
|
||||
|
||||
libefi_sources = files(
|
||||
'chid.c',
|
||||
'console.c',
|
||||
'device-path-util.c',
|
||||
'devicetree.c',
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "chid.h"
|
||||
#include "devicetree.h"
|
||||
#include "pe.h"
|
||||
#include "util.h"
|
||||
|
||||
@ -162,6 +164,14 @@ static bool pe_section_name_equal(const char *a, const char *b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pe_use_this_dtb(
|
||||
const PeSectionHeader section_table[],
|
||||
size_t n_section_table,
|
||||
const void *base,
|
||||
size_t dtb_offset,
|
||||
size_t dtb_size,
|
||||
size_t section_nb);
|
||||
|
||||
static void pe_locate_sections(
|
||||
const PeSectionHeader section_table[],
|
||||
size_t n_section_table,
|
||||
@ -206,6 +216,11 @@ static void pe_locate_sections(
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Special handling for .dtbauto sections compared to plain .dtb */
|
||||
if (validate_base && pe_section_name_equal(section_names[i], ".dtbauto"))
|
||||
if (!pe_use_this_dtb(section_table, n_section_table, SIZE_TO_PTR(validate_base), j->VirtualAddress, j->VirtualSize, i))
|
||||
continue;
|
||||
|
||||
/* At this time, the sizes and offsets have been validated. Store them away */
|
||||
sections[i] = (PeSectionVector) {
|
||||
.memory_size = j->VirtualSize,
|
||||
@ -224,6 +239,72 @@ static void pe_locate_sections(
|
||||
}
|
||||
}
|
||||
|
||||
static bool pe_use_this_dtb(
|
||||
const PeSectionHeader section_table[],
|
||||
size_t n_section_table,
|
||||
const void *base,
|
||||
size_t dtb_offset,
|
||||
size_t dtb_size,
|
||||
size_t section_nb) {
|
||||
assert(section_table);
|
||||
assert(n_section_table > 0);
|
||||
assert(base);
|
||||
|
||||
EFI_STATUS err;
|
||||
|
||||
static const void *cached_base = NULL;
|
||||
static const Device *cached_device = NULL;
|
||||
|
||||
const void *dtb = (const uint8_t *) base + dtb_offset;
|
||||
|
||||
err = devicetree_match(dtb, dtb_size);
|
||||
if (err == EFI_SUCCESS)
|
||||
return true;
|
||||
if (err != EFI_UNSUPPORTED)
|
||||
return false;
|
||||
|
||||
/* Firmware does not provide the devicetree, so try matching against a list from .hwids section */
|
||||
const char *compatible = NULL;
|
||||
if (cached_base != base) {
|
||||
cached_base = base;
|
||||
cached_device = NULL;
|
||||
|
||||
static const char *const hwids_section_name[] = { ".hwids", NULL };
|
||||
PeSectionVector hwids_section = {};
|
||||
pe_locate_sections(
|
||||
section_table,
|
||||
n_section_table,
|
||||
hwids_section_name,
|
||||
PTR_TO_SIZE(base),
|
||||
&hwids_section);
|
||||
if (hwids_section.memory_size == 0) {
|
||||
log_info("HWIDs section is missing, no DT blob will be selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *hwids = (const uint8_t *) base + hwids_section.memory_offset;
|
||||
err = chid_match(hwids, hwids_section.memory_size, &cached_device);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_error_status(err, "HWID matching failed, no DT blob will be selected: %m");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached_device)
|
||||
return false;
|
||||
|
||||
compatible = device_get_compatible(cached_base, cached_device);
|
||||
if (!compatible)
|
||||
return false;
|
||||
|
||||
err = devicetree_match_by_compatible(dtb, dtb_size, compatible);
|
||||
if (err == EFI_SUCCESS)
|
||||
return true;
|
||||
if (err == EFI_INVALID_PARAMETER)
|
||||
log_error_status(err, "Found bad DT blob in PE section %zu", section_nb);
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) {
|
||||
/* The kernel may provide alternative PE entry points for different PE architectures. This allows
|
||||
* booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any
|
||||
|
@ -614,12 +614,13 @@ static EFI_STATUS load_addons(
|
||||
if (err != EFI_SUCCESS ||
|
||||
(!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) &&
|
||||
!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB) &&
|
||||
!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO) &&
|
||||
!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD) &&
|
||||
!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE))) {
|
||||
if (err == EFI_SUCCESS)
|
||||
err = EFI_NOT_FOUND;
|
||||
log_error_status(err,
|
||||
"Unable to locate embedded .cmdline/.dtb/.initrd/.ucode sections in %ls, ignoring: %m",
|
||||
"Unable to locate embedded .cmdline/.dtb/.dtbauto/.initrd/.ucode sections in %ls, ignoring: %m",
|
||||
items[i]);
|
||||
continue;
|
||||
}
|
||||
@ -647,7 +648,21 @@ static EFI_STATUS load_addons(
|
||||
*cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16);
|
||||
}
|
||||
|
||||
if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) {
|
||||
// FIXME: do we want to do something else here?
|
||||
// This should behave exactly as .dtb/.dtbauto in the main UKI
|
||||
if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO)) {
|
||||
*devicetree_addons = xrealloc(*devicetree_addons,
|
||||
*n_devicetree_addons * sizeof(NamedAddon),
|
||||
(*n_devicetree_addons + 1) * sizeof(NamedAddon));
|
||||
|
||||
(*devicetree_addons)[(*n_devicetree_addons)++] = (NamedAddon) {
|
||||
.blob = {
|
||||
.iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_DTBAUTO].memory_offset, sections[UNIFIED_SECTION_DTBAUTO].memory_size),
|
||||
.iov_len = sections[UNIFIED_SECTION_DTBAUTO].memory_size,
|
||||
},
|
||||
.filename = xstrdup16(items[i]),
|
||||
};
|
||||
} else if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) {
|
||||
*devicetree_addons = xrealloc(*devicetree_addons,
|
||||
*n_devicetree_addons * sizeof(NamedAddon),
|
||||
(*n_devicetree_addons + 1) * sizeof(NamedAddon));
|
||||
@ -968,13 +983,20 @@ static void install_embedded_devicetree(
|
||||
assert(sections);
|
||||
assert(dt_state);
|
||||
|
||||
if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB))
|
||||
const void *dtb = NULL;
|
||||
size_t dtb_size = 0;
|
||||
|
||||
/* Use automatically selected DT if available, otherwise go for "normal" one */
|
||||
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTBAUTO)) {
|
||||
dtb = (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTBAUTO].memory_offset;
|
||||
dtb_size = sections[UNIFIED_SECTION_DTBAUTO].memory_size;
|
||||
} else if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) {
|
||||
dtb = (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset;
|
||||
dtb_size = sections[UNIFIED_SECTION_DTB].memory_size;
|
||||
} else
|
||||
return;
|
||||
|
||||
err = devicetree_install_from_memory(
|
||||
dt_state,
|
||||
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset,
|
||||
sections[UNIFIED_SECTION_DTB].memory_size);
|
||||
err = devicetree_install_from_memory(dt_state, dtb, dtb_size);
|
||||
if (err != EFI_SUCCESS)
|
||||
log_error_status(err, "Error loading embedded devicetree, ignoring: %m");
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ static inline void* xmemdup(const void *p, size_t l) {
|
||||
}
|
||||
|
||||
#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
|
||||
#define xnew0(type, n) ((type *) xcalloc_multiply((n), sizeof(type)))
|
||||
|
||||
bool free_and_xstrdup16(char16_t **p, const char16_t *s);
|
||||
|
||||
|
@ -102,6 +102,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" --sbat=PATH Path to SBAT file %7$s .sbat\n"
|
||||
" --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n"
|
||||
" --profile=PATH Path to profile file %7$s .profile\n"
|
||||
" --hwids=PATH Path to HWIDs file %7$s .hwids\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
@ -145,8 +146,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SBAT,
|
||||
_ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */
|
||||
ARG_PCRPKEY,
|
||||
ARG_PROFILE,
|
||||
ARG_HWIDS,
|
||||
_ARG_SECTION_LAST,
|
||||
ARG_PROFILE = _ARG_SECTION_LAST,
|
||||
ARG_DTBAUTO = _ARG_SECTION_LAST,
|
||||
ARG_BANK,
|
||||
ARG_PRIVATE_KEY,
|
||||
ARG_PRIVATE_KEY_SOURCE,
|
||||
@ -169,10 +172,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "ucode", required_argument, NULL, ARG_UCODE },
|
||||
{ "splash", required_argument, NULL, ARG_SPLASH },
|
||||
{ "dtb", required_argument, NULL, ARG_DTB },
|
||||
{ "dtbauto", required_argument, NULL, ARG_DTBAUTO },
|
||||
{ "uname", required_argument, NULL, ARG_UNAME },
|
||||
{ "sbat", required_argument, NULL, ARG_SBAT },
|
||||
{ "pcrpkey", required_argument, NULL, ARG_PCRPKEY },
|
||||
{ "profile", required_argument, NULL, ARG_PROFILE },
|
||||
{ "hwids", required_argument, NULL, ARG_HWIDS },
|
||||
{ "current", no_argument, NULL, 'c' },
|
||||
{ "bank", required_argument, NULL, ARG_BANK },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
|
120
src/fundamental/chid-fundamental.c
Normal file
120
src/fundamental/chid-fundamental.c
Normal file
@ -0,0 +1,120 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
/*
|
||||
* Based on Nikita Travkin's dtbloader implementation.
|
||||
* Copyright (c) 2024 Nikita Travkin <nikita@trvn.ru>
|
||||
*
|
||||
* https://github.com/TravMurav/dtbloader/blob/main/src/chid.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* Based on Linaro dtbloader implementation.
|
||||
* Copyright (c) 2019, Linaro. All rights reserved.
|
||||
*
|
||||
* https://github.com/aarch64-laptops/edk2/blob/dtbloader-app/EmbeddedPkg/Application/ConfigTableLoader/CHID.c
|
||||
*/
|
||||
|
||||
#if SD_BOOT
|
||||
# include "efi-string.h"
|
||||
# include "util.h"
|
||||
#else
|
||||
# include <byteswap.h>
|
||||
# include <string.h>
|
||||
# include <uchar.h>
|
||||
# include <utf8.h>
|
||||
#define strsize16(str) ((char16_strlen(str) + 1) * sizeof(char16_t))
|
||||
#endif
|
||||
|
||||
#include "chid-fundamental.h"
|
||||
#include "macro-fundamental.h"
|
||||
#include "memory-util-fundamental.h"
|
||||
#include "sha1-fundamental.h"
|
||||
|
||||
static void get_chid(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], uint32_t mask, EFI_GUID *ret_chid) {
|
||||
assert(mask != 0);
|
||||
assert(ret_chid);
|
||||
const EFI_GUID namespace = { UINT32_C(0x12d8ff70), UINT16_C(0x7f4c), UINT16_C(0x7d4c), {} }; /* Swapped to BE */
|
||||
|
||||
struct sha1_ctx ctx = {};
|
||||
sha1_init_ctx(&ctx);
|
||||
|
||||
sha1_process_bytes(&namespace, sizeof(namespace), &ctx);
|
||||
|
||||
for (unsigned i = 0; i < _CHID_SMBIOS_FIELDS_MAX; i++)
|
||||
if ((mask >> i) & 1) {
|
||||
if (i > 0)
|
||||
sha1_process_bytes(L"&", 2, &ctx);
|
||||
sha1_process_bytes(smbios_fields[i], strsize16(smbios_fields[i]), &ctx);
|
||||
}
|
||||
|
||||
uint8_t hash[SHA1_DIGEST_SIZE];
|
||||
sha1_finish_ctx(&ctx, hash);
|
||||
|
||||
assert_cc(sizeof(hash) >= sizeof(*ret_chid));
|
||||
memcpy(ret_chid, hash, sizeof(*ret_chid));
|
||||
|
||||
/* Convert the resulting CHID back to little-endian: */
|
||||
ret_chid->Data1 = bswap_32(ret_chid->Data1);
|
||||
ret_chid->Data2 = bswap_16(ret_chid->Data2);
|
||||
ret_chid->Data3 = bswap_16(ret_chid->Data3);
|
||||
|
||||
/* set specific bits according to RFC4122 Section 4.1.3 */
|
||||
ret_chid->Data3 = (ret_chid->Data3 & 0x0fff) | (5 << 12);
|
||||
ret_chid->Data4[0] = (ret_chid->Data4[0] & UINT8_C(0x3f)) | UINT8_C(0x80);
|
||||
}
|
||||
|
||||
static const uint32_t chid_smbios_table[CHID_TYPES_MAX] = {
|
||||
[3] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_FAMILY) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
|
||||
|
||||
[4] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_FAMILY) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU),
|
||||
|
||||
[5] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_FAMILY) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME),
|
||||
|
||||
[6] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
|
||||
|
||||
[7] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU),
|
||||
|
||||
[8] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
|
||||
|
||||
[9] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME),
|
||||
|
||||
[10] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_FAMILY) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
|
||||
|
||||
[11] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_FAMILY),
|
||||
|
||||
[13] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_MANUFACTURER) |
|
||||
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
|
||||
};
|
||||
|
||||
void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]) {
|
||||
assert(smbios_fields);
|
||||
assert(ret_chids);
|
||||
for (size_t i = 0; i < _CHID_SMBIOS_FIELDS_MAX; i++)
|
||||
if (chid_smbios_table[i] != 0)
|
||||
get_chid(smbios_fields, chid_smbios_table[i], &ret_chids[i]);
|
||||
else
|
||||
memzero(&ret_chids[i], sizeof(EFI_GUID));
|
||||
}
|
21
src/fundamental/chid-fundamental.h
Normal file
21
src/fundamental/chid-fundamental.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "efi-fundamental.h"
|
||||
#include "string-util-fundamental.h"
|
||||
|
||||
#define CHID_TYPES_MAX 15
|
||||
|
||||
typedef enum ChidSmbiosFields {
|
||||
CHID_SMBIOS_MANUFACTURER,
|
||||
CHID_SMBIOS_FAMILY,
|
||||
CHID_SMBIOS_PRODUCT_NAME,
|
||||
CHID_SMBIOS_PRODUCT_SKU,
|
||||
CHID_SMBIOS_BASEBOARD_MANUFACTURER,
|
||||
CHID_SMBIOS_BASEBOARD_PRODUCT,
|
||||
_CHID_SMBIOS_FIELDS_MAX,
|
||||
} ChidSmbiosFields;
|
||||
|
||||
/* CHID (also called HWID by fwupd) is described at https://github.com/fwupd/fwupd/blob/main/docs/hwids.md */
|
||||
void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]);
|
@ -4,6 +4,7 @@ fundamental_include = include_directories('.')
|
||||
|
||||
fundamental_sources = files(
|
||||
'bootspec-fundamental.c',
|
||||
'chid-fundamental.c',
|
||||
'efivars-fundamental.c',
|
||||
'iovec-util-fundamental.h',
|
||||
'sha1-fundamental.c',
|
||||
|
@ -21,5 +21,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
|
||||
[UNIFIED_SECTION_PCRSIG] = ".pcrsig",
|
||||
[UNIFIED_SECTION_PCRPKEY] = ".pcrpkey",
|
||||
[UNIFIED_SECTION_PROFILE] = ".profile",
|
||||
[UNIFIED_SECTION_DTBAUTO] = ".dtbauto",
|
||||
[UNIFIED_SECTION_HWIDS] = ".hwids",
|
||||
NULL,
|
||||
};
|
||||
|
@ -18,6 +18,8 @@ typedef enum UnifiedSection {
|
||||
UNIFIED_SECTION_PCRSIG,
|
||||
UNIFIED_SECTION_PCRPKEY,
|
||||
UNIFIED_SECTION_PROFILE,
|
||||
UNIFIED_SECTION_DTBAUTO,
|
||||
UNIFIED_SECTION_HWIDS,
|
||||
_UNIFIED_SECTION_MAX,
|
||||
} UnifiedSection;
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "operation.h"
|
||||
#include "process-util.h"
|
||||
|
||||
static int operation_done_internal(const siginfo_t *si, Operation *o, sd_bus_error *error) {
|
||||
static int read_operation_errno(const siginfo_t *si, Operation *o) {
|
||||
int r;
|
||||
|
||||
assert(si);
|
||||
@ -27,15 +27,6 @@ static int operation_done_internal(const siginfo_t *si, Operation *o, sd_bus_err
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received unexpectedly short message when reading operation's errno");
|
||||
}
|
||||
|
||||
if (o->done)
|
||||
/* A completion routine is set for this operation, call it. */
|
||||
return o->done(o, r, error);
|
||||
|
||||
/* The default operation when done is to simply return an error on failure or an empty success
|
||||
* message on success. */
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Operation failed: %m");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -51,10 +42,18 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
|
||||
|
||||
o->pid = 0;
|
||||
|
||||
r = read_operation_errno(si, o);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Operation failed: %m");
|
||||
|
||||
/* If a completion routine (o->done) is set for this operation, call it. It sends a response, but can return an error in which case it expect us to reply.
|
||||
* Otherwise, the default action is to simply return an error on failure or an empty success message on success. */
|
||||
|
||||
if (o->message) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
if (o->done)
|
||||
r = o->done(o, r, &error);
|
||||
|
||||
r = operation_done_internal(si, o, &error);
|
||||
if (r < 0) {
|
||||
if (!sd_bus_error_is_set(&error))
|
||||
sd_bus_error_set_errno(&error, r);
|
||||
@ -62,16 +61,20 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
|
||||
r = sd_bus_reply_method_error(o->message, &error);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to reply to dbus message: %m");
|
||||
} else {
|
||||
} else if (!o->done) {
|
||||
/* when o->done set it's responsible for sending reply in a happy-path case */
|
||||
r = sd_bus_reply_method_return(o->message, NULL);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to reply to dbus message: %m");
|
||||
}
|
||||
} else if (o->link) {
|
||||
r = operation_done_internal(si, o, /* error = */ NULL);
|
||||
if (o->done)
|
||||
r = o->done(o, r, /* error = */ NULL);
|
||||
|
||||
if (r < 0)
|
||||
(void) sd_varlink_error_errno(o->link, r);
|
||||
else
|
||||
else if (!o->done)
|
||||
/* when o->done set it's responsible for sending reply in a happy-path case */
|
||||
(void) sd_varlink_reply(o->link, NULL);
|
||||
} else
|
||||
assert_not_reached();
|
||||
|
@ -290,7 +290,7 @@ static int handle_arg_console(const char *arg) {
|
||||
else if (streq(arg, "passive"))
|
||||
arg_console_mode = CONSOLE_PASSIVE;
|
||||
else if (streq(arg, "pipe")) {
|
||||
if (isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO))
|
||||
if (isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO))
|
||||
log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE,
|
||||
"Console mode 'pipe' selected, but standard input/output are connected to an interactive TTY. "
|
||||
"Most likely you want to use 'interactive' console mode for proper interactivity and shell job control. "
|
||||
@ -298,7 +298,7 @@ static int handle_arg_console(const char *arg) {
|
||||
|
||||
arg_console_mode = CONSOLE_PIPE;
|
||||
} else if (streq(arg, "autopipe")) {
|
||||
if (isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO))
|
||||
if (isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO))
|
||||
arg_console_mode = CONSOLE_INTERACTIVE;
|
||||
else
|
||||
arg_console_mode = CONSOLE_PIPE;
|
||||
@ -5981,7 +5981,7 @@ static int run(int argc, char *argv[]) {
|
||||
umask(0022);
|
||||
|
||||
if (arg_console_mode < 0)
|
||||
arg_console_mode = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) ?
|
||||
arg_console_mode = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) ?
|
||||
CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY;
|
||||
|
||||
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
|
||||
|
431
src/run/run.c
431
src/run/run.c
@ -22,6 +22,7 @@
|
||||
#include "chase.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "event-util.h"
|
||||
#include "exec-util.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
@ -85,6 +86,7 @@ static char *arg_exec_path = NULL;
|
||||
static bool arg_ignore_failure = false;
|
||||
static char *arg_background = NULL;
|
||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
||||
static char *arg_shell_prompt_prefix = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
|
||||
@ -96,6 +98,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_shell_prompt_prefix, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@ -171,6 +174,10 @@ static int help_sudo_mode(void) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with
|
||||
* sudo's short switches, hence please do not introduce new short switches unless they have a roughly
|
||||
* equivalent purpose on sudo. Use long options for everything private to run0. */
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
|
||||
"\n%sElevate privileges interactively.%s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
@ -188,6 +195,9 @@ static int help_sudo_mode(void) {
|
||||
" -D --chdir=PATH Set working directory\n"
|
||||
" --setenv=NAME[=VALUE] Set environment variable\n"
|
||||
" --background=COLOR Set ANSI color for background\n"
|
||||
" --pty Request allocation of a pseudo TTY for stdio\n"
|
||||
" --pipe Request direct pipe for stdio\n"
|
||||
" --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
@ -674,7 +684,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
/* If we both --pty and --pipe are specified we'll automatically pick --pty if we are connected fully
|
||||
* to a TTY and pick direct fd passing otherwise. This way, we automatically adapt to usage in a shell
|
||||
* pipeline, but we are neatly interactive with tty-level isolation otherwise. */
|
||||
arg_stdio = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ?
|
||||
arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ?
|
||||
ARG_STDIO_PTY :
|
||||
ARG_STDIO_DIRECT;
|
||||
|
||||
@ -770,27 +780,33 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
ARG_NICE,
|
||||
ARG_SETENV,
|
||||
ARG_BACKGROUND,
|
||||
ARG_PTY,
|
||||
ARG_PIPE,
|
||||
ARG_SHELL_PROMPT_PREFIX,
|
||||
};
|
||||
|
||||
/* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions
|
||||
* though (but limit the extension to long options). */
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{ "machine", required_argument, NULL, ARG_MACHINE },
|
||||
{ "unit", required_argument, NULL, ARG_UNIT },
|
||||
{ "property", required_argument, NULL, ARG_PROPERTY },
|
||||
{ "description", required_argument, NULL, ARG_DESCRIPTION },
|
||||
{ "slice", required_argument, NULL, ARG_SLICE },
|
||||
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
|
||||
{ "user", required_argument, NULL, 'u' },
|
||||
{ "group", required_argument, NULL, 'g' },
|
||||
{ "nice", required_argument, NULL, ARG_NICE },
|
||||
{ "chdir", required_argument, NULL, 'D' },
|
||||
{ "setenv", required_argument, NULL, ARG_SETENV },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{ "machine", required_argument, NULL, ARG_MACHINE },
|
||||
{ "unit", required_argument, NULL, ARG_UNIT },
|
||||
{ "property", required_argument, NULL, ARG_PROPERTY },
|
||||
{ "description", required_argument, NULL, ARG_DESCRIPTION },
|
||||
{ "slice", required_argument, NULL, ARG_SLICE },
|
||||
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
|
||||
{ "user", required_argument, NULL, 'u' },
|
||||
{ "group", required_argument, NULL, 'g' },
|
||||
{ "nice", required_argument, NULL, ARG_NICE },
|
||||
{ "chdir", required_argument, NULL, 'D' },
|
||||
{ "setenv", required_argument, NULL, ARG_SETENV },
|
||||
{ "background", required_argument, NULL, ARG_BACKGROUND },
|
||||
{ "pty", no_argument, NULL, ARG_PTY },
|
||||
{ "pipe", no_argument, NULL, ARG_PIPE },
|
||||
{ "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX },
|
||||
{},
|
||||
};
|
||||
|
||||
@ -883,6 +899,26 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
|
||||
break;
|
||||
|
||||
case ARG_PTY:
|
||||
if (IN_SET(arg_stdio, ARG_STDIO_DIRECT, ARG_STDIO_AUTO)) /* if --pipe is already used, upgrade to auto mode */
|
||||
arg_stdio = ARG_STDIO_AUTO;
|
||||
else
|
||||
arg_stdio = ARG_STDIO_PTY;
|
||||
break;
|
||||
|
||||
case ARG_PIPE:
|
||||
if (IN_SET(arg_stdio, ARG_STDIO_PTY, ARG_STDIO_AUTO)) /* If --pty is already used, upgrade to auto mode */
|
||||
arg_stdio = ARG_STDIO_AUTO;
|
||||
else
|
||||
arg_stdio = ARG_STDIO_DIRECT;
|
||||
break;
|
||||
|
||||
case ARG_SHELL_PROMPT_PREFIX:
|
||||
r = free_and_strdup_warn(&arg_shell_prompt_prefix, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@ -913,7 +949,9 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
arg_wait = true;
|
||||
arg_aggressive_gc = true;
|
||||
|
||||
arg_stdio = isatty_safe(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
|
||||
if (IN_SET(arg_stdio, ARG_STDIO_NONE, ARG_STDIO_AUTO))
|
||||
arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
|
||||
|
||||
arg_expand_environment = false;
|
||||
arg_send_sighup = true;
|
||||
|
||||
@ -993,6 +1031,25 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) {
|
||||
log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m");
|
||||
}
|
||||
|
||||
if (!arg_shell_prompt_prefix) {
|
||||
const char *e = secure_getenv("SYSTEMD_RUN_SHELL_PROMPT_PREFIX");
|
||||
if (e) {
|
||||
arg_shell_prompt_prefix = strdup(e);
|
||||
if (!arg_shell_prompt_prefix)
|
||||
return log_oom();
|
||||
} else if (emoji_enabled()) {
|
||||
arg_shell_prompt_prefix = strjoin(special_glyph(SPECIAL_GLYPH_SUPERHERO), " ");
|
||||
if (!arg_shell_prompt_prefix)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isempty(arg_shell_prompt_prefix)) {
|
||||
r = strv_env_assign(&arg_environment, "SHELL_PROMPT_PREFIX", arg_shell_prompt_prefix);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set $SHELL_PROMPT_PREFIX environment variable: %m");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1181,7 +1238,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
send_term = isatty_safe(STDIN_FILENO) || isatty(STDOUT_FILENO) || isatty(STDERR_FILENO);
|
||||
send_term = isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO);
|
||||
}
|
||||
|
||||
if (send_term) {
|
||||
@ -1345,78 +1402,75 @@ static int transient_timer_set_properties(sd_bus_message *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_unit_name(sd_bus *bus, UnitType t, char **ret) {
|
||||
unsigned soft_reboots_count = 0;
|
||||
const char *unique, *id;
|
||||
char *p;
|
||||
static int make_unit_name(UnitType t, char **ret) {
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(t >= 0);
|
||||
assert(t < _UNIT_TYPE_MAX);
|
||||
assert(ret);
|
||||
|
||||
r = sd_bus_get_unique_name(bus, &unique);
|
||||
/* Preferably use our PID + pidfd ID as identifier, if available. It's a boot time unique identifier
|
||||
* managed by the kernel. Unfortunately only new kernels support this, hence we keep some fallback
|
||||
* logic in place. */
|
||||
|
||||
_cleanup_(pidref_done) PidRef self = PIDREF_NULL;
|
||||
r = pidref_set_self(&self);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get reference to my own process: %m");
|
||||
|
||||
r = pidref_acquire_pidfd_id(&self);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to acquire pidfd ID of myself, defaulting to randomized unit name: %m");
|
||||
|
||||
/* We couldn't get the pidfd id. In that case, just pick a random uuid as name */
|
||||
sd_id128_t rnd;
|
||||
|
||||
/* We couldn't get the unique name, which is a pretty
|
||||
* common case if we are connected to systemd
|
||||
* directly. In that case, just pick a random uuid as
|
||||
* name */
|
||||
|
||||
r = sd_id128_randomize(&rnd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate random run unit name: %m");
|
||||
|
||||
if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0)
|
||||
return log_oom();
|
||||
r = asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t));
|
||||
} else
|
||||
r = asprintf(ret, "run-p" PID_FMT "-i%" PRIu64 ".%s", self.pid, self.fd_id, unit_type_to_string(t));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We managed to get the unique name, then let's use that to name our transient units. */
|
||||
static int connect_bus(sd_bus **ret) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
|
||||
id = startswith(unique, ":1."); /* let' strip the usual prefix */
|
||||
if (!id)
|
||||
id = startswith(unique, ":"); /* the spec only requires things to start with a colon, hence
|
||||
* let's add a generic fallback for that. */
|
||||
if (!id)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Unique name %s has unexpected format.",
|
||||
unique);
|
||||
assert(ret);
|
||||
|
||||
/* The unique D-Bus names are actually unique per D-Bus instance, so on soft-reboot they will wrap
|
||||
* and start over since the D-Bus broker is restarted. If there's a failed unit left behind that
|
||||
* hasn't been garbage collected, we'll conflict. Append the soft-reboot counter to avoid clashing. */
|
||||
if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
r = bus_get_property_trivial(
|
||||
bus, bus_systemd_mgr, "SoftRebootsCount", &error, 'u', &soft_reboots_count);
|
||||
if (r < 0)
|
||||
log_debug_errno(r,
|
||||
"Failed to get SoftRebootsCount property, ignoring: %s",
|
||||
bus_error_message(&error, r));
|
||||
}
|
||||
/* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the
|
||||
* limited direct connection */
|
||||
if (arg_wait ||
|
||||
arg_stdio != ARG_STDIO_NONE ||
|
||||
(arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)))
|
||||
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
|
||||
else
|
||||
r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
||||
|
||||
if (soft_reboots_count > 0) {
|
||||
if (asprintf(&p, "run-u%s-s%u.%s", id, soft_reboots_count, unit_type_to_string(t)) < 0)
|
||||
return log_oom();
|
||||
} else {
|
||||
p = strjoin("run-u", id, ".", unit_type_to_string(t));
|
||||
if (!p)
|
||||
return log_oom();
|
||||
}
|
||||
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
||||
|
||||
*ret = p;
|
||||
*ret = TAKE_PTR(bus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct RunContext {
|
||||
sd_bus *bus;
|
||||
sd_event *event;
|
||||
PTYForward *forward;
|
||||
sd_bus_slot *match;
|
||||
char *service;
|
||||
char *bus_path;
|
||||
|
||||
/* Bus objects */
|
||||
sd_bus *bus;
|
||||
sd_bus_slot *match_properties_changed;
|
||||
sd_bus_slot *match_disconnected;
|
||||
sd_event_source *retry_timer;
|
||||
|
||||
/* Current state of the unit */
|
||||
char *active_state;
|
||||
@ -1437,16 +1491,72 @@ typedef struct RunContext {
|
||||
uint32_t exit_status;
|
||||
} RunContext;
|
||||
|
||||
static void run_context_free(RunContext *c) {
|
||||
static int run_context_update(RunContext *c);
|
||||
static int run_context_attach_bus(RunContext *c, sd_bus *bus);
|
||||
static void run_context_detach_bus(RunContext *c);
|
||||
static int run_context_reconnect(RunContext *c);
|
||||
|
||||
static void run_context_done(RunContext *c) {
|
||||
assert(c);
|
||||
|
||||
run_context_detach_bus(c);
|
||||
|
||||
c->retry_timer = sd_event_source_disable_unref(c->retry_timer);
|
||||
c->forward = pty_forward_free(c->forward);
|
||||
c->match = sd_bus_slot_unref(c->match);
|
||||
c->bus = sd_bus_unref(c->bus);
|
||||
c->event = sd_event_unref(c->event);
|
||||
|
||||
free(c->active_state);
|
||||
free(c->result);
|
||||
free(c->bus_path);
|
||||
free(c->service);
|
||||
}
|
||||
|
||||
static int on_retry_timer(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
RunContext *c = ASSERT_PTR(userdata);
|
||||
|
||||
c->retry_timer = sd_event_source_disable_unref(c->retry_timer);
|
||||
|
||||
return run_context_reconnect(c);
|
||||
}
|
||||
|
||||
static int run_context_reconnect(RunContext *c) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
run_context_detach_bus(c);
|
||||
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
r = connect_bus(&bus);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to reconnect, retrying in 2s: %m");
|
||||
|
||||
r = event_reset_time_relative(
|
||||
c->event,
|
||||
&c->retry_timer,
|
||||
CLOCK_MONOTONIC,
|
||||
2 * USEC_PER_SEC, /* accuracy= */ 0,
|
||||
on_retry_timer, c,
|
||||
SD_EVENT_PRIORITY_NORMAL,
|
||||
"retry-timeout",
|
||||
/* force_reset= */ false);
|
||||
if (r < 0) {
|
||||
(void) sd_event_exit(c->event, EXIT_FAILURE);
|
||||
return log_error_errno(r, "Failed to install retry timer: %m");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = run_context_attach_bus(c, bus);
|
||||
if (r < 0) {
|
||||
(void) sd_event_exit(c->event, EXIT_FAILURE);
|
||||
return r;
|
||||
}
|
||||
|
||||
log_info("Reconnected to bus.");
|
||||
|
||||
return run_context_update(c);
|
||||
}
|
||||
|
||||
static void run_context_check_done(RunContext *c) {
|
||||
@ -1454,16 +1564,13 @@ static void run_context_check_done(RunContext *c) {
|
||||
|
||||
assert(c);
|
||||
|
||||
if (c->match)
|
||||
done = STRPTR_IN_SET(c->active_state, "inactive", "failed") && !c->has_job;
|
||||
else
|
||||
done = true;
|
||||
done = STRPTR_IN_SET(c->active_state, "inactive", "failed") && !c->has_job;
|
||||
|
||||
if (c->forward && !pty_forward_is_done(c->forward) && done) /* If the service is gone, it's time to drain the output */
|
||||
done = pty_forward_drain(c->forward);
|
||||
|
||||
if (done)
|
||||
sd_event_exit(c->event, EXIT_SUCCESS);
|
||||
(void) sd_event_exit(c->event, EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static int map_job(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
|
||||
@ -1480,7 +1587,7 @@ static int map_job(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_er
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_context_update(RunContext *c, const char *path) {
|
||||
static int run_context_update(RunContext *c) {
|
||||
|
||||
static const struct bus_properties_map map[] = {
|
||||
{ "ActiveState", "s", NULL, offsetof(RunContext, active_state) },
|
||||
@ -1503,16 +1610,35 @@ static int run_context_update(RunContext *c, const char *path) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
int r;
|
||||
|
||||
r = bus_map_all_properties(c->bus,
|
||||
"org.freedesktop.systemd1",
|
||||
path,
|
||||
map,
|
||||
BUS_MAP_STRDUP,
|
||||
&error,
|
||||
NULL,
|
||||
c);
|
||||
assert(c);
|
||||
assert(c->bus);
|
||||
|
||||
r = bus_map_all_properties(
|
||||
c->bus,
|
||||
"org.freedesktop.systemd1",
|
||||
c->bus_path,
|
||||
map,
|
||||
BUS_MAP_STRDUP,
|
||||
&error,
|
||||
NULL,
|
||||
c);
|
||||
if (r < 0) {
|
||||
sd_event_exit(c->event, EXIT_FAILURE);
|
||||
/* If this is a connection error, then try to reconnect. This might be because the service
|
||||
* manager is being restarted. Handle this gracefully. */
|
||||
if (sd_bus_error_has_names(
|
||||
&error,
|
||||
SD_BUS_ERROR_NO_REPLY,
|
||||
SD_BUS_ERROR_DISCONNECTED,
|
||||
SD_BUS_ERROR_TIMED_OUT,
|
||||
SD_BUS_ERROR_SERVICE_UNKNOWN,
|
||||
SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
|
||||
|
||||
log_info("Bus call failed due to connection problems. Trying to reconnect...");
|
||||
/* Not propagating error, because we handled it already, by reconnecting. */
|
||||
return run_context_reconnect(c);
|
||||
}
|
||||
|
||||
(void) sd_event_exit(c->event, EXIT_FAILURE);
|
||||
return log_error_errno(r, "Failed to query unit state: %s", bus_error_message(&error, r));
|
||||
}
|
||||
|
||||
@ -1521,11 +1647,67 @@ static int run_context_update(RunContext *c, const char *path) {
|
||||
}
|
||||
|
||||
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
RunContext *c = ASSERT_PTR(userdata);
|
||||
return run_context_update(ASSERT_PTR(userdata));
|
||||
}
|
||||
|
||||
assert(m);
|
||||
static int on_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
/* If our connection gets terminated, then try to reconnect. This might be because the service
|
||||
* manager is being restarted. Handle this gracefully. */
|
||||
log_info("Got disconnected from bus connection. Trying to reconnect...");
|
||||
return run_context_reconnect(ASSERT_PTR(userdata));
|
||||
}
|
||||
|
||||
return run_context_update(c, sd_bus_message_get_path(m));
|
||||
static int run_context_attach_bus(RunContext *c, sd_bus *bus) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(bus);
|
||||
|
||||
assert(!c->bus);
|
||||
assert(!c->match_properties_changed);
|
||||
assert(!c->match_disconnected);
|
||||
|
||||
c->bus = sd_bus_ref(bus);
|
||||
|
||||
r = sd_bus_match_signal_async(
|
||||
c->bus,
|
||||
&c->match_properties_changed,
|
||||
"org.freedesktop.systemd1",
|
||||
c->bus_path,
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
on_properties_changed, NULL, c);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to request PropertiesChanged signal match: %m");
|
||||
|
||||
r = sd_bus_match_signal_async(
|
||||
bus,
|
||||
&c->match_disconnected,
|
||||
"org.freedesktop.DBus.Local",
|
||||
/* path= */ NULL,
|
||||
"org.freedesktop.DBus.Local",
|
||||
"Disconnected",
|
||||
on_disconnected, NULL, c);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to request Disconnected signal match: %m");
|
||||
|
||||
r = sd_bus_attach_event(c->bus, c->event, SD_EVENT_PRIORITY_NORMAL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach bus to event loop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_context_detach_bus(RunContext *c) {
|
||||
assert(c);
|
||||
|
||||
if (c->bus) {
|
||||
(void) sd_bus_detach_event(c->bus);
|
||||
c->bus = sd_bus_unref(c->bus);
|
||||
}
|
||||
|
||||
c->match_properties_changed = sd_bus_slot_unref(c->match_properties_changed);
|
||||
c->match_disconnected = sd_bus_slot_unref(c->match_disconnected);
|
||||
}
|
||||
|
||||
static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
|
||||
@ -1541,7 +1723,7 @@ static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
|
||||
/* If --wait is specified, we'll only exit the pty forwarding, but will continue to wait
|
||||
* for the service to end. If the user hits ^C we'll exit too. */
|
||||
} else if (rcode < 0) {
|
||||
sd_event_exit(c->event, EXIT_FAILURE);
|
||||
(void) sd_event_exit(c->event, EXIT_FAILURE);
|
||||
return log_error_errno(rcode, "Error on PTY forwarding logic: %m");
|
||||
}
|
||||
|
||||
@ -1818,7 +2000,7 @@ static int start_transient_service(sd_bus *bus) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to mangle unit name: %m");
|
||||
} else {
|
||||
r = make_unit_name(bus, UNIT_SERVICE, &service);
|
||||
r = make_unit_name(UNIT_SERVICE, &service);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -1860,7 +2042,7 @@ static int start_transient_service(sd_bus *bus) {
|
||||
}
|
||||
|
||||
if (arg_wait || arg_stdio != ARG_STDIO_NONE) {
|
||||
_cleanup_(run_context_free) RunContext c = {
|
||||
_cleanup_(run_context_done) RunContext c = {
|
||||
.cpu_usage_nsec = NSEC_INFINITY,
|
||||
.memory_peak = UINT64_MAX,
|
||||
.memory_swap_peak = UINT64_MAX,
|
||||
@ -1871,14 +2053,19 @@ static int start_transient_service(sd_bus *bus) {
|
||||
.inactive_exit_usec = USEC_INFINITY,
|
||||
.inactive_enter_usec = USEC_INFINITY,
|
||||
};
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
c.bus = sd_bus_ref(bus);
|
||||
|
||||
r = sd_event_default(&c.event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get event loop: %m");
|
||||
|
||||
c.service = strdup(service);
|
||||
if (!c.service)
|
||||
return log_oom();
|
||||
|
||||
c.bus_path = unit_dbus_path_from_name(service);
|
||||
if (!c.bus_path)
|
||||
return log_oom();
|
||||
|
||||
if (master >= 0) {
|
||||
(void) sd_event_set_signal_exit(c.event, true);
|
||||
|
||||
@ -1900,26 +2087,11 @@ static int start_transient_service(sd_bus *bus) {
|
||||
set_window_title(c.forward);
|
||||
}
|
||||
|
||||
path = unit_dbus_path_from_name(service);
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
r = sd_bus_match_signal_async(
|
||||
bus,
|
||||
&c.match,
|
||||
"org.freedesktop.systemd1",
|
||||
path,
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
on_properties_changed, NULL, &c);
|
||||
r = run_context_attach_bus(&c, bus);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to request properties changed signal match: %m");
|
||||
return r;
|
||||
|
||||
r = sd_bus_attach_event(bus, c.event, SD_EVENT_PRIORITY_NORMAL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach bus to event loop: %m");
|
||||
|
||||
r = run_context_update(&c, path);
|
||||
r = run_context_update(&c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -2033,7 +2205,7 @@ static int start_transient_scope(sd_bus *bus) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to mangle scope name: %m");
|
||||
} else {
|
||||
r = make_unit_name(bus, UNIT_SCOPE, &scope);
|
||||
r = make_unit_name(UNIT_SCOPE, &scope);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -2332,7 +2504,7 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
r = make_unit_name(bus, UNIT_SERVICE, &service);
|
||||
r = make_unit_name(UNIT_SERVICE, &service);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -2411,16 +2583,30 @@ static int run(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (!arg_description) {
|
||||
char *t;
|
||||
_cleanup_free_ char *t = NULL;
|
||||
|
||||
if (strv_isempty(arg_cmdline))
|
||||
t = strdup(arg_unit);
|
||||
else
|
||||
else if (startswith(arg_cmdline[0], "-")) {
|
||||
/* Drop the login shell marker from the command line when generating the description,
|
||||
* in order to minimize user confusion. */
|
||||
_cleanup_strv_free_ char **l = strv_copy(arg_cmdline);
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
r = free_and_strdup_warn(l + 0, l[0] + 1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
t = quote_command_line(l, SHELL_ESCAPE_EMPTY);
|
||||
} else
|
||||
t = quote_command_line(arg_cmdline, SHELL_ESCAPE_EMPTY);
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
free_and_replace(arg_description, t);
|
||||
arg_description = strjoin("[", program_invocation_short_name, "] ", t);
|
||||
if (!arg_description)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
/* For backward compatibility reasons env var expansion is disabled by default for scopes, and
|
||||
@ -2435,18 +2621,9 @@ static int run(int argc, char* argv[]) {
|
||||
" Use --expand-environment=yes/no to explicitly control it as needed.");
|
||||
}
|
||||
|
||||
/* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the
|
||||
* limited direct connection */
|
||||
if (arg_wait ||
|
||||
arg_stdio != ARG_STDIO_NONE ||
|
||||
(arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)))
|
||||
r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus);
|
||||
else
|
||||
r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus);
|
||||
r = connect_bus(&bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport, arg_runtime_scope);
|
||||
|
||||
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
||||
return r;
|
||||
|
||||
if (arg_scope)
|
||||
return start_transient_scope(bus);
|
||||
|
@ -82,7 +82,7 @@ TEST(keymaps) {
|
||||
|
||||
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
|
||||
TEST(dump_special_glyphs) {
|
||||
assert_cc(SPECIAL_GLYPH_GREEN_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
|
||||
assert_cc(SPECIAL_GLYPH_SUPERHERO + 1 == _SPECIAL_GLYPH_MAX);
|
||||
|
||||
log_info("is_locale_utf8: %s", yes_no(is_locale_utf8()));
|
||||
|
||||
@ -133,6 +133,7 @@ TEST(dump_special_glyphs) {
|
||||
dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_GREEN_CIRCLE);
|
||||
dump_glyph(SPECIAL_GLYPH_SUPERHERO);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
@ -261,4 +261,17 @@ if [[ -e /usr/lib/pam.d/systemd-run0 ]] || [[ -e /etc/pam.d/systemd-run0 ]]; the
|
||||
assert_eq "$(run0 -D / pwd)" "/"
|
||||
assert_eq "$(run0 --user=testuser pwd)" "/home/testuser"
|
||||
assert_eq "$(run0 -D / --user=testuser pwd)" "/"
|
||||
|
||||
# Verify that all combinations of --pty/--pipe come to the sam results
|
||||
assert_eq "$(run0 echo -n foo)" "foo"
|
||||
assert_eq "$(run0 --pty echo -n foo)" "foo"
|
||||
assert_eq "$(run0 --pipe echo -n foo)" "foo"
|
||||
assert_eq "$(run0 --pipe --pty echo -n foo)" "foo"
|
||||
|
||||
# Validate when we invoke run0 without a tty, that depending on --pty it either allocates a tty or not
|
||||
assert_neq "$(run0 --pty tty < /dev/null)" "not a tty"
|
||||
assert_eq "$(run0 --pipe tty < /dev/null)" "not a tty"
|
||||
fi
|
||||
|
||||
# Tests whether intermediate disconnects corrupt us (modified testcase from https://github.com/systemd/systemd/issues/27204)
|
||||
assert_rc "37" systemd-run --unit=disconnecttest --wait --pipe --user -M testuser@.host bash -ec 'systemctl --user daemon-reexec; sleep 3; exit 37'
|
||||
|
@ -39,6 +39,15 @@ assert_eq() {(
|
||||
fi
|
||||
)}
|
||||
|
||||
assert_neq() {(
|
||||
set +ex
|
||||
|
||||
if [[ "${1?}" = "${2?}" ]]; then
|
||||
echo "FAIL: not expected: '$2' actual: '$1'" >&2
|
||||
exit 1
|
||||
fi
|
||||
)}
|
||||
|
||||
assert_le() {(
|
||||
set +ex
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user