aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO4
-rw-r--r--man/bootctl.xml5
-rw-r--r--meson.build2
-rw-r--r--meson_options.txt2
-rw-r--r--presets/90-systemd.preset1
-rw-r--r--src/basic/fileio.h2
-rw-r--r--src/basic/locale-util.c10
-rw-r--r--src/basic/locale-util.h7
-rw-r--r--src/basic/signal-util.c2
-rw-r--r--src/boot/bootctl.c49
-rw-r--r--src/core/main.c16
-rw-r--r--src/coredump/stacktrace.c2
-rw-r--r--src/cryptenroll/cryptenroll-tpm2.c7
-rw-r--r--src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c10
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c28
-rw-r--r--src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h2
-rw-r--r--src/cryptsetup/cryptsetup-tpm2.c23
-rw-r--r--src/cryptsetup/cryptsetup-tpm2.h4
-rw-r--r--src/cryptsetup/cryptsetup.c4
-rw-r--r--src/machine/machined-varlink.c2
-rw-r--r--src/partition/repart.c5
-rw-r--r--src/shared/btrfs-util.c13
-rw-r--r--src/shared/btrfs-util.h1
-rw-r--r--src/shared/clock-util.c18
-rw-r--r--src/shared/clock-util.h11
-rw-r--r--src/shared/copy.c252
-rw-r--r--src/shared/copy.h22
-rw-r--r--src/shared/creds-util.c15
-rw-r--r--src/shared/import-util.c105
-rw-r--r--src/shared/import-util.h11
-rw-r--r--src/shared/nscd-flush.c2
-rw-r--r--src/shared/rm-rf.c223
-rw-r--r--src/shared/rm-rf.h3
-rw-r--r--src/shared/tpm2-util.c147
-rw-r--r--src/shared/tpm2-util.h11
-rw-r--r--src/shared/varlink.c2
-rw-r--r--src/test/meson.build2
-rw-r--r--src/test/test-import-util.c72
-rw-r--r--src/test/test-locale-util.c5
-rw-r--r--units/systemd-boot-update.service24
40 files changed, 890 insertions, 236 deletions
diff --git a/TODO b/TODO
index 17dff7331dc..16a91fa0236 100644
--- a/TODO
+++ b/TODO
@@ -1019,10 +1019,6 @@ Features:
* bootctl,sd-boot: actually honour the "architecture" key
-* sd-boot: add service that automatically runs "bootctl update" on every boot,
- in a graceful way, so that updated /usr trees automatically propagate into
- updated boot loaders on reboot.
-
* bootctl:
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
diff --git a/man/bootctl.xml b/man/bootctl.xml
index d05c3f34d07..a958cde7df8 100644
--- a/man/bootctl.xml
+++ b/man/bootctl.xml
@@ -233,8 +233,9 @@
<varlistentry>
<term><option>--graceful</option></term>
- <listitem><para>Ignore failure when the EFI System Partition cannot be found, or when EFI variables
- cannot be written. Currently only applies to random seed operations.</para></listitem>
+ <listitem><para>Ignore failure when the EFI System Partition cannot be found, when EFI variables
+ cannot be written, or a different or newer boot loader is already installed. Currently only applies
+ to random seed and update operations.</para></listitem>
</varlistentry>
<varlistentry>
diff --git a/meson.build b/meson.build
index 27dcf956f3e..e08a7662a7f 100644
--- a/meson.build
+++ b/meson.build
@@ -722,6 +722,8 @@ if time_epoch == -1
endif
conf.set('TIME_EPOCH', time_epoch)
+conf.set('CLOCK_VALID_RANGE_USEC_MAX', get_option('clock-valid-range-usec-max'))
+
foreach tuple : [['system-alloc-uid-min', 'SYS_UID_MIN', 1], # Also see login.defs(5).
['system-uid-max', 'SYS_UID_MAX', 999],
['system-alloc-gid-min', 'SYS_GID_MIN', 1],
diff --git a/meson_options.txt b/meson_options.txt
index 0b01fb2cbf2..110a471b6be 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -208,6 +208,8 @@ option('status-unit-format-default', type : 'combo',
description : 'use unit name or description in messages by default')
option('time-epoch', type : 'integer', value : '-1',
description : 'time epoch for time clients')
+option('clock-valid-range-usec-max', type : 'integer', value : '473364000000000', # 15 years
+ description : 'maximum value in microseconds for the difference between RTC and epoch, exceeding which is considered an RTC error')
option('system-alloc-uid-min', type : 'integer', value : '-1',
description : 'minimum system UID used when allocating')
diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset
index d26087445c9..8a1a08210cf 100644
--- a/presets/90-systemd.preset
+++ b/presets/90-systemd.preset
@@ -22,6 +22,7 @@ enable systemd-resolved.service
enable systemd-homed.service
enable systemd-userdbd.socket
enable systemd-pstore.service
+enable systemd-boot-update.service
disable console-getty.service
disable debug-shell.service
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index af797cfafdb..4295b84a85c 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -2,11 +2,11 @@
#pragma once
#include <dirent.h>
+#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/stat.h>
-#include <sys/fcntl.h>
#include <sys/types.h>
#include "macro.h"
diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c
index fd6b01cfaad..007e3a091ec 100644
--- a/src/basic/locale-util.c
+++ b/src/basic/locale-util.c
@@ -375,6 +375,9 @@ const char *special_glyph(SpecialGlyph code) {
[SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[",
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,",
[SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */
+ [SPECIAL_GLYPH_RECYCLING] = "~",
+ [SPECIAL_GLYPH_DOWNLOAD] = "\\",
+ [SPECIAL_GLYPH_SPARKLES] = "*",
},
/* UTF-8 */
@@ -421,7 +424,12 @@ const char *special_glyph(SpecialGlyph code) {
[SPECIAL_GLYPH_LOCK_AND_KEY] = "\360\237\224\220", /* ๐Ÿ” (actually called: CLOSED LOCK WITH KEY) */
/* This emoji is a single character cell glyph in Unicode, and two in ASCII */
- [SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* ๐Ÿ‘† (actually called: BACKHAND INDEX POINTING UP */
+ [SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* ๐Ÿ‘† (actually called: BACKHAND INDEX POINTING UP) */
+
+ /* These three emojis are single character cell glyphs in Unicode and also in ASCII. */
+ [SPECIAL_GLYPH_RECYCLING] = "\u267B\uFE0F ", /* โ™ป๏ธ (actually called: UNIVERSAL RECYCLNG SYMBOL) */
+ [SPECIAL_GLYPH_DOWNLOAD] = "\u2935\uFE0F ", /* โคต๏ธ (actually called: RIGHT ARROW CURVING DOWN) */
+ [SPECIAL_GLYPH_SPARKLES] = "\u2728", /* โœจ */
},
};
diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h
index df259d1bbd9..3430eb6ee1f 100644
--- a/src/basic/locale-util.h
+++ b/src/basic/locale-util.h
@@ -69,6 +69,9 @@ typedef enum SpecialGlyph {
SPECIAL_GLYPH_DEPRESSED_SMILEY,
SPECIAL_GLYPH_LOCK_AND_KEY,
SPECIAL_GLYPH_TOUCH,
+ SPECIAL_GLYPH_RECYCLING,
+ SPECIAL_GLYPH_DOWNLOAD,
+ SPECIAL_GLYPH_SPARKLES,
_SPECIAL_GLYPH_MAX,
_SPECIAL_GLYPH_INVALID = -EINVAL,
} SpecialGlyph;
@@ -95,3 +98,7 @@ static inline void locale_variables_freep(char*(*l)[_VARIABLE_LC_MAX]) {
static inline const char *special_glyph_check_mark(bool b) {
return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK);
}
+
+static inline const char *special_glyph_check_mark_space(bool b) {
+ return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : " ";
+}
diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c
index b06b5ce7744..34b2b279189 100644
--- a/src/basic/signal-util.c
+++ b/src/basic/signal-util.c
@@ -265,7 +265,7 @@ int pop_pending_signal_internal(int sig, ...) {
if (sigemptyset(&ss) < 0)
return -errno;
- /* Add first signal (if the signal is zero, we'll silently skip it, to make it easiert to build
+ /* Add first signal (if the signal is zero, we'll silently skip it, to make it easier to build
* parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles
* this.) */
if (sig > 0 && sigaddset(&ss, sig) < 0)
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index fa8c6003218..5d126f4bea3 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -65,6 +65,7 @@ static const char *arg_dollar_boot_path(void) {
static int acquire_esp(
bool unprivileged_mode,
+ bool graceful,
uint32_t *ret_part,
uint64_t *ret_pstart,
uint64_t *ret_psize,
@@ -80,10 +81,14 @@ static int acquire_esp(
* this). */
r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid);
- if (r == -ENOKEY)
+ if (r == -ENOKEY) {
+ if (graceful)
+ return log_info_errno(r, "Couldn't find EFI system partition, skipping.");
+
return log_error_errno(r,
"Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n"
"Alternatively, use --esp-path= to specify path to mount point.");
+ }
if (r < 0)
return r;
@@ -500,7 +505,7 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t
if (r < 0)
return r;
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
"Source file \"%s\" does not carry version information!",
from);
@@ -508,12 +513,15 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t
if (r < 0)
return r;
if (r == 0 || compare_product(a, b) != 0)
- return log_notice_errno(SYNTHETIC_ERRNO(EEXIST),
+ return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE),
"Skipping \"%s\", since it's owned by another boot loader.",
to);
- if (compare_version(a, b) < 0)
- return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since a newer boot loader version exists already.", to);
+ r = compare_version(a, b);
+ if (r < 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since newer boot loader version in place already.", to);
+ else if (r == 0)
+ return log_info_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since same boot loader version in place already.", to);
return 0;
}
@@ -665,6 +673,10 @@ static int install_binaries(const char *esp_path, bool force) {
continue;
k = copy_one_file(esp_path, de->d_name, force);
+ /* Don't propagate an error code if no update necessary, installed version already equal or
+ * newer version, or other boot loader in place. */
+ if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE))
+ continue;
if (k < 0 && r == 0)
r = k;
}
@@ -1243,7 +1255,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL;
int r, k;
- r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid);
+ r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid);
if (arg_print_esp_path) {
if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only
* error the find_esp_and_warn() won't log on its own) */
@@ -1254,7 +1266,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
puts(arg_esp_path);
}
- r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid);
+ r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, &xbootldr_uuid);
if (arg_print_dollar_boot_path) {
if (r == -EACCES)
return log_error_errno(r, "Failed to determine XBOOTLDR location: %m");
@@ -1402,13 +1414,13 @@ static int verb_list(int argc, char *argv[], void *userdata) {
* off logging about access errors and turn off potentially privileged device probing. Here we're interested in
* the latter but not the former, hence request the mode, and log about EACCES. */
- r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, NULL);
+ r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, NULL);
if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */
return log_error_errno(r, "Failed to determine ESP: %m");
if (r < 0)
return r;
- r = acquire_xbootldr(geteuid() != 0, NULL);
+ r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, NULL);
if (r == -EACCES)
return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
if (r < 0)
@@ -1600,21 +1612,24 @@ static int verb_install(int argc, char *argv[], void *userdata) {
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
- bool install;
+ bool install, graceful;
int r;
- r = acquire_esp(false, &part, &pstart, &psize, &uuid);
+ install = streq(argv[0], "install");
+ graceful = !install && arg_graceful; /* support graceful mode for updates */
+
+ r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid);
+ if (graceful && r == -ENOKEY)
+ return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */
if (r < 0)
return r;
- r = acquire_xbootldr(false, NULL);
+ r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL);
if (r < 0)
return r;
settle_make_machine_id_directory();
- install = streq(argv[0], "install");
-
RUN_WITH_UMASK(0002) {
if (install) {
/* Don't create any of these directories when we are just updating. When we update
@@ -1663,11 +1678,11 @@ static int verb_remove(int argc, char *argv[], void *userdata) {
sd_id128_t uuid = SD_ID128_NULL;
int r, q;
- r = acquire_esp(false, NULL, NULL, NULL, &uuid);
+ r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid);
if (r < 0)
return r;
- r = acquire_xbootldr(false, NULL);
+ r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL);
if (r < 0)
return r;
@@ -1726,7 +1741,7 @@ static int verb_is_installed(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *p = NULL;
int r;
- r = acquire_esp(false, NULL, NULL, NULL, NULL);
+ r = acquire_esp(/* privileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, NULL);
if (r < 0)
return r;
diff --git a/src/core/main.c b/src/core/main.c
index 8920d70d5d7..473dc0920ea 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -83,6 +83,7 @@
#include "switch-root.h"
#include "sysctl-util.h"
#include "terminal-util.h"
+#include "time-util.h"
#include "umask-util.h"
#include "user-util.h"
#include "util.h"
@@ -1598,11 +1599,18 @@ static void initialize_clock(void) {
*/
(void) clock_reset_timewarp();
- r = clock_apply_epoch();
- if (r < 0)
- log_error_errno(r, "Current system time is before build time, but cannot correct: %m");
- else if (r > 0)
+ ClockChangeDirection change_dir;
+ r = clock_apply_epoch(&change_dir);
+ if (r > 0 && change_dir == CLOCK_CHANGE_FORWARD)
log_info("System time before build time, advancing clock.");
+ else if (r > 0 && change_dir == CLOCK_CHANGE_BACKWARD)
+ log_info("System time is further ahead than %s after build time, resetting clock to build time.",
+ FORMAT_TIMESPAN(CLOCK_VALID_RANGE_USEC_MAX, USEC_PER_DAY));
+ else if (r < 0 && change_dir == CLOCK_CHANGE_FORWARD)
+ log_error_errno(r, "Current system time is before build time, but cannot correct: %m");
+ else if (r < 0 && change_dir == CLOCK_CHANGE_BACKWARD)
+ log_error_errno(r, "Current system time is further ahead %s after build time, but cannot correct: %m",
+ FORMAT_TIMESPAN(CLOCK_VALID_RANGE_USEC_MAX, USEC_PER_DAY));
}
static void apply_clock_update(void) {
diff --git a/src/coredump/stacktrace.c b/src/coredump/stacktrace.c
index 0edb1b40a72..e46b324cdf7 100644
--- a/src/coredump/stacktrace.c
+++ b/src/coredump/stacktrace.c
@@ -153,6 +153,8 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e
program_header->p_offset,
program_header->p_filesz,
ELF_T_NHDR);
+ if (!data)
+ continue;
while (note_offset < data->d_size &&
(note_offset = gelf_getnote(data, note_offset, &note_header, &name_offset, &desc_offset)) > 0) {
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
index 9c1478c474e..697b4c2335b 100644
--- a/src/cryptenroll/cryptenroll-tpm2.c
+++ b/src/cryptenroll/cryptenroll-tpm2.c
@@ -65,6 +65,7 @@ int enroll_tpm2(struct crypt_device *cd,
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
size_t secret_size, secret2_size, blob_size, hash_size;
_cleanup_free_ void *blob = NULL, *hash = NULL;
+ uint16_t pcr_bank;
const char *node;
int r, keyslot;
@@ -75,7 +76,7 @@ int enroll_tpm2(struct crypt_device *cd,
assert_se(node = crypt_get_device_name(cd));
- r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
+ r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank);
if (r < 0)
return r;
@@ -92,7 +93,7 @@ int enroll_tpm2(struct crypt_device *cd,
/* Quick verification that everything is in order, we are not in a hurry after all. */
log_debug("Unsealing for verification...");
- r = tpm2_unseal(device, pcr_mask, blob, blob_size, hash, hash_size, &secret2, &secret2_size);
+ r = tpm2_unseal(device, pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &secret2, &secret2_size);
if (r < 0)
return r;
@@ -118,7 +119,7 @@ int enroll_tpm2(struct crypt_device *cd,
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
- r = tpm2_make_luks2_json(keyslot, pcr_mask, blob, blob_size, hash, hash_size, &v);
+ r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
index 152b06b111a..d3aa092f6b8 100644
--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -57,6 +57,7 @@ _public_ int cryptsetup_token_open(
const char *json;
size_t blob_size, policy_hash_size, decrypted_key_size;
uint32_t pcr_mask;
+ uint16_t pcr_bank;
systemd_tpm2_plugin_params params = {
.search_pcr_mask = UINT32_MAX
};
@@ -77,7 +78,7 @@ _public_ int cryptsetup_token_open(
if (usrptr)
params = *(systemd_tpm2_plugin_params *)usrptr;
- r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &base64_blob, &hex_policy_hash);
+ r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash);
if (r < 0)
return log_debug_open_error(cd, r);
@@ -93,6 +94,7 @@ _public_ int cryptsetup_token_open(
r = acquire_luks2_key(
pcr_mask,
+ pcr_bank,
params.device,
blob,
blob_size,
@@ -133,6 +135,7 @@ _public_ void cryptsetup_token_dump(
int r;
uint32_t i, pcr_mask;
+ uint16_t pcr_bank;
size_t decoded_blob_size;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL,
*pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL;
@@ -140,7 +143,7 @@ _public_ void cryptsetup_token_dump(
assert(json);
- r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &base64_blob, &hex_policy_hash);
+ r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
@@ -162,7 +165,8 @@ _public_ void cryptsetup_token_dump(
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
- crypt_log(cd, "\ttpm2-pcrs: %s\n", pcrs_str ?: "");
+ crypt_log(cd, "\ttpm2-pcrs: %s\n", strna(pcrs_str));
+ crypt_log(cd, "\ttpm2-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
crypt_log(cd, "\ttmp2-blob: %s\n", blob_str);
crypt_log(cd, "\ttmp2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
index 00540659266..a5571f31f6d 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
@@ -10,6 +10,7 @@
int acquire_luks2_key(
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const char *device,
const void *key_data,
size_t key_data_size,
@@ -34,7 +35,12 @@ int acquire_luks2_key(
device = auto_device;
}
- return tpm2_unseal(device, pcr_mask, key_data, key_data_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
+ return tpm2_unseal(
+ device,
+ pcr_mask, pcr_bank,
+ key_data, key_data_size,
+ policy_hash, policy_hash_size,
+ ret_decrypted_key, ret_decrypted_key_size);
}
/* this function expects valid "systemd-tpm2" in json */
@@ -42,19 +48,22 @@ int parse_luks2_tpm2_data(
const char *json,
uint32_t search_pcr_mask,
uint32_t *ret_pcr_mask,
+ uint16_t *ret_pcr_bank,
char **ret_base64_blob,
char **ret_hex_policy_hash) {
int r;
JsonVariant *w, *e;
uint32_t pcr_mask = 0;
+ uint16_t pcr_bank = UINT16_MAX;
_cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
assert(json);
+ assert(ret_pcr_mask);
+ assert(ret_pcr_bank);
assert(ret_base64_blob);
assert(ret_hex_policy_hash);
- assert(ret_pcr_mask);
r = json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
@@ -81,6 +90,20 @@ int parse_luks2_tpm2_data(
search_pcr_mask != pcr_mask)
return -ENXIO;
+ w = json_variant_by_key(v, "tpm2-pcr-bank");
+ if (w) {
+ /* The PCR bank field is optional */
+
+ if (!json_variant_is_string(w))
+ return -EINVAL;
+
+ r = tpm2_pcr_bank_from_string(json_variant_string(w));
+ if (r < 0)
+ return r;
+
+ pcr_bank = r;
+ }
+
w = json_variant_by_key(v, "tpm2-blob");
if (!w || !json_variant_is_string(w))
return -EINVAL;
@@ -98,6 +121,7 @@ int parse_luks2_tpm2_data(
return -ENOMEM;
*ret_pcr_mask = pcr_mask;
+ *ret_pcr_bank = pcr_bank;
*ret_base64_blob = TAKE_PTR(base64_blob);
*ret_hex_policy_hash = TAKE_PTR(hex_policy_hash);
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
index d36623baf96..1a20f2cc1fd 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
@@ -6,6 +6,7 @@ struct crypt_device;
int acquire_luks2_key(
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const char *device,
const void *key_data,
size_t key_data_size,
@@ -18,5 +19,6 @@ int parse_luks2_tpm2_data(
const char *json,
uint32_t search_pcr_mask,
uint32_t *ret_pcr_mask,
+ uint16_t *ret_pcr_bank,
char **ret_base64_blob,
char **ret_hex_policy_hash);
diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c
index 4757c5882d0..8da1880a35c 100644
--- a/src/cryptsetup/cryptsetup-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tpm2.c
@@ -13,6 +13,7 @@ int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
@@ -62,7 +63,7 @@ int acquire_tpm2_key(
blob = loaded_blob;
}
- return tpm2_unseal(device, pcr_mask, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
+ return tpm2_unseal(device, pcr_mask, pcr_bank, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
}
int find_tpm2_auto_data(
@@ -70,6 +71,7 @@ int find_tpm2_auto_data(
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
+ uint16_t *ret_pcr_bank,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
@@ -81,6 +83,7 @@ int find_tpm2_auto_data(
size_t blob_size = 0, policy_hash_size = 0;
int r, keyslot = -1, token = -1;
uint32_t pcr_mask = 0;
+ uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
assert(cd);
@@ -119,6 +122,23 @@ int find_tpm2_auto_data(
search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */
continue;
+ /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded to SHA256 */
+ assert(pcr_bank == UINT16_MAX);
+ w = json_variant_by_key(v, "tpm2-pcr-bank");
+ if (w) {
+ /* The PCR bank field is optional */
+
+ if (!json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "TPM2 PCR bank is not a string.");
+
+ r = tpm2_pcr_bank_from_string(json_variant_string(w));
+ if (r < 0)
+ return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w));
+
+ pcr_bank = r;
+ }
+
assert(!blob);
w = json_variant_by_key(v, "tpm2-blob");
if (!w || !json_variant_is_string(w))
@@ -163,6 +183,7 @@ int find_tpm2_auto_data(
*ret_policy_hash_size = policy_hash_size;
*ret_keyslot = keyslot;
*ret_token = token;
+ *ret_pcr_bank = pcr_bank;
return 0;
}
diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h
index 8ddf301a635..a82ecb45941 100644
--- a/src/cryptsetup/cryptsetup-tpm2.h
+++ b/src/cryptsetup/cryptsetup-tpm2.h
@@ -13,6 +13,7 @@ int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
@@ -28,6 +29,7 @@ int find_tpm2_auto_data(
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
+ uint16_t *ret_pcr_bank,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
@@ -41,6 +43,7 @@ static inline int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
@@ -60,6 +63,7 @@ static inline int find_tpm2_auto_data(
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
+ uint16_t *ret_pcr_bank,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 48621ef5838..0d3ea9cda64 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -1103,6 +1103,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
name,
arg_tpm2_device,
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
+ UINT16_MAX,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
NULL, 0, /* we don't know the policy hash */
@@ -1139,12 +1140,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
for (;;) {
uint32_t pcr_mask;
+ uint16_t pcr_bank;
r = find_tpm2_auto_data(
cd,
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
token, /* search for the token with this index, or any later index than this */
&pcr_mask,
+ &pcr_bank,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
&keyslot,
@@ -1166,6 +1169,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
name,
arg_tpm2_device,
pcr_mask,
+ pcr_bank,
NULL, 0, 0, /* no key file */
blob, blob_size,
policy_hash, policy_hash_size,
diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c
index 009d283acc6..fc0b0f11ad9 100644
--- a/src/machine/machined-varlink.c
+++ b/src/machine/machined-varlink.c
@@ -297,7 +297,7 @@ static int group_lookup_name(Manager *m, const char *name, gid_t *ret_gid, char
desc = mfree(desc);
*ret_gid = converted_gid;
- *ret_description = desc;
+ *ret_description = TAKE_PTR(desc);
return 0;
}
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 6d535a3bab0..42a8260c2e2 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -2624,9 +2624,10 @@ static int partition_encrypt(
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
size_t secret_size, blob_size, hash_size;
+ uint16_t pcr_bank;
int keyslot;
- r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
+ r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
@@ -2648,7 +2649,7 @@ static int partition_encrypt(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
- r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, blob, blob_size, hash, hash_size, &v);
+ r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c
index f4e291385be..00a6a37ff70 100644
--- a/src/shared/btrfs-util.c
+++ b/src/shared/btrfs-util.c
@@ -1613,7 +1613,7 @@ int btrfs_subvol_snapshot_fd_full(
return -EISDIR;
r = btrfs_subvol_make(new_path);
- if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
+ if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
/* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */
if (mkdir(new_path, 0755) < 0)
return -errno;
@@ -1624,8 +1624,15 @@ int btrfs_subvol_snapshot_fd_full(
r = copy_directory_fd_full(
old_fd, new_path,
- COPY_MERGE|COPY_REFLINK|COPY_SAME_MOUNT|COPY_HARDLINKS|(FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0),
- progress_path, progress_bytes, userdata);
+ COPY_MERGE_EMPTY|
+ COPY_REFLINK|
+ COPY_SAME_MOUNT|
+ COPY_HARDLINKS|
+ (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGINT) ? COPY_SIGINT : 0)|
+ (FLAGS_SET(flags, BTRFS_SNAPSHOT_SIGTERM) ? COPY_SIGTERM : 0),
+ progress_path,
+ progress_bytes,
+ userdata);
if (r < 0)
goto fallback_fail;
diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h
index 7b18f57719a..b67a4c10fe8 100644
--- a/src/shared/btrfs-util.h
+++ b/src/shared/btrfs-util.h
@@ -35,6 +35,7 @@ typedef enum BtrfsSnapshotFlags {
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 1 << 4, /* If the destination doesn't support subvolumes, reflink/copy instead */
BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 1 << 5, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */
BTRFS_SNAPSHOT_SIGINT = 1 << 6, /* Check for SIGINT regularly, and return EINTR if seen */
+ BTRFS_SNAPSHOT_SIGTERM = 1 << 7, /* Ditto, but for SIGTERM */
} BtrfsSnapshotFlags;
typedef enum BtrfsRemoveFlags {
diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c
index b446daf5819..7c1c48d0690 100644
--- a/src/shared/clock-util.c
+++ b/src/shared/clock-util.c
@@ -139,10 +139,15 @@ int clock_reset_timewarp(void) {
#define EPOCH_FILE "/usr/lib/clock-epoch"
-int clock_apply_epoch(void) {
+int clock_apply_epoch(ClockChangeDirection *ret_attempted_change) {
struct stat st;
struct timespec ts;
- usec_t epoch_usec;
+ usec_t epoch_usec, now_usec;
+
+ /* NB: we update *ret_attempted_change in *all* cases, both
+ * on success and failure, to indicate what we intended to do! */
+
+ assert(ret_attempted_change);
if (stat(EPOCH_FILE, &st) < 0) {
if (errno != ENOENT)
@@ -152,8 +157,15 @@ int clock_apply_epoch(void) {
} else
epoch_usec = timespec_load(&st.st_mtim);
- if (now(CLOCK_REALTIME) >= epoch_usec)
+ now_usec = now(CLOCK_REALTIME);
+ if (now_usec < epoch_usec)
+ *ret_attempted_change = CLOCK_CHANGE_FORWARD;
+ else if (now_usec > usec_add(epoch_usec, CLOCK_VALID_RANGE_USEC_MAX))
+ *ret_attempted_change = CLOCK_CHANGE_BACKWARD;
+ else {
+ *ret_attempted_change = CLOCK_CHANGE_NOOP;
return 0;
+ }
if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, epoch_usec)) < 0)
return -errno;
diff --git a/src/shared/clock-util.h b/src/shared/clock-util.h
index 9e96d50d963..c8f6d1b1f1e 100644
--- a/src/shared/clock-util.h
+++ b/src/shared/clock-util.h
@@ -1,11 +1,20 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <errno.h>
#include <time.h>
+typedef enum ClockChangeDirection {
+ CLOCK_CHANGE_NOOP,
+ CLOCK_CHANGE_FORWARD,
+ CLOCK_CHANGE_BACKWARD,
+ _CLOCK_CHANGE_MAX,
+ _CLOCK_CHANGE_INVALID = -EINVAL,
+} ClockChangeDirection;
+
int clock_is_localtime(const char* adjtime_path);
int clock_set_timezone(int *ret_minutesdelta);
int clock_reset_timewarp(void);
int clock_get_hwclock(struct tm *tm);
int clock_set_hwclock(const struct tm *tm);
-int clock_apply_epoch(void);
+int clock_apply_epoch(ClockChangeDirection *ret_attempted_change);
diff --git a/src/shared/copy.c b/src/shared/copy.c
index 65d75a15d54..a01b6df1b22 100644
--- a/src/shared/copy.c
+++ b/src/shared/copy.c
@@ -88,6 +88,23 @@ static int fd_is_nonblock_pipe(int fd) {
return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE;
}
+static int look_for_signals(CopyFlags copy_flags) {
+ int r;
+
+ if ((copy_flags & (COPY_SIGINT|COPY_SIGTERM)) == 0)
+ return 0;
+
+ r = pop_pending_signal(copy_flags & COPY_SIGINT ? SIGINT : 0,
+ copy_flags & COPY_SIGTERM ? SIGTERM : 0);
+ if (r < 0)
+ return r;
+ if (r != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINTR),
+ "Got %s, cancelling copy operation.", signal_to_string(r));
+
+ return 0;
+}
+
int copy_bytes_full(
int fdf, int fdt,
uint64_t max_bytes,
@@ -176,13 +193,9 @@ int copy_bytes_full(
if (max_bytes <= 0)
return 1; /* return > 0 if we hit the max_bytes limit */
- if (FLAGS_SET(copy_flags, COPY_SIGINT)) {
- r = pop_pending_signal(SIGINT);
- if (r < 0)
- return r;
- if (r > 0)
- return -EINTR;
- }
+ r = look_for_signals(copy_flags);
+ if (r < 0)
+ return r;
if (max_bytes != UINT64_MAX && m > max_bytes)
m = max_bytes;
@@ -627,10 +640,8 @@ static int fd_copy_regular(
return -errno;
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress, userdata);
- if (r < 0) {
- (void) unlinkat(dt, to, 0);
- return r;
- }
+ if (r < 0)
+ goto fail;
if (fchown(fdt,
uid_is_valid(override_uid) ? override_uid : st->st_uid,
@@ -643,16 +654,25 @@ static int fd_copy_regular(
(void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim });
(void) copy_xattr(fdf, fdt);
- q = close(fdt);
- fdt = -1;
+ if (copy_flags & COPY_FSYNC) {
+ if (fsync(fdt) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+ q = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
if (q < 0) {
- r = -errno;
- (void) unlinkat(dt, to, 0);
+ r = q;
+ goto fail;
}
(void) memorize_hardlink(hardlink_context, st, dt, to);
return r;
+
+fail:
+ (void) unlinkat(dt, to, 0);
+ return r;
}
static int fd_copy_fifo(
@@ -845,13 +865,9 @@ static int fd_copy_directory(
if (dot_or_dot_dot(de->d_name))
continue;
- if (FLAGS_SET(copy_flags, COPY_SIGINT)) {
- r = pop_pending_signal(SIGINT);
- if (r < 0)
- return r;
- if (r > 0)
- return -EINTR;
- }
+ r = look_for_signals(copy_flags);
+ if (r < 0)
+ return r;
if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) {
r = -errno;
@@ -912,7 +928,7 @@ static int fd_copy_directory(
else
q = -EOPNOTSUPP;
- if (q == -EINTR) /* Propagate SIGINT up instantly */
+ if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */
return q;
if (q == -EEXIST && (copy_flags & COPY_MERGE))
q = 0;
@@ -933,6 +949,11 @@ static int fd_copy_directory(
(void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim });
}
+ if (copy_flags & COPY_FSYNC_FULL) {
+ if (fsync(fdt) < 0)
+ return -errno;
+ }
+
return r;
}
@@ -949,6 +970,7 @@ int copy_tree_at_full(
void *userdata) {
struct stat st;
+ int r;
assert(from);
assert(to);
@@ -957,17 +979,47 @@ int copy_tree_at_full(
return -errno;
if (S_ISREG(st.st_mode))
- return fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata);
+ r = fd_copy_regular(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL, progress_bytes, userdata);
else if (S_ISDIR(st.st_mode))
- return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
+ r = fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, override_gid, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
else if (S_ISLNK(st.st_mode))
- return fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags);
+ r = fd_copy_symlink(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags);
else if (S_ISFIFO(st.st_mode))
- return fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
+ r = fd_copy_fifo(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode) || S_ISSOCK(st.st_mode))
- return fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
+ r = fd_copy_node(fdf, from, &st, fdt, to, override_uid, override_gid, copy_flags, NULL);
else
return -EOPNOTSUPP;
+ if (r < 0)
+ return r;
+
+ if (S_ISDIR(st.st_mode) && (copy_flags & COPY_SYNCFS)) {
+ /* If the top-level inode is a directory run syncfs() now. */
+ r = syncfs_path(fdt, to);
+ if (r < 0)
+ return r;
+ } else if ((copy_flags & (COPY_FSYNC_FULL|COPY_SYNCFS)) != 0) {
+ /* fsync() the parent dir of what we just copied if COPY_FSYNC_FULL is set. Also do this in
+ * case COPY_SYNCFS is set but the top-level inode wasn't actually a directory. We do this so that
+ * COPY_SYNCFS provides reasonable synchronization semantics on any kind of inode: when the
+ * copy operation is done the whole inode โ€” regardless of its type โ€” and all its children
+ * will be synchronized to disk. */
+ r = fsync_parent_at(fdt, to);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int sync_dir_by_flags(const char *path, CopyFlags copy_flags) {
+
+ if (copy_flags & COPY_SYNCFS)
+ return syncfs_path(AT_FDCWD, path);
+ if (copy_flags & COPY_FSYNC_FULL)
+ return fsync_parent_at(AT_FDCWD, path);
+
+ return 0;
}
int copy_directory_fd_full(
@@ -991,7 +1043,26 @@ int copy_directory_fd_full(
if (r < 0)
return r;
- return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
+ r = fd_copy_directory(
+ dirfd, NULL,
+ &st,
+ AT_FDCWD, to,
+ st.st_dev,
+ COPY_DEPTH_MAX,
+ UID_INVALID, GID_INVALID,
+ copy_flags,
+ NULL, NULL,
+ progress_path,
+ progress_bytes,
+ userdata);
+ if (r < 0)
+ return r;
+
+ r = sync_dir_by_flags(to, copy_flags);
+ if (r < 0)
+ return r;
+
+ return 0;
}
int copy_directory_full(
@@ -1015,7 +1086,26 @@ int copy_directory_full(
if (r < 0)
return r;
- return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, NULL, NULL, progress_path, progress_bytes, userdata);
+ r = fd_copy_directory(
+ AT_FDCWD, from,
+ &st,
+ AT_FDCWD, to,
+ st.st_dev,
+ COPY_DEPTH_MAX,
+ UID_INVALID, GID_INVALID,
+ copy_flags,
+ NULL, NULL,
+ progress_path,
+ progress_bytes,
+ userdata);
+ if (r < 0)
+ return r;
+
+ r = sync_dir_by_flags(to, copy_flags);
+ if (r < 0)
+ return r;
+
+ return 0;
}
int copy_file_fd_full(
@@ -1026,6 +1116,7 @@ int copy_file_fd_full(
void *userdata) {
_cleanup_close_ int fdf = -1;
+ struct stat st;
int r;
assert(from);
@@ -1035,12 +1126,32 @@ int copy_file_fd_full(
if (fdf < 0)
return -errno;
+ r = fd_verify_regular(fdf);
+ if (r < 0)
+ return r;
+
+ if (fstat(fdt, &st) < 0)
+ return -errno;
+
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata);
+ if (r < 0)
+ return r;
- (void) copy_times(fdf, fdt, copy_flags);
- (void) copy_xattr(fdf, fdt);
+ if (S_ISREG(fdt)) {
+ (void) copy_times(fdf, fdt, copy_flags);
+ (void) copy_xattr(fdf, fdt);
+ }
- return r;
+ if (copy_flags & COPY_FSYNC_FULL) {
+ r = fsync_full(fdt);
+ if (r < 0)
+ return r;
+ } else if (copy_flags & COPY_FSYNC) {
+ if (fsync(fdt) < 0)
+ return -errno;
+ }
+
+ return 0;
}
int copy_file_full(
@@ -1054,9 +1165,9 @@ int copy_file_full(
copy_progress_bytes_t progress_bytes,
void *userdata) {
- _cleanup_close_ int fdf = -1;
+ _cleanup_close_ int fdf = -1, fdt = -1;
struct stat st;
- int r, fdt = -1; /* avoid false maybe-uninitialized warning */
+ int r;
assert(from);
assert(to);
@@ -1065,9 +1176,12 @@ int copy_file_full(
if (fdf < 0)
return -errno;
- if (mode == MODE_INVALID)
- if (fstat(fdf, &st) < 0)
- return -errno;
+ if (fstat(fdf, &st) < 0)
+ return -errno;
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return r;
RUN_WITH_UMASK(0000) {
if (copy_flags & COPY_MAC_CREATE) {
@@ -1083,15 +1197,18 @@ int copy_file_full(
return -errno;
}
+ if (!FLAGS_SET(flags, O_EXCL)) { /* if O_EXCL was used we created the thing as regular file, no need to check again */
+ r = fd_verify_regular(fdt);
+ if (r < 0)
+ goto fail;
+ }
+
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & CHATTR_EARLY_FL, NULL);
r = copy_bytes_full(fdf, fdt, UINT64_MAX, copy_flags, NULL, NULL, progress_bytes, userdata);
- if (r < 0) {
- close(fdt);
- (void) unlink(to);
- return r;
- }
+ if (r < 0)
+ goto fail;
(void) copy_times(fdf, fdt, copy_flags);
(void) copy_xattr(fdf, fdt);
@@ -1099,12 +1216,31 @@ int copy_file_full(
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL);
- if (close(fdt) < 0) {
- unlink_noerrno(to);
- return -errno;
+ if (copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL)) {
+ if (fsync(fdt) < 0) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
+ if (r < 0)
+ goto fail;
+
+ if (copy_flags & COPY_FSYNC_FULL) {
+ r = fsync_parent_at(AT_FDCWD, to);
+ if (r < 0)
+ goto fail;
}
return 0;
+
+fail:
+ /* Only unlink if we definitely are the ones who created the file */
+ if (FLAGS_SET(flags, O_EXCL))
+ (void) unlink(to);
+
+ return r;
}
int copy_file_atomic_full(
@@ -1172,6 +1308,12 @@ int copy_file_atomic_full(
if (fchmod(fdt, mode) < 0)
return -errno;
+ if ((copy_flags & (COPY_FSYNC|COPY_FSYNC_FULL))) {
+ /* Sync the file */
+ if (fsync(fdt) < 0)
+ return -errno;
+ }
+
if (copy_flags & COPY_REPLACE) {
if (renameat(AT_FDCWD, t, AT_FDCWD, to) < 0)
return -errno;
@@ -1181,11 +1323,27 @@ int copy_file_atomic_full(
return r;
}
+ t = mfree(t);
+
if (chattr_mask != 0)
(void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL);
- t = mfree(t);
+ r = close_nointr(TAKE_FD(fdt)); /* even if this fails, the fd is now invalidated */
+ if (r < 0)
+ goto fail;
+
+ if (copy_flags & COPY_FSYNC_FULL) {
+ /* Sync the parent directory */
+ r = fsync_parent_at(AT_FDCWD, to);
+ if (r < 0)
+ goto fail;
+ }
+
return 0;
+
+fail:
+ (void) unlink(to);
+ return r;
}
int copy_times(int fdf, int fdt, CopyFlags flags) {
diff --git a/src/shared/copy.h b/src/shared/copy.h
index b36ddfcb015..f8992843e45 100644
--- a/src/shared/copy.h
+++ b/src/shared/copy.h
@@ -10,15 +10,19 @@
#include <sys/types.h>
typedef enum CopyFlags {
- COPY_REFLINK = 1 << 0, /* Try to reflink */
- COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
- COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
- COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
- COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
- COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */
- COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */
- COPY_MAC_CREATE = 1 << 7, /* Create files with the correct MAC label (currently SELinux only) */
- COPY_HARDLINKS = 1 << 8, /* Try to reproduce hard links */
+ COPY_REFLINK = 1 << 0, /* Try to reflink */
+ COPY_MERGE = 1 << 1, /* Merge existing trees with our new one to copy */
+ COPY_REPLACE = 1 << 2, /* Replace an existing file if there's one */
+ COPY_SAME_MOUNT = 1 << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
+ COPY_MERGE_EMPTY = 1 << 4, /* Merge an existing, empty directory with our new tree to copy */
+ COPY_CRTIME = 1 << 5, /* Generate a user.crtime_usec xattr off the source crtime if there is one, on copying */
+ COPY_SIGINT = 1 << 6, /* Check for SIGINT regularly and return EINTR if seen (caller needs to block SIGINT) */
+ COPY_SIGTERM = 1 << 7, /* ditto, but for SIGTERM */
+ COPY_MAC_CREATE = 1 << 8, /* Create files with the correct MAC label (currently SELinux only) */
+ COPY_HARDLINKS = 1 << 9, /* Try to reproduce hard links */
+ COPY_FSYNC = 1 << 10, /* fsync() after we are done */
+ COPY_FSYNC_FULL = 1 << 11, /* fsync_full() after we are done */
+ COPY_SYNCFS = 1 << 12, /* syncfs() the *top-level* dir after we are done */
} CopyFlags;
typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index 6c2d9dbc76c..ee279e0c9c1 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -369,7 +369,10 @@ struct _packed_ encrypted_credential_header {
};
struct _packed_ tpm2_credential_header {
- le64_t pcr_mask;
+ le64_t pcr_mask; /* Note that the spec for PC Clients only mandates 24 PCRs, and that's what systems
+ * generally have. But keep the door open for more. */
+ le16_t pcr_bank; /* For now, either TPM2_ALG_SHA256 or TPM2_ALG_SHA1 */
+ le16_t _zero; /* Filler to maintain 32bit alignment */
le32_t blob_size;
le32_t policy_hash_size;
uint8_t policy_hash_and_blob[];
@@ -439,6 +442,7 @@ int encrypt_credential_and_warn(
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
uint8_t md[SHA256_DIGEST_LENGTH];
+ uint16_t tpm2_pcr_bank = 0;
const EVP_CIPHER *cc;
#if HAVE_TPM2
bool try_tpm2 = false;
@@ -505,7 +509,8 @@ int encrypt_credential_and_warn(
&tpm2_blob,
&tpm2_blob_size,
&tpm2_policy_hash,
- &tpm2_policy_hash_size);
+ &tpm2_policy_hash_size,
+ &tpm2_pcr_bank);
if (r < 0) {
if (!sd_id128_is_null(with_key))
return r;
@@ -598,6 +603,7 @@ int encrypt_credential_and_warn(
t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
t->pcr_mask = htole64(tpm2_pcr_mask);
+ t->pcr_bank = htole16(tpm2_pcr_bank);
t->blob_size = htole32(tpm2_blob_size);
t->policy_hash_size = htole32(tpm2_policy_hash_size);
memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size);
@@ -739,6 +745,10 @@ int decrypt_credential_and_warn(
if (le64toh(t->pcr_mask) >= (UINT64_C(1) << TPM2_PCRS_MAX))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
+ if (!tpm2_pcr_bank_supported(le16toh(t->pcr_bank)))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR bank invalid or not supported");
+ if (le16toh(t->_zero) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 padding space not zero.");
if (le32toh(t->blob_size) > CREDENTIAL_FIELD_SIZE_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 blob size.");
if (le32toh(t->policy_hash_size) > CREDENTIAL_FIELD_SIZE_MAX)
@@ -755,6 +765,7 @@ int decrypt_credential_and_warn(
r = tpm2_unseal(tpm2_device,
le64toh(t->pcr_mask),
+ le16toh(t->pcr_bank),
t->policy_hash_and_blob,
le32toh(t->blob_size),
t->policy_hash_and_blob + le32toh(t->blob_size),
diff --git a/src/shared/import-util.c b/src/shared/import-util.c
index 2a30e40686f..bb1e5136a73 100644
--- a/src/shared/import-util.c
+++ b/src/shared/import-util.c
@@ -14,53 +14,114 @@
#include "string-table.h"
#include "string-util.h"
-int import_url_last_component(const char *url, char **ret) {
- const char *e, *p;
- char *s;
+static const char *skip_protocol_and_hostname(const char *url) {
+ const char *d;
+ size_t n;
+
+ /* A very very lenient implementation of RFC3986 Section 3.2 */
+
+ /* Find colon separating protocol and hostname */
+ d = strchr(url, ':');
+ if (!d || url == d)
+ return NULL;
+ d++;
+
+ /* Skip slashes after colon */
+ d += strspn(d, "/");
+
+ /* Skip everything till next slash or end */
+ n = strcspn(d, "/?#");
+ if (n == 0)
+ return NULL;
+
+ return d + n;
+}
- e = strchrnul(url, '?');
+int import_url_last_component(
+ const char *url,
+ char **ret) {
- while (e > url && e[-1] == '/')
+ const char *e, *p, *h;
+
+ /* This extracts the last path component of the specified URI, i.e. the last non-empty substrings
+ * between two "/" characters. This ignores "Query" and "Fragment" suffixes (as per RFC3986). */
+
+ h = skip_protocol_and_hostname(url);
+ if (!h)
+ return -EINVAL;
+
+ e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
+
+ while (e > h && e[-1] == '/') /* Eat trailing slashes */
e--;
p = e;
- while (p > url && p[-1] != '/')
+ while (p > h && p[-1] != '/') /* Find component before that */
p--;
- if (e <= p)
- return -EINVAL;
+ if (e <= p) /* Empty component? */
+ return -EADDRNOTAVAIL;
- s = strndup(p, e - p);
- if (!s)
- return -ENOMEM;
+ if (ret) {
+ char *s;
+
+ s = strndup(p, e - p);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = s;
+ }
- *ret = s;
return 0;
}
-int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
- const char *e;
+int import_url_change_suffix(
+ const char *url,
+ size_t n_drop_components,
+ const char *suffix,
+ char **ret) {
+
+ const char *e, *h;
char *s;
assert(url);
assert(ret);
- e = strchrnul(url, '?');
+ /* This drops the specified number of path components of the specified URI, i.e. the specified number
+ * of non-empty substring between two "/" characters from the end of the string, and then append the
+ * specified suffix instead. Before doing all this it chops off the "Query" and "Fragment" suffixes
+ * (they are *not* readded to the final URL). Note that n_drop_components may be 0 (in which case the
+ * component are simply added to the end). The suffix may be specified as NULL or empty string in
+ * which case nothing is appended, only the specified number of components chopped off. Note that the
+ * function may be called with n_drop_components == 0 and suffix == NULL, in which case the "Query"
+ * and "Fragment" is chopped off, and ensured the URL ends in a single "/", and that's it. */
+
+ h = skip_protocol_and_hostname(url);
+ if (!h)
+ return -EINVAL;
- while (e > url && e[-1] == '/')
- e--;
+ e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
- while (e > url && e[-1] != '/')
+ while (e > h && e[-1] == '/') /* Eat trailing slashes */
e--;
- if (e <= url)
- return -EINVAL;
+ /* Drop the specified number of components from the end. Note that this is pretty lenient: if there
+ * are less component we silently drop those and then append the suffix to the top. */
+ while (n_drop_components > 0) {
+ while (e > h && e[-1] != '/') /* Eat last word (we don't mind if empty) */
+ e--;
+
+ while (e > h && e[-1] == '/') /* Eat slashes before the last word */
+ e--;
+
+ n_drop_components--;
+ }
- s = new(char, (e - url) + strlen(suffix) + 1);
+ s = new(char, (e - url) + 1 + strlen_ptr(suffix) + 1);
if (!s)
return -ENOMEM;
- strcpy(mempcpy(s, url, e - url), suffix);
+ strcpy(stpcpy(mempcpy(s, url, e - url), "/"), strempty(suffix));
*ret = s;
return 0;
}
diff --git a/src/shared/import-util.h b/src/shared/import-util.h
index c7ec3b4eabd..3b2425b9165 100644
--- a/src/shared/import-util.h
+++ b/src/shared/import-util.h
@@ -14,7 +14,16 @@ typedef enum ImportVerify {
} ImportVerify;
int import_url_last_component(const char *url, char **ret);
-int import_url_change_last_component(const char *url, const char *suffix, char **ret);
+
+int import_url_change_suffix(const char *url, size_t n_drop_components, const char *suffix, char **ret);
+
+static inline int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
+ return import_url_change_suffix(url, 1, suffix, ret);
+}
+
+static inline int import_url_append_component(const char *url, const char *suffix, char **ret) {
+ return import_url_change_suffix(url, 0, suffix, ret);
+}
const char* import_verify_to_string(ImportVerify v) _const_;
ImportVerify import_verify_from_string(const char *s) _pure_;
diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c
index dfc47c42344..19e16d93455 100644
--- a/src/shared/nscd-flush.c
+++ b/src/shared/nscd-flush.c
@@ -1,5 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <sys/poll.h>
+#include <poll.h>
#include "fd-util.h"
#include "io-util.h"
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 900a7fb5fff..dffb9cf6ee8 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -19,6 +19,9 @@
#include "stat-util.h"
#include "string-util.h"
+/* We treat tmpfs/ramfs + cgroupfs as non-physical file sytems. cgroupfs is similar to tmpfs in a way after
+ * all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it to remove
+ * those again. */
static bool is_physical_fs(const struct statfs *sfs) {
return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
}
@@ -113,133 +116,145 @@ int fstatat_harder(int dfd,
return 0;
}
-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
- _cleanup_closedir_ DIR *d = NULL;
- struct dirent *de;
- int ret = 0, r;
- struct statfs sfs;
+static int rm_rf_children_inner(
+ int fd,
+ const char *fname,
+ int is_dir,
+ RemoveFlags flags,
+ const struct stat *root_dev) {
- assert(fd >= 0);
+ struct stat st;
+ int r;
- /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
- * fd, in all cases, including on failure.. */
+ assert(fd >= 0);
+ assert(fname);
- if (!(flags & REMOVE_PHYSICAL)) {
+ if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
- r = fstatfs(fd, &sfs);
- if (r < 0) {
- safe_close(fd);
- return -errno;
- }
+ r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags);
+ if (r < 0)
+ return r;
- if (is_physical_fs(&sfs)) {
- /* We refuse to clean physical file systems with this call,
- * unless explicitly requested. This is extra paranoia just
- * to be sure we never ever remove non-state data. */
- _cleanup_free_ char *path = NULL;
+ is_dir = S_ISDIR(st.st_mode);
+ }
- (void) fd_get_path(fd, &path);
- log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
- strna(path));
+ if (is_dir) {
+ _cleanup_close_ int subdir_fd = -1;
+ int q;
- safe_close(fd);
- return -EPERM;
- }
- }
+ /* if root_dev is set, remove subdirectories only if device is same */
+ if (root_dev && st.st_dev != root_dev->st_dev)
+ return 0;
- d = fdopendir(fd);
- if (!d) {
- safe_close(fd);
- return errno == ENOENT ? 0 : -errno;
- }
+ /* Stop at mount points */
+ r = fd_is_mount_point(fd, fname, 0);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0;
- FOREACH_DIRENT_ALL(de, d, return -errno) {
- bool is_dir;
- struct stat st;
+ if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
- if (dot_or_dot_dot(de->d_name))
- continue;
+ /* This could be a subvolume, try to remove it */
- if (de->d_type == DT_UNKNOWN ||
- (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
- r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags);
+ r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
if (r < 0) {
- if (ret == 0 && r != -ENOENT)
- ret = r;
- continue;
- }
+ if (!IN_SET(r, -ENOTTY, -EINVAL))
+ return r;
- is_dir = S_ISDIR(st.st_mode);
- } else
- is_dir = de->d_type == DT_DIR;
+ /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
+ } else
+ /* It was a subvolume, done. */
+ return 1;
+ }
- if (is_dir) {
- _cleanup_close_ int subdir_fd = -1;
+ subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ if (subdir_fd < 0)
+ return -errno;
- /* if root_dev is set, remove subdirectories only if device is same */
- if (root_dev && st.st_dev != root_dev->st_dev)
- continue;
+ /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
+ * again for each directory */
+ q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
- subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
- if (subdir_fd < 0) {
- if (ret == 0 && errno != ENOENT)
- ret = -errno;
- continue;
- }
+ r = unlinkat_harder(fd, fname, AT_REMOVEDIR, flags);
+ if (r < 0)
+ return r;
+ if (q < 0)
+ return q;
- /* Stop at mount points */
- r = fd_is_mount_point(fd, de->d_name, 0);
- if (r < 0) {
- if (ret == 0 && r != -ENOENT)
- ret = r;
+ return 1;
- continue;
- }
- if (r > 0)
- continue;
+ } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+ r = unlinkat_harder(fd, fname, 0, flags);
+ if (r < 0)
+ return r;
- if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
+ return 1;
+ }
- /* This could be a subvolume, try to remove it */
+ return 0;
+}
- r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
- if (r < 0) {
- if (!IN_SET(r, -ENOTTY, -EINVAL)) {
- if (ret == 0)
- ret = r;
+int rm_rf_children(
+ int fd,
+ RemoveFlags flags,
+ const struct stat *root_dev) {
- continue;
- }
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ int ret = 0, r;
- /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
- } else
- /* It was a subvolume, continue. */
- continue;
- }
+ assert(fd >= 0);
+
+ /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
+ * fd, in all cases, including on failure. */
+
+ d = fdopendir(fd);
+ if (!d) {
+ safe_close(fd);
+ return -errno;
+ }
- /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
- * system type again for each directory */
- r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
- if (r < 0 && ret == 0)
- ret = r;
+ if (!(flags & REMOVE_PHYSICAL)) {
+ struct statfs sfs;
- r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags);
- if (r < 0 && r != -ENOENT && ret == 0)
- ret = r;
+ if (fstatfs(dirfd(d), &sfs) < 0)
+ return -errno;
+
+ if (is_physical_fs(&sfs)) {
+ /* We refuse to clean physical file systems with this call, unless explicitly
+ * requested. This is extra paranoia just to be sure we never ever remove non-state
+ * data. */
- } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+ _cleanup_free_ char *path = NULL;
- r = unlinkat_harder(fd, de->d_name, 0, flags);
- if (r < 0 && r != -ENOENT && ret == 0)
- ret = r;
+ (void) fd_get_path(fd, &path);
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Attempted to remove disk file system under \"%s\", and we can't allow that.",
+ strna(path));
}
}
+
+ FOREACH_DIRENT_ALL(de, d, return -errno) {
+ int is_dir;
+
+ if (dot_or_dot_dot(de->d_name))
+ continue;
+
+ is_dir =
+ de->d_type == DT_UNKNOWN ? -1 :
+ de->d_type == DT_DIR;
+
+ r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev);
+ if (r < 0 && r != -ENOENT && ret == 0)
+ ret = r;
+ }
+
return ret;
}
int rm_rf(const char *path, RemoveFlags flags) {
int fd, r;
- struct statfs s;
assert(path);
@@ -284,9 +299,10 @@ int rm_rf(const char *path, RemoveFlags flags) {
if (FLAGS_SET(flags, REMOVE_ROOT)) {
if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
+ struct statfs s;
+
if (statfs(path, &s) < 0)
return -errno;
-
if (is_physical_fs(&s))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Attempted to remove files from a disk file system under \"%s\", refusing.",
@@ -314,3 +330,22 @@ int rm_rf(const char *path, RemoveFlags flags) {
return r;
}
+
+int rm_rf_child(int fd, const char *name, RemoveFlags flags) {
+
+ /* Removes one specific child of the specified directory */
+
+ if (fd < 0)
+ return -EBADF;
+
+ if (!filename_is_valid(name))
+ return -EINVAL;
+
+ if ((flags & (REMOVE_ROOT|REMOVE_MISSING_OK)) != 0) /* Doesn't really make sense here, we are not supposed to remove 'fd' anyway */
+ return -EINVAL;
+
+ if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
+ return -EINVAL;
+
+ return rm_rf_children_inner(fd, name, -1, flags, NULL);
+}
diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h
index 40f0894c96d..577a2795e0f 100644
--- a/src/shared/rm-rf.h
+++ b/src/shared/rm-rf.h
@@ -23,7 +23,8 @@ int fstatat_harder(int dfd,
int fstatat_flags,
RemoveFlags remove_flags);
-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
+int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev);
+int rm_rf_child(int fd, const char *name, RemoveFlags flags);
int rm_rf(const char *path, RemoveFlags flags);
/* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index 79e879b4824..5cb6563b434 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -25,6 +25,7 @@ TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHand
void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
void (*sym_Esys_Free)(void *ptr) = NULL;
+TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData);
TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
@@ -51,6 +52,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_Finalize),
DLSYM_ARG(Esys_FlushContext),
DLSYM_ARG(Esys_Free),
+ DLSYM_ARG(Esys_GetCapability),
DLSYM_ARG(Esys_GetRandom),
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
@@ -310,11 +312,93 @@ static int tpm2_make_primary(
return 0;
}
+static int tpm2_get_best_pcr_bank(
+ ESYS_CONTEXT *c,
+ TPMI_ALG_HASH *ret) {
+
+ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL;
+ TPMI_ALG_HASH hash = TPM2_ALG_SHA1;
+ bool found = false;
+ TPMI_YES_NO more;
+ TSS2_RC rc;
+
+ rc = sym_Esys_GetCapability(
+ c,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ TPM2_CAP_PCRS,
+ 0,
+ 1,
+ &more,
+ &pcap);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc));
+
+ assert(pcap->capability == TPM2_CAP_PCRS);
+
+ for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
+ bool valid = true;
+
+ /* As per
+ * https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
+ * TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over
+ * it. */
+ if (pcap->data.assignedPCR.pcrSelections[i].sizeofSelect < TPM2_PCRS_MAX/8) {
+ log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.",
+ strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash)));
+ continue;
+ }
+
+ assert_cc(TPM2_PCRS_MAX % 8 == 0);
+
+ /* It's not enought to check how many PCRs there are, we also need to check that the 24 are
+ * enabled for this bank. Otherwise this TPM doesn't qualify. */
+ for (size_t j = 0; j < TPM2_PCRS_MAX/8; j++)
+ if (pcap->data.assignedPCR.pcrSelections[i].pcrSelect[j] != 0xFF) {
+ valid = false;
+ break;
+ }
+
+ if (!valid) {
+ log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.",
+ strna(tpm2_pcr_bank_to_string(pcap->data.assignedPCR.pcrSelections[i].hash)));
+ continue;
+ }
+
+ if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) {
+ hash = TPM2_ALG_SHA256;
+ found = true;
+ break;
+ }
+
+ if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1)
+ found = true;
+ }
+
+ if (!found)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate.");
+
+ if (hash == TPM2_ALG_SHA256)
+ log_debug("TPM2 device supports SHA256 PCR banks, yay!");
+ else {
+ assert(hash == TPM2_ALG_SHA1);
+ log_debug("TPM2 device lacks support for SHA256 PCR banks, falling back to SHA1 banks.");
+ }
+
+ *ret = hash;
+ return 0;
+}
+
static int tpm2_make_pcr_session(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
+ uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */
ESYS_TR *ret_session,
- TPM2B_DIGEST **ret_policy_digest) {
+ TPM2B_DIGEST **ret_policy_digest,
+ TPMI_ALG_HASH *ret_pcr_bank) {
static const TPMT_SYM_DEF symmetric = {
.algorithm = TPM2_ALG_AES,
@@ -327,7 +411,7 @@ static int tpm2_make_pcr_session(
};
TPML_PCR_SELECTION pcr_selection = {
.count = 1,
- .pcrSelections[0].hash = TPM2_ALG_SHA256,
+ .pcrSelections[0].hash = TPM2_ALG_SHA256, /* overriden below, depending on TPM2 capabilities */
.pcrSelections[0].sizeofSelect = 3,
.pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF,
.pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF,
@@ -342,6 +426,16 @@ static int tpm2_make_pcr_session(
log_debug("Starting authentication session.");
+ if (pcr_bank != UINT16_MAX)
+ pcr_selection.pcrSelections[0].hash = pcr_bank;
+ else {
+ /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect
+ * that use that, but preferably use SHA256 */
+ r = tpm2_get_best_pcr_bank(c, &pcr_selection.pcrSelections[0].hash);
+ if (r < 0)
+ return r;
+ }
+
rc = sym_Esys_StartAuthSession(
c,
ESYS_TR_NONE,
@@ -412,6 +506,9 @@ static int tpm2_make_pcr_session(
if (ret_policy_digest)
*ret_policy_digest = TAKE_PTR(policy_digest);
+ if (ret_pcr_bank)
+ *ret_pcr_bank = pcr_selection.pcrSelections[0].hash;
+
r = 0;
finish:
@@ -427,7 +524,8 @@ int tpm2_seal(
void **ret_blob,
size_t *ret_blob_size,
void **ret_pcr_hash,
- size_t *ret_pcr_hash_size) {
+ size_t *ret_pcr_hash_size,
+ uint16_t *ret_pcr_bank) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
@@ -439,6 +537,7 @@ int tpm2_seal(
TPM2B_SENSITIVE_CREATE hmac_sensitive;
ESYS_TR primary = ESYS_TR_NONE;
TPM2B_PUBLIC hmac_template;
+ TPMI_ALG_HASH pcr_bank;
size_t k, blob_size;
usec_t start;
TSS2_RC rc;
@@ -450,6 +549,7 @@ int tpm2_seal(
assert(ret_blob_size);
assert(ret_pcr_hash);
assert(ret_pcr_hash_size);
+ assert(ret_pcr_bank);
assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
@@ -478,7 +578,7 @@ int tpm2_seal(
if (r < 0)
return r;
- r = tpm2_make_pcr_session(c.esys_context, pcr_mask, NULL, &policy_digest);
+ r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, NULL, &policy_digest, &pcr_bank);
if (r < 0)
goto finish;
@@ -600,6 +700,7 @@ int tpm2_seal(
*ret_blob_size = blob_size;
*ret_pcr_hash = TAKE_PTR(hash);
*ret_pcr_hash_size = policy_digest->size;
+ *ret_pcr_bank = pcr_bank;
r = 0;
@@ -611,6 +712,7 @@ finish:
int tpm2_unseal(
const char *device,
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const void *blob,
size_t blob_size,
const void *known_policy_hash,
@@ -670,7 +772,7 @@ int tpm2_unseal(
if (r < 0)
return r;
- r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest);
+ r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, &session, &policy_digest, NULL);
if (r < 0)
goto finish;
@@ -909,6 +1011,7 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
int tpm2_make_luks2_json(
int keyslot,
uint32_t pcr_mask,
+ uint16_t pcr_bank,
const void *blob,
size_t blob_size,
const void *policy_hash,
@@ -951,6 +1054,7 @@ int tpm2_make_luks2_json(
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)),
+ JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))),
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size))));
if (r < 0)
return r;
@@ -960,3 +1064,36 @@ int tpm2_make_luks2_json(
return keyslot;
}
+
+/* We want the helpers below to work also if TPM2 libs are not available, hence define these two defines if
+ * they are missing. */
+#ifndef TPM2_ALG_SHA256
+#define TPM2_ALG_SHA256 0xB
+#endif
+
+#ifndef TPM2_ALG_SHA1
+#define TPM2_ALG_SHA1 0x4
+#endif
+
+int tpm2_pcr_bank_supported(uint16_t bank) {
+ /* For now, let's officially only support these two. We can extend this later on, should the need
+ * arise. */
+ return IN_SET(bank, TPM2_ALG_SHA256, TPM2_ALG_SHA1);
+}
+
+const char *tpm2_pcr_bank_to_string(uint16_t bank) {
+ /* Similar here, only support the two for now, we can always extend this later. */
+ if (bank == TPM2_ALG_SHA256)
+ return "sha256";
+ if (bank == TPM2_ALG_SHA1)
+ return "sha1";
+ return NULL;
+}
+
+int tpm2_pcr_bank_from_string(const char *bank) {
+ if (streq_ptr(bank, "sha256"))
+ return TPM2_ALG_SHA256;
+ if (streq_ptr(bank, "sha1"))
+ return TPM2_ALG_SHA1;
+ return -EINVAL;
+}
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 9f60fef083d..8032afe7b00 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -15,6 +15,7 @@ extern TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR prim
extern void (*sym_Esys_Finalize)(ESYS_CONTEXT **context);
extern TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle);
extern void (*sym_Esys_Free)(void *ptr);
+extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData);
extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
@@ -33,8 +34,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
int dlopen_tpm2(void);
-int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size);
-int tpm2_unseal(const char *device, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank);
+int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size);
#endif
@@ -43,13 +44,17 @@ int tpm2_find_device_auto(int log_level, char **ret);
int tpm2_parse_pcrs(const char *s, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret);
+int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret);
#define TPM2_PCRS_MAX 24
/* Default to PCR 7 only */
#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)
+int tpm2_pcr_bank_supported(uint16_t bank);
+const char *tpm2_pcr_bank_to_string(uint16_t bank);
+int tpm2_pcr_bank_from_string(const char *bank);
+
typedef struct {
uint32_t search_pcr_mask;
const char *device;
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
index 3a53c16a724..4033947d3b8 100644
--- a/src/shared/varlink.c
+++ b/src/shared/varlink.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <malloc.h>
-#include <sys/poll.h>
+#include <poll.h>
#include "alloc-util.h"
#include "errno-util.h"
diff --git a/src/test/meson.build b/src/test/meson.build
index 07625ad38c1..e9204b150d0 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -250,6 +250,8 @@ tests += [
[['src/test/test-sysctl-util.c']],
+ [['src/test/test-import-util.c']],
+
[['src/test/test-user-record.c']],
[['src/test/test-user-util.c']],
diff --git a/src/test/test-import-util.c b/src/test/test-import-util.c
new file mode 100644
index 00000000000..92ff0f56545
--- /dev/null
+++ b/src/test/test-import-util.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "import-util.h"
+#include "log.h"
+#include "string-util.h"
+#include "tests.h"
+
+static void test_import_url_last_component_one(const char *input, const char *output, int ret) {
+ _cleanup_free_ char *s = NULL;
+
+ assert_se(import_url_last_component(input, &s) == ret);
+ assert_se(streq_ptr(output, s));
+}
+
+static void test_import_url_last_component(void) {
+ test_import_url_last_component_one("https://foobar/waldo/quux", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/quux/", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/", "waldo", 0);
+ test_import_url_last_component_one("https://foobar/", NULL, -EADDRNOTAVAIL);
+ test_import_url_last_component_one("https://foobar", NULL, -EADDRNOTAVAIL);
+ test_import_url_last_component_one("https://foobar/waldo/quux?foo=bar", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar#piep", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/quux/#piep", "quux", 0);
+ test_import_url_last_component_one("https://foobar/waldo/quux#piep", "quux", 0);
+ test_import_url_last_component_one("https://", NULL, -EINVAL);
+ test_import_url_last_component_one("", NULL, -EINVAL);
+ test_import_url_last_component_one(":", NULL, -EINVAL);
+ test_import_url_last_component_one(":/", NULL, -EINVAL);
+ test_import_url_last_component_one("x:/", NULL, -EINVAL);
+ test_import_url_last_component_one("x:y", NULL, -EADDRNOTAVAIL);
+ test_import_url_last_component_one("x:y/z", "z", 0);
+}
+
+static void test_import_url_change_suffix_one(const char *input, size_t n, const char *suffix, const char *output, int ret) {
+ _cleanup_free_ char *s = NULL;
+
+ assert_se(import_url_change_suffix(input, n, suffix, &s) == ret);
+ assert_se(streq_ptr(output, s));
+}
+
+static void test_import_url_change_suffix(void) {
+ test_import_url_change_suffix_one("https://foobar/waldo/quux", 1, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux/", 1, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief", 1, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief#opopo", 1, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 2, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux/quff/", 2, "wuff", "https://foobar/waldo/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0);
+ test_import_url_change_suffix_one("https://foobar/waldo/quux/quff?aa?bb##4", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0);
+ test_import_url_change_suffix_one("https://", 0, "wuff", NULL, -EINVAL);
+ test_import_url_change_suffix_one("", 0, "wuff", NULL, -EINVAL);
+ test_import_url_change_suffix_one(":", 0, "wuff", NULL, -EINVAL);
+ test_import_url_change_suffix_one(":/", 0, "wuff", NULL, -EINVAL);
+ test_import_url_change_suffix_one("x:/", 0, "wuff", NULL, -EINVAL);
+ test_import_url_change_suffix_one("x:y", 0, "wuff", "x:y/wuff", 0);
+ test_import_url_change_suffix_one("x:y/z", 0, "wuff", "x:y/z/wuff", 0);
+ test_import_url_change_suffix_one("x:y/z/", 0, "wuff", "x:y/z/wuff", 0);
+ test_import_url_change_suffix_one("x:y/z/", 1, "wuff", "x:y/wuff", 0);
+ test_import_url_change_suffix_one("x:y/z/", 2, "wuff", "x:y/wuff", 0);
+}
+
+int main(int argc, char *argv[]) {
+
+ test_setup_logging(LOG_INFO);
+
+ test_import_url_last_component();
+ test_import_url_change_suffix();
+
+ return 0;
+}
diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c
index 972423e2fa2..e22481b1b6e 100644
--- a/src/test/test-locale-util.c
+++ b/src/test/test-locale-util.c
@@ -89,7 +89,7 @@ static void test_keymaps(void) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
static void dump_special_glyphs(void) {
- assert_cc(SPECIAL_GLYPH_TOUCH + 1 == _SPECIAL_GLYPH_MAX);
+ assert_cc(SPECIAL_GLYPH_SPARKLES + 1 == _SPECIAL_GLYPH_MAX);
log_info("/* %s */", __func__);
@@ -120,6 +120,9 @@ static void dump_special_glyphs(void) {
dump_glyph(SPECIAL_GLYPH_DEPRESSED_SMILEY);
dump_glyph(SPECIAL_GLYPH_LOCK_AND_KEY);
dump_glyph(SPECIAL_GLYPH_TOUCH);
+ dump_glyph(SPECIAL_GLYPH_RECYCLING);
+ dump_glyph(SPECIAL_GLYPH_DOWNLOAD);
+ dump_glyph(SPECIAL_GLYPH_SPARKLES);
}
int main(int argc, char *argv[]) {
diff --git a/units/systemd-boot-update.service b/units/systemd-boot-update.service
new file mode 100644
index 00000000000..61ff12762a5
--- /dev/null
+++ b/units/systemd-boot-update.service
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Automatic Boot Loader Update
+Documentation=man:bootctl(1)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=local-fs.target
+Before=sysinit.target shutdown.target systemd-update-done.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=bootctl --no-variables --graceful update
+
+[Install]
+WantedBy=sysinit.target