summaryrefslogtreecommitdiffstats
path: root/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/ask-password-api.c114
-rw-r--r--src/shared/ask-password-api.h18
-rw-r--r--src/shared/async.c2
-rw-r--r--src/shared/bitmap.c4
-rw-r--r--src/shared/blockdev-util.c98
-rw-r--r--src/shared/blockdev-util.h1
-rw-r--r--src/shared/boot-entry.c36
-rw-r--r--src/shared/boot-entry.h4
-rw-r--r--src/shared/bootspec.c604
-rw-r--r--src/shared/bootspec.h24
-rw-r--r--src/shared/bpf-compat.h1
-rw-r--r--src/shared/bpf-dlopen.c106
-rw-r--r--src/shared/bpf-dlopen.h53
-rw-r--r--src/shared/bpf-link.c4
-rw-r--r--src/shared/bpf-program.c2
-rw-r--r--src/shared/btrfs-util.c69
-rw-r--r--src/shared/btrfs-util.h2
-rw-r--r--src/shared/bus-map-properties.c11
-rw-r--r--src/shared/bus-polkit.c444
-rw-r--r--src/shared/bus-polkit.h31
-rw-r--r--src/shared/bus-print-properties.c6
-rw-r--r--src/shared/bus-unit-util.c145
-rw-r--r--src/shared/bus-unit-util.h14
-rw-r--r--src/shared/bus-util.c249
-rw-r--r--src/shared/bus-util.h11
-rw-r--r--src/shared/bus-wait-for-jobs.c216
-rw-r--r--src/shared/bus-wait-for-jobs.h15
-rw-r--r--src/shared/bus-wait-for-units.c76
-rw-r--r--src/shared/bus-wait-for-units.h21
-rw-r--r--src/shared/capsule-util.c17
-rw-r--r--src/shared/capsule-util.h4
-rw-r--r--src/shared/cgroup-setup.c122
-rw-r--r--src/shared/cgroup-setup.h2
-rw-r--r--src/shared/cgroup-show.c17
-rw-r--r--src/shared/clean-ipc.c2
-rw-r--r--src/shared/clock-util.c7
-rw-r--r--src/shared/color-util.c80
-rw-r--r--src/shared/color-util.h11
-rw-r--r--src/shared/compare-operator.c11
-rw-r--r--src/shared/condition.c32
-rw-r--r--src/shared/conf-parser.c193
-rw-r--r--src/shared/conf-parser.h45
-rw-r--r--src/shared/copy.c15
-rw-r--r--src/shared/cpu-set-util.c77
-rw-r--r--src/shared/cpu-set-util.h5
-rw-r--r--src/shared/creds-util.c826
-rw-r--r--src/shared/creds-util.h32
-rw-r--r--src/shared/cryptsetup-fido2.c42
-rw-r--r--src/shared/cryptsetup-fido2.h24
-rw-r--r--src/shared/cryptsetup-tpm2.c302
-rw-r--r--src/shared/cryptsetup-tpm2.h108
-rw-r--r--src/shared/cryptsetup-util.c93
-rw-r--r--src/shared/cryptsetup-util.h85
-rw-r--r--src/shared/daemon-util.c16
-rw-r--r--src/shared/daemon-util.h7
-rw-r--r--src/shared/data-fd-util.c40
-rw-r--r--src/shared/data-fd-util.h11
-rw-r--r--src/shared/dev-setup.c64
-rw-r--r--src/shared/discover-image.c499
-rw-r--r--src/shared/discover-image.h4
-rw-r--r--src/shared/dissect-image.c453
-rw-r--r--src/shared/dissect-image.h18
-rw-r--r--src/shared/dlfcn-util.c66
-rw-r--r--src/shared/dlfcn-util.h39
-rw-r--r--src/shared/dns-domain.c41
-rw-r--r--src/shared/dns-domain.h1
-rw-r--r--src/shared/dropin.c7
-rw-r--r--src/shared/edit-util.c151
-rw-r--r--src/shared/edit-util.h19
-rw-r--r--src/shared/efi-api.c41
-rw-r--r--src/shared/efi-loader.c2
-rw-r--r--src/shared/elf-util.c96
-rw-r--r--src/shared/ethtool-util.c8
-rw-r--r--src/shared/exec-util.c32
-rw-r--r--src/shared/fdset.c9
-rw-r--r--src/shared/find-esp.c40
-rw-r--r--src/shared/firewall-util-iptables.c16
-rw-r--r--src/shared/firewall-util-nft.c2
-rw-r--r--src/shared/firewall-util.h2
-rw-r--r--src/shared/format-table.c133
-rw-r--r--src/shared/format-table.h13
-rw-r--r--src/shared/fstab-util.c138
-rw-r--r--src/shared/fstab-util.h15
-rw-r--r--src/shared/generator.c106
-rw-r--r--src/shared/generator.h17
-rw-r--r--src/shared/group-record.c32
-rw-r--r--src/shared/hibernate-util.c51
-rw-r--r--src/shared/hibernate-util.h2
-rw-r--r--src/shared/hostname-setup.c92
-rw-r--r--src/shared/hwdb-util.c16
-rw-r--r--src/shared/hwdb-util.h1
-rw-r--r--src/shared/idn-util.c22
-rw-r--r--src/shared/idn-util.h14
-rw-r--r--src/shared/image-policy.c106
-rw-r--r--src/shared/image-policy.h10
-rw-r--r--src/shared/in-addr-prefix-util.c6
-rw-r--r--src/shared/initreq.h10
-rw-r--r--src/shared/install-file.c2
-rw-r--r--src/shared/install-file.h2
-rw-r--r--src/shared/install-printf.c17
-rw-r--r--src/shared/install.c505
-rw-r--r--src/shared/install.h23
-rw-r--r--src/shared/journal-file-util.c33
-rw-r--r--src/shared/journal-file-util.h1
-rw-r--r--src/shared/journal-importer.c14
-rw-r--r--src/shared/journal-util.c4
-rw-r--r--src/shared/journal-util.h2
-rw-r--r--src/shared/json.c187
-rw-r--r--src/shared/json.h77
-rw-r--r--src/shared/kbd-util.c45
-rw-r--r--src/shared/kbd-util.h8
-rw-r--r--src/shared/kernel-config.c72
-rw-r--r--src/shared/kernel-config.h10
-rw-r--r--src/shared/keyring-util.c35
-rw-r--r--src/shared/keyring-util.h11
-rw-r--r--src/shared/killall.c16
-rw-r--r--src/shared/label-util.c9
-rw-r--r--src/shared/label-util.h6
-rw-r--r--src/shared/libarchive-util.c67
-rw-r--r--src/shared/libarchive-util.h45
-rw-r--r--src/shared/libcrypt-util.c11
-rw-r--r--src/shared/libfido2-util.c143
-rw-r--r--src/shared/libfido2-util.h100
-rw-r--r--src/shared/local-addresses.c322
-rw-r--r--src/shared/local-addresses.h8
-rw-r--r--src/shared/logs-show.c642
-rw-r--r--src/shared/logs-show.h14
-rw-r--r--src/shared/loop-util.c65
-rw-r--r--src/shared/loop-util.h4
-rw-r--r--src/shared/loopback-setup.c6
-rw-r--r--src/shared/machine-credential.c100
-rw-r--r--src/shared/machine-credential.h16
-rw-r--r--src/shared/machine-id-setup.c61
-rw-r--r--src/shared/main-func.h49
-rw-r--r--src/shared/meson.build56
-rw-r--r--src/shared/mkfs-util.c13
-rw-r--r--src/shared/module-util.c125
-rw-r--r--src/shared/module-util.h50
-rw-r--r--src/shared/mount-setup.c285
-rw-r--r--src/shared/mount-setup.h8
-rw-r--r--src/shared/mount-util.c100
-rw-r--r--src/shared/mount-util.h10
-rw-r--r--src/shared/netif-naming-scheme.c83
-rw-r--r--src/shared/netif-naming-scheme.h7
-rw-r--r--src/shared/netif-sriov.c2
-rw-r--r--src/shared/netif-util.c90
-rw-r--r--src/shared/netif-util.h6
-rw-r--r--src/shared/nscd-flush.c2
-rw-r--r--src/shared/nsresource.c330
-rw-r--r--src/shared/nsresource.h10
-rw-r--r--src/shared/open-file.c12
-rw-r--r--src/shared/open-file.h10
-rw-r--r--src/shared/openssl-util.c357
-rw-r--r--src/shared/openssl-util.h29
-rw-r--r--src/shared/pager.c2
-rw-r--r--src/shared/pam-util.c106
-rw-r--r--src/shared/pam-util.h9
-rw-r--r--src/shared/parse-helpers.c37
-rw-r--r--src/shared/parse-helpers.h23
-rw-r--r--src/shared/password-quality-util-passwdqc.c17
-rw-r--r--src/shared/password-quality-util-passwdqc.h14
-rw-r--r--src/shared/password-quality-util-pwquality.c35
-rw-r--r--src/shared/password-quality-util-pwquality.h18
-rw-r--r--src/shared/pcre2-util.c19
-rw-r--r--src/shared/pcre2-util.h16
-rw-r--r--src/shared/pcrextend-util.c2
-rw-r--r--src/shared/pe-binary.c5
-rw-r--r--src/shared/pkcs11-util.c1033
-rw-r--r--src/shared/pkcs11-util.h47
-rw-r--r--src/shared/pretty-print.c198
-rw-r--r--src/shared/pretty-print.h7
-rw-r--r--src/shared/ptyfwd.c516
-rw-r--r--src/shared/ptyfwd.h17
-rw-r--r--src/shared/qrcode-util.c9
-rw-r--r--src/shared/reboot-util.c10
-rw-r--r--src/shared/reboot-util.h1
-rw-r--r--src/shared/resize-fs.c4
-rw-r--r--src/shared/rm-rf.c2
-rw-r--r--src/shared/seccomp-util.c2
-rw-r--r--src/shared/selinux-util.c22
-rw-r--r--src/shared/serialize.c2
-rw-r--r--src/shared/service-util.c25
-rw-r--r--src/shared/sleep-config.c78
-rw-r--r--src/shared/sleep-config.h24
-rw-r--r--src/shared/socket-netlink.c72
-rw-r--r--src/shared/socket-netlink.h2
-rw-r--r--src/shared/specifier.c50
-rw-r--r--src/shared/switch-root.c27
-rw-r--r--src/shared/tests.c15
-rw-r--r--src/shared/tests.h242
-rw-r--r--src/shared/tpm2-util.c878
-rw-r--r--src/shared/tpm2-util.h32
-rw-r--r--src/shared/udev-util.c92
-rw-r--r--src/shared/udev-util.h5
-rw-r--r--src/shared/user-record-nss.c124
-rw-r--r--src/shared/user-record-show.c81
-rw-r--r--src/shared/user-record.c299
-rw-r--r--src/shared/user-record.h19
-rw-r--r--src/shared/userdb.c10
-rw-r--r--src/shared/varlink-idl.c130
-rw-r--r--src/shared/varlink-io.systemd.BootControl.c59
-rw-r--r--src/shared/varlink-io.systemd.BootControl.h6
-rw-r--r--src/shared/varlink-io.systemd.Credentials.c42
-rw-r--r--src/shared/varlink-io.systemd.Credentials.h6
-rw-r--r--src/shared/varlink-io.systemd.Hostname.c39
-rw-r--r--src/shared/varlink-io.systemd.Hostname.h6
-rw-r--r--src/shared/varlink-io.systemd.Machine.c25
-rw-r--r--src/shared/varlink-io.systemd.Machine.h6
-rw-r--r--src/shared/varlink-io.systemd.MountFileSystem.c69
-rw-r--r--src/shared/varlink-io.systemd.MountFileSystem.h6
-rw-r--r--src/shared/varlink-io.systemd.NamespaceResource.c62
-rw-r--r--src/shared/varlink-io.systemd.NamespaceResource.h6
-rw-r--r--src/shared/varlink-io.systemd.Network.c58
-rw-r--r--src/shared/varlink-io.systemd.Network.h6
-rw-r--r--src/shared/varlink-io.systemd.PCRLock.c25
-rw-r--r--src/shared/varlink-io.systemd.PCRLock.h6
-rw-r--r--src/shared/varlink-io.systemd.Resolve.Monitor.c92
-rw-r--r--src/shared/varlink-io.systemd.Resolve.c134
-rw-r--r--src/shared/varlink-io.systemd.Resolve.h3
-rw-r--r--src/shared/varlink.c731
-rw-r--r--src/shared/varlink.h61
-rw-r--r--src/shared/verbs.c2
-rw-r--r--src/shared/vpick.c699
-rw-r--r--src/shared/vpick.h61
-rw-r--r--src/shared/wall.c5
-rw-r--r--src/shared/watchdog.c44
-rw-r--r--src/shared/wifi-util.c2
227 files changed, 13465 insertions, 4492 deletions
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c
index 0e323f4..bf79dc2 100644
--- a/src/shared/ask-password-api.c
+++ b/src/shared/ask-password-api.c
@@ -147,18 +147,17 @@ static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, c
return 0;
}
-static int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) {
-
+static int ask_password_keyring(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) {
key_serial_t serial;
int r;
- assert(keyname);
+ assert(req);
assert(ret);
if (!FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED))
return -EUNATCH;
- r = lookup_key(keyname, &serial);
+ r = lookup_key(req->keyring, &serial);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EPERM)
/* When retrieving, the distinction between "kernel or container manager don't support or
* allow this" and "no matching key known" doesn't matter. Note that we propagate EACCESS
@@ -205,7 +204,7 @@ static int backspace_string(int ttyfd, const char *str) {
}
int ask_password_plymouth(
- const char *message,
+ const AskPasswordRequest *req,
usec_t until,
AskPasswordFlags flags,
const char *flag_file,
@@ -225,8 +224,10 @@ int ask_password_plymouth(
assert(ret);
- if (!message)
- message = "Password:";
+ if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
+ return -ENOEXEC;
+
+ const char *message = req && req->message ? req->message : "Password:";
if (flag_file) {
notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
@@ -357,8 +358,7 @@ int ask_password_plymouth(
int ask_password_tty(
int ttyfd,
- const char *message,
- const char *keyname,
+ const AskPasswordRequest *req,
usec_t until,
AskPasswordFlags flags,
const char *flag_file,
@@ -381,16 +381,19 @@ int ask_password_tty(
assert(ret);
+ if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
+ return -ENOEXEC;
+
if (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY))
return -EUNATCH;
- if (!message)
- message = "Password:";
+ const char *message = req && req->message ? req->message : "Password:";
+ const char *keyring = req ? req->keyring : NULL;
if (!FLAGS_SET(flags, ASK_PASSWORD_HIDE_EMOJI) && emoji_enabled())
message = strjoina(special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), " ", message);
- if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname)) {
+ if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) {
notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if (notify < 0)
return -errno;
@@ -399,8 +402,8 @@ int ask_password_tty(
if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0)
return -errno;
}
- if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
- r = ask_password_keyring(keyname, flags, ret);
+ if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && keyring) {
+ r = ask_password_keyring(req, flags, ret);
if (r >= 0)
return 0;
else if (r != -ENOKEY)
@@ -443,9 +446,7 @@ int ask_password_tty(
(void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX);
new_termios = old_termios;
- new_termios.c_lflag &= ~(ICANON|ECHO);
- new_termios.c_cc[VMIN] = 1;
- new_termios.c_cc[VTIME] = 0;
+ termios_disable_echo(&new_termios);
r = RET_NERRNO(tcsetattr(ttyfd, TCSADRAIN, &new_termios));
if (r < 0)
@@ -489,10 +490,10 @@ int ask_password_tty(
goto finish;
}
- if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyname) {
+ if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyring) {
(void) flush_fd(notify);
- r = ask_password_keyring(keyname, flags, ret);
+ r = ask_password_keyring(req, flags, ret);
if (r >= 0) {
r = 0;
goto finish;
@@ -631,8 +632,8 @@ skipped:
if (strv_isempty(l))
r = log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "Password query was cancelled.");
else {
- if (keyname)
- (void) add_to_keyring_and_log(keyname, flags, l);
+ if (keyring)
+ (void) add_to_keyring_and_log(keyring, flags, l);
*ret = TAKE_PTR(l);
r = 0;
@@ -681,10 +682,7 @@ static int create_socket(char **ret) {
}
int ask_password_agent(
- const char *message,
- const char *icon,
- const char *id,
- const char *keyname,
+ const AskPasswordRequest *req,
usec_t until,
AskPasswordFlags flags,
char ***ret) {
@@ -708,17 +706,20 @@ int ask_password_agent(
assert(ret);
+ if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS))
+ return -ENOEXEC;
+
if (FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT))
return -EUNATCH;
assert_se(sigemptyset(&mask) >= 0);
- assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
+ assert_se(sigset_add_many(&mask, SIGINT, SIGTERM) >= 0);
assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0);
(void) mkdir_p_label("/run/systemd/ask-password", 0755);
- if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
- r = ask_password_keyring(keyname, flags, ret);
+ if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && req->keyring) {
+ r = ask_password_keyring(req, flags, ret);
if (r >= 0) {
r = 0;
goto finish;
@@ -777,14 +778,16 @@ int ask_password_agent(
until,
FLAGS_SET(flags, ASK_PASSWORD_SILENT));
- if (message)
- fprintf(f, "Message=%s\n", message);
+ if (req) {
+ if (req->message)
+ fprintf(f, "Message=%s\n", req->message);
- if (icon)
- fprintf(f, "Icon=%s\n", icon);
+ if (req->icon)
+ fprintf(f, "Icon=%s\n", req->icon);
- if (id)
- fprintf(f, "Id=%s\n", id);
+ if (req->id)
+ fprintf(f, "Id=%s\n", req->id);
+ }
r = fflush_and_check(f);
if (r < 0)
@@ -839,12 +842,14 @@ int ask_password_agent(
if (notify >= 0 && pollfd[FD_INOTIFY].revents != 0) {
(void) flush_fd(notify);
- r = ask_password_keyring(keyname, flags, ret);
- if (r >= 0) {
- r = 0;
- goto finish;
- } else if (r != -ENOKEY)
- goto finish;
+ if (req && req->keyring) {
+ r = ask_password_keyring(req, flags, ret);
+ if (r >= 0) {
+ r = 0;
+ goto finish;
+ } else if (r != -ENOKEY)
+ goto finish;
+ }
}
if (pollfd[FD_SOCKET].revents == 0)
@@ -923,8 +928,8 @@ int ask_password_agent(
log_debug("Invalid packet");
}
- if (keyname)
- (void) add_to_keyring_and_log(keyname, flags, l);
+ if (req && req->keyring)
+ (void) add_to_keyring_and_log(req->keyring, flags, l);
*ret = TAKE_PTR(l);
r = 0;
@@ -942,16 +947,17 @@ finish:
return r;
}
-static int ask_password_credential(const char *credential_name, AskPasswordFlags flags, char ***ret) {
+static int ask_password_credential(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) {
_cleanup_(erase_and_freep) char *buffer = NULL;
size_t size;
char **l;
int r;
- assert(credential_name);
+ assert(req);
+ assert(req->credential);
assert(ret);
- r = read_credential(credential_name, (void**) &buffer, &size);
+ r = read_credential(req->credential, (void**) &buffer, &size);
if (IN_SET(r, -ENXIO, -ENOENT)) /* No credentials passed or this credential not defined? */
return -ENOKEY;
@@ -964,11 +970,7 @@ static int ask_password_credential(const char *credential_name, AskPasswordFlags
}
int ask_password_auto(
- const char *message,
- const char *icon,
- const char *id, /* id in "ask-password" protocol */
- const char *key_name, /* name in kernel keyring */
- const char *credential_name, /* name in $CREDENTIALS_DIRECTORY directory */
+ const AskPasswordRequest *req,
usec_t until,
AskPasswordFlags flags,
char ***ret) {
@@ -977,26 +979,26 @@ int ask_password_auto(
assert(ret);
- if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && credential_name) {
- r = ask_password_credential(credential_name, flags, ret);
+ if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && req && req->credential) {
+ r = ask_password_credential(req, flags, ret);
if (r != -ENOKEY)
return r;
}
if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) &&
- key_name &&
+ req && req->keyring &&
(FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) || !isatty(STDIN_FILENO)) &&
FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) {
- r = ask_password_keyring(key_name, flags, ret);
+ r = ask_password_keyring(req, flags, ret);
if (r != -ENOKEY)
return r;
}
if (!FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO))
- return ask_password_tty(-1, message, key_name, until, flags, NULL, ret);
+ return ask_password_tty(-EBADF, req, until, flags, NULL, ret);
if (!FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT))
- return ask_password_agent(message, icon, id, key_name, until, flags, ret);
+ return ask_password_agent(req, until, flags, ret);
return -EUNATCH;
}
diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h
index 7464e7f..e851d6d 100644
--- a/src/shared/ask-password-api.h
+++ b/src/shared/ask-password-api.h
@@ -15,9 +15,19 @@ typedef enum AskPasswordFlags {
ASK_PASSWORD_CONSOLE_COLOR = 1 << 6, /* Use color if /dev/console points to a console that supports color */
ASK_PASSWORD_NO_CREDENTIAL = 1 << 7, /* never use $CREDENTIALS_DIRECTORY data */
ASK_PASSWORD_HIDE_EMOJI = 1 << 8, /* hide the lock and key emoji */
+ ASK_PASSWORD_HEADLESS = 1 << 9, /* headless mode: never query interactively */
} AskPasswordFlags;
-int ask_password_tty(int tty_fd, const char *message, const char *key_name, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
-int ask_password_plymouth(const char *message, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
-int ask_password_agent(const char *message, const char *icon, const char *id, const char *key_name, usec_t until, AskPasswordFlags flag, char ***ret);
-int ask_password_auto(const char *message, const char *icon, const char *id, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags flag, char ***ret);
+/* Encapsulates the mostly static fields of a password query */
+typedef struct AskPasswordRequest {
+ const char *message; /* The human readable password prompt when asking interactively */
+ const char *keyring; /* kernel keyring key name (key of "user" type) */
+ const char *icon; /* freedesktop icon spec name */
+ const char *id; /* some identifier used for this prompt for the "ask-password" protocol */
+ const char *credential; /* $CREDENTIALS_DIRECTORY credential name */
+} AskPasswordRequest;
+
+int ask_password_tty(int tty_fd, const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
+int ask_password_plymouth(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
+int ask_password_agent(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret);
+int ask_password_auto(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret);
diff --git a/src/shared/async.c b/src/shared/async.c
index 41f6b97..bbb8b81 100644
--- a/src/shared/async.c
+++ b/src/shared/async.c
@@ -94,7 +94,7 @@ int asynchronous_close(int fd) {
pid = clone_with_nested_stack(close_func, CLONE_FILES | ((v & NEED_DOUBLE_FORK) ? 0 : SIGCHLD), UINT_TO_PTR(v));
if (pid < 0)
- assert_se(close_nointr(fd) != -EBADF); /* local fallback */
+ safe_close(fd); /* local fallback */
else if (v & NEED_DOUBLE_FORK) {
/* Reap the intermediate child. Key here is that we specify __WCLONE, since we didn't ask for
diff --git a/src/shared/bitmap.c b/src/shared/bitmap.c
index 6cf08b8..9aa7661 100644
--- a/src/shared/bitmap.c
+++ b/src/shared/bitmap.c
@@ -163,9 +163,9 @@ bool bitmap_iterate(const Bitmap *b, Iterator *i, unsigned *n) {
rem = BITMAP_NUM_TO_REM(i->idx);
bitmask = UINT64_C(1) << rem;
- for (; offset < b->n_bitmaps; offset ++) {
+ for (; offset < b->n_bitmaps; offset++) {
if (b->bitmaps[offset]) {
- for (; bitmask; bitmask <<= 1, rem ++) {
+ for (; bitmask; bitmask <<= 1, rem++) {
if (b->bitmaps[offset] & bitmask) {
*n = BITMAP_OFFSET_TO_NUM(offset, rem);
i->idx = *n + 1;
diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c
index 7a2dd1c..2055550 100644
--- a/src/shared/blockdev-util.c
+++ b/src/shared/blockdev-util.c
@@ -57,23 +57,12 @@ static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) {
}
int block_device_is_whole_disk(sd_device *dev) {
- const char *s;
- int r;
-
assert(dev);
- r = sd_device_get_subsystem(dev, &s);
- if (r < 0)
- return r;
-
- if (!streq(s, "block"))
+ if (!device_in_subsystem(dev, "block"))
return -ENOTBLK;
- r = sd_device_get_devtype(dev, &s);
- if (r < 0)
- return r;
-
- return streq(s, "disk");
+ return device_is_devtype(dev, "disk");
}
int block_device_get_whole_disk(sd_device *dev, sd_device **ret) {
@@ -380,15 +369,44 @@ int blockdev_partscan_enabled(int fd) {
* is 1, which can be check with 'ext_range' sysfs attribute. Explicit flag ('GENHD_FL_NO_PART_SCAN')
* can be obtained from 'capability' sysattr.
*
- * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), we
- * can check the flag from 'ext_range' sysfs attribute directly.
+ * With https://github.com/torvalds/linux/commit/46e7eac647b34ed4106a8262f8bedbb90801fadd (v5.17),
+ * the flag is renamed to GENHD_FL_NO_PART.
+ *
+ * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17),
+ * we can check the flag from 'ext_range' sysfs attribute directly.
+ *
+ * With https://github.com/torvalds/linux/commit/430cc5d3ab4d0ba0bd011cfbb0035e46ba92920c (v5.17),
+ * the value of GENHD_FL_NO_PART is changed from 0x0200 to 0x0004. 💣💣💣
+ * Note, the new value was used by the GENHD_FL_MEDIA_CHANGE_NOTIFY flag, which was introduced by
+ * 86ce18d7b7925bfd6b64c061828ca2a857ee83b8 (v2.6.22), and removed by
+ * 9243c6f3e012a92dd900d97ef45efaf8a8edc448 (v5.7). If we believe the commit message of
+ * e81cd5a983bb35dabd38ee472cf3fea1c63e0f23, the flag was never used. So, fortunately, we can use
+ * both the new and old values safely.
+ *
+ * With https://github.com/torvalds/linux/commit/b9684a71fca793213378dd410cd11675d973eaa1 (v5.19),
+ * another flag GD_SUPPRESS_PART_SCAN is introduced for loopback block device, and partition scanning
+ * is done only when both GENHD_FL_NO_PART and GD_SUPPRESS_PART_SCAN are not set. Before the commit,
+ * LO_FLAGS_PARTSCAN flag was directly tied with GENHD_FL_NO_PART. But with this change now it is
+ * tied with GD_SUPPRESS_PART_SCAN. So, LO_FLAGS_PARTSCAN cannot be obtained from 'ext_range'
+ * sysattr, which corresponds to GENHD_FL_NO_PART, and we need to read 'loop/partscan'. 💣💣💣
+ *
+ * With https://github.com/torvalds/linux/commit/73a166d9749230d598320fdae3b687cdc0e2e205 (v6.3),
+ * the GD_SUPPRESS_PART_SCAN flag is also introduced for userspace block device (ublk). Though, not
+ * sure if we should support the device...
*
* With https://github.com/torvalds/linux/commit/e81cd5a983bb35dabd38ee472cf3fea1c63e0f23 (v6.3),
- * the 'capability' sysfs attribute is deprecated, hence we cannot check the flag from it.
+ * the 'capability' sysfs attribute is deprecated, hence we cannot check flags from it. 💣💣💣
+ *
+ * With https://github.com/torvalds/linux/commit/a4217c6740dc64a3eb6815868a9260825e8c68c6 (v6.10,
+ * backported to v6.6+), the partscan status is directly exposed as 'partscan' sysattr.
*
- * To support both old and new kernels, we need to do the following: first check 'ext_range' sysfs
- * attribute, and if '1' we can conclude partition scanning is disabled, otherwise check 'capability'
- * sysattr for older version. */
+ * To support both old and new kernels, we need to do the following:
+ * 1) check 'partscan' sysfs attribute where the information is made directly available,
+ * 2) check if the blockdev refers to a partition, where partscan is not supported,
+ * 3) check 'loop/partscan' sysfs attribute for loopback block devices, and if '0' we can conclude
+ * partition scanning is disabled,
+ * 4) check 'ext_range' sysfs attribute, and if '1' we can conclude partition scanning is disabled,
+ * 5) otherwise check 'capability' sysfs attribute for ancient version. */
assert(fd >= 0);
@@ -396,6 +414,21 @@ int blockdev_partscan_enabled(int fd) {
if (r < 0)
return r;
+ /* For v6.10 or newer. */
+ r = device_get_sysattr_bool(dev, "partscan");
+ if (r != -ENOENT)
+ return r;
+
+ /* Partition block devices never have partition scanning on, there's no concept of sub-partitions for
+ * partitions. */
+ if (device_is_devtype(dev, "partition"))
+ return false;
+
+ /* For loopback block device, especially for v5.19 or newer. Even if this is enabled, we also need to
+ * check GENHD_FL_NO_PART flag through 'ext_range' and 'capability' sysfs attributes below. */
+ if (device_get_sysattr_bool(dev, "loop/partscan") == 0)
+ return false;
+
r = device_get_sysattr_int(dev, "ext_range", &ext_range);
if (r == -ENOENT) /* If the ext_range file doesn't exist then we are most likely looking at a
* partition block device, not the whole block device. And that means we have no
@@ -405,7 +438,7 @@ int blockdev_partscan_enabled(int fd) {
if (r < 0)
return r;
- if (ext_range <= 1) /* The valus should be always positive, but the kernel uses '%d' for the
+ if (ext_range <= 1) /* The value should be always positive, but the kernel uses '%d' for the
* attribute. Let's gracefully handle zero or negative. */
return false;
@@ -415,12 +448,10 @@ int blockdev_partscan_enabled(int fd) {
if (r < 0)
return r;
-#ifndef GENHD_FL_NO_PART_SCAN
-#define GENHD_FL_NO_PART_SCAN (0x0200)
-#endif
-
- /* If 0x200 is set, part scanning is definitely off. */
- if (FLAGS_SET(capability, GENHD_FL_NO_PART_SCAN))
+#define GENHD_FL_NO_PART_OLD 0x0200
+#define GENHD_FL_NO_PART_NEW 0x0004
+ /* If one of the NO_PART flags is set, part scanning is definitely off. */
+ if ((capability & (GENHD_FL_NO_PART_OLD | GENHD_FL_NO_PART_NEW)) != 0)
return false;
/* Otherwise, assume part scanning is on, we have no further checks available. Assume the best. */
@@ -801,6 +832,21 @@ int blockdev_get_sector_size(int fd, uint32_t *ret) {
return 0;
}
+int blockdev_get_device_size(int fd, uint64_t *ret) {
+ uint64_t sz = 0;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ /* This is just a type-safe wrapper around BLKGETSIZE64 that gets us around having to include messy linux/fs.h in various clients */
+
+ if (ioctl(fd, BLKGETSIZE64, &sz) < 0)
+ return -errno;
+
+ *ret = sz;
+ return 0;
+}
+
int blockdev_get_root(int level, dev_t *ret) {
_cleanup_free_ char *p = NULL;
dev_t devno;
diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h
index 954a23d..28aede2 100644
--- a/src/shared/blockdev-util.h
+++ b/src/shared/blockdev-util.h
@@ -57,5 +57,6 @@ int block_device_has_partitions(sd_device *dev);
int blockdev_reread_partition_table(sd_device *dev);
int blockdev_get_sector_size(int fd, uint32_t *ret);
+int blockdev_get_device_size(int fd, uint64_t *ret);
int blockdev_get_root(int level, dev_t *ret);
diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c
index e726073..72d3cbe 100644
--- a/src/shared/boot-entry.c
+++ b/src/shared/boot-entry.c
@@ -15,20 +15,18 @@ bool boot_entry_token_valid(const char *p) {
return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p);
}
-static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) {
+static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) {
_cleanup_free_ char *buf = NULL, *p = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(dir);
assert(type);
assert(*type == BOOT_ENTRY_TOKEN_AUTO);
assert(token);
- if (!etc_kernel)
- return 0;
-
- p = path_join(etc_kernel, "entry-token");
+ p = path_join(dir, "entry-token");
if (!p)
return log_oom();
@@ -55,6 +53,26 @@ static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType
return 1;
}
+static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) {
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(type);
+ assert(*type == BOOT_ENTRY_TOKEN_AUTO);
+ assert(token);
+
+ if (conf_root)
+ return entry_token_load_one(rfd, conf_root, type, token);
+
+ FOREACH_STRING(path, "/etc/kernel", "/usr/lib/kernel") {
+ r = entry_token_load_one(rfd, path, type, token);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) {
char *p;
@@ -123,7 +141,7 @@ static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char *
int boot_entry_token_ensure_at(
int rfd,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
@@ -141,7 +159,7 @@ int boot_entry_token_ensure_at(
switch (*type) {
case BOOT_ENTRY_TOKEN_AUTO:
- r = entry_token_load(rfd, etc_kernel, type, token);
+ r = entry_token_load(rfd, conf_root, type, token);
if (r != 0)
return r;
@@ -198,7 +216,7 @@ int boot_entry_token_ensure_at(
int boot_entry_token_ensure(
const char *root,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
@@ -215,7 +233,7 @@ int boot_entry_token_ensure(
if (rfd < 0)
return -errno;
- return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token);
+ return boot_entry_token_ensure_at(rfd, conf_root, machine_id, machine_id_is_random, type, token);
}
int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) {
diff --git a/src/shared/boot-entry.h b/src/shared/boot-entry.h
index f3a6f28..836b637 100644
--- a/src/shared/boot-entry.h
+++ b/src/shared/boot-entry.h
@@ -17,14 +17,14 @@ bool boot_entry_token_valid(const char *p);
int boot_entry_token_ensure(
const char *root,
- const char *etc_kernel, /* will be prefixed with root, typically /etc/kernel. */
+ const char *conf_root, /* will be prefixed with root, typically /etc/kernel. */
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type, /* input and output */
char **token); /* output, but do not pass uninitialized value. */
int boot_entry_token_ensure_at(
int rfd,
- const char *etc_kernel,
+ const char *conf_root,
sd_id128_t machine_id,
bool machine_id_is_random,
BootEntryTokenType *type,
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
index f4b2fdc..4bc3ae7 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -57,6 +57,7 @@ static void boot_entry_free(BootEntry *entry) {
free(entry->machine_id);
free(entry->architecture);
strv_free(entry->options);
+ free(entry->local_addons.items);
free(entry->kernel);
free(entry->efi);
strv_free(entry->initrd);
@@ -78,10 +79,7 @@ static int mangle_path(
assert(ret);
/* Spec leaves open if prefixed with "/" or not, let's normalize that */
- if (path_is_absolute(p))
- c = strdup(p);
- else
- c = strjoin("/", p);
+ c = path_make_absolute(p, "/");
if (!c)
return -ENOMEM;
@@ -288,7 +286,6 @@ static int boot_entry_load_type1(
BootEntry *entry) {
_cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_CONF);
- unsigned line = 1;
char *c;
int r;
@@ -323,18 +320,16 @@ static int boot_entry_load_type1(
if (!tmp.root)
return log_oom();
- for (;;) {
+ for (unsigned line = 1;; line++) {
_cleanup_free_ char *buf = NULL, *field = NULL;
r = read_stripped_line(f, LONG_LINE_MAX, &buf);
- if (r == 0)
- break;
if (r == -ENOBUFS)
return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long.");
if (r < 0)
return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m");
-
- line++;
+ if (r == 0)
+ break;
if (IN_SET(buf[0], '#', '\0'))
continue;
@@ -421,44 +416,36 @@ void boot_config_free(BootConfig *config) {
assert(config);
free(config->default_pattern);
- free(config->timeout);
- free(config->editor);
- free(config->auto_entries);
- free(config->auto_firmware);
- free(config->console_mode);
- free(config->beep);
free(config->entry_oneshot);
free(config->entry_default);
free(config->entry_selected);
- for (size_t i = 0; i < config->n_entries; i++)
- boot_entry_free(config->entries + i);
+ FOREACH_ARRAY(i, config->entries, config->n_entries)
+ boot_entry_free(i);
free(config->entries);
+ free(config->global_addons.items);
set_free(config->inodes_seen);
}
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
- unsigned line = 1;
int r;
assert(config);
assert(file);
assert(path);
- for (;;) {
+ for (unsigned line = 1;; line++) {
_cleanup_free_ char *buf = NULL, *field = NULL;
r = read_stripped_line(file, LONG_LINE_MAX, &buf);
- if (r == 0)
- break;
if (r == -ENOBUFS)
return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long.");
if (r < 0)
return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m");
-
- line++;
+ if (r == 0)
+ break;
if (IN_SET(buf[0], '#', '\0'))
continue;
@@ -480,20 +467,10 @@ int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
if (streq(field, "default"))
r = free_and_strdup(&config->default_pattern, p);
- else if (streq(field, "timeout"))
- r = free_and_strdup(&config->timeout, p);
- else if (streq(field, "editor"))
- r = free_and_strdup(&config->editor, p);
- else if (streq(field, "auto-entries"))
- r = free_and_strdup(&config->auto_entries, p);
- else if (streq(field, "auto-firmware"))
- r = free_and_strdup(&config->auto_firmware, p);
- else if (streq(field, "console-mode"))
- r = free_and_strdup(&config->console_mode, p);
- else if (streq(field, "random-seed-mode"))
- log_syntax(NULL, LOG_WARNING, path, line, 0, "'random-seed-mode' has been deprecated, ignoring.");
- else if (streq(field, "beep"))
- r = free_and_strdup(&config->beep, p);
+ else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware",
+ "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker",
+ "secure-boot-enroll", "console-mode"))
+ r = 0; /* we don't parse these in userspace, but they are OK */
else {
log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field);
continue;
@@ -609,8 +586,8 @@ static int boot_entries_find_type1(
if (r < 0)
return log_error_errno(r, "Failed to read directory '%s': %m", full);
- for (size_t i = 0; i < dentries->n_entries; i++) {
- const struct dirent *de = dentries->entries[i];
+ FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
+ const struct dirent *de = *i;
_cleanup_fclose_ FILE *f = NULL;
if (!dirent_is_file(de))
@@ -633,7 +610,7 @@ static int boot_entries_find_type1(
r = boot_config_load_type1(config, f, root, full, de->d_name);
if (r == -ENOMEM) /* ignore all other errors */
- return r;
+ return log_oom();
}
return 0;
@@ -753,13 +730,12 @@ static int boot_entry_load_unified(
static int find_sections(
int fd,
const char *path,
- char **ret_osrelease,
- char **ret_cmdline) {
+ IMAGE_SECTION_HEADER **ret_sections,
+ PeHeader **ret_pe_header) {
- _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
- _cleanup_free_ char *osrel = NULL, *cmdline = NULL;
- _cleanup_free_ PeHeader *pe_header = NULL;
+ IMAGE_SECTION_HEADER *sections;
+ PeHeader *pe_header;
int r;
assert(fd >= 0);
@@ -773,25 +749,253 @@ static int find_sections(
if (r < 0)
return log_warning_errno(r, "Failed to parse PE sections of '%s': %m", path);
- if (!pe_is_uki(pe_header, sections))
- return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
+ if (ret_pe_header)
+ *ret_pe_header = TAKE_PTR(pe_header);
+ if (ret_sections)
+ *ret_sections = TAKE_PTR(sections);
- r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) &osrel, NULL);
- if (r < 0)
- return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path);
+ return 0;
+}
+
+static int find_cmdline_section(
+ int fd,
+ const char *path,
+ IMAGE_SECTION_HEADER *sections,
+ PeHeader *pe_header,
+ char **ret_cmdline) {
+
+ int r;
+ char *cmdline = NULL, *t = NULL;
+ _cleanup_free_ char *word = NULL;
+
+ assert(path);
+
+ if (!ret_cmdline)
+ return 0;
r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL);
- if (r < 0 && r != -ENXIO) /* cmdline is optional */
+ if (r == -ENXIO) { /* cmdline is optional */
+ *ret_cmdline = NULL;
+ return 0;
+ }
+ if (r < 0)
return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path);
- if (ret_osrelease)
- *ret_osrelease = TAKE_PTR(osrel);
- if (ret_cmdline)
+ word = strdup(cmdline);
+ if (!word)
+ return log_oom();
+
+ /* Quick test to check if there is actual content in the addon cmdline */
+ t = delete_chars(word, NULL);
+ if (isempty(t))
+ *ret_cmdline = NULL;
+ else
*ret_cmdline = TAKE_PTR(cmdline);
return 0;
}
+static int find_osrel_section(
+ int fd,
+ const char *path,
+ IMAGE_SECTION_HEADER *sections,
+ PeHeader *pe_header,
+ char **ret_osrelease) {
+
+ int r;
+
+ if (!ret_osrelease)
+ return 0;
+
+ r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path);
+
+ return 0;
+}
+
+static int find_uki_sections(
+ int fd,
+ const char *path,
+ char **ret_osrelease,
+ char **ret_cmdline) {
+
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ int r;
+
+ r = find_sections(fd, path, &sections, &pe_header);
+ if (r < 0)
+ return r;
+
+ if (!pe_is_uki(pe_header, sections))
+ return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
+
+ r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease);
+ if (r < 0)
+ return r;
+
+ r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int find_addon_sections(
+ int fd,
+ const char *path,
+ char **ret_cmdline) {
+
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ int r;
+
+ r = find_sections(fd, path, &sections, &pe_header);
+ if (r < 0)
+ return r;
+
+ r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
+ /* If addon cmdline is empty or contains just separators,
+ * don't bother tracking it.
+ * Don't check r because it cannot return <0 if cmdline is empty,
+ * as cmdline is always optional. */
+ if (!ret_cmdline)
+ return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Addon %s contains empty cmdline and will be therefore ignored.", path);
+
+ return r;
+}
+
+static int insert_boot_entry_addon(
+ BootEntryAddons *addons,
+ char *location,
+ char *cmdline) {
+
+ assert(addons);
+
+ if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
+ return log_oom();
+
+ addons->items[addons->n_items++] = (BootEntryAddon) {
+ .location = location,
+ .cmdline = cmdline,
+ };
+
+ return 0;
+}
+
+static void boot_entry_addons_done(BootEntryAddons *addons) {
+ assert(addons);
+
+ FOREACH_ARRAY(addon, addons->items, addons->n_items) {
+ free(addon->cmdline);
+ free(addon->location);
+ }
+ addons->items = mfree(addons->items);
+ addons->n_items = 0;
+}
+
+static int boot_entries_find_unified_addons(
+ BootConfig *config,
+ int d_fd,
+ const char *addon_dir,
+ const char *root,
+ BootEntryAddons *ret_addons) {
+
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *full = NULL;
+ _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
+ int r;
+
+ assert(ret_addons);
+ assert(config);
+
+ r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to open '%s/%s': %m", root, addon_dir);
+
+ FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
+ _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ if (!dirent_is_file(de))
+ continue;
+
+ if (!endswith_no_case(de->d_name, ".addon.efi"))
+ continue;
+
+ fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
+ if (fd < 0) {
+ log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
+ continue;
+ }
+
+ r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
+ if (r < 0)
+ return r;
+ if (r == 0) /* inode already seen or otherwise not relevant */
+ continue;
+
+ j = path_join(full, de->d_name);
+ if (!j)
+ return log_oom();
+
+ if (find_addon_sections(fd, j, &cmdline) < 0)
+ continue;
+
+ location = strdup(j);
+ if (!location)
+ return log_oom();
+
+ r = insert_boot_entry_addon(&addons, location, cmdline);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(location);
+ TAKE_PTR(cmdline);
+ }
+
+ *ret_addons = TAKE_STRUCT(addons);
+ return 0;
+}
+
+static int boot_entries_find_unified_global_addons(
+ BootConfig *config,
+ const char *root,
+ const char *d_name) {
+
+ int r;
+ _cleanup_closedir_ DIR *d = NULL;
+
+ r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name);
+
+ return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, &config->global_addons);
+}
+
+static int boot_entries_find_unified_local_addons(
+ BootConfig *config,
+ int d_fd,
+ const char *d_name,
+ const char *root,
+ BootEntry *ret) {
+
+ _cleanup_free_ char *addon_dir = NULL;
+
+ assert(ret);
+
+ addon_dir = strjoin(d_name, ".extra.d");
+ if (!addon_dir)
+ return log_oom();
+
+ return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
+}
+
static int boot_entries_find_unified(
BootConfig *config,
const char *root,
@@ -839,13 +1043,18 @@ static int boot_entries_find_unified(
if (!j)
return log_oom();
- if (find_sections(fd, j, &osrelease, &cmdline) < 0)
+ if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0)
continue;
r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries);
if (r < 0)
continue;
+ /* look for .efi.extra.d */
+ r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries);
+ if (r < 0)
+ continue;
+
config->n_entries++;
}
@@ -1062,6 +1271,7 @@ int boot_config_load(
int r;
assert(config);
+ config->global_addons = (BootEntryAddons) {};
if (esp_path) {
r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
@@ -1075,6 +1285,10 @@ int boot_config_load(
r = boot_entries_find_unified(config, esp_path, "/EFI/Linux/");
if (r < 0)
return r;
+
+ r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/");
+ if (r < 0)
+ return r;
}
if (xbootldr_path) {
@@ -1238,13 +1452,155 @@ static void boot_entry_file_list(
*ret_status = status;
}
+static void print_addon(
+ BootEntryAddon *addon,
+ const char *addon_str) {
+
+ printf(" %s: %s\n", addon_str, addon->location);
+ printf(" options: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), addon->cmdline);
+}
+
+static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
+ _cleanup_free_ char *t = NULL;
+ _cleanup_strv_free_ char **ts = NULL;
+
+ assert(ret_cmdline);
+
+ ts = strv_split_newlines(cmdline);
+ if (!ts)
+ return -ENOMEM;
+
+ t = strv_join(ts, "\n ");
+ if (!t)
+ return -ENOMEM;
+
+ *ret_cmdline = TAKE_PTR(t);
+
+ return 0;
+}
+
+static int print_cmdline(
+ const BootEntry *e,
+ const BootEntryAddons *global_arr) {
+
+ _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
+
+ assert(e);
+
+ if (!strv_isempty(e->options)) {
+ _cleanup_free_ char *t = NULL;
+
+ options = strv_join(e->options, " ");
+ if (!options)
+ return log_oom();
+
+ if (indent_embedded_newlines(options, &t) < 0)
+ return log_oom();
+
+ printf(" options: %s\n", t);
+ t2 = strdup(options);
+ if (!t2)
+ return log_oom();
+ }
+
+ FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) {
+ print_addon(addon, "global-addon");
+ if (!strextend(&t2, " ", addon->cmdline))
+ return log_oom();
+ }
+
+ FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
+ /* Add space at the beginning of addon_str to align it correctly */
+ print_addon(addon, " local-addon");
+ if (!strextend(&t2, " ", addon->cmdline))
+ return log_oom();
+ }
+
+ /* Don't print the combined cmdline if it's same as options. */
+ if (streq_ptr(t2, options))
+ return 0;
+
+ if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
+ return log_oom();
+
+ if (combined_cmdline)
+ printf(" cmdline: %s\n", combined_cmdline);
+
+ return 0;
+}
+
+static int json_addon(
+ BootEntryAddon *addon,
+ const char *addon_str,
+ JsonVariant **array) {
+
+ int r;
+
+ assert(addon);
+ assert(addon_str);
+
+ r = json_variant_append_arrayb(
+ array,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR(addon_str, JSON_BUILD_STRING(addon->location)),
+ JSON_BUILD_PAIR("options", JSON_BUILD_STRING(addon->cmdline))));
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+}
+
+static int json_cmdline(
+ const BootEntry *e,
+ const BootEntryAddons *global_arr,
+ const char *def_cmdline,
+ JsonVariant **v) {
+
+ _cleanup_free_ char *combined_cmdline = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *addons_array = NULL;
+ int r;
+
+ assert(e);
+
+ if (def_cmdline) {
+ combined_cmdline = strdup(def_cmdline);
+ if (!combined_cmdline)
+ return log_oom();
+ }
+
+ FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) {
+ r = json_addon(addon, "globalAddon", &addons_array);
+ if (r < 0)
+ return r;
+ if (!strextend(&combined_cmdline, " ", addon->cmdline))
+ return log_oom();
+ }
+
+ FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
+ r = json_addon(addon, "localAddon", &addons_array);
+ if (r < 0)
+ return r;
+ if (!strextend(&combined_cmdline, " ", addon->cmdline))
+ return log_oom();
+ }
+
+ r = json_variant_merge_objectb(
+ v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array)),
+ JSON_BUILD_PAIR_CONDITION(combined_cmdline, "cmdline", JSON_BUILD_STRING(combined_cmdline))));
+ if (r < 0)
+ return log_oom();
+ return 0;
+}
+
int show_boot_entry(
const BootEntry *e,
+ const BootEntryAddons *global_addons,
bool show_as_default,
bool show_as_selected,
bool show_reported) {
- int status = 0;
+ int status = 0, r = 0;
/* Returns 0 on success, negative on processing error, and positive if something is wrong with the
boot entry itself. */
@@ -1323,24 +1679,9 @@ int show_boot_entry(
*s,
&status);
- if (!strv_isempty(e->options)) {
- _cleanup_free_ char *t = NULL, *t2 = NULL;
- _cleanup_strv_free_ char **ts = NULL;
-
- t = strv_join(e->options, " ");
- if (!t)
- return log_oom();
-
- ts = strv_split_newlines(t);
- if (!ts)
- return log_oom();
-
- t2 = strv_join(ts, "\n ");
- if (!t2)
- return log_oom();
-
- printf(" options: %s\n", t2);
- }
+ r = print_cmdline(e, global_addons);
+ if (r < 0)
+ return r;
if (e->device_tree)
boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
@@ -1354,6 +1695,71 @@ int show_boot_entry(
return -status;
}
+int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *opts = NULL;
+ const BootEntry *e;
+ int r;
+
+ assert(c);
+ assert(ret);
+
+ if (i >= c->n_entries) {
+ *ret = NULL;
+ return 0;
+ }
+
+ e = c->entries + i;
+
+ if (!strv_isempty(e->options)) {
+ opts = strv_join(e->options, " ");
+ if (!opts)
+ return log_oom();
+ }
+
+ r = json_variant_merge_objectb(
+ &v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
+ JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
+ JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
+ JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
+ JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)),
+ JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))),
+ JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)),
+ JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
+ JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
+ JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
+ JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
+ JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
+ JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
+ JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
+ JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)),
+ JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay))));
+ if (r < 0)
+ return log_oom();
+
+ /* Sanitizers (only memory sanitizer?) do not like function call with too many
+ * arguments and trigger false positive warnings. Let's not add too many json objects
+ * at once. */
+ r = json_variant_merge_objectb(
+ &v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)),
+ JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)),
+ JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)),
+ JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
+ JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry))));
+
+ if (r < 0)
+ return log_oom();
+
+ r = json_cmdline(e, &c->global_addons, opts, &v);
+ if (r < 0)
+ return log_oom();
+
+ *ret = TAKE_PTR(v);
+ return 1;
+}
+
int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
int r;
@@ -1363,48 +1769,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
for (size_t i = 0; i < config->n_entries; i++) {
- _cleanup_free_ char *opts = NULL;
- const BootEntry *e = config->entries + i;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- if (!strv_isempty(e->options)) {
- opts = strv_join(e->options, " ");
- if (!opts)
- return log_oom();
- }
-
- r = json_variant_merge_objectb(
- &v, JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
- JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
- JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
- JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
- JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)),
- JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))),
- JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)),
- JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
- JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
- JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
- JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
- JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
- JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
- JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
- JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)),
- JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay))));
- if (r < 0)
- return log_oom();
-
- /* Sanitizers (only memory sanitizer?) do not like function call with too many
- * arguments and trigger false positive warnings. Let's not add too many json objects
- * at once. */
- r = json_variant_merge_objectb(
- &v, JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)),
- JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)),
- JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)),
- JSON_BUILD_PAIR_CONDITION(config->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) config->default_entry)),
- JSON_BUILD_PAIR_CONDITION(config->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) config->selected_entry))));
-
+ r = boot_entry_to_json(config, i, &v);
if (r < 0)
return log_oom();
@@ -1413,12 +1780,12 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
return log_oom();
}
- json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
-
- } else {
+ return json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
+ } else
for (size_t n = 0; n < config->n_entries; n++) {
r = show_boot_entry(
config->entries + n,
+ &config->global_addons,
/* show_as_default= */ n == (size_t) config->default_entry,
/* show_as_selected= */ n == (size_t) config->selected_entry,
/* show_discovered= */ true);
@@ -1428,7 +1795,6 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
if (n+1 < config->n_entries)
putchar('\n');
}
- }
return 0;
}
diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h
index ddd149e..1885a88 100644
--- a/src/shared/bootspec.h
+++ b/src/shared/bootspec.h
@@ -20,6 +20,18 @@ typedef enum BootEntryType {
_BOOT_ENTRY_TYPE_INVALID = -EINVAL,
} BootEntryType;
+typedef struct BootEntryAddon {
+ char *location;
+ char *cmdline;
+} BootEntryAddon;
+
+typedef struct BootEntryAddons {
+ BootEntryAddon *items;
+ size_t n_items;
+} BootEntryAddons;
+
+BootEntryAddon* boot_entry_addon_free(BootEntryAddon *t);
+
typedef struct BootEntry {
BootEntryType type;
bool reported_by_loader;
@@ -34,6 +46,7 @@ typedef struct BootEntry {
char *machine_id;
char *architecture;
char **options;
+ BootEntryAddons local_addons;
char *kernel; /* linux is #defined to 1, yikes! */
char *efi;
char **initrd;
@@ -52,12 +65,6 @@ typedef struct BootEntry {
typedef struct BootConfig {
char *default_pattern;
- char *timeout;
- char *editor;
- char *auto_entries;
- char *auto_firmware;
- char *console_mode;
- char *beep;
char *entry_oneshot;
char *entry_default;
@@ -66,6 +73,8 @@ typedef struct BootConfig {
BootEntry *entries;
size_t n_entries;
+ BootEntryAddons global_addons;
+
ssize_t default_entry;
ssize_t selected_entry;
@@ -119,6 +128,7 @@ static inline const char* boot_entry_title(const BootEntry *entry) {
int show_boot_entry(
const BootEntry *e,
+ const BootEntryAddons *global_addons,
bool show_as_default,
bool show_as_selected,
bool show_reported);
@@ -127,3 +137,5 @@ int show_boot_entries(
JsonFormatFlags json_format);
int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done);
+
+int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret);
diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h
index 9ccb7d8..5a7945c 100644
--- a/src/shared/bpf-compat.h
+++ b/src/shared/bpf-compat.h
@@ -26,6 +26,7 @@ struct bpf_map_create_opts;
* When removing this file move these back to bpf-dlopen.h */
extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *);
extern int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *);
+extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map);
/* compat symbols removed in libbpf 1.0 */
extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c
index f00dbea..50491fc 100644
--- a/src/shared/bpf-dlopen.c
+++ b/src/shared/bpf-dlopen.c
@@ -18,26 +18,41 @@
#define MODERN_LIBBPF 0
#endif
-struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int);
-struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *);
-int (*sym_bpf_link__fd)(const struct bpf_link *);
-int (*sym_bpf_link__destroy)(struct bpf_link *);
-int (*sym_bpf_map__fd)(const struct bpf_map *);
-const char* (*sym_bpf_map__name)(const struct bpf_map *);
+DLSYM_FUNCTION(bpf_link__destroy);
+DLSYM_FUNCTION(bpf_link__fd);
+DLSYM_FUNCTION(bpf_link__open);
+DLSYM_FUNCTION(bpf_link__pin);
+DLSYM_FUNCTION(bpf_map__fd);
+DLSYM_FUNCTION(bpf_map__name);
+DLSYM_FUNCTION(bpf_map__set_inner_map_fd);
+DLSYM_FUNCTION(bpf_map__set_max_entries);
+DLSYM_FUNCTION(bpf_map__set_pin_path);
+DLSYM_FUNCTION(bpf_map_delete_elem);
+DLSYM_FUNCTION(bpf_map_get_fd_by_id);
+DLSYM_FUNCTION(bpf_map_lookup_elem);
+DLSYM_FUNCTION(bpf_map_update_elem);
+DLSYM_FUNCTION(bpf_object__attach_skeleton);
+DLSYM_FUNCTION(bpf_object__destroy_skeleton);
+DLSYM_FUNCTION(bpf_object__detach_skeleton);
+DLSYM_FUNCTION(bpf_object__load_skeleton);
+DLSYM_FUNCTION(bpf_object__name);
+DLSYM_FUNCTION(bpf_object__open_skeleton);
+DLSYM_FUNCTION(bpf_object__pin_maps);
+DLSYM_FUNCTION(bpf_program__attach);
+DLSYM_FUNCTION(bpf_program__attach_cgroup);
+DLSYM_FUNCTION(bpf_program__attach_lsm);
+DLSYM_FUNCTION(bpf_program__name);
+DLSYM_FUNCTION(libbpf_get_error);
+DLSYM_FUNCTION(libbpf_set_print);
+DLSYM_FUNCTION(ring_buffer__epoll_fd);
+DLSYM_FUNCTION(ring_buffer__free);
+DLSYM_FUNCTION(ring_buffer__new);
+DLSYM_FUNCTION(ring_buffer__poll);
+
+/* new symbols available from libbpf 0.7.0 */
int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *);
-int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32);
-int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64);
-int (*sym_bpf_map_delete_elem)(int, const void *);
-int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int);
-int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *);
-int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *);
-int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *);
-void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *);
-void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *);
int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *);
-const char* (*sym_bpf_program__name)(const struct bpf_program *);
-libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t);
-long (*sym_libbpf_get_error)(const void *);
+struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map);
/* compat symbols removed in libbpf 1.0 */
int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags);
@@ -61,6 +76,11 @@ int dlopen_bpf(void) {
void *dl;
int r;
+ ELF_NOTE_DLOPEN("bpf",
+ "Support firewalling and sandboxing with BPF",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libbpf.so.1", "libbpf.so.0");
+
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
dl = dlopen("libbpf.so.1", RTLD_LAZY);
@@ -88,6 +108,8 @@ int dlopen_bpf(void) {
DLSYM_ARG(bpf_probe_prog_type)
#endif
);
+
+ /* NB: we don't try to load bpf_object__next_map() on old versions */
} else {
log_debug("Loaded 'libbpf.so.1' via dlopen()");
@@ -96,11 +118,13 @@ int dlopen_bpf(void) {
dl, LOG_DEBUG,
#if MODERN_LIBBPF
DLSYM_ARG(bpf_map_create),
- DLSYM_ARG(libbpf_probe_bpf_prog_type)
+ DLSYM_ARG(libbpf_probe_bpf_prog_type),
+ DLSYM_ARG(bpf_object__next_map)
#else
/* These symbols did not exist in old libbpf, hence we cannot type check them */
DLSYM_ARG_FORCE(bpf_map_create),
- DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type)
+ DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type),
+ DLSYM_ARG_FORCE(bpf_object__next_map)
#endif
);
}
@@ -111,28 +135,41 @@ int dlopen_bpf(void) {
dl, LOG_DEBUG,
DLSYM_ARG(bpf_link__destroy),
DLSYM_ARG(bpf_link__fd),
+ DLSYM_ARG(bpf_link__open),
+ DLSYM_ARG(bpf_link__pin),
DLSYM_ARG(bpf_map__fd),
DLSYM_ARG(bpf_map__name),
+ DLSYM_ARG(bpf_map__set_inner_map_fd),
DLSYM_ARG(bpf_map__set_max_entries),
- DLSYM_ARG(bpf_map_update_elem),
+ DLSYM_ARG(bpf_map__set_pin_path),
DLSYM_ARG(bpf_map_delete_elem),
- DLSYM_ARG(bpf_map__set_inner_map_fd),
- DLSYM_ARG(bpf_object__open_skeleton),
- DLSYM_ARG(bpf_object__load_skeleton),
+ DLSYM_ARG(bpf_map_get_fd_by_id),
+ DLSYM_ARG(bpf_map_lookup_elem),
+ DLSYM_ARG(bpf_map_update_elem),
DLSYM_ARG(bpf_object__attach_skeleton),
- DLSYM_ARG(bpf_object__detach_skeleton),
DLSYM_ARG(bpf_object__destroy_skeleton),
+ DLSYM_ARG(bpf_object__detach_skeleton),
+ DLSYM_ARG(bpf_object__load_skeleton),
+ DLSYM_ARG(bpf_object__name),
+ DLSYM_ARG(bpf_object__open_skeleton),
+ DLSYM_ARG(bpf_object__pin_maps),
#if MODERN_LIBBPF
+ DLSYM_ARG(bpf_program__attach),
DLSYM_ARG(bpf_program__attach_cgroup),
DLSYM_ARG(bpf_program__attach_lsm),
#else
/* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */
+ DLSYM_ARG_FORCE(bpf_program__attach),
DLSYM_ARG_FORCE(bpf_program__attach_cgroup),
DLSYM_ARG_FORCE(bpf_program__attach_lsm),
#endif
DLSYM_ARG(bpf_program__name),
+ DLSYM_ARG(libbpf_get_error),
DLSYM_ARG(libbpf_set_print),
- DLSYM_ARG(libbpf_get_error));
+ DLSYM_ARG(ring_buffer__epoll_fd),
+ DLSYM_ARG(ring_buffer__free),
+ DLSYM_ARG(ring_buffer__new),
+ DLSYM_ARG(ring_buffer__poll));
if (r < 0)
return r;
@@ -144,6 +181,23 @@ int dlopen_bpf(void) {
return r;
}
+int bpf_get_error_translated(const void *ptr) {
+ int r;
+
+ r = sym_libbpf_get_error(ptr);
+
+ switch (r) {
+ case -524:
+ /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until
+ * it is fixed:
+ * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27
+ */
+ return -EOPNOTSUPP;
+ default:
+ return r;
+ }
+}
+
#else
int dlopen_bpf(void) {
diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h
index 0750abc..df12d08 100644
--- a/src/shared/bpf-dlopen.h
+++ b/src/shared/bpf-dlopen.h
@@ -7,27 +7,44 @@
#include <bpf/libbpf.h>
#include "bpf-compat.h"
+#include "dlfcn-util.h"
-extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int);
-extern struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *);
-extern int (*sym_bpf_link__fd)(const struct bpf_link *);
-extern int (*sym_bpf_link__destroy)(struct bpf_link *);
-extern int (*sym_bpf_map__fd)(const struct bpf_map *);
-extern const char* (*sym_bpf_map__name)(const struct bpf_map *);
-extern int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32);
-extern int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64);
-extern int (*sym_bpf_map_delete_elem)(int, const void *);
-extern int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int);
+DLSYM_PROTOTYPE(bpf_link__destroy);
+DLSYM_PROTOTYPE(bpf_link__fd);
+DLSYM_PROTOTYPE(bpf_link__open);
+DLSYM_PROTOTYPE(bpf_link__pin);
+DLSYM_PROTOTYPE(bpf_map__fd);
+DLSYM_PROTOTYPE(bpf_map__name);
+DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd);
+DLSYM_PROTOTYPE(bpf_map__set_max_entries);
+DLSYM_PROTOTYPE(bpf_map__set_pin_path);
+DLSYM_PROTOTYPE(bpf_map_delete_elem);
+DLSYM_PROTOTYPE(bpf_map_get_fd_by_id);
+DLSYM_PROTOTYPE(bpf_map_lookup_elem);
+DLSYM_PROTOTYPE(bpf_map_update_elem);
/* The *_skeleton APIs are autogenerated by bpftool, the targets can be found
* in ./build/src/core/bpf/socket_bind/socket-bind.skel.h */
-extern int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *);
-extern int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *);
-extern int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *);
-extern void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *);
-extern void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *);
-extern const char* (*sym_bpf_program__name)(const struct bpf_program *);
-extern libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t);
-extern long (*sym_libbpf_get_error)(const void *);
+DLSYM_PROTOTYPE(bpf_object__attach_skeleton);
+DLSYM_PROTOTYPE(bpf_object__destroy_skeleton);
+DLSYM_PROTOTYPE(bpf_object__detach_skeleton);
+DLSYM_PROTOTYPE(bpf_object__load_skeleton);
+DLSYM_PROTOTYPE(bpf_object__name);
+DLSYM_PROTOTYPE(bpf_object__open_skeleton);
+DLSYM_PROTOTYPE(bpf_object__pin_maps);
+DLSYM_PROTOTYPE(bpf_program__attach);
+DLSYM_PROTOTYPE(bpf_program__attach_cgroup);
+DLSYM_PROTOTYPE(bpf_program__attach_lsm);
+DLSYM_PROTOTYPE(bpf_program__name);
+DLSYM_PROTOTYPE(libbpf_set_print);
+DLSYM_PROTOTYPE(ring_buffer__epoll_fd);
+DLSYM_PROTOTYPE(ring_buffer__free);
+DLSYM_PROTOTYPE(ring_buffer__new);
+DLSYM_PROTOTYPE(ring_buffer__poll);
+
+/* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use
+ * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos
+ * we understand. */
+int bpf_get_error_translated(const void *ptr);
#endif
diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c
index fea49b2..77f6a4e 100644
--- a/src/shared/bpf-link.c
+++ b/src/shared/bpf-link.c
@@ -16,7 +16,7 @@ bool bpf_can_link_program(struct bpf_program *prog) {
link = sym_bpf_program__attach_cgroup(prog, /*cgroup_fd=*/-1);
/* EBADF indicates that bpf_link is supported by kernel. */
- return sym_libbpf_get_error(link) == -EBADF;
+ return bpf_get_error_translated(link) == -EBADF;
}
int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) {
@@ -25,7 +25,7 @@ int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *li
if (!link)
return -ENOENT;
- if (sym_libbpf_get_error(link) != 0)
+ if (bpf_get_error_translated(link) != 0)
return -EINVAL;
return serialize_fd(f, fds, key, sym_bpf_link__fd(link));
diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c
index bbdd4f6..ac92ec8 100644
--- a/src/shared/bpf-program.c
+++ b/src/shared/bpf-program.c
@@ -321,7 +321,7 @@ int bpf_map_new(
/* The map name is primarily informational for debugging purposes, and typically too short
* to carry the full unit name, hence we employ a trivial lossy escaping to make it fit
* (truncation + only alphanumerical, "." and "_" are allowed as per
- * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */
+ * https://docs.kernel.org/bpf/maps.html#usage-notes) */
for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++)
attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_';
diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c
index 2ed6bf2..f6055a8 100644
--- a/src/shared/btrfs-util.c
+++ b/src/shared/btrfs-util.c
@@ -262,24 +262,49 @@ static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args
}
typedef struct BtrfsForeachIterator {
- const void *p;
- size_t i;
+ const struct btrfs_ioctl_search_args *args;
+ size_t offset;
+ unsigned index;
+ struct btrfs_ioctl_search_header *header;
+ const void **body;
} BtrfsForeachIterator;
+static int btrfs_iterate(BtrfsForeachIterator *i) {
+ assert(i);
+ assert(i->args);
+ assert(i->header);
+ assert(i->body);
+
+ if (i->index >= i->args->key.nr_items)
+ return 0; /* end */
+
+ assert_cc(BTRFS_SEARCH_ARGS_BUFSIZE >= sizeof(struct btrfs_ioctl_search_header));
+ if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header))
+ return -EBADMSG;
+
+ struct btrfs_ioctl_search_header h;
+ memcpy(&h, (const uint8_t*) i->args->buf + i->offset, sizeof(struct btrfs_ioctl_search_header));
+
+ if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header) - h.len)
+ return -EBADMSG;
+
+ *i->body = (const uint8_t*) i->args->buf + i->offset + sizeof(struct btrfs_ioctl_search_header);
+ *i->header = h;
+ i->offset += sizeof(struct btrfs_ioctl_search_header) + h.len;
+ i->index++;
+
+ return 1;
+}
+
/* Iterates through a series of struct btrfs_file_extent_item elements. They are unfortunately not aligned,
* hence we copy out the header from them */
-#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) \
+#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(_sh, _body, _args) \
for (BtrfsForeachIterator iterator = { \
- .p = ({ \
- memcpy(&(sh), (args).buf, sizeof(struct btrfs_ioctl_search_header)); \
- (body) = (const void*) ((const uint8_t*) (args).buf + sizeof(struct btrfs_ioctl_search_header)); \
- (args).buf; \
- }), \
+ .args = &(_args), \
+ .header = &(_sh), \
+ .body = &(_body), \
}; \
- iterator.i < (args).key.nr_items; \
- iterator.i++, \
- memcpy(&(sh), iterator.p = (const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header) + (sh).len, sizeof(struct btrfs_ioctl_search_header)), \
- (body) = (const void*) ((const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header)))
+ btrfs_iterate(&iterator) > 0; )
int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) {
struct btrfs_ioctl_search_args args = {
@@ -1162,6 +1187,8 @@ static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_sub
if (n_old_qgroups <= 0) /* Nothing to copy */
return n_old_qgroups;
+ assert(old_qgroups); /* Coverity gets confused by the macro iterator allocating this, add a hint */
+
r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id);
if (r == -ENXIO)
/* We have no parent, hence nothing to copy. */
@@ -1785,6 +1812,24 @@ int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_i
return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup);
}
+int btrfs_subvol_make_default(const char *path) {
+ _cleanup_close_ int fd = -EBADF;
+ uint64_t id;
+ int r;
+
+ assert(path);
+
+ fd = open(path, O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+ if (fd < 0)
+ return -errno;
+
+ r = btrfs_subvol_get_id_fd(fd, &id);
+ if (r < 0)
+ return r;
+
+ return RET_NERRNO(ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id));
+}
+
int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) {
struct btrfs_ioctl_search_args args = {
diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h
index cd80903..6108a26 100644
--- a/src/shared/btrfs-util.h
+++ b/src/shared/btrfs-util.h
@@ -108,6 +108,8 @@ int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t
int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup);
int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup);
+int btrfs_subvol_make_default(const char *path);
+
int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret);
int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id);
diff --git a/src/shared/bus-map-properties.c b/src/shared/bus-map-properties.c
index 809759d..a4833a5 100644
--- a/src/shared/bus-map-properties.c
+++ b/src/shared/bus-map-properties.c
@@ -8,21 +8,12 @@
int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
sd_id128_t *p = userdata;
- const void *v;
- size_t n;
int r;
- r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n);
+ r = bus_message_read_id128(m, p);
if (r < 0)
return bus_log_parse_error_debug(r);
- if (n == 0)
- *p = SD_ID128_NULL;
- else if (n == 16)
- memcpy((*p).bytes, v, n);
- else
- return -EINVAL;
-
return 0;
}
diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c
index 904b897..0382d0b 100644
--- a/src/shared/bus-polkit.c
+++ b/src/shared/bus-polkit.c
@@ -4,10 +4,11 @@
#include "bus-message.h"
#include "bus-polkit.h"
#include "bus-util.h"
+#include "process-util.h"
#include "strv.h"
#include "user-util.h"
-static int check_good_user(sd_bus_message *m, uid_t good_user) {
+static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t sender_uid;
int r;
@@ -15,7 +16,7 @@ static int check_good_user(sd_bus_message *m, uid_t good_user) {
assert(m);
if (good_user == UID_INVALID)
- return 0;
+ return false;
r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
@@ -47,14 +48,10 @@ static int bus_message_append_strv_key_value(sd_bus_message *m, const char **l)
return r;
}
- r = sd_bus_message_close_container(m);
- if (r < 0)
- return r;
-
- return r;
+ return sd_bus_message_close_container(m);
}
-static int bus_message_new_polkit_auth_call(
+static int bus_message_new_polkit_auth_call_for_bus(
sd_bus_message *m,
const char *action,
const char **details,
@@ -102,7 +99,6 @@ static int bus_message_new_polkit_auth_call(
int bus_test_polkit(
sd_bus_message *call,
- int capability,
const char *action,
const char **details,
uid_t good_user,
@@ -116,11 +112,11 @@ int bus_test_polkit(
/* Tests non-interactively! */
- r = check_good_user(call, good_user);
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
- r = sd_bus_query_sender_privilege(call, capability);
+ r = sd_bus_query_sender_privilege(call, -1);
if (r < 0)
return r;
if (r > 0)
@@ -130,7 +126,7 @@ int bus_test_polkit(
_cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL;
int authorized = false, challenge = false;
- r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request);
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request);
if (r < 0)
return r;
@@ -189,18 +185,21 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQueryAction*, async_polkit_query_action_f
typedef struct AsyncPolkitQuery {
unsigned n_ref;
- AsyncPolkitQueryAction *action;
+ AsyncPolkitQueryAction *action; /* action currently being processed */
- sd_bus_message *request;
+ sd_bus *bus;
+ sd_bus_message *request; /* the original bus method call that triggered the polkit auth, NULL in case of varlink */
sd_bus_slot *slot;
+ Varlink *link; /* the original varlink method call that triggered the polkit auth, NULL in case of bus */
Hashmap *registry;
sd_event_source *defer_event_source;
- LIST_HEAD(AsyncPolkitQueryAction, authorized_actions);
- AsyncPolkitQueryAction *denied_action;
- AsyncPolkitQueryAction *error_action;
- sd_bus_error error;
+ LIST_HEAD(AsyncPolkitQueryAction, authorized_actions); /* actions we successfully were authorized for */
+ AsyncPolkitQueryAction *denied_action; /* if we received denial for an action, it's this one */
+ AsyncPolkitQueryAction *absent_action; /* If polkit was absent for some action, it's this one */
+ AsyncPolkitQueryAction *error_action; /* if we encountered any other error, it's this one */
+ sd_bus_error error; /* the precise error, in case error_action is set */
} AsyncPolkitQuery;
static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
@@ -209,11 +208,18 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
sd_bus_slot_unref(q->slot);
- if (q->registry && q->request)
- hashmap_remove(q->registry, q->request);
+ if (q->registry) {
+ if (q->request)
+ hashmap_remove(q->registry, q->request);
+ if (q->link)
+ hashmap_remove(q->registry, q->link);
+ }
sd_bus_message_unref(q->request);
+ sd_bus_unref(q->bus);
+ varlink_unref(q->link);
+
async_polkit_query_action_free(q->action);
sd_event_source_disable_unref(q->defer_event_source);
@@ -221,6 +227,7 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
LIST_CLEAR(authorized, q->authorized_actions, async_polkit_query_action_free);
async_polkit_query_action_free(q->denied_action);
+ async_polkit_query_action_free(q->absent_action);
async_polkit_query_action_free(q->error_action);
sd_bus_error_free(&q->error);
@@ -231,6 +238,14 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ async_polkit_query_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ AsyncPolkitQuery,
+ async_polkit_query_unref);
+
static int async_polkit_defer(sd_event_source *s, void *userdata) {
AsyncPolkitQuery *q = ASSERT_PTR(userdata);
@@ -252,23 +267,34 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) {
/* Processing of a PolicyKit checks is canceled on the first auth. error. */
assert(!q->denied_action);
+ assert(!q->absent_action);
assert(!q->error_action);
assert(!sd_bus_error_is_set(&q->error));
- assert(q->action);
- a = TAKE_PTR(q->action);
+ a = ASSERT_PTR(TAKE_PTR(q->action));
if (sd_bus_message_is_method_error(reply, NULL)) {
const sd_bus_error *e;
e = sd_bus_message_get_error(reply);
- if (bus_error_is_unknown_service(e))
- /* Treat no PK available as access denied */
+ if (bus_error_is_unknown_service(e)) {
+ /* If PK is absent, then store this away, as it depends on the callers flags whether
+ * this means deny or allow */
+ log_debug("Polkit found to be unavailable while trying to authorize action '%s'.", a->action);
+ q->absent_action = TAKE_PTR(a);
+ } else if (sd_bus_error_has_names(
+ e,
+ "org.freedesktop.PolicyKit1.Error.Failed",
+ "org.freedesktop.PolicyKit1.Error.Cancelled",
+ "org.freedesktop.PolicyKit1.Error.NotAuthorized")) {
+ /* Treat some of the well-known PK errors as denial. */
+ log_debug("Polkit authorization for action '%s' failed with an polkit error: %s", a->action, e->name);
q->denied_action = TAKE_PTR(a);
- else {
+ } else {
/* Save error from polkit reply, so it can be returned when the same authorization
* is attempted for second time */
+ log_debug("Polkit authorization for action '%s' failed with an unexpected error: %s", a->action, e->name);
q->error_action = TAKE_PTR(a);
r = sd_bus_error_copy(&q->error, e);
if (r == -ENOMEM)
@@ -284,13 +310,17 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) {
if (r < 0)
return r;
- if (authorized)
+ if (authorized) {
+ log_debug("Polkit authorization for action '%s' succeeded.", a->action);
LIST_PREPEND(authorized, q->authorized_actions, TAKE_PTR(a));
- else if (challenge) {
+ } else if (challenge) {
+ log_debug("Polkit authorization for action requires '%s' interactive authentication, which we didn't allow.", a->action);
q->error_action = TAKE_PTR(a);
sd_bus_error_set_const(&q->error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
- } else
+ } else {
+ log_debug("Polkit authorization for action '%s' denied.", a->action);
q->denied_action = TAKE_PTR(a);
+ }
return 0;
}
@@ -317,7 +347,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q
if (!q->defer_event_source) {
r = sd_event_add_defer(
- sd_bus_get_event(sd_bus_message_get_bus(reply)),
+ sd_bus_get_event(q->bus),
&q->defer_event_source,
async_polkit_defer,
q);
@@ -333,13 +363,21 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q
if (r < 0)
return r;
- r = sd_bus_message_rewind(q->request, true);
- if (r < 0)
- return r;
+ if (q->request) {
+ r = sd_bus_message_rewind(q->request, true);
+ if (r < 0)
+ return r;
- r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request);
- if (r < 0)
- return r;
+ r = sd_bus_enqueue_for_read(q->bus, q->request);
+ if (r < 0)
+ return r;
+ }
+
+ if (q->link) {
+ r = varlink_dispatch_again(q->link);
+ if (r < 0)
+ return r;
+ }
return 1;
}
@@ -353,35 +391,54 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e
r = async_polkit_process_reply(reply, q);
if (r < 0) {
log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m");
- (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->request)
+ (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->link)
+ (void) varlink_error_errno(q->link, r);
async_polkit_query_unref(q);
}
return r;
}
+static bool async_polkit_query_have_action(
+ AsyncPolkitQuery *q,
+ const char *action,
+ const char **details) {
+
+ assert(q);
+ assert(action);
+
+ LIST_FOREACH(authorized, a, q->authorized_actions)
+ if (streq(a->action, action) && strv_equal(a->details, (char**) details))
+ return true;
+
+ return false;
+}
+
static int async_polkit_query_check_action(
AsyncPolkitQuery *q,
const char *action,
const char **details,
+ PolkitFlags flags,
sd_bus_error *ret_error) {
assert(q);
assert(action);
- assert(ret_error);
- LIST_FOREACH(authorized, a, q->authorized_actions)
- if (streq(a->action, action) && strv_equal(a->details, (char**) details))
- return 1;
+ if (async_polkit_query_have_action(q, action, details))
+ return 1; /* Allow! */
if (q->error_action && streq(q->error_action->action, action))
return sd_bus_error_copy(ret_error, &q->error);
if (q->denied_action && streq(q->denied_action->action, action))
- return -EACCES;
+ return -EACCES; /* Deny! */
- return 0;
-}
+ if (q->absent_action)
+ return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 /* Allow! */ : -EACCES /* Deny! */;
+ return 0; /* no reply yet */
+}
#endif
/* bus_verify_polkit_async() handles verification of D-Bus calls with polkit. Because the polkit API
@@ -465,24 +522,24 @@ static int async_polkit_query_check_action(
* <- async_polkit_defer(q)
*/
-int bus_verify_polkit_async(
+int bus_verify_polkit_async_full(
sd_bus_message *call,
- int capability,
const char *action,
const char **details,
- bool interactive,
uid_t good_user,
+ PolkitFlags flags,
Hashmap **registry,
- sd_bus_error *ret_error) {
+ sd_bus_error *error) {
int r;
assert(call);
assert(action);
assert(registry);
- assert(ret_error);
- r = check_good_user(call, good_user);
+ log_debug("Trying to acquire polkit authentication for '%s'.", action);
+
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
@@ -493,20 +550,25 @@ int bus_verify_polkit_async(
/* This is a repeated invocation of this function, hence let's check if we've already got
* a response from polkit for this action */
if (q) {
- r = async_polkit_query_check_action(q, action, details, ret_error);
- if (r != 0)
+ r = async_polkit_query_check_action(q, action, details, flags, error);
+ if (r != 0) {
+ log_debug("Found matching previous polkit authentication for '%s'.", action);
return r;
+ }
}
#endif
- r = sd_bus_query_sender_privilege(call, capability);
- if (r < 0)
- return r;
- if (r > 0)
- return 1;
+ if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) {
+ /* Don't query PK if client is privileged */
+ r = sd_bus_query_sender_privilege(call, /* capability= */ -1);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
#if ENABLE_POLKIT
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ bool interactive = FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE);
int c = sd_bus_message_get_allow_interactive_authorization(call);
if (c < 0)
@@ -514,11 +576,8 @@ int bus_verify_polkit_async(
if (c > 0)
interactive = true;
- r = hashmap_ensure_allocated(registry, NULL);
- if (r < 0)
- return r;
-
- r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk);
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk);
if (r < 0)
return r;
@@ -530,6 +589,7 @@ int bus_verify_polkit_async(
*q = (AsyncPolkitQuery) {
.n_ref = 1,
.request = sd_bus_message_ref(call),
+ .bus = sd_bus_ref(sd_bus_message_get_bus(call)),
};
}
@@ -546,7 +606,7 @@ int bus_verify_polkit_async(
return -ENOMEM;
if (!q->registry) {
- r = hashmap_put(*registry, call, q);
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, call, q);
if (r < 0)
return r;
@@ -560,16 +620,264 @@ int bus_verify_polkit_async(
TAKE_PTR(q);
return 0;
+#else
+ return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES;
#endif
+}
- return -EACCES;
+static int varlink_check_good_user(Varlink *link, uid_t good_user) {
+ int r;
+
+ assert(link);
+
+ if (good_user == UID_INVALID)
+ return false;
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ return good_user == peer_uid;
+}
+
+static int varlink_check_peer_privilege(Varlink *link) {
+ int r;
+
+ assert(link);
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ uid_t our_uid = getuid();
+ return peer_uid == our_uid ||
+ (our_uid != 0 && peer_uid == 0);
+}
+
+#if ENABLE_POLKIT
+static int bus_message_new_polkit_auth_call_for_varlink(
+ sd_bus *bus,
+ Varlink *link,
+ const char *action,
+ const char **details,
+ bool interactive,
+ sd_bus_message **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ int r;
+
+ assert(bus);
+ assert(link);
+ assert(action);
+ assert(ret);
+
+ r = varlink_get_peer_pidref(link, &pidref);
+ if (r < 0)
+ return r;
+ if (r == 0) /* if we couldn't get a pidfd this returns == 0 */
+ return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate.");
+
+ uid_t uid;
+ r = varlink_get_peer_uid(link, &uid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &c,
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ c,
+ "(sa{sv})s",
+ "unix-process", 2,
+ "pidfd", "h", (uint32_t) pidref.fd,
+ "uid", "i", (int32_t) uid,
+ action);
+ if (r < 0)
+ return r;
+
+ r = bus_message_append_strv_key_value(c, details);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(c, "us", interactive, NULL);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(c);
+ return 0;
+}
+
+static bool varlink_allow_interactive_authentication(Varlink *link) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(link);
+
+ /* We look for the allowInteractiveAuthentication field in the message currently being dispatched,
+ * always under the same name. */
+
+ r = varlink_get_current_parameters(link, &v);
+ if (r < 0) {
+ log_debug_errno(r, "Unable to query current parameters: %m");
+ return false;
+ }
+
+ JsonVariant *b;
+ b = json_variant_by_key(v, "allowInteractiveAuthentication");
+ if (b) {
+ if (json_variant_is_boolean(b))
+ return json_variant_boolean(b);
+
+ log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring.");
+ }
+
+ return false;
+}
+#endif
+
+int varlink_verify_polkit_async_full(
+ Varlink *link,
+ sd_bus *bus,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ PolkitFlags flags,
+ Hashmap **registry) {
+
+ int r;
+
+ assert(link);
+ assert(registry);
+
+ log_debug("Trying to acquire polkit authentication for '%s'.", action);
+
+ /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink
+ * connection rather than the sender of a bus message. */
+
+ r = varlink_check_good_user(link, good_user);
+ if (r != 0)
+ return r;
+
+ if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) {
+ r = varlink_check_peer_privilege(link);
+ if (r != 0)
+ return r;
+ }
+
+#if ENABLE_POLKIT
+ _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL;
+
+ q = async_polkit_query_ref(hashmap_get(*registry, link));
+ /* This is a repeated invocation of this function, hence let's check if we've already got
+ * a response from polkit for this action */
+ if (q) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = async_polkit_query_check_action(q, action, details, flags, &error);
+ if (r != 0)
+ log_debug("Found matching previous polkit authentication for '%s'.", action);
+ if (r < 0) {
+ /* Reply with a nice error */
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED))
+ (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+ else if (ERRNO_IS_NEG_PRIVILEGE(r))
+ (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
+
+ return r;
+ }
+ if (r > 0)
+ return r;
+ }
+
+ _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL;
+ if (!bus) {
+ r = sd_bus_open_system(&mybus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_attach_event(mybus, varlink_get_event(link), 0);
+ if (r < 0)
+ return r;
+
+ bus = mybus;
+ }
+
+ bool interactive =
+ FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE) ||
+ varlink_allow_interactive_authentication(link);
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk);
+ if (r < 0)
+ return r;
+
+ if (!q) {
+ q = new(AsyncPolkitQuery, 1);
+ if (!q)
+ return -ENOMEM;
+
+ *q = (AsyncPolkitQuery) {
+ .n_ref = 1,
+ .link = varlink_ref(link),
+ .bus = sd_bus_ref(bus),
+ };
+ }
+
+ assert(!q->action);
+ q->action = new(AsyncPolkitQueryAction, 1);
+ if (!q->action)
+ return -ENOMEM;
+
+ *q->action = (AsyncPolkitQueryAction) {
+ .action = strdup(action),
+ .details = strv_copy((char**) details),
+ };
+ if (!q->action->action || !q->action->details)
+ return -ENOMEM;
+
+ if (!q->registry) {
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, link, q);
+ if (r < 0)
+ return r;
+
+ q->registry = *registry;
+ }
+
+ r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(q);
+
+ return 0;
+#else
+ return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES;
+#endif
}
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) {
+bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry) {
+ assert(link);
+ assert(action);
+ assert(registry);
+
+ /* Checks if we already have acquired some action previously */
+
#if ENABLE_POLKIT
- return hashmap_free_with_destructor(registry, async_polkit_query_unref);
+ AsyncPolkitQuery *q = hashmap_get(*registry, link);
+ if (!q)
+ return false;
+
+ return async_polkit_query_have_action(q, action, details);
#else
- assert(hashmap_isempty(registry));
- return hashmap_free(registry);
+ return false;
#endif
}
diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h
index e2a3b7e..f3741b2 100644
--- a/src/shared/bus-polkit.h
+++ b/src/shared/bus-polkit.h
@@ -4,8 +4,33 @@
#include "sd-bus.h"
#include "hashmap.h"
+#include "user-util.h"
+#include "varlink.h"
-int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
+typedef enum PolkitFLags {
+ POLKIT_ALLOW_INTERACTIVE = 1 << 0, /* Allow interactive auth (typically not required, because can be derived from bus message/link automatically) */
+ POLKIT_ALWAYS_QUERY = 1 << 1, /* Query polkit even if client is privileged */
+ POLKIT_DEFAULT_ALLOW = 1 << 2, /* If polkit is not around, assume "allow" rather than the usual "deny" */
+} PolkitFlags;
-int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error);
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry);
+int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
+
+int bus_verify_polkit_async_full(sd_bus_message *call, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, sd_bus_error *error);
+static inline int bus_verify_polkit_async(sd_bus_message *call, const char *action, const char **details, Hashmap **registry, sd_bus_error *error) {
+ return bus_verify_polkit_async_full(call, action, details, UID_INVALID, 0, registry, error);
+}
+
+int varlink_verify_polkit_async_full(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry);
+static inline int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, Hashmap **registry) {
+ return varlink_verify_polkit_async_full(link, bus, action, details, UID_INVALID, 0, registry);
+}
+
+/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for
+ * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors
+ * regarding unexpected fields) */
+#define VARLINK_DISPATCH_POLKIT_FIELD { \
+ .name = "allowInteractiveAuthentication", \
+ .type = JSON_VARIANT_BOOLEAN, \
+ }
+
+bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry);
diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c
index 6704e1e..99b1cc7 100644
--- a/src/shared/bus-print-properties.c
+++ b/src/shared/bus-print-properties.c
@@ -164,9 +164,11 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b
bus_print_property_value(name, expected_value, flags, "[not set]");
- else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
+ else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin",
+ "MemoryHigh", "MemoryMax",
+ "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") &&
u == CGROUP_LIMIT_MAX) ||
- (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) ||
+ (endswith(name, "TasksMax") && u == UINT64_MAX) ||
(startswith(name, "Limit") && u == UINT64_MAX) ||
(startswith(name, "DefaultLimit") && u == UINT64_MAX))
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 50de989..da83422 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -2,6 +2,7 @@
#include "af-list.h"
#include "alloc-util.h"
+#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-unit-util.h"
@@ -494,14 +495,14 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char *
if (r == 0)
break;
if (isempty(tuple))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field);
q = tuple;
- r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set, NULL);
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set);
if (r == -ENOMEM)
return log_oom();
if (r != 4 || !isempty(q))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field);
assert(source_str);
assert(nfproto_str);
@@ -510,11 +511,11 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char *
source = nft_set_source_from_string(source_str);
if (!IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field);
nfproto = nfproto_from_string(nfproto_str);
if (nfproto < 0 || !nft_identifier_valid(table) || !nft_identifier_valid(set))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field);
r = sd_bus_message_append(m, "(iiss)", source, nfproto, table, set);
if (r < 0)
@@ -562,6 +563,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
if (STR_IN_SET(field, "CPUAccounting",
"MemoryAccounting",
+ "MemoryZSwapWriteback",
"IOAccounting",
"BlockIOAccounting",
"TasksAccounting",
@@ -678,8 +680,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
else {
r = parse_permyriad_unbounded(eq);
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
- "CPU quota too small.");
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "CPU quota too small.");
if (r < 0)
return log_error_errno(r, "CPU quota '%s' invalid.", eq);
@@ -1213,7 +1214,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
_cleanup_free_ void *decoded = NULL;
size_t decoded_size;
- r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size);
+ r = unbase64mem(p, &decoded, &decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to base64 decode encrypted credential: %m");
@@ -1400,7 +1401,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
_cleanup_free_ void *decoded = NULL;
size_t sz;
- r = unbase64mem(eq, SIZE_MAX, &decoded, &sz);
+ r = unbase64mem(eq, &decoded, &sz);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 data '%s': %m", eq);
@@ -1787,11 +1788,11 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return bus_append_string(m, "RootHashPath", eq);
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
- r = unhexmem(eq, strlen(eq), &roothash_decoded, &roothash_decoded_size);
+ r = unhexmem(eq, &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHash= '%s': %m", eq);
if (roothash_decoded_size < sizeof(sd_id128_t))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short: %m", eq);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short.", eq);
return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size);
}
@@ -1806,10 +1807,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return bus_append_string(m, "RootHashSignaturePath", eq);
if (!(value = startswith(eq, "base64:")))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:'.", eq);
/* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */
- r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size);
+ r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq);
@@ -1894,7 +1895,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
break;
q = tuple;
- r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second, NULL);
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second);
if (r < 0)
return log_error_errno(r, "Failed to parse MountImages= property: %s", eq);
if (r == 0)
@@ -1926,7 +1927,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
for (;;) {
_cleanup_free_ char *partition = NULL, *mount_options = NULL;
- r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL);
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options);
if (r < 0)
return log_error_errno(r, "Failed to parse MountImages= property: %s", eq);
if (r == 0)
@@ -2027,7 +2028,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
for (;;) {
_cleanup_free_ char *partition = NULL, *mount_options = NULL;
- r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL);
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options);
if (r < 0)
return log_error_errno(r, "Failed to parse ExtensionImages= property: %s", eq);
if (r == 0)
@@ -2088,7 +2089,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
break;
const char *t = tuple;
- r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL);
+ r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination);
if (r <= 0)
return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m");
@@ -2449,6 +2450,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
"Transparent",
"Broadcast",
"PassCredentials",
+ "PassFileDescriptorsToExec",
"PassSecurity",
"PassPacketInfo",
"ReusePort",
@@ -2643,6 +2645,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const
if (unit_dependency_from_string(field) >= 0 ||
STR_IN_SET(field, "Documentation",
"RequiresMountsFor",
+ "WantsMountsFor",
"Markers"))
return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
@@ -2819,13 +2822,13 @@ int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char
return 0;
}
-int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref) {
+int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd) {
assert(m);
if (!pidref_is_set(pidref))
return -ESRCH;
- if (pidref->fd >= 0)
+ if (pidref->fd >= 0 && allow_pidfd)
return sd_bus_message_append(
m, "(sv)",
"PIDFDs", "ah", 1, pidref->fd);
@@ -2936,3 +2939,107 @@ int bus_service_manager_reload(sd_bus *bus) {
return 0;
}
+
+typedef struct UnitFreezer {
+ char *name;
+ sd_bus *bus;
+} UnitFreezer;
+
+/* Wait for 60 seconds at maximum for freezer operation */
+#define FREEZE_BUS_CALL_TIMEOUT (60 * USEC_PER_SEC)
+
+UnitFreezer* unit_freezer_free(UnitFreezer *f) {
+ if (!f)
+ return NULL;
+
+ free(f->name);
+ sd_bus_flush_close_unref(f->bus);
+
+ return mfree(f);
+}
+
+int unit_freezer_new(const char *name, UnitFreezer **ret) {
+ _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL;
+ int r;
+
+ assert(name);
+ assert(ret);
+
+ f = new(UnitFreezer, 1);
+ if (!f)
+ return log_oom();
+
+ *f = (UnitFreezer) {
+ .name = strdup(name),
+ };
+ if (!f->name)
+ return log_oom();
+
+ r = bus_connect_system_systemd(&f->bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open connection to systemd: %m");
+
+ (void) sd_bus_set_method_call_timeout(f->bus, FREEZE_BUS_CALL_TIMEOUT);
+
+ *ret = TAKE_PTR(f);
+ return 0;
+}
+
+static int unit_freezer_action(UnitFreezer *f, bool freeze) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(f);
+ assert(f->name);
+ assert(f->bus);
+
+ r = bus_call_method(f->bus, bus_systemd_mgr,
+ freeze ? "FreezeUnit" : "ThawUnit",
+ &error,
+ /* reply = */ NULL,
+ "s",
+ f->name);
+ if (r < 0) {
+ if (sd_bus_error_has_names(&error,
+ BUS_ERROR_NO_SUCH_UNIT,
+ BUS_ERROR_UNIT_INACTIVE,
+ SD_BUS_ERROR_NOT_SUPPORTED)) {
+
+ log_debug_errno(r, "Skipping freezer for '%s': %s", f->name, bus_error_message(&error, r));
+ return 0;
+ }
+
+ return log_error_errno(r, "Failed to %s unit '%s': %s",
+ freeze ? "freeze" : "thaw", f->name, bus_error_message(&error, r));
+ }
+
+ log_info("Successfully %s unit '%s'.", freeze ? "froze" : "thawed", f->name);
+ return 1;
+}
+
+int unit_freezer_freeze(UnitFreezer *f) {
+ return unit_freezer_action(f, true);
+}
+
+int unit_freezer_thaw(UnitFreezer *f) {
+ return unit_freezer_action(f, false);
+}
+
+int unit_freezer_new_freeze(const char *name, UnitFreezer **ret) {
+ _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL;
+ int r;
+
+ assert(name);
+ assert(ret);
+
+ r = unit_freezer_new(name, &f);
+ if (r < 0)
+ return r;
+
+ r = unit_freezer_freeze(f);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(f);
+ return 0;
+}
diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h
index d52c847..ea4056c 100644
--- a/src/shared/bus-unit-util.h
+++ b/src/shared/bus-unit-util.h
@@ -26,7 +26,7 @@ int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u);
int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment);
int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l);
-int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref);
+int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd);
int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
@@ -35,3 +35,15 @@ int unit_load_state(sd_bus *bus, const char *name, char **load_state);
int unit_info_compare(const UnitInfo *a, const UnitInfo *b);
int bus_service_manager_reload(sd_bus *bus);
+
+typedef struct UnitFreezer UnitFreezer;
+
+UnitFreezer* unit_freezer_free(UnitFreezer *f);
+DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFreezer*, unit_freezer_free);
+
+int unit_freezer_new(const char *name, UnitFreezer **ret);
+
+int unit_freezer_freeze(UnitFreezer *f);
+int unit_freezer_thaw(UnitFreezer *f);
+
+int unit_freezer_new_freeze(const char *name, UnitFreezer **ret);
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 4123152..30f9602 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -18,12 +18,17 @@
#include "bus-internal.h"
#include "bus-label.h"
#include "bus-util.h"
+#include "capsule-util.h"
+#include "chase.h"
+#include "daemon-util.h"
#include "data-fd-util.h"
#include "fd-util.h"
+#include "format-util.h"
#include "memstream-util.h"
#include "path-util.h"
#include "socket-util.h"
#include "stdio-util.h"
+#include "uid-classification.h"
static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
sd_event *e = ASSERT_PTR(userdata);
@@ -128,8 +133,8 @@ int bus_event_loop_with_idle(
if (r == 0 && !exiting && idle) {
/* Inform the service manager that we are going down, so that it will queue all
- * further start requests, instead of assuming we are already running. */
- sd_notify(false, "STOPPING=1");
+ * further start requests, instead of assuming we are still running. */
+ (void) sd_notify(false, NOTIFY_STOPPING);
r = bus_async_unregister_and_exit(e, bus, name);
if (r < 0)
@@ -267,6 +272,131 @@ int bus_connect_user_systemd(sd_bus **ret_bus) {
return 0;
}
+static int pin_capsule_socket(const char *capsule, const char *suffix, uid_t *ret_uid, gid_t *ret_gid) {
+ _cleanup_close_ int inode_fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ struct stat st;
+ int r;
+
+ assert(capsule);
+ assert(suffix);
+ assert(ret_uid);
+ assert(ret_gid);
+
+ p = path_join("/run/capsules", capsule, suffix);
+ if (!p)
+ return -ENOMEM;
+
+ /* We enter territory owned by the user, hence let's be paranoid about symlinks and ownership */
+ r = chase(p, /* root= */ NULL, CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS, /* ret_path= */ NULL, &inode_fd);
+ if (r < 0)
+ return r;
+
+ if (fstat(inode_fd, &st) < 0)
+ return negative_errno();
+
+ /* Paranoid safety check */
+ if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid))
+ return -EPERM;
+
+ *ret_uid = st.st_uid;
+ *ret_gid = st.st_gid;
+
+ return TAKE_FD(inode_fd);
+}
+
+static int bus_set_address_capsule(sd_bus *bus, const char *capsule, const char *suffix, int *ret_pin_fd) {
+ _cleanup_close_ int inode_fd = -EBADF;
+ _cleanup_free_ char *pp = NULL;
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ assert(bus);
+ assert(capsule);
+ assert(suffix);
+ assert(ret_pin_fd);
+
+ /* Connects to a capsule's user bus. We need to do so under the capsule's UID/GID, otherwise
+ * the service manager might refuse our connection. Hence fake it. */
+
+ r = capsule_name_is_valid(capsule);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ inode_fd = pin_capsule_socket(capsule, suffix, &uid, &gid);
+ if (inode_fd < 0)
+ return inode_fd;
+
+ pp = bus_address_escape(FORMAT_PROC_FD_PATH(inode_fd));
+ if (!pp)
+ return -ENOMEM;
+
+ if (asprintf(&bus->address, "unix:path=%s,uid=" UID_FMT ",gid=" GID_FMT, pp, uid, gid) < 0)
+ return -ENOMEM;
+
+ *ret_pin_fd = TAKE_FD(inode_fd); /* This fd must be kept pinned until the connection has been established */
+ return 0;
+}
+
+int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd) {
+ return bus_set_address_capsule(bus, capsule, "bus", ret_pin_fd);
+}
+
+int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus) {
+ _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int inode_fd = -EBADF;
+ int r;
+
+ assert(capsule);
+ assert(ret_bus);
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_capsule(bus, capsule, "systemd/private", &inode_fd);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return r;
+
+ *ret_bus = TAKE_PTR(bus);
+ return 0;
+}
+
+int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus) {
+ _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int inode_fd = -EBADF;
+ int r;
+
+ assert(capsule);
+ assert(ret_bus);
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_capsule_bus(bus, capsule, &inode_fd);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_set_bus_client(bus, true);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return r;
+
+ *ret_bus = TAKE_PTR(bus);
+ return 0;
+}
+
int bus_connect_transport(
BusTransport transport,
const char *host,
@@ -280,12 +410,10 @@ int bus_connect_transport(
assert(transport < _BUS_TRANSPORT_MAX);
assert(ret);
- assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL);
- assert_return(transport != BUS_TRANSPORT_REMOTE || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP);
-
switch (transport) {
case BUS_TRANSPORT_LOCAL:
+ assert_return(!host, -EINVAL);
switch (runtime_scope) {
@@ -307,11 +435,12 @@ int bus_connect_transport(
break;
case BUS_TRANSPORT_REMOTE:
+ assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP);
+
r = sd_bus_open_system_remote(&bus, host);
break;
case BUS_TRANSPORT_MACHINE:
-
switch (runtime_scope) {
case RUNTIME_SCOPE_USER:
@@ -328,6 +457,12 @@ int bus_connect_transport(
break;
+ case BUS_TRANSPORT_CAPSULE:
+ assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL);
+
+ r = bus_connect_capsule_bus(host, &bus);
+ break;
+
default:
assert_not_reached();
}
@@ -342,28 +477,32 @@ int bus_connect_transport(
return 0;
}
-int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus) {
+int bus_connect_transport_systemd(
+ BusTransport transport,
+ const char *host,
+ RuntimeScope runtime_scope,
+ sd_bus **ret_bus) {
+
assert(transport >= 0);
assert(transport < _BUS_TRANSPORT_MAX);
- assert(bus);
-
- assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL);
- assert_return(transport == BUS_TRANSPORT_LOCAL || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP);
+ assert(ret_bus);
switch (transport) {
case BUS_TRANSPORT_LOCAL:
+ assert_return(!host, -EINVAL);
+
switch (runtime_scope) {
case RUNTIME_SCOPE_USER:
- return bus_connect_user_systemd(bus);
+ return bus_connect_user_systemd(ret_bus);
case RUNTIME_SCOPE_SYSTEM:
if (sd_booted() <= 0)
/* Print a friendly message when the local system is actually not running systemd as PID 1. */
return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN),
"System has not been booted with systemd as init system (PID 1). Can't operate.");
- return bus_connect_system_systemd(bus);
+ return bus_connect_system_systemd(ret_bus);
default:
assert_not_reached();
@@ -372,10 +511,16 @@ int bus_connect_transport_systemd(BusTransport transport, const char *host, Runt
break;
case BUS_TRANSPORT_REMOTE:
- return sd_bus_open_system_remote(bus, host);
+ assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP);
+ return sd_bus_open_system_remote(ret_bus, host);
case BUS_TRANSPORT_MACHINE:
- return sd_bus_open_system_machine(bus, host);
+ assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP);
+ return sd_bus_open_system_machine(ret_bus, host);
+
+ case BUS_TRANSPORT_CAPSULE:
+ assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL);
+ return bus_connect_capsule_systemd(host, ret_bus);
default:
assert_not_reached();
@@ -626,7 +771,7 @@ static int method_dump_memory_state_by_fd(sd_bus_message *message, void *userdat
if (r < 0)
return r;
- fd = acquire_data_fd(dump, dump_size, 0);
+ fd = acquire_data_fd_full(dump, dump_size, /* flags = */ 0);
if (fd < 0)
return fd;
@@ -709,3 +854,75 @@ int bus_property_get_string_set(
return bus_message_append_string_set(reply, *s);
}
+
+int bus_creds_get_pidref(
+ sd_bus_creds *c,
+ PidRef *ret) {
+
+ int pidfd = -EBADF;
+ pid_t pid;
+ int r;
+
+ assert(c);
+ assert(ret);
+
+ r = sd_bus_creds_get_pid(c, &pid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_pidfd_dup(c, &pidfd);
+ if (r < 0 && r != -ENODATA)
+ return r;
+
+ *ret = (PidRef) {
+ .pid = pid,
+ .fd = pidfd,
+ };
+
+ return 0;
+}
+
+int bus_query_sender_pidref(
+ sd_bus_message *m,
+ PidRef *ret) {
+
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds);
+ if (r < 0)
+ return r;
+
+ return bus_creds_get_pidref(creds, ret);
+}
+
+int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret) {
+ const void *a;
+ size_t sz;
+ int r;
+
+ assert(m);
+
+ r = sd_bus_message_read_array(m, 'y', &a, &sz);
+ if (r < 0)
+ return r;
+
+ switch (sz) {
+ case 0:
+ if (ret)
+ *ret = SD_ID128_NULL;
+ return 0;
+
+ case sizeof(sd_id128_t):
+ if (ret)
+ memcpy(ret, a, sz);
+ return !memeqzero(a, sz); /* This mimics sd_id128_is_null(), but ret may be NULL,
+ * and a may be misaligned, so use memeqzero() here. */
+
+ default:
+ return -EINVAL;
+ }
+}
diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h
index 869c639..9c6f01d 100644
--- a/src/shared/bus-util.h
+++ b/src/shared/bus-util.h
@@ -11,6 +11,7 @@
#include "errno-util.h"
#include "macro.h"
+#include "pidref.h"
#include "runtime-scope.h"
#include "set.h"
#include "string-util.h"
@@ -20,6 +21,7 @@ typedef enum BusTransport {
BUS_TRANSPORT_LOCAL,
BUS_TRANSPORT_REMOTE,
BUS_TRANSPORT_MACHINE,
+ BUS_TRANSPORT_CAPSULE,
_BUS_TRANSPORT_MAX,
_BUS_TRANSPORT_INVALID = -EINVAL,
} BusTransport;
@@ -35,8 +37,12 @@ bool bus_error_is_unknown_service(const sd_bus_error *error);
int bus_check_peercred(sd_bus *c);
+int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd);
+
int bus_connect_system_systemd(sd_bus **ret_bus);
int bus_connect_user_systemd(sd_bus **ret_bus);
+int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus);
+int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus);
int bus_connect_transport(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus);
int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus);
@@ -73,3 +79,8 @@ extern const struct hash_ops bus_message_hash_ops;
int bus_message_append_string_set(sd_bus_message *m, Set *s);
int bus_property_get_string_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
+
+int bus_creds_get_pidref(sd_bus_creds *c, PidRef *ret);
+int bus_query_sender_pidref(sd_bus_message *m, PidRef *ret);
+
+int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret);
diff --git a/src/shared/bus-wait-for-jobs.c b/src/shared/bus-wait-for-jobs.c
index 969c629..e12189f 100644
--- a/src/shared/bus-wait-for-jobs.c
+++ b/src/shared/bus-wait-for-jobs.c
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
-#include "bus-wait-for-jobs.h"
-#include "set.h"
-#include "bus-util.h"
#include "bus-internal.h"
-#include "unit-def.h"
+#include "bus-util.h"
+#include "bus-wait-for-jobs.h"
#include "escape.h"
+#include "set.h"
#include "strv.h"
+#include "unit-def.h"
typedef struct BusWaitForJobs {
sd_bus *bus;
@@ -23,60 +23,56 @@ typedef struct BusWaitForJobs {
sd_bus_slot *slot_disconnected;
} BusWaitForJobs;
+BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) {
+ if (!d)
+ return NULL;
+
+ set_free(d->jobs);
+
+ sd_bus_slot_unref(d->slot_disconnected);
+ sd_bus_slot_unref(d->slot_job_removed);
+
+ sd_bus_unref(d->bus);
+
+ free(d->name);
+ free(d->result);
+
+ return mfree(d);
+}
+
static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
assert(m);
- log_error("Warning! D-Bus connection terminated.");
+ log_warning("D-Bus connection terminated while waiting for jobs.");
sd_bus_close(sd_bus_message_get_bus(m));
return 0;
}
static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
- const char *path, *unit, *result;
BusWaitForJobs *d = ASSERT_PTR(userdata);
- uint32_t id;
- char *found;
+ _cleanup_free_ char *job_found = NULL;
+ const char *path, *unit, *result;
int r;
assert(m);
- r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
+ r = sd_bus_message_read(m, "uoss", /* id = */ NULL, &path, &unit, &result);
if (r < 0) {
bus_log_parse_error(r);
return 0;
}
- found = set_remove(d->jobs, (char*) path);
- if (!found)
+ job_found = set_remove(d->jobs, (char*) path);
+ if (!job_found)
return 0;
- free(found);
-
- (void) free_and_strdup(&d->result, empty_to_null(result));
-
(void) free_and_strdup(&d->name, empty_to_null(unit));
+ (void) free_and_strdup(&d->result, empty_to_null(result));
return 0;
}
-BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) {
- if (!d)
- return NULL;
-
- set_free(d->jobs);
-
- sd_bus_slot_unref(d->slot_disconnected);
- sd_bus_slot_unref(d->slot_job_removed);
-
- sd_bus_unref(d->bus);
-
- free(d->name);
- free(d->result);
-
- return mfree(d);
-}
-
int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
int r;
@@ -92,9 +88,8 @@ int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
.bus = sd_bus_ref(bus),
};
- /* When we are a bus client we match by sender. Direct
- * connections OTOH have no initialized sender field, and
- * hence we ignore the sender then */
+ /* When we are a bus client we match by sender. Direct connections OTOH have no initialized sender
+ * field, and hence we ignore the sender then */
r = sd_bus_match_signal_async(
bus,
&d->slot_job_removed,
@@ -138,12 +133,12 @@ static int bus_process_wait(sd_bus *bus) {
}
}
-static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
+static int bus_job_get_service_result(BusWaitForJobs *d, char **ret) {
_cleanup_free_ char *dbus_path = NULL;
assert(d);
assert(d->name);
- assert(result);
+ assert(ret);
if (!endswith(d->name, ".service"))
return -EINVAL;
@@ -158,67 +153,57 @@ static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
"org.freedesktop.systemd1.Service",
"Result",
NULL,
- result);
+ ret);
}
static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
- _cleanup_free_ char *service_shell_quoted = NULL;
- const char *systemctl = "systemctl", *journalctl = "journalctl";
static const struct {
const char *result, *explanation;
} explanations[] = {
- { "resources", "of unavailable resources or another system error" },
+ { "resources", "of unavailable resources or another system error" },
{ "protocol", "the service did not take the steps required by its unit configuration" },
- { "timeout", "a timeout was exceeded" },
- { "exit-code", "the control process exited with error code" },
- { "signal", "a fatal signal was delivered to the control process" },
+ { "timeout", "a timeout was exceeded" },
+ { "exit-code", "the control process exited with error code" },
+ { "signal", "a fatal signal was delivered to the control process" },
{ "core-dump", "a fatal signal was delivered causing the control process to dump core" },
- { "watchdog", "the service failed to send watchdog ping" },
- { "start-limit", "start of the service was attempted too often" }
+ { "watchdog", "the service failed to send watchdog ping" },
+ { "start-limit", "start of the service was attempted too often" },
};
+ _cleanup_free_ char *service_shell_quoted = NULL;
+ const char *systemctl = "systemctl", *journalctl = "journalctl";
+
assert(service);
service_shell_quoted = shell_maybe_quote(service, 0);
- if (!strv_isempty((char**) extra_args)) {
+ if (!strv_isempty((char* const*) extra_args)) {
_cleanup_free_ char *t = NULL;
- t = strv_join((char**) extra_args, " ");
+ t = strv_join((char* const*) extra_args, " ");
systemctl = strjoina("systemctl ", t ?: "<args>");
journalctl = strjoina("journalctl ", t ?: "<args>");
}
- if (!isempty(result)) {
- size_t i;
-
- for (i = 0; i < ELEMENTSOF(explanations); ++i)
- if (streq(result, explanations[i].result))
- break;
-
- if (i < ELEMENTSOF(explanations)) {
- log_error("Job for %s failed because %s.\n"
- "See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
- service,
- explanations[i].explanation,
- systemctl,
- service_shell_quoted ?: "<service>",
- journalctl,
- service_shell_quoted ?: "<service>");
- goto finish;
- }
- }
+ if (!isempty(result))
+ FOREACH_ELEMENT(i, explanations)
+ if (streq(result, i->result)) {
+ log_error("Job for %s failed because %s.\n"
+ "See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
+ service, i->explanation,
+ systemctl, service_shell_quoted ?: "<service>",
+ journalctl, service_shell_quoted ?: "<service>");
+ goto extra;
+ }
log_error("Job for %s failed.\n"
"See \"%s status %s\" and \"%s -xeu %s\" for details.\n",
service,
- systemctl,
- service_shell_quoted ?: "<service>",
- journalctl,
- service_shell_quoted ?: "<service>");
+ systemctl, service_shell_quoted ?: "<service>",
+ journalctl, service_shell_quoted ?: "<service>");
-finish:
+extra:
/* For some results maybe additional explanation is required */
if (streq_ptr(result, "start-limit"))
log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
@@ -227,42 +212,56 @@ finish:
service_shell_quoted ?: "<service>");
}
-static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
+static int check_wait_response(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) {
+ int r;
+
assert(d);
assert(d->name);
assert(d->result);
- if (!quiet) {
+ if (streq(d->result, "done")) {
+ if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS))
+ log_info("Job for %s finished.", d->name);
+
+ return 0;
+ } else if (streq(d->result, "skipped")) {
+ if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS))
+ log_info("Job for %s was skipped.", d->name);
+
+ return 0;
+ }
+
+ if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_ERROR)) {
if (streq(d->result, "canceled"))
- log_error("Job for %s canceled.", strna(d->name));
+ log_error("Job for %s canceled.", d->name);
else if (streq(d->result, "timeout"))
- log_error("Job for %s timed out.", strna(d->name));
+ log_error("Job for %s timed out.", d->name);
else if (streq(d->result, "dependency"))
- log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
+ log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", d->name);
else if (streq(d->result, "invalid"))
- log_error("%s is not active, cannot reload.", strna(d->name));
+ log_error("%s is not active, cannot reload.", d->name);
else if (streq(d->result, "assert"))
- log_error("Assertion failed on job for %s.", strna(d->name));
+ log_error("Assertion failed on job for %s.", d->name);
else if (streq(d->result, "unsupported"))
- log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
+ log_error("Operation on or unit type of %s not supported on this system.", d->name);
else if (streq(d->result, "collected"))
- log_error("Queued job for %s was garbage collected.", strna(d->name));
+ log_error("Queued job for %s was garbage collected.", d->name);
else if (streq(d->result, "once"))
- log_error("Unit %s was started already once and can't be started again.", strna(d->name));
- else if (!STR_IN_SET(d->result, "done", "skipped")) {
-
- if (d->name && endswith(d->name, ".service")) {
- _cleanup_free_ char *result = NULL;
- int q;
-
- q = bus_job_get_service_result(d, &result);
- if (q < 0)
- log_debug_errno(q, "Failed to get Result property of unit %s: %m", d->name);
-
- log_job_error_with_service_result(d->name, result, extra_args);
- } else
- log_error("Job failed. See \"journalctl -xe\" for details.");
- }
+ log_error("Unit %s was started already once and can't be started again.", d->name);
+ else if (streq(d->result, "frozen"))
+ log_error("Cannot perform operation on frozen unit %s.", d->name);
+ else if (endswith(d->name, ".service")) {
+ /* Job result is unknown. For services, let's also try Result property. */
+ _cleanup_free_ char *result = NULL;
+
+ r = bus_job_get_service_result(d, &result);
+ if (r < 0)
+ log_debug_errno(r, "Failed to get Result property of unit %s, ignoring: %m",
+ d->name);
+
+ log_job_error_with_service_result(d->name, result, extra_args);
+ } else /* Otherwise we just show a generic message. */
+ log_error("Job failed. See \"journalctl -xe\" for details.");
}
if (STR_IN_SET(d->result, "canceled", "collected"))
@@ -279,14 +278,15 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const*
return -EOPNOTSUPP;
else if (streq(d->result, "once"))
return -ESTALE;
- else if (STR_IN_SET(d->result, "done", "skipped"))
- return 0;
+ else if (streq(d->result, "frozen"))
+ return -EDEADLK;
- return log_debug_errno(SYNTHETIC_ERRNO(EIO),
- "Unexpected job result, assuming server side newer than us: %s", d->result);
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+ "Unexpected job result '%s' for unit '%s', assuming server side newer than us.",
+ d->result, d->name);
}
-int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
+int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) {
int r = 0;
assert(d);
@@ -299,14 +299,12 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_ar
return log_error_errno(q, "Failed to wait for response: %m");
if (d->name && d->result) {
- q = check_wait_response(d, quiet, extra_args);
- /* Return the first error as it is most likely to be
- * meaningful. */
- if (q < 0 && r == 0)
- r = q;
+ q = check_wait_response(d, flags, extra_args);
+ /* Return the first error as it is most likely to be meaningful. */
+ RET_GATHER(r, q);
log_full_errno_zerook(LOG_DEBUG, q,
- "Got result %s/%m for job %s", d->result, d->name);
+ "Got result %s/%m for job %s.", d->result, d->name);
}
d->name = mfree(d->name);
@@ -322,12 +320,12 @@ int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
return set_put_strdup(&d->jobs, path);
}
-int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args) {
+int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args) {
int r;
r = bus_wait_for_jobs_add(d, path);
if (r < 0)
return log_oom();
- return bus_wait_for_jobs(d, quiet, extra_args);
+ return bus_wait_for_jobs(d, flags, extra_args);
}
diff --git a/src/shared/bus-wait-for-jobs.h b/src/shared/bus-wait-for-jobs.h
index 5acf8b9..2336b13 100644
--- a/src/shared/bus-wait-for-jobs.h
+++ b/src/shared/bus-wait-for-jobs.h
@@ -7,10 +7,15 @@
typedef struct BusWaitForJobs BusWaitForJobs;
-int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret);
-BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d);
-int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path);
-int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args);
-int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args);
+typedef enum WaitJobsFlags {
+ BUS_WAIT_JOBS_LOG_ERROR = 1 << 0,
+ BUS_WAIT_JOBS_LOG_SUCCESS = 1 << 1,
+} WaitJobsFlags;
+BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d);
DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free);
+
+int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret);
+int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path);
+int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args);
+int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args);
diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c
index 0dd2a29..6ccf822 100644
--- a/src/shared/bus-wait-for-units.c
+++ b/src/shared/bus-wait-for-units.c
@@ -18,7 +18,7 @@ typedef struct WaitForItem {
sd_bus_slot *slot_get_all;
sd_bus_slot *slot_properties_changed;
- bus_wait_for_units_unit_callback unit_callback;
+ bus_wait_for_units_unit_callback_t unit_callback;
void *userdata;
char *active_state;
@@ -32,11 +32,6 @@ typedef struct BusWaitForUnits {
Hashmap *items;
- bus_wait_for_units_ready_callback ready_callback;
- void *userdata;
-
- WaitForItem *current;
-
BusWaitForUnitsState state;
bool has_failed:1;
} BusWaitForUnits;
@@ -63,10 +58,7 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) {
log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
}
- assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
-
- if (item->parent->current == item)
- item->parent->current = NULL;
+ assert_se(hashmap_remove_value(item->parent->items, item->bus_path, item));
}
sd_bus_slot_unref(item->slot_properties_changed);
@@ -82,8 +74,6 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) {
DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
static void call_unit_callback_and_wait(BusWaitForUnits *d, WaitForItem *item, bool good) {
- d->current = item;
-
if (item->unit_callback)
item->unit_callback(d, item->bus_path, good, item->userdata);
@@ -109,14 +99,10 @@ static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *e
assert(m);
- log_error("Warning! D-Bus connection terminated.");
+ log_warning("D-Bus connection terminated while waiting for unit.");
bus_wait_for_units_clear(d);
-
- if (d->ready_callback)
- d->ready_callback(d, false, d->userdata);
- else /* If no ready callback is specified close the connection so that the event loop exits */
- sd_bus_close(sd_bus_message_get_bus(m));
+ sd_bus_close(sd_bus_message_get_bus(m));
return 0;
}
@@ -172,13 +158,6 @@ static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
return hashmap_isempty(d->items);
}
-void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
- assert(d);
-
- d->ready_callback = callback;
- d->userdata = userdata;
-}
-
static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
assert(d);
@@ -186,9 +165,6 @@ static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
return;
d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
-
- if (d->ready_callback)
- d->ready_callback(d, d->state, d->userdata);
}
static void wait_for_item_check_ready(WaitForItem *item) {
@@ -221,32 +197,26 @@ static void wait_for_item_check_ready(WaitForItem *item) {
bus_wait_for_units_check_ready(d);
}
-static int property_map_job(
+static int property_map_job_id(
sd_bus *bus,
const char *member,
sd_bus_message *m,
sd_bus_error *error,
void *userdata) {
- WaitForItem *item = ASSERT_PTR(userdata);
- const char *path;
- uint32_t id;
- int r;
+ uint32_t *job_id = ASSERT_PTR(userdata);
- r = sd_bus_message_read(m, "(uo)", &id, &path);
- if (r < 0)
- return r;
+ assert(m);
- item->job_id = id;
- return 0;
+ return sd_bus_message_read(m, "(uo)", job_id, /* path = */ NULL);
}
static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
static const struct bus_properties_map map[] = {
- { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
- { "Job", "(uo)", property_map_job, 0 },
- { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
+ { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
+ { "Job", "(uo)", property_map_job_id, offsetof(WaitForItem, job_id) },
+ { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
{}
};
@@ -315,20 +285,23 @@ int bus_wait_for_units_add_unit(
BusWaitForUnits *d,
const char *unit,
BusWaitForUnitsFlags flags,
- bus_wait_for_units_unit_callback callback,
+ bus_wait_for_units_unit_callback_t callback,
void *userdata) {
_cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
+ _cleanup_free_ char *bus_path = NULL;
int r;
assert(d);
assert(unit);
+ assert((flags & _BUS_WAIT_FOR_TARGET) != 0);
- assert(flags != 0);
+ bus_path = unit_dbus_path_from_name(unit);
+ if (!bus_path)
+ return -ENOMEM;
- r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
- if (r < 0)
- return r;
+ if (hashmap_contains(d->items, bus_path))
+ return 0;
item = new(WaitForItem, 1);
if (!item)
@@ -336,15 +309,12 @@ int bus_wait_for_units_add_unit(
*item = (WaitForItem) {
.flags = flags,
- .bus_path = unit_dbus_path_from_name(unit),
+ .bus_path = TAKE_PTR(bus_path),
.unit_callback = callback,
.userdata = userdata,
.job_id = UINT32_MAX,
};
- if (!item->bus_path)
- return -ENOMEM;
-
if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
r = sd_bus_call_method_async(
d->bus,
@@ -388,14 +358,16 @@ int bus_wait_for_units_add_unit(
if (r < 0)
return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
- r = hashmap_put(d->items, item->bus_path, item);
+ r = hashmap_ensure_put(&d->items, &string_hash_ops, item->bus_path, item);
if (r < 0)
return r;
+ assert(r > 0);
d->state = BUS_WAIT_RUNNING;
item->parent = d;
TAKE_PTR(item);
- return 0;
+
+ return 1;
}
int bus_wait_for_units_run(BusWaitForUnits *d) {
diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h
index 2623e72..a4a4dc4 100644
--- a/src/shared/bus-wait-for-units.h
+++ b/src/shared/bus-wait-for-units.h
@@ -8,7 +8,7 @@ typedef struct BusWaitForUnits BusWaitForUnits;
typedef enum BusWaitForUnitsState {
BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */
- BUS_WAIT_FAILURE, /* dito, but something failed */
+ BUS_WAIT_FAILURE, /* ditto, but something failed */
BUS_WAIT_RUNNING, /* Still something to wait for */
_BUS_WAIT_FOR_UNITS_STATE_MAX,
_BUS_WAIT_FOR_UNITS_STATE_INVALID = -EINVAL,
@@ -19,17 +19,22 @@ typedef enum BusWaitForUnitsFlags {
BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */
BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */
BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */
+ _BUS_WAIT_FOR_TARGET = BUS_WAIT_FOR_MAINTENANCE_END|BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB,
} BusWaitForUnitsFlags;
-typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata);
-typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
+typedef void (*bus_wait_for_units_unit_callback_t)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret);
+
BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d);
+DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);
-BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
-void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata);
-int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata);
-int bus_wait_for_units_run(BusWaitForUnits *d);
+int bus_wait_for_units_add_unit(
+ BusWaitForUnits *d,
+ const char *unit,
+ BusWaitForUnitsFlags flags,
+ bus_wait_for_units_unit_callback_t callback,
+ void *userdata);
-DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);
+int bus_wait_for_units_run(BusWaitForUnits *d);
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
diff --git a/src/shared/capsule-util.c b/src/shared/capsule-util.c
new file mode 100644
index 0000000..3689a78
--- /dev/null
+++ b/src/shared/capsule-util.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "capsule-util.h"
+#include "path-util.h"
+#include "user-util.h"
+
+int capsule_name_is_valid(const char *name) {
+
+ if (!filename_is_valid(name))
+ return false;
+
+ _cleanup_free_ char *prefixed = strjoin("c-", name);
+ if (!prefixed)
+ return -ENOMEM;
+
+ return valid_user_group_name(prefixed, /* flags= */ 0);
+}
diff --git a/src/shared/capsule-util.h b/src/shared/capsule-util.h
new file mode 100644
index 0000000..437153b
--- /dev/null
+++ b/src/shared/capsule-util.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int capsule_name_is_valid(const char *name);
diff --git a/src/shared/cgroup-setup.c b/src/shared/cgroup-setup.c
index 934a16e..093b6d0 100644
--- a/src/shared/cgroup-setup.c
+++ b/src/shared/cgroup-setup.c
@@ -51,7 +51,7 @@ static int cg_any_controller_used_for_v1(void) {
continue;
const char *p = *line;
- r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled, NULL);
+ r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled);
if (r < 0)
return log_debug_errno(r, "Error parsing /proc/cgroups line, ignoring: %m");
else if (r < 4) {
@@ -81,9 +81,6 @@ static int cg_any_controller_used_for_v1(void) {
bool cg_is_unified_wanted(void) {
static thread_local int wanted = -1;
- bool b;
- const bool is_default = DEFAULT_HIERARCHY == CGROUP_UNIFIED_ALL;
- _cleanup_free_ char *c = NULL;
int r;
/* If we have a cached value, return that. */
@@ -96,21 +93,20 @@ bool cg_is_unified_wanted(void) {
return (wanted = r >= CGROUP_UNIFIED_ALL);
/* If we were explicitly passed systemd.unified_cgroup_hierarchy, respect that. */
+ bool b;
r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", /* flags = */ 0, &b);
if (r > 0)
return (wanted = b);
/* If we passed cgroup_no_v1=all with no other instructions, it seems highly unlikely that we want to
* use hybrid or legacy hierarchy. */
+ _cleanup_free_ char *c = NULL;
r = proc_cmdline_get_key("cgroup_no_v1", 0, &c);
if (r > 0 && streq_ptr(c, "all"))
return (wanted = true);
/* If any controller is in use as v1, don't use unified. */
- if (cg_any_controller_used_for_v1() > 0)
- return (wanted = false);
-
- return (wanted = is_default);
+ return (wanted = (cg_any_controller_used_for_v1() <= 0));
}
bool cg_is_legacy_wanted(void) {
@@ -132,10 +128,6 @@ bool cg_is_legacy_wanted(void) {
bool cg_is_hybrid_wanted(void) {
static thread_local int wanted = -1;
int r;
- bool b;
- const bool is_default = DEFAULT_HIERARCHY >= CGROUP_UNIFIED_SYSTEMD;
- /* We default to true if the default is "hybrid", obviously, but also when the default is "unified",
- * because if we get called, it means that unified hierarchy was not mounted. */
/* If we have a cached value, return that. */
if (wanted >= 0)
@@ -146,12 +138,33 @@ bool cg_is_hybrid_wanted(void) {
return (wanted = false);
/* Otherwise, let's see what the kernel command line has to say. Since checking is expensive, cache
- * a non-error result. */
+ * a non-error result.
+ * The meaning of the kernel option is reversed wrt. to the return value of this function, hence the
+ * negation. */
+ bool b;
r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", /* flags = */ 0, &b);
+ if (r > 0)
+ return (wanted = !b);
- /* The meaning of the kernel option is reversed wrt. to the return value of this function, hence the
- * negation. */
- return (wanted = r > 0 ? !b : is_default);
+ /* The default hierarchy is "unified". But if this is reached, it means that unified hierarchy was
+ * not mounted, so return true too. */
+ return (wanted = true);
+}
+
+bool cg_is_legacy_force_enabled(void) {
+ bool force;
+
+ if (!cg_is_legacy_wanted())
+ return false;
+
+ /* If in container, we have to follow host's cgroup hierarchy. */
+ if (detect_container() > 0)
+ return true;
+
+ if (proc_cmdline_get_bool("SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE", /* flags = */ 0, &force) < 0)
+ return false;
+
+ return force;
}
int cg_weight_parse(const char *s, uint64_t *ret) {
@@ -371,6 +384,20 @@ int cg_attach(const char *controller, const char *path, pid_t pid) {
return 0;
}
+int cg_fd_attach(int fd, pid_t pid) {
+ char c[DECIMAL_STR_MAX(pid_t) + 2];
+
+ assert(fd >= 0);
+ assert(pid >= 0);
+
+ if (pid == 0)
+ pid = getpid_cached();
+
+ xsprintf(c, PID_FMT "\n", pid);
+
+ return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER);
+}
+
int cg_attach_fallback(const char *controller, const char *path, pid_t pid) {
int r;
@@ -571,75 +598,56 @@ int cg_migrate(
bool done = false;
_cleanup_set_free_ Set *s = NULL;
int r, ret = 0;
- pid_t my_pid;
assert(cfrom);
assert(pfrom);
assert(cto);
assert(pto);
- s = set_new(NULL);
- if (!s)
- return -ENOMEM;
-
- my_pid = getpid_cached();
-
do {
_cleanup_fclose_ FILE *f = NULL;
- pid_t pid = 0;
+ pid_t pid;
+
done = true;
r = cg_enumerate_processes(cfrom, pfrom, &f);
- if (r < 0) {
- if (ret >= 0 && r != -ENOENT)
- return r;
-
- return ret;
- }
+ if (r < 0)
+ return RET_GATHER(ret, r);
- while ((r = cg_read_pid(f, &pid)) > 0) {
+ while ((r = cg_read_pid(f, &pid, flags)) > 0) {
+ /* Throw an error if unmappable PIDs are in output, we can't migrate those. */
+ if (pid == 0)
+ return -EREMOTE;
- /* This might do weird stuff if we aren't a
- * single-threaded program. However, we
- * luckily know we are not */
- if ((flags & CGROUP_IGNORE_SELF) && pid == my_pid)
+ /* This might do weird stuff if we aren't a single-threaded program. However, we
+ * luckily know we are. */
+ if (FLAGS_SET(flags, CGROUP_IGNORE_SELF) && pid == getpid_cached())
continue;
- if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid))
+ if (set_contains(s, PID_TO_PTR(pid)))
continue;
- /* Ignore kernel threads. Since they can only
- * exist in the root cgroup, we only check for
- * them there. */
- if (cfrom &&
- empty_or_root(pfrom) &&
+ /* Ignore kernel threads. Since they can only exist in the root cgroup, we only
+ * check for them there. */
+ if (cfrom && empty_or_root(pfrom) &&
pid_is_kernel_thread(pid) > 0)
continue;
r = cg_attach(cto, pto, pid);
if (r < 0) {
- if (ret >= 0 && r != -ESRCH)
- ret = r;
+ if (r != -ESRCH)
+ RET_GATHER(ret, r);
} else if (ret == 0)
ret = 1;
done = false;
- r = set_put(s, PID_TO_PTR(pid));
- if (r < 0) {
- if (ret >= 0)
- return r;
-
- return ret;
- }
- }
-
- if (r < 0) {
- if (ret >= 0)
- return r;
-
- return ret;
+ r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid));
+ if (r < 0)
+ return RET_GATHER(ret, r);
}
+ if (r < 0)
+ return RET_GATHER(ret, r);
} while (!done);
return ret;
diff --git a/src/shared/cgroup-setup.h b/src/shared/cgroup-setup.h
index 1b6f071..283ab67 100644
--- a/src/shared/cgroup-setup.h
+++ b/src/shared/cgroup-setup.h
@@ -10,6 +10,7 @@
bool cg_is_unified_wanted(void);
bool cg_is_legacy_wanted(void);
bool cg_is_hybrid_wanted(void);
+bool cg_is_legacy_force_enabled(void);
int cg_weight_parse(const char *s, uint64_t *ret);
int cg_cpu_weight_parse(const char *s, uint64_t *ret);
@@ -20,6 +21,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root);
int cg_create(const char *controller, const char *path);
int cg_attach(const char *controller, const char *path, pid_t pid);
+int cg_fd_attach(int fd, pid_t pid);
int cg_attach_fallback(const char *controller, const char *path, pid_t pid);
int cg_create_and_attach(const char *controller, const char *path, pid_t pid);
diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c
index c2ee1c5..8717731 100644
--- a/src/shared/cgroup-show.c
+++ b/src/shared/cgroup-show.c
@@ -108,7 +108,7 @@ static int show_cgroup_one_by_path(
* From https://docs.kernel.org/admin-guide/cgroup-v2.html#threads,
* “cgroup.procs” in a threaded domain cgroup contains the PIDs of all processes in
* the subtree and is not readable in the subtree proper. */
- r = cg_read_pid(f, &pid);
+ r = cg_read_pid(f, &pid, /* flags = */ 0);
if (IN_SET(r, 0, -EOPNOTSUPP))
break;
if (r < 0)
@@ -150,18 +150,9 @@ static int show_cgroup_name(
delegate = r > 0;
if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) {
- cg_file_handle fh = CG_FILE_HANDLE_INIT;
- int mnt_id = -1;
-
- if (name_to_handle_at(
- fd,
- "",
- &fh.file_handle,
- &mnt_id,
- AT_EMPTY_PATH) < 0)
- log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path);
- else
- cgroupid = CG_FILE_HANDLE_CGROUPID(fh);
+ r = cg_fd_get_cgroupid(fd, &cgroupid);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine cgroup ID of %s, ignoring: %m", path);
}
r = path_extract_filename(path, &b);
diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c
index bbb343f..1e90cc2 100644
--- a/src/shared/clean-ipc.c
+++ b/src/shared/clean-ipc.c
@@ -58,7 +58,7 @@ static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) {
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
- return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m");
+ return log_warning_errno(r, "Failed to read /proc/sysvipc/shm: %m");
if (r == 0)
break;
diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c
index b0cbe30..37d0232 100644
--- a/src/shared/clock-util.c
+++ b/src/shared/clock-util.c
@@ -27,10 +27,11 @@ int clock_get_hwclock(struct tm *tm) {
if (fd < 0)
return -errno;
- /* This leaves the timezone fields of struct tm
- * uninitialized! */
+ /* This leaves the timezone fields of struct tm uninitialized! */
if (ioctl(fd, RTC_RD_TIME, tm) < 0)
- return -errno;
+ /* Some drivers return -EINVAL in case the time could not be kept, i.e. power loss
+ * happened. Let's turn that into a clearly recognizable error */
+ return errno == EINVAL ? -ENODATA : -errno;
/* We don't know daylight saving, so we reset this in order not
* to confuse mktime(). */
diff --git a/src/shared/color-util.c b/src/shared/color-util.c
new file mode 100644
index 0000000..9d714c0
--- /dev/null
+++ b/src/shared/color-util.c
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <math.h>
+
+#include "color-util.h"
+#include "macro.h"
+
+void rgb_to_hsv(double r, double g, double b,
+ double *ret_h, double *ret_s, double *ret_v) {
+
+ assert(r >= 0 && r <= 1);
+ assert(g >= 0 && g <= 1);
+ assert(b >= 0 && b <= 1);
+
+ double max_color = fmax(r, fmax(g, b));
+ double min_color = fmin(r, fmin(g, b));
+ double delta = max_color - min_color;
+
+ if (ret_v)
+ *ret_v = max_color * 100.0;
+
+ if (max_color <= 0) {
+ if (ret_s)
+ *ret_s = 0;
+ if (ret_h)
+ *ret_h = NAN;
+ return;
+ }
+
+ if (ret_s)
+ *ret_s = delta / max_color * 100.0;
+
+ if (ret_h) {
+ if (delta > 0) {
+ if (r >= max_color)
+ *ret_h = 60 * fmod((g - b) / delta, 6);
+ else if (g >= max_color)
+ *ret_h = 60 * (((b - r) / delta) + 2);
+ else if (b >= max_color)
+ *ret_h = 60 * (((r - g) / delta) + 4);
+
+ *ret_h = fmod(*ret_h, 360);
+ } else
+ *ret_h = NAN;
+ }
+}
+
+void hsv_to_rgb(double h, double s, double v,
+ uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) {
+
+ double c, x, m, r, g, b;
+
+ assert(s >= 0 && s <= 100);
+ assert(v >= 0 && v <= 100);
+ assert(ret_r);
+ assert(ret_g);
+ assert(ret_b);
+
+ h = fmod(h, 360);
+ c = (s / 100.0) * (v / 100.0);
+ x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
+ m = (v / 100) - c;
+
+ if (h >= 0 && h < 60)
+ r = c, g = x, b = 0.0;
+ else if (h >= 60 && h < 120)
+ r = x, g = c, b = 0.0;
+ else if (h >= 120 && h < 180)
+ r = 0.0, g = c, b = x;
+ else if (h >= 180 && h < 240)
+ r = 0.0, g = x, b = c;
+ else if (h >= 240 && h < 300)
+ r = x, g = 0.0, b = c;
+ else
+ r = c, g = 0.0, b = x;
+
+ *ret_r = (uint8_t) ((r + m) * 255);
+ *ret_g = (uint8_t) ((g + m) * 255);
+ *ret_b = (uint8_t) ((b + m) * 255);
+}
diff --git a/src/shared/color-util.h b/src/shared/color-util.h
new file mode 100644
index 0000000..a4ae9eb
--- /dev/null
+++ b/src/shared/color-util.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+
+void rgb_to_hsv(double r, double g, double b,
+ double *ret_h, double *ret_s, double *ret_v);
+
+void hsv_to_rgb(
+ double h, double s, double v,
+ uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b);
diff --git a/src/shared/compare-operator.c b/src/shared/compare-operator.c
index 0da28fc..a13db8e 100644
--- a/src/shared/compare-operator.c
+++ b/src/shared/compare-operator.c
@@ -6,6 +6,7 @@
#include "string-util.h"
CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags flags) {
+
static const struct {
CompareOperator op;
const char *str;
@@ -40,19 +41,19 @@ CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags
* parse_compare_operator() are use on the same string? */
return _COMPARE_OPERATOR_INVALID;
- for (size_t i = 0; i < ELEMENTSOF(table); i ++) {
+ FOREACH_ELEMENT(i, table) {
const char *e;
- if (table[i].need_mask != 0 && !FLAGS_SET(flags, table[i].need_mask))
+ if (i->need_mask != 0 && !FLAGS_SET(flags, i->need_mask))
continue;
- e = startswith(*s, table[i].str);
+ e = startswith(*s, i->str);
if (e) {
- if (table[i].valid_mask != 0 && !FLAGS_SET(flags, table[i].valid_mask))
+ if (i->valid_mask != 0 && !FLAGS_SET(flags, i->valid_mask))
return _COMPARE_OPERATOR_INVALID;
*s = e;
- return table[i].op;
+ return i->op;
}
}
diff --git a/src/shared/condition.c b/src/shared/condition.c
index d3446e8..b53b2ef 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -59,7 +59,7 @@
#include "string-util.h"
#include "tomoyo-util.h"
#include "tpm2-util.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
#include "virt.h"
@@ -141,7 +141,6 @@ static int condition_test_kernel_command_line(Condition *c, char **env) {
}
static int condition_test_credential(Condition *c, char **env) {
- int (*gd)(const char **ret);
int r;
assert(c);
@@ -155,7 +154,8 @@ static int condition_test_credential(Condition *c, char **env) {
if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */
return false;
- FOREACH_POINTER(gd, get_credentials_dir, get_encrypted_credentials_dir) {
+ int (*gd)(const char **ret);
+ FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) {
_cleanup_free_ char *j = NULL;
const char *cd;
@@ -260,13 +260,13 @@ static int condition_test_osrelease(Condition *c, char **env) {
/* The os-release spec mandates env-var-like key names */
if (r == 0 || isempty(word) || !env_name_is_valid(key))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to parse parameter, key/value format expected: %m");
+ "Failed to parse parameter, key/value format expected.");
/* Do not allow whitespace after the separator, as that's not a valid os-release format */
operator = parse_compare_operator(&word, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
if (operator < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to parse parameter, key/value format expected: %m");
+ "Failed to parse parameter, key/value format expected.");
r = parse_os_release(NULL, key, &actual_value);
if (r < 0)
@@ -543,7 +543,7 @@ static int condition_test_firmware_smbios_field(const char *expression) {
/* Read actual value from sysfs */
if (!filename_is_valid(field))
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name");
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name.");
const char *p = strjoina("/sys/class/dmi/id/", field);
r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL);
@@ -599,7 +599,7 @@ static int condition_test_firmware(Condition *c, char **env) {
end = strrchr(arg, ')');
if (!end || *(end + 1) != '\0')
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s: %m", c->parameter);
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s.", c->parameter);
smbios_arg = strndup(arg, end - arg);
if (!smbios_arg)
@@ -751,9 +751,9 @@ static int condition_test_needs_update(Condition *c, char **env) {
assert(c->parameter);
assert(c->type == CONDITION_NEEDS_UPDATE);
- r = proc_cmdline_get_bool("systemd.condition-needs-update", /* flags = */ 0, &b);
+ r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b);
if (r < 0)
- log_debug_errno(r, "Failed to parse systemd.condition-needs-update= kernel command line argument, ignoring: %m");
+ log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
if (r > 0)
return b;
@@ -931,7 +931,7 @@ static int condition_test_path_is_mount_point(Condition *c, char **env) {
assert(c->parameter);
assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
- return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0;
+ return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0;
}
static int condition_test_path_is_read_write(Condition *c, char **env) {
@@ -1024,7 +1024,7 @@ static int condition_test_psi(Condition *c, char **env) {
"io";
p = c->parameter;
- r = extract_many_words(&p, ":", 0, &first, &second, NULL);
+ r = extract_many_words(&p, ":", 0, &first, &second);
if (r <= 0)
return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
/* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */
@@ -1046,7 +1046,7 @@ static int condition_test_psi(Condition *c, char **env) {
slice = strstrip(first);
if (!slice)
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
r = cg_all_unified();
if (r < 0)
@@ -1099,7 +1099,7 @@ static int condition_test_psi(Condition *c, char **env) {
/* If a value including a specific timespan (in the intervals allowed by the kernel),
* parse it, otherwise we assume just a plain percentage that will be checked if it is
* smaller or equal to the current pressure average over 5 minutes. */
- r = extract_many_words(&value, "/", 0, &third, &fourth, NULL);
+ r = extract_many_words(&value, "/", 0, &third, &fourth);
if (r <= 0)
return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
if (r == 1)
@@ -1109,7 +1109,7 @@ static int condition_test_psi(Condition *c, char **env) {
timespan = skip_leading_chars(fourth, NULL);
if (!timespan)
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
if (startswith(timespan, "10sec"))
current = &pressure.avg10;
@@ -1118,12 +1118,12 @@ static int condition_test_psi(Condition *c, char **env) {
else if (startswith(timespan, "5min"))
current = &pressure.avg300;
else
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
}
value = strstrip(third);
if (!value)
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
r = parse_permyriad(value);
if (r < 0)
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
index e8ecd9b..fcc45c6 100644
--- a/src/shared/conf-parser.c
+++ b/src/shared/conf-parser.c
@@ -8,6 +8,7 @@
#include <sys/types.h>
#include "alloc-util.h"
+#include "chase.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "constants.h"
@@ -159,7 +160,11 @@ static int next_assignment(
/* Warn about unknown non-extension fields. */
if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-"))
log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Unknown key name '%s' in section '%s', ignoring.", lvalue, section);
+ "Unknown key '%s'%s%s%s, ignoring.",
+ lvalue,
+ section ? " in section [" : "",
+ strempty(section),
+ section ? "]" : "");
return 0;
}
@@ -480,6 +485,7 @@ int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const s
}
static int config_parse_many_files(
+ const char *root,
const char* const* conf_files,
char **files,
const char *sections,
@@ -502,19 +508,16 @@ static int config_parse_many_files(
}
STRV_FOREACH(fn, files) {
- _cleanup_free_ struct stat *st_dropin = NULL;
_cleanup_fclose_ FILE *f = NULL;
- int fd;
+ _cleanup_free_ char *fname = NULL;
- f = fopen(*fn, "re");
- if (!f) {
- if (errno == ENOENT)
- continue;
-
- return -errno;
- }
+ r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", &fname, &f);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
- fd = fileno(f);
+ int fd = fileno(f);
r = ordered_hashmap_ensure_put(&dropins, &config_file_hash_ops_fclose, *fn, f);
if (r < 0) {
@@ -527,7 +530,7 @@ static int config_parse_many_files(
/* Get inodes for all drop-ins. Later we'll verify if main config is a symlink to or is
* symlinked as one of them. If so, we skip reading main config file directly. */
- st_dropin = new(struct stat, 1);
+ _cleanup_free_ struct stat *st_dropin = new(struct stat, 1);
if (!st_dropin)
return -ENOMEM;
@@ -543,13 +546,11 @@ static int config_parse_many_files(
STRV_FOREACH(fn, conf_files) {
_cleanup_fclose_ FILE *f = NULL;
- f = fopen(*fn, "re");
- if (!f) {
- if (errno == ENOENT)
- continue;
-
- return -errno;
- }
+ r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return r;
if (inodes) {
if (fstat(fileno(f), &st) < 0)
@@ -561,7 +562,7 @@ static int config_parse_many_files(
}
}
- r = config_parse(NULL, *fn, f, sections, lookup, table, flags, userdata, &st);
+ r = config_parse(/* unit= */ NULL, *fn, f, sections, lookup, table, flags, userdata, &st);
if (r < 0)
return r;
assert(r > 0);
@@ -580,7 +581,7 @@ static int config_parse_many_files(
const char *path_dropin;
FILE *f_dropin;
ORDERED_HASHMAP_FOREACH_KEY(f_dropin, path_dropin, dropins) {
- r = config_parse(NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st);
+ r = config_parse(/* unit= */ NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st);
if (r < 0)
return r;
assert(r > 0);
@@ -598,54 +599,6 @@ static int config_parse_many_files(
return 0;
}
-/* Parse one main config file located in /etc/systemd and its drop-ins, which is what all systemd daemons
- * do. */
-int config_parse_config_file(
- const char *conf_file,
- const char *sections,
- ConfigItemLookup lookup,
- const void *table,
- ConfigParseFlags flags,
- void *userdata) {
-
- _cleanup_strv_free_ char **dropins = NULL, **dropin_dirs = NULL;
- char **conf_paths = CONF_PATHS_STRV("");
- int r;
-
- assert(conf_file);
-
- /* build the dropin dir list */
- dropin_dirs = new0(char*, strv_length(conf_paths) + 1);
- if (!dropin_dirs) {
- if (flags & CONFIG_PARSE_WARN)
- return log_oom();
- return -ENOMEM;
- }
-
- size_t i = 0;
- STRV_FOREACH(p, conf_paths) {
- char *d;
-
- d = strjoin(*p, "systemd/", conf_file, ".d");
- if (!d) {
- if (flags & CONFIG_PARSE_WARN)
- return log_oom();
- return -ENOMEM;
- }
-
- dropin_dirs[i++] = d;
- }
-
- r = conf_files_list_strv(&dropins, ".conf", NULL, 0, (const char**) dropin_dirs);
- if (r < 0)
- return r;
-
- const char *sysconf_file = strjoina(PKGSYSCONFDIR, "/", conf_file);
-
- return config_parse_many_files(STRV_MAKE_CONST(sysconf_file), dropins,
- sections, lookup, table, flags, userdata, NULL);
-}
-
/* Parse each config file in the directories specified as strv. */
int config_parse_many(
const char* const* conf_files,
@@ -665,14 +618,13 @@ int config_parse_many(
assert(conf_file_dirs);
assert(dropin_dirname);
- assert(sections);
assert(table);
r = conf_files_list_dropins(&files, dropin_dirname, root, conf_file_dirs);
if (r < 0)
return r;
- r = config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
+ r = config_parse_many_files(root, conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
if (r < 0)
return r;
@@ -682,6 +634,50 @@ int config_parse_many(
return 0;
}
+int config_parse_standard_file_with_dropins_full(
+ const char *root,
+ const char *main_file, /* A path like "systemd/frobnicator.conf" */
+ const char *sections,
+ ConfigItemLookup lookup,
+ const void *table,
+ ConfigParseFlags flags,
+ void *userdata,
+ Hashmap **ret_stats_by_path,
+ char ***ret_dropin_files) {
+
+ const char* const *conf_paths = (const char* const*) CONF_PATHS_STRV("");
+ _cleanup_strv_free_ char **configs = NULL;
+ int r;
+
+ /* Build the list of main config files */
+ r = strv_extend_strv_biconcat(&configs, root, conf_paths, main_file);
+ if (r < 0) {
+ if (flags & CONFIG_PARSE_WARN)
+ log_oom();
+ return r;
+ }
+
+ _cleanup_free_ char *dropin_dirname = strjoin(main_file, ".d");
+ if (!dropin_dirname) {
+ if (flags & CONFIG_PARSE_WARN)
+ log_oom();
+ return -ENOMEM;
+ }
+
+ return config_parse_many(
+ (const char* const*) configs,
+ conf_paths,
+ dropin_dirname,
+ root,
+ sections,
+ lookup,
+ table,
+ flags,
+ userdata,
+ ret_stats_by_path,
+ ret_dropin_files);
+}
+
static int dropins_get_stats_by_path(
const char* conf_file,
const char* const* conf_file_dirs,
@@ -795,12 +791,12 @@ bool stats_by_path_equal(Hashmap *a, Hashmap *b) {
return true;
}
-static void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
+void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
siphash24_compress_string(c->filename, state);
- siphash24_compress(&c->line, sizeof(c->line), state);
+ siphash24_compress_typesafe(c->line, state);
}
-static int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) {
+int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) {
int r;
r = strcmp(x->filename, y->filename);
@@ -1062,7 +1058,7 @@ int config_parse_tristate(
if (isempty(rvalue)) {
*t = -1;
- return 0;
+ return 1;
}
r = parse_tristate(rvalue, t);
@@ -1072,7 +1068,7 @@ int config_parse_tristate(
return 0;
}
- return 0;
+ return 1;
}
int config_parse_string(
@@ -1088,6 +1084,7 @@ int config_parse_string(
void *userdata) {
char **s = ASSERT_PTR(data);
+ int r;
assert(filename);
assert(lvalue);
@@ -1095,7 +1092,7 @@ int config_parse_string(
if (isempty(rvalue)) {
*s = mfree(*s);
- return 0;
+ return 1;
}
if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) {
@@ -1116,7 +1113,11 @@ int config_parse_string(
return 0;
}
- return free_and_strdup_warn(s, empty_to_null(rvalue));
+ r = free_and_strdup_warn(s, empty_to_null(rvalue));
+ if (r < 0)
+ return r;
+
+ return 1;
}
int config_parse_dns_name(
@@ -1592,7 +1593,7 @@ int config_parse_mtu(
return 0;
}
- return 0;
+ return 1;
}
int config_parse_rlimit(
@@ -1982,3 +1983,37 @@ int config_parse_unsigned_bounded(
DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value");
DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value");
DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value");
+
+int config_parse_timezone(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **tz = ASSERT_PTR(data);
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *tz = mfree(*tz);
+ return 0;
+ }
+
+ r = verify_timezone(rvalue, LOG_WARNING);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Timezone is not valid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ return free_and_strdup_warn(tz, rvalue);
+}
diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h
index a1768cd..35e203c 100644
--- a/src/shared/conf-parser.h
+++ b/src/shared/conf-parser.h
@@ -93,20 +93,12 @@ int config_parse(
void *userdata,
struct stat *ret_stat); /* possibly NULL */
-int config_parse_config_file(
- const char *conf_file,
- const char *sections, /* nulstr */
- ConfigItemLookup lookup,
- const void *table,
- ConfigParseFlags flags,
- void *userdata);
-
int config_parse_many(
const char* const* conf_files, /* possibly empty */
const char* const* conf_file_dirs,
const char *dropin_dirname,
const char *root,
- const char *sections, /* nulstr */
+ const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
@@ -114,6 +106,36 @@ int config_parse_many(
Hashmap **ret_stats_by_path, /* possibly NULL */
char ***ret_drop_in_files); /* possibly NULL */
+int config_parse_standard_file_with_dropins_full(
+ const char *root,
+ const char *main_file, /* A path like "systemd/frobnicator.conf" */
+ const char *sections,
+ ConfigItemLookup lookup,
+ const void *table,
+ ConfigParseFlags flags,
+ void *userdata,
+ Hashmap **ret_stats_by_path, /* possibly NULL */
+ char ***ret_dropin_files); /* possibly NULL */
+
+static inline int config_parse_standard_file_with_dropins(
+ const char *main_file, /* A path like "systemd/frobnicator.conf" */
+ const char *sections, /* nulstr */
+ ConfigItemLookup lookup,
+ const void *table,
+ ConfigParseFlags flags,
+ void *userdata) {
+ return config_parse_standard_file_with_dropins_full(
+ /* root= */ NULL,
+ main_file,
+ sections,
+ lookup,
+ table,
+ flags,
+ userdata,
+ /* ret_stats_by_path= */ NULL,
+ /* ret_dropin_files= */ NULL);
+}
+
int config_get_stats_by_path(
const char *suffix,
const char *root,
@@ -137,7 +159,11 @@ static inline ConfigSection* config_section_free(ConfigSection *cs) {
DEFINE_TRIVIAL_CLEANUP_FUNC(ConfigSection*, config_section_free);
int config_section_new(const char *filename, unsigned line, ConfigSection **ret);
+
+void config_section_hash_func(const ConfigSection *c, struct siphash *state);
+int config_section_compare_func(const ConfigSection *x, const ConfigSection *y);
extern const struct hash_ops config_section_hash_ops;
+
int _hashmap_by_section_find_unused_line(
HashmapBase *entries_by_section,
const char *filename,
@@ -224,6 +250,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_percent);
CONFIG_PARSER_PROTOTYPE(config_parse_permyriad);
CONFIG_PARSER_PROTOTYPE(config_parse_pid);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
+CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
typedef enum Disabled {
DISABLED_CONFIGURATION,
diff --git a/src/shared/copy.c b/src/shared/copy.c
index 2b87cba..8389774 100644
--- a/src/shared/copy.c
+++ b/src/shared/copy.c
@@ -170,13 +170,14 @@ int copy_bytes_full(
assert(fdt >= 0);
assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD));
- /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a maximum
- * of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on
- * error, zero if EOF is hit before the bytes limit is hit and positive otherwise. If the copy fails for some
- * reason but we read but didn't yet write some data an ret_remains/ret_remains_size is not NULL, then it will
- * be initialized with an allocated buffer containing this "remaining" data. Note that these two parameters are
- * initialized with a valid buffer only on failure and only if there's actually data already read. Otherwise
- * these parameters if non-NULL are set to NULL. */
+ /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a
+ * maximum of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied.
+ * Returns negative on error, zero if EOF is hit before the bytes limit is hit and positive
+ * otherwise. If the copy fails for some reason but we read but didn't yet write some data and
+ * ret_remains/ret_remains_size is not NULL, then it will be initialized with an allocated buffer
+ * containing this "remaining" data. Note that these two parameters are initialized with a valid
+ * buffer only on failure and only if there's actually data already read. Otherwise these parameters
+ * if non-NULL are set to NULL. */
if (ret_remains)
*ret_remains = NULL;
diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c
index d096576..1112de1 100644
--- a/src/shared/cpu-set-util.c
+++ b/src/shared/cpu-set-util.c
@@ -11,6 +11,7 @@
#include "errno-util.h"
#include "extract-word.h"
#include "fd-util.h"
+#include "hexdecoct.h"
#include "log.h"
#include "macro.h"
#include "memory-util.h"
@@ -82,6 +83,63 @@ char *cpu_set_to_range_string(const CPUSet *set) {
return TAKE_PTR(str) ?: strdup("");
}
+char* cpu_set_to_mask_string(const CPUSet *a) {
+ _cleanup_free_ char *str = NULL;
+ size_t len = 0;
+ bool found_nonzero = false;
+
+ assert(a);
+
+ /* Return CPU set in hexadecimal bitmap mask, e.g.
+ * CPU 0 -> "1"
+ * CPU 1 -> "2"
+ * CPU 0,1 -> "3"
+ * CPU 0-3 -> "f"
+ * CPU 0-7 -> "ff"
+ * CPU 4-7 -> "f0"
+ * CPU 7 -> "80"
+ * None -> "0"
+ *
+ * When there are more than 32 CPUs, separate every 32 CPUs by comma, e.g.
+ * CPU 0-47 -> "ffff,ffffffff"
+ * CPU 0-63 -> "ffffffff,ffffffff"
+ * CPU 0-71 -> "ff,ffffffff,ffffffff" */
+
+ for (ssize_t i = a->allocated * 8; i >= 0; i -= 4) {
+ uint8_t m = 0;
+
+ for (size_t j = 0; j < 4; j++)
+ if (CPU_ISSET_S(i + j, a->allocated, a->set))
+ m |= 1U << j;
+
+ if (!found_nonzero)
+ found_nonzero = m > 0;
+
+ if (!found_nonzero && m == 0)
+ /* Skip leading zeros */
+ continue;
+
+ if (!GREEDY_REALLOC(str, len + 3))
+ return NULL;
+
+ str[len++] = hexchar(m);
+ if (i >= 4 && i % 32 == 0)
+ /* Separate by comma for each 32 CPUs. */
+ str[len++] = ',';
+ str[len] = 0;
+ }
+
+ return TAKE_PTR(str) ?: strdup("0");
+}
+
+CPUSet* cpu_set_free(CPUSet *c) {
+ if (!c)
+ return c;
+
+ cpu_set_reset(c);
+ return mfree(c);
+}
+
int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus) {
size_t need;
@@ -290,3 +348,22 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set) {
*set = TAKE_STRUCT(s);
return 0;
}
+
+int cpu_mask_add_all(CPUSet *mask) {
+ long m;
+ int r;
+
+ assert(mask);
+
+ m = sysconf(_SC_NPROCESSORS_ONLN);
+ if (m < 0)
+ return -errno;
+
+ for (unsigned i = 0; i < (unsigned) m; i++) {
+ r = cpu_set_add(mask, i);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h
index 3c63a58..618fe1b 100644
--- a/src/shared/cpu-set-util.h
+++ b/src/shared/cpu-set-util.h
@@ -19,11 +19,15 @@ static inline void cpu_set_reset(CPUSet *a) {
*a = (CPUSet) {};
}
+CPUSet* cpu_set_free(CPUSet *c);
+DEFINE_TRIVIAL_CLEANUP_FUNC(CPUSet*, cpu_set_free);
+
int cpu_set_add_all(CPUSet *a, const CPUSet *b);
int cpu_set_add(CPUSet *a, unsigned cpu);
char* cpu_set_to_string(const CPUSet *a);
char *cpu_set_to_range_string(const CPUSet *a);
+char* cpu_set_to_mask_string(const CPUSet *a);
int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus);
int parse_cpu_set_full(
@@ -50,3 +54,4 @@ int cpu_set_to_dbus(const CPUSet *set, uint8_t **ret, size_t *allocated);
int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set);
int cpus_in_affinity_mask(void);
+int cpu_mask_add_all(CPUSet *mask);
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index fa8ebe0..1d8bd91 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -12,23 +12,28 @@
#include "capability-util.h"
#include "chattr-util.h"
#include "constants.h"
+#include "copy.h"
#include "creds-util.h"
#include "efi-api.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "format-util.h"
#include "fs-util.h"
#include "io-util.h"
#include "memory-util.h"
-#include "mkdir.h"
+#include "mkdir-label.h"
#include "openssl-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "random-util.h"
+#include "recurse-dir.h"
#include "sparse-endian.h"
#include "stat-util.h"
+#include "tmpfile-util.h"
#include "tpm2-util.h"
-#include "virt.h"
+#include "user-util.h"
+#include "varlink.h"
#define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
@@ -100,6 +105,17 @@ int get_encrypted_credentials_dir(const char **ret) {
return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret);
}
+int open_credentials_dir(void) {
+ const char *d;
+ int r;
+
+ r = get_credentials_dir(&d);
+ if (r < 0)
+ return r;
+
+ return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY));
+}
+
int read_credential(const char *name, void **ret, size_t *ret_size) {
_cleanup_free_ char *fn = NULL;
const char *d;
@@ -127,14 +143,13 @@ int read_credential(const char *name, void **ret, size_t *ret_size) {
}
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
+ _cleanup_(iovec_done_erase) struct iovec ret_iovec = {};
_cleanup_(erase_and_freep) void *data = NULL;
_cleanup_free_ char *fn = NULL;
size_t sz = 0;
const char *d;
int r;
- assert(ret);
-
/* Just like read_credential() but will also look for encrypted credentials. Note that services only
* receive decrypted credentials, hence use read_credential() for those. This helper here is for
* generators, i.e. code that runs outside of service context, and thus has no decrypted credentials
@@ -177,23 +192,37 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
if (r < 0)
return log_error_errno(r, "Failed to read encrypted credential data: %m");
- r = decrypt_credential_and_warn(
- name,
- now(CLOCK_REALTIME),
- /* tpm2_device = */ NULL,
- /* tpm2_signature_path = */ NULL,
- data,
- sz,
- ret,
- ret_size);
+ if (geteuid() != 0)
+ r = ipc_decrypt_credential(
+ name,
+ now(CLOCK_REALTIME),
+ getuid(),
+ &IOVEC_MAKE(data, sz),
+ CREDENTIAL_ANY_SCOPE,
+ &ret_iovec);
+ else
+ r = decrypt_credential_and_warn(
+ name,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ getuid(),
+ &IOVEC_MAKE(data, sz),
+ CREDENTIAL_ANY_SCOPE,
+ &ret_iovec);
if (r < 0)
return r;
+ if (ret)
+ *ret = TAKE_PTR(ret_iovec.iov_base);
+ if (ret_size)
+ *ret_size = ret_iovec.iov_len;
+
return 1; /* found */
not_found:
- *ret = NULL;
-
+ if (ret)
+ *ret = NULL;
if (ret_size)
*ret_size = 0;
@@ -205,6 +234,7 @@ int read_credential_strings_many_internal(
...) {
_cleanup_free_ void *b = NULL;
+ bool all = true;
int r, ret = 0;
/* Reads a bunch of credentials into the specified buffers. If the specified buffers are already
@@ -220,10 +250,11 @@ int read_credential_strings_many_internal(
r = read_credential(first_name, &b, NULL);
if (r == -ENXIO) /* No creds passed at all? Bail immediately. */
return 0;
- if (r < 0) {
- if (r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*first_value, b);
va_list ap;
@@ -238,20 +269,19 @@ int read_credential_strings_many_internal(
if (!name)
break;
- value = va_arg(ap, char **);
- if (*value)
- continue;
+ value = ASSERT_PTR(va_arg(ap, char **));
r = read_credential(name, &bb, NULL);
- if (r < 0) {
- if (ret >= 0 && r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*value, bb);
}
va_end(ap);
- return ret;
+ return ret < 0 ? ret : all;
}
int read_credential_bool(const char *name) {
@@ -341,8 +371,7 @@ static int make_credential_host_secret(
CredentialSecretFlags flags,
const char *dirname,
const char *fn,
- void **ret_data,
- size_t *ret_size) {
+ struct iovec *ret) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
@@ -351,22 +380,9 @@ static int make_credential_host_secret(
assert(dfd >= 0);
assert(fn);
- /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
- * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
- * capability when it uses the AT_EMPTY_PATH flag. */
- if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
- if (fd < 0)
- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
- }
- if (fd < 0) {
- if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
- return -ENOMEM;
-
- fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
- if (fd < 0)
- return -errno;
- }
+ fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m");
r = chattr_secret(fd, 0);
if (r < 0)
@@ -386,44 +402,34 @@ static int make_credential_host_secret(
if (r < 0)
goto fail;
- if (fsync(fd) < 0) {
+ if (fchmod(fd, 0400) < 0) {
r = -errno;
goto fail;
}
- warn_not_encrypted(fd, flags, dirname, fn);
-
- if (t) {
- r = rename_noreplace(dfd, t, dfd, fn);
- if (r < 0)
- goto fail;
-
- t = mfree(t);
- } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
+ if (fsync(fd) < 0) {
r = -errno;
goto fail;
}
- if (fsync(dfd) < 0) {
- r = -errno;
+ warn_not_encrypted(fd, flags, dirname, fn);
+
+ r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to link host key into place: %m");
goto fail;
}
- if (ret_data) {
+ if (ret) {
void *copy;
copy = memdup(buf.data, sizeof(buf.data));
- if (!copy) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!copy)
+ return -ENOMEM;
- *ret_data = copy;
+ *ret = IOVEC_MAKE(copy, sizeof(buf.data));
}
- if (ret_size)
- *ret_size = sizeof(buf.data);
-
return 0;
fail:
@@ -433,7 +439,7 @@ fail:
return r;
}
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
_cleanup_free_ char *_dirname = NULL, *_filename = NULL;
_cleanup_close_ int dfd = -EBADF;
sd_id128_t machine_id;
@@ -501,7 +507,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
"Failed to open %s/%s: %m", dirname, filename);
- r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size);
+ r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret);
if (r == -EEXIST) {
log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.",
dirname, filename);
@@ -549,7 +555,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
"Failed to read %s/%s: %m", dirname, filename);
if ((size_t) n != l) /* What? The size changed? */
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to read %s/%s: %m", dirname, filename);
+ "Failed to read %s/%s.", dirname, filename);
if (sd_id128_equal(machine_id, f->machine_id)) {
size_t sz;
@@ -568,12 +574,9 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
if (!copy)
return log_oom_debug();
- *ret = copy;
+ *ret = IOVEC_MAKE(copy, sz);
}
- if (ret_size)
- *ret_size = sz;
-
return 0;
}
@@ -658,6 +661,11 @@ struct _packed_ tpm2_public_key_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
+struct _packed_ scoped_credential_header {
+ le64_t flags; /* SCOPE_HASH_DATA_BASE_FLAGS for now */
+};
+
+/* This header is encrypted */
struct _packed_ metadata_credential_header {
le64_t timestamp;
le64_t not_after;
@@ -666,23 +674,38 @@ struct _packed_ metadata_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
+struct _packed_ scoped_hash_data {
+ le64_t flags; /* copy of the scoped_credential_header.flags */
+ le32_t uid;
+ sd_id128_t machine_id;
+ char username[]; /* followed by the username */
+ /* Later on we might want to extend this: with a cgroup path to allow per-app secrets, and with the user's $HOME encryption key */
+};
+
+enum {
+ /* Flags for scoped_hash_data.flags and scoped_credential_header.flags */
+ SCOPE_HASH_DATA_HAS_UID = 1 << 0,
+ SCOPE_HASH_DATA_HAS_MACHINE = 1 << 1,
+ SCOPE_HASH_DATA_HAS_USERNAME = 1 << 2,
+
+ SCOPE_HASH_DATA_BASE_FLAGS = SCOPE_HASH_DATA_HAS_UID | SCOPE_HASH_DATA_HAS_USERNAME | SCOPE_HASH_DATA_HAS_MACHINE,
+};
+
/* Some generic limit for parts of the encrypted credential for which we don't know the right size ahead of
* time, but where we are really sure it won't be larger than this. Should be larger than any possible IV,
* padding, tag size and so on. This is purely used for early filtering out of invalid sizes. */
#define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U)
static int sha256_hash_host_and_tpm2_key(
- const void *host_key,
- size_t host_key_size,
- const void *tpm2_key,
- size_t tpm2_key_size,
+ const struct iovec *host_key,
+ const struct iovec *tpm2_key,
uint8_t ret[static SHA256_DIGEST_LENGTH]) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL;
unsigned l;
- assert(host_key_size == 0 || host_key);
- assert(tpm2_key_size == 0 || tpm2_key);
+ assert(iovec_is_valid(host_key));
+ assert(iovec_is_valid(tpm2_key));
assert(ret);
/* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */
@@ -694,10 +717,10 @@ static int sha256_hash_host_and_tpm2_key(
if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context.");
- if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1)
+ if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key.");
- if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1)
+ if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key.");
assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH);
@@ -709,6 +732,58 @@ static int sha256_hash_host_and_tpm2_key(
return 0;
}
+static int mangle_uid_into_key(
+ uid_t uid,
+ uint8_t md[static SHA256_DIGEST_LENGTH]) {
+
+ sd_id128_t mid;
+ int r;
+
+ assert(uid_is_valid(uid));
+ assert(md);
+
+ /* If we shall encrypt for a specific user, we HMAC() a structure with the user's credentials
+ * (specifically, UID, user name, machine ID) with the key we'd otherwise use for system credentials,
+ * and use the resulting hash as actual encryption key. */
+
+ errno = 0;
+ struct passwd *pw = getpwuid(uid);
+ if (!pw)
+ return log_error_errno(
+ IN_SET(errno, 0, ENOENT) ? SYNTHETIC_ERRNO(ESRCH) : errno,
+ "Failed to resolve UID " UID_FMT ": %m", uid);
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read machine ID: %m");
+
+ size_t sz = offsetof(struct scoped_hash_data, username) + strlen(pw->pw_name) + 1;
+ _cleanup_free_ struct scoped_hash_data *d = malloc0(sz);
+ if (!d)
+ return log_oom();
+
+ d->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS);
+ d->uid = htole32(uid);
+ d->machine_id = mid;
+
+ strcpy(d->username, pw->pw_name);
+
+ _cleanup_(erase_and_freep) void *buf = NULL;
+ size_t buf_size = 0;
+ r = openssl_hmac_many(
+ "sha256",
+ md, SHA256_DIGEST_LENGTH,
+ &IOVEC_MAKE(d, sz), 1,
+ &buf, &buf_size);
+ if (r < 0)
+ return r;
+
+ assert(buf_size == SHA256_DIGEST_LENGTH);
+ memcpy(md, buf, buf_size);
+
+ return 0;
+}
+
int encrypt_credential_and_warn(
sd_id128_t with_key,
const char *name,
@@ -718,38 +793,39 @@ int encrypt_credential_and_warn(
uint32_t tpm2_hash_pcr_mask,
const char *tpm2_pubkey_path,
uint32_t tpm2_pubkey_pcr_mask,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ uid_t uid,
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
+ _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {};
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml;
- _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL;
_cleanup_free_ struct metadata_credential_header *m = NULL;
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
- _cleanup_free_ void *pubkey = NULL;
- size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
+ size_t p, ml;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_TPM2_ABSENT))
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED,
+ CRED_AES256_GCM_BY_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
if (name && !credential_name_valid(name))
@@ -770,18 +846,31 @@ int encrypt_credential_and_warn(
}
if (sd_id128_in_set(with_key,
+ _CRED_AUTO_SCOPED,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
+ if (!uid_is_valid(uid))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scoped credential selected, but no UID specified.");
+ } else
+ uid = UID_INVALID;
+
+ if (sd_id128_in_set(with_key,
_CRED_AUTO,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
r = get_credential_host_secret(
CREDENTIAL_SECRET_GENERATE|
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
- (sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
- &host_key,
- &host_key_size);
- if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO))
+ (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
+ &host_key);
+ if (r == -ENOMEDIUM && sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED))
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
else if (r < 0)
return log_error_errno(r, "Failed to determine local credential host secret: %m");
@@ -789,7 +878,7 @@ int encrypt_credential_and_warn(
#if HAVE_TPM2
bool try_tpm2;
- if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
/* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a
* container tpm2_support will detect this, and will return a different flag combination of
* TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
@@ -802,27 +891,31 @@ int encrypt_credential_and_warn(
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
if (try_tpm2) {
if (sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
/* Load public key for PCR policies, if one is specified, or explicitly requested */
- r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
- if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
+ if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED))
return log_error_errno(r, "Failed read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
}
}
- if (!pubkey)
+ if (!iovec_is_set(&pubkey))
tpm2_pubkey_pcr_mask = 0;
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
@@ -844,8 +937,8 @@ int encrypt_credential_and_warn(
return log_error_errno(r, "Could not read PCR values: %m");
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
@@ -854,7 +947,7 @@ int encrypt_credential_and_warn(
r = tpm2_calculate_sealing_policy(
tpm2_hash_pcr_values,
tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy);
@@ -865,56 +958,61 @@ int encrypt_credential_and_warn(
/* seal_key_handle= */ 0,
&tpm2_policy,
/* pin= */ NULL,
- &tpm2_key, &tpm2_key_size,
- &tpm2_blob, &tpm2_blob_size,
+ &tpm2_key,
+ &tpm2_blob,
&tpm2_primary_alg,
- /* ret_srk_buf= */ NULL,
- /* ret_srk_buf_size= */ NULL);
+ /* ret_srk= */ NULL);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
- else if (!sd_id128_equal(with_key, _CRED_AUTO))
+ else if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED))
return log_error_errno(r, "Failed to seal to TPM2: %m");
log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m");
}
- tpm2_policy_hash_size = tpm2_policy.size;
- tpm2_policy_hash = malloc(tpm2_policy_hash_size);
- if (!tpm2_policy_hash)
+ if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom();
- memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size);
- assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX);
- assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
}
#endif
- if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
/* Let's settle the key type in auto mode now. */
- if (host_key && tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
- else if (tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
- else if (host_key)
- id = CRED_AES256_GCM_BY_HOST;
+ if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ?
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)
+ : (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ?
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ else if (iovec_is_set(&tpm2_key) && !sd_id128_equal(with_key, _CRED_AUTO_SCOPED))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
+ else if (iovec_is_set(&host_key))
+ id = sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? CRED_AES256_GCM_BY_HOST_SCOPED : CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- id = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ id = CRED_AES256_GCM_BY_NULL;
else
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"TPM2 not available and host key located on temporary file system, no encryption key available.");
} else
id = with_key;
- if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT))
+ if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
- r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
if (r < 0)
return r;
+ if (uid_is_valid(uid)) {
+ r = mangle_uid_into_key(uid, md);
+ if (r < 0)
+ return r;
+ }
+
assert_se(cc = EVP_aes_256_gcm());
ksz = EVP_CIPHER_key_length(cc);
@@ -928,11 +1026,13 @@ int encrypt_credential_and_warn(
if (ivsz > 0) {
assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX);
- iv = malloc(ivsz);
- if (!iv)
+ iv.iov_base = malloc(ivsz);
+ if (!iv.iov_base)
return log_oom();
- r = crypto_random_bytes(iv, ivsz);
+ iv.iov_len = ivsz;
+
+ r = crypto_random_bytes(iv.iov_base, iv.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to acquired randomized IV: %m");
}
@@ -944,61 +1044,71 @@ int encrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1)
+ if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Just an upper estimate */
- output_size =
+ output.iov_len =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
- ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
- ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
+ ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) +
+ ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) +
+ ALIGN8(uid_is_valid(uid) ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
- input_size + 2U * (size_t) bsz +
+ input->iov_len + 2U * (size_t) bsz +
tsz;
- output = malloc0(output_size);
- if (!output)
+ output.iov_base = malloc0(output.iov_len);
+ if (!output.iov_base)
return log_oom();
- h = (struct encrypted_credential_header*) output;
+ h = (struct encrypted_credential_header*) output.iov_base;
h->id = id;
h->block_size = htole32(bsz);
h->key_size = htole32(ksz);
h->tag_size = htole32(tsz);
h->iv_size = htole32(ivsz);
- memcpy(h->iv, iv, ivsz);
+ memcpy(h->iv, iv.iov_base, ivsz);
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz);
- if (tpm2_key) {
+ if (iovec_is_set(&tpm2_key)) {
struct tpm2_credential_header *t;
- t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
+ t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p);
t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
- 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);
- memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size);
+ t->blob_size = htole32(tpm2_blob.iov_len);
+ t->policy_hash_size = htole32(tpm2_policy_hash.iov_len);
+ memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len);
+ memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len);
- p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
+ p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len);
}
- if (pubkey) {
+ if (iovec_is_set(&pubkey)) {
struct tpm2_public_key_credential_header *z;
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p);
z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
- z->size = htole32(pubkey_size);
- memcpy(z->data, pubkey, pubkey_size);
+ z->size = htole32(pubkey.iov_len);
+ memcpy(z->data, pubkey.iov_base, pubkey.iov_len);
+
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len);
+ }
+
+ if (uid_is_valid(uid)) {
+ struct scoped_credential_header *w;
+
+ w = (struct scoped_credential_header*) ((uint8_t*) output.iov_base + p);
+ w->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS);
- p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
+ p += ALIGN8(sizeof(struct scoped_credential_header));
}
- /* Pass the encrypted + TPM2 header as AAD */
- if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
+ /* Pass the encrypted + TPM2 header + scoped header as AAD */
+ if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
@@ -1014,53 +1124,52 @@ int encrypt_credential_and_warn(
memcpy_safe(m->name, name, ml);
/* And encrypt the metadata header */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Then encrypt the plaintext */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Finalize */
- if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1)
+ if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
- assert(p <= output_size - tsz);
+ assert(p <= output.iov_len - tsz);
/* Append tag */
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s",
ERR_error_string(ERR_get_error(), NULL));
p += tsz;
- assert(p <= output_size);
+ assert(p <= output.iov_len);
+ output.iov_len = p;
- if (DEBUG_LOGGING && input_size > 0) {
+ if (DEBUG_LOGGING && input->iov_len > 0) {
size_t base64_size;
- base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */
- assert(base64_size >= input_size);
- log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100);
+ base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */
+ assert(base64_size >= input->iov_len);
+ log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100);
}
- *ret = TAKE_PTR(output);
- *ret_size = p;
-
+ *ret = TAKE_STRUCT(output);
return 0;
}
@@ -1069,39 +1178,39 @@ int decrypt_credential_and_warn(
usec_t validate_timestamp,
const char *tpm2_device,
const char *tpm2_signature_path,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ uid_t uid,
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
- bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
+ bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope;
const EVP_CIPHER *cc;
+ size_t p, hs;
int r, added;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
- h = (struct encrypted_credential_header*) input;
+ h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
- if (input_size < sizeof(h->id))
+ if (input->iov_len < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
- with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
- with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
- with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
- is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
+ with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
+ with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
+ with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED) || with_tpm2_pk;
+ with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL);
+ with_scope = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
- if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
+ if (!with_host_key && !with_tpm2 && !with_null)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data.");
if (with_tpm2_pk) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
@@ -1109,7 +1218,7 @@ int decrypt_credential_and_warn(
return log_error_errno(r, "Failed to load pcr signature: %m");
}
- if (is_tpm2_absent) {
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
@@ -1129,8 +1238,19 @@ int decrypt_credential_and_warn(
log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting.");
}
+ if (with_scope) {
+ if (!uid_is_valid(uid))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to a user, but no user selected.");
+ } else {
+ /* Refuse to unlock system credentials if user scope is requested. */
+ if (uid_is_valid(uid) && !FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to the system, but user scope selected.");
+
+ uid = UID_INVALID;
+ }
+
/* Now we know the minimum header size */
- if (input_size < offsetof(struct encrypted_credential_header, iv))
+ if (input->iov_len < offsetof(struct encrypted_credential_header, iv))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
/* Verify some basic header values */
@@ -1145,10 +1265,11 @@ int decrypt_credential_and_warn(
/* Ensure we have space for the full header now (we don't know the size of the name hence this is a
* lower limit only) */
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1157,7 +1278,7 @@ int decrypt_credential_and_warn(
if (with_tpm2) {
#if HAVE_TPM2
- struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
+ struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p);
struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
@@ -1173,10 +1294,11 @@ int decrypt_credential_and_warn(
/* Ensure we have space for the full TPM2 header now (still don't know the name, and its size
* though, hence still just a lower limit test only) */
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1186,17 +1308,17 @@ int decrypt_credential_and_warn(
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
if (le32toh(z->size) > PUBLIC_KEY_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
- ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1215,21 +1337,16 @@ int decrypt_credential_and_warn(
r = tpm2_unseal(tpm2_context,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
- z ? z->data : NULL,
- z ? le32toh(z->size) : 0,
+ z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL,
z ? le64toh(z->pcr_mask) : 0,
signature_json,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg),
- t->policy_hash_and_blob,
- le32toh(t->blob_size),
- t->policy_hash_and_blob + le32toh(t->blob_size),
- le32toh(t->policy_hash_size),
- /* srk_buf= */ NULL,
- /* srk_buf_size= */ 0,
- &tpm2_key,
- &tpm2_key_size);
+ &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
+ &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
+ /* srk= */ NULL,
+ &tpm2_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
#else
@@ -1237,19 +1354,38 @@ int decrypt_credential_and_warn(
#endif
}
+ if (with_scope) {
+ struct scoped_credential_header* sh = (struct scoped_credential_header*) ((uint8_t*) input->iov_base + p);
+
+ if (le64toh(sh->flags) != SCOPE_HASH_DATA_BASE_FLAGS)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Scoped credential with unsupported flags.");
+
+ if (input->iov_len <
+ p +
+ sizeof(struct scoped_credential_header) +
+ ALIGN8(offsetof(struct metadata_credential_header, name)) +
+ le32toh(h->tag_size))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
+
+ p += sizeof(struct scoped_credential_header);
+ }
+
if (with_host_key) {
- r = get_credential_host_secret(
- 0,
- &host_key,
- &host_key_size);
+ r = get_credential_host_secret(/* flags= */ 0, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to determine local credential key: %m");
}
- if (is_tpm2_absent)
+ if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided.");
- sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
+
+ if (with_scope) {
+ r = mangle_uid_into_key(uid, md);
+ if (r < 0)
+ return r;
+ }
assert_se(cc = EVP_aes_256_gcm());
@@ -1276,41 +1412,41 @@ int decrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1)
+ if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext = malloc(input_size - p - le32toh(h->tag_size));
- if (!plaintext)
+ plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size));
+ if (!plaintext.iov_base)
return -ENOMEM;
if (EVP_DecryptUpdate(
context,
- plaintext,
+ plaintext.iov_base,
&added,
- (uint8_t*) input + p,
- input_size - p - le32toh(h->tag_size)) != 1)
+ (uint8_t*) input->iov_base + p,
+ input->iov_len - p - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= input_size - p - le32toh(h->tag_size));
- plaintext_size = added;
+ assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size));
+ plaintext.iov_len = added;
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1)
+ if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext_size += added;
+ plaintext.iov_len += added;
- if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name)))
+ if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name)))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
- m = plaintext;
+ m = plaintext.iov_base;
if (le64toh(m->timestamp) != USEC_INFINITY &&
le64toh(m->not_after) != USEC_INFINITY &&
@@ -1321,7 +1457,7 @@ int decrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing.");
hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size));
- if (plaintext_size < hs)
+ if (plaintext.iov_len < hs)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
if (le32toh(m->name_size) > 0) {
@@ -1336,7 +1472,7 @@ int decrypt_credential_and_warn(
if (validate_name && !streq(embedded_name, validate_name)) {
- r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NAME");
+ r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NAME");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m");
if (r != 0)
@@ -1352,7 +1488,7 @@ int decrypt_credential_and_warn(
if (le64toh(m->not_after) != USEC_INFINITY && le64toh(m->not_after) < validate_timestamp) {
- r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER");
+ r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER: %m");
if (r != 0)
@@ -1363,33 +1499,245 @@ int decrypt_credential_and_warn(
}
if (ret) {
- char *without_metadata;
+ _cleanup_(iovec_done_erase) struct iovec without_metadata = {};
- without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs);
- if (!without_metadata)
+ without_metadata.iov_len = plaintext.iov_len - hs;
+ without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len);
+ if (!without_metadata.iov_base)
return log_oom();
- *ret = without_metadata;
+ *ret = TAKE_STRUCT(without_metadata);
}
- if (ret_size)
- *ret_size = plaintext_size - hs;
-
return 0;
}
#else
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
#endif
+
+int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r;
+
+ assert(input && iovec_is_valid(input));
+ assert(ret);
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m");
+
+ /* Mark anything we get from the service as sensitive, given that it might use a NULL cypher, at least in theory */
+ r = varlink_set_input_sensitive(vl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable sensitive Varlink input: %m");
+
+ /* Create the input data blob object separately, so that we can mark it as sensitive */
+ _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL;
+ r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input));
+ if (r < 0)
+ return log_error_errno(r, "Failed to create input object: %m");
+
+ json_variant_sensitive(jinput);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.Credentials.Encrypt",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(name, "name", JSON_BUILD_STRING(name)),
+ JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(jinput)),
+ JSON_BUILD_PAIR_CONDITION(timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(timestamp)),
+ JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", JSON_BUILD_UNSIGNED(not_after)),
+ JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")),
+ JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call Encrypt() varlink call.");
+ if (!isempty(error_id)) {
+ if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
+
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to encrypt: %s", error_id);
+ }
+
+ r = json_dispatch(
+ reply,
+ (const JsonDispatch[]) {
+ { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY },
+ {},
+ },
+ JSON_LOG|JSON_ALLOW_EXTENSIONS,
+ /* userdata= */ NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r;
+
+ assert(input && iovec_is_valid(input));
+ assert(ret);
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m");
+
+ r = varlink_set_input_sensitive(vl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable sensitive Varlink input: %m");
+
+ /* Create the input data blob object separately, so that we can mark it as sensitive (it's supposed
+ * to be encrypted, but who knows maybe it uses the NULL cypher). */
+ _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL;
+ r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input));
+ if (r < 0)
+ return log_error_errno(r, "Failed to create input object: %m");
+
+ json_variant_sensitive(jinput);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.Credentials.Decrypt",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(validate_name, "name", JSON_BUILD_STRING(validate_name)),
+ JSON_BUILD_PAIR("blob", JSON_BUILD_VARIANT(jinput)),
+ JSON_BUILD_PAIR_CONDITION(validate_timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(validate_timestamp)),
+ JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")),
+ JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call Decrypt() varlink call.");
+ if (!isempty(error_id)) {
+ if (streq(error_id, "io.systemd.Credentials.BadFormat"))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format.");
+ if (streq(error_id, "io.systemd.Credentials.NameMismatch"))
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations.");
+ if (streq(error_id, "io.systemd.Credentials.TimeMismatch"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window.");
+ if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
+ if (streq(error_id, "io.systemd.Credentials.BadScope"))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach.");
+
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id);
+ }
+
+ r = json_dispatch(
+ reply,
+ (const JsonDispatch[]) {
+ { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY },
+ {},
+ },
+ JSON_LOG|JSON_ALLOW_EXTENSIONS,
+ /* userdata= */ NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int pick_up_credential_one(
+ int credential_dir_fd,
+ const char *credential_name,
+ const PickUpCredential *table_entry) {
+
+ _cleanup_free_ char *fn = NULL, *target_path = NULL;
+ const char *e;
+ int r;
+
+ assert(credential_dir_fd >= 0);
+ assert(credential_name);
+ assert(table_entry);
+
+ e = startswith(credential_name, table_entry->credential_prefix);
+ if (!e)
+ return 0; /* unmatched */
+
+ fn = strjoin(e, table_entry->filename_suffix);
+ if (!fn)
+ return log_oom();
+
+ if (!filename_is_valid(fn))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Passed credential '%s' would result in invalid filename '%s'.",
+ credential_name, fn);
+
+ r = mkdir_p_label(table_entry->target_dir, 0755);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to create '%s': %m", table_entry->target_dir);
+
+ target_path = path_join(table_entry->target_dir, fn);
+ if (!target_path)
+ return log_oom();
+
+ r = copy_file_at(
+ credential_dir_fd, credential_name,
+ AT_FDCWD, target_path,
+ /* open_flags= */ 0,
+ 0644,
+ /* flags= */ 0);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to copy credential %s → file %s: %m",
+ credential_name, target_path);
+
+ log_info("Installed %s from credential.", target_path);
+ return 1; /* done */
+}
+
+int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) {
+ _cleanup_close_ int credential_dir_fd = -EBADF;
+ int r, ret = 0;
+
+ assert(table);
+ assert(n_table_entry > 0);
+
+ credential_dir_fd = open_credentials_dir();
+ if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) {
+ /* Credential env var not set, or dir doesn't exist. */
+ log_debug("No credentials found.");
+ return 0;
+ }
+ if (credential_dir_fd < 0)
+ return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
+
+ _cleanup_free_ DirectoryEntries *des = NULL;
+ r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate credentials: %m");
+
+ FOREACH_ARRAY(i, des->entries, des->n_entries) {
+ struct dirent *de = *i;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ FOREACH_ARRAY(t, table, n_table_entry) {
+ r = pick_up_credential_one(credential_dir_fd, de->d_name, t);
+ if (r != 0) {
+ RET_GATHER(ret, r);
+ break; /* Done, or failed. Let's move to the next credential. */
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h
index 5e39a6a..b80755b 100644
--- a/src/shared/creds-util.h
+++ b/src/shared/creds-util.h
@@ -31,6 +31,8 @@ bool credential_glob_valid(const char *s);
int get_credentials_dir(const char **ret);
int get_encrypted_credentials_dir(const char **ret);
+int open_credentials_dir(void);
+
/* Where creds have been passed to the system */
#define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system"
#define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted"
@@ -51,21 +53,31 @@ typedef enum CredentialSecretFlags {
CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2,
} CredentialSecretFlags;
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size);
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret);
int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed);
+typedef enum CredentialFlags {
+ CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */
+ CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */
+} CredentialFlags;
+
/* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of
* both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no
* authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler
* for us to handle). */
#define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a)
+#define CRED_AES256_GCM_BY_HOST_SCOPED SD_ID128_MAKE(55,b9,ed,1d,38,59,4d,43,a8,31,9d,2e,bb,33,2a,c6)
#define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe)
#define CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
+#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED \
+ SD_ID128_MAKE(ef,4a,c1,36,79,a9,48,0e,a7,db,68,89,7f,9f,16,5d)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \
SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
-#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
+#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED \
+ SD_ID128_MAKE(ad,bc,4c,a3,ef,b6,42,01,ba,88,1b,6f,2e,40,95,ea)
+#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
* an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and
@@ -74,6 +86,18 @@ int get_credential_user_password(const char *username, char **ret_password, bool
* with an underscore. */
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
+#define _CRED_AUTO_SCOPED SD_ID128_MAKE(23,88,96,85,6f,74,48,8a,9c,78,6f,6a,b0,e7,3b,6a)
+
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+
+int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
+
+typedef struct PickUpCredential {
+ const char *credential_prefix;
+ const char *target_dir;
+ const char *filename_suffix;
+} PickUpCredential;
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size);
+int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry);
diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c
index 285b82a..5ab5cef 100644
--- a/src/shared/cryptsetup-fido2.c
+++ b/src/shared/cryptsetup-fido2.c
@@ -24,11 +24,11 @@ int acquire_fido2_key(
const void *key_data,
size_t key_data_size,
usec_t until,
- bool headless,
Fido2EnrollFlags required,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags) {
+ size_t *ret_decrypted_key_size) {
_cleanup_(erase_and_freep) char *envpw = NULL;
_cleanup_strv_free_erase_ char **pins = NULL;
@@ -38,11 +38,11 @@ int acquire_fido2_key(
size_t salt_size;
int r;
- if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && headless)
+ if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG),
"Local verification is required to unlock this volume, but the 'headless' parameter was set.");
- ask_password_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED;
+ askpw_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED;
assert(cid);
assert(key_file || key_data);
@@ -115,15 +115,22 @@ int acquire_fido2_key(
device_exists = true; /* that a PIN is needed/wasn't correct means that we managed to
* talk to a device */
- if (headless)
+ if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable.");
+ static const AskPasswordRequest req = {
+ .message = "Please enter security token PIN:",
+ .icon = "drive-harddisk",
+ .keyring = "fido2-pin",
+ .credential = "cryptsetup.fido2-pin",
+ };
+
pins = strv_free_erase(pins);
- r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, ask_password_flags, &pins);
+ r = ask_password_auto(&req, until, askpw_flags, &pins);
if (r < 0)
return log_error_errno(r, "Failed to ask for user password: %m");
- ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
+ askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
}
}
@@ -133,10 +140,10 @@ int acquire_fido2_key_auto(
const char *friendly_name,
const char *fido2_device,
usec_t until,
- bool headless,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags) {
+ size_t *ret_decrypted_key_size) {
_cleanup_free_ void *cid = NULL;
size_t cid_size = 0;
@@ -150,7 +157,7 @@ int acquire_fido2_key_auto(
/* Loads FIDO2 metadata from LUKS2 JSON token headers. */
- for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) {
+ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w;
_cleanup_free_ void *salt = NULL;
@@ -177,7 +184,7 @@ int acquire_fido2_key_auto(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data lacks 'fido2-credential' field.");
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size);
+ r = unbase64mem(json_variant_string(w), &cid, &cid_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'fido2-credential' field.");
@@ -189,7 +196,7 @@ int acquire_fido2_key_auto(
assert(!salt);
assert(salt_size == 0);
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size);
+ r = unbase64mem(json_variant_string(w), &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded salt.");
@@ -254,10 +261,11 @@ int acquire_fido2_key_auto(
/* key_file_offset= */ 0,
salt, salt_size,
until,
- headless,
required,
- ret_decrypted_key, ret_decrypted_key_size,
- ask_password_flags);
+ "cryptsetup.fido2-pin",
+ askpw_flags,
+ ret_decrypted_key,
+ ret_decrypted_key_size);
if (ret == 0)
break;
}
diff --git a/src/shared/cryptsetup-fido2.h b/src/shared/cryptsetup-fido2.h
index d96bb40..bd25566 100644
--- a/src/shared/cryptsetup-fido2.h
+++ b/src/shared/cryptsetup-fido2.h
@@ -23,11 +23,11 @@ int acquire_fido2_key(
const void *key_data,
size_t key_data_size,
usec_t until,
- bool headless,
Fido2EnrollFlags required,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags);
+ size_t *ret_decrypted_key_size);
int acquire_fido2_key_auto(
struct crypt_device *cd,
@@ -35,10 +35,10 @@ int acquire_fido2_key_auto(
const char *friendly_name,
const char *fido2_device,
usec_t until,
- bool headless,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags);
+ size_t *ret_decrypted_key_size);
#else
@@ -55,11 +55,11 @@ static inline int acquire_fido2_key(
const void *key_data,
size_t key_data_size,
usec_t until,
- bool headless,
Fido2EnrollFlags required,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags) {
+ size_t *ret_decrypted_key_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 token support not available.");
@@ -71,10 +71,10 @@ static inline int acquire_fido2_key_auto(
const char *friendly_name,
const char *fido2_device,
usec_t until,
- bool headless,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
void **ret_decrypted_key,
- size_t *ret_decrypted_key_size,
- AskPasswordFlags ask_password_flags) {
+ size_t *ret_decrypted_key_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 token support not available.");
diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c
new file mode 100644
index 0000000..bfd7d3a
--- /dev/null
+++ b/src/shared/cryptsetup-tpm2.c
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "cryptsetup-tpm2.h"
+#include "env-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "sha256.h"
+#include "tpm2-util.h"
+
+static int get_pin(
+ usec_t until,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
+ char **ret_pin_str) {
+ _cleanup_(erase_and_freep) char *pin_str = NULL;
+ _cleanup_strv_free_erase_ char **pin = NULL;
+ int r;
+
+ assert(ret_pin_str);
+
+ r = getenv_steal_erase("PIN", &pin_str);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+ if (!r) {
+ if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
+ return log_error_errno(
+ SYNTHETIC_ERRNO(ENOPKG),
+ "PIN querying disabled via 'headless' option. "
+ "Use the '$PIN' environment variable.");
+
+ AskPasswordRequest req = {
+ .message = "Please enter TPM2 PIN:",
+ .icon = "drive-harddisk",
+ .keyring = "tpm2-pin",
+ .credential = askpw_credential,
+ };
+
+ pin = strv_free_erase(pin);
+ r = ask_password_auto(&req, until, askpw_flags, &pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to ask for user pin: %m");
+ assert(strv_length(pin) == 1);
+
+ pin_str = strdup(pin[0]);
+ if (!pin_str)
+ return log_oom();
+ }
+
+ *ret_pin_str = TAKE_PTR(pin_str);
+
+ return r;
+}
+
+int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const struct iovec *pubkey,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pcrlock_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
+ const struct iovec *pcrlock_nv,
+ TPM2Flags flags,
+ usec_t until,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
+ struct iovec *ret_decrypted_key) {
+
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
+ _cleanup_free_ void *loaded_blob = NULL;
+ _cleanup_free_ char *auto_device = NULL;
+ struct iovec blob;
+ int r;
+
+ assert(iovec_is_valid(salt));
+
+ if (!device) {
+ r = tpm2_find_device_auto(&auto_device);
+ if (r == -ENODEV)
+ return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
+ if (r < 0)
+ return log_error_errno(r, "Could not find TPM2 device: %m");
+
+ device = auto_device;
+ }
+
+ if (iovec_is_set(key_data))
+ blob = *key_data;
+ else {
+ _cleanup_free_ char *bindname = NULL;
+
+ /* If we read the salt via AF_UNIX, make this client recognizable */
+ if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, key_file,
+ key_file_offset == 0 ? UINT64_MAX : key_file_offset,
+ key_file_size == 0 ? SIZE_MAX : key_file_size,
+ READ_FULL_FILE_CONNECT_SOCKET,
+ bindname,
+ (char**) &loaded_blob, &blob.iov_len);
+ if (r < 0)
+ return r;
+
+ blob.iov_base = loaded_blob;
+ }
+
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load pcr signature: %m");
+ }
+
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
+
+ if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) {
+ r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Not found? Then search among passed credentials */
+ r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume.");
+ }
+ }
+
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
+ r = tpm2_context_new_or_warn(device, &tpm2_context);
+ if (r < 0)
+ return r;
+
+ if (!(flags & TPM2_FLAGS_USE_PIN)) {
+ r = tpm2_unseal(tpm2_context,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey,
+ pubkey_pcr_mask,
+ signature_json,
+ /* pin= */ NULL,
+ FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
+ primary_alg,
+ &blob,
+ policy_hash,
+ srk,
+ ret_decrypted_key);
+ if (r < 0)
+ return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
+
+ return r;
+ }
+
+ for (int i = 5;; i--) {
+ _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL;
+
+ if (i <= 0)
+ return -EACCES;
+
+ r = get_pin(until, askpw_credential, askpw_flags, &pin_str);
+ if (r < 0)
+ return r;
+
+ if (iovec_is_set(salt)) {
+ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
+ CLEANUP_ERASE(salted_pin);
+
+ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to perform PBKDF2: %m");
+
+ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to base64 encode salted pin: %m");
+ } else
+ /* no salting needed, backwards compat with non-salted pins */
+ b64_salted_pin = TAKE_PTR(pin_str);
+
+ r = tpm2_unseal(tpm2_context,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey,
+ pubkey_pcr_mask,
+ signature_json,
+ b64_salted_pin,
+ FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
+ primary_alg,
+ &blob,
+ policy_hash,
+ srk,
+ ret_decrypted_key);
+ if (r < 0) {
+ log_error_errno(r, "Failed to unseal secret using TPM2: %m");
+
+ /* We get this error in case there is an authentication policy mismatch. This should
+ * not happen, but this avoids confusing behavior, just in case. */
+ if (!IN_SET(r, -EPERM, -ENOLCK))
+ continue;
+ }
+
+ return r;
+ }
+}
+
+int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ struct iovec *ret_pubkey,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
+ struct iovec *ret_pcrlock_nv,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token) {
+
+ int r, token;
+
+ assert(cd);
+
+ for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags;
+ int keyslot;
+
+ r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
+ if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read JSON token data off disk: %m");
+
+ r = tpm2_parse_luks2_json(
+ v,
+ &keyslot,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob,
+ &policy_hash,
+ &salt,
+ &srk,
+ &pcrlock_nv,
+ &flags);
+ if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse TPM2 JSON data: %m");
+
+ if (search_pcr_mask == UINT32_MAX ||
+ search_pcr_mask == hash_pcr_mask) {
+
+ if (start_token <= 0)
+ log_info("Automatically discovered security TPM2 token unlocks volume.");
+
+ *ret_hash_pcr_mask = hash_pcr_mask;
+ *ret_pcr_bank = pcr_bank;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
+ *ret_pubkey_pcr_mask = pubkey_pcr_mask;
+ *ret_primary_alg = primary_alg;
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
+ *ret_salt = TAKE_STRUCT(salt);
+ *ret_keyslot = keyslot;
+ *ret_token = token;
+ *ret_srk = TAKE_STRUCT(srk);
+ *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv);
+ *ret_flags = flags;
+ return 0;
+ }
+
+ /* PCR mask doesn't match what is configured, ignore this entry, let's see next */
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found.");
+}
diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h
new file mode 100644
index 0000000..b9905f4
--- /dev/null
+++ b/src/shared/cryptsetup-tpm2.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "ask-password-api.h"
+#include "cryptsetup-util.h"
+#include "log.h"
+#include "time-util.h"
+#include "tpm2-util.h"
+
+#if HAVE_TPM2
+
+int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const struct iovec *pubkey,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pcrlock_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
+ const struct iovec *pcrlock_nv,
+ TPM2Flags flags,
+ usec_t until,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
+ struct iovec *ret_decrypted_key);
+
+int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ struct iovec *ret_pubkey,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
+ struct iovec *ret_pcrlock_nv,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token);
+
+#else
+
+static inline int acquire_tpm2_key(
+ const char *volume_name,
+ const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const struct iovec *pubkey,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pcrlock_path,
+ uint16_t primary_alg,
+ const char *key_file,
+ size_t key_file_size,
+ uint64_t key_file_offset,
+ const struct iovec *key_data,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
+ const struct iovec *pcrlock_nv,
+ TPM2Flags flags,
+ usec_t until,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
+ struct iovec *ret_decrypted_key) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "TPM2 support not available.");
+}
+
+static inline int find_tpm2_auto_data(
+ struct crypt_device *cd,
+ uint32_t search_pcr_mask,
+ int start_token,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ struct iovec *ret_pubkey,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
+ struct iovec *ret_pcrlock_nv,
+ TPM2Flags *ret_flags,
+ int *ret_keyslot,
+ int *ret_token) {
+
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "TPM2 support not available.");
+}
+
+#endif
diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c
index ab5764d..288e6e8 100644
--- a/src/shared/cryptsetup-util.c
+++ b/src/shared/cryptsetup-util.c
@@ -9,58 +9,62 @@
#if HAVE_LIBCRYPTSETUP
static void *cryptsetup_dl = NULL;
-int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags);
+DLSYM_FUNCTION(crypt_activate_by_passphrase);
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
-int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags);
+DLSYM_FUNCTION(crypt_activate_by_signed_key);
#endif
-int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags);
-int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags);
-int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params);
-void (*sym_crypt_free)(struct crypt_device *cd);
-const char *(*sym_crypt_get_cipher)(struct crypt_device *cd);
-const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd);
-uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd);
-const char *(*sym_crypt_get_device_name)(struct crypt_device *cd);
-const char *(*sym_crypt_get_dir)(void);
-const char *(*sym_crypt_get_type)(struct crypt_device *cd);
-const char *(*sym_crypt_get_uuid)(struct crypt_device *cd);
-int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp);
-int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd);
-int (*sym_crypt_init)(struct crypt_device **cd, const char *device);
-int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name);
-int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size);
-int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot);
-int (*sym_crypt_keyslot_max)(const char *type);
-int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params);
-int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size);
-int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size);
-int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device);
-void (*sym_crypt_set_debug_level)(int level);
-void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr);
+DLSYM_FUNCTION(crypt_activate_by_volume_key);
+DLSYM_FUNCTION(crypt_deactivate_by_name);
+DLSYM_FUNCTION(crypt_format);
+DLSYM_FUNCTION(crypt_free);
+DLSYM_FUNCTION(crypt_get_cipher);
+DLSYM_FUNCTION(crypt_get_cipher_mode);
+DLSYM_FUNCTION(crypt_get_data_offset);
+DLSYM_FUNCTION(crypt_get_device_name);
+DLSYM_FUNCTION(crypt_get_dir);
+DLSYM_FUNCTION(crypt_get_type);
+DLSYM_FUNCTION(crypt_get_uuid);
+DLSYM_FUNCTION(crypt_get_verity_info);
+DLSYM_FUNCTION(crypt_get_volume_key_size);
+DLSYM_FUNCTION(crypt_init);
+DLSYM_FUNCTION(crypt_init_by_name);
+DLSYM_FUNCTION(crypt_keyslot_add_by_volume_key);
+DLSYM_FUNCTION(crypt_keyslot_destroy);
+DLSYM_FUNCTION(crypt_keyslot_max);
+DLSYM_FUNCTION(crypt_load);
+DLSYM_FUNCTION(crypt_resize);
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+DLSYM_FUNCTION(crypt_resume_by_volume_key);
+#endif
+DLSYM_FUNCTION(crypt_set_data_device);
+DLSYM_FUNCTION(crypt_set_debug_level);
+DLSYM_FUNCTION(crypt_set_log_callback);
#if HAVE_CRYPT_SET_METADATA_SIZE
-int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size);
+DLSYM_FUNCTION(crypt_set_metadata_size);
#endif
-int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf);
-int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name);
-int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json);
-int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json);
+DLSYM_FUNCTION(crypt_set_pbkdf_type);
+DLSYM_FUNCTION(crypt_suspend);
+DLSYM_FUNCTION(crypt_token_json_get);
+DLSYM_FUNCTION(crypt_token_json_set);
#if HAVE_CRYPT_TOKEN_MAX
-int (*sym_crypt_token_max)(const char *type);
+DLSYM_FUNCTION(crypt_token_max);
#endif
-crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
-int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
+DLSYM_FUNCTION(crypt_token_status);
+DLSYM_FUNCTION(crypt_volume_key_get);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
-int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
+DLSYM_FUNCTION(crypt_reencrypt_init_by_passphrase);
#endif
#if HAVE_CRYPT_REENCRYPT
-int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
+DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+DLSYM_FUNCTION(crypt_reencrypt);
+REENABLE_WARNING;
#endif
-int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
+DLSYM_FUNCTION(crypt_metadata_locking);
#if HAVE_CRYPT_SET_DATA_OFFSET
-int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
+DLSYM_FUNCTION(crypt_set_data_offset);
#endif
-int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
-int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
+DLSYM_FUNCTION(crypt_header_restore);
+DLSYM_FUNCTION(crypt_volume_key_keyring);
/* Unfortunately libcryptsetup provides neither an environment variable to redirect where to look for token
* modules, nor does it have an API to change the token lookup path at runtime. The maintainers suggest using
@@ -248,6 +252,11 @@ int dlopen_cryptsetup(void) {
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+ ELF_NOTE_DLOPEN("cryptsetup",
+ "Support for disk encryption, integrity, and authentication",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libcryptsetup.so.12");
+
r = dlopen_many_sym_or_warn(
&cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG,
DLSYM_ARG(crypt_activate_by_passphrase),
@@ -274,7 +283,9 @@ int dlopen_cryptsetup(void) {
DLSYM_ARG(crypt_keyslot_max),
DLSYM_ARG(crypt_load),
DLSYM_ARG(crypt_resize),
- DLSYM_ARG(crypt_resume_by_passphrase),
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+ DLSYM_ARG(crypt_resume_by_volume_key),
+#endif
DLSYM_ARG(crypt_set_data_device),
DLSYM_ARG(crypt_set_debug_level),
DLSYM_ARG(crypt_set_log_callback),
diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h
index 5ff439d..f00ac36 100644
--- a/src/shared/cryptsetup-util.h
+++ b/src/shared/cryptsetup-util.h
@@ -2,6 +2,7 @@
#pragma once
#include "alloc-util.h"
+#include "dlfcn-util.h"
#include "json.h"
#include "macro.h"
@@ -16,43 +17,45 @@
#define CRYPT_ACTIVATE_NO_WRITE_WORKQUEUE (1 << 25)
#endif
-extern int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags);
+DLSYM_PROTOTYPE(crypt_activate_by_passphrase);
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
-extern int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags);
+DLSYM_PROTOTYPE(crypt_activate_by_signed_key);
#endif
-extern int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags);
-extern int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags);
-extern int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params);
-extern void (*sym_crypt_free)(struct crypt_device *cd);
-extern const char *(*sym_crypt_get_cipher)(struct crypt_device *cd);
-extern const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd);
-extern uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd);
-extern const char *(*sym_crypt_get_device_name)(struct crypt_device *cd);
-extern const char *(*sym_crypt_get_dir)(void);
-extern const char *(*sym_crypt_get_type)(struct crypt_device *cd);
-extern const char *(*sym_crypt_get_uuid)(struct crypt_device *cd);
-extern int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp);
-extern int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd);
-extern int (*sym_crypt_init)(struct crypt_device **cd, const char *device);
-extern int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name);
-extern int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size);
-extern int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot);
-extern int (*sym_crypt_keyslot_max)(const char *type);
-extern int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params);
-extern int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size);
-extern int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size);
-extern int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device);
-extern void (*sym_crypt_set_debug_level)(int level);
-extern void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr);
+DLSYM_PROTOTYPE(crypt_activate_by_volume_key);
+DLSYM_PROTOTYPE(crypt_deactivate_by_name);
+DLSYM_PROTOTYPE(crypt_format);
+DLSYM_PROTOTYPE(crypt_free);
+DLSYM_PROTOTYPE(crypt_get_cipher);
+DLSYM_PROTOTYPE(crypt_get_cipher_mode);
+DLSYM_PROTOTYPE(crypt_get_data_offset);
+DLSYM_PROTOTYPE(crypt_get_device_name);
+DLSYM_PROTOTYPE(crypt_get_dir);
+DLSYM_PROTOTYPE(crypt_get_type);
+DLSYM_PROTOTYPE(crypt_get_uuid);
+DLSYM_PROTOTYPE(crypt_get_verity_info);
+DLSYM_PROTOTYPE(crypt_get_volume_key_size);
+DLSYM_PROTOTYPE(crypt_init);
+DLSYM_PROTOTYPE(crypt_init_by_name);
+DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key);
+DLSYM_PROTOTYPE(crypt_keyslot_destroy);
+DLSYM_PROTOTYPE(crypt_keyslot_max);
+DLSYM_PROTOTYPE(crypt_load);
+DLSYM_PROTOTYPE(crypt_resize);
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+DLSYM_PROTOTYPE(crypt_resume_by_volume_key);
+#endif
+DLSYM_PROTOTYPE(crypt_set_data_device);
+DLSYM_PROTOTYPE(crypt_set_debug_level);
+DLSYM_PROTOTYPE(crypt_set_log_callback);
#if HAVE_CRYPT_SET_METADATA_SIZE
-extern int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size);
+DLSYM_PROTOTYPE(crypt_set_metadata_size);
#endif
-extern int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf);
-extern int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name);
-extern int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json);
-extern int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json);
+DLSYM_PROTOTYPE(crypt_set_pbkdf_type);
+DLSYM_PROTOTYPE(crypt_suspend);
+DLSYM_PROTOTYPE(crypt_token_json_get);
+DLSYM_PROTOTYPE(crypt_token_json_set);
#if HAVE_CRYPT_TOKEN_MAX
-extern int (*sym_crypt_token_max)(const char *type);
+DLSYM_PROTOTYPE(crypt_token_max);
#else
/* As a fallback, use the same hard-coded value libcryptsetup uses internally. */
static inline int crypt_token_max(_unused_ const char *type) {
@@ -62,20 +65,22 @@ static inline int crypt_token_max(_unused_ const char *type) {
}
#define sym_crypt_token_max(type) crypt_token_max(type)
#endif
-extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type);
-extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
+DLSYM_PROTOTYPE(crypt_token_status);
+DLSYM_PROTOTYPE(crypt_volume_key_get);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
-extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params);
+DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase);
#endif
#if HAVE_CRYPT_REENCRYPT
-extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr));
+DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+DLSYM_PROTOTYPE(crypt_reencrypt);
+REENABLE_WARNING;
#endif
-extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable);
+DLSYM_PROTOTYPE(crypt_metadata_locking);
#if HAVE_CRYPT_SET_DATA_OFFSET
-extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset);
+DLSYM_PROTOTYPE(crypt_set_data_offset);
#endif
-extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file);
-extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable);
+DLSYM_PROTOTYPE(crypt_header_restore);
+DLSYM_PROTOTYPE(crypt_volume_key_keyring);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL);
diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c
index 32180a1..aa386b4 100644
--- a/src/shared/daemon-util.c
+++ b/src/shared/daemon-util.c
@@ -4,6 +4,7 @@
#include "fd-util.h"
#include "log.h"
#include "string-util.h"
+#include "time-util.h"
static int notify_remove_fd_warn(const char *name) {
int r;
@@ -74,3 +75,18 @@ int notify_push_fdf(int fd, const char *format, ...) {
return notify_push_fd(fd, name);
}
+
+int notify_reloading_full(const char *status) {
+ int r;
+
+ r = sd_notifyf(/* unset_environment = */ false,
+ "RELOADING=1\n"
+ "MONOTONIC_USEC=" USEC_FMT
+ "%s%s",
+ now(CLOCK_MONOTONIC),
+ status ? "\nSTATUS=" : "", strempty(status));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to notify service manager for reloading status: %m");
+
+ return 0;
+}
diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h
index 711885b..cbefa8d 100644
--- a/src/shared/daemon-util.h
+++ b/src/shared/daemon-util.h
@@ -10,7 +10,7 @@
#define NOTIFY_READY "READY=1\n" "STATUS=Processing requests..."
#define NOTIFY_STOPPING "STOPPING=1\n" "STATUS=Shutting down..."
-static inline const char *notify_start(const char *start, const char *stop) {
+static inline const char* notify_start(const char *start, const char *stop) {
if (start)
(void) sd_notify(false, start);
@@ -26,3 +26,8 @@ static inline void notify_on_cleanup(const char **p) {
int notify_remove_fd_warnf(const char *format, ...) _printf_(1, 2);
int close_and_notify_warn(int fd, const char *name);
int notify_push_fdf(int fd, const char *format, ...) _printf_(2, 3);
+
+int notify_reloading_full(const char *status);
+static inline int notify_reloading(void) {
+ return notify_reloading_full("Reloading configuration...");
+}
diff --git a/src/shared/data-fd-util.c b/src/shared/data-fd-util.c
index b939206..adc7d74 100644
--- a/src/shared/data-fd-util.c
+++ b/src/shared/data-fd-util.c
@@ -20,16 +20,15 @@
#include "tmpfile-util.h"
/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
-#define DATA_FD_MEMORY_LIMIT (64U*1024U)
+#define DATA_FD_MEMORY_LIMIT (64U * U64_KB)
/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
-#define DATA_FD_TMP_LIMIT (1024U*1024U)
+#define DATA_FD_TMP_LIMIT (1U * U64_MB)
-int acquire_data_fd(const void *data, size_t size, unsigned flags) {
- _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR;
+int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags) {
_cleanup_close_ int fd = -EBADF;
- int isz = 0, r;
ssize_t n;
+ int r;
assert(data || size == 0);
@@ -52,24 +51,25 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags) {
* It sucks a bit that depending on the situation we return very different objects here, but that's Linux I
* figure. */
- if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0))
+ if (size == SIZE_MAX)
+ size = strlen(data);
+
+ if (size == 0 && !FLAGS_SET(flags, ACQUIRE_NO_DEV_NULL))
/* As a special case, return /dev/null if we have been called for an empty data block */
return RET_NERRNO(open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY));
- if ((flags & ACQUIRE_NO_MEMFD) == 0) {
+ if (!FLAGS_SET(flags, ACQUIRE_NO_MEMFD)) {
fd = memfd_new_and_seal("data-fd", data, size);
- if (fd < 0) {
- if (ERRNO_IS_NOT_SUPPORTED(fd))
- goto try_pipe;
-
+ if (fd < 0 && !ERRNO_IS_NOT_SUPPORTED(fd))
return fd;
- }
-
- return TAKE_FD(fd);
+ if (fd >= 0)
+ return TAKE_FD(fd);
}
-try_pipe:
- if ((flags & ACQUIRE_NO_PIPE) == 0) {
+ if (!FLAGS_SET(flags, ACQUIRE_NO_PIPE)) {
+ _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR;
+ int isz;
+
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
return -errno;
@@ -106,7 +106,7 @@ try_pipe:
}
try_dev_shm:
- if ((flags & ACQUIRE_NO_TMPFILE) == 0) {
+ if (!FLAGS_SET(flags, ACQUIRE_NO_TMPFILE)) {
fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500);
if (fd < 0)
goto try_dev_shm_without_o_tmpfile;
@@ -122,7 +122,7 @@ try_dev_shm:
}
try_dev_shm_without_o_tmpfile:
- if ((flags & ACQUIRE_NO_REGULAR) == 0) {
+ if (!FLAGS_SET(flags, ACQUIRE_NO_REGULAR)) {
char pattern[] = "/dev/shm/data-fd-XXXXXX";
fd = mkostemp_safe(pattern);
@@ -179,7 +179,7 @@ int copy_data_fd(int fd) {
* that we use the reported regular file size only as a hint, given that there are plenty special files in
* /proc and /sys which report a zero file size but can be read from. */
- if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
+ if (!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_MEMORY_LIMIT) {
/* Try a memfd first */
copy_fd = memfd_new("data-fd");
@@ -252,7 +252,7 @@ int copy_data_fd(int fd) {
}
/* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
- if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
+ if ((!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_TMP_LIMIT) &&
(DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
off_t f;
diff --git a/src/shared/data-fd-util.h b/src/shared/data-fd-util.h
index 6d99209..d77e09f 100644
--- a/src/shared/data-fd-util.h
+++ b/src/shared/data-fd-util.h
@@ -2,15 +2,20 @@
#pragma once
#include <stddef.h>
+#include <stdint.h>
-enum {
+typedef enum DataFDFlags {
ACQUIRE_NO_DEV_NULL = 1 << 0,
ACQUIRE_NO_MEMFD = 1 << 1,
ACQUIRE_NO_PIPE = 1 << 2,
ACQUIRE_NO_TMPFILE = 1 << 3,
ACQUIRE_NO_REGULAR = 1 << 4,
-};
+} DataFDFlags;
+
+int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags);
+static inline int acquire_data_fd(const void *data) {
+ return acquire_data_fd_full(data, SIZE_MAX, 0);
+}
-int acquire_data_fd(const void *data, size_t size, unsigned flags);
int copy_data_fd(int fd);
int memfd_clone_fd(int fd, const char *name, int mode);
diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c
index f7ed161..4b4b625 100644
--- a/src/shared/dev-setup.c
+++ b/src/shared/dev-setup.c
@@ -7,6 +7,7 @@
#include "alloc-util.h"
#include "dev-setup.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "label-util.h"
#include "lock-util.h"
#include "log.h"
@@ -21,13 +22,15 @@ int lock_dev_console(void) {
_cleanup_close_ int fd = -EBADF;
int r;
- fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+ /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
+ * pty in /dev/console, in which case it should be fine to lock the target TTY. */
+ fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return fd;
r = lock_generic(fd, LOCK_BSD, LOCK_EX);
if (r < 0)
- return log_error_errno(r, "Failed to lock /dev/console: %m");
+ return r;
return TAKE_FD(fd);
}
@@ -79,15 +82,11 @@ int make_inaccessible_nodes(
uid_t uid,
gid_t gid) {
- static const struct {
- const char *name;
- mode_t mode;
- } table[] = {
- { "inaccessible", S_IFDIR | 0755 },
- { "inaccessible/reg", S_IFREG | 0000 },
- { "inaccessible/dir", S_IFDIR | 0000 },
- { "inaccessible/fifo", S_IFIFO | 0000 },
- { "inaccessible/sock", S_IFSOCK | 0000 },
+ static const mode_t table[] = {
+ S_IFREG,
+ S_IFDIR,
+ S_IFIFO,
+ S_IFSOCK,
/* The following two are likely to fail if we lack the privs for it (for example in an userns
* environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibits creation of
@@ -95,10 +94,13 @@ int make_inaccessible_nodes(
* should implement falling back to use a different node then, for example
* <root>/inaccessible/sock, which is close enough in behaviour and semantics for most uses.
*/
- { "inaccessible/chr", S_IFCHR | 0000 },
- { "inaccessible/blk", S_IFBLK | 0000 },
+ S_IFCHR,
+ S_IFBLK,
+
+ /* NB: S_IFLNK is not listed here, as there is no such thing as an inaccessible symlink */
};
+ _cleanup_close_ int parent_fd = -EBADF, inaccessible_fd = -EBADF;
int r;
if (!parent_dir)
@@ -106,32 +108,48 @@ int make_inaccessible_nodes(
BLOCK_WITH_UMASK(0000);
+ parent_fd = open(parent_dir, O_DIRECTORY|O_CLOEXEC|O_PATH, 0);
+ if (parent_fd < 0)
+ return -errno;
+
+ inaccessible_fd = open_mkdir_at_full(parent_fd, "inaccessible", O_CLOEXEC, XO_LABEL, 0755);
+ if (inaccessible_fd < 0)
+ return inaccessible_fd;
+
/* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting
* ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try
* to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the
* underlying file, i.e. in the best case we offer the same node type as the underlying node. */
- for (size_t i = 0; i < ELEMENTSOF(table); i++) {
+ FOREACH_ELEMENT(m, table) {
_cleanup_free_ char *path = NULL;
+ mode_t inode_type = *m;
+ const char *fn;
- path = path_join(parent_dir, table[i].name);
+ fn = inode_type_to_string(inode_type);
+ path = path_join(parent_dir, fn);
if (!path)
return log_oom();
- if (S_ISDIR(table[i].mode))
- r = mkdir_label(path, table[i].mode & 07777);
+ if (S_ISDIR(inode_type))
+ r = mkdirat_label(inaccessible_fd, fn, 0000);
else
- r = mknod_label(path, table[i].mode, makedev(0, 0));
- if (r < 0) {
+ r = mknodat_label(inaccessible_fd, fn, inode_type | 0000, makedev(0, 0));
+ if (r == -EEXIST) {
+ if (fchmodat(inaccessible_fd, fn, 0000, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to adjust access mode of existing inode '%s', ignoring: %m", path);
+ } else if (r < 0) {
log_debug_errno(r, "Failed to create '%s', ignoring: %m", path);
continue;
}
- if (uid != UID_INVALID || gid != GID_INVALID) {
- if (lchown(path, uid, gid) < 0)
- log_debug_errno(errno, "Failed to chown '%s': %m", path);
- }
+ if (uid_is_valid(uid) || gid_is_valid(gid))
+ if (fchownat(inaccessible_fd, fn, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
+ log_debug_errno(errno, "Failed to chown '%s', ignoring: %m", path);
}
+ if (fchmod(inaccessible_fd, 0555) < 0)
+ log_debug_errno(errno, "Failed to mark inaccessible directory read-only, ignoring: %m");
+
return 0;
}
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index e8f4dfb..1079d28 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -13,6 +13,7 @@
#include <unistd.h>
#include "alloc-util.h"
+#include "blockdev-util.h"
#include "btrfs-util.h"
#include "chase.h"
#include "chattr-util.h"
@@ -44,9 +45,10 @@
#include "strv.h"
#include "time-util.h"
#include "utf8.h"
+#include "vpick.h"
#include "xattr-util.h"
-static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
+const char* const image_search_path[_IMAGE_CLASS_MAX] = {
[IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */
"/run/machines\0" /* and here too */
"/var/lib/machines\0" /* the main place for images */
@@ -74,15 +76,20 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
"/usr/lib/confexts\0",
};
-/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
- * search dir) */
+/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext/ and
+ * .extra/confext/ in extension search dir) */
static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
/* (entries that aren't listed here will get the same search path as for the non initrd-case) */
[IMAGE_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */
"/run/extensions\0" /* and here too */
"/var/lib/extensions\0" /* the main place for images */
- "/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */
+ "/.extra/sysext\0", /* put sysext picked up by systemd-stub last, since not trusted */
+
+ [IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */
+ "/var/lib/confexts\0" /* the main place for images */
+ "/usr/local/lib/confexts\0"
+ "/.extra/confext\0", /* put confext picked up by systemd-stub last, since not trusted */
};
static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = {
@@ -92,6 +99,15 @@ static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_class_suffix, ImageClass);
+static const char *const image_root_table[_IMAGE_CLASS_MAX] = {
+ [IMAGE_MACHINE] = "/var/lib/machines",
+ [IMAGE_PORTABLE] = "/var/lib/portables",
+ [IMAGE_SYSEXT] = "/var/lib/extensions",
+ [IMAGE_CONFEXT] = "/var/lib/confexts",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass);
+
static Image *image_free(Image *i) {
assert(i);
@@ -209,43 +225,101 @@ static int image_new(
return 0;
}
-static int extract_pretty(
+static int extract_image_basename(
const char *path,
- const char *class_suffix,
- const char *format_suffix,
- char **ret) {
+ const char *class_suffix, /* e.g. ".sysext" (this is an optional suffix) */
+ char **format_suffixes, /* e.g. ".raw" (one of these will be required) */
+ char **ret_basename,
+ char **ret_suffix) {
- _cleanup_free_ char *name = NULL;
+ _cleanup_free_ char *name = NULL, *suffix = NULL;
int r;
assert(path);
- assert(ret);
r = path_extract_filename(path, &name);
if (r < 0)
return r;
- if (format_suffix) {
- char *e = endswith(name, format_suffix);
+ if (format_suffixes) {
+ char *e = endswith_strv(name, format_suffixes);
if (!e) /* Format suffix is required */
return -EINVAL;
+ if (ret_suffix) {
+ suffix = strdup(e);
+ if (!suffix)
+ return -ENOMEM;
+ }
+
*e = 0;
}
if (class_suffix) {
char *e = endswith(name, class_suffix);
- if (e) /* Class suffix is optional */
+ if (e) { /* Class suffix is optional */
+ if (ret_suffix) {
+ _cleanup_free_ char *j = strjoin(e, suffix);
+ if (!j)
+ return -ENOMEM;
+
+ free_and_replace(suffix, j);
+ }
+
*e = 0;
+ }
}
if (!image_name_is_valid(name))
return -EINVAL;
- *ret = TAKE_PTR(name);
+ if (ret_suffix)
+ *ret_suffix = TAKE_PTR(suffix);
+
+ if (ret_basename)
+ *ret_basename = TAKE_PTR(name);
+
return 0;
}
+static int image_update_quota(Image *i, int fd) {
+ _cleanup_close_ int fd_close = -EBADF;
+ int r;
+
+ assert(i);
+
+ if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
+ return -EROFS;
+
+ if (i->type != IMAGE_SUBVOLUME)
+ return -EOPNOTSUPP;
+
+ if (fd < 0) {
+ fd_close = open(i->path, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+ if (fd_close < 0)
+ return -errno;
+ fd = fd_close;
+ }
+
+ r = btrfs_quota_scan_ongoing(fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0;
+
+ BtrfsQuotaInfo quota;
+ r = btrfs_subvol_get_subtree_quota_fd(fd, 0, &quota);
+ if (r < 0)
+ return r;
+
+ i->usage = quota.referenced;
+ i->usage_exclusive = quota.exclusive;
+ i->limit = quota.referenced_max;
+ i->limit_exclusive = quota.exclusive_max;
+
+ return 1;
+}
+
static int image_make(
ImageClass c,
const char *pretty,
@@ -297,7 +371,12 @@ static int image_make(
return 0;
if (!pretty) {
- r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ image_class_suffix_to_string(c),
+ /* format_suffix= */ NULL,
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -334,19 +413,7 @@ static int image_make(
if (r < 0)
return r;
- if (btrfs_quota_scan_ongoing(fd) == 0) {
- BtrfsQuotaInfo quota;
-
- r = btrfs_subvol_get_subtree_quota_fd(fd, 0, &quota);
- if (r >= 0) {
- (*ret)->usage = quota.referenced;
- (*ret)->usage_exclusive = quota.exclusive;
-
- (*ret)->limit = quota.referenced_max;
- (*ret)->limit_exclusive = quota.exclusive_max;
- }
- }
-
+ (void) image_update_quota(*ret, fd);
return 0;
}
}
@@ -384,7 +451,12 @@ static int image_make(
(void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime);
if (!pretty) {
- r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ image_class_suffix_to_string(c),
+ STRV_MAKE(".raw"),
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -418,7 +490,12 @@ static int image_make(
return 0;
if (!pretty) {
- r = extract_pretty(filename, NULL, NULL, &pretty_buffer);
+ r = extract_image_basename(
+ filename,
+ /* class_suffix= */ NULL,
+ /* format_suffix= */ NULL,
+ &pretty_buffer,
+ /* ret_suffix= */ NULL);
if (r < 0)
return r;
@@ -446,8 +523,9 @@ static int image_make(
read_only = true;
}
- if (ioctl(block_fd, BLKGETSIZE64, &size) < 0)
- log_debug_errno(errno, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename);
+ r = blockdev_get_device_size(block_fd, &size);
+ if (r < 0)
+ log_debug_errno(r, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename);
block_fd = safe_close(block_fd);
}
@@ -481,6 +559,37 @@ static const char *pick_image_search_path(ImageClass class) {
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
}
+static char **make_possible_filenames(ImageClass class, const char *image_name) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ assert(image_name);
+
+ FOREACH_STRING(v_suffix, "", ".v")
+ FOREACH_STRING(format_suffix, "", ".raw") {
+ _cleanup_free_ char *j = NULL;
+ const char *class_suffix;
+
+ class_suffix = image_class_suffix_to_string(class);
+ if (class_suffix) {
+ j = strjoin(image_name, class_suffix, format_suffix, v_suffix);
+ if (!j)
+ return NULL;
+
+ if (strv_consume(&l, TAKE_PTR(j)) < 0)
+ return NULL;
+ }
+
+ j = strjoin(image_name, format_suffix, v_suffix);
+ if (!j)
+ return NULL;
+
+ if (strv_consume(&l, TAKE_PTR(j)) < 0)
+ return NULL;
+ }
+
+ return TAKE_PTR(l);
+}
+
int image_find(ImageClass class,
const char *name,
const char *root,
@@ -496,6 +605,10 @@ int image_find(ImageClass class,
if (!image_name_is_valid(name))
return -ENOENT;
+ _cleanup_strv_free_ char **names = make_possible_filenames(class, name);
+ if (!names)
+ return -ENOMEM;
+
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
@@ -512,43 +625,97 @@ int image_find(ImageClass class,
* to symlink block devices into the search path. (For now, we disable that when operating
* relative to some root directory.) */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
- if (fstatat(dirfd(d), name, &st, flags) < 0) {
- _cleanup_free_ char *raw = NULL;
- if (errno != ENOENT)
- return -errno;
+ STRV_FOREACH(n, names) {
+ _cleanup_free_ char *fname_buf = NULL;
+ const char *fname = *n;
- raw = strjoin(name, ".raw");
- if (!raw)
- return -ENOMEM;
+ if (fstatat(dirfd(d), fname, &st, flags) < 0) {
+ if (errno != ENOENT)
+ return -errno;
- if (fstatat(dirfd(d), raw, &st, flags) < 0) {
- if (errno == ENOENT)
+ continue; /* Vanished while we were looking at it */
+ }
+
+ if (endswith(fname, ".raw")) {
+ if (!S_ISREG(st.st_mode)) {
+ log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname);
continue;
+ }
- return -errno;
- }
+ } else if (endswith(fname, ".v")) {
- if (!S_ISREG(st.st_mode))
- continue;
+ if (!S_ISDIR(st.st_mode)) {
+ log_debug("Ignoring non-directory file '%s' with .v suffix.", fname);
+ continue;
+ }
- r = image_make(class, name, dirfd(d), resolved, raw, &st, ret);
+ _cleanup_free_ char *suffix = NULL;
+ suffix = strdup(ASSERT_PTR(startswith(fname, name)));
+ if (!suffix)
+ return -ENOMEM;
+
+ *ASSERT_PTR(endswith(suffix, ".v")) = 0;
+
+ _cleanup_free_ char *vp = path_join(resolved, fname);
+ if (!vp)
+ return -ENOMEM;
+
+ PickFilter filter = {
+ .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
+ .basename = name,
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = STRV_MAKE(suffix),
+ };
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ r = path_pick(root,
+ /* toplevel_fd= */ AT_FDCWD,
+ vp,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ continue;
+ }
+ if (!result.path) {
+ log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ continue;
+ }
+
+ /* Refresh the stat data for the discovered target */
+ st = result.st;
+
+ _cleanup_free_ char *bn = NULL;
+ r = path_extract_filename(result.path, &bn);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+ continue;
+ }
+
+ fname_buf = path_join(fname, bn);
+ if (!fname_buf)
+ return log_oom();
- } else {
- if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
+ fname = fname_buf;
+
+ } else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+ log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname);
continue;
+ }
- r = image_make(class, name, dirfd(d), resolved, name, &st, ret);
- }
- if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
- continue;
- if (r < 0)
- return r;
+ r = image_make(class, name, dirfd(d), resolved, fname, &st, ret);
+ if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
+ continue;
+ if (r < 0)
+ return r;
- if (ret)
- (*ret)->discoverable = true;
+ if (ret)
+ (*ret)->discoverable = true;
- return 1;
+ return 1;
+ }
}
if (class == IMAGE_MACHINE && streq(name, ".host")) {
@@ -559,7 +726,7 @@ int image_find(ImageClass class,
if (ret)
(*ret)->discoverable = true;
- return r;
+ return 1;
}
return -ENOENT;
@@ -606,43 +773,133 @@ int image_discover(
return r;
FOREACH_DIRENT_ALL(de, d, return -errno) {
+ _cleanup_free_ char *pretty = NULL, *fname_buf = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
- _cleanup_free_ char *pretty = NULL;
+ const char *fname = de->d_name;
struct stat st;
int flags;
- if (dot_or_dot_dot(de->d_name))
+ if (dot_or_dot_dot(fname))
continue;
/* As mentioned above, we follow symlinks on this fstatat(), because we want to
* permit people to symlink block devices into the search path. */
flags = root ? AT_SYMLINK_NOFOLLOW : 0;
- if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) {
+ if (fstatat(dirfd(d), fname, &st, flags) < 0) {
if (errno == ENOENT)
continue;
return -errno;
}
- if (S_ISREG(st.st_mode))
- r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty);
- else if (S_ISDIR(st.st_mode))
- r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty);
- else if (S_ISBLK(st.st_mode))
- r = extract_pretty(de->d_name, NULL, NULL, &pretty);
- else {
- log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name);
- continue;
- }
- if (r < 0) {
- log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name);
+ if (S_ISREG(st.st_mode)) {
+ r = extract_image_basename(
+ fname,
+ image_class_suffix_to_string(class),
+ STRV_MAKE(".raw"),
+ &pretty,
+ /* suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ const char *v;
+
+ v = endswith(fname, ".v");
+ if (v) {
+ _cleanup_free_ char *suffix = NULL, *nov = NULL;
+
+ nov = strndup(fname, v - fname); /* Chop off the .v */
+ if (!nov)
+ return -ENOMEM;
+
+ r = extract_image_basename(
+ nov,
+ image_class_suffix_to_string(class),
+ STRV_MAKE(".raw", ""),
+ &pretty,
+ &suffix);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname);
+ continue;
+ }
+
+ _cleanup_free_ char *vp = path_join(resolved, fname);
+ if (!vp)
+ return -ENOMEM;
+
+ PickFilter filter = {
+ .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
+ .basename = pretty,
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = STRV_MAKE(suffix),
+ };
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ r = path_pick(root,
+ /* toplevel_fd= */ AT_FDCWD,
+ vp,
+ &filter,
+ PICK_ARCHITECTURE|PICK_TRIES,
+ &result);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ continue;
+ }
+ if (!result.path) {
+ log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ continue;
+ }
+
+ /* Refresh the stat data for the discovered target */
+ st = result.st;
+
+ _cleanup_free_ char *bn = NULL;
+ r = path_extract_filename(result.path, &bn);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path);
+ continue;
+ }
+
+ fname_buf = path_join(fname, bn);
+ if (!fname_buf)
+ return log_oom();
+
+ fname = fname_buf;
+ } else {
+ r = extract_image_basename(
+ fname,
+ image_class_suffix_to_string(class),
+ /* format_suffix= */ NULL,
+ &pretty,
+ /* ret_suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ }
+
+ } else if (S_ISBLK(st.st_mode)) {
+ r = extract_image_basename(
+ fname,
+ /* class_suffix= */ NULL,
+ /* format_suffix= */ NULL,
+ &pretty,
+ /* ret_v_suffix= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname);
+ continue;
+ }
+ } else {
+ log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname);
continue;
}
if (hashmap_contains(h, pretty))
continue;
- r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image);
+ r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
@@ -1056,6 +1313,7 @@ int image_read_only(Image *i, bool b) {
return -EOPNOTSUPP;
}
+ i->read_only = b;
return 0;
}
@@ -1064,7 +1322,12 @@ static void make_lock_dir(void) {
(void) mkdir("/run/systemd/nspawn/locks", 0700);
}
-int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) {
+int image_path_lock(
+ const char *path,
+ int operation,
+ LockFile *ret_global,
+ LockFile *ret_local) {
+
_cleanup_free_ char *p = NULL;
LockFile t = LOCK_FILE_INIT;
struct stat st;
@@ -1072,8 +1335,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
int r;
assert(path);
- assert(global);
- assert(local);
+ assert(ret_local);
/* Locks an image path. This actually creates two locks: one "local" one, next to the image path
* itself, which might be shared via NFS. And another "global" one, in /run, that uses the
@@ -1095,7 +1357,9 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
}
if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) {
- *local = *global = (LockFile) LOCK_FILE_INIT;
+ *ret_local = LOCK_FILE_INIT;
+ if (ret_global)
+ *ret_global = LOCK_FILE_INIT;
return 0;
}
@@ -1111,19 +1375,23 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
if (exclusive)
return -EBUSY;
- *local = *global = (LockFile) LOCK_FILE_INIT;
+ *ret_local = LOCK_FILE_INIT;
+ if (ret_global)
+ *ret_global = LOCK_FILE_INIT;
return 0;
}
- if (stat(path, &st) >= 0) {
- if (S_ISBLK(st.st_mode))
- r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev));
- else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))
- r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino);
- else
- return -ENOTTY;
- if (r < 0)
- return -ENOMEM;
+ if (ret_global) {
+ if (stat(path, &st) >= 0) {
+ if (S_ISBLK(st.st_mode))
+ r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev));
+ else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))
+ r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino);
+ else
+ return -ENOTTY;
+ if (r < 0)
+ return -ENOMEM;
+ }
}
/* For block devices we don't need the "local" lock, as the major/minor lock above should be
@@ -1141,19 +1409,21 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
if (p) {
make_lock_dir();
- r = make_lock_file(p, operation, global);
+ r = make_lock_file(p, operation, ret_global);
if (r < 0) {
release_lock_file(&t);
return r;
}
- } else
- *global = (LockFile) LOCK_FILE_INIT;
+ } else if (ret_global)
+ *ret_global = LOCK_FILE_INIT;
- *local = t;
+ *ret_local = t;
return 0;
}
int image_set_limit(Image *i, uint64_t referenced_max) {
+ int r;
+
assert(i);
if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
@@ -1169,7 +1439,12 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
(void) btrfs_qgroup_set_limit(i->path, 0, referenced_max);
(void) btrfs_subvol_auto_qgroup(i->path, 0, true);
- return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
+ r = btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
+ if (r < 0)
+ return r;
+
+ (void) image_update_quota(i, -EBADF);
+ return 0;
}
int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
@@ -1206,7 +1481,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
else if (r >= 0) {
r = read_etc_hostname(path, &hostname);
if (r < 0)
- log_debug_errno(errno, "Failed to read /etc/hostname of image %s: %m", i->name);
+ log_debug_errno(r, "Failed to read /etc/hostname of image %s: %m", i->name);
}
path = mfree(path);
@@ -1249,8 +1524,25 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
case IMAGE_BLOCK: {
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
-
- r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
+ DissectImageFlags flags =
+ DISSECT_IMAGE_GENERIC_ROOT |
+ DISSECT_IMAGE_REQUIRE_ROOT |
+ DISSECT_IMAGE_RELAX_VAR_CHECK |
+ DISSECT_IMAGE_READ_ONLY |
+ DISSECT_IMAGE_USR_NO_ROOT |
+ DISSECT_IMAGE_ADD_PARTITION_DEVICES |
+ DISSECT_IMAGE_PIN_PARTITION_DEVICES |
+ DISSECT_IMAGE_VALIDATE_OS |
+ DISSECT_IMAGE_VALIDATE_OS_EXT |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
+
+ r = loop_device_make_by_path(
+ i->path,
+ O_RDONLY,
+ /* sector_size= */ UINT32_MAX,
+ LO_FLAGS_PARTSCAN,
+ LOCK_SH,
+ &d);
if (r < 0)
return r;
@@ -1259,20 +1551,15 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
/* verity= */ NULL,
/* mount_options= */ NULL,
image_policy,
- DISSECT_IMAGE_GENERIC_ROOT |
- DISSECT_IMAGE_REQUIRE_ROOT |
- DISSECT_IMAGE_RELAX_VAR_CHECK |
- DISSECT_IMAGE_READ_ONLY |
- DISSECT_IMAGE_USR_NO_ROOT |
- DISSECT_IMAGE_ADD_PARTITION_DEVICES |
- DISSECT_IMAGE_PIN_PARTITION_DEVICES,
+ flags,
&m);
if (r < 0)
return r;
- r = dissected_image_acquire_metadata(m,
- DISSECT_IMAGE_VALIDATE_OS |
- DISSECT_IMAGE_VALIDATE_OS_EXT);
+ r = dissected_image_acquire_metadata(
+ m,
+ /* userns_fd= */ -EBADF,
+ flags);
if (r < 0)
return r;
diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h
index a30a3d9..6491cec 100644
--- a/src/shared/discover-image.h
+++ b/src/shared/discover-image.h
@@ -119,4 +119,8 @@ static inline bool IMAGE_IS_HOST(const struct Image *i) {
int image_to_json(const struct Image *i, JsonVariant **ret);
+const char *image_root_to_string(ImageClass c) _const_;
+
extern const struct hash_ops image_hash_ops;
+
+extern const char* const image_search_path[_IMAGE_CLASS_MAX];
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index 84cfbcd..a9e211f 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -32,6 +32,7 @@
#include "copy.h"
#include "cryptsetup-util.h"
#include "device-nodes.h"
+#include "device-private.h"
#include "device-util.h"
#include "devnum-util.h"
#include "discover-image.h"
@@ -60,6 +61,7 @@
#include "openssl-util.h"
#include "os-util.h"
#include "path-util.h"
+#include "proc-cmdline.h"
#include "process-util.h"
#include "raw-clone.h"
#include "resize-fs.h"
@@ -73,6 +75,7 @@
#include "tmpfile-util.h"
#include "udev-util.h"
#include "user-util.h"
+#include "varlink.h"
#include "xattr-util.h"
/* how many times to wait for the device nodes to appear */
@@ -267,16 +270,8 @@ int probe_filesystem_full(
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
if (fstype) {
- char *t;
-
log_debug("Probed fstype '%s' on partition %s.", fstype, path);
-
- t = strdup(fstype);
- if (!t)
- return -ENOMEM;
-
- *ret_fstype = t;
- return 1;
+ return strdup_to_full(ret_fstype, fstype);
}
not_found:
@@ -522,6 +517,38 @@ static void dissected_partition_done(DissectedPartition *p) {
}
#if HAVE_BLKID
+static int diskseq_should_be_used(
+ const char *whole_devname,
+ uint64_t diskseq,
+ DissectImageFlags flags) {
+
+ int r;
+
+ assert(whole_devname);
+
+ /* No diskseq. We cannot use by-diskseq symlink. */
+ if (diskseq == 0)
+ return false;
+
+ /* Do not use by-diskseq link unless DISSECT_IMAGE_DISKSEQ_DEVNODE flag is explicitly set. */
+ if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE))
+ return false;
+
+ _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+ r = sd_device_new_from_devname(&dev, whole_devname);
+ if (r < 0)
+ return r;
+
+ /* When ID_IGNORE_DISKSEQ udev property is set, the by-diskseq symlink will not be created. */
+ r = device_get_property_bool(dev, "ID_IGNORE_DISKSEQ");
+ if (r >= 0)
+ return !r; /* If explicitly specified, use it. */
+ if (r != -ENOENT)
+ return r;
+
+ return true;
+}
+
static int make_partition_devname(
const char *whole_devname,
uint64_t diskseq,
@@ -536,8 +563,10 @@ static int make_partition_devname(
assert(nr != 0); /* zero is not a valid partition nr */
assert(ret);
- if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE) || diskseq == 0) {
-
+ r = diskseq_should_be_used(whole_devname, diskseq, flags);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine if diskseq should be used for %s, assuming no, ignoring: %m", whole_devname);
+ if (r <= 0) {
/* Given a whole block device node name (e.g. /dev/sda or /dev/loop7) generate a partition
* device name (e.g. /dev/sda7 or /dev/loop7p5). The rule the kernel uses is simple: if whole
* block device node name ends in a digit, then suffix a 'p', followed by the partition
@@ -799,7 +828,7 @@ static int dissect_image(
if (suuid) {
/* blkid will return FAT's serial number as UUID, hence it is quite possible
* that parsing this will fail. We'll ignore the ID, since it's just too
- * short to be useful as tru identifier. */
+ * short to be useful as true identifier. */
r = sd_id128_from_string(suuid, &uuid);
if (r < 0)
log_debug_errno(r, "Failed to parse file system UUID '%s', ignoring: %m", suuid);
@@ -1547,6 +1576,7 @@ int dissect_image_file(
#if HAVE_BLKID
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_close_ int fd = -EBADF;
+ struct stat st;
int r;
assert(path);
@@ -1555,7 +1585,10 @@ int dissect_image_file(
if (fd < 0)
return -errno;
- r = fd_verify_regular(fd);
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ r = stat_verify_regular(&st);
if (r < 0)
return r;
@@ -1563,6 +1596,8 @@ int dissect_image_file(
if (r < 0)
return r;
+ m->image_size = st.st_size;
+
r = probe_sector_size(fd, &m->sector_size);
if (r < 0)
return r;
@@ -1644,6 +1679,20 @@ int dissect_image_file_and_warn(
verity);
}
+void dissected_image_close(DissectedImage *m) {
+ if (!m)
+ return;
+
+ /* Closes all fds we keep open associated with this, but nothing else */
+
+ FOREACH_ARRAY(p, m->partitions, _PARTITION_DESIGNATOR_MAX) {
+ p->mount_node_fd = safe_close(p->mount_node_fd);
+ p->fsmount_fd = safe_close(p->fsmount_fd);
+ }
+
+ m->loop = loop_device_unref(m->loop);
+}
+
DissectedImage* dissected_image_unref(DissectedImage *m) {
if (!m)
return NULL;
@@ -1759,8 +1808,9 @@ static int fs_grow(const char *node_path, int mount_fd, const char *mount_path)
if (node_fd < 0)
return log_debug_errno(errno, "Failed to open node device %s: %m", node_path);
- if (ioctl(node_fd, BLKGETSIZE64, &size) != 0)
- return log_debug_errno(errno, "Failed to get block device size of %s: %m", node_path);
+ r = blockdev_get_device_size(node_fd, &size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to get block device size of %s: %m", node_path);
if (mount_fd < 0) {
assert(mount_path);
@@ -1861,9 +1911,12 @@ int partition_pick_mount_options(
* access that actually modifies stuff work on such image files. Or to say this differently: if
* people want their file systems to be fixed up they should just open them in writable mode, where
* all these problems don't exist. */
- if (!rw && fstype && fstype_can_norecovery(fstype))
- if (!strextend_with_separator(&options, ",", "norecovery"))
+ if (!rw && fstype) {
+ const char *option = fstype_norecovery_option(fstype);
+
+ if (option && !strextend_with_separator(&options, ",", option))
return -ENOMEM;
+ }
if (discard && fstype && fstype_can_discard(fstype))
if (!strextend_with_separator(&options, ",", "discard"))
@@ -1958,7 +2011,7 @@ static int mount_partition(
if (where) {
if (directory) {
/* Automatically create missing mount points inside the image, if necessary. */
- r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL);
+ r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755);
if (r < 0 && r != -EROFS)
return r;
@@ -2113,7 +2166,7 @@ int dissected_image_mount(
* If 'where' is not NULL then we'll either mount the partitions to the right places ourselves,
* or use DissectedPartition.fsmount_fd and bind it to the right places.
*
- * This allows splitting the setting up up the superblocks and the binding to file systems paths into
+ * This allows splitting the setting up the superblocks and the binding to file systems paths into
* two distinct and differently privileged components: one that gets the fsmount fds, and the other
* that then applies them.
*
@@ -2137,7 +2190,7 @@ int dissected_image_mount(
if (userns_fd < 0 && need_user_mapping(uid_shift, uid_range) && FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) {
- my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
+ my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT);
if (my_userns_fd < 0)
return my_userns_fd;
@@ -2278,19 +2331,19 @@ int dissected_image_mount_and_warn(
r = dissected_image_mount(m, where, uid_shift, uid_range, userns_fd, flags);
if (r == -ENXIO)
- return log_error_errno(r, "Not root file system found in image.");
+ return log_error_errno(r, "Failed to mount image: No root file system found in image.");
if (r == -EMEDIUMTYPE)
- return log_error_errno(r, "No suitable os-release/extension-release file in image found.");
+ return log_error_errno(r, "Failed to mount image: No suitable os-release/extension-release file in image found.");
if (r == -EUNATCH)
- return log_error_errno(r, "Encrypted file system discovered, but decryption not requested.");
+ return log_error_errno(r, "Failed to mount image: Encrypted file system discovered, but decryption not requested.");
if (r == -EUCLEAN)
- return log_error_errno(r, "File system check on image failed.");
+ return log_error_errno(r, "Failed to mount image: File system check on image failed.");
if (r == -EBUSY)
- return log_error_errno(r, "File system already mounted elsewhere.");
+ return log_error_errno(r, "Failed to mount image: File system already mounted elsewhere.");
if (r == -EAFNOSUPPORT)
- return log_error_errno(r, "File system type not supported or not known.");
+ return log_error_errno(r, "Failed to mount image: File system type not supported or not known.");
if (r == -EIDRM)
- return log_error_errno(r, "File system is too uncommon, refused.");
+ return log_error_errno(r, "Failed to mount image: File system is too uncommon, refused.");
if (r < 0)
return log_error_errno(r, "Failed to mount image: %m");
@@ -2530,7 +2583,35 @@ static char* dm_deferred_remove_clean(char *name) {
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
-static int validate_signature_userspace(const VeritySettings *verity) {
+static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) {
+ int r;
+
+ if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) {
+ log_debug("Userspace dm-verity signature authentication disabled via flag.");
+ return 0;
+ }
+
+ r = secure_getenv_bool("SYSTEMD_ALLOW_USERSPACE_VERITY");
+ if (r < 0 && r != -ENXIO) {
+ log_debug_errno(r, "Failed to parse $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable, refusing userspace dm-verity signature authentication.");
+ return 0;
+ }
+ if (!r) {
+ log_debug("Userspace dm-verity signature authentication disabled via $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable.");
+ return 0;
+ }
+
+ bool b;
+ r = proc_cmdline_get_bool("systemd.allow_userspace_verity", PROC_CMDLINE_TRUE_WHEN_MISSING, &b);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse systemd.allow_userspace_verity= kernel command line option, refusing userspace dm-verity signature authentication.");
+ return 0;
+ }
+ if (!b) {
+ log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable.");
+ return 0;
+ }
+
#if HAVE_OPENSSL
_cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL;
_cleanup_strv_free_ char **certs = NULL;
@@ -2539,7 +2620,6 @@ static int validate_signature_userspace(const VeritySettings *verity) {
_cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order
* of declaration in place, please */
const unsigned char *d;
- int r;
assert(verity);
assert(verity->root_hash);
@@ -2611,7 +2691,8 @@ static int validate_signature_userspace(const VeritySettings *verity) {
static int do_crypt_activate_verity(
struct crypt_device *cd,
const char *name,
- const VeritySettings *verity) {
+ const VeritySettings *verity,
+ DissectImageFlags flags) {
bool check_signature;
int r, k;
@@ -2621,7 +2702,7 @@ static int do_crypt_activate_verity(
assert(verity);
if (verity->root_hash_sig) {
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE");
@@ -2656,7 +2737,7 @@ static int do_crypt_activate_verity(
/* Preferably propagate the original kernel error, so that the fallback logic can work,
* as the device-mapper is finicky around concurrent activations of the same volume */
- k = validate_signature_userspace(verity);
+ k = validate_signature_userspace(verity, flags);
if (k < 0)
return r < 0 ? r : k;
if (k == 0)
@@ -2777,7 +2858,7 @@ static int verity_partition(
goto check; /* The device already exists. Let's check it. */
/* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */
- r = do_crypt_activate_verity(cd, name, verity);
+ r = do_crypt_activate_verity(cd, name, verity, flags);
if (r >= 0)
goto try_open; /* The device is activated. Let's open it. */
/* libdevmapper can return EINVAL when the device is already in the activation stage.
@@ -2787,7 +2868,9 @@ static int verity_partition(
* https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */
if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
break;
- if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */
+ /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again if
+ * sharing is enabled. */
+ if (r == -ENODEV && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))
goto try_again;
if (!IN_SET(r,
-EEXIST, /* Volume has already been opened and ready to be used. */
@@ -2907,6 +2990,7 @@ int dissected_image_decrypt(
* > 0 → Decrypted successfully
* -ENOKEY → There's something to decrypt but no key was supplied
* -EKEYREJECTED → Passed key was not correct
+ * -EBUSY → Generic Verity error (kernel is not very explanatory)
*/
if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t))
@@ -2933,7 +3017,9 @@ int dissected_image_decrypt(
k = partition_verity_of(i);
if (k >= 0) {
- r = verity_partition(i, p, m->partitions + k, verity, flags | DISSECT_IMAGE_VERITY_SHARE, d);
+ flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0;
+
+ r = verity_partition(i, p, m->partitions + k, verity, flags, d);
if (r < 0)
return r;
}
@@ -2978,9 +3064,16 @@ int dissected_image_decrypt_interactively(
return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED),
"Too many retries.");
- z = strv_free(z);
+ z = strv_free_erase(z);
+
+ static const AskPasswordRequest req = {
+ .message = "Please enter image passphrase:",
+ .id = "dissect",
+ .keyring = "dissect",
+ .credential = "dissect.passphrase",
+ };
- r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", "dissect.passphrase", USEC_INFINITY, 0, &z);
+ r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &z);
if (r < 0)
return log_error_errno(r, "Failed to query for passphrase: %m");
@@ -3082,7 +3175,7 @@ int verity_settings_load(
if (is_device_path(image))
return 0;
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIDECAR");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIDECAR");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIDECAR, ignoring: %m");
if (r == 0)
@@ -3159,7 +3252,7 @@ int verity_settings_load(
}
if (text) {
- r = unhexmem(text, strlen(text), &root_hash, &root_hash_size);
+ r = unhexmem(text, &root_hash, &root_hash_size);
if (r < 0)
return r;
if (root_hash_size < sizeof(sd_id128_t))
@@ -3267,7 +3360,7 @@ int dissected_image_load_verity_sig_partition(
if (verity->root_hash && verity->root_hash_sig) /* Already loaded? */
return 0;
- r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_EMBEDDED");
+ r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_EMBEDDED");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_EMBEDDED, ignoring: %m");
if (r == 0)
@@ -3313,7 +3406,7 @@ int dissected_image_load_verity_sig_partition(
if (!json_variant_is_string(rh))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string.");
- r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size);
+ r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse root hash field: %m");
@@ -3334,7 +3427,7 @@ int dissected_image_load_verity_sig_partition(
if (!json_variant_is_string(sig))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string.");
- r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size);
+ r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size);
if (r < 0)
return log_debug_errno(r, "Failed to parse signature field: %m");
@@ -3347,7 +3440,10 @@ int dissected_image_load_verity_sig_partition(
return 1;
}
-int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) {
+int dissected_image_acquire_metadata(
+ DissectedImage *m,
+ int userns_fd,
+ DissectImageFlags extra_flags) {
enum {
META_HOSTNAME,
@@ -3375,11 +3471,10 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL;
+ _cleanup_free_ char *hostname = NULL, *t = NULL;
_cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR;
- _cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
sd_id128_t machine_id = SD_ID128_NULL;
- _cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0;
int fds[2 * _META_MAX], r, v;
int has_init_system = -1;
@@ -3389,11 +3484,8 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
assert(m);
- for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) {
- if (!paths[n_meta_initialized]) {
- fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -EBADF;
- continue;
- }
+ for (; n_meta_initialized < _META_MAX; n_meta_initialized++) {
+ assert(paths[n_meta_initialized]);
if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) {
r = -errno;
@@ -3401,7 +3493,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
}
}
- r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t);
+ r = get_common_dissect_directory(&t);
if (r < 0)
goto finish;
@@ -3410,13 +3502,22 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
goto finish;
}
- r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
+ r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &child);
if (r < 0)
goto finish;
if (r == 0) {
- /* Child in a new mount namespace */
+ /* Child */
error_pipe[0] = safe_close(error_pipe[0]);
+ if (userns_fd < 0)
+ r = detach_mount_namespace_harder(0, 0);
+ else
+ r = detach_mount_namespace_userns(userns_fd);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to detach mount namespace: %m");
+ goto inner_fail;
+ }
+
r = dissected_image_mount(
m,
t,
@@ -3435,8 +3536,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
for (unsigned k = 0; k < _META_MAX; k++) {
_cleanup_close_ int fd = -ENOENT;
- if (!paths[k])
- continue;
+ assert(paths[k]);
fds[2*k] = safe_close(fds[2*k]);
@@ -3541,8 +3641,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_
for (unsigned k = 0; k < _META_MAX; k++) {
_cleanup_fclose_ FILE *f = NULL;
- if (!paths[k])
- continue;
+ assert(paths[k]);
fds[2*k+1] = safe_close(fds[2*k+1]);
@@ -3703,6 +3802,7 @@ int dissect_loop_device(
return r;
m->loop = loop_device_ref(loop);
+ m->image_size = m->loop->device_size;
m->sector_size = m->loop->sector_size;
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
@@ -3953,10 +4053,12 @@ int verity_dissect_and_mount(
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
- dissect_image_flags = (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) |
+ dissect_image_flags =
+ (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) |
(relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0) |
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
- DISSECT_IMAGE_PIN_PARTITION_DEVICES;
+ DISSECT_IMAGE_PIN_PARTITION_DEVICES |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
/* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be
* accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */
@@ -4067,3 +4169,236 @@ int verity_dissect_and_mount(
return 0;
}
+
+int get_common_dissect_directory(char **ret) {
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ /* A common location we mount dissected images to. The assumption is that everyone who uses this
+ * function runs in their own private mount namespace (with mount propagation off on /run/systemd/,
+ * and thus can mount something here without affecting anyone else). */
+
+ t = strdup("/run/systemd/dissect-root");
+ if (!t)
+ return log_oom_debug();
+
+ r = mkdir_parents(t, 0755);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t);
+
+ r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */
+ if (r < 0 && r != -EEXIST)
+ return log_debug_errno(r, "Failed to create mount point '%s': %m", t);
+
+ if (ret)
+ *ret = TAKE_PTR(t);
+
+ return 0;
+}
+
+#if HAVE_BLKID
+
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_architecture, Architecture, architecture_from_string);
+static JSON_DISPATCH_ENUM_DEFINE(dispatch_partition_designator, PartitionDesignator, partition_designator_from_string);
+
+typedef struct PartitionFields {
+ PartitionDesignator designator;
+ bool rw;
+ bool growfs;
+ unsigned partno;
+ Architecture architecture;
+ sd_id128_t uuid;
+ char *fstype;
+ char *label;
+ uint64_t size;
+ uint64_t offset;
+ unsigned fsmount_fd_idx;
+} PartitionFields;
+
+static void partition_fields_done(PartitionFields *f) {
+ assert(f);
+
+ f->fstype = mfree(f->fstype);
+ f->label = mfree(f->label);
+}
+
+typedef struct ReplyParameters {
+ JsonVariant *partitions;
+ char *image_policy;
+ uint64_t image_size;
+ uint32_t sector_size;
+ sd_id128_t image_uuid;
+} ReplyParameters;
+
+static void reply_parameters_done(ReplyParameters *p) {
+ assert(p);
+
+ p->image_policy = mfree(p->image_policy);
+ p->partitions = json_variant_unref(p->partitions);
+}
+
+#endif
+
+int mountfsd_mount_image(
+ const char *path,
+ int userns_fd,
+ const ImagePolicy *image_policy,
+ DissectImageFlags flags,
+ DissectedImage **ret) {
+
+#if HAVE_BLKID
+ _cleanup_(reply_parameters_done) ReplyParameters p = {};
+
+ static const JsonDispatch dispatch_table[] = {
+ { "partitions", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(struct ReplyParameters, partitions), JSON_MANDATORY },
+ { "imagePolicy", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct ReplyParameters, image_policy), 0 },
+ { "imageSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct ReplyParameters, image_size), JSON_MANDATORY },
+ { "sectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct ReplyParameters, sector_size), JSON_MANDATORY },
+ { "imageUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct ReplyParameters, image_uuid), 0 },
+ {}
+ };
+
+ _cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
+ _cleanup_close_ int image_fd = -EBADF;
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ _cleanup_free_ char *ps = NULL;
+ unsigned max_fd = UINT_MAX;
+ const char *error_id;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to mountfsd: %m");
+
+ r = varlink_set_allow_fd_passing_input(vl, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable varlink fd passing for read: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ image_fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (image_fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+ r = varlink_push_dup_fd(vl, image_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to push image fd into varlink connection: %m");
+
+ if (userns_fd >= 0) {
+ r = varlink_push_dup_fd(vl, userns_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to push image fd into varlink connection: %m");
+ }
+
+ if (image_policy) {
+ r = image_policy_to_string(image_policy, /* simplify= */ false, &ps);
+ if (r < 0)
+ return log_error_errno(r, "Failed format image policy to string: %m");
+ }
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(
+ vl,
+ "io.systemd.MountFileSystem.MountImage",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("imageFileDescriptor", JSON_BUILD_UNSIGNED(0)),
+ JSON_BUILD_PAIR_CONDITION(userns_fd >= 0, "userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(1)),
+ JSON_BUILD_PAIR("readOnly", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY))),
+ JSON_BUILD_PAIR("growFileSystems", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))),
+ JSON_BUILD_PAIR_CONDITION(ps, "imagePolicy", JSON_BUILD_STRING(ps)),
+ JSON_BUILD_PAIR("allowInteractiveAuthentication", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH)))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call MountImage() varlink call: %m");
+ if (!isempty(error_id))
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to call MountImage() varlink call: %s", error_id);
+
+ r = json_dispatch(reply, dispatch_table, JSON_ALLOW_EXTENSIONS, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse MountImage() reply: %m");
+
+ log_debug("Effective image policy: %s", p.image_policy);
+
+ JsonVariant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, p.partitions) {
+ _cleanup_close_ int fsmount_fd = -EBADF;
+
+ _cleanup_(partition_fields_done) PartitionFields pp = {
+ .designator = _PARTITION_DESIGNATOR_INVALID,
+ .architecture = _ARCHITECTURE_INVALID,
+ .size = UINT64_MAX,
+ .offset = UINT64_MAX,
+ .fsmount_fd_idx = UINT_MAX,
+ };
+
+ static const JsonDispatch partition_dispatch_table[] = {
+ { "designator", JSON_VARIANT_STRING, dispatch_partition_designator, offsetof(struct PartitionFields, designator), JSON_MANDATORY },
+ { "writable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, rw), JSON_MANDATORY },
+ { "growFileSystem", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, growfs), JSON_MANDATORY },
+ { "partitionNumber", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, partno), 0 },
+ { "architecture", JSON_VARIANT_STRING, dispatch_architecture, offsetof(struct PartitionFields, architecture), 0 },
+ { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct PartitionFields, uuid), 0 },
+ { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, fstype), JSON_MANDATORY },
+ { "partitionLabel", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, label), 0 },
+ { "size", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, size), JSON_MANDATORY },
+ { "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, offset), JSON_MANDATORY },
+ { "mountFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, fsmount_fd_idx), JSON_MANDATORY },
+ {}
+ };
+
+ r = json_dispatch(i, partition_dispatch_table, JSON_ALLOW_EXTENSIONS, &pp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse partition data: %m");
+
+ if (pp.fsmount_fd_idx != UINT_MAX) {
+ if (max_fd == UINT_MAX || pp.fsmount_fd_idx > max_fd)
+ max_fd = pp.fsmount_fd_idx;
+
+ fsmount_fd = varlink_take_fd(vl, pp.fsmount_fd_idx);
+ if (fsmount_fd < 0)
+ return fsmount_fd;
+ }
+
+ assert(pp.designator >= 0);
+
+ if (!di) {
+ r = dissected_image_new(path, &di);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocated new dissected image structure: %m");
+ }
+
+ if (di->partitions[pp.designator].found)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate partition data for '%s'.", partition_designator_to_string(pp.designator));
+
+ di->partitions[pp.designator] = (DissectedPartition) {
+ .found = true,
+ .rw = pp.rw,
+ .growfs = pp.growfs,
+ .partno = pp.partno,
+ .architecture = pp.architecture,
+ .uuid = pp.uuid,
+ .fstype = TAKE_PTR(pp.fstype),
+ .label = TAKE_PTR(pp.label),
+ .mount_node_fd = -EBADF,
+ .size = pp.size,
+ .offset = pp.offset,
+ .fsmount_fd = TAKE_FD(fsmount_fd),
+ };
+ }
+
+ di->image_size = p.image_size;
+ di->sector_size = p.sector_size;
+ di->image_uuid = p.image_uuid;
+
+ *ret = TAKE_PTR(di);
+ return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 15c0bf7..e31fd54 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -87,6 +87,8 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_DISKSEQ_DEVNODE = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE = 1 << 25, /* Try to mount the image beneath the specified mountpoint, rather than on top of it, and then umount the top */
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY = 1 << 26, /* Allow userspace verity keyring in /etc/verity.d/ and related dirs */
+ DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH = 1 << 27, /* Allow interactive authorization when going through mountfsd */
} DissectImageFlags;
struct DissectedImage {
@@ -102,6 +104,7 @@ struct DissectedImage {
DecryptedImage *decrypted_image;
uint32_t sector_size;
+ uint64_t image_size;
char *image_name;
sd_id128_t image_uuid;
@@ -161,6 +164,7 @@ int dissect_image_file_and_warn(const char *path, const VeritySettings *verity,
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+void dissected_image_close(DissectedImage *m);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
@@ -169,7 +173,7 @@ int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphr
int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
-int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags);
+int dissected_image_acquire_metadata(DissectedImage *m, int userns_fd, DissectImageFlags extra_flags);
Architecture dissected_image_architecture(DissectedImage *m);
@@ -196,6 +200,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref);
int dissected_image_relinquish(DissectedImage *m);
int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path);
+
+static inline bool verity_settings_set(const VeritySettings *settings) {
+ return settings &&
+ (settings->root_hash_size > 0 ||
+ (settings->root_hash_sig_size > 0 ||
+ settings->data_path));
+}
+
void verity_settings_done(VeritySettings *verity);
static inline bool verity_settings_data_covers(const VeritySettings *verity, PartitionDesignator d) {
@@ -228,3 +240,7 @@ static inline const char *dissected_partition_fstype(const DissectedPartition *m
return m->decrypted_node ? m->decrypted_fstype : m->fstype;
}
+
+int get_common_dissect_directory(char **ret);
+
+int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
diff --git a/src/shared/dlfcn-util.c b/src/shared/dlfcn-util.c
deleted file mode 100644
index 8022f55..0000000
--- a/src/shared/dlfcn-util.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "dlfcn-util.h"
-
-static int dlsym_many_or_warnv(void *dl, int log_level, va_list ap) {
- void (**fn)(void);
-
- /* Tries to resolve a bunch of function symbols, and logs an error about if it cannot resolve one of
- * them. Note that this function possibly modifies the supplied function pointers if the whole
- * operation fails. */
-
- while ((fn = va_arg(ap, typeof(fn)))) {
- void (*tfn)(void);
- const char *symbol;
-
- symbol = va_arg(ap, typeof(symbol));
-
- tfn = (typeof(tfn)) dlsym(dl, symbol);
- if (!tfn)
- return log_full_errno(log_level,
- SYNTHETIC_ERRNO(ELIBBAD),
- "Can't find symbol %s: %s", symbol, dlerror());
- *fn = tfn;
- }
-
- return 0;
-}
-
-int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) {
- va_list ap;
- int r;
-
- va_start(ap, log_level);
- r = dlsym_many_or_warnv(dl, log_level, ap);
- va_end(ap);
-
- return r;
-}
-
-int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) {
- _cleanup_(dlclosep) void *dl = NULL;
- int r;
-
- if (*dlp)
- return 0; /* Already loaded */
-
- dl = dlopen(filename, RTLD_LAZY);
- if (!dl)
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "%s is not installed: %s", filename, dlerror());
-
- log_debug("Loaded '%s' via dlopen()", filename);
-
- va_list ap;
- va_start(ap, log_level);
- r = dlsym_many_or_warnv(dl, log_level, ap);
- va_end(ap);
-
- if (r < 0)
- return r;
-
- /* Note that we never release the reference here, because there's no real reason to. After all this
- * was traditionally a regular shared library dependency which lives forever too. */
- *dlp = TAKE_PTR(dl);
- return 1;
-}
diff --git a/src/shared/dlfcn-util.h b/src/shared/dlfcn-util.h
deleted file mode 100644
index 7d8cb4c..0000000
--- a/src/shared/dlfcn-util.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <dlfcn.h>
-
-#include "macro.h"
-
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(void*, dlclose, NULL);
-
-int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_;
-int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_;
-
-#define dlsym_many_or_warn(dl, log_level, ...) \
- dlsym_many_or_warn_sentinel(dl, log_level, __VA_ARGS__, NULL)
-#define dlopen_many_sym_or_warn(dlp, filename, log_level, ...) \
- dlopen_many_sym_or_warn_sentinel(dlp, filename, log_level, __VA_ARGS__, NULL)
-
-#define DLSYM_PROTOTYPE(symbol) \
- extern typeof(symbol)* sym_##symbol
-#define DLSYM_FUNCTION(symbol) \
- typeof(symbol)* sym_##symbol = NULL
-
-/* Macro useful for putting together variable/symbol name pairs when calling dlsym_many_or_warn(). Assumes
- * that each library symbol to resolve will be placed in a variable with the "sym_" prefix, i.e. a symbol
- * "foobar" is loaded into a variable "sym_foobar". */
-#define DLSYM_ARG(arg) \
- ({ assert_cc(__builtin_types_compatible_p(typeof(sym_##arg), typeof(&arg))); &sym_##arg; }), STRINGIFY(arg)
-
-/* libbpf is a bit confused about type-safety and API compatibility. Provide a macro that can tape over that mess. Sad. */
-#define DLSYM_ARG_FORCE(arg) \
- &sym_##arg, STRINGIFY(arg)
-
-static inline void *safe_dlclose(void *p) {
- if (!p)
- return NULL;
-
- assert_se(dlclose(p) == 0);
- return NULL;
-}
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index b41c9b0..ba24a77 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -410,7 +410,7 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r
goto finish;
for (;;) {
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
r = dns_label_unescape(&p, label, sizeof label, flags);
if (r < 0)
@@ -507,7 +507,7 @@ int dns_name_compare_func(const char *a, const char *b) {
y = b + strlen(b);
for (;;) {
- char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+ char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
if (x == NULL && y == NULL)
return 0;
@@ -543,7 +543,7 @@ int dns_name_equal(const char *x, const char *y) {
assert(y);
for (;;) {
- char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+ char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
r = dns_label_unescape(&x, la, sizeof la, 0);
if (r < 0)
@@ -574,7 +574,7 @@ int dns_name_endswith(const char *name, const char *suffix) {
s = suffix;
for (;;) {
- char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+ char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
r = dns_label_unescape(&n, ln, sizeof ln, 0);
if (r < 0)
@@ -612,7 +612,7 @@ int dns_name_startswith(const char *name, const char *prefix) {
p = prefix;
for (;;) {
- char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
+ char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1];
r = dns_label_unescape(&p, lp, sizeof lp, 0);
if (r < 0)
@@ -644,7 +644,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
s = old_suffix;
for (;;) {
- char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
+ char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
if (!saved_before)
saved_before = n;
@@ -929,7 +929,7 @@ bool dns_srv_type_is_valid(const char *name) {
return false;
for (;;) {
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
/* This more or less implements RFC 6335, Section 5.1 */
@@ -980,6 +980,29 @@ bool dns_service_name_is_valid(const char *name) {
return true;
}
+bool dns_subtype_name_is_valid(const char *name) {
+ size_t l;
+
+ /* This more or less implements RFC 6763, Section 7.2 */
+
+ if (!name)
+ return false;
+
+ if (!utf8_is_valid(name))
+ return false;
+
+ if (string_has_cc(name, NULL))
+ return false;
+
+ l = strlen(name);
+ if (l <= 0)
+ return false;
+ if (l > DNS_LABEL_MAX)
+ return false;
+
+ return true;
+}
+
int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
char escaped[DNS_LABEL_ESCAPED_MAX];
_cleanup_free_ char *n = NULL;
@@ -1227,7 +1250,7 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
return m;
for (;;) {
- char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+ char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
const char *x, *y;
if (k >= n || k >= m) {
@@ -1328,7 +1351,7 @@ int dns_name_apply_idna(const char *name, char **ret) {
assert(ret);
for (;;) {
- char label[DNS_LABEL_MAX];
+ char label[DNS_LABEL_MAX+1];
r = dns_label_unescape(&name, label, sizeof label, 0);
if (r < 0)
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index 331fb89..8ad00d6 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -83,6 +83,7 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
bool dns_srv_type_is_valid(const char *name);
bool dnssd_srv_type_is_valid(const char *name);
bool dns_service_name_is_valid(const char *name);
+bool dns_subtype_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
int dns_service_split(const char *joined, char **ret_name, char **ret_type, char **ret_domain);
diff --git a/src/shared/dropin.c b/src/shared/dropin.c
index d46e838..9a786d0 100644
--- a/src/shared/dropin.c
+++ b/src/shared/dropin.c
@@ -26,7 +26,7 @@
int drop_in_file(const char *dir, const char *unit, unsigned level,
const char *name, char **ret_p, char **ret_q) {
- char prefix[DECIMAL_STR_MAX(unsigned)];
+ char prefix[DECIMAL_STR_MAX(unsigned) + 1] = {};
_cleanup_free_ char *b = NULL, *p = NULL, *q = NULL;
assert(unit);
@@ -34,7 +34,8 @@ int drop_in_file(const char *dir, const char *unit, unsigned level,
assert(ret_p);
assert(ret_q);
- sprintf(prefix, "%u", level);
+ if (level != UINT_MAX)
+ xsprintf(prefix, "%u-", level);
b = xescape(name, "/.");
if (!b)
@@ -44,7 +45,7 @@ int drop_in_file(const char *dir, const char *unit, unsigned level,
return -EINVAL;
p = strjoin(dir, "/", unit, ".d");
- q = strjoin(p, "/", prefix, "-", b, ".conf");
+ q = strjoin(p, "/", prefix, b, ".conf");
if (!p || !q)
return -ENOMEM;
diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c
index 045839b..cfb2828 100644
--- a/src/shared/edit-util.c
+++ b/src/shared/edit-util.c
@@ -16,6 +16,15 @@
#include "strv.h"
#include "tmpfile-util-label.h"
+typedef struct EditFile {
+ EditFileContext *context;
+ char *path;
+ char *original_path;
+ char **comment_paths;
+ char *temp;
+ unsigned line;
+} EditFile;
+
void edit_file_context_done(EditFileContext *context) {
int r;
@@ -93,41 +102,22 @@ int edit_files_add(
.path = TAKE_PTR(new_path),
.original_path = TAKE_PTR(new_original_path),
.comment_paths = TAKE_PTR(new_comment_paths),
+ .line = 1,
};
context->n_files++;
return 1;
}
-static int create_edit_temp_file(EditFile *e) {
- _cleanup_(unlink_and_freep) char *temp = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- const char *source;
- bool has_original, has_target;
- unsigned line = 1;
- int r;
-
+static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) {
assert(e);
- assert(e->context);
- assert(e->path);
- assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end));
+ assert(f);
+ assert(filename);
- if (e->temp)
- return 0;
-
- r = mkdir_parents_label(e->path, 0755);
- if (r < 0)
- return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
-
- r = fopen_temporary_label(e->path, e->path, &f, &temp);
- if (r < 0)
- return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
-
- if (fchmod(fileno(f), 0644) < 0)
- return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
-
- has_original = e->original_path && access(e->original_path, F_OK) >= 0;
- has_target = access(e->path, F_OK) >= 0;
+ bool has_original = e->original_path && access(e->original_path, F_OK) >= 0;
+ bool has_target = access(e->path, F_OK) >= 0;
+ const char *source;
+ int r;
if (has_original && (!has_target || e->context->overwrite_with_origin))
/* We are asked to overwrite target with original_path or target doesn't exist. */
@@ -160,7 +150,7 @@ static int create_edit_temp_file(EditFile *e) {
source_contents && endswith(source_contents, "\n") ? "" : "\n",
e->context->marker_end);
- line = 4; /* Start editing at the contents area */
+ e->line = 4; /* Start editing at the contents area */
STRV_FOREACH(path, e->comment_paths) {
_cleanup_free_ char *comment = NULL;
@@ -189,16 +179,54 @@ static int create_edit_temp_file(EditFile *e) {
r = copy_file_fd(source, fileno(f), COPY_REFLINK);
if (r < 0) {
assert(r != -ENOENT);
- return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp);
+ return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m",
+ source, filename);
}
}
+ return 0;
+}
+
+static int create_edit_temp_file(EditFile *e, const char *contents, size_t contents_size) {
+ _cleanup_(unlink_and_freep) char *temp = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(e);
+ assert(e->context);
+ assert(e->path);
+ assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end));
+ assert(contents || contents_size == 0);
+
+ if (e->temp)
+ return 0;
+
+ r = mkdir_parents_label(e->path, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
+
+ r = fopen_temporary_label(e->path, e->path, &f, &temp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
+
+ if (fchmod(fileno(f), 0644) < 0)
+ return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
+
+ if (e->context->stdin) {
+ if (fwrite(contents, 1, contents_size, f) != contents_size)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to copy input to temporary file '%s'.", temp);
+ } else {
+ r = populate_edit_temp_file(e, f, temp);
+ if (r < 0)
+ return r;
+ }
+
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
e->temp = TAKE_PTR(temp);
- e->line = line;
return 0;
}
@@ -282,7 +310,7 @@ static int run_editor(const EditFileContext *context) {
}
static int strip_edit_temp_file(EditFile *e) {
- _cleanup_free_ char *old_contents = NULL, *new_contents = NULL;
+ _cleanup_free_ char *old_contents = NULL, *tmp = NULL, *new_contents = NULL;
const char *stripped;
int r;
@@ -294,15 +322,17 @@ static int strip_edit_temp_file(EditFile *e) {
if (r < 0)
return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
- if (e->context->marker_start) {
+ tmp = strdup(old_contents);
+ if (!tmp)
+ return log_oom();
+
+ if (e->context->marker_start && !e->context->stdin) {
/* Trim out the lines between the two markers */
char *contents_start, *contents_end;
assert(e->context->marker_end);
- contents_start = strstrafter(old_contents, e->context->marker_start);
- if (!contents_start)
- contents_start = old_contents;
+ contents_start = strstrafter(tmp, e->context->marker_start) ?: tmp;
contents_end = strstr(contents_start, e->context->marker_end);
if (contents_end)
@@ -310,9 +340,13 @@ static int strip_edit_temp_file(EditFile *e) {
stripped = strstrip(contents_start);
} else
- stripped = strstrip(old_contents);
- if (isempty(stripped))
- return 0; /* File is empty (has no real changes) */
+ stripped = strstrip(tmp);
+
+ if (isempty(stripped)) {
+ /* File is empty (has no real changes) */
+ log_notice("%s: after editing, new contents are empty, not writing file.", e->path);
+ return 0;
+ }
/* Trim prefix and suffix, but ensure suffixed by single newline */
new_contents = strjoin(stripped, "\n");
@@ -320,16 +354,19 @@ static int strip_edit_temp_file(EditFile *e) {
return log_oom();
if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */
- return 1; /* Contents unchanged after stripping but has changes */
+ return 1; /* Contents have real changes */
- r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
+ r = write_string_file(e->temp, new_contents,
+ WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
if (r < 0)
return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp);
- return 1; /* Contents have real changes and are changed after stripping */
+ return 1; /* Contents have real changes */
}
int do_edit_files_and_install(EditFileContext *context) {
+ _cleanup_free_ char *data = NULL;
+ size_t data_size = 0;
int r;
assert(context);
@@ -337,33 +374,41 @@ int do_edit_files_and_install(EditFileContext *context) {
if (context->n_files == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Got no files to edit.");
- FOREACH_ARRAY(i, context->files, context->n_files) {
- r = create_edit_temp_file(i);
+ if (context->stdin) {
+ r = read_full_stream(stdin, &data, &data_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read stdin: %m");
+ }
+
+ FOREACH_ARRAY(editfile, context->files, context->n_files) {
+ r = create_edit_temp_file(editfile, data, data_size);
if (r < 0)
return r;
}
- r = run_editor(context);
- if (r < 0)
- return r;
+ if (!context->stdin) {
+ r = run_editor(context);
+ if (r < 0)
+ return r;
+ }
- FOREACH_ARRAY(i, context->files, context->n_files) {
+ FOREACH_ARRAY(editfile, context->files, context->n_files) {
/* Always call strip_edit_temp_file which will tell if the temp file has actual changes */
- r = strip_edit_temp_file(i);
+ r = strip_edit_temp_file(editfile);
if (r < 0)
return r;
if (r == 0) /* temp file doesn't carry actual changes, ignoring */
continue;
- r = RET_NERRNO(rename(i->temp, i->path));
+ r = RET_NERRNO(rename(editfile->temp, editfile->path));
if (r < 0)
return log_error_errno(r,
"Failed to rename temporary file '%s' to target file '%s': %m",
- i->temp,
- i->path);
- i->temp = mfree(i->temp);
+ editfile->temp,
+ editfile->path);
+ editfile->temp = mfree(editfile->temp);
- log_info("Successfully installed edited file '%s'.", i->path);
+ log_info("Successfully installed edited file '%s'.", editfile->path);
}
return 0;
diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h
index 83b3df8..9d9c890 100644
--- a/src/shared/edit-util.h
+++ b/src/shared/edit-util.h
@@ -7,25 +7,16 @@
#define DROPIN_MARKER_END "### Edits below this comment will be discarded"
typedef struct EditFile EditFile;
-typedef struct EditFileContext EditFileContext;
-
-struct EditFile {
- EditFileContext *context;
- char *path;
- char *original_path;
- char **comment_paths;
- char *temp;
- unsigned line;
-};
-
-struct EditFileContext {
+
+typedef struct EditFileContext {
EditFile *files;
size_t n_files;
const char *marker_start;
const char *marker_end;
bool remove_parent;
- bool overwrite_with_origin; /* whether to always overwrite target with original file */
-};
+ bool overwrite_with_origin; /* Always overwrite target with original file. */
+ bool stdin; /* Read contents from stdin instead of launching an editor. */
+} EditFileContext;
void edit_file_context_done(EditFileContext *context);
diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c
index 4cd1091..3ca33ef 100644
--- a/src/shared/efi-api.c
+++ b/src/shared/efi-api.c
@@ -7,6 +7,7 @@
#include "efi-api.h"
#include "efivars.h"
#include "fd-util.h"
+#include "fileio.h"
#include "sort-util.h"
#include "stat-util.h"
#include "stdio-util.h"
@@ -453,13 +454,13 @@ int efi_get_boot_options(uint16_t **ret_options) {
FOREACH_DIRENT(de, dir, return -errno) {
int id;
- if (strncmp(de->d_name, "Boot", 4) != 0)
+ if (!startswith(de->d_name, "Boot"))
continue;
if (strlen(de->d_name) != 45)
continue;
- if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */
+ if (!streq(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR(""))) /* generate variable suffix using macro */
continue;
id = boot_id_hex(de->d_name + 4);
@@ -481,6 +482,7 @@ int efi_get_boot_options(uint16_t **ret_options) {
bool efi_has_tpm2(void) {
static int cache = -1;
+ int r;
/* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */
@@ -488,30 +490,35 @@ bool efi_has_tpm2(void) {
return cache;
/* First, check if we are on an EFI boot at all. */
- if (!is_efi_boot()) {
- cache = 0;
- return cache;
- }
+ if (!is_efi_boot())
+ return (cache = false);
/* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see:
* https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf
- * This table exists whenever the firmware is hooked up to TPM2. */
- cache = access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0;
- if (cache)
- return cache;
-
+ * This table exists whenever the firmware knows ACPI and is hooked up to TPM2. */
+ if (access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0)
+ return (cache = true);
if (errno != ENOENT)
log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m");
/* As the last try, check if the EFI firmware provides the EFI_TCG2_FINAL_EVENTS_TABLE
* stored in EFI configuration table, see:
- * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf
- */
- cache = access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0;
- if (!cache && errno != ENOENT)
- log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m");
+ *
+ * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf */
+ if (access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0) {
+ _cleanup_free_ char *major = NULL;
+
+ /* The EFI table might exist for TPM 1.2 as well, hence let's check explicitly which TPM version we are looking at here. */
+ r = read_virtual_file("/sys/class/tpm/tpm0/tpm_version_major", SIZE_MAX, &major, /* ret_size= */ NULL);
+ if (r >= 0)
+ return (cache = streq(strstrip(major), "2"));
+
+ log_debug_errno(r, "Unable to read /sys/class/tpm/tpm0/tpm_version_major, assuming TPM does not qualify as TPM2: %m");
+
+ } else if (errno != ENOENT)
+ log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m");
- return cache;
+ return (cache = false);
}
#endif
diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c
index 7d6bda9..ab377aa 100644
--- a/src/shared/efi-loader.c
+++ b/src/shared/efi-loader.c
@@ -262,7 +262,7 @@ int efi_measured_uki(int log_level) {
* being used, but it measured things into a different PCR than we are configured for in
* userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */
- r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test,
+ r = secure_getenv_bool("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test,
* for debugging purposes */
if (r >= 0)
return (cached = r);
diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c
index 24ed16e..9d1f494 100644
--- a/src/shared/elf-util.c
+++ b/src/shared/elf-util.c
@@ -38,55 +38,60 @@ static void *dw_dl = NULL;
static void *elf_dl = NULL;
/* libdw symbols */
-Dwarf_Attribute *(*sym_dwarf_attr_integrate)(Dwarf_Die *, unsigned int, Dwarf_Attribute *);
-const char *(*sym_dwarf_diename)(Dwarf_Die *);
-const char *(*sym_dwarf_formstring)(Dwarf_Attribute *);
-int (*sym_dwarf_getscopes)(Dwarf_Die *, Dwarf_Addr, Dwarf_Die **);
-int (*sym_dwarf_getscopes_die)(Dwarf_Die *, Dwarf_Die **);
-Elf *(*sym_dwelf_elf_begin)(int);
+static DLSYM_FUNCTION(dwarf_attr_integrate);
+static DLSYM_FUNCTION(dwarf_diename);
+static DLSYM_FUNCTION(dwarf_formstring);
+static DLSYM_FUNCTION(dwarf_getscopes);
+static DLSYM_FUNCTION(dwarf_getscopes_die);
+static DLSYM_FUNCTION(dwelf_elf_begin);
#if HAVE_DWELF_ELF_E_MACHINE_STRING
-const char *(*sym_dwelf_elf_e_machine_string)(int);
+static DLSYM_FUNCTION(dwelf_elf_e_machine_string);
#endif
-ssize_t (*sym_dwelf_elf_gnu_build_id)(Elf *, const void **);
-int (*sym_dwarf_tag)(Dwarf_Die *);
-Dwfl_Module *(*sym_dwfl_addrmodule)(Dwfl *, Dwarf_Addr);
-Dwfl *(*sym_dwfl_begin)(const Dwfl_Callbacks *);
-int (*sym_dwfl_build_id_find_elf)(Dwfl_Module *, void **, const char *, Dwarf_Addr, char **, Elf **);
-int (*sym_dwfl_core_file_attach)(Dwfl *, Elf *);
-int (*sym_dwfl_core_file_report)(Dwfl *, Elf *, const char *);
-void (*sym_dwfl_end)(Dwfl *);
-const char *(*sym_dwfl_errmsg)(int);
-int (*sym_dwfl_errno)(void);
-bool (*sym_dwfl_frame_pc)(Dwfl_Frame *, Dwarf_Addr *, bool *);
-ptrdiff_t (*sym_dwfl_getmodules)(Dwfl *, int (*)(Dwfl_Module *, void **, const char *, Dwarf_Addr, void *), void *, ptrdiff_t);
-int (*sym_dwfl_getthreads)(Dwfl *, int (*)(Dwfl_Thread *, void *), void *);
-Dwarf_Die *(*sym_dwfl_module_addrdie)(Dwfl_Module *, Dwarf_Addr, Dwarf_Addr *);
-const char *(*sym_dwfl_module_addrname)(Dwfl_Module *, GElf_Addr);
-int (*sym_dwfl_module_build_id)(Dwfl_Module *, const unsigned char **, GElf_Addr *);
-Elf *(*sym_dwfl_module_getelf)(Dwfl_Module *, GElf_Addr *);
-const char *(*sym_dwfl_module_info)(Dwfl_Module *, void ***, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, const char **, const char **);
-int (*sym_dwfl_offline_section_address)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, GElf_Word, const GElf_Shdr *, Dwarf_Addr *);
-int (*sym_dwfl_report_end)(Dwfl *, int (*)(Dwfl_Module *, void *, const char *, Dwarf_Addr, void *), void *);
-int (*sym_dwfl_standard_find_debuginfo)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, const char *, GElf_Word, char **);
-int (*sym_dwfl_thread_getframes)(Dwfl_Thread *, int (*)(Dwfl_Frame *, void *), void *);
-pid_t (*sym_dwfl_thread_tid)(Dwfl_Thread *);
+static DLSYM_FUNCTION(dwelf_elf_gnu_build_id);
+static DLSYM_FUNCTION(dwarf_tag);
+static DLSYM_FUNCTION(dwfl_addrmodule);
+static DLSYM_FUNCTION(dwfl_begin);
+static DLSYM_FUNCTION(dwfl_build_id_find_elf);
+static DLSYM_FUNCTION(dwfl_core_file_attach);
+static DLSYM_FUNCTION(dwfl_core_file_report);
+static DLSYM_FUNCTION(dwfl_end);
+static DLSYM_FUNCTION(dwfl_errmsg);
+static DLSYM_FUNCTION(dwfl_errno);
+static DLSYM_FUNCTION(dwfl_frame_pc);
+static DLSYM_FUNCTION(dwfl_getmodules);
+static DLSYM_FUNCTION(dwfl_getthreads);
+static DLSYM_FUNCTION(dwfl_module_addrdie);
+static DLSYM_FUNCTION(dwfl_module_addrname);
+static DLSYM_FUNCTION(dwfl_module_build_id);
+static DLSYM_FUNCTION(dwfl_module_getelf);
+static DLSYM_FUNCTION(dwfl_module_info);
+static DLSYM_FUNCTION(dwfl_offline_section_address);
+static DLSYM_FUNCTION(dwfl_report_end);
+static DLSYM_FUNCTION(dwfl_standard_find_debuginfo);
+static DLSYM_FUNCTION(dwfl_thread_getframes);
+static DLSYM_FUNCTION(dwfl_thread_tid);
/* libelf symbols */
-Elf *(*sym_elf_begin)(int, Elf_Cmd, Elf *);
-int (*sym_elf_end)(Elf *);
-Elf_Data *(*sym_elf_getdata_rawchunk)(Elf *, int64_t, size_t, Elf_Type);
-GElf_Ehdr *(*sym_gelf_getehdr)(Elf *, GElf_Ehdr *);
-int (*sym_elf_getphdrnum)(Elf *, size_t *);
-const char *(*sym_elf_errmsg)(int);
-int (*sym_elf_errno)(void);
-Elf *(*sym_elf_memory)(char *, size_t);
-unsigned int (*sym_elf_version)(unsigned int);
-GElf_Phdr *(*sym_gelf_getphdr)(Elf *, int, GElf_Phdr *);
-size_t (*sym_gelf_getnote)(Elf_Data *, size_t, GElf_Nhdr *, size_t *, size_t *);
+static DLSYM_FUNCTION(elf_begin);
+static DLSYM_FUNCTION(elf_end);
+static DLSYM_FUNCTION(elf_getdata_rawchunk);
+static DLSYM_FUNCTION(gelf_getehdr);
+static DLSYM_FUNCTION(elf_getphdrnum);
+static DLSYM_FUNCTION(elf_errmsg);
+static DLSYM_FUNCTION(elf_errno);
+static DLSYM_FUNCTION(elf_memory);
+static DLSYM_FUNCTION(elf_version);
+static DLSYM_FUNCTION(gelf_getphdr);
+static DLSYM_FUNCTION(gelf_getnote);
int dlopen_dw(void) {
int r;
+ ELF_NOTE_DLOPEN("dw",
+ "Support for backtrace and ELF package metadata decoding from core files",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libdw.so.1");
+
r = dlopen_many_sym_or_warn(
&dw_dl, "libdw.so.1", LOG_DEBUG,
DLSYM_ARG(dwarf_getscopes),
@@ -130,6 +135,11 @@ int dlopen_dw(void) {
int dlopen_elf(void) {
int r;
+ ELF_NOTE_DLOPEN("elf",
+ "Support for backtraces and reading ELF package metadata from core files",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libelf.so.1");
+
r = dlopen_many_sym_or_warn(
&elf_dl, "libelf.so.1", LOG_DEBUG,
DLSYM_ARG(elf_begin),
@@ -348,7 +358,7 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e
/* Package metadata is in PT_NOTE headers. */
program_header = sym_gelf_getphdr(elf, i, &mem);
- if (!program_header || (program_header->p_type != PT_NOTE && program_header->p_type != PT_INTERP))
+ if (!program_header || !IN_SET(program_header->p_type, PT_NOTE, PT_INTERP))
continue;
if (program_header->p_type == PT_INTERP) {
@@ -540,7 +550,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name,
continue;
/* Check that the end of segment is a valid address. */
- if (__builtin_add_overflow(program_header->p_vaddr, program_header->p_memsz, &end_of_segment)) {
+ if (!ADD_SAFE(&end_of_segment, program_header->p_vaddr, program_header->p_memsz)) {
log_error("Abort due to corrupted core dump, end of segment address %#zx + %#zx overflows", (size_t)program_header->p_vaddr, (size_t)program_header->p_memsz);
return DWARF_CB_ABORT;
}
diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c
index dce9e00..1e100c3 100644
--- a/src/shared/ethtool-util.c
+++ b/src/shared/ethtool-util.c
@@ -182,7 +182,6 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
- char *d;
int r;
assert(ethtool_fd);
@@ -201,12 +200,7 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
if (isempty(ecmd.driver))
return -ENODATA;
- d = strdup(ecmd.driver);
- if (!d)
- return -ENOMEM;
-
- *ret = d;
- return 0;
+ return strdup_to(ret, ecmd.driver);
}
int ethtool_get_link_info(
diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c
index c27f3a5..996edbf 100644
--- a/src/shared/exec-util.c
+++ b/src/shared/exec-util.c
@@ -36,27 +36,35 @@
/* Put this test here for a lack of better place */
assert_cc(EAGAIN == EWOULDBLOCK);
-static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, bool set_systemd_exec_pid) {
- pid_t _pid;
+static int do_spawn(
+ const char *path,
+ char *argv[],
+ int stdout_fd,
+ bool set_systemd_exec_pid,
+ pid_t *ret_pid) {
+
int r;
+ assert(path);
+ assert(ret_pid);
+
if (null_or_empty_path(path) > 0) {
log_debug("%s is empty (a mask).", path);
return 0;
}
- r = safe_fork("(direxec)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &_pid);
+ pid_t pid;
+ r = safe_fork_full(
+ "(direxec)",
+ (const int[]) { STDIN_FILENO, stdout_fd < 0 ? STDOUT_FILENO : stdout_fd, STDERR_FILENO },
+ /* except_fds= */ NULL, /* n_except_fds= */ 0,
+ FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS,
+ &pid);
if (r < 0)
return r;
if (r == 0) {
char *_argv[2];
- if (stdout_fd >= 0) {
- r = rearrange_stdio(STDIN_FILENO, TAKE_FD(stdout_fd), STDERR_FILENO);
- if (r < 0)
- _exit(EXIT_FAILURE);
- }
-
if (set_systemd_exec_pid) {
r = setenv_systemd_exec_pid(false);
if (r < 0)
@@ -75,7 +83,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b
_exit(EXIT_FAILURE);
}
- *pid = _pid;
+ *ret_pid = pid;
return 1;
}
@@ -147,7 +155,7 @@ static int do_execute(
log_debug("About to execute %s%s%s", t, argv ? " " : "", argv ? strnull(args) : "");
}
- r = do_spawn(t, argv, fd, &pid, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID));
+ r = do_spawn(t, argv, fd, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID), &pid);
if (r <= 0)
continue;
@@ -539,7 +547,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret
r = safe_fork_full(name,
NULL,
- except,
+ (int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */
n_except,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE,
ret_pid);
diff --git a/src/shared/fdset.c b/src/shared/fdset.c
index e5b8e92..cb5a69e 100644
--- a/src/shared/fdset.c
+++ b/src/shared/fdset.c
@@ -3,6 +3,7 @@
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
+#include <unistd.h>
#include "sd-daemon.h"
@@ -40,8 +41,8 @@ int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) {
if (!s)
return -ENOMEM;
- for (size_t i = 0; i < n_fds; i++) {
- r = fdset_put(s, fds[i]);
+ FOREACH_ARRAY(fd, fds, n_fds) {
+ r = fdset_put(s, *fd);
if (r < 0)
return r;
}
@@ -71,7 +72,7 @@ void fdset_close(FDSet *s) {
log_debug("Closing set fd %i (%s)", fd, strna(path));
}
- (void) close_nointr(fd);
+ (void) close(fd);
}
}
@@ -246,7 +247,7 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) {
return -ENOMEM;
n = sd_listen_fds(unset);
- for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
r = fdset_put(s, fd);
if (r < 0)
return r;
diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c
index db87084..f830d6d 100644
--- a/src/shared/find-esp.c
+++ b/src/shared/find-esp.c
@@ -556,13 +556,16 @@ int find_esp_and_warn(
if (rfd < 0)
return -errno;
- r = find_esp_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_part ? &part : NULL,
- ret_pstart ? &pstart : NULL,
- ret_psize ? &psize : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_esp_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_part ? &part : NULL,
+ ret_pstart ? &pstart : NULL,
+ ret_psize ? &psize : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
@@ -871,12 +874,12 @@ int find_xbootldr_and_warn_at(
}
int find_xbootldr_and_warn(
- const char *root,
- const char *path,
- int unprivileged_mode,
- char **ret_path,
- sd_id128_t *ret_uuid,
- dev_t *ret_devid) {
+ const char *root,
+ const char *path,
+ int unprivileged_mode,
+ char **ret_path,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
_cleanup_close_ int rfd = -EBADF;
_cleanup_free_ char *p = NULL;
@@ -888,10 +891,13 @@ int find_xbootldr_and_warn(
if (rfd < 0)
return -errno;
- r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode,
- ret_path ? &p : NULL,
- ret_uuid ? &uuid : NULL,
- ret_devid ? &devid : NULL);
+ r = find_xbootldr_and_warn_at(
+ rfd,
+ path,
+ unprivileged_mode,
+ ret_path ? &p : NULL,
+ ret_uuid ? &uuid : NULL,
+ ret_devid ? &devid : NULL);
if (r < 0)
return r;
diff --git a/src/shared/firewall-util-iptables.c b/src/shared/firewall-util-iptables.c
index b70b740..e2e5bb3 100644
--- a/src/shared/firewall-util-iptables.c
+++ b/src/shared/firewall-util-iptables.c
@@ -1,19 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/* Temporary work-around for broken glibc vs. linux kernel header definitions
- * This is already fixed upstream, remove this when distributions have updated.
- */
-#define _NET_IF_H 1
-
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
#include <arpa/inet.h>
#include <endian.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
-#include <net/if.h>
-#ifndef IFNAMSIZ
-#define IFNAMSIZ 16
-#endif
#include <linux/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/nf_nat.h>
@@ -361,6 +354,11 @@ int fw_iptables_add_local_dnat(
}
static int dlopen_iptc(void) {
+ ELF_NOTE_DLOPEN("ip4tc",
+ "Support for firewall rules with iptables backend",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libip4tc.so.2");
+
return dlopen_many_sym_or_warn(
&iptc_dl,
"libip4tc.so.2", LOG_DEBUG,
diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c
index fe986ed..e9bd286 100644
--- a/src/shared/firewall-util-nft.c
+++ b/src/shared/firewall-util-nft.c
@@ -1316,7 +1316,7 @@ int config_parse_nft_set(
return 0;
q = tuple;
- r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set, NULL);
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set);
if (r == -ENOMEM)
return log_oom();
if (r != 4 || !isempty(q)) {
diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h
index 14e35be..25ba0d4 100644
--- a/src/shared/firewall-util.h
+++ b/src/shared/firewall-util.h
@@ -43,7 +43,7 @@ typedef enum NFTSetSource {
NFT_SET_SOURCE_GROUP,
_NFT_SET_SOURCE_MAX,
_NFT_SET_SOURCE_INVALID = -EINVAL,
-} NFTSetSource;
+} NFTSetSource;
typedef struct NFTSet {
NFTSetSource source;
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
index 9a19177..9146444 100644
--- a/src/shared/format-table.c
+++ b/src/shared/format-table.c
@@ -77,7 +77,9 @@ typedef struct TableData {
unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
- bool uppercase; /* Uppercase string on display */
+ bool uppercase:1; /* Uppercase string on display */
+ bool underline:1;
+ bool rgap_underline:1;
const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
@@ -401,7 +403,7 @@ static bool table_data_matches(
return false;
/* If a color/url is set, refuse to merge */
- if (d->color || d->rgap_color)
+ if (d->color || d->rgap_color || d->underline || d->rgap_underline)
return false;
if (d->url)
return false;
@@ -617,6 +619,8 @@ static int table_dedup_cell(Table *t, TableCell *cell) {
nd->color = od->color;
nd->rgap_color = od->rgap_color;
+ nd->underline = od->underline;
+ nd->rgap_underline = od->rgap_underline;
nd->url = TAKE_PTR(curl);
table_data_unref(od);
@@ -759,6 +763,46 @@ int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
return 0;
}
+int table_set_underline(Table *t, TableCell *cell, bool b) {
+ TableData *d;
+ int r;
+
+ assert(t);
+ assert(cell);
+
+ r = table_dedup_cell(t, cell);
+ if (r < 0)
+ return r;
+
+ assert_se(d = table_get_data(t, cell));
+
+ if (d->underline == b)
+ return 0;
+
+ d->underline = b;
+ return 1;
+}
+
+int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
+ TableData *d;
+ int r;
+
+ assert(t);
+ assert(cell);
+
+ r = table_dedup_cell(t, cell);
+ if (r < 0)
+ return r;
+
+ assert_se(d = table_get_data(t, cell));
+
+ if (d->rgap_underline == b)
+ return 0;
+
+ d->rgap_underline = b;
+ return 1;
+}
+
int table_set_url(Table *t, TableCell *cell, const char *url) {
_cleanup_free_ char *copy = NULL;
int r;
@@ -834,6 +878,8 @@ int table_update(Table *t, TableCell *cell, TableDataType type, const void *data
nd->color = od->color;
nd->rgap_color = od->rgap_color;
+ nd->underline = od->underline;
+ nd->rgap_underline = od->rgap_underline;
nd->url = TAKE_PTR(curl);
table_data_unref(od);
@@ -1101,6 +1147,31 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
goto check;
}
+ case TABLE_SET_UNDERLINE: {
+ int u = va_arg(ap, int);
+ r = table_set_underline(t, last_cell, u);
+ goto check;
+ }
+
+ case TABLE_SET_RGAP_UNDERLINE: {
+ int u = va_arg(ap, int);
+ r = table_set_rgap_underline(t, last_cell, u);
+ goto check;
+ }
+
+ case TABLE_SET_BOTH_UNDERLINES: {
+ int u = va_arg(ap, int);
+
+ r = table_set_underline(t, last_cell, u);
+ if (r < 0) {
+ va_end(ap);
+ return r;
+ }
+
+ r = table_set_rgap_underline(t, last_cell, u);
+ goto check;
+ }
+
case TABLE_SET_URL: {
const char *u = va_arg(ap, const char*);
r = table_set_url(t, last_cell, u);
@@ -1641,7 +1712,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
if (!p)
return NULL;
- if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
+ if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
return table_ersatz_string(t);
n = strlen(p);
@@ -1932,7 +2003,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
if (d->mode == MODE_INVALID)
return table_ersatz_string(t);
- return inode_type_to_string(d->mode);
+ return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
case TABLE_DEVNUM:
if (devnum_is_zero(d->devnum))
@@ -2130,8 +2201,6 @@ static const char* table_data_color(TableData *d) {
if (d->type == TABLE_FIELD)
return ansi_bright_blue();
- if (d->type == TABLE_HEADER)
- return ansi_underline();
return NULL;
}
@@ -2139,11 +2208,29 @@ static const char* table_data_color(TableData *d) {
static const char* table_data_rgap_color(TableData *d) {
assert(d);
- if (d->rgap_color)
- return d->rgap_color;
+ return d->rgap_color ?: d->rgap_color;
+}
+
+static const char* table_data_underline(TableData *d) {
+ assert(d);
+
+ if (d->underline)
+ return /* cescape( */ansi_add_underline_grey()/* ) */;
if (d->type == TABLE_HEADER)
- return ansi_underline();
+ return ansi_add_underline();
+
+ return NULL;
+}
+
+static const char* table_data_rgap_underline(TableData *d) {
+ assert(d);
+
+ if (d->rgap_underline)
+ return ansi_add_underline_grey();
+
+ if (d->type == TABLE_HEADER)
+ return ansi_add_underline();
return NULL;
}
@@ -2418,13 +2505,13 @@ int table_print(Table *t, FILE *f) {
row = t->data + i * t->n_columns;
do {
- const char *gap_color = NULL;
+ const char *gap_color = NULL, *gap_underline = NULL;
more_sublines = false;
for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *buffer = NULL, *extracted = NULL;
bool lines_truncated = false;
- const char *field, *color = NULL;
+ const char *field, *color = NULL, *underline = NULL;
TableData *d;
size_t l;
@@ -2490,6 +2577,7 @@ int table_print(Table *t, FILE *f) {
/* Drop trailing white spaces of last column when no cosmetics is set. */
if (j == display_columns - 1 &&
(!colors_enabled() || !table_data_color(d)) &&
+ (!underline_enabled() || !table_data_underline(d)) &&
(!urlify_enabled() || !d->url))
delete_trailing_chars(aligned, NULL);
@@ -2511,31 +2599,40 @@ int table_print(Table *t, FILE *f) {
if (colors_enabled() && gap_color)
fputs(gap_color, f);
+ if (underline_enabled() && gap_underline)
+ fputs(gap_underline, f);
if (j > 0)
fputc(' ', f); /* column separator left of cell */
+ /* Undo gap color/underline */
+ if ((colors_enabled() && gap_color) ||
+ (underline_enabled() && gap_underline))
+ fputs(ANSI_NORMAL, f);
+
if (colors_enabled()) {
color = table_data_color(d);
-
- /* Undo gap color */
- if (gap_color)
- fputs(ANSI_NORMAL, f);
-
if (color)
fputs(color, f);
}
+ if (underline_enabled()) {
+ underline = table_data_underline(d);
+ if (underline)
+ fputs(underline, f);
+ }
+
fputs(field, f);
- if (colors_enabled() && color)
+ if (color || underline)
fputs(ANSI_NORMAL, f);
gap_color = table_data_rgap_color(d);
+ gap_underline = table_data_rgap_underline(d);
}
fputc('\n', f);
- n_subline ++;
+ n_subline++;
} while (more_sublines);
}
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
index 37bfbca..b169eb0 100644
--- a/src/shared/format-table.h
+++ b/src/shared/format-table.h
@@ -68,6 +68,9 @@ typedef enum TableDataType {
TABLE_SET_COLOR,
TABLE_SET_RGAP_COLOR,
TABLE_SET_BOTH_COLORS,
+ TABLE_SET_UNDERLINE,
+ TABLE_SET_RGAP_UNDERLINE,
+ TABLE_SET_BOTH_UNDERLINES,
TABLE_SET_URL,
TABLE_SET_UPPERCASE,
@@ -111,6 +114,8 @@ int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_color(Table *t, TableCell *cell, const char *color);
int table_set_rgap_color(Table *t, TableCell *cell, const char *color);
+int table_set_underline(Table *t, TableCell *cell, bool b);
+int table_set_rgap_underline(Table *t, TableCell *cell, bool b);
int table_set_url(Table *t, TableCell *cell, const char *url);
int table_set_uppercase(Table *t, TableCell *cell, bool b);
@@ -129,7 +134,7 @@ int table_set_sort_internal(Table *t, size_t first_column, ...);
#define table_set_sort(...) table_set_sort_internal(__VA_ARGS__, SIZE_MAX)
int table_set_reverse(Table *t, size_t column, bool b);
int table_hide_column_from_display_internal(Table *t, ...);
-#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, (size_t) -1)
+#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX)
int table_print(Table *t, FILE *f);
int table_format(Table *t, char **ret);
@@ -139,6 +144,12 @@ static inline TableCell* TABLE_HEADER_CELL(size_t i) {
}
size_t table_get_rows(Table *t);
+static inline bool table_isempty(Table *t) {
+ if (!t)
+ return true;
+
+ return table_get_rows(t) <= 1;
+}
size_t table_get_columns(Table *t);
size_t table_get_current_column(Table *t);
diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c
index 55e76b6..60ceff5 100644
--- a/src/shared/fstab-util.c
+++ b/src/shared/fstab-util.c
@@ -74,8 +74,8 @@ bool fstab_is_extrinsic(const char *mount, const char *opts) {
"/run/initramfs", /* This should stay around from before we boot until after we shutdown */
"/run/nextroot", /* Similar (though might be updated from the host) */
"/proc", /* All of this is API VFS */
- "/sys", /* … dito … */
- "/dev")) /* … dito … */
+ "/sys", /* … ditto … */
+ "/dev")) /* … ditto … */
return true;
/* If this is an initrd mount, and we are not in the initrd, then leave
@@ -105,6 +105,34 @@ static int fstab_is_same_node(const char *what_fstab, const char *path) {
return false;
}
+int fstab_has_mount_point_prefix_strv(char **prefixes) {
+ _cleanup_endmntent_ FILE *f = NULL;
+
+ assert(prefixes);
+
+ /* This function returns true if at least one entry in fstab has a mount point that starts with one
+ * of the passed prefixes. */
+
+ if (!fstab_enabled())
+ return false;
+
+ f = setmntent(fstab_path(), "re");
+ if (!f)
+ return errno == ENOENT ? false : -errno;
+
+ for (;;) {
+ struct mntent *me;
+
+ errno = 0;
+ me = getmntent(f);
+ if (!me)
+ return errno != 0 ? -errno : false;
+
+ if (path_startswith_strv(me->mnt_dir, prefixes))
+ return true;
+ }
+}
+
int fstab_is_mount_point_full(const char *where, const char *path) {
_cleanup_endmntent_ FILE *f = NULL;
int r;
@@ -136,8 +164,6 @@ int fstab_is_mount_point_full(const char *where, const char *path) {
if (r > 0 || (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)))
return r;
}
-
- return false;
}
int fstab_filter_options(
@@ -148,16 +174,16 @@ int fstab_filter_options(
char ***ret_values,
char **ret_filtered) {
- const char *namefound = NULL, *x;
- _cleanup_strv_free_ char **stor = NULL, **values = NULL;
- _cleanup_free_ char *value = NULL, **filtered = NULL;
+ _cleanup_strv_free_ char **values = NULL;
+ _cleanup_free_ char *value = NULL, *filtered = NULL;
+ const char *namefound = NULL;
int r;
- assert(names && *names);
+ assert(!isempty(names));
assert(!(ret_value && ret_values));
if (!opts)
- goto answer;
+ goto finish;
/* Finds any options matching 'names', and returns:
* - the last matching option name in ret_namefound,
@@ -169,50 +195,49 @@ int fstab_filter_options(
*
* Returns negative on error, true if any matching options were found, false otherwise. */
- if (ret_filtered || ret_value || ret_values) {
+ if (ret_value || ret_values || ret_filtered) {
+ _cleanup_strv_free_ char **opts_split = NULL;
+ _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */
+
/* For backwards compatibility, we need to pass-through escape characters.
* The only ones we "consume" are the ones used as "\," or "\\". */
- r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX);
+ r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX);
if (r < 0)
return r;
- filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1));
- if (!filtered)
- return -ENOMEM;
+ STRV_FOREACH(opt, opts_split) {
+ bool found = false;
+ const char *x;
- char **t = filtered;
- for (char **s = t; *s; s++) {
NULSTR_FOREACH(name, names) {
- x = startswith(*s, name);
+ x = startswith(*opt, name);
if (!x)
continue;
- /* Match name, but when ret_values, only when followed by assignment. */
+
+ /* If ret_values, only accept settings followed by assignment. */
if (*x == '=' || (!ret_values && *x == '\0')) {
- /* Keep the last occurrence found */
namefound = name;
- goto found;
+ found = true;
+ break;
}
}
- *t = *s;
- t++;
- continue;
- found:
- if (ret_value || ret_values) {
- assert(IN_SET(*x, '=', '\0'));
-
- if (ret_value) {
+ if (found) {
+ if (ret_value)
r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL);
- if (r < 0)
- return r;
- } else if (*x) {
+ else if (ret_values)
r = strv_extend(&values, x + 1);
- if (r < 0)
- return r;
- }
- }
+ else
+ r = 0;
+ } else
+ r = strv_push(&filtered_strv, *opt);
+ if (r < 0)
+ return r;
}
- *t = NULL;
+
+ filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator = */ true);
+ if (!filtered)
+ return -ENOMEM;
} else
for (const char *word = opts;;) {
const char *end = word;
@@ -226,64 +251,55 @@ int fstab_filter_options(
if (IN_SET(*end, ',', '\0'))
break;
assert(*end == '\\');
- end ++; /* Skip the backslash */
+ end++; /* Skip the backslash */
if (*end != '\0')
- end ++; /* Skip the escaped char, but watch out for a trailing comma */
+ end++; /* Skip the escaped char, but watch out for a trailing comma */
}
NULSTR_FOREACH(name, names) {
- if (end < word + strlen(name))
- continue;
- if (!strneq(word, name, strlen(name)))
+ const char *x = startswith(word, name);
+ if (!x || x > end)
continue;
/* We know that the string is NUL terminated, so *x is valid */
- x = word + strlen(name);
if (IN_SET(*x, '\0', '=', ',')) {
namefound = name;
break;
}
}
- if (*end)
- word = end + 1;
- else
+ if (*end == '\0')
break;
+
+ word = end + 1;
}
-answer:
+finish:
if (ret_namefound)
- *ret_namefound = namefound;
- if (ret_filtered) {
- char *f;
-
- f = strv_join_full(filtered, ",", NULL, true);
- if (!f)
- return -ENOMEM;
-
- *ret_filtered = f;
- }
+ *ret_namefound = namefound; /* owned by 'names' (passed-in) */
if (ret_value)
*ret_value = TAKE_PTR(value);
if (ret_values)
*ret_values = TAKE_PTR(values);
+ if (ret_filtered)
+ *ret_filtered = TAKE_PTR(filtered);
return !!namefound;
}
-int fstab_find_pri(const char *options, int *ret) {
- _cleanup_free_ char *opt = NULL;
+int fstab_find_pri(const char *opts, int *ret) {
+ _cleanup_free_ char *v = NULL;
int r, pri;
assert(ret);
- r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL);
+ r = fstab_filter_options(opts, "pri\0", NULL, &v, NULL, NULL);
if (r < 0)
return r;
- if (r == 0 || !opt)
+ if (r == 0 || !v)
return 0;
- r = safe_atoi(opt, &pri);
+ r = safe_atoi(v, &pri);
if (r < 0)
return r;
diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h
index 9cf34f0..040f076 100644
--- a/src/shared/fstab-util.h
+++ b/src/shared/fstab-util.h
@@ -25,6 +25,8 @@ static inline int fstab_has_node(const char *path) {
return fstab_is_mount_point_full(NULL, path);
}
+int fstab_has_mount_point_prefix_strv(char **prefixes);
+
int fstab_filter_options(
const char *opts,
const char *names,
@@ -32,23 +34,20 @@ int fstab_filter_options(
char **ret_value,
char ***ret_values,
char **ret_filtered);
-
static inline bool fstab_test_option(const char *opts, const char *names) {
- return !!fstab_filter_options(opts, names, NULL, NULL, NULL, NULL);
+ return fstab_filter_options(opts, names, NULL, NULL, NULL, NULL);
}
-
-int fstab_find_pri(const char *options, int *ret);
-
static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) {
- const char *opt;
+ const char *opt_found;
/* If first name given is last, return 1.
* If second name given is last or neither is found, return 0. */
- assert_se(fstab_filter_options(opts, yes_no, &opt, NULL, NULL, NULL) >= 0);
+ assert_se(fstab_filter_options(opts, yes_no, &opt_found, NULL, NULL, NULL) >= 0);
- return opt == yes_no;
+ return opt_found == yes_no;
}
+int fstab_find_pri(const char *opts, int *ret);
char *fstab_node_to_udev_node(const char *p);
diff --git a/src/shared/generator.c b/src/shared/generator.c
index 5626587..1b3304a 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -15,6 +15,7 @@
#include "log.h"
#include "macro.h"
#include "mkdir-label.h"
+#include "mountpoint-util.h"
#include "path-util.h"
#include "process-util.h"
#include "special.h"
@@ -29,6 +30,7 @@ int generator_open_unit_file_full(
const char *source,
const char *fn,
FILE **ret_file,
+ char **ret_final_path,
char **ret_temp_path) {
_cleanup_free_ char *p = NULL;
@@ -72,10 +74,13 @@ int generator_open_unit_file_full(
program_invocation_short_name);
*ret_file = f;
+
+ if (ret_final_path)
+ *ret_final_path = TAKE_PTR(p);
+
return 0;
}
-
int generator_add_symlink_full(
const char *dir,
const char *dst,
@@ -88,11 +93,13 @@ int generator_add_symlink_full(
assert(dir);
assert(dst);
- assert(dep_type);
assert(src);
- /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If
- * <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
+ /* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise).
+ *
+ * If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias.
+ *
+ * If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */
r = path_extract_directory(src, &dn);
if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */
@@ -110,11 +117,19 @@ int generator_add_symlink_full(
return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance);
}
- from = path_join(dn ?: "..", fn);
- if (!from)
- return log_oom();
+ if (dep_type) { /* Create a .wants/ style dep */
+ from = path_join(dn ?: "..", fn);
+ if (!from)
+ return log_oom();
+
+ to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ } else { /* or create an alias */
+ from = dn ? path_join(dn, fn) : strdup(fn);
+ if (!from)
+ return log_oom();
- to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn);
+ to = strjoin(dir, "/", dst);
+ }
if (!to)
return log_oom();
@@ -694,6 +709,77 @@ int generator_hook_up_pcrfs(
return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance);
}
+int generator_hook_up_quotacheck(
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *target,
+ const char *fstype) {
+
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
+ int r;
+
+ assert(dir);
+ assert(where);
+
+ if (isempty(fstype) || streq(fstype, "auto"))
+ return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what);
+ if (!fstype_needs_quota(fstype))
+ return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype);
+
+ /* quotacheck unit for system root */
+ if (path_equal(where, "/"))
+ return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE);
+
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTACHECK_SERVICE, instance);
+ if (r < 0)
+ return r;
+ }
+
+ r = unit_name_from_path(where, ".mount", &where_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
+
+ return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance);
+}
+
+int generator_hook_up_quotaon(
+ const char *dir,
+ const char *where,
+ const char *target) {
+
+ _cleanup_free_ char *where_unit = NULL, *instance = NULL;
+ int r;
+
+ assert(dir);
+ assert(where);
+
+ /* quotaon unit for system root is not instantiated */
+ if (path_equal(where, "/"))
+ return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_ROOT_SERVICE);
+
+ r = unit_name_path_escape(where, &instance);
+ if (r < 0)
+ return log_error_errno(r, "Failed to escape path '%s': %m", where);
+
+ if (target) {
+ r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTAON_SERVICE, instance);
+ if (r < 0)
+ return r;
+ }
+
+ r = unit_name_from_path(where, ".mount", &where_unit);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
+
+ return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_SERVICE, instance);
+}
+
int generator_enable_remount_fs_service(const char *dir) {
/* Pull in systemd-remount-fs.service */
return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants",
@@ -790,6 +876,7 @@ int generator_write_cryptsetup_service_section(
"TimeoutSec=infinity\n" /* The binary handles timeouts on its own */
"KeyringMode=shared\n" /* Make sure we can share cached keys among instances */
"OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */
+ "ImportCredential=cryptsetup.*\n"
"ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
"ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
name_escaped, what_escaped, strempty(key_file_escaped), strempty(options_escaped),
@@ -883,6 +970,5 @@ void log_setup_generator(void) {
log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
}
- log_parse_environment();
- (void) log_open();
+ log_setup();
}
diff --git a/src/shared/generator.h b/src/shared/generator.h
index d97d6ed..baf1daf 100644
--- a/src/shared/generator.h
+++ b/src/shared/generator.h
@@ -6,10 +6,10 @@
#include "macro.h"
#include "main-func.h"
-int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path);
+int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path);
static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) {
- return generator_open_unit_file_full(dest, source, name, ret_file, NULL);
+ return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL);
}
int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance);
@@ -85,6 +85,16 @@ int generator_hook_up_pcrfs(
const char *dir,
const char *where,
const char *target);
+int generator_hook_up_quotacheck(
+ const char *dir,
+ const char *what,
+ const char *where,
+ const char *target,
+ const char *fstype);
+int generator_hook_up_quotaon(
+ const char *dir,
+ const char *where,
+ const char *target);
int generator_enable_remount_fs_service(const char *dir);
@@ -102,4 +112,5 @@ void log_setup_generator(void);
impl(argv[1], \
argv[argc == 4 ? 2 : 1], \
argv[argc == 4 ? 3 : 1]), \
- r < 0 ? EXIT_FAILURE : EXIT_SUCCESS)
+ exit_failure_if_negative, \
+ exit_failure_if_negative)
diff --git a/src/shared/group-record.c b/src/shared/group-record.c
index 1e33bdf..6c1e41a 100644
--- a/src/shared/group-record.c
+++ b/src/shared/group-record.c
@@ -2,7 +2,7 @@
#include "group-record.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-util.h"
GroupRecord* group_record_new(void) {
@@ -102,33 +102,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
- bool matching = false;
- JsonVariant *m;
-
if (!json_variant_is_object(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
- m = json_variant_by_key(e, "matchMachineId");
- if (m) {
- r = per_machine_id_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
-
- if (!matching) {
- m = json_variant_by_key(e, "matchHostname");
- if (m) {
- r = per_machine_hostname_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
- }
-
- if (!matching)
+ r = per_machine_match(e, flags);
+ if (r < 0)
+ return r;
+ if (r == 0)
continue;
r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
@@ -230,7 +210,7 @@ int group_record_load(
if (r < 0)
return r;
- r = json_dispatch(h->json, group_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c
index c3991cf..7c21157 100644
--- a/src/shared/hibernate-util.c
+++ b/src/shared/hibernate-util.c
@@ -159,8 +159,9 @@ static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) {
}
if (devno == 0 && offset > 0 && offset != UINT64_MAX)
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset);
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+ "Found populated /sys/power/resume_offset (%" PRIu64 ") but /sys/power/resume is not set, refusing.",
+ offset);
*ret_devno = devno;
*ret_offset = offset;
@@ -393,7 +394,7 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_
if (!entry) {
/* No need to check n_swaps == 0, since it's rejected early */
assert(resume_config_devno > 0);
- return log_debug_errno(SYNTHETIC_ERRNO(ENOSPC), "Cannot find swap entry corresponding to /sys/power/resume.");
+ return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Cannot find swap entry corresponding to /sys/power/resume.");
}
if (ret_device) {
@@ -451,11 +452,11 @@ int hibernation_is_safe(void) {
bypass_space_check = getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0;
r = find_suitable_hibernation_device_full(NULL, &size, &used);
- if (r == -ENOSPC && bypass_space_check)
- /* If we don't have any available swap space at all, and SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK
- * is set, skip all remaining checks since we can't do that properly anyway. It is quite
- * possible that the user is using a setup similar to #30083. When we actually perform
- * hibernation in sleep.c we'll check everything again. */
+ if (IN_SET(r, -ENOSPC, -ESTALE) && bypass_space_check)
+ /* If we don't have any available swap space at all, or the specified resume device is missing,
+ * and $SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK is set, skip all remaining checks since
+ * we can't do that properly anyway. It is quite possible that the user is using a setup
+ * similar to #30083. When we actually perform hibernation in sleep.c we'll check everything again. */
return 0;
if (r < 0)
return r;
@@ -466,7 +467,7 @@ int hibernation_is_safe(void) {
"Not running on EFI and resume= is not set. Hibernation is not safe.");
if (bypass_space_check)
- return true;
+ return 0;
r = get_proc_meminfo_active(&active);
if (r < 0)
@@ -483,30 +484,23 @@ int hibernation_is_safe(void) {
int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
- _cleanup_free_ char *path = NULL;
const char *devno_str;
int r;
+ assert(devno > 0);
+ assert(device);
+
devno_str = FORMAT_DEVNUM(devno);
xsprintf(offset_str, "%" PRIu64, offset);
- if (!device) {
- r = device_path_make_canonical(S_IFBLK, devno, &path);
- if (r < 0)
- return log_error_errno(r,
- "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
- DEVNUM_FORMAT_VAL(devno));
- device = path;
- }
-
/* We write the offset first since it's safer. Note that this file is only available in 4.17+, so
* fail gracefully if it doesn't exist and we're only overwriting it with 0. */
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r == -ENOENT) {
if (offset != 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Can't configure hibernation offset %" PRIu64 ", kernel does not support /sys/power/resume_offset. Refusing.",
- offset);
+ "Can't configure swap file offset %s, kernel does not support /sys/power/resume_offset. Refusing.",
+ offset_str);
log_warning_errno(r, "/sys/power/resume_offset is unavailable, skipping writing swap file offset.");
} else if (r < 0)
@@ -526,3 +520,18 @@ int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
return 0;
}
+
+int clear_efi_hibernate_location_and_warn(void) {
+ int r;
+
+ if (!is_efi_boot())
+ return 0;
+
+ r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "Failed to clear HibernateLocation EFI variable: %m");
+
+ return 1;
+}
diff --git a/src/shared/hibernate-util.h b/src/shared/hibernate-util.h
index 2ae10fb..394d0b4 100644
--- a/src/shared/hibernate-util.h
+++ b/src/shared/hibernate-util.h
@@ -22,5 +22,7 @@ int hibernation_is_safe(void);
int write_resume_config(dev_t devno, uint64_t offset, const char *device);
+int clear_efi_hibernate_location_and_warn(void);
+
/* Only for test-fiemap */
int read_fiemap(int fd, struct fiemap **ret);
diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c
index 137c29a..6cfd4b5 100644
--- a/src/shared/hostname-setup.c
+++ b/src/shared/hostname-setup.c
@@ -6,12 +6,16 @@
#include <sys/utsname.h>
#include <unistd.h>
+#include "sd-daemon.h"
+
#include "alloc-util.h"
+#include "creds-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-setup.h"
#include "hostname-util.h"
+#include "initrd-util.h"
#include "log.h"
#include "macro.h"
#include "proc-cmdline.h"
@@ -23,7 +27,8 @@ static int sethostname_idempotent_full(const char *s, bool really) {
assert(s);
- assert_se(uname(&u) >= 0);
+ if (uname(&u) < 0)
+ return -errno;
if (streq_ptr(s, u.nodename))
return 0;
@@ -40,37 +45,56 @@ int sethostname_idempotent(const char *s) {
}
int shorten_overlong(const char *s, char **ret) {
- char *h, *p;
+ _cleanup_free_ char *h = NULL;
/* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
* whatever comes earlier. */
assert(s);
+ assert(ret);
h = strdup(s);
if (!h)
return -ENOMEM;
if (hostname_is_valid(h, 0)) {
- *ret = h;
+ *ret = TAKE_PTR(h);
return 0;
}
- p = strchr(h, '.');
+ char *p = strchr(h, '.');
if (p)
*p = 0;
strshorten(h, HOST_NAME_MAX);
- if (!hostname_is_valid(h, 0)) {
- free(h);
+ if (!hostname_is_valid(h, /* flags= */ 0))
return -EDOM;
- }
- *ret = h;
+ *ret = TAKE_PTR(h);
return 1;
}
+static int acquire_hostname_from_credential(char **ret) {
+ _cleanup_free_ char *cred = NULL;
+ int r;
+
+ assert(ret);
+
+ r = read_credential_with_decryption("system.hostname", (void **) &cred, /* ret_size= */ NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to read system.hostname credential, ignoring: %m");
+ if (r == 0) /* not found */
+ return -ENXIO;
+
+ if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */
+ return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred);
+
+ log_info("Initializing hostname from credential.");
+ *ret = TAKE_PTR(cred);
+ return 0;
+}
+
int read_etc_hostname_stream(FILE *f, char **ret) {
int r;
@@ -126,66 +150,64 @@ void hostname_update_source_hint(const char *hostname, HostnameSource source) {
r = write_string_file("/run/systemd/default-hostname", hostname,
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC);
if (r < 0)
- log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\": %m");
+ log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\", ignoring: %m");
} else
unlink_or_warn("/run/systemd/default-hostname");
}
int hostname_setup(bool really) {
- _cleanup_free_ char *b = NULL;
- const char *hn = NULL;
+ _cleanup_free_ char *hn = NULL;
HostnameSource source;
bool enoent = false;
int r;
- r = proc_cmdline_get_key("systemd.hostname", 0, &b);
+ r = proc_cmdline_get_key("systemd.hostname", 0, &hn);
if (r < 0)
log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m");
else if (r > 0) {
- if (hostname_is_valid(b, VALID_HOSTNAME_TRAILING_DOT)) {
- hn = b;
+ if (hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT))
source = HOSTNAME_TRANSIENT;
- } else {
- log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b);
- b = mfree(b);
+ else {
+ log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", hn);
+ hn = mfree(hn);
}
}
if (!hn) {
- r = read_etc_hostname(NULL, &b);
- if (r < 0) {
- if (r == -ENOENT)
- enoent = true;
- else
- log_warning_errno(r, "Failed to read configured hostname: %m");
- } else {
- hn = b;
+ r = read_etc_hostname(NULL, &hn);
+ if (r == -ENOENT)
+ enoent = true;
+ else if (r < 0)
+ log_warning_errno(r, "Failed to read configured hostname, ignoring: %m");
+ else
source = HOSTNAME_STATIC;
- }
}
if (!hn) {
- _cleanup_free_ char *buf = NULL;
+ r = acquire_hostname_from_credential(&hn);
+ if (r >= 0)
+ source = HOSTNAME_TRANSIENT;
+ }
+ if (!hn) {
/* Don't override the hostname if it is already set and not explicitly configured */
- r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &buf);
+ r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &hn);
if (r == -ENOMEM)
return log_oom();
if (r >= 0) {
- log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf);
- return 0;
+ log_debug("No hostname configured, leaving existing hostname <%s> in place.", hn);
+ goto finish;
}
if (enoent)
log_info("No hostname configured, using default hostname.");
- hn = b = get_default_hostname();
+ hn = get_default_hostname();
if (!hn)
return log_oom();
source = HOSTNAME_DEFAULT;
-
}
r = sethostname_idempotent_full(hn, really);
@@ -201,7 +223,11 @@ int hostname_setup(bool really) {
if (really)
hostname_update_source_hint(hn, source);
- return r;
+finish:
+ if (!in_initrd())
+ (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_HOSTNAME=%s", hn);
+
+ return 0;
}
static const char* const hostname_source_table[] = {
diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c
index f67e917..d96902c 100644
--- a/src/shared/hwdb-util.c
+++ b/src/shared/hwdb-util.c
@@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "conf-files.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -483,7 +484,7 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
if (r == 0)
break;
- line_number ++;
+ line_number++;
/* comment line */
if (line[0] == '#')
@@ -710,3 +711,16 @@ bool hwdb_should_reload(sd_hwdb *hwdb) {
return true;
return false;
}
+
+int hwdb_bypass(void) {
+ int r;
+
+ r = getenv_bool("SYSTEMD_HWDB_UPDATE_BYPASS");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_HWDB_UPDATE_BYPASS, assuming no.");
+ if (r <= 0)
+ return false;
+
+ log_debug("$SYSTEMD_HWDB_UPDATE_BYPASS is enabled, skipping execution.");
+ return true;
+}
diff --git a/src/shared/hwdb-util.h b/src/shared/hwdb-util.h
index cb93690..00610b1 100644
--- a/src/shared/hwdb-util.h
+++ b/src/shared/hwdb-util.h
@@ -8,3 +8,4 @@
bool hwdb_should_reload(sd_hwdb *hwdb);
int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool compat);
int hwdb_query(const char *modalias, const char *root);
+int hwdb_bypass(void);
diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c
index 26a9d60..aa88e11 100644
--- a/src/shared/idn-util.c
+++ b/src/shared/idn-util.c
@@ -16,11 +16,16 @@ static void* idn_dl = NULL;
#endif
#if HAVE_LIBIDN2
-int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags) = NULL;
+DLSYM_FUNCTION(idn2_lookup_u8);
const char *(*sym_idn2_strerror)(int rc) _const_ = NULL;
-int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags) = NULL;
+DLSYM_FUNCTION(idn2_to_unicode_8z8z);
int dlopen_idn(void) {
+ ELF_NOTE_DLOPEN("idn",
+ "Support for internationalized domain names",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libidn2.so.0");
+
return dlopen_many_sym_or_warn(
&idn_dl, "libidn2.so.0", LOG_DEBUG,
DLSYM_ARG(idn2_lookup_u8),
@@ -30,15 +35,20 @@ int dlopen_idn(void) {
#endif
#if HAVE_LIBIDN
-int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags);
-int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen, uint32_t * out, size_t * outlen, int flags);
-char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written);
-uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written);
+DLSYM_FUNCTION(idna_to_ascii_4i);
+DLSYM_FUNCTION(idna_to_unicode_44i);
+DLSYM_FUNCTION(stringprep_ucs4_to_utf8);
+DLSYM_FUNCTION(stringprep_utf8_to_ucs4);
int dlopen_idn(void) {
_cleanup_(dlclosep) void *dl = NULL;
int r;
+ ELF_NOTE_DLOPEN("idn",
+ "Support for internationalized domain names",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libidn.so.12", "libidn.so.11");
+
if (idn_dl)
return 0; /* Already loaded */
diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h
index e64bd99..3ae2f13 100644
--- a/src/shared/idn-util.h
+++ b/src/shared/idn-util.h
@@ -11,6 +11,8 @@
#include <inttypes.h>
#if HAVE_LIBIDN2 || HAVE_LIBIDN
+#include "dlfcn-util.h"
+
int dlopen_idn(void);
#else
static inline int dlopen_idn(void) {
@@ -19,14 +21,14 @@ static inline int dlopen_idn(void) {
#endif
#if HAVE_LIBIDN2
-extern int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags);
+DLSYM_PROTOTYPE(idn2_lookup_u8);
extern const char *(*sym_idn2_strerror)(int rc) _const_;
-extern int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags);
+DLSYM_PROTOTYPE(idn2_to_unicode_8z8z);
#endif
#if HAVE_LIBIDN
-extern int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags);
-extern int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags);
-extern char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written);
-extern uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written);
+DLSYM_PROTOTYPE(idna_to_ascii_4i);
+DLSYM_PROTOTYPE(idna_to_unicode_44i);
+DLSYM_PROTOTYPE(stringprep_ucs4_to_utf8);
+DLSYM_PROTOTYPE(stringprep_utf8_to_ucs4);
#endif
diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c
index 3c3de50..e7bd84e 100644
--- a/src/shared/image-policy.c
+++ b/src/shared/image-policy.c
@@ -50,6 +50,20 @@ PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) {
return flags;
}
+PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags) {
+ /* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all
+ * possible options, let's remove it from the flags to make them shorter */
+
+ if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK))
+ flags &= ~_PARTITION_POLICY_USE_MASK;
+ if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK))
+ flags &= ~_PARTITION_POLICY_READ_ONLY_MASK;
+ if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK))
+ flags &= ~_PARTITION_POLICY_GROWFS_MASK;
+
+ return flags;
+}
+
static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
@@ -676,6 +690,90 @@ int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
return free_and_replace_full(*policy, np, image_policy_free);
}
+static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) {
+
+ if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
+ return true;
+ if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
+ return true;
+ if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
+ return true;
+
+ return false;
+}
+
+int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
+ _cleanup_(image_policy_freep) ImagePolicy *p = NULL;
+
+ /* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This
+ * might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root
+ * partition my neither be used, nor be absent, nor be unused then this is considered
+ * "impossible". */
+
+ p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
+ if (!p)
+ return -ENOMEM;
+
+ p->default_flags =
+ partition_policy_flags_extend(image_policy_default(a)) &
+ partition_policy_flags_extend(image_policy_default(b));
+
+ if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */
+ return -ENAVAIL;
+
+ p->default_flags = partition_policy_flags_reduce(p->default_flags);
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags x, y, z, df;
+
+ /* If this designator has no entry in either policy we don't need to include it in the intersection either. */
+ if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d))
+ continue;
+
+ /* Expand this policy flags field to the "long" form, i.e. for each part of the flags that
+ * are left unspcified add in all possible options */
+ x = image_policy_get_exhaustively(a, d);
+ if (x < 0)
+ return x;
+
+ y = image_policy_get_exhaustively(b, d);
+ if (y < 0)
+ return y;
+
+ /* Mask it */
+ z = x & y;
+
+ /* Check if the intersection is empty for this partition. If so, generate a clear error */
+ if (partition_policy_flags_has_unspecified(z))
+ return -ENAVAIL;
+
+ df = partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = image_policy_default(p),
+ .designator = d,
+ });
+ if (df < 0)
+ return df;
+ if (df == z) /* Same as default? then let's skip this */
+ continue;
+
+ /* image_policy_get_exhaustively() may have extended the flags mask to include all
+ * read-only/growfs flags if not set. Let's remove them again, if they are both set to
+ * minimize the policy again. */
+ z = partition_policy_flags_reduce(z);
+
+ p->policies[p->n_policies++] = (struct PartitionPolicy) {
+ .designator = d,
+ .flags = z,
+ };
+ }
+
+ if (ret)
+ *ret = TAKE_PTR(p);
+
+ return 0;
+}
+
const ImagePolicy image_policy_allow = {
/* Allow policy */
.n_policies = 0,
@@ -726,6 +824,14 @@ const ImagePolicy image_policy_confext = {
.default_flags = PARTITION_POLICY_IGNORE,
};
+const ImagePolicy image_policy_confext_strict = {
+ .n_policies = 1,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
const ImagePolicy image_policy_container = {
/* For systemd-nspawn containers we use all partitions, with the exception of swap */
.n_policies = 8,
diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h
index f59c16e..a1a6afa 100644
--- a/src/shared/image-policy.h
+++ b/src/shared/image-policy.h
@@ -58,9 +58,10 @@ struct ImagePolicy {
extern const ImagePolicy image_policy_allow;
extern const ImagePolicy image_policy_deny;
extern const ImagePolicy image_policy_ignore;
-extern const ImagePolicy image_policy_sysext; /* No verity required */
-extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
-extern const ImagePolicy image_policy_confext; /* No verity required */
+extern const ImagePolicy image_policy_sysext; /* No verity required */
+extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
+extern const ImagePolicy image_policy_confext; /* No verity required */
+extern const ImagePolicy image_policy_confext_strict; /* Signed verity required */
extern const ImagePolicy image_policy_container;
extern const ImagePolicy image_policy_service;
extern const ImagePolicy image_policy_host;
@@ -79,6 +80,7 @@ static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
}
PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags);
+PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags);
PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
@@ -94,6 +96,8 @@ bool image_policy_equiv_deny(const ImagePolicy *policy);
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
+int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret);
+
static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
return mfree(p);
}
diff --git a/src/shared/in-addr-prefix-util.c b/src/shared/in-addr-prefix-util.c
index 7c0033d..edccca5 100644
--- a/src/shared/in-addr-prefix-util.c
+++ b/src/shared/in-addr-prefix-util.c
@@ -59,9 +59,9 @@ static void in_addr_prefix_hash_func(const struct in_addr_prefix *a, struct siph
assert(a);
assert(state);
- siphash24_compress(&a->family, sizeof(a->family), state);
- siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
- siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
+ siphash24_compress_typesafe(a->family, state);
+ siphash24_compress_typesafe(a->prefixlen, state);
+ in_addr_hash_func(&a->address, a->family, state);
}
static int in_addr_prefix_compare_func(const struct in_addr_prefix *x, const struct in_addr_prefix *y) {
diff --git a/src/shared/initreq.h b/src/shared/initreq.h
index da9783c..c6a1050 100644
--- a/src/shared/initreq.h
+++ b/src/shared/initreq.h
@@ -1,14 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.0-or-later */
/*
- * initreq.h Interface to talk to init through /dev/initctl.
- *
- * Copyright (C) 1995-2004 Miquel van Smoorenburg
- *
- * This library 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 of the License, or (at your option) any later version.
- *
+ * Copyright (C) 1995-2004 Miquel van Smoorenburg
* Version: @(#)initreq.h 1.28 31-Mar-2004 MvS
*/
diff --git a/src/shared/install-file.c b/src/shared/install-file.c
index 3b4d651..ea2189f 100644
--- a/src/shared/install-file.c
+++ b/src/shared/install-file.c
@@ -12,7 +12,7 @@
#include "rm-rf.h"
#include "sync-util.h"
-int fs_make_very_read_only(int fd) {
+static int fs_make_very_read_only(int fd) {
struct stat st;
int r;
diff --git a/src/shared/install-file.h b/src/shared/install-file.h
index c37254f..af07ab2 100644
--- a/src/shared/install-file.h
+++ b/src/shared/install-file.h
@@ -1,8 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
-int fs_make_very_read_only(int fd);
-
typedef enum InstallFileFlags {
INSTALL_REPLACE = 1 << 0, /* Replace an existing inode */
INSTALL_READ_ONLY = 1 << 1, /* Call fs_make_very_read_only() to make the inode comprehensively read-only */
diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c
index 3cc7093..bdb44b3 100644
--- a/src/shared/install-printf.c
+++ b/src/shared/install-printf.c
@@ -37,16 +37,11 @@ static int specifier_prefix_and_instance(char specifier, const void *data, const
static int specifier_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
const InstallInfo *i = ASSERT_PTR(userdata);
- char *ans;
if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE) && i->default_instance)
return unit_name_replace_instance(i->name, i->default_instance, ret);
- ans = strdup(i->name);
- if (!ans)
- return -ENOMEM;
- *ret = ans;
- return 0;
+ return strdup_to(ret, i->name);
}
static int specifier_prefix(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
@@ -86,14 +81,10 @@ static int specifier_last_component(char specifier, const void *data, const char
return r;
dash = strrchr(prefix, '-');
- if (dash) {
- dash = strdup(dash + 1);
- if (!dash)
- return -ENOMEM;
- *ret = dash;
- } else
- *ret = TAKE_PTR(prefix);
+ if (dash)
+ return strdup_to(ret, dash + 1);
+ *ret = TAKE_PTR(prefix);
return 0;
}
diff --git a/src/shared/install.c b/src/shared/install.c
index 27a421e..dd2bd5c 100644
--- a/src/shared/install.c
+++ b/src/shared/install.c
@@ -10,6 +10,7 @@
#include <unistd.h>
#include "alloc-util.h"
+#include "bus-common-errors.h"
#include "chase.h"
#include "conf-files.h"
#include "conf-parser.h"
@@ -161,9 +162,10 @@ static int path_is_generator(const LookupPaths *lp, const char *path) {
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->generator) ||
- path_equal_ptr(parent, lp->generator_early) ||
- path_equal_ptr(parent, lp->generator_late);
+ return PATH_IN_SET(parent,
+ lp->generator,
+ lp->generator_early,
+ lp->generator_late);
}
static int path_is_transient(const LookupPaths *lp, const char *path) {
@@ -177,7 +179,7 @@ static int path_is_transient(const LookupPaths *lp, const char *path) {
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->transient);
+ return path_equal(parent, lp->transient);
}
static int path_is_control(const LookupPaths *lp, const char *path) {
@@ -191,8 +193,9 @@ static int path_is_control(const LookupPaths *lp, const char *path) {
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->persistent_control) ||
- path_equal_ptr(parent, lp->runtime_control);
+ return PATH_IN_SET(parent,
+ lp->persistent_control,
+ lp->runtime_control);
}
static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) {
@@ -213,8 +216,9 @@ static int path_is_config(const LookupPaths *lp, const char *path, bool check_pa
path = parent;
}
- return path_equal_ptr(path, lp->persistent_config) ||
- path_equal_ptr(path, lp->runtime_config);
+ return PATH_IN_SET(path,
+ lp->persistent_config,
+ lp->runtime_config);
}
static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) {
@@ -240,12 +244,13 @@ static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_p
path = parent;
}
- return path_equal_ptr(path, lp->runtime_config) ||
- path_equal_ptr(path, lp->generator) ||
- path_equal_ptr(path, lp->generator_early) ||
- path_equal_ptr(path, lp->generator_late) ||
- path_equal_ptr(path, lp->transient) ||
- path_equal_ptr(path, lp->runtime_control);
+ return PATH_IN_SET(path,
+ lp->runtime_config,
+ lp->generator,
+ lp->generator_early,
+ lp->generator_late,
+ lp->transient,
+ lp->runtime_control);
}
static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) {
@@ -324,130 +329,176 @@ InstallChangeType install_changes_add(
void install_changes_free(InstallChange *changes, size_t n_changes) {
assert(changes || n_changes == 0);
- for (size_t i = 0; i < n_changes; i++) {
- free(changes[i].path);
- free(changes[i].source);
+ FOREACH_ARRAY(i, changes, n_changes) {
+ free(i->path);
+ free(i->source);
}
free(changes);
}
-void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet) {
- int err = 0;
+static void install_change_dump_success(const InstallChange *change) {
+ assert(change);
+ assert(change->path);
- assert(changes || n_changes == 0);
- /* If verb is not specified, errors are not allowed! */
- assert(verb || r >= 0);
+ switch (change->type) {
- for (size_t i = 0; i < n_changes; i++) {
- assert(changes[i].path);
- /* This tries to tell the compiler that it's safe to use 'verb' in a string format if there
- * was an error, but the compiler doesn't care and fails anyway, so strna(verb) is used
- * too. */
- assert(verb || changes[i].type >= 0);
- verb = strna(verb);
+ case INSTALL_CHANGE_SYMLINK:
+ return log_info("Created symlink '%s' %s '%s'.",
+ change->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), change->source);
- /* When making changes here, make sure to also change install_error() in dbus-manager.c. */
+ case INSTALL_CHANGE_UNLINK:
+ return log_info("Removed '%s'.", change->path);
- switch (changes[i].type) {
- case INSTALL_CHANGE_SYMLINK:
- if (!quiet)
- log_info("Created symlink %s %s %s.",
- changes[i].path,
- special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
- changes[i].source);
- break;
- case INSTALL_CHANGE_UNLINK:
- if (!quiet)
- log_info("Removed \"%s\".", changes[i].path);
- break;
- case INSTALL_CHANGE_IS_MASKED:
- if (!quiet)
- log_info("Unit %s is masked, ignoring.", changes[i].path);
- break;
- case INSTALL_CHANGE_IS_MASKED_GENERATOR:
- if (!quiet)
- log_info("Unit %s is masked via a generator and cannot be unmasked.",
- changes[i].path);
- break;
- case INSTALL_CHANGE_IS_DANGLING:
- if (!quiet)
- log_info("Unit %s is an alias to a unit that is not present, ignoring.",
- changes[i].path);
- break;
- case INSTALL_CHANGE_DESTINATION_NOT_PRESENT:
- if (!quiet)
- log_warning("Unit %s is added as a dependency to a non-existent unit %s.",
- changes[i].source, changes[i].path);
- break;
- case INSTALL_CHANGE_AUXILIARY_FAILED:
+ case INSTALL_CHANGE_IS_MASKED:
+ return log_info("Unit %s is masked, ignoring.", change->path);
+
+ case INSTALL_CHANGE_IS_MASKED_GENERATOR:
+ return log_info("Unit %s is masked via a generator and cannot be unmasked, skipping.", change->path);
+
+ case INSTALL_CHANGE_IS_DANGLING:
+ return log_info("Unit %s is an alias to a non-existent unit, ignoring.", change->path);
+
+ case INSTALL_CHANGE_DESTINATION_NOT_PRESENT:
+ return log_warning("Unit %s is added as a dependency to a non-existent unit %s.",
+ change->source, change->path);
+
+ case INSTALL_CHANGE_AUXILIARY_FAILED:
+ return log_warning("Failed to enable auxiliary unit %s, ignoring.", change->path);
+
+ default:
+ assert_not_reached();
+ }
+}
+
+int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error) {
+ char *m;
+ const char *bus_error;
+
+ /* Returns 0: known error and ret_errmsg formatted
+ * < 0: non-recognizable error */
+
+ assert(change);
+ assert(change->path);
+ assert(change->type < 0);
+ assert(ret_errmsg);
+
+ switch (change->type) {
+
+ case -EEXIST:
+ m = strjoin("File '", change->path, "' already exists",
+ change->source ? " and is a symlink to " : NULL, change->source);
+ bus_error = BUS_ERROR_UNIT_EXISTS;
+ break;
+
+ case -ERFKILL:
+ m = strjoin("Unit ", change->path, " is masked");
+ bus_error = BUS_ERROR_UNIT_MASKED;
+ break;
+
+ case -EADDRNOTAVAIL:
+ m = strjoin("Unit ", change->path, " is transient or generated");
+ bus_error = BUS_ERROR_UNIT_GENERATED;
+ break;
+
+ case -ETXTBSY:
+ m = strjoin("File '", change->path, "' is under the systemd unit hierarchy already");
+ bus_error = BUS_ERROR_UNIT_BAD_PATH;
+ break;
+
+ case -EBADSLT:
+ m = strjoin("Invalid specifier in unit ", change->path);
+ bus_error = BUS_ERROR_BAD_UNIT_SETTING;
+ break;
+
+ case -EIDRM:
+ m = strjoin("Refusing to operate on template unit ", change->source,
+ " when destination unit ", change->path, " is a non-template unit");
+ bus_error = BUS_ERROR_BAD_UNIT_SETTING;
+ break;
+
+ case -EUCLEAN:
+ m = strjoin("Invalid unit name ", change->path);
+ bus_error = BUS_ERROR_BAD_UNIT_SETTING;
+ break;
+
+ case -ELOOP:
+ m = strjoin("Refusing to operate on linked unit file ", change->path);
+ bus_error = BUS_ERROR_UNIT_LINKED;
+ break;
+
+ case -EXDEV:
+ if (change->source)
+ m = strjoin("Cannot alias ", change->source, " as ", change->path);
+ else
+ m = strjoin("Invalid unit reference ", change->path);
+ bus_error = BUS_ERROR_BAD_UNIT_SETTING;
+ break;
+
+ case -ENOENT:
+ m = strjoin("Unit ", change->path, " does not exist");
+ bus_error = BUS_ERROR_NO_SUCH_UNIT;
+ break;
+
+ case -ENOLINK:
+ m = strjoin("Unit ", change->path, " is an unresolvable alias");
+ bus_error = BUS_ERROR_NO_SUCH_UNIT;
+ break;
+
+ case -EUNATCH:
+ m = strjoin("Cannot resolve specifiers in unit ", change->path);
+ bus_error = BUS_ERROR_BAD_UNIT_SETTING;
+ break;
+
+ default:
+ return change->type;
+ }
+ if (!m)
+ return -ENOMEM;
+
+ *ret_errmsg = m;
+ if (ret_bus_error)
+ *ret_bus_error = bus_error;
+
+ return 0;
+}
+
+void install_changes_dump(
+ int error,
+ const char *verb,
+ const InstallChange *changes,
+ size_t n_changes,
+ bool quiet) {
+
+ bool err_logged = false;
+ int r;
+
+ /* If verb is not specified, errors are not allowed! */
+ assert(verb || error >= 0);
+ assert(changes || n_changes == 0);
+
+ FOREACH_ARRAY(i, changes, n_changes)
+ if (i->type >= 0) {
if (!quiet)
- log_warning("Failed to enable auxiliary unit %s, ignoring.", changes[i].path);
- break;
- case -EEXIST:
- if (changes[i].source)
- err = log_error_errno(changes[i].type,
- "Failed to %s unit, file \"%s\" already exists and is a symlink to \"%s\".",
- verb, changes[i].path, changes[i].source);
- else
- err = log_error_errno(changes[i].type,
- "Failed to %s unit, file \"%s\" already exists.",
- verb, changes[i].path);
- break;
- case -ERFKILL:
- err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.",
- verb, changes[i].path);
- break;
- case -EADDRNOTAVAIL:
- err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.",
- verb, changes[i].path);
- break;
- case -ETXTBSY:
- err = log_error_errno(changes[i].type, "Failed to %s unit, file %s is under the systemd unit hierarchy already.",
- verb, changes[i].path);
- break;
- case -EBADSLT:
- err = log_error_errno(changes[i].type, "Failed to %s unit, invalid specifier in \"%s\".",
- verb, changes[i].path);
- break;
- case -EIDRM:
- err = log_error_errno(changes[i].type, "Failed to %s %s, destination unit %s is a non-template unit.",
- verb, changes[i].source, changes[i].path);
- break;
- case -EUCLEAN:
- err = log_error_errno(changes[i].type,
- "Failed to %s unit, \"%s\" is not a valid unit name.",
- verb, changes[i].path);
- break;
- case -ELOOP:
- err = log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s.",
- verb, changes[i].path);
- break;
- case -EXDEV:
- if (changes[i].source)
- err = log_error_errno(changes[i].type, "Failed to %s unit, cannot alias %s as %s.",
- verb, changes[i].source, changes[i].path);
+ install_change_dump_success(i);
+ } else {
+ _cleanup_free_ char *err_message = NULL;
+
+ assert(verb);
+
+ r = install_change_dump_error(i, &err_message, /* ret_bus_error = */ NULL);
+ if (r == -ENOMEM)
+ return (void) log_oom();
+ if (r < 0)
+ log_error_errno(r, "Failed to %s unit %s: %m", verb, i->path);
else
- err = log_error_errno(changes[i].type, "Failed to %s unit, invalid unit reference \"%s\".",
- verb, changes[i].path);
- break;
- case -ENOENT:
- err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s does not exist.",
- verb, changes[i].path);
- break;
- case -EUNATCH:
- err = log_error_errno(changes[i].type, "Failed to %s unit, cannot resolve specifiers in \"%s\".",
- verb, changes[i].path);
- break;
- default:
- assert(changes[i].type < 0);
- err = log_error_errno(changes[i].type, "Failed to %s unit, file \"%s\": %m",
- verb, changes[i].path);
+ log_error_errno(i->type, "Failed to %s unit: %s", verb, err_message);
+
+ err_logged = true;
}
- }
- if (r < 0 && err >= 0)
- log_error_errno(r, "Failed to %s: %m.", verb);
+ if (error < 0 && !err_logged)
+ log_error_errno(error, "Failed to %s unit: %m.", verb);
}
/**
@@ -750,7 +801,7 @@ static int remove_marked_symlinks(
assert(config_path);
assert(lp);
- if (set_size(remove_symlinks_to) <= 0)
+ if (set_isempty(remove_symlinks_to))
return 0;
fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC);
@@ -996,7 +1047,7 @@ static int find_symlinks_in_scope(
if (r > 0) {
/* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
- if (path_equal_ptr(*p, lp->persistent_config)) {
+ if (path_equal(*p, lp->persistent_config)) {
/* This is the best outcome, let's return it immediately. */
*state = UNIT_FILE_ENABLED;
return 1;
@@ -1017,7 +1068,7 @@ static int find_symlinks_in_scope(
enabled_at_all = true;
} else if (same_name_link) {
- if (path_equal_ptr(*p, lp->persistent_config))
+ if (path_equal(*p, lp->persistent_config))
same_name_link_config = true;
else {
r = path_is_runtime(lp, *p, false);
@@ -1813,7 +1864,7 @@ int unit_file_verify_alias(
_cleanup_free_ char *dir = NULL;
char *p;
- path_alias ++; /* skip over slash */
+ path_alias++; /* skip over slash */
r = path_extract_directory(dst, &dir);
if (r < 0)
@@ -1892,28 +1943,25 @@ static int install_info_symlink_alias(
InstallChange **changes,
size_t *n_changes) {
- int r = 0, q;
+ int r, ret = 0;
assert(info);
assert(lp);
assert(config_path);
STRV_FOREACH(s, info->aliases) {
- _cleanup_free_ char *alias_path = NULL, *dst = NULL, *dst_updated = NULL;
- bool broken;
+ _cleanup_free_ char *alias_path = NULL, *alias_target = NULL, *dst = NULL, *dst_updated = NULL;
- q = install_name_printf(scope, info, *s, &dst);
- if (q < 0) {
- install_changes_add(changes, n_changes, q, *s, NULL);
- r = r < 0 ? r : q;
+ r = install_name_printf(scope, info, *s, &dst);
+ if (r < 0) {
+ RET_GATHER(ret, install_changes_add(changes, n_changes, r, *s, NULL));
continue;
}
- q = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes);
- if (q == -ELOOP)
- continue;
- if (q < 0) {
- r = r < 0 ? r : q;
+ r = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes);
+ if (r < 0) {
+ if (r != -ELOOP)
+ RET_GATHER(ret, r);
continue;
}
@@ -1921,18 +1969,30 @@ static int install_info_symlink_alias(
if (!alias_path)
return -ENOMEM;
- q = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL);
- if (q < 0 && q != -ENOENT) {
- r = r < 0 ? r : q;
+ r = in_search_path(lp, info->path);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* The unit path itself is outside of the search path. To
+ * correctly apply the alias, we need the alias symlink to
+ * point to the symlink that was created in the search path. */
+ alias_target = path_join(config_path, info->name);
+ if (!alias_target)
+ return -ENOMEM;
+ }
+
+ bool broken;
+ r = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, /* ret_path = */ NULL, /* ret_fd = */ NULL);
+ if (r < 0 && r != -ENOENT) {
+ RET_GATHER(ret, r);
continue;
}
- broken = q == 0; /* symlink target does not exist? */
+ broken = r == 0; /* symlink target does not exist? */
- q = create_symlink(lp, info->path, alias_path, force || broken, changes, n_changes);
- r = r < 0 ? r : q;
+ RET_GATHER(ret, create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes));
}
- return r;
+ return ret;
}
static int install_info_symlink_wants(
@@ -1995,10 +2055,7 @@ static int install_info_symlink_wants(
q = install_name_printf(scope, info, *s, &dst);
if (q < 0) {
- install_changes_add(changes, n_changes, q, *s, NULL);
- if (r >= 0)
- r = q;
-
+ RET_GATHER(r, install_changes_add(changes, n_changes, q, *s, NULL));
continue;
}
@@ -2010,15 +2067,13 @@ static int install_info_symlink_wants(
* 'systemctl enable serial-getty@.service' should fail, the user should specify an
* instance like in 'systemctl enable serial-getty@ttyS0.service'.
*/
- if (file_flags & UNIT_FILE_IGNORE_AUXILIARY_FAILURE)
+ if (FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE))
continue;
if (unit_name_is_valid(dst, UNIT_NAME_ANY))
- q = install_changes_add(changes, n_changes, -EIDRM, dst, n);
+ RET_GATHER(r, install_changes_add(changes, n_changes, -EIDRM, dst, n));
else
- q = install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL);
- if (r >= 0)
- r = q;
+ RET_GATHER(r, install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL));
continue;
}
@@ -2027,7 +2082,7 @@ static int install_info_symlink_wants(
if (!path)
return -ENOMEM;
- q = create_symlink(lp, info->path, path, true, changes, n_changes);
+ q = create_symlink(lp, info->path, path, /* force = */ true, changes, n_changes);
if ((q < 0 && r >= 0) || r == 0)
r = q;
@@ -2259,7 +2314,7 @@ int unit_file_mask(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
const char *config_path;
int r;
@@ -2274,13 +2329,13 @@ int unit_file_mask(
if (!config_path)
return -ENXIO;
+ r = 0;
+
STRV_FOREACH(name, names) {
_cleanup_free_ char *path = NULL;
- int q;
if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) {
- if (r == 0)
- r = -EINVAL;
+ RET_GATHER(r, -EINVAL);
continue;
}
@@ -2288,9 +2343,7 @@ int unit_file_mask(
if (!path)
return -ENOMEM;
- q = create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes);
- if (q < 0 && r >= 0)
- r = q;
+ RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes));
}
return r;
@@ -2304,7 +2357,7 @@ int unit_file_unmask(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
_cleanup_strv_free_ char **todo = NULL;
const char *config_path;
@@ -2386,8 +2439,7 @@ int unit_file_unmask(
if (!dry_run && unlink(path) < 0) {
if (errno != ENOENT) {
- if (r >= 0)
- r = -errno;
+ RET_GATHER(r, -errno);
install_changes_add(changes, n_changes, -errno, path, NULL);
}
@@ -2404,9 +2456,7 @@ int unit_file_unmask(
return q;
}
- q = remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes);
- if (r >= 0)
- r = q;
+ RET_GATHER(r, remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes));
return r;
}
@@ -2419,11 +2469,11 @@ int unit_file_link(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_strv_free_ char **todo = NULL;
const char *config_path;
size_t n_todo = 0;
- int r, q;
+ int r;
assert(scope >= 0);
assert(scope < _RUNTIME_SCOPE_MAX);
@@ -2492,9 +2542,7 @@ int unit_file_link(
if (!new_path)
return -ENOMEM;
- q = create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes);
- if (q < 0 && r >= 0)
- r = q;
+ RET_GATHER(r, create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes));
}
return r;
@@ -2527,7 +2575,7 @@ int unit_file_revert(
size_t *n_changes) {
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_strv_free_ char **todo = NULL;
size_t n_todo = 0;
int r, q;
@@ -2685,7 +2733,7 @@ int unit_file_add_dependency(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
InstallInfo *info, *target_info;
const char *config_path;
@@ -2786,7 +2834,7 @@ int unit_file_enable(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
int r;
assert(scope >= 0);
@@ -2814,11 +2862,12 @@ static int do_unit_file_disable(
_cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
_cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
- InstallInfo *info;
bool has_install_info = false;
int r;
STRV_FOREACH(name, names) {
+ InstallInfo *info;
+
if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL);
@@ -2838,7 +2887,6 @@ static int do_unit_file_disable(
r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes);
if (r >= 0)
r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes);
-
if (r < 0)
return r;
@@ -2854,7 +2902,7 @@ int unit_file_disable(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
int r;
assert(scope >= 0);
@@ -2897,7 +2945,7 @@ static int normalize_linked_files(
return log_debug_errno(SYNTHETIC_ERRNO(EISDIR),
"Unexpected path to a directory \"%s\", refusing.", *a);
- if (!is_path(*a)) {
+ if (!is_path(*a) && !unit_name_is_valid(*a, UNIT_NAME_INSTANCE)) {
r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL);
if (r < 0)
log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n);
@@ -2937,7 +2985,7 @@ int unit_file_reenable(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_strv_free_ char **names = NULL, **files = NULL;
int r;
@@ -2973,7 +3021,7 @@ int unit_file_set_default(
InstallChange **changes,
size_t *n_changes) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
InstallInfo *info;
const char *new_path;
@@ -3003,17 +3051,16 @@ int unit_file_set_default(
int unit_file_get_default(
RuntimeScope scope,
const char *root_dir,
- char **name) {
+ char **ret) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
InstallInfo *info;
- char *n;
int r;
assert(scope >= 0);
assert(scope < _RUNTIME_SCOPE_MAX);
- assert(name);
+ assert(ret);
r = lookup_paths_init(&lp, scope, 0, root_dir);
if (r < 0)
@@ -3024,12 +3071,7 @@ int unit_file_get_default(
if (r < 0)
return r;
- n = strdup(info->name);
- if (!n)
- return -ENOMEM;
-
- *name = n;
- return 0;
+ return strdup_to(ret, info->name);
}
int unit_file_lookup_state(
@@ -3136,7 +3178,7 @@ int unit_file_get_state(
const char *name,
UnitFileState *ret) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
int r;
assert(scope >= 0);
@@ -3150,8 +3192,10 @@ int unit_file_get_state(
return unit_file_lookup_state(scope, &lp, name, ret);
}
-int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) {
- _cleanup_(install_context_done) InstallContext c = { .scope = scope };
+int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) {
+ _cleanup_(install_context_done) InstallContext c = {
+ .scope = scope,
+ };
int r;
assert(lp);
@@ -3160,12 +3204,31 @@ int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
return -EINVAL;
- r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL);
- if (r == -ENOENT)
+ InstallInfo *info = NULL;
+ r = install_info_discover(
+ &c,
+ lp,
+ name,
+ /* flags= */ 0,
+ ret_path ? &info : NULL,
+ /* changes= */ NULL,
+ /* n_changes= */ NULL);
+ if (r == -ENOENT) {
+ if (ret_path)
+ *ret_path = NULL;
return 0;
+ }
if (r < 0)
return r;
+ if (ret_path) {
+ assert(info);
+
+ r = strdup_to(ret_path, info->path);
+ if (r < 0)
+ return r;
+ }
+
return 1;
}
@@ -3203,8 +3266,8 @@ static int split_pattern_into_name_and_instances(const char *pattern, char **out
}
static int presets_find_config(RuntimeScope scope, const char *root_dir, char ***files) {
- static const char* const system_dirs[] = {CONF_PATHS("systemd/system-preset"), NULL};
- static const char* const user_dirs[] = {CONF_PATHS_USR("systemd/user-preset"), NULL};
+ static const char* const system_dirs[] = { CONF_PATHS("systemd/system-preset"), NULL };
+ static const char* const user_dirs[] = { CONF_PATHS("systemd/user-preset"), NULL };
const char* const* dirs;
assert(scope >= 0);
@@ -3540,7 +3603,7 @@ int unit_file_preset(
size_t *n_changes) {
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path;
int r;
@@ -3579,7 +3642,7 @@ int unit_file_preset_all(
size_t *n_changes) {
_cleanup_(install_context_done) InstallContext plus = {}, minus = {};
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
_cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
const char *config_path = NULL;
int r;
@@ -3600,18 +3663,19 @@ int unit_file_preset_all(
if (r < 0)
return r;
+ r = 0;
STRV_FOREACH(i, lp.search_path) {
_cleanup_closedir_ DIR *d = NULL;
d = opendir(*i);
if (!d) {
- if (errno == ENOENT)
- continue;
-
- return -errno;
+ if (errno != ENOENT)
+ RET_GATHER(r, -errno);
+ continue;
}
- FOREACH_DIRENT(de, d, return -errno) {
+ FOREACH_DIRENT(de, d, RET_GATHER(r, -errno)) {
+ int k;
if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
continue;
@@ -3619,12 +3683,23 @@ int unit_file_preset_all(
if (!IN_SET(de->d_type, DT_LNK, DT_REG))
continue;
- r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes);
- if (r < 0 &&
- !IN_SET(r, -EEXIST, -ERFKILL, -EADDRNOTAVAIL, -EBADSLT, -EIDRM, -EUCLEAN, -ELOOP, -ENOENT, -EUNATCH, -EXDEV))
+ k = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes);
+ if (k < 0 &&
+ !IN_SET(k, -EEXIST,
+ -ERFKILL,
+ -EADDRNOTAVAIL,
+ -ETXTBSY,
+ -EBADSLT,
+ -EIDRM,
+ -EUCLEAN,
+ -ELOOP,
+ -EXDEV,
+ -ENOENT,
+ -ENOLINK,
+ -EUNATCH))
/* Ignore generated/transient/missing/invalid units when applying preset, propagate other errors.
- * Coordinate with install_changes_dump() above. */
- return r;
+ * Coordinate with install_change_dump_error() above. */
+ RET_GATHER(r, k);
}
}
@@ -3656,7 +3731,7 @@ int unit_file_get_list(
char **states,
char **patterns) {
- _cleanup_(lookup_paths_free) LookupPaths lp = {};
+ _cleanup_(lookup_paths_done) LookupPaths lp = {};
int r;
assert(scope >= 0);
diff --git a/src/shared/install.h b/src/shared/install.h
index bc0c6db..13d1630 100644
--- a/src/shared/install.h
+++ b/src/shared/install.h
@@ -63,9 +63,9 @@ struct InstallChange {
char *source;
};
-static inline bool install_changes_have_modification(const InstallChange* changes, size_t n_changes) {
- for (size_t i = 0; i < n_changes; i++)
- if (IN_SET(changes[i].type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK))
+static inline bool install_changes_have_modification(const InstallChange *changes, size_t n_changes) {
+ FOREACH_ARRAY(i, changes, n_changes)
+ if (IN_SET(i->type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK))
return true;
return false;
}
@@ -175,7 +175,7 @@ int unit_file_set_default(
int unit_file_get_default(
RuntimeScope scope,
const char *root_dir,
- char **name);
+ char **ret);
int unit_file_add_dependency(
RuntimeScope scope,
UnitFileFlags flags,
@@ -193,7 +193,11 @@ int unit_file_lookup_state(
UnitFileState *ret);
int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret);
-int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name);
+
+int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path);
+static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) {
+ return unit_file_exists_full(scope, paths, name, NULL);
+}
int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns);
@@ -201,7 +205,14 @@ extern const struct hash_ops unit_file_list_hash_ops_free;
InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source);
void install_changes_free(InstallChange *changes, size_t n_changes);
-void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet);
+
+int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error);
+void install_changes_dump(
+ int error,
+ const char *verb,
+ const InstallChange *changes,
+ size_t n_changes,
+ bool quiet);
int unit_file_verify_alias(
const InstallInfo *info,
diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c
index bdceac4..8df165d 100644
--- a/src/shared/journal-file-util.c
+++ b/src/shared/journal-file-util.c
@@ -404,7 +404,7 @@ JournalFile* journal_file_offline_close(JournalFile *f) {
if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0)
journal_file_post_change(f);
- sd_event_source_disable_unref(f->post_change_timer);
+ f->post_change_timer = sd_event_source_disable_unref(f->post_change_timer);
journal_file_set_offline(f, true);
@@ -451,7 +451,7 @@ int journal_file_rotate(
set_clear_with_destructor(deferred_closes, journal_file_offline_close);
r = journal_file_open(
- /* fd= */ -1,
+ /* fd= */ -EBADF,
path,
(*f)->open_flags,
file_flags,
@@ -476,14 +476,13 @@ int journal_file_open_reliably(
uint64_t compress_threshold_bytes,
JournalMetrics *metrics,
MMapCache *mmap_cache,
- JournalFile *template,
JournalFile **ret) {
_cleanup_(journal_file_offline_closep) JournalFile *old_file = NULL;
int r;
r = journal_file_open(
- /* fd= */ -1,
+ /* fd= */ -EBADF,
fname,
open_flags,
file_flags,
@@ -491,7 +490,7 @@ int journal_file_open_reliably(
compress_threshold_bytes,
metrics,
mmap_cache,
- template,
+ /* template = */ NULL,
ret);
if (!IN_SET(r,
-EBADMSG, /* Corrupted */
@@ -517,23 +516,19 @@ int journal_file_open_reliably(
/* The file is corrupted. Rotate it away and try it again (but only once) */
log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
- if (!template) {
- /* The file is corrupted and no template is specified. Try opening it read-only as the
- * template before rotating to inherit its sequence number and ID. */
- r = journal_file_open(-1, fname,
- (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY,
- file_flags, 0, compress_threshold_bytes, NULL,
- mmap_cache, NULL, &old_file);
- if (r < 0)
- log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname);
- else
- template = old_file;
- }
+ /* The file is corrupted. Try opening it read-only as the template before rotating to inherit its
+ * sequence number and ID. */
+ r = journal_file_open(-EBADF, fname,
+ (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY,
+ file_flags, 0, compress_threshold_bytes, NULL,
+ mmap_cache, /* template = */ NULL, &old_file);
+ if (r < 0)
+ log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname);
r = journal_file_dispose(AT_FDCWD, fname);
if (r < 0)
return r;
- return journal_file_open(-1, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics,
- mmap_cache, template, ret);
+ return journal_file_open(-EBADF, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics,
+ mmap_cache, /* template = */ old_file, ret);
}
diff --git a/src/shared/journal-file-util.h b/src/shared/journal-file-util.h
index f9426c4..8df10a7 100644
--- a/src/shared/journal-file-util.h
+++ b/src/shared/journal-file-util.h
@@ -17,7 +17,6 @@ int journal_file_open_reliably(
uint64_t compress_threshold_bytes,
JournalMetrics *metrics,
MMapCache *mmap_cache,
- JournalFile *template,
JournalFile **ret);
JournalFile* journal_file_initiate_close(JournalFile *f, Set *deferred_closes);
diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c
index 83e9834..bb0536e 100644
--- a/src/shared/journal-importer.c
+++ b/src/shared/journal-importer.c
@@ -93,7 +93,12 @@ static int get_line(JournalImporter *imp, char **line, size_t *size) {
imp->buf + imp->filled,
MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled);
if (n < 0) {
- if (errno != EAGAIN)
+ if (ERRNO_IS_DISCONNECT(errno)) {
+ log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name));
+ return 0;
+ }
+
+ if (!ERRNO_IS_TRANSIENT(errno))
log_error_errno(errno, "read(%d, ..., %zu): %m",
imp->fd,
MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled);
@@ -134,7 +139,12 @@ static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) {
n = read(imp->fd, imp->buf + imp->filled,
MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled);
if (n < 0) {
- if (errno != EAGAIN)
+ if (ERRNO_IS_DISCONNECT(errno)) {
+ log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name));
+ return 0;
+ }
+
+ if (!ERRNO_IS_TRANSIENT(errno))
log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd,
MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled);
return -errno;
diff --git a/src/shared/journal-util.c b/src/shared/journal-util.c
index d73d7c4..ab70f4d 100644
--- a/src/shared/journal-util.c
+++ b/src/shared/journal-util.c
@@ -145,7 +145,7 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_use
return r;
}
-int journal_open_machine(sd_journal **ret, const char *machine) {
+int journal_open_machine(sd_journal **ret, const char *machine, int flags) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -178,7 +178,7 @@ int journal_open_machine(sd_journal **ret, const char *machine) {
if (machine_fd < 0)
return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
- r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD);
+ r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD | flags);
if (r < 0)
return log_error_errno(r, "Failed to open journal in machine '%s': %m", machine);
diff --git a/src/shared/journal-util.h b/src/shared/journal-util.h
index afad249..5bd8e34 100644
--- a/src/shared/journal-util.h
+++ b/src/shared/journal-util.h
@@ -8,4 +8,4 @@
int journal_access_blocked(sd_journal *j);
int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_users);
-int journal_open_machine(sd_journal **ret, const char *machine);
+int journal_open_machine(sd_journal **ret, const char *machine, int flags);
diff --git a/src/shared/json.c b/src/shared/json.c
index 06c9e85..4af34e5 100644
--- a/src/shared/json.c
+++ b/src/shared/json.c
@@ -14,13 +14,16 @@
#include "fd-util.h"
#include "fileio.h"
#include "float.h"
+#include "glyph-util.h"
#include "hexdecoct.h"
+#include "iovec-util.h"
#include "json-internal.h"
#include "json.h"
#include "macro.h"
#include "math-util.h"
#include "memory-util.h"
#include "memstream-util.h"
+#include "path-util.h"
#include "set.h"
#include "string-table.h"
#include "string-util.h"
@@ -87,6 +90,9 @@ struct JsonVariant {
/* Erase from memory when freeing */
bool sensitive:1;
+ /* True if we know that any referenced json object is marked sensitive */
+ bool recursive_sensitive:1;
+
/* If this is an object the fields are strictly ordered by name */
bool sorted:1;
@@ -559,7 +565,7 @@ static int _json_variant_array_put_element(JsonVariant *array, JsonVariant *elem
return -ELNRNG;
if (d >= array->depth)
array->depth = d + 1;
- array->n_elements ++;
+ array->n_elements++;
*w = (JsonVariant) {
.is_embedded = true,
@@ -1451,6 +1457,33 @@ bool json_variant_is_sensitive(JsonVariant *v) {
return v->sensitive;
}
+bool json_variant_is_sensitive_recursive(JsonVariant *v) {
+ if (!v)
+ return false;
+ if (json_variant_is_sensitive(v))
+ return true;
+ if (!json_variant_is_regular(v))
+ return false;
+ if (v->recursive_sensitive) /* Already checked this before */
+ return true;
+ if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+ return false;
+ if (v->is_reference) {
+ if (!json_variant_is_sensitive_recursive(v->reference))
+ return false;
+
+ return (v->recursive_sensitive = true);
+ }
+
+ for (size_t i = 0; i < json_variant_elements(v); i++)
+ if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
+ return (v->recursive_sensitive = true);
+
+ /* Note: we only cache the result here in case true, since we allow all elements down the tree to
+ * have their sensitive flag toggled later on (but never off) */
+ return false;
+}
+
static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) {
if (json_variant_is_sensitive(from))
json_variant_sensitive(to);
@@ -1580,6 +1613,15 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
assert(f);
assert(v);
+ if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) {
+ if (flags & JSON_FORMAT_COLOR)
+ fputs(ansi_red(), f);
+ fputs("\"<sensitive data>\"", f);
+ if (flags & JSON_FORMAT_COLOR)
+ fputs(ANSI_NORMAL, f);
+ return 0;
+ }
+
switch (json_variant_type(v)) {
case JSON_VARIANT_REAL: {
@@ -2310,7 +2352,7 @@ static int json_variant_copy(JsonVariant **nv, JsonVariant *v) {
source = json_variant_string(v);
k = strnlen(source, INLINE_STRING_MAX + 1);
if (k <= INLINE_STRING_MAX) {
- k ++;
+ k++;
break;
}
@@ -2594,7 +2636,7 @@ static int json_parse_string(const char **p, char **ret) {
return -ENOMEM;
s[n++] = ch;
- c ++;
+ c++;
continue;
}
@@ -3790,7 +3832,8 @@ int json_buildv(JsonVariant **ret, va_list ap) {
break;
}
- case _JSON_BUILD_IOVEC_BASE64: {
+ case _JSON_BUILD_IOVEC_BASE64:
+ case _JSON_BUILD_IOVEC_HEX: {
const struct iovec *iov;
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
@@ -3798,10 +3841,14 @@ int json_buildv(JsonVariant **ret, va_list ap) {
goto finish;
}
- iov = ASSERT_PTR(va_arg(ap, const struct iovec*));
+ iov = va_arg(ap, const struct iovec*);
if (current->n_suppress == 0) {
- r = json_variant_new_base64(&add, iov->iov_base, iov->iov_len);
+ if (iov)
+ r = command == _JSON_BUILD_IOVEC_BASE64 ? json_variant_new_base64(&add, iov->iov_base, iov->iov_len) :
+ json_variant_new_hex(&add, iov->iov_base, iov->iov_len);
+ else
+ r = json_variant_new_string(&add, "");
if (r < 0)
goto finish;
}
@@ -4574,7 +4621,7 @@ int json_dispatch_full(
}
}
- done ++;
+ done++;
} else { /* Didn't find a matching entry! ☹️ */
@@ -4589,11 +4636,15 @@ int json_dispatch_full(
return r;
} else
- done ++;
+ done++;
} else {
- json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
+ if (flags & JSON_ALLOW_EXTENSIONS) {
+ json_log(value, flags|JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", json_variant_string(key));
+ continue;
+ }
+ json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key));
if (flags & JSON_PERMISSIVE)
continue;
@@ -4762,6 +4813,42 @@ int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFla
return 0;
}
+int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ int8_t *i = ASSERT_PTR(userdata);
+ int64_t i64;
+ int r;
+
+ assert(variant);
+
+ r = json_dispatch_int64(name, variant, flags, &i64);
+ if (r < 0)
+ return r;
+
+ if (i64 < INT8_MIN || i64 > INT8_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *i = (int8_t) i64;
+ return 0;
+}
+
+int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ uint8_t *u = ASSERT_PTR(userdata);
+ uint64_t u64;
+ int r;
+
+ assert(variant);
+
+ r = json_dispatch_uint64(name, variant, flags, &u64);
+ if (r < 0)
+ return r;
+
+ if (u64 > UINT8_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name));
+
+ *u = (uint8_t) u64;
+ return 0;
+}
+
int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = ASSERT_PTR(userdata);
int r;
@@ -4920,6 +5007,27 @@ int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDi
return 0;
}
+int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ const char *path;
+ char **p = ASSERT_PTR(userdata);
+
+ assert(variant);
+
+ if (!json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+
+ path = json_variant_string(variant);
+ if (!path_is_valid(path))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid path.", strna(name));
+ if (!path_is_absolute(path))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' must be an absolute path.", strna(name));
+
+ if (free_and_strdup(p, path) < 0)
+ return json_log_oom(variant, flags);
+
+ return 0;
+}
+
int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
sd_id128_t *uuid = userdata;
int r;
@@ -4961,6 +5069,63 @@ int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDis
return 0;
}
+int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ _cleanup_free_ uint8_t *buffer = NULL;
+ struct iovec *iov = ASSERT_PTR(userdata);
+ size_t sz, k = 0;
+
+ assert(variant);
+
+ if (!json_variant_is_array(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
+
+ sz = json_variant_elements(variant);
+
+ buffer = new(uint8_t, sz + 1);
+ if (!buffer)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.");
+
+ JsonVariant *i;
+ JSON_VARIANT_ARRAY_FOREACH(i, variant) {
+ uint64_t b;
+
+ if (!json_variant_is_unsigned(i))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name));
+
+ b = json_variant_unsigned(i);
+ if (b > 0xff)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
+ "Element %zu of JSON field '%s' is out of range 0%s255.",
+ k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS));
+
+ buffer[k++] = (uint8_t) b;
+ }
+ assert(k == sz);
+
+ /* Append a NUL byte for safety, like we do in memdup_suffix0() and others. */
+ buffer[sz] = 0;
+
+ free_and_replace(iov->iov_base, buffer);
+ iov->iov_len = sz;
+ return 0;
+}
+
+int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ struct in_addr *address = ASSERT_PTR(userdata);
+ _cleanup_(iovec_done) struct iovec iov = {};
+ int r;
+
+ r = json_dispatch_byte_array_iovec(name, variant, flags, &iov);
+ if (r < 0)
+ return r;
+
+ if (iov.iov_len != sizeof(struct in_addr))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
+
+ memcpy(address, iov.iov_base, iov.iov_len);
+ return 0;
+}
+
static int json_cmp_strings(const void *x, const void *y) {
JsonVariant *const *a = x, *const *b = y;
@@ -5107,14 +5272,14 @@ int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) {
if (!json_variant_is_string(v))
return -EINVAL;
- return unbase64mem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unbase64mem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size);
}
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) {
if (!json_variant_is_string(v))
return -EINVAL;
- return unhexmem(json_variant_string(v), SIZE_MAX, ret, ret_size);
+ return unhexmem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size);
}
static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
diff --git a/src/shared/json.h b/src/shared/json.h
index c40c234..6a93530 100644
--- a/src/shared/json.h
+++ b/src/shared/json.h
@@ -155,6 +155,7 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b);
void json_variant_sensitive(JsonVariant *v);
bool json_variant_is_sensitive(JsonVariant *v);
+bool json_variant_is_sensitive_recursive(JsonVariant *v);
struct json_variant_foreach_state {
JsonVariant *variant;
@@ -185,17 +186,18 @@ struct json_variant_foreach_state {
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
typedef enum JsonFormatFlags {
- JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
- JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
- JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
- JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
- JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
- JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
- JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
- JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
- JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
- JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
- JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
+ JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
+ JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
+ JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
+ JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
+ JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
+ JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
+ JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
+ JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
+ JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
+ JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "<sensitive data>" */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
@@ -271,6 +273,7 @@ enum {
_JSON_BUILD_IOVEC_BASE64,
_JSON_BUILD_BASE32HEX,
_JSON_BUILD_HEX,
+ _JSON_BUILD_IOVEC_HEX,
_JSON_BUILD_OCTESCAPE,
_JSON_BUILD_ID128,
_JSON_BUILD_UUID,
@@ -315,6 +318,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user
#define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov }
#define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n }
+#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov }
#define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n }
#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) }
#define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) }
@@ -345,6 +349,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user
#define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n))
#define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov))
#define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n))
+#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov))
#define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id))
#define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id))
#define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n))
@@ -375,15 +380,16 @@ int json_buildv(JsonVariant **ret, va_list ap);
* entry, as well the bitmask specified for json_log() calls */
typedef enum JsonDispatchFlags {
/* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */
- JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */
- JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
- JSON_LOG = 1 << 2, /* Should the parser log about errors? */
- JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
- JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this field or object? */
+ JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */
+ JSON_LOG = 1 << 2, /* Should the parser log about errors? */
+ JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
+ JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
+ JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */
/* The following two may be passed into log_json() in addition to those above */
- JSON_DEBUG = 1 << 5, /* Indicates that this log message is a debug message */
- JSON_WARNING = 1 << 6, /* Indicates that this log message is a warning message */
+ JSON_DEBUG = 1 << 6, /* Indicates that this log message is a debug message */
+ JSON_WARNING = 1 << 7, /* Indicates that this log message is a warning message */
} JsonDispatchFlags;
typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
@@ -415,11 +421,16 @@ int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFla
int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
assert_cc(sizeof(uint32_t) == sizeof(unsigned));
#define json_dispatch_uint json_dispatch_uint32
@@ -427,6 +438,28 @@ assert_cc(sizeof(uint32_t) == sizeof(unsigned));
assert_cc(sizeof(int32_t) == sizeof(int));
#define json_dispatch_int json_dispatch_int32
+#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \
+ int name(const char *n, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { \
+ type *c = ASSERT_PTR(userdata); \
+ \
+ assert(variant); \
+ \
+ if (json_variant_is_null(variant)) { \
+ *c = (type) -EINVAL; \
+ return 0; \
+ } \
+ \
+ if (!json_variant_is_string(variant)) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \
+ \
+ type cc = func(json_variant_string(variant)); \
+ if (cc < 0) \
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \
+ \
+ *c = cc; \
+ return 0; \
+ }
+
static inline int json_dispatch_level(JsonDispatchFlags flags) {
/* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as
@@ -470,5 +503,13 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi
int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size);
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size);
+static inline int json_variant_unbase64_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
+static inline int json_variant_unhex_iovec(JsonVariant *v, struct iovec *ret) {
+ return json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL);
+}
+
const char *json_variant_type_to_string(JsonVariantType t);
JsonVariantType json_variant_type_from_string(const char *s);
diff --git a/src/shared/kbd-util.c b/src/shared/kbd-util.c
index 2f2d161..60e0429 100644
--- a/src/shared/kbd-util.c
+++ b/src/shared/kbd-util.c
@@ -1,9 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "env-util.h"
#include "errno-util.h"
#include "kbd-util.h"
#include "log.h"
-#include "nulstr-util.h"
#include "path-util.h"
#include "recurse-dir.h"
#include "set.h"
@@ -11,6 +11,25 @@
#include "strv.h"
#include "utf8.h"
+#define KBD_KEYMAP_DIRS \
+ "/usr/share/keymaps/", \
+ "/usr/share/kbd/keymaps/", \
+ "/usr/lib/kbd/keymaps/"
+
+int keymap_directories(char ***ret) {
+ assert(ret);
+
+ if (getenv_path_list("SYSTEMD_KEYMAP_DIRECTORIES", ret) >= 0)
+ return 0;
+
+ char **paths = strv_new(KBD_KEYMAP_DIRS);
+ if (!paths)
+ return log_oom_debug();
+
+ *ret = TAKE_PTR(paths);
+ return 0;
+}
+
struct recurse_dir_userdata {
const char *keymap_name;
Set *keymaps;
@@ -65,16 +84,21 @@ static int keymap_recurse_dir_callback(
int get_keymaps(char ***ret) {
_cleanup_set_free_free_ Set *keymaps = NULL;
+ _cleanup_strv_free_ char **keymap_dirs = NULL;
int r;
+ r = keymap_directories(&keymap_dirs);
+ if (r < 0)
+ return r;
+
keymaps = set_new(&string_hash_ops);
if (!keymaps)
return -ENOMEM;
- NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
+ STRV_FOREACH(dir, keymap_dirs) {
r = recurse_dir_at(
AT_FDCWD,
- dir,
+ *dir,
/* statx_mask= */ 0,
/* n_depth_max= */ UINT_MAX,
RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
@@ -85,9 +109,9 @@ int get_keymaps(char ***ret) {
if (r == -ENOENT)
continue;
if (ERRNO_IS_NEG_RESOURCE(r))
- return log_warning_errno(r, "Failed to read keymap list from %s: %m", dir);
+ return log_warning_errno(r, "Failed to read keymap list from %s: %m", *dir);
if (r < 0)
- log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir);
+ log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir);
}
_cleanup_strv_free_ char **l = set_get_strv(keymaps);
@@ -127,15 +151,20 @@ bool keymap_is_valid(const char *name) {
}
int keymap_exists(const char *name) {
+ _cleanup_strv_free_ char **keymap_dirs = NULL;
int r;
if (!keymap_is_valid(name))
return -EINVAL;
- NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
+ r = keymap_directories(&keymap_dirs);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(dir, keymap_dirs) {
r = recurse_dir_at(
AT_FDCWD,
- dir,
+ *dir,
/* statx_mask= */ 0,
/* n_depth_max= */ UINT_MAX,
RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
@@ -148,7 +177,7 @@ int keymap_exists(const char *name) {
if (ERRNO_IS_NEG_RESOURCE(r))
return r;
if (r < 0 && r != -ENOENT)
- log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir);
+ log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir);
}
return false;
diff --git a/src/shared/kbd-util.h b/src/shared/kbd-util.h
index aca0dee..a8e365e 100644
--- a/src/shared/kbd-util.h
+++ b/src/shared/kbd-util.h
@@ -3,11 +3,7 @@
#include <stdbool.h>
-#define KBD_KEYMAP_DIRS \
- "/usr/share/keymaps/\0" \
- "/usr/share/kbd/keymaps/\0" \
- "/usr/lib/kbd/keymaps/\0"
-
-int get_keymaps(char ***l);
+int keymap_directories(char ***ret);
+int get_keymaps(char ***ret);
bool keymap_is_valid(const char *name);
int keymap_exists(const char *name);
diff --git a/src/shared/kernel-config.c b/src/shared/kernel-config.c
new file mode 100644
index 0000000..483ca28
--- /dev/null
+++ b/src/shared/kernel-config.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "conf-parser.h"
+#include "kernel-config.h"
+#include "macro.h"
+#include "path-util.h"
+#include "strv.h"
+
+int load_kernel_install_conf(
+ const char *root,
+ const char *conf_root,
+ char **ret_machine_id,
+ char **ret_boot_root,
+ char **ret_layout,
+ char **ret_initrd_generator,
+ char **ret_uki_generator) {
+
+ _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL,
+ *initrd_generator = NULL, *uki_generator = NULL;
+ const ConfigTableItem items[] = {
+ { NULL, "MACHINE_ID", config_parse_string, 0, &machine_id },
+ { NULL, "BOOT_ROOT", config_parse_string, 0, &boot_root },
+ { NULL, "layout", config_parse_string, 0, &layout },
+ { NULL, "initrd_generator", config_parse_string, 0, &initrd_generator },
+ { NULL, "uki_generator", config_parse_string, 0, &uki_generator },
+ {}
+ };
+ int r;
+
+ if (conf_root) {
+ _cleanup_free_ char *conf = path_join(conf_root, "install.conf");
+ if (!conf)
+ return log_oom();
+
+ r = config_parse_many(
+ STRV_MAKE_CONST(conf),
+ STRV_MAKE_CONST(conf_root),
+ "install.conf.d",
+ /* root= */ NULL, /* $KERNEL_INSTALL_CONF_ROOT and --root are independent */
+ /* sections= */ NULL,
+ config_item_table_lookup, items,
+ CONFIG_PARSE_WARN,
+ /* userdata= */ NULL,
+ /* ret_stats_by_path= */ NULL,
+ /* ret_dropin_files= */ NULL);
+ } else
+ r = config_parse_standard_file_with_dropins_full(
+ root,
+ "kernel/install.conf",
+ /* sections= */ NULL,
+ config_item_table_lookup, items,
+ CONFIG_PARSE_WARN,
+ /* userdata= */ NULL,
+ /* ret_stats_by_path= */ NULL,
+ /* ret_dropin_files= */ NULL);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ if (ret_machine_id)
+ *ret_machine_id = TAKE_PTR(machine_id);
+ if (ret_boot_root)
+ *ret_boot_root = TAKE_PTR(boot_root);
+ if (ret_layout)
+ *ret_layout = TAKE_PTR(layout);
+ if (ret_initrd_generator)
+ *ret_initrd_generator = TAKE_PTR(initrd_generator);
+ if (ret_uki_generator)
+ *ret_uki_generator = TAKE_PTR(uki_generator);
+ return r >= 0; /* Return 0 if we got -ENOENT above, 1 otherwise. */
+}
diff --git a/src/shared/kernel-config.h b/src/shared/kernel-config.h
new file mode 100644
index 0000000..5681870
--- /dev/null
+++ b/src/shared/kernel-config.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int load_kernel_install_conf(
+ const char *root,
+ const char *conf_root,
+ char **ret_machine_id,
+ char **ret_boot_root,
+ char **ret_layout,
+ char **ret_initrd_generator,
+ char **ret_uki_generator);
diff --git a/src/shared/keyring-util.c b/src/shared/keyring-util.c
deleted file mode 100644
index fadd90e..0000000
--- a/src/shared/keyring-util.c
+++ /dev/null
@@ -1,35 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "keyring-util.h"
-#include "memory-util.h"
-#include "missing_syscall.h"
-
-int keyring_read(key_serial_t serial, void **ret, size_t *ret_size) {
- size_t bufsize = 100;
-
- for (;;) {
- _cleanup_(erase_and_freep) uint8_t *buf = NULL;
- long n;
-
- buf = new(uint8_t, bufsize + 1);
- if (!buf)
- return -ENOMEM;
-
- n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) buf, (unsigned long) bufsize, 0);
- if (n < 0)
- return -errno;
-
- if ((size_t) n <= bufsize) {
- buf[n] = 0; /* NUL terminate, just in case */
-
- if (ret)
- *ret = TAKE_PTR(buf);
- if (ret_size)
- *ret_size = n;
-
- return 0;
- }
-
- bufsize = (size_t) n;
- }
-}
diff --git a/src/shared/keyring-util.h b/src/shared/keyring-util.h
deleted file mode 100644
index c8c53f1..0000000
--- a/src/shared/keyring-util.h
+++ /dev/null
@@ -1,11 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <sys/types.h>
-
-#include "missing_keyctl.h"
-
-/* Like TAKE_PTR() but for key_serial_t, resetting them to -1 */
-#define TAKE_KEY_SERIAL(key_serial) TAKE_GENERIC(key_serial, key_serial_t, -1)
-
-int keyring_read(key_serial_t serial, void **ret, size_t *ret_size);
diff --git a/src/shared/killall.c b/src/shared/killall.c
index 917b773..a087364 100644
--- a/src/shared/killall.c
+++ b/src/shared/killall.c
@@ -116,18 +116,18 @@ static bool ignore_proc(const PidRef *pid, bool warn_rootfs) {
return true;
}
-static void log_children_no_yet_killed(Set *pids) {
+static void log_children_not_yet_killed(Set *pids) {
_cleanup_free_ char *lst_child = NULL;
- void *p;
int r;
+ void *p;
SET_FOREACH(p, pids) {
_cleanup_free_ char *s = NULL;
if (pid_get_comm(PTR_TO_PID(p), &s) >= 0)
- r = strextendf(&lst_child, ", " PID_FMT " (%s)", PTR_TO_PID(p), s);
+ r = strextendf_with_separator(&lst_child, ", ", PID_FMT " (%s)", PTR_TO_PID(p), s);
else
- r = strextendf(&lst_child, ", " PID_FMT, PTR_TO_PID(p));
+ r = strextendf_with_separator(&lst_child, ", ", PID_FMT, PTR_TO_PID(p));
if (r < 0)
return (void) log_oom_warning();
}
@@ -135,7 +135,7 @@ static void log_children_no_yet_killed(Set *pids) {
if (isempty(lst_child))
return;
- log_warning("Waiting for process: %s", lst_child + 2);
+ log_warning("Waiting for process: %s", lst_child);
}
static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) {
@@ -200,7 +200,7 @@ static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) {
n = now(CLOCK_MONOTONIC);
if (date_log_child > 0 && n >= date_log_child) {
- log_children_no_yet_killed(pids);
+ log_children_not_yet_killed(pids);
/* Log the children not yet killed only once */
date_log_child = 0;
}
@@ -234,14 +234,14 @@ static int killall(int sig, Set *pids, bool send_sighup) {
r = proc_dir_open(&dir);
if (r < 0)
- return log_warning_errno(r, "opendir(/proc) failed: %m");
+ return log_warning_errno(r, "Failed to open /proc/: %m");
for (;;) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
r = proc_dir_read_pidref(dir, &pidref);
if (r < 0)
- return log_warning_errno(r, "Failed to enumerate /proc: %m");
+ return log_warning_errno(r, "Failed to enumerate /proc/: %m");
if (r == 0)
break;
diff --git a/src/shared/label-util.c b/src/shared/label-util.c
index 308fbff..2c482da 100644
--- a/src/shared/label-util.c
+++ b/src/shared/label-util.c
@@ -81,22 +81,23 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati
return mac_smack_fix(to, 0);
}
-int mknod_label(const char *pathname, mode_t mode, dev_t dev) {
+int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev) {
int r;
+ assert(dirfd >= 0 || dirfd == AT_FDCWD);
assert(pathname);
- r = mac_selinux_create_file_prepare(pathname, mode);
+ r = mac_selinux_create_file_prepare_at(dirfd, pathname, mode);
if (r < 0)
return r;
- r = RET_NERRNO(mknod(pathname, mode, dev));
+ r = RET_NERRNO(mknodat(dirfd, pathname, mode, dev));
mac_selinux_create_file_clear();
if (r < 0)
return r;
- return mac_smack_fix(pathname, 0);
+ return mac_smack_fix_full(dirfd, pathname, NULL, 0);
}
int btrfs_subvol_make_label(const char *path) {
diff --git a/src/shared/label-util.h b/src/shared/label-util.h
index 7fb98c7..5a19a4c 100644
--- a/src/shared/label-util.h
+++ b/src/shared/label-util.h
@@ -21,7 +21,11 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati
static inline int symlink_atomic_label(const char *from, const char *to) {
return symlink_atomic_full_label(from, to, false);
}
-int mknod_label(const char *pathname, mode_t mode, dev_t dev);
+
+int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev);
+static inline int mknod_label(const char *pathname, mode_t mode, dev_t dev) {
+ return mknodat_label(AT_FDCWD, pathname, mode, dev);
+}
int btrfs_subvol_make_label(const char *path);
diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c
new file mode 100644
index 0000000..58f6554
--- /dev/null
+++ b/src/shared/libarchive-util.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "libarchive-util.h"
+
+#if HAVE_LIBARCHIVE
+static void *libarchive_dl = NULL;
+
+DLSYM_FUNCTION(archive_entry_free);
+DLSYM_FUNCTION(archive_entry_new);
+DLSYM_FUNCTION(archive_entry_set_ctime);
+DLSYM_FUNCTION(archive_entry_set_filetype);
+DLSYM_FUNCTION(archive_entry_set_gid);
+DLSYM_FUNCTION(archive_entry_set_mtime);
+DLSYM_FUNCTION(archive_entry_set_pathname);
+DLSYM_FUNCTION(archive_entry_set_perm);
+DLSYM_FUNCTION(archive_entry_set_rdevmajor);
+DLSYM_FUNCTION(archive_entry_set_rdevminor);
+DLSYM_FUNCTION(archive_entry_set_symlink);
+DLSYM_FUNCTION(archive_entry_set_size);
+DLSYM_FUNCTION(archive_entry_set_uid);
+DLSYM_FUNCTION(archive_error_string);
+DLSYM_FUNCTION(archive_write_close);
+DLSYM_FUNCTION(archive_write_data);
+DLSYM_FUNCTION(archive_write_free);
+DLSYM_FUNCTION(archive_write_header);
+DLSYM_FUNCTION(archive_write_new);
+DLSYM_FUNCTION(archive_write_open_FILE);
+DLSYM_FUNCTION(archive_write_open_fd);
+DLSYM_FUNCTION(archive_write_set_format_filter_by_ext);
+DLSYM_FUNCTION(archive_write_set_format_gnutar);
+
+int dlopen_libarchive(void) {
+ ELF_NOTE_DLOPEN("archive",
+ "Support for decompressing archive files",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libarchive.so.13");
+
+ return dlopen_many_sym_or_warn(
+ &libarchive_dl,
+ "libarchive.so.13",
+ LOG_DEBUG,
+ DLSYM_ARG(archive_entry_free),
+ DLSYM_ARG(archive_entry_new),
+ DLSYM_ARG(archive_entry_set_ctime),
+ DLSYM_ARG(archive_entry_set_filetype),
+ DLSYM_ARG(archive_entry_set_gid),
+ DLSYM_ARG(archive_entry_set_mtime),
+ DLSYM_ARG(archive_entry_set_pathname),
+ DLSYM_ARG(archive_entry_set_perm),
+ DLSYM_ARG(archive_entry_set_rdevmajor),
+ DLSYM_ARG(archive_entry_set_rdevminor),
+ DLSYM_ARG(archive_entry_set_size),
+ DLSYM_ARG(archive_entry_set_symlink),
+ DLSYM_ARG(archive_entry_set_uid),
+ DLSYM_ARG(archive_error_string),
+ DLSYM_ARG(archive_write_close),
+ DLSYM_ARG(archive_write_data),
+ DLSYM_ARG(archive_write_free),
+ DLSYM_ARG(archive_write_header),
+ DLSYM_ARG(archive_write_new),
+ DLSYM_ARG(archive_write_open_FILE),
+ DLSYM_ARG(archive_write_open_fd),
+ DLSYM_ARG(archive_write_set_format_filter_by_ext),
+ DLSYM_ARG(archive_write_set_format_gnutar));
+}
+
+#endif
diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h
new file mode 100644
index 0000000..8003da9
--- /dev/null
+++ b/src/shared/libarchive-util.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "dlfcn-util.h"
+
+#if HAVE_LIBARCHIVE
+#include <archive.h>
+#include <archive_entry.h>
+
+DLSYM_PROTOTYPE(archive_entry_free);
+DLSYM_PROTOTYPE(archive_entry_new);
+DLSYM_PROTOTYPE(archive_entry_set_ctime);
+DLSYM_PROTOTYPE(archive_entry_set_filetype);
+DLSYM_PROTOTYPE(archive_entry_set_gid);
+DLSYM_PROTOTYPE(archive_entry_set_mtime);
+DLSYM_PROTOTYPE(archive_entry_set_pathname);
+DLSYM_PROTOTYPE(archive_entry_set_perm);
+DLSYM_PROTOTYPE(archive_entry_set_rdevmajor);
+DLSYM_PROTOTYPE(archive_entry_set_rdevminor);
+DLSYM_PROTOTYPE(archive_entry_set_symlink);
+DLSYM_PROTOTYPE(archive_entry_set_size);
+DLSYM_PROTOTYPE(archive_entry_set_uid);
+DLSYM_PROTOTYPE(archive_error_string);
+DLSYM_PROTOTYPE(archive_write_close);
+DLSYM_PROTOTYPE(archive_write_data);
+DLSYM_PROTOTYPE(archive_write_free);
+DLSYM_PROTOTYPE(archive_write_header);
+DLSYM_PROTOTYPE(archive_write_new);
+DLSYM_PROTOTYPE(archive_write_open_FILE);
+DLSYM_PROTOTYPE(archive_write_open_fd);
+DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext);
+DLSYM_PROTOTYPE(archive_write_set_format_gnutar);
+
+int dlopen_libarchive(void);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive_entry*, sym_archive_entry_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_write_free, NULL);
+
+#else
+
+static inline int dlopen_libarchive(void) {
+ return -EOPNOTSUPP;
+}
+
+#endif
diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c
index 81e6f17..50b2502 100644
--- a/src/shared/libcrypt-util.c
+++ b/src/shared/libcrypt-util.c
@@ -114,7 +114,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da
if (!*data) {
*data = new0(struct crypt_data, 1);
if (!*data) {
- errno = -ENOMEM;
+ errno = ENOMEM;
return NULL;
}
@@ -140,7 +140,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da
int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret) {
_cleanup_free_ char *salt = NULL;
_cleanup_(erase_and_freep) void *_cd_data = NULL;
- char *p;
+ const char *p;
int r, _cd_size = 0;
assert(!!cd_data == !!cd_size);
@@ -155,12 +155,7 @@ int hash_password_full(const char *password, void **cd_data, int *cd_size, char
return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)),
CRYPT_RA_NAME "() failed: %m");
- p = strdup(p);
- if (!p)
- return -ENOMEM;
-
- *ret = p;
- return 0;
+ return strdup_to(ret, p);
}
bool looks_like_hashed_password(const char *s) {
diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c
index 1cc3afe..37f6898 100644
--- a/src/shared/libfido2-util.c
+++ b/src/shared/libfido2-util.c
@@ -16,53 +16,54 @@
static void *libfido2_dl = NULL;
-int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t) = NULL;
-void (*sym_fido_assert_free)(fido_assert_t **) = NULL;
-size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t) = NULL;
-const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t) = NULL;
-fido_assert_t* (*sym_fido_assert_new)(void) = NULL;
-int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t) = NULL;
-int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL;
-int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL;
-int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL;
-int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL;
-int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL;
-size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL;
-char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL;
-void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL;
-fido_cbor_info_t* (*sym_fido_cbor_info_new)(void) = NULL;
-size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *) = NULL;
-char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *) = NULL;
-const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *) = NULL;
-void (*sym_fido_cred_free)(fido_cred_t **) = NULL;
-size_t (*sym_fido_cred_id_len)(const fido_cred_t *) = NULL;
-const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *) = NULL;
-fido_cred_t* (*sym_fido_cred_new)(void) = NULL;
-int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t) = NULL;
-int (*sym_fido_cred_set_extensions)(fido_cred_t *, int) = NULL;
-int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t) = NULL;
-int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *) = NULL;
-int (*sym_fido_cred_set_type)(fido_cred_t *, int) = NULL;
-int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *) = NULL;
-int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t) = NULL;
-void (*sym_fido_dev_free)(fido_dev_t **) = NULL;
-int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *) = NULL;
-int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *) = NULL;
-void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t) = NULL;
-int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *) = NULL;
-const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *) = NULL;
-const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *) = NULL;
-fido_dev_info_t* (*sym_fido_dev_info_new)(size_t) = NULL;
-const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *) = NULL;
-const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t) = NULL;
-bool (*sym_fido_dev_is_fido2)(const fido_dev_t *) = NULL;
-int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *) = NULL;
-fido_dev_t* (*sym_fido_dev_new)(void) = NULL;
-int (*sym_fido_dev_open)(fido_dev_t *, const char *) = NULL;
-int (*sym_fido_dev_close)(fido_dev_t *) = NULL;
-void (*sym_fido_init)(int) = NULL;
-void (*sym_fido_set_log_handler)(fido_log_handler_t *) = NULL;
-const char* (*sym_fido_strerr)(int) = NULL;
+DLSYM_FUNCTION(fido_assert_allow_cred);
+DLSYM_FUNCTION(fido_assert_free);
+DLSYM_FUNCTION(fido_assert_hmac_secret_len);
+DLSYM_FUNCTION(fido_assert_hmac_secret_ptr);
+DLSYM_FUNCTION(fido_assert_new);
+DLSYM_FUNCTION(fido_assert_set_clientdata_hash);
+DLSYM_FUNCTION(fido_assert_set_extensions);
+DLSYM_FUNCTION(fido_assert_set_hmac_salt);
+DLSYM_FUNCTION(fido_assert_set_rp);
+DLSYM_FUNCTION(fido_assert_set_up);
+DLSYM_FUNCTION(fido_assert_set_uv);
+DLSYM_FUNCTION(fido_cbor_info_extensions_len);
+DLSYM_FUNCTION(fido_cbor_info_extensions_ptr);
+DLSYM_FUNCTION(fido_cbor_info_free);
+DLSYM_FUNCTION(fido_cbor_info_new);
+DLSYM_FUNCTION(fido_cbor_info_options_len);
+DLSYM_FUNCTION(fido_cbor_info_options_name_ptr);
+DLSYM_FUNCTION(fido_cbor_info_options_value_ptr);
+DLSYM_FUNCTION(fido_cred_free);
+DLSYM_FUNCTION(fido_cred_id_len);
+DLSYM_FUNCTION(fido_cred_id_ptr);
+DLSYM_FUNCTION(fido_cred_new);
+DLSYM_FUNCTION(fido_cred_set_clientdata_hash);
+DLSYM_FUNCTION(fido_cred_set_extensions);
+DLSYM_FUNCTION(fido_cred_set_prot);
+DLSYM_FUNCTION(fido_cred_set_rk);
+DLSYM_FUNCTION(fido_cred_set_rp);
+DLSYM_FUNCTION(fido_cred_set_type);
+DLSYM_FUNCTION(fido_cred_set_user);
+DLSYM_FUNCTION(fido_cred_set_uv);
+DLSYM_FUNCTION(fido_dev_free);
+DLSYM_FUNCTION(fido_dev_get_assert);
+DLSYM_FUNCTION(fido_dev_get_cbor_info);
+DLSYM_FUNCTION(fido_dev_info_free);
+DLSYM_FUNCTION(fido_dev_info_manifest);
+DLSYM_FUNCTION(fido_dev_info_manufacturer_string);
+DLSYM_FUNCTION(fido_dev_info_product_string);
+DLSYM_FUNCTION(fido_dev_info_new);
+DLSYM_FUNCTION(fido_dev_info_path);
+DLSYM_FUNCTION(fido_dev_info_ptr);
+DLSYM_FUNCTION(fido_dev_is_fido2);
+DLSYM_FUNCTION(fido_dev_make_cred);
+DLSYM_FUNCTION(fido_dev_new);
+DLSYM_FUNCTION(fido_dev_open);
+DLSYM_FUNCTION(fido_dev_close);
+DLSYM_FUNCTION(fido_init);
+DLSYM_FUNCTION(fido_set_log_handler);
+DLSYM_FUNCTION(fido_strerr);
static void fido_log_propagate_handler(const char *s) {
log_debug("libfido2: %s", strempty(s));
@@ -71,6 +72,11 @@ static void fido_log_propagate_handler(const char *s) {
int dlopen_libfido2(void) {
int r;
+ ELF_NOTE_DLOPEN("fido2",
+ "Support fido2 for encryption and authentication",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libfido2.so.1");
+
r = dlopen_many_sym_or_warn(
&libfido2_dl, "libfido2.so.1", LOG_DEBUG,
DLSYM_ARG(fido_assert_allow_cred),
@@ -97,6 +103,7 @@ int dlopen_libfido2(void) {
DLSYM_ARG(fido_cred_new),
DLSYM_ARG(fido_cred_set_clientdata_hash),
DLSYM_ARG(fido_cred_set_extensions),
+ DLSYM_ARG(fido_cred_set_prot),
DLSYM_ARG(fido_cred_set_rk),
DLSYM_ARG(fido_cred_set_rp),
DLSYM_ARG(fido_cred_set_type),
@@ -574,7 +581,7 @@ static int fido2_use_hmac_hash_specific_token(
/* COSE_ECDH_ES256 is not usable with fido_cred_set_type() thus it's not listed here. */
static const char *fido2_algorithm_to_string(int alg) {
- switch(alg) {
+ switch (alg) {
case COSE_ES256:
return "es256";
case COSE_RS256:
@@ -686,7 +693,8 @@ int fido2_generate_hmac_hash(
const char *user_name,
const char *user_display_name,
const char *user_icon,
- const char *askpw_icon_name,
+ const char *askpw_icon,
+ const char *askpw_credential,
Fido2EnrollFlags lock_with,
int cred_alg,
void **ret_cid, size_t *ret_cid_size,
@@ -775,10 +783,21 @@ int fido2_generate_hmac_hash(
if (!c)
return log_oom();
- r = sym_fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
+ int extensions = FIDO_EXT_HMAC_SECRET;
+ if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) {
+ /* Attempt to use the "cred protect" extension, requiring user verification (UV) for this
+ * credential. If the authenticator doesn't support the extension, it will be ignored. */
+ extensions |= FIDO_EXT_CRED_PROTECT;
+
+ r = sym_fido_cred_set_prot(c, FIDO_CRED_PROT_UV_REQUIRED);
+ if (r != FIDO_OK)
+ log_warning("Failed to set protection level on FIDO2 credential, ignoring: %s", sym_fido_strerr(r));
+ }
+
+ r = sym_fido_cred_set_extensions(c, extensions);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r));
+ "Failed to enable extensions on FIDO2 credential: %s", sym_fido_strerr(r));
r = sym_fido_cred_set_rp(c, rp_id, rp_name);
if (r != FIDO_OK)
@@ -829,7 +848,17 @@ int fido2_generate_hmac_hash(
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
- r = sym_fido_dev_make_cred(d, c, NULL);
+ /* If we are using the user PIN, then we must pass that PIN to the get_assertion call below, or
+ * the authenticator will use the non-user-verification HMAC secret (which differs from the one when
+ * the PIN is passed).
+ *
+ * Rather than potentially trying and failing to create the credential, just collect the PIN first
+ * and then pass it to both the make_credential and the get_assertion operations. */
+ if (FLAGS_SET(lock_with, FIDO2ENROLL_PIN))
+ r = FIDO_ERR_PIN_REQUIRED;
+ else
+ r = sym_fido_dev_make_cred(d, c, NULL);
+
if (r == FIDO_ERR_PIN_REQUIRED) {
if (!has_client_pin)
@@ -838,8 +867,14 @@ int fido2_generate_hmac_hash(
for (;;) {
_cleanup_strv_free_erase_ char **pin = NULL;
-
- r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin);
+ AskPasswordRequest req = {
+ .message = "Please enter security token PIN:",
+ .icon = askpw_icon,
+ .keyring = "fido2-pin",
+ .credential = askpw_credential,
+ };
+
+ r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h
index 4cfc95f..5e40b2a 100644
--- a/src/shared/libfido2-util.h
+++ b/src/shared/libfido2-util.h
@@ -17,53 +17,56 @@ typedef enum Fido2EnrollFlags {
#if HAVE_LIBFIDO2
#include <fido.h>
-extern int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t);
-extern void (*sym_fido_assert_free)(fido_assert_t **);
-extern size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t);
-extern const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t);
-extern fido_assert_t* (*sym_fido_assert_new)(void);
-extern int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t);
-extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int);
-extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t);
-extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *);
-extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t);
-extern int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t);
-extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *);
-extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *);
-extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **);
-extern fido_cbor_info_t* (*sym_fido_cbor_info_new)(void);
-extern size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *);
-extern char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *);
-extern const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *);
-extern void (*sym_fido_cred_free)(fido_cred_t **);
-extern size_t (*sym_fido_cred_id_len)(const fido_cred_t *);
-extern const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *);
-extern fido_cred_t* (*sym_fido_cred_new)(void);
-extern int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t);
-extern int (*sym_fido_cred_set_extensions)(fido_cred_t *, int);
-extern int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t);
-extern int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *);
-extern int (*sym_fido_cred_set_type)(fido_cred_t *, int);
-extern int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *);
-extern int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t);
-extern void (*sym_fido_dev_free)(fido_dev_t **);
-extern int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *);
-extern int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *);
-extern void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t);
-extern int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *);
-extern const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *);
-extern const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *);
-extern fido_dev_info_t* (*sym_fido_dev_info_new)(size_t);
-extern const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *);
-extern const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t);
-extern bool (*sym_fido_dev_is_fido2)(const fido_dev_t *);
-extern int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *);
-extern fido_dev_t* (*sym_fido_dev_new)(void);
-extern int (*sym_fido_dev_open)(fido_dev_t *, const char *);
-extern int (*sym_fido_dev_close)(fido_dev_t *);
-extern void (*sym_fido_init)(int);
-extern void (*sym_fido_set_log_handler)(fido_log_handler_t *);
-extern const char* (*sym_fido_strerr)(int);
+#include "dlfcn-util.h"
+
+DLSYM_PROTOTYPE(fido_assert_allow_cred);
+DLSYM_PROTOTYPE(fido_assert_free);
+DLSYM_PROTOTYPE(fido_assert_hmac_secret_len);
+DLSYM_PROTOTYPE(fido_assert_hmac_secret_ptr);
+DLSYM_PROTOTYPE(fido_assert_new);
+DLSYM_PROTOTYPE(fido_assert_set_clientdata_hash);
+DLSYM_PROTOTYPE(fido_assert_set_extensions);
+DLSYM_PROTOTYPE(fido_assert_set_hmac_salt);
+DLSYM_PROTOTYPE(fido_assert_set_rp);
+DLSYM_PROTOTYPE(fido_assert_set_up);
+DLSYM_PROTOTYPE(fido_assert_set_uv);
+DLSYM_PROTOTYPE(fido_cbor_info_extensions_len);
+DLSYM_PROTOTYPE(fido_cbor_info_extensions_ptr);
+DLSYM_PROTOTYPE(fido_cbor_info_free);
+DLSYM_PROTOTYPE(fido_cbor_info_new);
+DLSYM_PROTOTYPE(fido_cbor_info_options_len);
+DLSYM_PROTOTYPE(fido_cbor_info_options_name_ptr);
+DLSYM_PROTOTYPE(fido_cbor_info_options_value_ptr);
+DLSYM_PROTOTYPE(fido_cred_free);
+DLSYM_PROTOTYPE(fido_cred_id_len);
+DLSYM_PROTOTYPE(fido_cred_id_ptr);
+DLSYM_PROTOTYPE(fido_cred_new);
+DLSYM_PROTOTYPE(fido_cred_set_clientdata_hash);
+DLSYM_PROTOTYPE(fido_cred_set_extensions);
+DLSYM_PROTOTYPE(fido_cred_set_prot);
+DLSYM_PROTOTYPE(fido_cred_set_rk);
+DLSYM_PROTOTYPE(fido_cred_set_rp);
+DLSYM_PROTOTYPE(fido_cred_set_type);
+DLSYM_PROTOTYPE(fido_cred_set_user);
+DLSYM_PROTOTYPE(fido_cred_set_uv);
+DLSYM_PROTOTYPE(fido_dev_free);
+DLSYM_PROTOTYPE(fido_dev_get_assert);
+DLSYM_PROTOTYPE(fido_dev_get_cbor_info);
+DLSYM_PROTOTYPE(fido_dev_info_free);
+DLSYM_PROTOTYPE(fido_dev_info_manifest);
+DLSYM_PROTOTYPE(fido_dev_info_manufacturer_string);
+DLSYM_PROTOTYPE(fido_dev_info_product_string);
+DLSYM_PROTOTYPE(fido_dev_info_new);
+DLSYM_PROTOTYPE(fido_dev_info_path);
+DLSYM_PROTOTYPE(fido_dev_info_ptr);
+DLSYM_PROTOTYPE(fido_dev_is_fido2);
+DLSYM_PROTOTYPE(fido_dev_make_cred);
+DLSYM_PROTOTYPE(fido_dev_new);
+DLSYM_PROTOTYPE(fido_dev_open);
+DLSYM_PROTOTYPE(fido_dev_close);
+DLSYM_PROTOTYPE(fido_init);
+DLSYM_PROTOTYPE(fido_set_log_handler);
+DLSYM_PROTOTYPE(fido_strerr);
int dlopen_libfido2(void);
@@ -109,7 +112,8 @@ int fido2_generate_hmac_hash(
const char *user_name,
const char *user_display_name,
const char *user_icon,
- const char *askpw_icon_name,
+ const char *askpw_icon,
+ const char *askpw_credential,
Fido2EnrollFlags lock_with,
int cred_alg,
void **ret_cid, size_t *ret_cid_size,
diff --git a/src/shared/local-addresses.c b/src/shared/local-addresses.c
index a1577de..5d5435f 100644
--- a/src/shared/local-addresses.c
+++ b/src/shared/local-addresses.c
@@ -25,7 +25,11 @@ static int address_compare(const struct local_address *a, const struct local_add
if (r != 0)
return r;
- r = CMP(a->metric, b->metric);
+ r = CMP(a->priority, b->priority);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->weight, b->weight);
if (r != 0)
return r;
@@ -36,6 +40,17 @@ static int address_compare(const struct local_address *a, const struct local_add
return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family));
}
+bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle) {
+ assert(addresses || n_addresses == 0);
+ assert(needle);
+
+ for (size_t i = 0; i < n_addresses; i++)
+ if (address_compare(addresses + i, needle) == 0)
+ return true;
+
+ return false;
+}
+
static void suppress_duplicates(struct local_address *list, size_t *n_list) {
size_t old_size, new_size;
@@ -58,6 +73,48 @@ static void suppress_duplicates(struct local_address *list, size_t *n_list) {
*n_list = new_size;
}
+static int add_local_address_full(
+ struct local_address **list,
+ size_t *n_list,
+ int ifindex,
+ unsigned char scope,
+ uint32_t priority,
+ uint32_t weight,
+ int family,
+ const union in_addr_union *address) {
+
+ assert(list);
+ assert(n_list);
+ assert(ifindex > 0);
+ assert(IN_SET(family, AF_INET, AF_INET6));
+ assert(address);
+
+ if (!GREEDY_REALLOC(*list, *n_list + 1))
+ return -ENOMEM;
+
+ (*list)[(*n_list)++] = (struct local_address) {
+ .ifindex = ifindex,
+ .scope = scope,
+ .priority = priority,
+ .weight = weight,
+ .family = family,
+ .address = *address,
+ };
+
+ return 1;
+}
+
+static int add_local_address(
+ struct local_address **list,
+ size_t *n_list,
+ int ifindex,
+ unsigned char scope,
+ int family,
+ const union in_addr_union *address) {
+
+ return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address);
+}
+
int local_addresses(
sd_netlink *context,
int ifindex,
@@ -91,8 +148,8 @@ int local_addresses(
return r;
for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
- struct local_address *a;
- unsigned char flags;
+ union in_addr_union a;
+ unsigned char flags, scope;
uint16_t type;
int ifi, family;
@@ -115,62 +172,58 @@ int local_addresses(
r = sd_rtnl_message_addr_get_family(m, &family);
if (r < 0)
return r;
+ if (!IN_SET(family, AF_INET, AF_INET6))
+ continue;
if (af != AF_UNSPEC && af != family)
continue;
r = sd_rtnl_message_addr_get_flags(m, &flags);
if (r < 0)
return r;
- if (flags & IFA_F_DEPRECATED)
+ if ((flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) != 0)
continue;
- if (!GREEDY_REALLOC0(list, n_list+1))
- return -ENOMEM;
-
- a = list + n_list;
-
- r = sd_rtnl_message_addr_get_scope(m, &a->scope);
+ r = sd_rtnl_message_addr_get_scope(m, &scope);
if (r < 0)
return r;
- if (ifindex == 0 && IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE))
+ if (ifindex == 0 && IN_SET(scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE))
continue;
switch (family) {
case AF_INET:
- r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in);
+ r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a.in);
if (r < 0) {
- r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in);
+ r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a.in);
if (r < 0)
continue;
}
break;
case AF_INET6:
- r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6);
+ r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a.in6);
if (r < 0) {
- r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6);
+ r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a.in6);
if (r < 0)
continue;
}
break;
default:
- continue;
+ assert_not_reached();
}
- a->ifindex = ifi;
- a->family = family;
-
- n_list++;
+ r = add_local_address(&list, &n_list, ifi, scope, family, &a);
+ if (r < 0)
+ return r;
};
- if (ret) {
- typesafe_qsort(list, n_list, address_compare);
- suppress_duplicates(list, &n_list);
+ typesafe_qsort(list, n_list, address_compare);
+ suppress_duplicates(list, &n_list);
+
+ if (ret)
*ret = TAKE_PTR(list);
- }
return (int) n_list;
}
@@ -178,27 +231,117 @@ int local_addresses(
static int add_local_gateway(
struct local_address **list,
size_t *n_list,
- int af,
int ifindex,
- uint32_t metric,
- const RouteVia *via) {
+ uint32_t priority,
+ uint32_t weight,
+ int family,
+ const union in_addr_union *address) {
+
+ return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address);
+}
+
+static int parse_nexthop_one(
+ struct local_address **list,
+ size_t *n_list,
+ bool allow_via,
+ int family,
+ uint32_t priority,
+ const struct rtnexthop *rtnh) {
+
+ bool has_gw = false;
+ int r;
+
+ assert(rtnh);
+
+ size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop);
+ for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
+
+ switch (attr->rta_type) {
+ case RTA_GATEWAY:
+ if (has_gw)
+ return -EBADMSG;
+
+ has_gw = true;
+
+ if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family)))
+ return -EBADMSG;
+
+ union in_addr_union a;
+ memcpy(&a, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family));
+ r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case RTA_VIA:
+ if (has_gw)
+ return -EBADMSG;
+
+ has_gw = true;
+
+ if (!allow_via)
+ continue;
+
+ if (family != AF_INET)
+ return -EBADMSG; /* RTA_VIA is only supported for IPv4 routes. */
+
+ if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia)))
+ return -EBADMSG;
+
+ RouteVia *via = RTA_DATA(attr);
+ if (via->family != AF_INET6)
+ return -EBADMSG; /* gateway address should be always IPv6. */
+
+ r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, via->family,
+ &(union in_addr_union) { .in6 = via->address.in6 });
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ return 0;
+}
+
+static int parse_nexthops(
+ struct local_address **list,
+ size_t *n_list,
+ int ifindex,
+ bool allow_via,
+ int family,
+ uint32_t priority,
+ const struct rtnexthop *rtnh,
+ size_t size) {
+
+ int r;
assert(list);
assert(n_list);
- assert(via);
+ assert(IN_SET(family, AF_INET, AF_INET6));
+ assert(rtnh || size == 0);
- if (af != AF_UNSPEC && af != via->family)
- return 0;
+ if (size < sizeof(struct rtnexthop))
+ return -EBADMSG;
- if (!GREEDY_REALLOC(*list, *n_list + 1))
- return -ENOMEM;
+ for (; size >= sizeof(struct rtnexthop); ) {
+ if (NLMSG_ALIGN(rtnh->rtnh_len) > size)
+ return -EBADMSG;
- (*list)[(*n_list)++] = (struct local_address) {
- .ifindex = ifindex,
- .metric = metric,
- .family = via->family,
- .address = via->address,
- };
+ if (rtnh->rtnh_len < sizeof(struct rtnexthop))
+ return -EBADMSG;
+
+ if (ifindex > 0 && rtnh->rtnh_ifindex != ifindex)
+ goto next_nexthop;
+
+ r = parse_nexthop_one(list, n_list, allow_via, family, priority, rtnh);
+ if (r < 0)
+ return r;
+
+ next_nexthop:
+ size -= NLMSG_ALIGN(rtnh->rtnh_len);
+ rtnh = RTNH_NEXT(rtnh);
+ }
return 0;
}
@@ -215,6 +358,12 @@ int local_gateways(
size_t n_list = 0;
int r;
+ /* The RTA_VIA attribute is used only for IPv4 routes with an IPv6 gateway. If IPv4 gateways are
+ * requested (af == AF_INET), then we do not return IPv6 gateway addresses. Similarly, if IPv6
+ * gateways are requested (af == AF_INET6), then we do not return gateway addresses for IPv4 routes.
+ * So, the RTA_VIA attribute is only parsed when af == AF_UNSPEC. */
+ bool allow_via = af == AF_UNSPEC;
+
if (context)
rtnl = sd_netlink_ref(context);
else {
@@ -244,15 +393,10 @@ int local_gateways(
return r;
for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
- _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL;
- _cleanup_free_ void *rta_multipath = NULL;
- union in_addr_union gateway;
uint16_t type;
unsigned char dst_len, src_len, table;
- uint32_t ifi = 0, metric = 0;
- size_t rta_len;
+ uint32_t ifi = 0, priority = 0;
int family;
- RouteVia via;
r = sd_netlink_message_get_errno(m);
if (r < 0)
@@ -283,7 +427,7 @@ int local_gateways(
if (table != RT_TABLE_MAIN)
continue;
- r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &metric);
+ r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &priority);
if (r < 0 && r != -ENODATA)
return r;
@@ -292,6 +436,8 @@ int local_gateways(
return r;
if (!IN_SET(family, AF_INET, AF_INET6))
continue;
+ if (af != AF_UNSPEC && af != family)
+ continue;
r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi);
if (r < 0 && r != -ENODATA)
@@ -302,64 +448,73 @@ int local_gateways(
if (ifindex > 0 && (int) ifi != ifindex)
continue;
+ union in_addr_union gateway;
r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway);
if (r < 0 && r != -ENODATA)
return r;
if (r >= 0) {
- via.family = family;
- via.address = gateway;
- r = add_local_gateway(&list, &n_list, af, ifi, metric, &via);
+ r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway);
if (r < 0)
return r;
continue;
}
+ if (!allow_via)
+ continue;
+
if (family != AF_INET)
continue;
+ RouteVia via;
r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via);
if (r < 0 && r != -ENODATA)
return r;
if (r >= 0) {
- r = add_local_gateway(&list, &n_list, af, ifi, metric, &via);
+ if (via.family != AF_INET6)
+ return -EBADMSG;
+
+ r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family,
+ &(union in_addr_union) { .in6 = via.address.in6 });
if (r < 0)
return r;
-
- continue;
}
+
+ /* If the route has RTA_OIF, it does not have RTA_MULTIPATH. */
+ continue;
}
+ size_t rta_len;
+ _cleanup_free_ void *rta_multipath = NULL;
r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath);
if (r < 0 && r != -ENODATA)
return r;
if (r >= 0) {
- MultipathRoute *mr;
-
- r = rtattr_read_nexthop(rta_multipath, rta_len, family, &multipath_routes);
+ r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len);
if (r < 0)
return r;
-
- ORDERED_SET_FOREACH(mr, multipath_routes) {
- if (ifindex > 0 && mr->ifindex != ifindex)
- continue;
-
- r = add_local_gateway(&list, &n_list, af, ifi, metric, &mr->gateway);
- if (r < 0)
- return r;
- }
}
}
- if (ret) {
- typesafe_qsort(list, n_list, address_compare);
- suppress_duplicates(list, &n_list);
+ typesafe_qsort(list, n_list, address_compare);
+ suppress_duplicates(list, &n_list);
+
+ if (ret)
*ret = TAKE_PTR(list);
- }
return (int) n_list;
}
+static int add_local_outbound(
+ struct local_address **list,
+ size_t *n_list,
+ int ifindex,
+ int family,
+ const union in_addr_union *address) {
+
+ return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address);
+}
+
int local_outbounds(
sd_netlink *context,
int ifindex,
@@ -466,29 +621,20 @@ int local_outbounds(
if (in4_addr_is_null(&sa.in.sin_addr)) /* Auto-binding didn't work. :-( */
continue;
- if (!GREEDY_REALLOC(list, n_list+1))
- return -ENOMEM;
-
- list[n_list++] = (struct local_address) {
- .family = gateways[i].family,
- .ifindex = gateways[i].ifindex,
- .address.in = sa.in.sin_addr,
- };
-
+ r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family,
+ &(union in_addr_union) { .in = sa.in.sin_addr });
+ if (r < 0)
+ return r;
break;
case AF_INET6:
if (in6_addr_is_null(&sa.in6.sin6_addr))
continue;
- if (!GREEDY_REALLOC(list, n_list+1))
- return -ENOMEM;
-
- list[n_list++] = (struct local_address) {
- .family = gateways[i].family,
- .ifindex = gateways[i].ifindex,
- .address.in6 = sa.in6.sin6_addr,
- };
+ r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family,
+ &(union in_addr_union) { .in6 = sa.in6.sin6_addr });
+ if (r < 0)
+ return r;
break;
default:
@@ -496,11 +642,11 @@ int local_outbounds(
}
}
- if (ret) {
- typesafe_qsort(list, n_list, address_compare);
- suppress_duplicates(list, &n_list);
+ typesafe_qsort(list, n_list, address_compare);
+ suppress_duplicates(list, &n_list);
+
+ if (ret)
*ret = TAKE_PTR(list);
- }
return (int) n_list;
}
diff --git a/src/shared/local-addresses.h b/src/shared/local-addresses.h
index 38a17d2..399d0c6 100644
--- a/src/shared/local-addresses.h
+++ b/src/shared/local-addresses.h
@@ -6,12 +6,16 @@
#include "in-addr-util.h"
struct local_address {
- int family, ifindex;
+ int ifindex;
unsigned char scope;
- uint32_t metric;
+ uint32_t priority;
+ uint32_t weight;
+ int family;
union in_addr_union address;
};
+bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle);
+
int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret);
int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret);
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
index 0a31be3..c71c868 100644
--- a/src/shared/logs-show.c
+++ b/src/shared/logs-show.c
@@ -5,7 +5,6 @@
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
-#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
@@ -27,11 +26,9 @@
#include "log.h"
#include "logs-show.h"
#include "macro.h"
-#include "namespace-util.h"
#include "output-mode.h"
#include "parse-util.h"
#include "pretty-print.h"
-#include "process-util.h"
#include "sparse-endian.h"
#include "stdio-util.h"
#include "string-table.h"
@@ -367,39 +364,37 @@ static int output_timestamp_realtime(
sd_journal *j,
OutputMode mode,
OutputFlags flags,
- const dual_timestamp *display_ts) {
+ usec_t usec) {
char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)];
- int r;
assert(f);
assert(j);
- assert(display_ts);
- if (!VALID_REALTIME(display_ts->realtime))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
+ if (!VALID_REALTIME(usec))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available.");
if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) {
const char *k;
if (flags & OUTPUT_UTC)
- k = format_timestamp_style(buf, sizeof(buf), display_ts->realtime, TIMESTAMP_UTC);
+ k = format_timestamp_style(buf, sizeof(buf), usec, TIMESTAMP_UTC);
else
- k = format_timestamp(buf, sizeof(buf), display_ts->realtime);
+ k = format_timestamp(buf, sizeof(buf), usec);
if (!k)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to format timestamp: %" PRIu64, display_ts->realtime);
+ "Failed to format timestamp: %" PRIu64, usec);
} else {
struct tm tm;
time_t t;
- t = (time_t) (display_ts->realtime / USEC_PER_SEC);
+ t = (time_t) (usec / USEC_PER_SEC);
switch (mode) {
case OUTPUT_SHORT_UNIX:
- xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, display_ts->realtime % USEC_PER_SEC);
+ xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, usec % USEC_PER_SEC);
break;
case OUTPUT_SHORT_ISO:
@@ -407,13 +402,11 @@ static int output_timestamp_realtime(
size_t tail = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S",
localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC));
if (tail == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to format ISO time");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format ISO time.");
/* No usec in strftime, need to append */
if (mode == OUTPUT_SHORT_ISO_PRECISE) {
- assert(ELEMENTSOF(buf) - tail >= 7);
- snprintf(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, display_ts->realtime % USEC_PER_SEC);
+ assert_se(snprintf_ok(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, usec % USEC_PER_SEC));
tail += 7;
}
@@ -428,19 +421,12 @@ static int output_timestamp_realtime(
if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S",
localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to format syslog time");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format syslog time.");
if (mode == OUTPUT_SHORT_PRECISE) {
- size_t k;
-
assert(sizeof(buf) > strlen(buf));
- k = sizeof(buf) - strlen(buf);
-
- r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, display_ts->realtime % USEC_PER_SEC);
- if (r <= 0 || (size_t) r >= k) /* too long? */
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Failed to format precise time");
+ if (!snprintf_ok(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06"PRIu64, usec % USEC_PER_SEC))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format precise time.");
}
break;
@@ -453,6 +439,77 @@ static int output_timestamp_realtime(
return (int) strlen(buf);
}
+static void parse_display_realtime(
+ sd_journal *j,
+ const char *source_realtime,
+ const char *source_monotonic,
+ usec_t *ret) {
+
+ usec_t t, s, u;
+
+ assert(j);
+ assert(ret);
+
+ /* First, try _SOURCE_REALTIME_TIMESTAMP. */
+ if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) {
+ *ret = t;
+ return;
+ }
+
+ /* Read realtime timestamp in the entry header. */
+ if (sd_journal_get_realtime_usec(j, &t) < 0) {
+ *ret = USEC_INFINITY;
+ return;
+ }
+
+ /* If _SOURCE_MONOTONIC_TIMESTAMP is provided, adjust the header timestamp. */
+ if (source_monotonic && safe_atou64(source_monotonic, &s) >= 0 && VALID_MONOTONIC(s) &&
+ sd_journal_get_monotonic_usec(j, &u, &(sd_id128_t) {}) >= 0) {
+ *ret = map_clock_usec_raw(t, u, s);
+ return;
+ }
+
+ /* Otherwise, use the header timestamp as is. */
+ *ret = t;
+}
+
+static void parse_display_timestamp(
+ sd_journal *j,
+ const char *source_realtime,
+ const char *source_monotonic,
+ dual_timestamp *ret_display_ts,
+ sd_id128_t *ret_boot_id) {
+
+ dual_timestamp header_ts = DUAL_TIMESTAMP_INFINITY, source_ts = DUAL_TIMESTAMP_INFINITY;
+ sd_id128_t boot_id = SD_ID128_NULL;
+ usec_t t;
+
+ assert(j);
+ assert(ret_display_ts);
+ assert(ret_boot_id);
+
+ if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t))
+ source_ts.realtime = t;
+
+ if (source_monotonic && safe_atou64(source_monotonic, &t) >= 0 && VALID_MONOTONIC(t))
+ source_ts.monotonic = t;
+
+ (void) sd_journal_get_realtime_usec(j, &header_ts.realtime);
+ (void) sd_journal_get_monotonic_usec(j, &header_ts.monotonic, &boot_id);
+
+ /* Adjust timestamp if possible. */
+ if (header_ts.realtime != USEC_INFINITY && header_ts.monotonic != USEC_INFINITY) {
+ if (source_ts.realtime == USEC_INFINITY && source_ts.monotonic != USEC_INFINITY)
+ source_ts.realtime = map_clock_usec_raw(header_ts.realtime, header_ts.monotonic, source_ts.monotonic);
+ else if (source_ts.realtime != USEC_INFINITY && source_ts.monotonic == USEC_INFINITY)
+ source_ts.monotonic = map_clock_usec_raw(header_ts.monotonic, header_ts.realtime, source_ts.realtime);
+ }
+
+ ret_display_ts->realtime = source_ts.realtime != USEC_INFINITY ? source_ts.realtime : header_ts.realtime;
+ ret_display_ts->monotonic = source_ts.monotonic != USEC_INFINITY ? source_ts.monotonic : header_ts.monotonic;
+ *ret_boot_id = boot_id;
+}
+
static int output_short(
FILE *f,
sd_journal *j,
@@ -461,42 +518,43 @@ static int output_short(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id) {
+ dual_timestamp *previous_display_ts, /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */
+ sd_id128_t *previous_boot_id) { /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */
int r;
const void *data;
size_t length, n = 0;
_cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL,
*message = NULL, *priority = NULL, *transport = NULL,
- *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL;
+ *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL,
+ *realtime = NULL, *monotonic = NULL;
size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0,
priority_len = 0, transport_len = 0, config_file_len = 0,
unit_len = 0, user_unit_len = 0, documentation_url_len = 0;
+ dual_timestamp display_ts;
+ sd_id128_t boot_id;
int p = LOG_INFO;
bool ellipsized = false, audit;
const ParseFieldVec fields[] = {
- PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len),
- PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len),
- PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len),
- PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len),
- PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len),
- PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len),
- PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len),
- PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len),
- PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len),
- PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len),
- PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len),
- PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len),
+ PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len ),
+ PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len ),
+ PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len ),
+ PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len ),
+ PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len ),
+ PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len ),
+ PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len ),
+ PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len ),
+ PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len ),
+ PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len ),
+ PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len ),
+ PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len),
+ PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL ),
+ PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL ),
};
size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0};
assert(f);
assert(j);
- assert(display_ts);
- assert(boot_id);
assert(previous_display_ts);
assert(previous_boot_id);
@@ -523,6 +581,9 @@ static int output_short(
return 0;
}
+ if (identifier && set_contains(j->exclude_syslog_identifiers, identifier))
+ return 0;
+
if (!(flags & OUTPUT_SHOW_ALL))
strip_tab_ansi(&message, &message_len, highlight_shifted);
@@ -534,10 +595,14 @@ static int output_short(
audit = streq_ptr(transport, "audit");
- if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA))
- r = output_timestamp_monotonic(f, mode, display_ts, boot_id, previous_display_ts, previous_boot_id);
- else
- r = output_timestamp_realtime(f, j, mode, flags, display_ts);
+ if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) {
+ parse_display_timestamp(j, realtime, monotonic, &display_ts, &boot_id);
+ r = output_timestamp_monotonic(f, mode, &display_ts, &boot_id, previous_display_ts, previous_boot_id);
+ } else {
+ usec_t usec;
+ parse_display_realtime(j, realtime, monotonic, &usec);
+ r = output_timestamp_realtime(f, j, mode, flags, usec);
+ }
if (r < 0)
return r;
n += r;
@@ -658,9 +723,48 @@ static int output_short(
if (flags & OUTPUT_CATALOG)
(void) print_catalog(f, j);
+ if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) {
+ *previous_display_ts = display_ts;
+ *previous_boot_id = boot_id;
+ }
+
return ellipsized;
}
+static int get_display_realtime(sd_journal *j, usec_t *ret) {
+ const void *data;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL;
+ size_t length;
+ const ParseFieldVec message_fields[] = {
+ PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL),
+ PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL),
+ };
+ int r;
+
+ assert(j);
+ assert(ret);
+
+ JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
+ r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields));
+ if (r < 0)
+ return r;
+
+ if (realtime && monotonic)
+ break;
+ }
+ if (r < 0)
+ return r;
+
+ (void) parse_display_realtime(j, realtime, monotonic, ret);
+
+ /* Restart all data before */
+ sd_journal_restart_data(j);
+ sd_journal_restart_unique(j);
+ sd_journal_restart_fields(j);
+
+ return 0;
+}
+
static int output_verbose(
FILE *f,
sd_journal *j,
@@ -669,35 +773,38 @@ static int output_verbose(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id) {
+ dual_timestamp *previous_display_ts, /* unused */
+ sd_id128_t *previous_boot_id) { /* unused */
const void *data;
size_t length;
_cleanup_free_ char *cursor = NULL;
char buf[FORMAT_TIMESTAMP_MAX + 7];
const char *timestamp;
+ usec_t usec;
int r;
assert(f);
assert(j);
- assert(display_ts);
- assert(boot_id);
- assert(previous_display_ts);
- assert(previous_boot_id);
(void) sd_journal_set_data_threshold(j, 0);
- if (!VALID_REALTIME(display_ts->realtime))
+ r = get_display_realtime(j, &usec);
+ if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
+ log_debug_errno(r, "Skipping message we can't read: %m");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get journal fields: %m");
+
+ if (!VALID_REALTIME(usec))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available");
r = sd_journal_get_cursor(j, &cursor);
if (r < 0)
return log_error_errno(r, "Failed to get cursor: %m");
- timestamp = format_timestamp_style(buf, sizeof buf, display_ts->realtime,
+ timestamp = format_timestamp_style(buf, sizeof buf, usec,
flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US);
fprintf(f, "%s%s%s %s[%s]%s\n",
timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "",
@@ -786,10 +893,8 @@ static int output_export(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id) {
+ dual_timestamp *previous_display_ts, /* unused */
+ sd_id128_t *previous_boot_id) { /* unused */
sd_id128_t journal_boot_id, seqnum_id;
_cleanup_free_ char *cursor = NULL;
@@ -800,10 +905,6 @@ static int output_export(
int r;
assert(j);
- assert(display_ts);
- assert(boot_id);
- assert(previous_display_ts);
- assert(previous_boot_id);
(void) sd_journal_set_data_threshold(j, 0);
@@ -1055,10 +1156,8 @@ static int output_json(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id) {
+ dual_timestamp *previous_display_ts, /* unused */
+ sd_id128_t *previous_boot_id) { /* unused */
char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))];
_cleanup_(json_variant_unrefp) JsonVariant *object = NULL;
@@ -1073,10 +1172,6 @@ static int output_json(
int r;
assert(j);
- assert(display_ts);
- assert(boot_id);
- assert(previous_display_ts);
- assert(previous_boot_id);
(void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
@@ -1238,20 +1333,14 @@ static int output_cat(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id) {
+ dual_timestamp *previous_display_ts, /* unused */
+ sd_id128_t *previous_boot_id) { /* unused */
int r, prio = LOG_INFO;
const char *field;
assert(j);
assert(f);
- assert(display_ts);
- assert(boot_id);
- assert(previous_display_ts);
- assert(previous_boot_id);
(void) sd_journal_set_data_threshold(j, 0);
@@ -1291,63 +1380,6 @@ static int output_cat(
return 0;
}
-static int get_display_timestamp(
- sd_journal *j,
- dual_timestamp *ret_display_ts,
- sd_id128_t *ret_boot_id) {
-
- const void *data;
- _cleanup_free_ char *realtime = NULL, *monotonic = NULL;
- size_t length = 0, realtime_len = 0, monotonic_len = 0;
- const ParseFieldVec message_fields[] = {
- PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len),
- PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len),
- };
- int r;
- bool realtime_good = false, monotonic_good = false, boot_id_good = false;
-
- assert(j);
- assert(ret_display_ts);
- assert(ret_boot_id);
-
- JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
- r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields));
- if (r < 0)
- return r;
-
- if (realtime && monotonic)
- break;
- }
- if (r < 0)
- return r;
-
- if (realtime)
- realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0;
- if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime))
- realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0;
- if (!realtime_good)
- ret_display_ts->realtime = USEC_INFINITY;
-
- if (monotonic)
- monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0;
- if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic))
- monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0;
- if (!monotonic_good)
- ret_display_ts->monotonic = USEC_INFINITY;
-
- if (!boot_id_good)
- boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0;
- if (!boot_id_good)
- *ret_boot_id = SD_ID128_NULL;
-
- /* Restart all data before */
- sd_journal_restart_data(j);
- sd_journal_restart_unique(j);
- sd_journal_restart_fields(j);
-
- return 0;
-}
-
typedef int (*output_func_t)(
FILE *f,
sd_journal *j,
@@ -1356,10 +1388,8 @@ typedef int (*output_func_t)(
OutputFlags flags,
const Set *output_fields,
const size_t highlight[2],
- const dual_timestamp *display_ts,
- const sd_id128_t *boot_id,
- const dual_timestamp *previous_display_ts,
- const sd_id128_t *previous_boot_id);
+ dual_timestamp *previous_display_ts,
+ sd_id128_t *previous_boot_id);
static output_func_t output_funcs[_OUTPUT_MODE_MAX] = {
@@ -1393,8 +1423,6 @@ int show_journal_entry(
dual_timestamp *previous_display_ts,
sd_id128_t *previous_boot_id) {
- dual_timestamp display_ts = DUAL_TIMESTAMP_NULL;
- sd_id128_t boot_id = SD_ID128_NULL;
int r;
assert(mode >= 0);
@@ -1405,14 +1433,6 @@ int show_journal_entry(
if (n_columns <= 0)
n_columns = columns();
- r = get_display_timestamp(j, &display_ts, &boot_id);
- if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) {
- log_debug_errno(r, "Skipping message we can't read: %m");
- return 0;
- }
- if (r < 0)
- return log_error_errno(r, "Failed to get journal fields: %m");
-
r = output_funcs[mode](
f,
j,
@@ -1421,15 +1441,9 @@ int show_journal_entry(
flags,
output_fields,
highlight,
- &display_ts,
- &boot_id,
previous_display_ts,
previous_boot_id);
- /* Store timestamp and boot ID for next iteration */
- *previous_display_ts = display_ts;
- *previous_boot_id = boot_id;
-
if (ellipsized && r > 0)
*ellipsized = true;
@@ -1570,202 +1584,115 @@ int show_journal(
}
int add_matches_for_unit(sd_journal *j, const char *unit) {
- const char *m1, *m2, *m3, *m4;
int r;
assert(j);
assert(unit);
- m1 = strjoina("_SYSTEMD_UNIT=", unit);
- m2 = strjoina("COREDUMP_UNIT=", unit);
- m3 = strjoina("UNIT=", unit);
- m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit);
-
- (void)(
+ (void) (
/* Look for messages from the service itself */
- (r = sd_journal_add_match(j, m1, 0)) ||
+ (r = journal_add_match_pair(j, "_SYSTEMD_UNIT", unit)) ||
/* Look for coredumps of the service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) ||
- (r = sd_journal_add_match(j, "_UID=0", 0)) ||
- (r = sd_journal_add_match(j, m2, 0)) ||
+ (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", SIZE_MAX)) ||
+ (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ||
+ (r = journal_add_match_pair(j, "COREDUMP_UNIT", unit)) ||
/* Look for messages from PID 1 about this service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, "_PID=1", 0)) ||
- (r = sd_journal_add_match(j, m3, 0)) ||
+ (r = sd_journal_add_match(j, "_PID=1", SIZE_MAX)) ||
+ (r = journal_add_match_pair(j, "UNIT", unit)) ||
/* Look for messages from authorized daemons about this service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, "_UID=0", 0)) ||
- (r = sd_journal_add_match(j, m4, 0))
+ (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ||
+ (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_UNIT", unit))
);
- if (r == 0 && endswith(unit, ".slice")) {
- const char *m5;
-
- m5 = strjoina("_SYSTEMD_SLICE=", unit);
-
+ if (r == 0 && endswith(unit, ".slice"))
/* Show all messages belonging to a slice */
- (void)(
+ (void) (
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, m5, 0))
- );
- }
+ (r = journal_add_match_pair(j, "_SYSTEMD_SLICE", unit))
+ );
return r;
}
-int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) {
+int add_matches_for_user_unit(sd_journal *j, const char *unit) {
+ uid_t uid = getuid();
int r;
- char *m1, *m2, *m3, *m4;
- char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)];
assert(j);
assert(unit);
- m1 = strjoina("_SYSTEMD_USER_UNIT=", unit);
- m2 = strjoina("USER_UNIT=", unit);
- m3 = strjoina("COREDUMP_USER_UNIT=", unit);
- m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit);
- sprintf(muid, "_UID="UID_FMT, uid);
-
(void) (
/* Look for messages from the user service itself */
- (r = sd_journal_add_match(j, m1, 0)) ||
- (r = sd_journal_add_match(j, muid, 0)) ||
+ (r = journal_add_match_pair(j, "_SYSTEMD_USER_UNIT", unit)) ||
+ (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) ||
/* Look for messages from systemd about this service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, m2, 0)) ||
- (r = sd_journal_add_match(j, muid, 0)) ||
+ (r = journal_add_match_pair(j, "USER_UNIT", unit)) ||
+ (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) ||
/* Look for coredumps of the service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, m3, 0)) ||
- (r = sd_journal_add_match(j, muid, 0)) ||
- (r = sd_journal_add_match(j, "_UID=0", 0)) ||
+ (r = journal_add_match_pair(j, "COREDUMP_USER_UNIT", unit)) ||
+ (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) ||
+ (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ||
/* Look for messages from authorized daemons about this service */
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, m4, 0)) ||
- (r = sd_journal_add_match(j, muid, 0)) ||
- (r = sd_journal_add_match(j, "_UID=0", 0))
+ (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_USER_UNIT", unit)) ||
+ (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) ||
+ (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX))
);
- if (r == 0 && endswith(unit, ".slice")) {
- const char *m5;
-
- m5 = strjoina("_SYSTEMD_USER_SLICE=", unit);
-
+ if (r == 0 && endswith(unit, ".slice"))
/* Show all messages belonging to a slice */
- (void)(
+ (void) (
(r = sd_journal_add_disjunction(j)) ||
- (r = sd_journal_add_match(j, m5, 0)) ||
- (r = sd_journal_add_match(j, muid, 0))
- );
- }
+ (r = journal_add_match_pair(j, "_SYSTEMD_USER_SLICE", unit)) ||
+ (r = journal_add_matchf(j, "_UID="UID_FMT, uid))
+ );
return r;
}
-static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) {
- _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
- _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF;
- char buf[SD_ID128_UUID_STRING_MAX];
- pid_t pid, child;
- ssize_t k;
+int add_match_boot_id(sd_journal *j, sd_id128_t id) {
int r;
- assert(machine);
- assert(boot_id);
-
- r = container_get_leader(machine, &pid);
- if (r < 0)
- return r;
-
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd);
- if (r < 0)
- return r;
-
- if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
- return -errno;
-
- r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
- pidnsfd, mntnsfd, -1, -1, rootfd, &child);
- if (r < 0)
- return r;
- if (r == 0) {
- int fd;
-
- pair[0] = safe_close(pair[0]);
-
- fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (fd < 0)
- _exit(EXIT_FAILURE);
+ assert(j);
- r = loop_read_exact(fd, buf, 36, false);
- safe_close(fd);
+ if (sd_id128_is_null(id)) {
+ r = sd_id128_get_boot(&id);
if (r < 0)
- _exit(EXIT_FAILURE);
-
- k = send(pair[1], buf, 36, MSG_NOSIGNAL);
- if (k != 36)
- _exit(EXIT_FAILURE);
-
- _exit(EXIT_SUCCESS);
+ return log_error_errno(r, "Failed to get boot ID: %m");
}
- pair[1] = safe_close(pair[1]);
-
- r = wait_for_terminate_and_check("(sd-bootidns)", child, 0);
- if (r < 0)
- return r;
- if (r != EXIT_SUCCESS)
- return -EIO;
-
- k = recv(pair[0], buf, 36, 0);
- if (k != 36)
- return -EIO;
-
- buf[36] = 0;
- r = sd_id128_from_string(buf, boot_id);
+ r = journal_add_match_pair(j, "_BOOT_ID", SD_ID128_TO_STRING(id));
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to add match: %m");
return 0;
}
-int add_match_boot_id(sd_journal *j, sd_id128_t id) {
- char match[STRLEN("_BOOT_ID=") + SD_ID128_STRING_MAX];
-
- assert(j);
- assert(!sd_id128_is_null(id));
-
- sd_id128_to_string(id, stpcpy(match, "_BOOT_ID="));
- return sd_journal_add_match(j, match, strlen(match));
-}
-
int add_match_this_boot(sd_journal *j, const char *machine) {
sd_id128_t boot_id;
int r;
assert(j);
- if (machine) {
- r = get_boot_id_for_machine(machine, &boot_id);
- if (r < 0)
- return log_error_errno(r, "Failed to get boot id of container %s: %m", machine);
- } else {
- r = sd_id128_get_boot(&boot_id);
- if (r < 0)
- return log_error_errno(r, "Failed to get boot id: %m");
- }
+ r = id128_get_boot_for_machine(machine, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot ID%s%s: %m",
+ isempty(machine) ? "" : " of container ", strempty(machine));
r = add_match_boot_id(j, boot_id);
if (r < 0)
- return log_error_errno(r, "Failed to add match: %m");
+ return r;
r = sd_journal_add_conjunction(j);
if (r < 0)
@@ -1782,7 +1709,6 @@ int show_journal_by_unit(
unsigned n_columns,
usec_t not_before,
unsigned how_many,
- uid_t uid,
OutputFlags flags,
int journal_open_flags,
bool system_unit,
@@ -1798,14 +1724,17 @@ int show_journal_by_unit(
if (how_many <= 0)
return 0;
- r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE);
+ r = sd_journal_open_namespace(&j, log_namespace,
+ journal_open_flags |
+ SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE |
+ SD_JOURNAL_ASSUME_IMMUTABLE);
if (r < 0)
return log_error_errno(r, "Failed to open journal: %m");
if (system_unit)
r = add_matches_for_unit(j, unit);
else
- r = add_matches_for_user_unit(j, unit, uid);
+ r = add_matches_for_user_unit(j, unit);
if (r < 0)
return log_error_errno(r, "Failed to add unit matches: %m");
@@ -1861,6 +1790,7 @@ static int discover_next_boot(
if (r < 0)
return r;
if (r == 0) {
+ sd_journal_flush_matches(j);
*ret = (BootId) {};
return 0; /* End of journal, yay. */
}
@@ -1911,7 +1841,7 @@ static int discover_next_boot(
goto try_again;
}
- r = sd_journal_get_realtime_usec(j, &boot.first_usec);
+ r = sd_journal_get_realtime_usec(j, advance_older ? &boot.last_usec : &boot.first_usec);
if (r < 0)
return r;
@@ -1933,7 +1863,7 @@ static int discover_next_boot(
goto try_again;
}
- r = sd_journal_get_realtime_usec(j, &boot.last_usec);
+ r = sd_journal_get_realtime_usec(j, advance_older ? &boot.first_usec : &boot.last_usec);
if (r < 0)
return r;
@@ -1979,37 +1909,9 @@ static int discover_next_boot(
}
}
-int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id) {
- int r;
-
- assert(j);
- assert(!sd_id128_is_null(boot_id));
-
- sd_journal_flush_matches(j);
-
- r = add_match_boot_id(j, boot_id);
- if (r < 0)
- return r;
-
- r = sd_journal_seek_head(j); /* seek to oldest */
- if (r < 0)
- return r;
-
- r = sd_journal_next(j); /* read the oldest entry */
- if (r < 0)
- return r;
-
- /* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID.
- * After flushing the matches, one more invocation of _previous() will hence place us at the
- * following entry, which must then have an older boot ID */
-
- sd_journal_flush_matches(j);
- return r > 0;
-}
-
-int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) {
+int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret) {
bool advance_older;
- int r;
+ int r, offset_start;
assert(j);
assert(ret);
@@ -2018,21 +1920,52 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) {
* (chronological) first boot in the journal. */
advance_older = offset <= 0;
- if (advance_older)
- r = sd_journal_seek_tail(j); /* seek to newest */
- else
- r = sd_journal_seek_head(j); /* seek to oldest */
- if (r < 0)
- return r;
+ sd_journal_flush_matches(j);
- /* No sd_journal_next()/_previous() here.
- *
- * At this point the read pointer is positioned after the newest/before the oldest entry in the whole
- * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
- * entry we have. */
+ if (!sd_id128_is_null(boot_id)) {
+ r = add_match_boot_id(j, boot_id);
+ if (r < 0)
+ return r;
- sd_id128_t boot_id = SD_ID128_NULL;
- for (int off = !advance_older; ; off += advance_older ? -1 : 1) {
+ if (advance_older)
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ else
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ if (r < 0)
+ return r;
+
+ r = sd_journal_step_one(j, advance_older);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ sd_journal_flush_matches(j);
+ *ret = SD_ID128_NULL;
+ return false;
+ }
+ if (offset == 0) {
+ /* If boot ID is specified without an offset, then let's short cut the loop below. */
+ sd_journal_flush_matches(j);
+ *ret = boot_id;
+ return true;
+ }
+
+ offset_start = advance_older ? -1 : 1;
+ } else {
+ if (advance_older)
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ else
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ if (r < 0)
+ return r;
+
+ offset_start = advance_older ? 0 : 1;
+ }
+
+ /* At this point the cursor is positioned at the newest/oldest entry of the reference boot ID if
+ * specified, or whole journal otherwise. The next invocation of _previous()/_next() will hence
+ * position us at the newest/oldest entry we have. */
+
+ for (int off = offset_start; ; off += advance_older ? -1 : 1) {
BootId boot;
r = discover_next_boot(j, boot_id, advance_older, &boot);
@@ -2046,15 +1979,20 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) {
boot_id = boot.id;
log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off);
- if (off == offset)
- break;
+ if (off == offset) {
+ *ret = boot_id;
+ return true;
+ }
}
-
- *ret = boot_id;
- return true;
}
-int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
+int journal_get_boots(
+ sd_journal *j,
+ bool advance_older,
+ size_t max_ids,
+ BootId **ret_boots,
+ size_t *ret_n_boots) {
+
_cleanup_free_ BootId *boots = NULL;
size_t n_boots = 0;
int r;
@@ -2063,7 +2001,12 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
assert(ret_boots);
assert(ret_n_boots);
- r = sd_journal_seek_head(j); /* seek to oldest */
+ sd_journal_flush_matches(j);
+
+ if (advance_older)
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ else
+ r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
@@ -2076,7 +2019,10 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
for (;;) {
BootId boot;
- r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot);
+ if (n_boots >= max_ids)
+ break;
+
+ r = discover_next_boot(j, previous_boot_id, advance_older, &boot);
if (r < 0)
return r;
if (r == 0)
@@ -2090,10 +2036,8 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
* Exiting as otherwise this problem would cause an infinite loop. */
goto finish;
- if (!GREEDY_REALLOC(boots, n_boots + 1))
+ if (!GREEDY_REALLOC_APPEND(boots, n_boots, &boot, 1))
return -ENOMEM;
-
- boots[n_boots++] = boot;
}
finish:
diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h
index 3a8ce8b..7e7b2af 100644
--- a/src/shared/logs-show.h
+++ b/src/shared/logs-show.h
@@ -49,8 +49,7 @@ int add_matches_for_unit(
int add_matches_for_user_unit(
sd_journal *j,
- const char *unit,
- uid_t uid);
+ const char *unit);
int show_journal_by_unit(
FILE *f,
@@ -60,7 +59,6 @@ int show_journal_by_unit(
unsigned n_columns,
usec_t not_before,
unsigned how_many,
- uid_t uid,
OutputFlags flags,
int journal_open_flags,
bool system_unit,
@@ -72,6 +70,10 @@ void json_escape(
size_t l,
OutputFlags flags);
-int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id);
-int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret);
-int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots);
+int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret);
+int journal_get_boots(
+ sd_journal *j,
+ bool advance_older,
+ size_t max_ids,
+ BootId **ret_boots,
+ size_t *ret_n_boots);
diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c
index 6d55df7..e295eb2 100644
--- a/src/shared/loop-util.c
+++ b/src/shared/loop-util.c
@@ -57,21 +57,6 @@ static int loop_is_bound(int fd) {
return true; /* bound! */
}
-static int get_current_uevent_seqnum(uint64_t *ret) {
- _cleanup_free_ char *p = NULL;
- int r;
-
- r = read_full_virtual_file("/sys/kernel/uevent_seqnum", &p, NULL);
- if (r < 0)
- return log_debug_errno(r, "Failed to read current uevent sequence number: %m");
-
- r = safe_atou64(strstrip(p), ret);
- if (r < 0)
- return log_debug_errno(r, "Failed to parse current uevent sequence number: %s", p);
-
- return 0;
-}
-
static int open_lock_fd(int primary_fd, int operation) {
_cleanup_close_ int lock_fd = -EBADF;
@@ -149,8 +134,9 @@ static int loop_configure_verify(int fd, const struct loop_config *c) {
* effect hence. And if not use classic LOOP_SET_STATUS64. */
uint64_t z;
- if (ioctl(fd, BLKGETSIZE64, &z) < 0)
- return -errno;
+ r = blockdev_get_device_size(fd, &z);
+ if (r < 0)
+ return r;
if (z != c->info.lo_sizelimit) {
log_debug("LOOP_CONFIGURE is broken, doesn't honour .info.lo_sizelimit. Falling back to LOOP_SET_STATUS64.");
@@ -265,8 +251,7 @@ static int loop_configure(
_cleanup_(cleanup_clear_loop_close) int loop_with_fd = -EBADF; /* This must be declared before lock_fd. */
_cleanup_close_ int fd = -EBADF, lock_fd = -EBADF;
_cleanup_free_ char *node = NULL;
- uint64_t diskseq = 0, seqnum = UINT64_MAX;
- usec_t timestamp = USEC_INFINITY;
+ uint64_t diskseq = 0;
dev_t devno;
int r;
@@ -326,18 +311,6 @@ static int loop_configure(
"Removed partitions on the loopback block device.");
if (!loop_configure_broken) {
- /* Acquire uevent seqnum immediately before attaching the loopback device. This allows
- * callers to ignore all uevents with a seqnum before this one, if they need to associate
- * uevent with this attachment. Doing so isn't race-free though, as uevents that happen in
- * the window between this reading of the seqnum, and the LOOP_CONFIGURE call might still be
- * mistaken as originating from our attachment, even though might be caused by an earlier
- * use. But doing this at least shortens the race window a bit. */
- r = get_current_uevent_seqnum(&seqnum);
- if (r < 0)
- return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m");
-
- timestamp = now(CLOCK_MONOTONIC);
-
if (ioctl(fd, LOOP_CONFIGURE, c) < 0) {
/* Do fallback only if LOOP_CONFIGURE is not supported, propagate all other
* errors. Note that the kernel is weird: non-existing ioctls currently return EINVAL
@@ -369,13 +342,6 @@ static int loop_configure(
}
if (loop_configure_broken) {
- /* Let's read the seqnum again, to shorten the window. */
- r = get_current_uevent_seqnum(&seqnum);
- if (r < 0)
- return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m");
-
- timestamp = now(CLOCK_MONOTONIC);
-
if (ioctl(fd, LOOP_SET_FD, c->fd) < 0)
return log_device_debug_errno(dev, errno, "ioctl(LOOP_SET_FD) failed: %m");
@@ -404,6 +370,11 @@ static int loop_configure(
assert_not_reached();
}
+ uint64_t device_size;
+ r = blockdev_get_device_size(loop_with_fd, &device_size);
+ if (r < 0)
+ return log_device_debug_errno(dev, r, "Failed to get loopback device size: %m");
+
LoopDevice *d = new(LoopDevice, 1);
if (!d)
return log_oom_debug();
@@ -417,9 +388,9 @@ static int loop_configure(
.devno = devno,
.dev = TAKE_PTR(dev),
.diskseq = diskseq,
- .uevent_seqnum_not_before = seqnum,
- .timestamp_not_before = timestamp,
.sector_size = c->block_size,
+ .device_size = device_size,
+ .created = true,
};
*ret = TAKE_PTR(d);
@@ -944,6 +915,11 @@ int loop_device_open(
if (r < 0)
return r;
+ uint64_t device_size;
+ r = blockdev_get_device_size(fd, &device_size);
+ if (r < 0)
+ return r;
+
r = sd_device_get_devnum(dev, &devnum);
if (r < 0)
return r;
@@ -973,9 +949,9 @@ int loop_device_open(
.relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */
.devno = devnum,
.diskseq = diskseq,
- .uevent_seqnum_not_before = UINT64_MAX,
- .timestamp_not_before = USEC_INFINITY,
.sector_size = sector_size,
+ .device_size = device_size,
+ .created = false,
};
*ret = d;
@@ -1057,8 +1033,9 @@ static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) {
return -EINVAL;
current_offset *= 512U;
- if (ioctl(partition_fd, BLKGETSIZE64, &current_size) < 0)
- return -EINVAL;
+ r = blockdev_get_device_size(partition_fd, &current_size);
+ if (r < 0)
+ return r;
if (size == UINT64_MAX && offset == UINT64_MAX)
return 0;
diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h
index d77c314..4f56317 100644
--- a/src/shared/loop-util.h
+++ b/src/shared/loop-util.h
@@ -22,12 +22,12 @@ struct LoopDevice {
sd_device *dev;
char *backing_file;
bool relinquished;
+ bool created; /* If we created the device */
dev_t backing_devno; /* The backing file's dev_t */
ino_t backing_inode; /* The backing file's ino_t */
uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */
- uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */
- usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */
uint32_t sector_size;
+ uint64_t device_size;
};
/* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */
diff --git a/src/shared/loopback-setup.c b/src/shared/loopback-setup.c
index a02baf8..84d7234 100644
--- a/src/shared/loopback-setup.c
+++ b/src/shared/loopback-setup.c
@@ -60,7 +60,7 @@ static int start_loopback(sd_netlink *rtnl, struct state *s) {
if (r < 0)
return r;
- s->n_messages ++;
+ s->n_messages++;
return 0;
}
@@ -95,7 +95,7 @@ static int add_ipv4_address(sd_netlink *rtnl, struct state *s) {
if (r < 0)
return r;
- s->n_messages ++;
+ s->n_messages++;
return 0;
}
@@ -136,7 +136,7 @@ static int add_ipv6_address(sd_netlink *rtnl, struct state *s) {
if (r < 0)
return r;
- s->n_messages ++;
+ s->n_messages++;
return 0;
}
diff --git a/src/shared/machine-credential.c b/src/shared/machine-credential.c
index 17f7afc..c1e76e4 100644
--- a/src/shared/machine-credential.c
+++ b/src/shared/machine-credential.c
@@ -19,76 +19,82 @@ static void machine_credential_done(MachineCredential *cred) {
cred->size = 0;
}
-void machine_credential_free_all(MachineCredential *creds, size_t n) {
- assert(creds || n == 0);
+void machine_credential_context_done(MachineCredentialContext *ctx) {
+ assert(ctx);
- FOREACH_ARRAY(cred, creds, n)
+ FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials)
machine_credential_done(cred);
- free(creds);
+ free(ctx->credentials);
}
-int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string) {
- _cleanup_free_ char *word = NULL, *data = NULL;
+bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id) {
+ assert(ctx);
+ assert(id);
+
+ FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials)
+ if (streq(cred->id, id))
+ return true;
+
+ return false;
+}
+
+int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str) {
+ _cleanup_(machine_credential_done) MachineCredential cred = {};
ssize_t l;
int r;
- const char *p = ASSERT_PTR(cred_string);
- assert(credentials && n_credentials);
- assert(*credentials || *n_credentials == 0);
+ assert(ctx);
- r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ const char *p = ASSERT_PTR(cred_str);
+
+ r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_error_errno(r, "Failed to parse --set-credential= parameter: %m");
if (r == 0 || !p)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", cred_string);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Missing value for --set-credential=: %s", cred_str);
- if (!credential_name_valid(word))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
+ if (!credential_name_valid(cred.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id);
- FOREACH_ARRAY(cred, *credentials, *n_credentials)
- if (streq(cred->id, word))
- return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
+ if (machine_credentials_contains(ctx, cred.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id);
- l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data);
+ l = cunescape(p, UNESCAPE_ACCEPT_NUL, &cred.data);
if (l < 0)
return log_error_errno(l, "Failed to unescape credential data: %s", p);
+ cred.size = l;
- if (!GREEDY_REALLOC(*credentials, *n_credentials + 1))
+ if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1))
return log_oom();
- (*credentials)[(*n_credentials)++] = (MachineCredential) {
- .id = TAKE_PTR(word),
- .data = TAKE_PTR(data),
- .size = l,
- };
+ ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred);
return 0;
}
-int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path) {
+int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path) {
+ _cleanup_(machine_credential_done) MachineCredential cred = {};
+ _cleanup_free_ char *path_alloc = NULL;
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
- _cleanup_(erase_and_freep) char *data = NULL;
- _cleanup_free_ char *word = NULL, *j = NULL;
- const char *p = ASSERT_PTR(cred_path);
- size_t size;
int r;
- assert(credentials && n_credentials);
- assert(*credentials || *n_credentials == 0);
+ assert(ctx);
+
+ const char *p = ASSERT_PTR(cred_path);
- r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_error_errno(r, "Failed to parse --load-credential= parameter: %m");
if (r == 0 || !p)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --load-credential=: %s", cred_path);
- if (!credential_name_valid(word))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
+ if (!credential_name_valid(cred.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id);
- FOREACH_ARRAY(cred, *credentials, *n_credentials)
- if (streq(cred->id, word))
- return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
+ if (machine_credentials_contains(ctx, cred.id))
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id);
if (is_path(p) && path_is_valid(p))
flags |= READ_FULL_FILE_CONNECT_SOCKET;
@@ -97,31 +103,29 @@ int machine_credential_load(MachineCredential **credentials, size_t *n_credentia
r = get_credentials_dir(&e);
if (r < 0)
- return log_error_errno(r, "Credential not available (no credentials passed at all): %s", word);
+ return log_error_errno(r,
+ "Credential not available (no credentials passed at all): %s", cred.id);
- j = path_join(e, p);
- if (!j)
+ path_alloc = path_join(e, p);
+ if (!path_alloc)
return log_oom();
- p = j;
+ p = path_alloc;
} else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential source appears to be neither a valid path nor a credential name: %s", p);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Credential source appears to be neither a valid path nor a credential name: %s", p);
r = read_full_file_full(AT_FDCWD, p, UINT64_MAX, SIZE_MAX,
flags,
NULL,
- &data, &size);
+ &cred.data, &cred.size);
if (r < 0)
return log_error_errno(r, "Failed to read credential '%s': %m", p);
- if (!GREEDY_REALLOC(*credentials, *n_credentials + 1))
+ if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1))
return log_oom();
- (*credentials)[(*n_credentials)++] = (MachineCredential) {
- .id = TAKE_PTR(word),
- .data = TAKE_PTR(data),
- .size = size,
- };
+ ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred);
return 0;
}
diff --git a/src/shared/machine-credential.h b/src/shared/machine-credential.h
index c9044a2..182f077 100644
--- a/src/shared/machine-credential.h
+++ b/src/shared/machine-credential.h
@@ -5,10 +5,18 @@
typedef struct MachineCredential {
char *id;
- void *data;
+ char *data;
size_t size;
} MachineCredential;
-void machine_credential_free_all(MachineCredential *creds, size_t n);
-int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string);
-int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path);
+typedef struct MachineCredentialContext {
+ MachineCredential *credentials;
+ size_t n_credentials;
+} MachineCredentialContext;
+
+void machine_credential_context_done(MachineCredentialContext *ctx);
+
+bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id);
+
+int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str);
+int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path);
diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c
index 3efba03..1a63794 100644
--- a/src/shared/machine-id-setup.c
+++ b/src/shared/machine-id-setup.c
@@ -5,6 +5,7 @@
#include <sys/mount.h>
#include <unistd.h>
+#include "sd-daemon.h"
#include "sd-id128.h"
#include "alloc-util.h"
@@ -12,6 +13,7 @@
#include "creds-util.h"
#include "fd-util.h"
#include "id128-util.h"
+#include "initrd-util.h"
#include "io-util.h"
#include "log.h"
#include "machine-id-setup.h"
@@ -46,13 +48,22 @@ static int acquire_machine_id_from_credential(sd_id128_t *ret) {
return 0;
}
-static int generate_machine_id(const char *root, sd_id128_t *ret) {
+static int acquire_machine_id(const char *root, sd_id128_t *ret) {
_cleanup_close_ int fd = -EBADF;
int r;
assert(ret);
- /* First, try reading the D-Bus machine id, unless it is a symlink */
+ /* First, try reading the machine ID from /run/machine-id, which may not be mounted on
+ * /etc/machine-id yet. This is important on switching root especially on soft-reboot, Otherwise,
+ * machine ID may be changed after the transition. */
+ if (isempty(root) && running_in_chroot() <= 0 &&
+ id128_read("/run/machine-id", ID128_FORMAT_PLAIN, ret) >= 0) {
+ log_info("Reusing machine ID stored in /run/machine-id.");
+ return 1; /* Indicate that the machine ID is reused. */
+ }
+
+ /* Then, try reading the D-Bus machine id, unless it is a symlink */
fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT | CHASE_NOFOLLOW, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) {
log_info("Initializing machine ID from D-Bus machine ID.");
@@ -61,9 +72,8 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) {
if (isempty(root) && running_in_chroot() <= 0) {
/* Let's use a system credential for the machine ID if we can */
- r = acquire_machine_id_from_credential(ret);
- if (r >= 0)
- return r;
+ if (acquire_machine_id_from_credential(ret) >= 0)
+ return 0;
/* If that didn't work, see if we are running in a container,
* and a machine ID was passed in via $container_uuid the way
@@ -103,7 +113,7 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) {
int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_id, sd_id128_t *ret) {
const char *etc_machine_id, *run_machine_id;
_cleanup_close_ int fd = -EBADF;
- bool writable;
+ bool writable, write_run_machine_id = true;
int r;
etc_machine_id = prefix_roota(root, "/etc/machine-id");
@@ -141,13 +151,14 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_
if (sd_id128_is_null(machine_id)) {
/* Try to read any existing machine ID */
- if (id128_read_fd(fd, ID128_FORMAT_PLAIN, ret) >= 0)
- return 0;
+ if (id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id) >= 0)
+ goto finish;
- /* Hmm, so, the id currently stored is not useful, then let's generate one */
- r = generate_machine_id(root, &machine_id);
+ /* Hmm, so, the id currently stored is not useful, then let's acquire one. */
+ r = acquire_machine_id(root, &machine_id);
if (r < 0)
return r;
+ write_run_machine_id = !r;
}
if (writable) {
@@ -185,11 +196,13 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_
run_machine_id = prefix_roota(root, "/run/machine-id");
- WITH_UMASK(0022)
- r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id);
- if (r < 0) {
- (void) unlink(run_machine_id);
- return log_error_errno(r, "Cannot write %s: %m", run_machine_id);
+ if (write_run_machine_id) {
+ WITH_UMASK(0022)
+ r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id);
+ if (r < 0) {
+ (void) unlink(run_machine_id);
+ return log_error_errno(r, "Cannot write %s: %m", run_machine_id);
+ }
}
/* And now, let's mount it over */
@@ -207,6 +220,9 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_
return r;
finish:
+ if (!in_initrd())
+ (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_MACHINE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine_id));
+
if (ret)
*ret = machine_id;
@@ -237,7 +253,7 @@ int machine_id_commit(const char *root) {
etc_machine_id = prefix_roota(root, "/etc/machine-id");
- r = path_is_mount_point(etc_machine_id, NULL, 0);
+ r = path_is_mount_point(etc_machine_id);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id);
if (r == 0) {
@@ -265,7 +281,12 @@ int machine_id_commit(const char *root) {
fd = safe_close(fd);
/* Store current mount namespace */
- r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL);
+ r = namespace_open(0,
+ /* ret_pidns_fd = */ NULL,
+ &initial_mntns_fd,
+ /* ret_netns_fd = */ NULL,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
if (r < 0)
return log_error_errno(r, "Can't fetch current mount namespace: %m");
@@ -284,7 +305,11 @@ int machine_id_commit(const char *root) {
return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id);
/* Return to initial namespace and proceed a lazy tmpfs unmount */
- r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1);
+ r = namespace_enter(/* pidns_fd = */ -EBADF,
+ initial_mntns_fd,
+ /* netns_fd = */ -EBADF,
+ /* userns_fd = */ -EBADF,
+ /* root_fd = */ -EBADF);
if (r < 0)
return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id);
diff --git a/src/shared/main-func.h b/src/shared/main-func.h
index 3f6b6a8..d0689b4 100644
--- a/src/shared/main-func.h
+++ b/src/shared/main-func.h
@@ -3,16 +3,22 @@
#include <stdlib.h>
+#if HAVE_VALGRIND_VALGRIND_H
+# include <valgrind/valgrind.h>
+#endif
+
#include "sd-daemon.h"
#include "argv-util.h"
+#include "hashmap.h"
#include "pager.h"
#include "selinux-util.h"
+#include "signal-util.h"
#include "spawn-ask-password-agent.h"
#include "spawn-polkit-agent.h"
#include "static-destruct.h"
-#define _DEFINE_MAIN_FUNCTION(intro, impl, ret) \
+#define _DEFINE_MAIN_FUNCTION(intro, impl, result_to_exit_status, result_to_return_value) \
int main(int argc, char *argv[]) { \
int r; \
assert_se(argc > 0 && !isempty(argv[0])); \
@@ -21,22 +27,55 @@
r = impl; \
if (r < 0) \
(void) sd_notifyf(0, "ERRNO=%i", -r); \
- (void) sd_notifyf(0, "EXIT_STATUS=%i", ret); \
+ (void) sd_notifyf(0, "EXIT_STATUS=%i", \
+ result_to_exit_status(r)); \
ask_password_agent_close(); \
polkit_agent_close(); \
pager_close(); \
mac_selinux_finish(); \
static_destruct(); \
- return ret; \
+ return result_to_return_value(r); \
}
+static inline int exit_failure_if_negative(int result) {
+ return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
/* Negative return values from impl are mapped to EXIT_FAILURE, and
* everything else means success! */
#define DEFINE_MAIN_FUNCTION(impl) \
- _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : EXIT_SUCCESS)
+ _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, exit_failure_if_negative)
+
+static inline int exit_failure_if_nonzero(int result) {
+ return result < 0 ? EXIT_FAILURE : result;
+}
/* Zero is mapped to EXIT_SUCCESS, negative values are mapped to EXIT_FAILURE,
* and positive values are propagated.
* Note: "true" means failure! */
#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(impl) \
- _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : r)
+ _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_nonzero, exit_failure_if_nonzero)
+
+static inline int raise_or_exit_status(int ret) {
+ if (ret < 0)
+ return EXIT_FAILURE;
+ if (ret == 0)
+ return EXIT_SUCCESS;
+ if (!SIGNAL_VALID(ret))
+ return EXIT_FAILURE;
+
+#if HAVE_VALGRIND_VALGRIND_H
+ /* If raise() below succeeds, the destructor cleanup_pools() in hashmap.c will never called. */
+ if (RUNNING_ON_VALGRIND)
+ hashmap_trim_pools();
+#endif
+
+ (void) raise(ret);
+ /* exit with failure if raise() does not immediately abort the program. */
+ return EXIT_FAILURE;
+}
+
+/* Negative return values from impl are mapped to EXIT_FAILURE, zero is mapped to EXIT_SUCCESS,
+ * and raise if a positive signal is returned from impl. */
+#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(impl) \
+ _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, raise_or_exit_status)
diff --git a/src/shared/meson.build b/src/shared/meson.build
index b24a541..c5106d8 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -39,6 +39,7 @@ shared_sources = files(
'chown-recursive.c',
'clean-ipc.c',
'clock-util.c',
+ 'color-util.c',
'common-signal.c',
'compare-operator.c',
'condition.c',
@@ -54,7 +55,6 @@ shared_sources = files(
'device-nodes.c',
'discover-image.c',
'dissect-image.c',
- 'dlfcn-util.c',
'dm-util.c',
'dns-domain.c',
'dropin.c',
@@ -98,10 +98,11 @@ shared_sources = files(
'journal-util.c',
'json.c',
'kbd-util.c',
+ 'kernel-config.c',
'kernel-image.c',
- 'keyring-util.c',
'killall.c',
'label-util.c',
+ 'libarchive-util.c',
'libcrypt-util.c',
'libfido2-util.c',
'libmount-util.c',
@@ -117,6 +118,7 @@ shared_sources = files(
'macvlan-util.c',
'mkdir-label.c',
'mkfs-util.c',
+ 'module-util.c',
'mount-setup.c',
'mount-util.c',
'net-condition.c',
@@ -124,6 +126,7 @@ shared_sources = files(
'netif-sriov.c',
'netif-util.c',
'nsflags.c',
+ 'nsresource.c',
'numa-util.c',
'open-file.c',
'openssl-util.c',
@@ -139,6 +142,7 @@ shared_sources = files(
'pkcs11-util.c',
'plymouth-util.c',
'pretty-print.c',
+ 'capsule-util.c',
'ptyfwd.c',
'qrcode-util.c',
'quota-util.c',
@@ -172,11 +176,19 @@ shared_sources = files(
'varlink.c',
'varlink-idl.c',
'varlink-io.systemd.c',
+ 'varlink-io.systemd.BootControl.c',
+ 'varlink-io.systemd.Credentials.c',
+ 'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Journal.c',
+ 'varlink-io.systemd.Machine.c',
'varlink-io.systemd.ManagedOOM.c',
+ 'varlink-io.systemd.MountFileSystem.c',
+ 'varlink-io.systemd.NamespaceResource.c',
+ 'varlink-io.systemd.Network.c',
'varlink-io.systemd.PCRExtend.c',
- 'varlink-io.systemd.Resolve.Monitor.c',
+ 'varlink-io.systemd.PCRLock.c',
'varlink-io.systemd.Resolve.c',
+ 'varlink-io.systemd.Resolve.Monitor.c',
'varlink-io.systemd.UserDatabase.c',
'varlink-io.systemd.oom.c',
'varlink-io.systemd.service.c',
@@ -186,6 +198,7 @@ shared_sources = files(
'verbs.c',
'vlan-util.c',
'volatile-util.c',
+ 'vpick.c',
'wall.c',
'watchdog.c',
'web-util.c',
@@ -194,9 +207,7 @@ shared_sources = files(
)
if get_option('tests') != 'false'
- shared_sources += files(
- 'tests.c',
- )
+ shared_sources += files('tests.c')
endif
generate_syscall_list = find_program('generate-syscall-list.py')
@@ -210,9 +221,7 @@ syscall_list_h = custom_target(
capture : true)
if conf.get('HAVE_ACL') == 1
- shared_sources += files(
- 'devnode-acl.c',
- )
+ shared_sources += files('devnode-acl.c')
endif
if conf.get('ENABLE_UTMP') == 1
@@ -229,19 +238,11 @@ if conf.get('HAVE_LIBIPTC') == 1
endif
if conf.get('HAVE_LIBBPF') == 1
- shared_sources += files(
- 'bpf-link.c',
- )
-endif
-
-if conf.get('HAVE_KMOD') == 1
- shared_sources += files('module-util.c')
+ shared_sources += files('bpf-link.c')
endif
if conf.get('HAVE_PAM') == 1
- shared_sources += files(
- 'pam-util.c',
- )
+ shared_sources += files('pam-util.c')
endif
if conf.get('ENABLE_NSCD') == 1
@@ -252,6 +253,10 @@ if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1
shared_sources += files('cryptsetup-fido2.c')
endif
+if conf.get('HAVE_TPM2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1
+ shared_sources += files('cryptsetup-tpm2.c')
+endif
+
generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh')
ip_protocol_list_txt = custom_target(
'ip-protocol-list.txt',
@@ -320,8 +325,8 @@ libshared_deps = [threads,
libdl,
libgcrypt,
libiptc_cflags,
- libkmod,
- liblz4,
+ libkmod_cflags,
+ liblz4_cflags,
libmount,
libopenssl,
libp11kit_cflags,
@@ -330,8 +335,8 @@ libshared_deps = [threads,
libseccomp,
libselinux,
libxenctrl_cflags,
- libxz,
- libzstd]
+ libxz_cflags,
+ libzstd_cflags]
libshared_sym_path = meson.current_source_dir() / 'libshared.sym'
libshared_build_dir = meson.current_build_dir()
@@ -354,16 +359,13 @@ libshared = shared_library(
link_depends : libshared_sym_path,
link_whole : [libshared_static,
libbasic,
- libbasic_gcrypt,
libsystemd_static],
dependencies : [libshared_deps,
userspace],
install : true,
install_dir : pkglibdir)
-shared_fdisk_sources = files(
- 'fdisk-util.c',
-)
+shared_fdisk_sources = files('fdisk-util.c')
libshared_fdisk = static_library(
'shared-fdisk',
diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c
index 4e58b6e..4d44012 100644
--- a/src/shared/mkfs-util.c
+++ b/src/shared/mkfs-util.c
@@ -104,7 +104,6 @@ static int mangle_fat_label(const char *s, char **ret) {
static int do_mcopy(const char *node, const char *root) {
_cleanup_free_ char *mcopy = NULL;
_cleanup_strv_free_ char **argv = NULL;
- _cleanup_close_ int rfd = -EBADF;
_cleanup_free_ DirectoryEntries *de = NULL;
int r;
@@ -128,11 +127,7 @@ static int do_mcopy(const char *node, const char *root) {
/* mcopy copies the top level directory instead of everything in it so we have to pass all
* the subdirectories to mcopy instead to end up with the correct directory structure. */
- rfd = open(root, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
- if (rfd < 0)
- return log_error_errno(errno, "Failed to open directory '%s': %m", root);
-
- r = readdir_all(rfd, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de);
+ r = readdir_all_at(AT_FDCWD, root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de);
if (r < 0)
return log_error_errno(r, "Failed to read '%s' contents: %m", root);
@@ -430,7 +425,7 @@ int make_filesystem(
"-T", "default",
node);
- if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
+ if (root && strv_extend_many(&argv, "-d", root) < 0)
return log_oom();
if (quiet && strv_extend(&argv, "-q") < 0)
@@ -455,7 +450,7 @@ int make_filesystem(
if (!discard && strv_extend(&argv, "--nodiscard") < 0)
return log_oom();
- if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
+ if (root && strv_extend_many(&argv, "-r", root) < 0)
return log_oom();
if (quiet && strv_extend(&argv, "-q") < 0)
@@ -519,7 +514,7 @@ int make_filesystem(
if (!protofile_with_opt)
return -ENOMEM;
- if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0)
+ if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0)
return log_oom();
}
diff --git a/src/shared/module-util.c b/src/shared/module-util.c
index 951701d..fa1e0f8 100644
--- a/src/shared/module-util.c
+++ b/src/shared/module-util.c
@@ -6,8 +6,52 @@
#include "proc-cmdline.h"
#include "strv.h"
+#if HAVE_KMOD
+
+static void *libkmod_dl = NULL;
+
+DLSYM_FUNCTION(kmod_list_next);
+DLSYM_FUNCTION(kmod_load_resources);
+DLSYM_FUNCTION(kmod_module_get_initstate);
+DLSYM_FUNCTION(kmod_module_get_module);
+DLSYM_FUNCTION(kmod_module_get_name);
+DLSYM_FUNCTION(kmod_module_new_from_lookup);
+DLSYM_FUNCTION(kmod_module_probe_insert_module);
+DLSYM_FUNCTION(kmod_module_unref);
+DLSYM_FUNCTION(kmod_module_unref_list);
+DLSYM_FUNCTION(kmod_new);
+DLSYM_FUNCTION(kmod_set_log_fn);
+DLSYM_FUNCTION(kmod_unref);
+DLSYM_FUNCTION(kmod_validate_resources);
+
+int dlopen_libkmod(void) {
+ ELF_NOTE_DLOPEN("kmod",
+ "Support for loading kernel modules",
+ ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+ "libkmod.so.2");
+
+ return dlopen_many_sym_or_warn(
+ &libkmod_dl,
+ "libkmod.so.2",
+ LOG_DEBUG,
+ DLSYM_ARG(kmod_list_next),
+ DLSYM_ARG(kmod_load_resources),
+ DLSYM_ARG(kmod_module_get_initstate),
+ DLSYM_ARG(kmod_module_get_module),
+ DLSYM_ARG(kmod_module_get_name),
+ DLSYM_ARG(kmod_module_new_from_lookup),
+ DLSYM_ARG(kmod_module_probe_insert_module),
+ DLSYM_ARG(kmod_module_unref),
+ DLSYM_ARG(kmod_module_unref_list),
+ DLSYM_ARG(kmod_new),
+ DLSYM_ARG(kmod_set_log_fn),
+ DLSYM_ARG(kmod_unref),
+ DLSYM_ARG(kmod_validate_resources));
+}
+
static int denylist_modules(const char *p, char ***denylist) {
_cleanup_strv_free_ char **k = NULL;
+ int r;
assert(p);
assert(denylist);
@@ -16,8 +60,9 @@ static int denylist_modules(const char *p, char ***denylist) {
if (!k)
return -ENOMEM;
- if (strv_extend_strv(denylist, k, true) < 0)
- return -ENOMEM;
+ r = strv_extend_strv(denylist, k, /* filter_duplicates= */ true);
+ if (r < 0)
+ return r;
return 0;
}
@@ -39,19 +84,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
}
int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) {
- const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST;
- struct kmod_list *itr;
- _cleanup_(kmod_module_unref_listp) struct kmod_list *modlist = NULL;
+ _cleanup_(sym_kmod_module_unref_listp) struct kmod_list *modlist = NULL;
_cleanup_strv_free_ char **denylist = NULL;
bool denylist_parsed = false;
+ struct kmod_list *itr;
int r;
+ assert(ctx);
+ assert(module);
+
/* verbose==true means we should log at non-debug level if we
* fail to find or load the module. */
log_debug("Loading module: %s", module);
- r = kmod_module_new_from_lookup(ctx, module, &modlist);
+ r = sym_kmod_module_new_from_lookup(ctx, module, &modlist);
if (r < 0)
return log_full_errno(verbose ? LOG_ERR : LOG_DEBUG, r,
"Failed to look up module alias '%s': %m", module);
@@ -61,32 +108,37 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose)
SYNTHETIC_ERRNO(ENOENT),
"Failed to find module '%s'", module);
- kmod_list_foreach(itr, modlist) {
- _cleanup_(kmod_module_unrefp) struct kmod_module *mod = NULL;
+ sym_kmod_list_foreach(itr, modlist) {
+ _cleanup_(sym_kmod_module_unrefp) struct kmod_module *mod = NULL;
int state, err;
- mod = kmod_module_get_module(itr);
- state = kmod_module_get_initstate(mod);
+ mod = sym_kmod_module_get_module(itr);
+ state = sym_kmod_module_get_initstate(mod);
switch (state) {
case KMOD_MODULE_BUILTIN:
log_full(verbose ? LOG_INFO : LOG_DEBUG,
- "Module '%s' is built in", kmod_module_get_name(mod));
+ "Module '%s' is built in", sym_kmod_module_get_name(mod));
break;
case KMOD_MODULE_LIVE:
- log_debug("Module '%s' is already loaded", kmod_module_get_name(mod));
+ log_debug("Module '%s' is already loaded", sym_kmod_module_get_name(mod));
break;
default:
- err = kmod_module_probe_insert_module(mod, probe_flags,
- NULL, NULL, NULL, NULL);
+ err = sym_kmod_module_probe_insert_module(
+ mod,
+ KMOD_PROBE_APPLY_BLACKLIST,
+ /* extra_options= */ NULL,
+ /* run_install= */ NULL,
+ /* data= */ NULL,
+ /* print_action= */ NULL);
if (err == 0)
log_full(verbose ? LOG_INFO : LOG_DEBUG,
- "Inserted module '%s'", kmod_module_get_name(mod));
+ "Inserted module '%s'", sym_kmod_module_get_name(mod));
else if (err == KMOD_PROBE_APPLY_BLACKLIST)
log_full(verbose ? LOG_INFO : LOG_DEBUG,
- "Module '%s' is deny-listed (by kmod)", kmod_module_get_name(mod));
+ "Module '%s' is deny-listed (by kmod)", sym_kmod_module_get_name(mod));
else {
assert(err < 0);
@@ -100,9 +152,9 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose)
denylist_parsed = true;
}
- if (strv_contains(denylist, kmod_module_get_name(mod))) {
+ if (strv_contains(denylist, sym_kmod_module_get_name(mod))) {
log_full(verbose ? LOG_INFO : LOG_DEBUG,
- "Module '%s' is deny-listed (by kernel)", kmod_module_get_name(mod));
+ "Module '%s' is deny-listed (by kernel)", sym_kmod_module_get_name(mod));
continue;
}
}
@@ -113,7 +165,7 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose)
LOG_ERR,
err,
"Failed to insert module '%s': %m",
- kmod_module_get_name(mod));
+ sym_kmod_module_get_name(mod));
if (!IN_SET(err, -ENODEV, -ENOENT))
r = err;
}
@@ -122,3 +174,38 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose)
return r;
}
+
+_printf_(6,0) static void systemd_kmod_log(
+ void *data,
+ int priority,
+ const char *file,
+ int line,
+ const char *fn,
+ const char *format,
+ va_list args) {
+
+ log_internalv(priority, 0, file, line, fn, format, args);
+}
+
+int module_setup_context(struct kmod_ctx **ret) {
+ _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL;
+ int r;
+
+ assert(ret);
+
+ r = dlopen_libkmod();
+ if (r < 0)
+ return r;
+
+ ctx = sym_kmod_new(NULL, NULL);
+ if (!ctx)
+ return -ENOMEM;
+
+ (void) sym_kmod_load_resources(ctx);
+ sym_kmod_set_log_fn(ctx, systemd_kmod_log, NULL);
+
+ *ret = TAKE_PTR(ctx);
+ return 0;
+}
+
+#endif
diff --git a/src/shared/module-util.h b/src/shared/module-util.h
index 8ca6a06..0819738 100644
--- a/src/shared/module-util.h
+++ b/src/shared/module-util.h
@@ -1,12 +1,56 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "dlfcn-util.h"
+
+#if HAVE_KMOD
+
#include <libkmod.h>
#include "macro.h"
-DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, kmod_unref);
-DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, kmod_module_unref);
-DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, kmod_module_unref_list, NULL);
+DLSYM_PROTOTYPE(kmod_list_next);
+DLSYM_PROTOTYPE(kmod_load_resources);
+DLSYM_PROTOTYPE(kmod_module_get_initstate);
+DLSYM_PROTOTYPE(kmod_module_get_module);
+DLSYM_PROTOTYPE(kmod_module_get_name);
+DLSYM_PROTOTYPE(kmod_module_new_from_lookup);
+DLSYM_PROTOTYPE(kmod_module_probe_insert_module);
+DLSYM_PROTOTYPE(kmod_module_unref);
+DLSYM_PROTOTYPE(kmod_module_unref_list);
+DLSYM_PROTOTYPE(kmod_new);
+DLSYM_PROTOTYPE(kmod_set_log_fn);
+DLSYM_PROTOTYPE(kmod_unref);
+DLSYM_PROTOTYPE(kmod_validate_resources);
+
+int dlopen_libkmod(void);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, sym_kmod_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, sym_kmod_module_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, sym_kmod_module_unref_list, NULL);
+
+#define sym_kmod_list_foreach(list_entry, first_entry) \
+ for (list_entry = first_entry; \
+ list_entry != NULL; \
+ list_entry = sym_kmod_list_next(first_entry, list_entry))
int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose);
+int module_setup_context(struct kmod_ctx **ret);
+
+#else
+
+struct kmod_ctx;
+
+static inline int dlopen_libkmod(void) {
+ return -EOPNOTSUPP;
+}
+
+static inline int module_setup_context(struct kmod_ctx **ret) {
+ return -EOPNOTSUPP;
+}
+
+static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) {
+ return -EOPNOTSUPP;
+}
+
+#endif
diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c
index 1226ca1..ba291bd 100644
--- a/src/shared/mount-setup.c
+++ b/src/shared/mount-setup.c
@@ -69,7 +69,7 @@ static bool check_recursiveprot_supported(void) {
r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL);
if (r < 0)
- log_debug_errno(r, "Failed to determiner whether the 'memory_recursiveprot' mount option is supported, assuming not: %m");
+ log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m");
else if (r == 0)
log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option.");
@@ -107,16 +107,6 @@ static const MountPoint mount_table[] = {
cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
{ "cgroup2", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
- { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
- cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
- { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV,
- cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
- { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
- cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
- { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV,
- cg_is_legacy_wanted, MNT_IN_CONTAINER },
- { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV,
- cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
#if ENABLE_PSTORE
{ "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
NULL, MNT_NONE },
@@ -135,8 +125,8 @@ bool mount_point_is_api(const char *path) {
/* Checks if this mount point is considered "API", and hence
* should be ignored */
- for (size_t i = 0; i < ELEMENTSOF(mount_table); i ++)
- if (path_equal(path, mount_table[i].where))
+ FOREACH_ELEMENT(i, mount_table)
+ if (path_equal(path, i->where))
return true;
return path_startswith(path, "/sys/fs/cgroup/");
@@ -167,8 +157,11 @@ static int mount_one(const MountPoint *p, bool relabel) {
int r, priority;
assert(p);
+ assert(p->what);
+ assert(p->where);
+ assert(p->type);
- priority = (p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG;
+ priority = FLAGS_SET(p->mode, MNT_FATAL) ? LOG_ERR : LOG_DEBUG;
if (p->condition_fn && !p->condition_fn())
return 0;
@@ -177,16 +170,16 @@ static int mount_one(const MountPoint *p, bool relabel) {
if (relabel)
(void) label_fix(p->where, LABEL_IGNORE_ENOENT|LABEL_IGNORE_EROFS);
- r = path_is_mount_point(p->where, NULL, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point_full(p->where, /* root = */ NULL, AT_SYMLINK_FOLLOW);
if (r < 0 && r != -ENOENT) {
log_full_errno(priority, r, "Failed to determine whether %s is a mount point: %m", p->where);
- return (p->mode & MNT_FATAL) ? r : 0;
+ return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0;
}
if (r > 0)
return 0;
/* Skip securityfs in a container */
- if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0)
+ if (!FLAGS_SET(p->mode, MNT_IN_CONTAINER) && detect_container() > 0)
return 0;
/* The access mode here doesn't really matter too much, since
@@ -202,44 +195,37 @@ static int mount_one(const MountPoint *p, bool relabel) {
p->type,
strna(p->options));
- if (FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK))
- r = mount_follow_verbose(priority, p->what, p->where, p->type, p->flags, p->options);
- else
- r = mount_nofollow_verbose(priority, p->what, p->where, p->type, p->flags, p->options);
+ r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK));
if (r < 0)
- return (p->mode & MNT_FATAL) ? r : 0;
+ return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0;
/* Relabel again, since we now mounted something fresh here */
if (relabel)
(void) label_fix(p->where, 0);
- if (p->mode & MNT_CHECK_WRITABLE) {
+ if (FLAGS_SET(p->mode, MNT_CHECK_WRITABLE))
if (access(p->where, W_OK) < 0) {
r = -errno;
(void) umount2(p->where, UMOUNT_NOFOLLOW);
(void) rmdir(p->where);
- log_full_errno(priority, r, "Mount point %s not writable after mounting, undoing: %m", p->where);
- return (p->mode & MNT_FATAL) ? r : 0;
+ log_full_errno(priority, r, "Mount point '%s' not writable after mounting, undoing: %m", p->where);
+ return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0;
}
- }
return 1;
}
static int mount_points_setup(size_t n, bool loaded_policy) {
- int ret = 0, r;
+ int r = 0;
assert(n <= ELEMENTSOF(mount_table));
- FOREACH_ARRAY(mp, mount_table, n) {
- r = mount_one(mp, loaded_policy);
- if (r != 0 && ret >= 0)
- ret = r;
- }
+ FOREACH_ARRAY(mp, mount_table, n)
+ RET_GATHER(r, mount_one(mp, loaded_policy));
- return ret;
+ return r;
}
int mount_setup_early(void) {
@@ -297,81 +283,6 @@ static int symlink_controller(const char *target, const char *alias) {
return 0;
}
-int mount_cgroup_controllers(void) {
- _cleanup_set_free_ Set *controllers = NULL;
- int r;
-
- if (!cg_is_legacy_wanted())
- return 0;
-
- /* Mount all available cgroup controllers that are built into the kernel. */
- r = cg_kernel_controllers(&controllers);
- if (r < 0)
- return log_error_errno(r, "Failed to enumerate cgroup controllers: %m");
-
- for (;;) {
- _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL;
- const char *other_controller;
- MountPoint p = {
- .what = "cgroup",
- .type = "cgroup",
- .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV,
- .mode = MNT_IN_CONTAINER,
- };
-
- controller = set_steal_first(controllers);
- if (!controller)
- break;
-
- /* Check if we shall mount this together with another controller */
- other_controller = join_with(controller);
- if (other_controller) {
- _cleanup_free_ char *c = NULL;
-
- /* Check if the other controller is actually available in the kernel too */
- c = set_remove(controllers, other_controller);
- if (c) {
-
- /* Join the two controllers into one string, and maintain a stable ordering */
- if (strcmp(controller, other_controller) < 0)
- options = strjoin(controller, ",", other_controller);
- else
- options = strjoin(other_controller, ",", controller);
- if (!options)
- return log_oom();
- }
- }
-
- /* The simple case, where there's only one controller to mount together */
- if (!options)
- options = TAKE_PTR(controller);
-
- where = path_join("/sys/fs/cgroup", options);
- if (!where)
- return log_oom();
-
- p.where = where;
- p.options = options;
-
- r = mount_one(&p, true);
- if (r < 0)
- return r;
-
- /* Create symlinks from the individual controller names, in case we have a joined mount */
- if (controller)
- (void) symlink_controller(options, controller);
- if (other_controller)
- (void) symlink_controller(options, other_controller);
- }
-
- /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */
- (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs",
- MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY,
- "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP);
-
- return 0;
-}
-
#if HAVE_SELINUX || ENABLE_SMACK
static int relabel_cb(
RecurseDirEvent event,
@@ -415,34 +326,6 @@ static int relabel_tree(const char *path) {
return r;
}
-static int relabel_cgroup_filesystems(void) {
- int r;
- struct statfs st;
-
- r = cg_all_unified();
- if (r == 0) {
- /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this
- only when the filesystem has been already populated by a previous instance of systemd
- running from initrd. Otherwise don't remount anything and leave the filesystem read-write
- for the cgroup filesystems to be mounted inside. */
- if (statfs("/sys/fs/cgroup", &st) < 0)
- return log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup: %m");
-
- if (st.f_flags & ST_RDONLY)
- (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL);
-
- (void) label_fix("/sys/fs/cgroup", 0);
- (void) relabel_tree("/sys/fs/cgroup");
-
- if (st.f_flags & ST_RDONLY)
- (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL);
-
- } else if (r < 0)
- return log_error_errno(r, "Failed to determine whether we are in all unified mode: %m");
-
- return 0;
-}
-
static int relabel_extra(void) {
_cleanup_strv_free_ char **files = NULL;
int r, c = 0;
@@ -533,14 +416,12 @@ int mount_setup(bool loaded_policy, bool leave_propagation) {
FOREACH_STRING(i, "/dev", "/dev/shm", "/run")
(void) relabel_tree(i);
- (void) relabel_cgroup_filesystems();
-
n_extra = relabel_extra();
after_relabel = now(CLOCK_MONOTONIC);
- log_info("Relabeled /dev, /dev/shm, /run, /sys/fs/cgroup%s in %s.",
- n_extra > 0 ? ", additional files" : "",
+ log_info("Relabeled /dev/, /dev/shm/, /run/%s in %s.",
+ n_extra > 0 ? ", and additional files" : "",
FORMAT_TIMESPAN(after_relabel - before_relabel, 0));
}
#endif
@@ -589,3 +470,127 @@ int mount_setup(bool loaded_policy, bool leave_propagation) {
return 0;
}
+
+static const MountPoint cgroupv1_mount_table[] = {
+ { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
+ cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
+ { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
+ { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
+ { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ cg_is_legacy_wanted, MNT_IN_CONTAINER },
+ { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
+};
+
+static void relabel_cgroup_legacy_hierarchy(void) {
+#if HAVE_SELINUX || ENABLE_SMACK
+ struct statfs st;
+
+ assert(cg_is_legacy_wanted());
+
+ /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this
+ only when the filesystem has been already populated by a previous instance of systemd
+ running from initrd. Otherwise don't remount anything and leave the filesystem read-write
+ for the cgroup filesystems to be mounted inside. */
+ if (statfs("/sys/fs/cgroup", &st) < 0)
+ return (void) log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup/: %m");
+
+ if (st.f_flags & ST_RDONLY)
+ (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL);
+
+ (void) label_fix("/sys/fs/cgroup", 0);
+ (void) relabel_tree("/sys/fs/cgroup");
+
+ if (st.f_flags & ST_RDONLY)
+ (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL);
+#endif
+}
+
+int mount_cgroup_legacy_controllers(bool loaded_policy) {
+ _cleanup_set_free_ Set *controllers = NULL;
+ int r;
+
+ if (!cg_is_legacy_wanted())
+ return 0;
+
+ if (!cg_is_legacy_force_enabled())
+ return -ERFKILL;
+
+ FOREACH_ELEMENT(mp, cgroupv1_mount_table) {
+ r = mount_one(mp, loaded_policy);
+ if (r < 0)
+ return r;
+ }
+
+ if (loaded_policy)
+ relabel_cgroup_legacy_hierarchy();
+
+ /* Mount all available cgroup controllers that are built into the kernel. */
+ r = cg_kernel_controllers(&controllers);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate cgroup controllers: %m");
+
+ for (;;) {
+ _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL;
+ const char *other_controller;
+ MountPoint p = {
+ .what = "cgroup",
+ .type = "cgroup",
+ .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV,
+ .mode = MNT_IN_CONTAINER,
+ };
+
+ controller = set_steal_first(controllers);
+ if (!controller)
+ break;
+
+ /* Check if we shall mount this together with another controller */
+ other_controller = join_with(controller);
+ if (other_controller) {
+ _cleanup_free_ char *c = NULL;
+
+ /* Check if the other controller is actually available in the kernel too */
+ c = set_remove(controllers, other_controller);
+ if (c) {
+
+ /* Join the two controllers into one string, and maintain a stable ordering */
+ if (strcmp(controller, other_controller) < 0)
+ options = strjoin(controller, ",", other_controller);
+ else
+ options = strjoin(other_controller, ",", controller);
+ if (!options)
+ return log_oom();
+ }
+ }
+
+ /* The simple case, where there's only one controller to mount together */
+ if (!options)
+ options = TAKE_PTR(controller);
+
+ where = path_join("/sys/fs/cgroup", options);
+ if (!where)
+ return log_oom();
+
+ p.where = where;
+ p.options = options;
+
+ r = mount_one(&p, true);
+ if (r < 0)
+ return r;
+
+ /* Create symlinks from the individual controller names, in case we have a joined mount */
+ if (controller)
+ (void) symlink_controller(options, controller);
+ if (other_controller)
+ (void) symlink_controller(options, other_controller);
+ }
+
+ /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */
+ (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs",
+ MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY,
+ "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP);
+
+ return 1;
+}
diff --git a/src/shared/mount-setup.h b/src/shared/mount-setup.h
index 29bd62f..9fc4933 100644
--- a/src/shared/mount-setup.h
+++ b/src/shared/mount-setup.h
@@ -3,10 +3,10 @@
#include <stdbool.h>
+bool mount_point_is_api(const char *path);
+bool mount_point_ignore(const char *path);
+
int mount_setup_early(void);
int mount_setup(bool loaded_policy, bool leave_propagation);
-int mount_cgroup_controllers(void);
-
-bool mount_point_is_api(const char *path);
-bool mount_point_ignore(const char *path);
+int mount_cgroup_legacy_controllers(bool loaded_policy);
diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c
index 4f2acce..4ddfb9a 100644
--- a/src/shared/mount-util.c
+++ b/src/shared/mount-util.c
@@ -79,7 +79,9 @@ int umount_recursive_full(const char *prefix, int flags, char **keep) {
continue;
if (prefix && !path_startswith(path, prefix)) {
- log_trace("Not unmounting %s, outside of prefix: %s", path, prefix);
+ // FIXME: This is extremely noisy, we're probably doing something very wrong
+ // to trigger this so often, needs more investigation.
+ // log_trace("Not unmounting %s, outside of prefix: %s", path, prefix);
continue;
}
@@ -347,7 +349,7 @@ int bind_remount_recursive_with_mountinfo(
* think autofs, NFS, FUSE, …), but let's generate useful debug messages at
* the very least. */
- q = path_is_mount_point(x, NULL, 0);
+ q = path_is_mount_point(x);
if (IN_SET(q, 0, -ENOENT)) {
/* Hmm, whaaaa? The mount point is not actually a mount point? Then
* it is either obstructed by a later mount or somebody has been
@@ -453,14 +455,20 @@ int bind_remount_one_with_mountinfo(
return 0;
}
+int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask) {
+ _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
+
+ proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
+ if (!proc_self_mountinfo)
+ return log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m");
+
+ return bind_remount_one_with_mountinfo(path, new_flags, flags_mask, proc_self_mountinfo);
+}
+
static int mount_switch_root_pivot(int fd_newroot, const char *path) {
assert(fd_newroot >= 0);
assert(path);
- /* Change into the new rootfs. */
- if (fchdir(fd_newroot) < 0)
- return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path);
-
/* Let the kernel tuck the new root under the old one. */
if (pivot_root(".", ".") < 0)
return log_debug_errno(errno, "Failed to pivot root to new rootfs '%s': %m", path);
@@ -477,10 +485,6 @@ static int mount_switch_root_move(int fd_newroot, const char *path) {
assert(fd_newroot >= 0);
assert(path);
- /* Change into the new rootfs. */
- if (fchdir(fd_newroot) < 0)
- return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path);
-
/* Move the new root fs */
if (mount(".", "/", NULL, MS_MOVE, NULL) < 0)
return log_debug_errno(errno, "Failed to move new rootfs '%s': %m", path);
@@ -494,7 +498,7 @@ static int mount_switch_root_move(int fd_newroot, const char *path) {
int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move) {
_cleanup_close_ int fd_newroot = -EBADF;
- int r;
+ int r, is_current_root;
assert(path);
assert(mount_propagation_flag_is_valid(mount_propagation_flag));
@@ -503,19 +507,31 @@ int mount_switch_root_full(const char *path, unsigned long mount_propagation_fla
if (fd_newroot < 0)
return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path);
- if (!force_ms_move) {
- r = mount_switch_root_pivot(fd_newroot, path);
- if (r < 0) {
- log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path);
- force_ms_move = true;
+ is_current_root = path_is_root_at(fd_newroot, NULL);
+ if (is_current_root < 0)
+ return log_debug_errno(is_current_root, "Failed to determine if target dir is our root already: %m");
+
+ /* Change into the new rootfs. */
+ if (fchdir(fd_newroot) < 0)
+ return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path);
+
+ /* Make this a NOP if we are supposed to switch to our current root fs. After all, both pivot_root()
+ * and MS_MOVE don't like that. */
+ if (!is_current_root) {
+ if (!force_ms_move) {
+ r = mount_switch_root_pivot(fd_newroot, path);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path);
+ force_ms_move = true;
+ }
+ }
+ if (force_ms_move) {
+ /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is
+ * an initramfs in which case pivot_root() isn't supported. */
+ r = mount_switch_root_move(fd_newroot, path);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path);
}
- }
- if (force_ms_move) {
- /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is
- * an initramfs in which case pivot_root() isn't supported. */
- r = mount_switch_root_move(fd_newroot, path);
- if (r < 0)
- return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path);
}
/* Finally, let's establish the requested propagation flags. */
@@ -817,8 +833,8 @@ int mount_option_mangle(
if (!(ent->mask & MNT_INVERT))
mount_flags |= ent->id;
- else if (mount_flags & ent->id)
- mount_flags ^= ent->id;
+ else
+ mount_flags &= ~ent->id;
break;
}
@@ -1096,7 +1112,7 @@ static int mount_in_namespace(
if (!pidref_is_set(target))
return -ESRCH;
- r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
+ r = namespace_open(target->pid, &pidns_fd, &mntns_fd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &root_fd);
if (r < 0)
return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m");
@@ -1200,7 +1216,9 @@ static int mount_in_namespace(
(void) mkdir_parents(dest, 0755);
if (img) {
- DissectImageFlags f = DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE;
+ DissectImageFlags f =
+ DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
if (make_file_or_directory)
f |= DISSECT_IMAGE_MKDIR;
@@ -1279,7 +1297,7 @@ int make_mount_point(const char *path) {
/* If 'path' is already a mount point, does nothing and returns 0. If it is not it makes it one, and returns 1. */
- r = path_is_mount_point(path, NULL, 0);
+ r = path_is_mount_point(path);
if (r < 0)
return log_debug_errno(r, "Failed to determine whether '%s' is a mount point: %m", path);
if (r > 0)
@@ -1310,7 +1328,7 @@ int fd_make_mount_point(int fd) {
return 1;
}
-int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner, RemountIdmapping idmapping) {
_cleanup_close_ int userns_fd = -EBADF;
_cleanup_free_ char *line = NULL;
@@ -1345,8 +1363,20 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping
if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER) {
/* Remap the owner of the bind mounted directory to the root user within the container. This
* way every file written by root within the container to the bind-mounted directory will
- * be owned by the original user. All other user will remain unmapped. */
- if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", owner, uid_shift, 1u) < 0)
+ * be owned by the original user from the host. All other users will remain unmapped. */
+ if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", source_owner, uid_shift, 1u) < 0)
+ return log_oom_debug();
+ }
+
+ if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER) {
+ /* Remap the owner of the bind mounted directory to the owner of the target directory
+ * within the container. This way every file written by target directory owner within the
+ * container to the bind-mounted directory will be owned by the original host user.
+ * All other users will remain unmapped. */
+ if (asprintf(
+ &line,
+ UID_FMT " " UID_FMT " " UID_FMT "\n",
+ source_owner, dest_owner, 1u) < 0)
return log_oom_debug();
}
@@ -1366,7 +1396,7 @@ int remount_idmap_fd(
assert(userns_fd >= 0);
- /* This remounts all specified paths with the specified userns as idmap. It will do so in in the
+ /* This remounts all specified paths with the specified userns as idmap. It will do so in the
* order specified in the strv: the expectation is that the top-level directories are at the
* beginning, and nested directories in the right, so that the tree can be built correctly from left
* to right. */
@@ -1420,10 +1450,10 @@ int remount_idmap_fd(
return 0;
}
-int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) {
+int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner,RemountIdmapping idmapping) {
_cleanup_close_ int userns_fd = -EBADF;
- userns_fd = make_userns(uid_shift, uid_range, owner, idmapping);
+ userns_fd = make_userns(uid_shift, uid_range, source_owner, dest_owner, idmapping);
if (userns_fd < 0)
return userns_fd;
@@ -1586,7 +1616,7 @@ int bind_mount_submounts(
if (!t)
return -ENOMEM;
- r = path_is_mount_point(t, NULL, 0);
+ r = path_is_mount_point(t);
if (r < 0) {
log_debug_errno(r, "Failed to detect if '%s' already is a mount point, ignoring: %m", t);
continue;
diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h
index ef31104..26d96b2 100644
--- a/src/shared/mount-util.h
+++ b/src/shared/mount-util.h
@@ -26,6 +26,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f
}
int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo);
+int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask);
int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move);
static inline int mount_switch_root(const char *path, unsigned long mount_propagation_flag) {
@@ -116,16 +117,19 @@ typedef enum RemountIdmapping {
* certain security implications defaults to off, and requires explicit opt-in. */
REMOUNT_IDMAPPING_HOST_ROOT,
/* Define a mapping from root user within the container to the owner of the bind mounted directory.
- * This ensure no root-owned files will be written in a bind-mounted directory owned by a different
+ * This ensures no root-owned files will be written in a bind-mounted directory owned by a different
* user. No other users are mapped. */
REMOUNT_IDMAPPING_HOST_OWNER,
+ /* Define a mapping from bind-target owner within the container to the host owner of the bind mounted
+ * directory. No other users are mapped. */
+ REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER,
_REMOUNT_IDMAPPING_MAX,
_REMOUNT_IDMAPPING_INVALID = -EINVAL,
} RemountIdmapping;
-int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
+int make_userns(uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping);
int remount_idmap_fd(char **p, int userns_fd);
-int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping);
+int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping);
int bind_mount_submounts(
const char *source,
diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c
index fbaf5c5..2955b6e 100644
--- a/src/shared/netif-naming-scheme.c
+++ b/src/shared/netif-naming-scheme.c
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "sd-device.h"
+
#include "alloc-util.h"
+#include "device-private.h"
#include "netif-naming-scheme.h"
#include "proc-cmdline.h"
#include "string-util.h"
@@ -50,7 +53,7 @@ const NamingScheme* naming_scheme(void) {
return cache;
/* Acquire setting from the kernel command line */
- (void) proc_cmdline_get_key("net.naming-scheme", 0, &buffer);
+ (void) proc_cmdline_get_key("net.naming_scheme", 0, &buffer);
/* Also acquire it from an env var */
e = getenv("NET_NAMING_SCHEME");
@@ -101,3 +104,81 @@ static const char* const alternative_names_policy_table[_NAMEPOLICY_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(alternative_names_policy, NamePolicy);
+
+static int naming_sysattr_allowed_by_default(sd_device *dev) {
+ int r;
+
+ assert(dev);
+
+ r = device_get_property_bool(dev, "ID_NET_NAME_ALLOW");
+ if (r == -ENOENT)
+ return true;
+
+ return r;
+}
+
+static int naming_sysattr_allowed(sd_device *dev, const char *sysattr) {
+ char *sysattr_property;
+ int r;
+
+ assert(dev);
+ assert(sysattr);
+
+ sysattr_property = strjoina("ID_NET_NAME_ALLOW_", sysattr);
+ ascii_strupper(sysattr_property);
+
+ r = device_get_property_bool(dev, sysattr_property);
+ if (r == -ENOENT)
+ /* If ID_NET_NAME_ALLOW is not set or set to 1 default is to allow */
+ return naming_sysattr_allowed_by_default(dev);
+
+ return r;
+}
+
+int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value) {
+ int r;
+
+ r = naming_sysattr_allowed(device, sysattr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ return device_get_sysattr_int(device, sysattr, ret_value);
+}
+
+int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value) {
+ int r;
+
+ r = naming_sysattr_allowed(device, sysattr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ return device_get_sysattr_unsigned(device, sysattr, ret_value);
+}
+
+int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr) {
+ int r;
+
+ r = naming_sysattr_allowed(device, sysattr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ return device_get_sysattr_bool(device, sysattr);
+}
+
+int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value) {
+ int r;
+
+ r = naming_sysattr_allowed(device, sysattr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ENOENT;
+
+ return sd_device_get_sysattr_value(device, sysattr, ret_value);
+}
diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h
index 3f7be08..62afdc5 100644
--- a/src/shared/netif-naming-scheme.h
+++ b/src/shared/netif-naming-scheme.h
@@ -3,6 +3,8 @@
#include <stdbool.h>
+#include "sd-device.h"
+
#include "macro.h"
/* So here's the deal: net_id is supposed to be an exercise in providing stable names for network devices. However, we
@@ -95,3 +97,8 @@ NamePolicy name_policy_from_string(const char *p) _pure_;
const char *alternative_names_policy_to_string(NamePolicy p) _const_;
NamePolicy alternative_names_policy_from_string(const char *p) _pure_;
+
+int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value);
+int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value);
+int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr);
+int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value);
diff --git a/src/shared/netif-sriov.c b/src/shared/netif-sriov.c
index 7559b0d..20e175e 100644
--- a/src/shared/netif-sriov.c
+++ b/src/shared/netif-sriov.c
@@ -84,7 +84,7 @@ void sr_iov_hash_func(const SRIOV *sr_iov, struct siphash *state) {
assert(sr_iov);
assert(state);
- siphash24_compress(&sr_iov->vf, sizeof(sr_iov->vf), state);
+ siphash24_compress_typesafe(sr_iov->vf, state);
}
int sr_iov_compare_func(const SRIOV *s1, const SRIOV *s2) {
diff --git a/src/shared/netif-util.c b/src/shared/netif-util.c
index f56c564..8adc2c8 100644
--- a/src/shared/netif-util.c
+++ b/src/shared/netif-util.c
@@ -5,13 +5,17 @@
#include "arphrd-util.h"
#include "device-util.h"
+#include "hexdecoct.h"
#include "log-link.h"
#include "memory-util.h"
+#include "netif-naming-scheme.h"
#include "netif-util.h"
#include "siphash24.h"
#include "sparse-endian.h"
#include "strv.h"
+#define SHORTEN_IFNAME_HASH_KEY SD_ID128_MAKE(e1,90,a4,04,a8,ef,4b,51,8c,cc,c3,3a,9f,11,fc,a2)
+
bool netif_has_carrier(uint8_t operstate, unsigned flags) {
/* see Documentation/networking/operstates.txt in the kernel sources */
@@ -32,14 +36,8 @@ int net_get_type_string(sd_device *device, uint16_t iftype, char **ret) {
if (device &&
sd_device_get_devtype(device, &t) >= 0 &&
- !isempty(t)) {
- p = strdup(t);
- if (!p)
- return -ENOMEM;
-
- *ret = p;
- return 0;
- }
+ !isempty(t))
+ return strdup_to(ret, t);
t = arphrd_to_name(iftype);
if (!t)
@@ -204,3 +202,79 @@ int net_verify_hardware_address(
return 0;
}
+
+int net_generate_mac(
+ const char *machine_name,
+ struct ether_addr *mac,
+ sd_id128_t hash_key,
+ uint64_t idx) {
+
+ uint64_t result;
+ size_t l, sz;
+ uint8_t *v, *i;
+ int r;
+
+ l = strlen(machine_name);
+ sz = sizeof(sd_id128_t) + l;
+ if (idx > 0)
+ sz += sizeof(idx);
+
+ v = newa(uint8_t, sz);
+
+ /* fetch some persistent data unique to the host */
+ r = sd_id128_get_machine((sd_id128_t*) v);
+ if (r < 0)
+ return r;
+
+ /* combine with some data unique (on this host) to this
+ * container instance */
+ i = mempcpy(v + sizeof(sd_id128_t), machine_name, l);
+ if (idx > 0) {
+ idx = htole64(idx);
+ memcpy(i, &idx, sizeof(idx));
+ }
+
+ /* Let's hash the host machine ID plus the container name. We
+ * use a fixed, but originally randomly created hash key here. */
+ result = htole64(siphash24(v, sz, hash_key.bytes));
+
+ assert_cc(ETH_ALEN <= sizeof(result));
+ memcpy(mac->ether_addr_octet, &result, ETH_ALEN);
+
+ ether_addr_mark_random(mac);
+
+ return 0;
+}
+
+int net_shorten_ifname(char *ifname, bool check_naming_scheme) {
+ char new_ifname[IFNAMSIZ];
+
+ assert(ifname);
+
+ if (strlen(ifname) < IFNAMSIZ) /* Name is short enough */
+ return 0;
+
+ if (!check_naming_scheme || naming_scheme_has(NAMING_NSPAWN_LONG_HASH)) {
+ uint64_t h;
+
+ /* Calculate 64-bit hash value */
+ h = siphash24(ifname, strlen(ifname), SHORTEN_IFNAME_HASH_KEY.bytes);
+
+ /* Set the final four bytes (i.e. 32-bit) to the lower 24bit of the hash, encoded in url-safe base64 */
+ memcpy(new_ifname, ifname, IFNAMSIZ - 5);
+ new_ifname[IFNAMSIZ - 5] = urlsafe_base64char(h >> 18);
+ new_ifname[IFNAMSIZ - 4] = urlsafe_base64char(h >> 12);
+ new_ifname[IFNAMSIZ - 3] = urlsafe_base64char(h >> 6);
+ new_ifname[IFNAMSIZ - 2] = urlsafe_base64char(h);
+ } else
+ /* On old nspawn versions we just truncated the name, provide compatibility */
+ memcpy(new_ifname, ifname, IFNAMSIZ-1);
+
+ new_ifname[IFNAMSIZ - 1] = 0;
+
+ /* Log the incident to make it more discoverable */
+ log_warning("Network interface name '%s' has been changed to '%s' to fit length constraints.", ifname, new_ifname);
+
+ strcpy(ifname, new_ifname);
+ return 1;
+}
diff --git a/src/shared/netif-util.h b/src/shared/netif-util.h
index fb6a27c..02c531e 100644
--- a/src/shared/netif-util.h
+++ b/src/shared/netif-util.h
@@ -20,3 +20,9 @@ int net_verify_hardware_address(
uint16_t iftype,
const struct hw_addr_data *ib_hw_addr,
struct hw_addr_data *new_hw_addr);
+int net_generate_mac(
+ const char *machine_name,
+ struct ether_addr *mac,
+ sd_id128_t hash_key,
+ uint64_t idx);
+int net_shorten_ifname(char *ifname, bool check_naming_scheme);
diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c
index 6df18d7..3d47ae3 100644
--- a/src/shared/nscd-flush.c
+++ b/src/shared/nscd-flush.c
@@ -93,7 +93,7 @@ static int nscd_flush_cache_one(const char *database, usec_t end) {
ssize_t m;
if (has_read >= sizeof(resp))
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected: %m");
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected.");
m = recv(fd, (uint8_t*) &resp + has_read, sizeof(resp) - has_read, 0);
if (m < 0) {
diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c
new file mode 100644
index 0000000..bc0a352
--- /dev/null
+++ b/src/shared/nsresource.c
@@ -0,0 +1,330 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/prctl.h>
+
+#include "fd-util.h"
+#include "format-util.h"
+#include "missing_sched.h"
+#include "namespace-util.h"
+#include "nsresource.h"
+#include "process-util.h"
+#include "varlink.h"
+
+static int make_pid_name(char **ret) {
+ char comm[TASK_COMM_LEN];
+
+ assert(ret);
+
+ if (prctl(PR_GET_NAME, comm) < 0)
+ return -errno;
+
+ /* So the namespace name should be 16 chars at max (because we want that it is usable in usernames,
+ * which have a limit of 31 chars effectively, and the nsresourced service wants to prefix/suffix
+ * some bits). But it also should be unique if we are called multiple times in a row. Hence we take
+ * the "comm" name (which is 15 chars), and suffix it with the UID, possibly overriding the end. */
+ assert_cc(TASK_COMM_LEN == 15 + 1);
+
+ char spid[DECIMAL_STR_MAX(pid_t)];
+ xsprintf(spid, PID_FMT, getpid_cached());
+
+ assert(strlen(spid) <= 16);
+ strshorten(comm, 16 - strlen(spid));
+
+ _cleanup_free_ char *s = strjoin(comm, spid);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+int nsresource_allocate_userns(const char *name, uint64_t size) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ _cleanup_close_ int userns_fd = -EBADF;
+ _cleanup_free_ char *_name = NULL;
+ const char *error_id;
+ int r, userns_fd_idx;
+
+ /* Allocate a new dynamic user namespace via the userdb registry logic */
+
+ if (!name) {
+ r = make_pid_name(&_name);
+ if (r < 0)
+ return r;
+
+ name = _name;
+ }
+
+ if (size <= 0 || size > UINT64_C(0x100000000)) /* Note: the server actually only allows allocating 1 or 64K right now */
+ return -EINVAL;
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ userns_fd = userns_acquire_empty();
+ if (userns_fd < 0)
+ return log_debug_errno(userns_fd, "Failed to acquire empty user namespace: %m");
+
+ userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
+ if (userns_fd_idx < 0)
+ return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.NamespaceResource.AllocateUserRange",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
+ JSON_BUILD_PAIR("size", JSON_BUILD_UNSIGNED(size)),
+ JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx))));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to call AllocateUserRange() varlink call: %m");
+ if (error_id)
+ return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to allocate user namespace with %" PRIu64 " users: %s", size, error_id);
+
+ return TAKE_FD(userns_fd);
+}
+
+int nsresource_register_userns(const char *name, int userns_fd) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ _cleanup_close_ int _userns_fd = -EBADF;
+ _cleanup_free_ char *_name = NULL;
+ const char *error_id;
+ int r, userns_fd_idx;
+
+ /* Register the specified user namespace with userbd. */
+
+ if (!name) {
+ r = make_pid_name(&_name);
+ if (r < 0)
+ return r;
+
+ name = _name;
+ }
+
+ if (userns_fd < 0) {
+ _userns_fd = namespace_open_by_type(NAMESPACE_USER);
+ if (_userns_fd < 0)
+ return -errno;
+
+ userns_fd = _userns_fd;
+ }
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
+ if (userns_fd_idx < 0)
+ return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.NamespaceResource.RegisterUserNamespace",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
+ JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx))));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to call RegisterUserNamespace() varlink call: %m");
+ if (error_id)
+ return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to register user namespace: %s", error_id);
+
+ return 0;
+}
+
+int nsresource_add_mount(int userns_fd, int mount_fd) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ _cleanup_close_ int _userns_fd = -EBADF;
+ int r, userns_fd_idx, mount_fd_idx;
+ const char *error_id;
+
+ assert(mount_fd >= 0);
+
+ if (userns_fd < 0) {
+ _userns_fd = namespace_open_by_type(NAMESPACE_USER);
+ if (_userns_fd < 0)
+ return _userns_fd;
+
+ userns_fd = _userns_fd;
+ }
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to namespace resource manager: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
+ if (userns_fd_idx < 0)
+ return log_error_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
+
+ mount_fd_idx = varlink_push_dup_fd(vl, mount_fd);
+ if (mount_fd_idx < 0)
+ return log_error_errno(mount_fd_idx, "Failed to push mount fd into varlink connection: %m");
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.NamespaceResource.AddMountToUserNamespace",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
+ JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_UNSIGNED(mount_fd_idx))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call AddMountToUserNamespace() varlink call: %m");
+ if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
+ log_notice("User namespace has not been allocated via namespace resource registry, not adding mount to registration.");
+ return 0;
+ }
+ if (error_id)
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to mount image: %s", error_id);
+
+ return 1;
+}
+
+int nsresource_add_cgroup(int userns_fd, int cgroup_fd) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ _cleanup_close_ int _userns_fd = -EBADF;
+ int r, userns_fd_idx, cgroup_fd_idx;
+ const char *error_id;
+
+ assert(cgroup_fd >= 0);
+
+ if (userns_fd < 0) {
+ _userns_fd = namespace_open_by_type(NAMESPACE_USER);
+ if (_userns_fd < 0)
+ return -errno;
+
+ userns_fd = _userns_fd;
+ }
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
+ if (userns_fd_idx < 0)
+ return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
+
+ cgroup_fd_idx = varlink_push_dup_fd(vl, cgroup_fd);
+ if (cgroup_fd_idx < 0)
+ return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m");
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.NamespaceResource.AddControlGroupToUserNamespace",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
+ JSON_BUILD_PAIR("controlGroupFileDescriptor", JSON_BUILD_UNSIGNED(cgroup_fd_idx))));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to call AddControlGroupToUserNamespace() varlink call: %m");
+ if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
+ log_notice("User namespace has not been allocated via namespace resource registry, not adding cgroup to registration.");
+ return 0;
+ }
+ if (error_id)
+ return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add cgroup to user namespace: %s", error_id);
+
+ return 1;
+}
+
+int nsresource_add_netif(
+ int userns_fd,
+ int netns_fd,
+ const char *namespace_ifname,
+ char **ret_host_ifname,
+ char **ret_namespace_ifname) {
+
+ _cleanup_close_ int _userns_fd = -EBADF, _netns_fd = -EBADF;
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r, userns_fd_idx, netns_fd_idx;
+ const char *error_id;
+
+ if (userns_fd < 0) {
+ _userns_fd = namespace_open_by_type(NAMESPACE_USER);
+ if (_userns_fd < 0)
+ return -errno;
+
+ userns_fd = _userns_fd;
+ }
+
+ if (netns_fd < 0) {
+ _netns_fd = namespace_open_by_type(NAMESPACE_NET);
+ if (_netns_fd < 0)
+ return -errno;
+
+ netns_fd = _netns_fd;
+ }
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to connect to namespace resource manager: %m");
+
+ r = varlink_set_allow_fd_passing_output(vl, true);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m");
+
+ userns_fd_idx = varlink_push_dup_fd(vl, userns_fd);
+ if (userns_fd_idx < 0)
+ return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m");
+
+ netns_fd_idx = varlink_push_dup_fd(vl, netns_fd);
+ if (netns_fd_idx < 0)
+ return log_debug_errno(netns_fd_idx, "Failed to push netns fd into varlink connection: %m");
+
+ JsonVariant *reply = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.NamespaceResource.AddNetworkToUserNamespace",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)),
+ JSON_BUILD_PAIR("networkNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(netns_fd_idx)),
+ JSON_BUILD_PAIR("mode", JSON_BUILD_CONST_STRING("veth")),
+ JSON_BUILD_PAIR_CONDITION(namespace_ifname, "namespaceInterfaceName", JSON_BUILD_STRING(namespace_ifname))));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to call AddNetworkToUserNamespace() varlink call: %m");
+ if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) {
+ log_notice("User namespace has not been allocated via namespace resource registry, not adding network to registration.");
+ return 0;
+ }
+ if (error_id)
+ return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add network to user namespace: %s", error_id);
+
+ _cleanup_free_ char *host_interface_name = NULL, *namespace_interface_name = NULL;
+ r = json_dispatch(
+ reply,
+ (const JsonDispatch[]) {
+ { "hostInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&host_interface_name) },
+ { "namespaceInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&namespace_interface_name) },
+ },
+ JSON_ALLOW_EXTENSIONS,
+ /* userdata= */ NULL);
+
+ if (ret_host_ifname)
+ *ret_host_ifname = TAKE_PTR(host_interface_name);
+ if (ret_namespace_ifname)
+ *ret_namespace_ifname = TAKE_PTR(namespace_interface_name);
+
+ return 1;
+}
diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h
new file mode 100644
index 0000000..6b807b3
--- /dev/null
+++ b/src/shared/nsresource.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+
+int nsresource_allocate_userns(const char *name, uint64_t size);
+int nsresource_register_userns(const char *name, int userns_fd);
+int nsresource_add_mount(int userns_fd, int mount_fd);
+int nsresource_add_cgroup(int userns_fd, int cgroup_fd);
+int nsresource_add_netif(int userns_fd, int netns_fd, const char *namespace_ifname, char **ret_host_ifname, char **ret_namespace_ifname);
diff --git a/src/shared/open-file.c b/src/shared/open-file.c
index 7d7a8a9..a9c2a11 100644
--- a/src/shared/open-file.c
+++ b/src/shared/open-file.c
@@ -1,4 +1,3 @@
-
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
@@ -23,7 +22,7 @@ int open_file_parse(const char *v, OpenFile **ret) {
if (!of)
return -ENOMEM;
- r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options, NULL);
+ r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options);
if (r < 0)
return r;
if (r == 0)
@@ -122,19 +121,14 @@ int open_file_to_string(const OpenFile *of, char **ret) {
return 0;
}
-OpenFile *open_file_free(OpenFile *of) {
+OpenFile* open_file_free(OpenFile *of) {
if (!of)
return NULL;
free(of->path);
free(of->fdname);
- return mfree(of);
-}
-void open_file_free_many(OpenFile **head) {
- assert(head);
-
- LIST_CLEAR(open_files, *head, open_file_free);
+ return mfree(of);
}
static const char * const open_file_flags_table[_OPENFILE_MAX] = {
diff --git a/src/shared/open-file.h b/src/shared/open-file.h
index bb63ec8..4999c96 100644
--- a/src/shared/open-file.h
+++ b/src/shared/open-file.h
@@ -1,8 +1,8 @@
-
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "list.h"
+#include "macro.h"
typedef enum OpenFileFlag {
OPENFILE_READ_ONLY = 1 << 0,
@@ -27,10 +27,12 @@ int open_file_validate(const OpenFile *of);
int open_file_to_string(const OpenFile *of, char **ret);
-OpenFile *open_file_free(OpenFile *of);
+OpenFile* open_file_free(OpenFile *of);
DEFINE_TRIVIAL_CLEANUP_FUNC(OpenFile*, open_file_free);
-void open_file_free_many(OpenFile **head);
+static inline void open_file_free_many(OpenFile **head) {
+ LIST_CLEAR(open_files, *ASSERT_PTR(head), open_file_free);
+}
-const char *open_file_flags_to_string(OpenFileFlag t) _const_;
+const char* open_file_flags_to_string(OpenFileFlag t) _const_;
OpenFileFlag open_file_flags_from_string(const char *t) _pure_;
diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c
index b0a5563..2ab89c7 100644
--- a/src/shared/openssl-util.c
+++ b/src/shared/openssl-util.c
@@ -1,14 +1,28 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <endian.h>
+
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
+#include "memory-util.h"
#include "openssl-util.h"
+#include "random-util.h"
#include "string-util.h"
#if HAVE_OPENSSL
-/* For each error in the the OpenSSL thread error queue, log the provided message and the OpenSSL error
- * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No openssl
+# include <openssl/rsa.h>
+# include <openssl/ec.h>
+
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# include <openssl/engine.h>
+DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
+REENABLE_WARNING;
+# endif
+
+/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
+ * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
#define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__)
#define _log_openssl_errors(u, fmt, ...) \
@@ -523,7 +537,6 @@ int rsa_encrypt_bytes(
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
-
return 0;
}
@@ -624,48 +637,55 @@ int rsa_pkey_to_suitable_key_size(
return 0;
}
-/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it
- * must be provided here big-endian, e.g. wrap it with htobe32(). */
+/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here
+ * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */
int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) {
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
assert(n);
+ assert(n_size != 0);
assert(e);
+ assert(e_size != 0);
assert(ret);
- _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
if (!ctx)
return log_openssl_errors("Failed to create new EVP_PKEY_CTX");
- _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL);
- if (!bn_n)
- return log_openssl_errors("Failed to create BIGNUM for RSA n");
-
- _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL);
- if (!bn_e)
- return log_openssl_errors("Failed to create BIGNUM for RSA e");
-
-#if OPENSSL_VERSION_MAJOR >= 3
if (EVP_PKEY_fromdata_init(ctx) <= 0)
return log_openssl_errors("Failed to initialize EVP_PKEY_CTX");
- _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
- if (!bld)
- return log_openssl_errors("Failed to create new OSSL_PARAM_BLD");
+ OSSL_PARAM params[3];
- if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n))
- return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_N");
+#if __BYTE_ORDER == __BIG_ENDIAN
+ params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size);
+ params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size);
+#else
+ _cleanup_free_ void *native_n = memdup_reverse(n, n_size);
+ if (!native_n)
+ return log_oom_debug();
- if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e))
- return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_E");
+ _cleanup_free_ void *native_e = memdup_reverse(e, e_size);
+ if (!native_e)
+ return log_oom_debug();
- _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
- if (!params)
- return log_openssl_errors("Failed to build RSA OSSL_PARAM");
+ params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size);
+ params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size);
+#endif
+ params[2] = OSSL_PARAM_construct_end();
if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
return log_openssl_errors("Failed to create RSA EVP_PKEY");
#else
+ _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL);
+ if (!bn_n)
+ return log_openssl_errors("Failed to create BIGNUM for RSA n");
+
+ _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL);
+ if (!bn_e)
+ return log_openssl_errors("Failed to create BIGNUM for RSA e");
+
_cleanup_(RSA_freep) RSA *rsa_key = RSA_new();
if (!rsa_key)
return log_openssl_errors("Failed to create new RSA");
@@ -989,7 +1009,7 @@ int ecc_ecdh(const EVP_PKEY *private_pkey,
if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0)
return log_openssl_errors("Failed to get ECC shared secret size");
- _cleanup_free_ void *shared_secret = malloc(shared_secret_size);
+ _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size);
if (!shared_secret)
return log_oom_debug();
@@ -1128,6 +1148,258 @@ int string_hashsum(
return 0;
}
# endif
+
+static int ecc_pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_free_ unsigned char *saved_key = NULL;
+ size_t decrypted_key_size, saved_key_size;
+ int nid = NID_undef;
+ int r;
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_free_ char *curve_name = NULL;
+ size_t len = 0;
+
+ if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0)
+ return log_openssl_errors("Failed to determine PKEY group name length");
+
+ len++;
+ curve_name = new(char, len);
+ if (!curve_name)
+ return log_oom_debug();
+
+ if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1)
+ return log_openssl_errors("Failed to get PKEY group name");
+
+ nid = OBJ_sn2nid(curve_name);
+#else
+ EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey);
+ if (!ec_key)
+ return log_openssl_errors("PKEY doesn't have EC_KEY associated");
+
+ if (EC_KEY_check_key(ec_key) != 1)
+ return log_openssl_errors("EC_KEY associated with PKEY is not valid");
+
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key));
+#endif
+
+ r = ecc_pkey_new(nid, &pkey_new);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate a new EC keypair: %m");
+
+ r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to derive shared secret: %m");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points.
+ See https://github.com/openssl/openssl/discussions/22835 */
+ saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
+#else
+ EC_KEY *ec_key_new = EVP_PKEY_get0_EC_KEY(pkey_new);
+ if (!ec_key_new)
+ return log_openssl_errors("The generated key doesn't have associated EC_KEY");
+
+ if (EC_KEY_check_key(ec_key_new) != 1)
+ return log_openssl_errors("EC_KEY associated with the generated key is not valid");
+
+ saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
+ EC_KEY_get0_public_key(ec_key_new),
+ POINT_CONVERSION_UNCOMPRESSED,
+ NULL, 0, NULL);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to determine size of the generated public key");
+
+ saved_key = malloc(saved_key_size);
+ if (!saved_key)
+ return log_oom_debug();
+
+ saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
+ EC_KEY_get0_public_key(ec_key_new),
+ POINT_CONVERSION_UNCOMPRESSED,
+ saved_key, saved_key_size, NULL);
+ if (saved_key_size == 0)
+ return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
+#endif
+
+ *ret_decrypted_key = TAKE_PTR(decrypted_key);
+ *ret_decrypted_key_size = decrypted_key_size;
+ *ret_saved_key = TAKE_PTR(saved_key);
+ *ret_saved_key_size = saved_key_size;
+ return 0;
+}
+
+static int rsa_pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_free_ void *saved_key = NULL;
+ size_t decrypted_key_size, saved_key_size;
+ int r;
+
+ r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to determine RSA public key size.");
+
+ log_debug("Generating %zu bytes random key.", decrypted_key_size);
+
+ decrypted_key = malloc(decrypted_key_size);
+ if (!decrypted_key)
+ return log_oom_debug();
+
+ r = crypto_random_bytes(decrypted_key, decrypted_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate random key: %m");
+
+ r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to encrypt random key: %m");
+
+ *ret_decrypted_key = TAKE_PTR(decrypted_key);
+ *ret_decrypted_key_size = decrypted_key_size;
+ *ret_saved_key = TAKE_PTR(saved_key);
+ *ret_saved_key_size = saved_key_size;
+ return 0;
+}
+
+int pkey_generate_volume_keys(
+ EVP_PKEY *pkey,
+ void **ret_decrypted_key,
+ size_t *ret_decrypted_key_size,
+ void **ret_saved_key,
+ size_t *ret_saved_key_size) {
+
+ assert(pkey);
+ assert(ret_decrypted_key);
+ assert(ret_decrypted_key_size);
+ assert(ret_saved_key);
+ assert(ret_saved_key_size);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ int type = EVP_PKEY_get_base_id(pkey);
+#else
+ int type = EVP_PKEY_base_id(pkey);
+#endif
+ switch (type) {
+
+ case EVP_PKEY_RSA:
+ return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
+
+ case EVP_PKEY_EC:
+ return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
+
+ case NID_undef:
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key.");
+
+ default:
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type));
+ }
+}
+
+static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) {
+
+ assert(provider);
+ assert(private_key_uri);
+ assert(ret);
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ /* Load the provider so that this can work without any custom written configuration in /etc/.
+ * Also load the 'default' as that seems to be the recommendation. */
+ if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true))
+ return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider);
+ if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true))
+ return log_openssl_errors("Failed to load OpenSSL provider 'default'");
+
+ _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
+ private_key_uri,
+ /* ui_method= */ NULL,
+ /* ui_data= */ NULL,
+ /* post_process= */ NULL,
+ /* post_process_data= */ NULL);
+ if (!store)
+ return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri);
+
+ _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store);
+ if (!info)
+ return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri);
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info);
+ if (!private_key)
+ return log_openssl_errors("Failed to load private key via '%s'", private_key_uri);
+
+ *ret = TAKE_PTR(private_key);
+
+ return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
+
+ assert(engine);
+ assert(private_key_uri);
+ assert(ret);
+
+#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+ DISABLE_WARNING_DEPRECATED_DECLARATIONS;
+ _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine);
+ if (!e)
+ return log_openssl_errors("Failed to load signing engine '%s'", engine);
+
+ if (ENGINE_init(e) == 0)
+ return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(
+ e,
+ private_key_uri,
+ /* ui_method= */ NULL,
+ /* callback_data= */ NULL);
+ if (!private_key)
+ return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
+ REENABLE_WARNING;
+
+ *ret = TAKE_PTR(private_key);
+
+ return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+int openssl_load_key_from_token(
+ KeySourceType private_key_source_type,
+ const char *private_key_source,
+ const char *private_key,
+ EVP_PKEY **ret) {
+
+ assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
+ assert(private_key_source);
+ assert(private_key);
+
+ switch (private_key_source_type) {
+
+ case OPENSSL_KEY_SOURCE_ENGINE:
+ return load_key_from_engine(private_key_source, private_key, ret);
+ case OPENSSL_KEY_SOURCE_PROVIDER:
+ return load_key_from_provider(private_key_source, private_key, ret);
+ default:
+ assert_not_reached();
+ }
+}
#endif
int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
@@ -1144,6 +1416,37 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {
sha256_direct(der, dersz, buffer);
return 0;
#else
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint: %m");
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint.");
#endif
}
+
+int parse_openssl_key_source_argument(
+ const char *argument,
+ char **private_key_source,
+ KeySourceType *private_key_source_type) {
+
+ KeySourceType type;
+ const char *e = NULL;
+ int r;
+
+ assert(argument);
+ assert(private_key_source);
+ assert(private_key_source_type);
+
+ if (streq(argument, "file"))
+ type = OPENSSL_KEY_SOURCE_FILE;
+ else if ((e = startswith(argument, "engine:")))
+ type = OPENSSL_KEY_SOURCE_ENGINE;
+ else if ((e = startswith(argument, "provider:")))
+ type = OPENSSL_KEY_SOURCE_PROVIDER;
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument);
+
+ r = free_and_strdup_warn(private_key_source, e);
+ if (r < 0)
+ return r;
+
+ *private_key_source_type = type;
+
+ return 0;
+}
diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h
index e3f34a8..1a89fcc 100644
--- a/src/shared/openssl-util.h
+++ b/src/shared/openssl-util.h
@@ -5,6 +5,16 @@
#include "macro.h"
#include "sha256.h"
+typedef enum KeySourceType {
+ OPENSSL_KEY_SOURCE_FILE,
+ OPENSSL_KEY_SOURCE_ENGINE,
+ OPENSSL_KEY_SOURCE_PROVIDER,
+ _OPENSSL_KEY_SOURCE_MAX,
+ _OPENSSL_KEY_SOURCE_INVALID = -EINVAL,
+} KeySourceType;
+
+int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type);
+
#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE
#if HAVE_OPENSSL
@@ -25,6 +35,8 @@
# include <openssl/core_names.h>
# include <openssl/kdf.h>
# include <openssl/param_build.h>
+# include <openssl/provider.h>
+# include <openssl/store.h>
# endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL);
@@ -40,6 +52,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
+
#if OPENSSL_VERSION_MAJOR >= 3
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL);
@@ -49,6 +63,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL);
#else
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(HMAC_CTX*, HMAC_CTX_free, NULL);
@@ -108,10 +124,14 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret);
int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size);
+int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size);
+
int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size);
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
+int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret);
+
#else
typedef struct X509 X509;
@@ -127,6 +147,15 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) {
return NULL;
}
+static inline int openssl_load_key_from_token(
+ KeySourceType private_key_source_type,
+ const char *private_key_source,
+ const char *private_key,
+ EVP_PKEY **ret) {
+
+ return -EOPNOTSUPP;
+}
+
#endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
diff --git a/src/shared/pager.c b/src/shared/pager.c
index 19deefa..9b8ae76 100644
--- a/src/shared/pager.c
+++ b/src/shared/pager.c
@@ -175,7 +175,7 @@ void pager_open(PagerFlags flags) {
* pager. If they didn't, use secure mode when under euid is changed. If $SYSTEMD_PAGERSECURE
* wasn't explicitly set, and we autodetect the need for secure mode, only use the pager we
* know to be good. */
- int use_secure_mode = getenv_bool_secure("SYSTEMD_PAGERSECURE");
+ int use_secure_mode = secure_getenv_bool("SYSTEMD_PAGERSECURE");
bool trust_pager = use_secure_mode >= 0;
if (use_secure_mode == -ENXIO) {
uid_t uid;
diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c
index f5814ef..3cbe431 100644
--- a/src/shared/pam-util.c
+++ b/src/shared/pam-util.c
@@ -14,6 +14,14 @@
#include "stdio-util.h"
#include "string-util.h"
+void pam_log_setup(void) {
+ /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */
+ log_set_open_when_needed(true);
+
+ /* pam logs to syslog so let's make our generic logging functions do the same thing. */
+ log_set_target(LOG_TARGET_SYSLOG);
+}
+
int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) {
va_list ap;
@@ -96,7 +104,9 @@ static void pam_bus_data_destroy(pam_handle_t *handle, void *data, int error_sta
if (FLAGS_SET(error_status, PAM_DATA_SILENT) &&
d->bus && bus_origin_changed(d->bus))
/* Please adjust test/units/end.sh when updating the log message. */
- pam_syslog(handle, LOG_DEBUG, "Attempted to close sd-bus after fork whose connection is opened before the fork, this should not happen.");
+ pam_syslog(handle, LOG_DEBUG,
+ "Warning: cannot close sd-bus connection (%s) after fork when it was opened before the fork.",
+ strna(d->cache_id));
pam_bus_data_free(data);
}
@@ -177,6 +187,8 @@ int pam_acquire_bus_connection(
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@");
+ pam_syslog(handle, LOG_DEBUG, "New sd-bus connection (%s) opened.", d->cache_id);
+
success:
*ret_bus = sd_bus_ref(d->bus);
@@ -205,7 +217,99 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) {
return PAM_SUCCESS;
}
+int pam_get_bus_data(
+ pam_handle_t *handle,
+ const char *module_name,
+ PamBusData **ret) {
+
+ PamBusData *d = NULL;
+ _cleanup_free_ char *cache_id = NULL;
+ int r;
+
+ assert(handle);
+ assert(module_name);
+ assert(ret);
+
+ cache_id = pam_make_bus_cache_id(module_name);
+ if (!cache_id)
+ return pam_log_oom(handle);
+
+ /* We cache the bus connection so that we can share it between the session and the authentication hooks */
+ r = pam_get_data(handle, cache_id, (const void**) &d);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get bus connection: @PAMERR@");
+
+ *ret = d;
+ return PAM_SUCCESS;
+}
+
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) {
/* A generic destructor for pam_set_data() that just frees the specified data */
free(data);
}
+
+int pam_get_item_many_internal(pam_handle_t *handle, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, handle);
+ for (;;) {
+ int item_type = va_arg(ap, int);
+
+ if (item_type <= 0) {
+ r = PAM_SUCCESS;
+ break;
+ }
+
+ const void **value = ASSERT_PTR(va_arg(ap, const void **));
+
+ r = pam_get_item(handle, item_type, value);
+ if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
+ break;
+ }
+ va_end(ap);
+
+ return r;
+}
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) {
+ va_list args;
+ int r;
+
+ assert(handle);
+ assert(fmt);
+
+ /* This is just like pam_prompt(), but does not noisily (i.e. beyond LOG_DEBUG) log on its own, but leaves that to the caller */
+
+ _cleanup_free_ char *msg = NULL;
+ va_start(args, fmt);
+ r = vasprintf(&msg, fmt, args);
+ va_end(args);
+ if (r < 0)
+ return PAM_BUF_ERR;
+
+ const struct pam_conv *conv = NULL;
+ r = pam_get_item(handle, PAM_CONV, (const void**) &conv);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@");
+ if (!conv || !conv->conv) {
+ pam_syslog(handle, LOG_DEBUG, "No conversation function.");
+ return PAM_SYSTEM_ERR;
+ }
+
+ struct pam_message message = {
+ .msg_style = style,
+ .msg = msg,
+ };
+ const struct pam_message *pmessage = &message;
+ _cleanup_free_ struct pam_response *response = NULL;
+ r = conv->conv(1, &pmessage, &response, conv->appdata_ptr);
+ _cleanup_(erase_and_freep) char *rr = response ? response->resp : NULL; /* make sure string is freed + erased */
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Conversation function failed: @PAMERR@");
+
+ if (ret_response)
+ *ret_response = TAKE_PTR(rr);
+
+ return PAM_SUCCESS;
+}
diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h
index 5a05fb7..d627eb7 100644
--- a/src/shared/pam-util.h
+++ b/src/shared/pam-util.h
@@ -5,6 +5,8 @@
#include "sd-bus.h"
+void pam_log_setup(void);
+
int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5);
int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5);
@@ -37,5 +39,12 @@ void pam_bus_data_disconnectp(PamBusData **d);
* helps avoid a clash in the internal data structures of sd-bus. It will be used as key for cache items. */
int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret_bus, PamBusData **ret_bus_data);
int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
+int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret);
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
+
+int pam_get_item_many_internal(pam_handle_t *handle, ...);
+
+#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
+
+int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5);
diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c
index 9664b9c..ca6842d 100644
--- a/src/shared/parse-helpers.c
+++ b/src/shared/parse-helpers.c
@@ -4,6 +4,7 @@
#include "extract-word.h"
#include "ip-protocol-list.h"
#include "log.h"
+#include "mountpoint-util.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "path-util.h"
@@ -11,47 +12,56 @@
int path_simplify_and_warn(
char *path,
- unsigned flag,
+ PathSimplifyWarnFlags flags,
const char *unit,
const char *filename,
unsigned line,
const char *lvalue) {
- bool fatal = flag & PATH_CHECK_FATAL;
+ bool fatal = flags & PATH_CHECK_FATAL;
+ int level = fatal ? LOG_ERR : LOG_WARNING;
- assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+ assert(path);
+ assert(!FLAGS_SET(flags, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
+ assert(lvalue);
if (!utf8_is_valid(path))
return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
- if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
+ if (flags & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
bool absolute;
absolute = path_is_absolute(path);
- if (!absolute && (flag & PATH_CHECK_ABSOLUTE))
- return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+ if (!absolute && (flags & PATH_CHECK_ABSOLUTE))
+ return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL),
"%s= path is not absolute%s: %s",
lvalue, fatal ? "" : ", ignoring", path);
- if (absolute && (flag & PATH_CHECK_RELATIVE))
- return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+ if (absolute && (flags & PATH_CHECK_RELATIVE))
+ return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL),
"%s= path is absolute%s: %s",
lvalue, fatal ? "" : ", ignoring", path);
}
- path_simplify_full(path, flag & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0);
+ path_simplify_full(path, flags & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0);
if (!path_is_valid(path))
- return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+ return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL),
"%s= path has invalid length (%zu bytes)%s.",
lvalue, strlen(path), fatal ? "" : ", ignoring");
if (!path_is_normalized(path))
- return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
+ return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL),
"%s= path is not normalized%s: %s",
lvalue, fatal ? "" : ", ignoring", path);
+ if (FLAGS_SET(flags, PATH_CHECK_NON_API_VFS) && path_below_api_vfs(path))
+ return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL),
+ "%s= path is below API VFS%s: %s",
+ lvalue, fatal ? ", refusing" : ", ignoring",
+ path);
+
return 0;
}
@@ -102,6 +112,8 @@ static int parse_ip_ports_token(
uint16_t *nr_ports,
uint16_t *port_min) {
+ int r;
+
assert(token);
assert(nr_ports);
assert(port_min);
@@ -110,7 +122,7 @@ static int parse_ip_ports_token(
*nr_ports = *port_min = 0;
else {
uint16_t mn = 0, mx = 0;
- int r = parse_ip_port_range(token, &mn, &mx);
+ r = parse_ip_port_range(token, &mn, &mx, /* allow_zero = */ true);
if (r < 0)
return r;
@@ -194,6 +206,7 @@ int parse_socket_bind_item(
*ip_protocol = proto;
*nr_ports = nr;
*port_min = mn;
+
return 0;
}
diff --git a/src/shared/parse-helpers.h b/src/shared/parse-helpers.h
index 3e4ad3c..6d1034b 100644
--- a/src/shared/parse-helpers.h
+++ b/src/shared/parse-helpers.h
@@ -3,27 +3,28 @@
#include <stdint.h>
-enum {
- PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */
- PATH_CHECK_ABSOLUTE = 1 << 1,
- PATH_CHECK_RELATIVE = 1 << 2,
+typedef enum PathSimplifyWarnFlags {
+ PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */
+ PATH_CHECK_ABSOLUTE = 1 << 1,
+ PATH_CHECK_RELATIVE = 1 << 2,
PATH_KEEP_TRAILING_SLASH = 1 << 3,
-};
+ PATH_CHECK_NON_API_VFS = 1 << 4,
+} PathSimplifyWarnFlags;
int path_simplify_and_warn(
char *path,
- unsigned flag,
+ PathSimplifyWarnFlags flags,
const char *unit,
const char *filename,
unsigned line,
const char *lvalue);
int parse_socket_bind_item(
- const char *str,
- int *address_family,
- int *ip_protocol,
- uint16_t *nr_ports,
- uint16_t *port_min);
+ const char *str,
+ int *address_family,
+ int *ip_protocol,
+ uint16_t *nr_ports,
+ uint16_t *port_min);
int config_parse_path_or_ignore(
const char *unit,
diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c
index adfc14d..764b772 100644
--- a/src/shared/password-quality-util-passwdqc.c
+++ b/src/shared/password-quality-util-passwdqc.c
@@ -12,14 +12,19 @@
static void *passwdqc_dl = NULL;
-void (*sym_passwdqc_params_reset)(passwdqc_params_t *params);
-int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname);
-int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv);
-void (*sym_passwdqc_params_free)(passwdqc_params_t *params);
-const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw);
-char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params);
+DLSYM_FUNCTION(passwdqc_params_reset);
+DLSYM_FUNCTION(passwdqc_params_load);
+DLSYM_FUNCTION(passwdqc_params_parse);
+DLSYM_FUNCTION(passwdqc_params_free);
+DLSYM_FUNCTION(passwdqc_check);
+DLSYM_FUNCTION(passwdqc_random);
int dlopen_passwdqc(void) {
+ ELF_NOTE_DLOPEN("passwdqc",
+ "Support for password quality checks",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libpasswdqc.so.1");
+
return dlopen_many_sym_or_warn(
&passwdqc_dl, "libpasswdqc.so.1", LOG_DEBUG,
DLSYM_ARG(passwdqc_params_reset),
diff --git a/src/shared/password-quality-util-passwdqc.h b/src/shared/password-quality-util-passwdqc.h
index 0d528d2..9911888 100644
--- a/src/shared/password-quality-util-passwdqc.h
+++ b/src/shared/password-quality-util-passwdqc.h
@@ -6,12 +6,14 @@
#if HAVE_PASSWDQC
#include <passwdqc.h>
-extern void (*sym_passwdqc_params_reset)(passwdqc_params_t *params);
-extern int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname);
-extern int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv);
-extern void (*sym_passwdqc_params_free)(passwdqc_params_t *params);
-extern const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw);
-extern char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params);
+#include "dlfcn-util.h"
+
+DLSYM_PROTOTYPE(passwdqc_params_reset);
+DLSYM_PROTOTYPE(passwdqc_params_load);
+DLSYM_PROTOTYPE(passwdqc_params_parse);
+DLSYM_PROTOTYPE(passwdqc_params_free);
+DLSYM_PROTOTYPE(passwdqc_check);
+DLSYM_PROTOTYPE(passwdqc_random);
int dlopen_passwdqc(void);
diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c
index 80f7d58..7456469 100644
--- a/src/shared/password-quality-util-pwquality.c
+++ b/src/shared/password-quality-util-pwquality.c
@@ -14,16 +14,21 @@
static void *pwquality_dl = NULL;
-int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror);
-pwquality_settings_t *(*sym_pwquality_default_settings)(void);
-void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq);
-int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password);
-int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value);
-int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror);
-int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value);
-const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror);
+DLSYM_FUNCTION(pwquality_check);
+DLSYM_FUNCTION(pwquality_default_settings);
+DLSYM_FUNCTION(pwquality_free_settings);
+DLSYM_FUNCTION(pwquality_generate);
+DLSYM_FUNCTION(pwquality_get_str_value);
+DLSYM_FUNCTION(pwquality_read_config);
+DLSYM_FUNCTION(pwquality_set_int_value);
+DLSYM_FUNCTION(pwquality_strerror);
int dlopen_pwquality(void) {
+ ELF_NOTE_DLOPEN("pwquality",
+ "Support for password quality checks",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libpwquality.so.1");
+
return dlopen_many_sym_or_warn(
&pwquality_dl, "libpwquality.so.1", LOG_DEBUG,
DLSYM_ARG(pwquality_check),
@@ -101,7 +106,6 @@ int suggest_passwords(void) {
_cleanup_strv_free_erase_ char **suggestions = NULL;
_cleanup_(erase_and_freep) char *joined = NULL;
char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
- size_t i;
int r;
r = pwq_allocate_context(&pwq);
@@ -115,7 +119,7 @@ int suggest_passwords(void) {
if (!suggestions)
return log_oom();
- for (i = 0; i < N_SUGGESTIONS; i++) {
+ for (size_t i = 0; i < N_SUGGESTIONS; i++) {
r = sym_pwquality_generate(pwq, 64, suggestions + i);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s",
@@ -145,13 +149,10 @@ int check_password_quality(const char *password, const char *old, const char *us
r = sym_pwquality_check(pwq, password, old, username, &auxerror);
if (r < 0) {
if (ret_error) {
- _cleanup_free_ char *e = NULL;
-
- e = strdup(sym_pwquality_strerror(buf, sizeof(buf), r, auxerror));
- if (!e)
- return -ENOMEM;
-
- *ret_error = TAKE_PTR(e);
+ r = strdup_to(ret_error,
+ sym_pwquality_strerror(buf, sizeof(buf), r, auxerror));
+ if (r < 0)
+ return r;
}
return 0; /* all bad */
diff --git a/src/shared/password-quality-util-pwquality.h b/src/shared/password-quality-util-pwquality.h
index a420b0d..4c2517b 100644
--- a/src/shared/password-quality-util-pwquality.h
+++ b/src/shared/password-quality-util-pwquality.h
@@ -8,14 +8,16 @@
#include <sys/types.h>
#include <pwquality.h>
-extern int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror);
-extern pwquality_settings_t *(*sym_pwquality_default_settings)(void);
-extern void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq);
-extern int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password);
-extern int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value);
-extern int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror);
-extern int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value);
-extern const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror);
+#include "dlfcn-util.h"
+
+DLSYM_PROTOTYPE(pwquality_check);
+DLSYM_PROTOTYPE(pwquality_default_settings);
+DLSYM_PROTOTYPE(pwquality_free_settings);
+DLSYM_PROTOTYPE(pwquality_generate);
+DLSYM_PROTOTYPE(pwquality_get_str_value);
+DLSYM_PROTOTYPE(pwquality_read_config);
+DLSYM_PROTOTYPE(pwquality_set_int_value);
+DLSYM_PROTOTYPE(pwquality_strerror);
int dlopen_pwquality(void);
diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c
index 578b02d..7deb64f 100644
--- a/src/shared/pcre2-util.c
+++ b/src/shared/pcre2-util.c
@@ -7,13 +7,13 @@
#if HAVE_PCRE2
static void *pcre2_dl = NULL;
-pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *);
-void (*sym_pcre2_match_data_free)(pcre2_match_data *);
-void (*sym_pcre2_code_free)(pcre2_code *);
-pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *);
-int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE);
-int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *);
-PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *);
+DLSYM_FUNCTION(pcre2_match_data_create);
+DLSYM_FUNCTION(pcre2_match_data_free);
+DLSYM_FUNCTION(pcre2_code_free);
+DLSYM_FUNCTION(pcre2_compile);
+DLSYM_FUNCTION(pcre2_get_error_message);
+DLSYM_FUNCTION(pcre2_match);
+DLSYM_FUNCTION(pcre2_get_ovector_pointer);
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
pcre2_code_hash_ops_free,
@@ -27,6 +27,11 @@ const struct hash_ops pcre2_code_hash_ops_free = {};
int dlopen_pcre2(void) {
#if HAVE_PCRE2
+ ELF_NOTE_DLOPEN("pcre2",
+ "Support for regular expressions",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libpcre2-8.so.0");
+
/* So here's something weird: PCRE2 actually renames the symbols exported by the library via C
* macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is
* gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we
diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h
index f1e744d..27e2b02 100644
--- a/src/shared/pcre2-util.h
+++ b/src/shared/pcre2-util.h
@@ -6,16 +6,18 @@
#if HAVE_PCRE2
+#include "dlfcn-util.h"
+
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
-extern pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *);
-extern void (*sym_pcre2_match_data_free)(pcre2_match_data *);
-extern void (*sym_pcre2_code_free)(pcre2_code *);
-extern pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *);
-extern int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE);
-extern int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *);
-extern PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *);
+DLSYM_PROTOTYPE(pcre2_match_data_create);
+DLSYM_PROTOTYPE(pcre2_match_data_free);
+DLSYM_PROTOTYPE(pcre2_code_free);
+DLSYM_PROTOTYPE(pcre2_compile);
+DLSYM_PROTOTYPE(pcre2_get_error_message);
+DLSYM_PROTOTYPE(pcre2_match);
+DLSYM_PROTOTYPE(pcre2_get_ovector_pointer);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_match_data*, sym_pcre2_match_data_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_code*, sym_pcre2_code_free, NULL);
diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c
index fa066a4..5ec9a06 100644
--- a/src/shared/pcrextend-util.c
+++ b/src/shared/pcrextend-util.c
@@ -101,7 +101,7 @@ int pcrextend_file_system_word(const char *path, char **ret_word, char **ret_nor
if (r < 0)
return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized_path);
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized_path);
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing.", normalized_path);
normalized_escaped = xescape(normalized_path, ":"); /* Avoid ambiguity around ":" */
if (!normalized_escaped)
diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c
index 4c05323..997e0e4 100644
--- a/src/shared/pe-binary.c
+++ b/src/shared/pe-binary.c
@@ -234,8 +234,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections)
if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
return false;
+ /* Note that the UKI spec only requires .linux, but we are stricter here, and require .osrel too,
+ * since for sd-boot it just doesn't make sense to not have that. */
return
pe_header_find_section(pe_header, sections, ".osrel") &&
- pe_header_find_section(pe_header, sections, ".linux") &&
- pe_header_find_section(pe_header, sections, ".initrd");
+ pe_header_find_section(pe_header, sections, ".linux");
}
diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c
index 6e88dc3..b5cd9a3 100644
--- a/src/shared/pkcs11-util.c
+++ b/src/shared/pkcs11-util.c
@@ -43,22 +43,29 @@ bool pkcs11_uri_valid(const char *uri) {
static void *p11kit_dl = NULL;
-char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module);
-void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules);
-CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags);
-const char *(*sym_p11_kit_strerror)(CK_RV rv);
-int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
-void (*sym_p11_kit_uri_free)(P11KitUri *uri);
-CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
-CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
-CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
-CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
-int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info);
-const char *(*sym_p11_kit_uri_message)(int code);
-P11KitUri *(*sym_p11_kit_uri_new)(void);
-int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri);
+DLSYM_FUNCTION(p11_kit_module_get_name);
+DLSYM_FUNCTION(p11_kit_modules_finalize_and_release);
+DLSYM_FUNCTION(p11_kit_modules_load_and_initialize);
+DLSYM_FUNCTION(p11_kit_strerror);
+DLSYM_FUNCTION(p11_kit_uri_format);
+DLSYM_FUNCTION(p11_kit_uri_free);
+DLSYM_FUNCTION(p11_kit_uri_get_attributes);
+DLSYM_FUNCTION(p11_kit_uri_get_attribute);
+DLSYM_FUNCTION(p11_kit_uri_set_attribute);
+DLSYM_FUNCTION(p11_kit_uri_get_module_info);
+DLSYM_FUNCTION(p11_kit_uri_get_slot_info);
+DLSYM_FUNCTION(p11_kit_uri_get_token_info);
+DLSYM_FUNCTION(p11_kit_uri_match_token_info);
+DLSYM_FUNCTION(p11_kit_uri_message);
+DLSYM_FUNCTION(p11_kit_uri_new);
+DLSYM_FUNCTION(p11_kit_uri_parse);
int dlopen_p11kit(void) {
+ ELF_NOTE_DLOPEN("p11-kit",
+ "Support for PKCS11 hardware tokens",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libp11-kit.so.0");
+
return dlopen_many_sym_or_warn(
&p11kit_dl,
"libp11-kit.so.0", LOG_DEBUG,
@@ -69,6 +76,8 @@ int dlopen_p11kit(void) {
DLSYM_ARG(p11_kit_uri_format),
DLSYM_ARG(p11_kit_uri_free),
DLSYM_ARG(p11_kit_uri_get_attributes),
+ DLSYM_ARG(p11_kit_uri_get_attribute),
+ DLSYM_ARG(p11_kit_uri_set_attribute),
DLSYM_ARG(p11_kit_uri_get_module_info),
DLSYM_ARG(p11_kit_uri_get_slot_info),
DLSYM_ARG(p11_kit_uri_get_token_info),
@@ -287,12 +296,11 @@ int pkcs11_token_login(
CK_SLOT_ID slotid,
const CK_TOKEN_INFO *token_info,
const char *friendly_name,
- const char *icon_name,
- const char *key_name,
- const char *credential_name,
+ const char *askpw_icon,
+ const char *askpw_keyring,
+ const char *askpw_credential,
usec_t until,
- AskPasswordFlags ask_password_flags,
- bool headless,
+ AskPasswordFlags askpw_flags,
char **ret_used_pin) {
_cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL;
@@ -347,7 +355,7 @@ int pkcs11_token_login(
if (!passwords)
return log_oom();
- } else if (headless)
+ } else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the 'PIN' environment variable.");
else {
_cleanup_free_ char *text = NULL;
@@ -371,8 +379,16 @@ int pkcs11_token_login(
if (r < 0)
return log_oom();
+ AskPasswordRequest req = {
+ .message = text,
+ .icon = askpw_icon,
+ .id = id,
+ .keyring = askpw_keyring,
+ .credential = askpw_credential,
+ };
+
/* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */
- r = ask_password_auto(text, icon_name, id, key_name, credential_name, until, ask_password_flags, &passwords);
+ r = ask_password_auto(&req, until, askpw_flags, &passwords);
if (r < 0)
return log_error_errno(r, "Failed to query PIN for security token '%s': %m", token_label);
}
@@ -523,13 +539,294 @@ int pkcs11_token_find_x509_certificate(
}
#if HAVE_OPENSSL
+static int read_public_key_info(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object,
+ EVP_PKEY **ret_pkey) {
+
+ CK_ATTRIBUTE attribute = { CKA_PUBLIC_KEY_INFO, NULL_PTR, 0 };
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ CK_RV rv;
+
+ rv = m->C_GetAttributeValue(session, object, &attribute, 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to get size of CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv));
+
+ if (attribute.ulValueLen == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "CKA_PUBLIC_KEY_INFO is empty");
+
+ _cleanup_free_ void *buffer = malloc(attribute.ulValueLen);
+ if (!buffer)
+ return log_oom_debug();
+
+ attribute.pValue = buffer;
+
+ rv = m->C_GetAttributeValue(session, object, &attribute, 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to read CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv));
+
+ const unsigned char *value = attribute.pValue;
+ pkey = d2i_PUBKEY(NULL, &value, attribute.ulValueLen);
+ if (!pkey)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse CKA_PUBLIC_KEY_INFO");
+
+ *ret_pkey = TAKE_PTR(pkey);
+ return 0;
+}
+
+int pkcs11_token_read_public_key(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object,
+ EVP_PKEY **ret_pkey) {
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ CK_RV rv;
+ int r;
+
+ r = read_public_key_info(m, session, object, &pkey);
+ if (r >= 0) {
+ *ret_pkey = TAKE_PTR(pkey);
+ return 0;
+ }
+
+ CK_KEY_TYPE key_type;
+ CK_ATTRIBUTE attribute = { CKA_KEY_TYPE, &key_type, sizeof(key_type) };
+
+ rv = m->C_GetAttributeValue(session, object, &attribute, 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get CKA_KEY_TYPE of a public key: %s", sym_p11_kit_strerror(rv));
+
+ switch (key_type) {
+ case CKK_RSA: {
+ CK_ATTRIBUTE rsa_attributes[] = {
+ { CKA_MODULUS, NULL_PTR, 0 },
+ { CKA_PUBLIC_EXPONENT, NULL_PTR, 0 },
+ };
+
+ rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes));
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get size of attributes of an RSA public key: %s", sym_p11_kit_strerror(rv));
+
+ if (rsa_attributes[0].ulValueLen == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_MODULUS.");
+
+ _cleanup_free_ void *modulus = malloc(rsa_attributes[0].ulValueLen);
+ if (!modulus)
+ return log_oom_debug();
+
+ rsa_attributes[0].pValue = modulus;
+
+ if (rsa_attributes[1].ulValueLen == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_PUBLIC_EXPONENT.");
+
+ _cleanup_free_ void *public_exponent = malloc(rsa_attributes[1].ulValueLen);
+ if (!public_exponent)
+ return log_oom_debug();
+
+ rsa_attributes[1].pValue = public_exponent;
+
+ rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes));
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get attributes of an RSA public key: %s", sym_p11_kit_strerror(rv));
+
+ size_t n_size = rsa_attributes[0].ulValueLen, e_size = rsa_attributes[1].ulValueLen;
+ r = rsa_pkey_from_n_e(rsa_attributes[0].pValue, n_size, rsa_attributes[1].pValue, e_size, &pkey);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create an EVP_PKEY from RSA parameters.");
+
+ break;
+ }
+ case CKK_EC: {
+ CK_ATTRIBUTE ec_attributes[] = {
+ { CKA_EC_PARAMS, NULL_PTR, 0 },
+ { CKA_EC_POINT, NULL_PTR, 0 },
+ };
+
+ rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes));
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get size of attributes of an EC public key: %s", sym_p11_kit_strerror(rv));
+
+ if (ec_attributes[0].ulValueLen == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_PARAMS.");
+
+ _cleanup_free_ void *ec_group = malloc(ec_attributes[0].ulValueLen);
+ if (!ec_group)
+ return log_oom_debug();
+
+ ec_attributes[0].pValue = ec_group;
+
+ if (ec_attributes[1].ulValueLen == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_POINT.");
+
+ _cleanup_free_ void *ec_point = malloc(ec_attributes[1].ulValueLen);
+ if (!ec_point)
+ return log_oom_debug();
+
+ ec_attributes[1].pValue = ec_point;
+
+ rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes));
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get attributes of an EC public key: %s", sym_p11_kit_strerror(rv));
+
+ _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL;
+ _cleanup_(ASN1_OCTET_STRING_freep) ASN1_OCTET_STRING *os = NULL;
+
+ const unsigned char *ec_params_value = ec_attributes[0].pValue;
+ group = d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen);
+ if (!group)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS.");
+
+ const unsigned char *ec_point_value = ec_attributes[1].pValue;
+ os = d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen);
+ if (!os)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT.");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (!ctx)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create an EVP_PKEY_CTX for EC.");
+
+ if (EVP_PKEY_fromdata_init(ctx) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC.");
+
+ OSSL_PARAM ec_params[8] = {
+ OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, os->data, os->length)
+ };
+
+ _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL;
+ size_t order_size, p_size, a_size, b_size, generator_size;
+
+ int nid = EC_GROUP_get_curve_name(group);
+ if (nid != NID_undef) {
+ const char* name = OSSL_EC_curve_nid2name(nid);
+ ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name));
+ ec_params[2] = OSSL_PARAM_construct_end();
+ } else {
+ const char *field_type = EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ?
+ "prime-field" : "characteristic-two-field";
+
+ const BIGNUM *bn_order = EC_GROUP_get0_order(group);
+
+ _cleanup_(BN_CTX_freep) BN_CTX *bnctx = BN_CTX_new();
+ if (!bnctx)
+ return log_oom_debug();
+
+ _cleanup_(BN_freep) BIGNUM *bn_p = BN_new();
+ if (!bn_p)
+ return log_oom_debug();
+
+ _cleanup_(BN_freep) BIGNUM *bn_a = BN_new();
+ if (!bn_a)
+ return log_oom_debug();
+
+ _cleanup_(BN_freep) BIGNUM *bn_b = BN_new();
+ if (!bn_b)
+ return log_oom_debug();
+
+ if (EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP.");
+
+ order_size = BN_num_bytes(bn_order);
+ p_size = BN_num_bytes(bn_p);
+ a_size = BN_num_bytes(bn_a);
+ b_size = BN_num_bytes(bn_b);
+
+ order = malloc(order_size);
+ if (!order)
+ return log_oom_debug();
+
+ p = malloc(p_size);
+ if (!p)
+ return log_oom_debug();
+
+ a = malloc(a_size);
+ if (!a)
+ return log_oom_debug();
+
+ b = malloc(b_size);
+ if (!b)
+ return log_oom_debug();
+
+ if (BN_bn2nativepad(bn_order, order, order_size) <= 0 ||
+ BN_bn2nativepad(bn_p, p, p_size) <= 0 ||
+ BN_bn2nativepad(bn_a, a, a_size) <= 0 ||
+ BN_bn2nativepad(bn_b, b, b_size) <= 0 )
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order.");
+
+ const EC_POINT *point_gen = EC_GROUP_get0_generator(group);
+ generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx);
+ if (generator_size == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a EC generator.");
+
+ generator = malloc(generator_size);
+ if (!generator)
+ return log_oom_debug();
+
+ generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx);
+ if (generator_size == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC generator to octet string.");
+
+ ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type));
+ ec_params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size);
+ ec_params[3] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size);
+ ec_params[4] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size);
+ ec_params[5] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size);
+ ec_params[6] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size);
+ ec_params[7] = OSSL_PARAM_construct_end();
+ }
+
+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters.");
+#else
+ _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group);
+ if (!point)
+ return log_oom_debug();
+
+ if (EC_POINT_oct2point(group, point, os->data, os->length, NULL) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT.");
+
+ _cleanup_(EC_KEY_freep) EC_KEY *ec_key = EC_KEY_new();
+ if (!ec_key)
+ return log_oom_debug();
+
+ if (EC_KEY_set_group(ec_key, group) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set group for EC_KEY.");
+
+ if (EC_KEY_set_public_key(ec_key, point) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set public key for EC_KEY.");
+
+ pkey = EVP_PKEY_new();
+ if (!pkey)
+ return log_oom_debug();
+
+ if (EVP_PKEY_set1_EC_KEY(pkey, ec_key) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign EC_KEY to EVP_PKEY.");
+#endif
+ break;
+ }
+ default:
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported type of public key: %lu", key_type);
+ }
+
+ *ret_pkey = TAKE_PTR(pkey);
+ return 0;
+}
+
int pkcs11_token_read_x509_certificate(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE object,
X509 **ret_cert) {
- _cleanup_free_ void *buffer = NULL;
_cleanup_free_ char *t = NULL;
CK_ATTRIBUTE attribute = {
.type = CKA_VALUE
@@ -537,7 +834,6 @@ int pkcs11_token_read_x509_certificate(
CK_RV rv;
_cleanup_(X509_freep) X509 *x509 = NULL;
X509_NAME *name = NULL;
- const unsigned char *p;
int r;
r = dlopen_p11kit();
@@ -546,32 +842,32 @@ int pkcs11_token_read_x509_certificate(
rv = m->C_GetAttributeValue(session, object, &attribute, 1);
if (rv != CKR_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to read X.509 certificate size off token: %s", sym_p11_kit_strerror(rv));
- buffer = malloc(attribute.ulValueLen);
+ _cleanup_free_ void *buffer = malloc(attribute.ulValueLen);
if (!buffer)
- return log_oom();
+ return log_oom_debug();
attribute.pValue = buffer;
rv = m->C_GetAttributeValue(session, object, &attribute, 1);
if (rv != CKR_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv));
- p = attribute.pValue;
+ const unsigned char *p = attribute.pValue;
x509 = d2i_X509(NULL, &p, attribute.ulValueLen);
if (!x509)
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed parse X.509 certificate.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate.");
name = X509_get_subject_name(x509);
if (!name)
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name.");
t = X509_NAME_oneline(name, NULL, 0);
if (!t)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string.");
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string.");
log_debug("Using X.509 certificate issued for '%s'.", t);
@@ -586,143 +882,429 @@ int pkcs11_token_find_private_key(
P11KitUri *search_uri,
CK_OBJECT_HANDLE *ret_object) {
- bool found_decrypt = false, found_class = false, found_key_type = false;
+ bool found_class = false;
_cleanup_free_ CK_ATTRIBUTE *attributes_buffer = NULL;
- CK_ULONG n_attributes, a, n_objects;
- CK_ATTRIBUTE *attributes = NULL;
- CK_OBJECT_HANDLE objects[2];
- CK_RV rv, rv2;
- int r;
+ CK_KEY_TYPE key_type;
+ CK_BBOOL decrypt_value, derive_value;
+ CK_ATTRIBUTE optional_attributes[] = {
+ { CKA_KEY_TYPE, &key_type, sizeof(key_type) },
+ { CKA_DECRYPT, &decrypt_value, sizeof(decrypt_value) },
+ { CKA_DERIVE, &derive_value, sizeof(derive_value) },
+ };
+ uint8_t n_private_keys = 0;
+ CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE;
+ CK_RV rv;
assert(m);
assert(search_uri);
assert(ret_object);
- r = dlopen_p11kit();
- if (r < 0)
- return r;
-
- attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes);
- for (a = 0; a < n_attributes; a++) {
+ CK_ULONG n_attributes;
+ CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes);
+ for (CK_ULONG i = 0; i < n_attributes; i++) {
/* We use the URI's included match attributes, but make them more strict. This allows users
* to specify a token URL instead of an object URL and the right thing should happen if
* there's only one suitable key on the token. */
- switch (attributes[a].type) {
-
+ switch (attributes[i].type) {
case CKA_CLASS: {
- CK_OBJECT_CLASS c;
-
- if (attributes[a].ulValueLen != sizeof(c))
+ if (attributes[i].ulValueLen != sizeof(CK_OBJECT_CLASS))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_CLASS attribute size.");
- memcpy(&c, attributes[a].pValue, sizeof(c));
- if (c != CKO_PRIVATE_KEY)
+ CK_OBJECT_CLASS *class = (CK_OBJECT_CLASS*) attributes[i].pValue;
+ if (*class != CKO_PRIVATE_KEY)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Selected PKCS#11 object is not a private key, refusing.");
found_class = true;
break;
- }
+ }}
+ }
- case CKA_DECRYPT: {
- CK_BBOOL b;
+ if (!found_class) {
+ /* Hmm, let's slightly extend the attribute list we search for */
+ static const CK_OBJECT_CLASS required_class = CKO_PRIVATE_KEY;
- if (attributes[a].ulValueLen != sizeof(b))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_DECRYPT attribute size.");
+ attributes_buffer = new(CK_ATTRIBUTE, n_attributes + 1);
+ if (!attributes_buffer)
+ return log_oom();
- memcpy(&b, attributes[a].pValue, sizeof(b));
- if (!b)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Selected PKCS#11 object is not suitable for decryption, refusing.");
+ memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes);
+
+ attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
+ .type = CKA_CLASS,
+ .pValue = (CK_OBJECT_CLASS*) &required_class,
+ .ulValueLen = sizeof(required_class),
+ };
+
+ attributes = attributes_buffer;
+ }
- found_decrypt = true;
+ rv = m->C_FindObjectsInit(session, attributes, n_attributes);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
+
+ for (;;) {
+ CK_ULONG b;
+ CK_OBJECT_HANDLE candidate;
+ rv = m->C_FindObjects(session, &candidate, 1, &b);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to find objects: %s", sym_p11_kit_strerror(rv));
+
+ if (b == 0)
break;
- }
- case CKA_KEY_TYPE: {
- CK_KEY_TYPE t;
+ optional_attributes[0].ulValueLen = sizeof(key_type);
+ optional_attributes[1].ulValueLen = sizeof(decrypt_value);
+ optional_attributes[2].ulValueLen = sizeof(derive_value);
- if (attributes[a].ulValueLen != sizeof(t))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_KEY_TYPE attribute size.");
+ rv = m->C_GetAttributeValue(session, candidate, optional_attributes, ELEMENTSOF(optional_attributes));
+ if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get attributes of a found private key: %s", sym_p11_kit_strerror(rv));
- memcpy(&t, attributes[a].pValue, sizeof(t));
- if (t != CKK_RSA)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not an RSA key, refusing.");
+ if (optional_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) {
+ log_debug("A found private key does not have CKA_KEY_TYPE, rejecting the key.");
+ continue;
+ }
+
+ if (key_type == CKK_RSA)
+ if (optional_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION || decrypt_value == CK_FALSE) {
+ log_debug("A found private RSA key can't decrypt, rejecting the key.");
+ continue;
+ }
- found_key_type = true;
+ if (key_type == CKK_EC)
+ if (optional_attributes[2].ulValueLen == CK_UNAVAILABLE_INFORMATION || derive_value == CK_FALSE) {
+ log_debug("A found private EC key can't derive, rejecting the key.");
+ continue;
+ }
+
+ n_private_keys++;
+ if (n_private_keys > 1)
break;
- }}
+ private_key = candidate;
}
- if (!found_decrypt || !found_class || !found_key_type) {
- /* Hmm, let's slightly extend the attribute list we search for */
+ rv = m->C_FindObjectsFinal(session);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
- attributes_buffer = new(CK_ATTRIBUTE, n_attributes + !found_decrypt + !found_class + !found_key_type);
- if (!attributes_buffer)
+ if (n_private_keys == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Failed to find selected private key suitable for decryption or derivation on token.");
+
+ if (n_private_keys > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Configured private key URI matches multiple keys, refusing.");
+
+ *ret_object = private_key;
+ return 0;
+}
+
+static const char* object_class_to_string(CK_OBJECT_CLASS class) {
+ switch (class) {
+ case CKO_CERTIFICATE:
+ return "CKO_CERTIFICATE";
+ case CKO_PUBLIC_KEY:
+ return "CKO_PUBLIC_KEY";
+ case CKO_PRIVATE_KEY:
+ return "CKO_PRIVATE_KEY";
+ case CKO_SECRET_KEY:
+ return "CKO_SECRET_KEY";
+ default:
+ return NULL;
+ }
+}
+
+/* Returns an object with the given class and the same CKA_ID or CKA_LABEL as prototype */
+int pkcs11_token_find_related_object(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE prototype,
+ CK_OBJECT_CLASS class,
+ CK_OBJECT_HANDLE *ret_object ) {
+
+ _cleanup_free_ void *buffer = NULL;
+ CK_ATTRIBUTE attributes[] = {
+ { CKA_ID, NULL_PTR, 0 },
+ { CKA_LABEL, NULL_PTR, 0 }
+ };
+ CK_OBJECT_CLASS search_class = class;
+ CK_ATTRIBUTE search_attributes[2] = {
+ { CKA_CLASS, &search_class, sizeof(search_class) }
+ };
+ CK_ULONG n_objects;
+ CK_OBJECT_HANDLE objects[2];
+ CK_RV rv;
+
+ rv = m->C_GetAttributeValue(session, prototype, attributes, ELEMENTSOF(attributes));
+ if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID))
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve length of attributes: %s", sym_p11_kit_strerror(rv));
+
+ if (attributes[0].ulValueLen != CK_UNAVAILABLE_INFORMATION) {
+ buffer = malloc(attributes[0].ulValueLen);
+ if (!buffer)
return log_oom();
- memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes);
+ attributes[0].pValue = buffer;
+ rv = m->C_GetAttributeValue(session, prototype, &attributes[0], 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve CKA_ID: %s", sym_p11_kit_strerror(rv));
- if (!found_decrypt) {
- static const CK_BBOOL yes = true;
+ search_attributes[1] = attributes[0];
- attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
- .type = CKA_DECRYPT,
- .pValue = (CK_BBOOL*) &yes,
- .ulValueLen = sizeof(yes),
- };
- }
+ } else if (attributes[1].ulValueLen != CK_UNAVAILABLE_INFORMATION) {
+ buffer = malloc(attributes[1].ulValueLen);
+ if (!buffer)
+ return log_oom();
- if (!found_class) {
- static const CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
+ attributes[1].pValue = buffer;
+ rv = m->C_GetAttributeValue(session, prototype, &attributes[1], 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve CKA_LABEL: %s", sym_p11_kit_strerror(rv));
- attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
- .type = CKA_CLASS,
- .pValue = (CK_OBJECT_CLASS*) &class,
- .ulValueLen = sizeof(class),
- };
- }
+ search_attributes[1] = attributes[1];
- if (!found_key_type) {
- static const CK_KEY_TYPE type = CKK_RSA;
+ } else
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The prototype does not have CKA_ID or CKA_LABEL");
- attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
- .type = CKA_KEY_TYPE,
- .pValue = (CK_KEY_TYPE*) &type,
- .ulValueLen = sizeof(type),
- };
- }
+ rv = m->C_FindObjectsInit(session, search_attributes, 2);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
- attributes = attributes_buffer;
+ rv = m->C_FindObjects(session, objects, 2, &n_objects);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to find objects: %s", sym_p11_kit_strerror(rv));
+
+ rv = m->C_FindObjectsFinal(session);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
+
+ if (n_objects == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Failed to find a related object with class %s", object_class_to_string(class));
+
+ if (n_objects > 1)
+ log_warning("Found multiple related objects with class %s, using the first object.",
+ object_class_to_string(class));
+
+ *ret_object = objects[0];
+ return 0;
+}
+
+#if HAVE_OPENSSL
+static int ecc_convert_to_compressed(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object,
+ const void *uncompressed_point,
+ size_t uncompressed_point_size,
+ void **ret_compressed_point,
+ size_t *ret_compressed_point_size) {
+
+ _cleanup_free_ void *ec_params_buffer = NULL;
+ CK_ATTRIBUTE ec_params_attr = { CKA_EC_PARAMS, NULL_PTR, 0 };
+ CK_RV rv;
+ int r;
+
+ rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1);
+ if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv));
+
+ if (ec_params_attr.ulValueLen != CK_UNAVAILABLE_INFORMATION) {
+ ec_params_buffer = malloc(ec_params_attr.ulValueLen);
+ if (!ec_params_buffer)
+ return log_oom();
+
+ ec_params_attr.pValue = ec_params_buffer;
+ rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve CKA_EC_PARAMS from a private key: %s", sym_p11_kit_strerror(rv));
+ } else {
+ CK_OBJECT_HANDLE public_key;
+ r = pkcs11_token_find_related_object(m, session, object, CKO_PUBLIC_KEY, &public_key);
+ if (r < 0)
+ return log_error_errno(r, "Failed to find a public key for compressing a EC point");
+
+ ec_params_attr.ulValueLen = 0;
+ rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1);
+ if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv));
+
+ if (ec_params_attr.ulValueLen == CK_UNAVAILABLE_INFORMATION)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The public key does not have CKA_EC_PARAMS");
+
+ ec_params_buffer = malloc(ec_params_attr.ulValueLen);
+ if (!ec_params_buffer)
+ return log_oom();
+
+ ec_params_attr.pValue = ec_params_buffer;
+ rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve CKA_EC_PARAMS from a public key: %s", sym_p11_kit_strerror(rv));
}
- rv = m->C_FindObjectsInit(session, attributes, n_attributes);
+ _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL;
+ _cleanup_(EC_POINT_freep) EC_POINT *point = NULL;
+ _cleanup_(BN_CTX_freep) BN_CTX *bnctx = NULL;
+ _cleanup_free_ void *compressed_point = NULL;
+ size_t compressed_point_size;
+
+ const unsigned char *ec_params_value = ec_params_attr.pValue;
+ group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen);
+ if (!group)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS");
+
+ point = EC_POINT_new(group);
+ if (!point)
+ return log_oom();
+
+ bnctx = BN_CTX_new();
+ if (!bnctx)
+ return log_oom();
+
+ if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point");
+
+ compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx);
+ if (compressed_point_size == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point");
+
+ compressed_point = malloc(compressed_point_size);
+ if (!compressed_point)
+ return log_oom();
+
+ compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx);
+ if (compressed_point_size == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format");
+
+ *ret_compressed_point = TAKE_PTR(compressed_point);
+ *ret_compressed_point_size = compressed_point_size;
+ return 0;
+}
+#endif
+
+/* Since EC keys doesn't support encryption directly, we use ECDH protocol to derive shared secret here.
+ * We use PKCS#11 C_DeriveKey function to derive a shared secret with a private key stored in the token and
+ * a public key saved on enrollment. */
+static int pkcs11_token_decrypt_data_ecc(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object,
+ const void *encrypted_data,
+ size_t encrypted_data_size,
+ void **ret_decrypted_data,
+ size_t *ret_decrypted_data_size) {
+
+ static const CK_BBOOL yes = CK_TRUE, no = CK_FALSE;
+ static const CK_OBJECT_CLASS shared_secret_class = CKO_SECRET_KEY;
+ static const CK_KEY_TYPE shared_secret_type = CKK_GENERIC_SECRET;
+ static const CK_ATTRIBUTE shared_secret_template[] = {
+ { CKA_TOKEN, (void*) &no, sizeof(no) },
+ { CKA_CLASS, (void*) &shared_secret_class, sizeof(shared_secret_class) },
+ { CKA_KEY_TYPE, (void*) &shared_secret_type, sizeof(shared_secret_type) },
+ { CKA_SENSITIVE, (void*) &no, sizeof(no) },
+ { CKA_EXTRACTABLE, (void*) &yes, sizeof(yes) }
+ };
+ CK_ECDH1_DERIVE_PARAMS params = {
+ .kdf = CKD_NULL,
+ .pPublicData = (void*) encrypted_data,
+ .ulPublicDataLen = encrypted_data_size
+ };
+ CK_MECHANISM mechanism = {
+ .mechanism = CKM_ECDH1_DERIVE,
+ .pParameter = &params,
+ .ulParameterLen = sizeof(params)
+ };
+ CK_OBJECT_HANDLE shared_secret_handle;
+ CK_SESSION_INFO session_info;
+ CK_MECHANISM_INFO mechanism_info;
+ CK_RV rv, rv2;
+#if HAVE_OPENSSL
+ _cleanup_free_ void *compressed_point = NULL;
+ int r;
+#endif
+
+ rv = m->C_GetSessionInfo(session, &session_info);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
+ "Failed to get information about the PKCS#11 session: %s", sym_p11_kit_strerror(rv));
- rv = m->C_FindObjects(session, objects, ELEMENTSOF(objects), &n_objects);
- rv2 = m->C_FindObjectsFinal(session);
+ rv = m->C_GetMechanismInfo(session_info.slotID, CKM_ECDH1_DERIVE, &mechanism_info);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to find objects: %s", sym_p11_kit_strerror(rv));
+ "Failed to get information about CKM_ECDH1_DERIVE: %s", sym_p11_kit_strerror(rv));
+
+ if (!(mechanism_info.flags & CKF_EC_UNCOMPRESS)) {
+ if (mechanism_info.flags & CKF_EC_COMPRESS) {
+#if HAVE_OPENSSL
+ log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert.");
+ size_t compressed_point_size = 0; /* Explicit initialization to appease gcc */
+ r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size);
+ if (r < 0)
+ return r;
+
+ params.pPublicData = compressed_point;
+ params.ulPublicDataLen = compressed_point_size;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "CKM_ECDH1_DERIVE does not support uncompressed format of EC points");
+#endif
+ } else
+ log_debug("Both CKF_EC_UNCOMPRESS and CKF_EC_COMPRESS are false for CKM_ECDH1_DERIVE, ignoring.");
+ }
+
+ rv = m->C_DeriveKey(session, &mechanism, object, (CK_ATTRIBUTE*) shared_secret_template, ELEMENTSOF(shared_secret_template), &shared_secret_handle);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to derive a shared secret: %s", sym_p11_kit_strerror(rv));
+
+ CK_ATTRIBUTE shared_secret_attr = { CKA_VALUE, NULL_PTR, 0};
+
+ rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1);
+ if (rv != CKR_OK) {
+ rv2 = m->C_DestroyObject(session, shared_secret_handle);
+ if (rv2 != CKR_OK)
+ log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2));
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve shared secret length: %s", sym_p11_kit_strerror(rv));
+ }
+
+ shared_secret_attr.pValue = malloc(shared_secret_attr.ulValueLen);
+ if (!shared_secret_attr.pValue)
+ return log_oom();
+
+ rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1);
+ rv2 = m->C_DestroyObject(session, shared_secret_handle);
if (rv2 != CKR_OK)
- return log_error_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
- if (n_objects == 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
- "Failed to find selected private key suitable for decryption on token.");
- if (n_objects > 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
- "Configured private key URI matches multiple keys, refusing.");
+ log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2));
- *ret_object = objects[0];
+ if (rv != CKR_OK) {
+ erase_and_free(shared_secret_attr.pValue);
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve a shared secret: %s", sym_p11_kit_strerror(rv));
+ }
+
+ log_info("Successfully derived key with security token.");
+
+ *ret_decrypted_data = shared_secret_attr.pValue;
+ *ret_decrypted_data_size = shared_secret_attr.ulValueLen;
return 0;
}
-int pkcs11_token_decrypt_data(
+static int pkcs11_token_decrypt_data_rsa(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE object,
@@ -737,17 +1319,6 @@ int pkcs11_token_decrypt_data(
_cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL;
CK_ULONG dbuffer_size = 0;
CK_RV rv;
- int r;
-
- assert(m);
- assert(encrypted_data);
- assert(encrypted_data_size > 0);
- assert(ret_decrypted_data);
- assert(ret_decrypted_data_size);
-
- r = dlopen_p11kit();
- if (r < 0)
- return r;
rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object);
if (rv != CKR_OK)
@@ -780,6 +1351,42 @@ int pkcs11_token_decrypt_data(
return 0;
}
+int pkcs11_token_decrypt_data(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE object,
+ const void *encrypted_data,
+ size_t encrypted_data_size,
+ void **ret_decrypted_data,
+ size_t *ret_decrypted_data_size) {
+
+ CK_KEY_TYPE key_type;
+ CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) };
+ CK_RV rv;
+
+ assert(m);
+ assert(encrypted_data);
+ assert(encrypted_data_size > 0);
+ assert(ret_decrypted_data);
+ assert(ret_decrypted_data_size);
+
+ rv = m->C_GetAttributeValue(session, object, &key_type_template, 1);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve private key type");
+
+ switch (key_type) {
+
+ case CKK_RSA:
+ return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size);
+
+ case CKK_EC:
+ return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size);
+
+ default:
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported private key type: %lu", key_type);
+ }
+}
+
int pkcs11_token_acquire_rng(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session) {
@@ -1055,20 +1662,19 @@ int pkcs11_find_token(
}
#if HAVE_OPENSSL
-struct pkcs11_acquire_certificate_callback_data {
+struct pkcs11_acquire_public_key_callback_data {
char *pin_used;
- X509 *cert;
- const char *askpw_friendly_name, *askpw_icon_name;
+ EVP_PKEY *pkey;
+ const char *askpw_friendly_name, *askpw_icon, *askpw_credential;
AskPasswordFlags askpw_flags;
- bool headless;
};
-static void pkcs11_acquire_certificate_callback_data_release(struct pkcs11_acquire_certificate_callback_data *data) {
+static void pkcs11_acquire_public_key_callback_data_release(struct pkcs11_acquire_public_key_callback_data *data) {
erase_and_free(data->pin_used);
- X509_free(data->cert);
+ EVP_PKEY_free(data->pkey);
}
-static int pkcs11_acquire_certificate_callback(
+static int pkcs11_acquire_public_key_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
@@ -1078,8 +1684,16 @@ static int pkcs11_acquire_certificate_callback(
void *userdata) {
_cleanup_(erase_and_freep) char *pin_used = NULL;
- struct pkcs11_acquire_certificate_callback_data *data = ASSERT_PTR(userdata);
- CK_OBJECT_HANDLE object;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ CK_OBJECT_CLASS class;
+ CK_CERTIFICATE_TYPE type;
+ CK_ATTRIBUTE candidate_attributes[] = {
+ { CKA_CLASS, &class, sizeof(class) },
+ { CKA_CERTIFICATE_TYPE, &type, sizeof(type) },
+ };
+ CK_OBJECT_HANDLE candidate, public_key = CK_INVALID_HANDLE, certificate = CK_INVALID_HANDLE;
+ uint8_t n_public_keys = 0, n_certificates = 0;
+ CK_RV rv;
int r;
assert(m);
@@ -1087,6 +1701,8 @@ static int pkcs11_acquire_certificate_callback(
assert(token_info);
assert(uri);
+ struct pkcs11_acquire_public_key_callback_data *data = ASSERT_PTR(userdata);
+
/* Called for every token matching our URI */
r = pkcs11_token_login(
@@ -1095,50 +1711,154 @@ static int pkcs11_acquire_certificate_callback(
slot_id,
token_info,
data->askpw_friendly_name,
- data->askpw_icon_name,
- "pkcs11-pin",
+ data->askpw_icon,
"pkcs11-pin",
+ data->askpw_credential,
UINT64_MAX,
data->askpw_flags,
- data->headless,
&pin_used);
if (r < 0)
return r;
- r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
- if (r < 0)
- return r;
+ CK_ULONG n_attributes;
+ CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(uri, &n_attributes);
+ for (CK_ULONG i = 0; i < n_attributes; i++) {
+ switch (attributes[i].type) {
+ case CKA_CLASS: {
+ CK_OBJECT_CLASS requested_class = *((CK_OBJECT_CLASS*) attributes[i].pValue);
+ if (!IN_SET(requested_class, CKO_PUBLIC_KEY, CKO_CERTIFICATE))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Selected PKCS#11 object is not a public key or certificate, refusing.");
+ break;
+ }
- r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
- if (r < 0)
- return r;
+ case CKA_CERTIFICATE_TYPE: {
+ CK_CERTIFICATE_TYPE requested_type = *((CK_CERTIFICATE_TYPE*) attributes[i].pValue);
+ if (requested_type != CKC_X_509)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected PKCS#11 object is not an X.509 certificate, refusing.");
+ break;
+ }}
+ }
+
+ rv = m->C_FindObjectsInit(session, attributes, n_attributes);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
+
+ for (;;) {
+ CK_ULONG n;
+ rv = m->C_FindObjects(session, &candidate, 1, &n);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to find objects: %s", sym_p11_kit_strerror(rv));
+
+ if (n == 0)
+ break;
+
+ candidate_attributes[0].ulValueLen = sizeof(class);
+ candidate_attributes[1].ulValueLen = sizeof(type);
+ rv = m->C_GetAttributeValue(session, candidate, candidate_attributes, ELEMENTSOF(candidate_attributes));
+ if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to get attributes of a selected candidate: %s", sym_p11_kit_strerror(rv));
+
+ if (candidate_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) {
+ log_debug("Failed to get CKA_CLASS of a selected candidate");
+ continue;
+ }
+
+ if (class == CKO_PUBLIC_KEY) {
+ n_public_keys++;
+ if (n_public_keys > 1)
+ break;
+ public_key = candidate;
+ continue;
+ }
+
+ if (class == CKO_CERTIFICATE) {
+ if (candidate_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION) {
+ log_debug("Failed to get CKA_CERTIFICATE_TYPE of a selected candidate");
+ continue;
+ }
+ if (type != CKC_X_509)
+ continue;
+ n_certificates++;
+ if (n_certificates > 1)
+ break;
+ certificate = candidate;
+ continue;
+ }
+ }
+
+ rv = m->C_FindObjectsFinal(session);
+ if (rv != CKR_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
+
+ if (n_public_keys == 0 && n_certificates == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Failed to find selected public key or X.509 certificate.");
+
+ if (n_public_keys > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Provided URI matches multiple public keys, refusing.");
+ if (n_certificates > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Provided URI matches multiple certificates, refusing.");
+
+ if (n_public_keys != 0) {
+ r = pkcs11_token_read_public_key(m, session, public_key, &pkey);
+ if (r >= 0)
+ goto success;
+ }
+
+ if (n_certificates == 0)
+ return log_error_errno(r, "Failed to read a found public key.");
+
+ {
+ _cleanup_(X509_freep) X509 *cert = NULL;
+
+ r = pkcs11_token_read_x509_certificate(m, session, certificate, &cert);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read a found X.509 certificate.");
+
+ pkey = X509_get_pubkey(cert);
+ if (!pkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
+ }
+success:
/* Let's read some random data off the token and write it to the kernel pool before we generate our
* random key from it. This way we can claim the quality of the RNG is at least as good as the
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
data->pin_used = TAKE_PTR(pin_used);
- return 1;
+ data->pkey = TAKE_PTR(pkey);
+ return 0;
}
-int pkcs11_acquire_certificate(
+int pkcs11_acquire_public_key(
const char *uri,
const char *askpw_friendly_name,
- const char *askpw_icon_name,
- X509 **ret_cert,
+ const char *askpw_icon,
+ const char *askpw_credential,
+ AskPasswordFlags askpw_flags,
+ EVP_PKEY **ret_pkey,
char **ret_pin_used) {
- _cleanup_(pkcs11_acquire_certificate_callback_data_release) struct pkcs11_acquire_certificate_callback_data data = {
+ _cleanup_(pkcs11_acquire_public_key_callback_data_release) struct pkcs11_acquire_public_key_callback_data data = {
.askpw_friendly_name = askpw_friendly_name,
- .askpw_icon_name = askpw_icon_name,
+ .askpw_icon = askpw_icon,
+ .askpw_credential = askpw_credential,
+ .askpw_flags = askpw_flags,
};
int r;
assert(uri);
- assert(ret_cert);
+ assert(ret_pkey);
- r = pkcs11_find_token(uri, pkcs11_acquire_certificate_callback, &data);
+ r = pkcs11_find_token(uri, pkcs11_acquire_public_key_callback, &data);
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"Specified PKCS#11 token with URI '%s' not found.",
@@ -1146,11 +1866,9 @@ int pkcs11_acquire_certificate(
if (r < 0)
return r;
- *ret_cert = TAKE_PTR(data.cert);
-
+ *ret_pkey = TAKE_PTR(data.pkey);
if (ret_pin_used)
*ret_pin_used = TAKE_PTR(data.pin_used);
-
return 0;
}
#endif
@@ -1229,7 +1947,7 @@ int pkcs11_list_tokens(void) {
if (r < 0 && r != -EAGAIN)
return r;
- if (table_get_rows(t) <= 1) {
+ if (table_isempty(t)) {
log_info("No suitable PKCS#11 tokens found.");
return 0;
}
@@ -1338,10 +2056,9 @@ int pkcs11_crypt_device_callback(
data->friendly_name,
"drive-harddisk",
"pkcs11-pin",
- "cryptsetup.pkcs11-pin",
+ data->askpw_credential,
data->until,
data->askpw_flags,
- data->headless,
NULL);
if (r < 0)
return r;
diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h
index 5bc23c1..23ab823 100644
--- a/src/shared/pkcs11-util.h
+++ b/src/shared/pkcs11-util.h
@@ -1,6 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#if HAVE_OPENSSL
+# include <openssl/evp.h>
+# include <openssl/x509.h>
+#endif
#include <stdbool.h>
#if HAVE_P11KIT
@@ -9,28 +13,29 @@
#endif
#include "ask-password-api.h"
+#include "dlfcn-util.h"
#include "macro.h"
-#include "openssl-util.h"
#include "time-util.h"
bool pkcs11_uri_valid(const char *uri);
#if HAVE_P11KIT
-
-extern char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module);
-extern void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules);
-extern CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags);
-extern const char *(*sym_p11_kit_strerror)(CK_RV rv);
-extern int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string);
-extern void (*sym_p11_kit_uri_free)(P11KitUri *uri);
-extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs);
-extern CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri);
-extern CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri);
-extern CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri);
-extern int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info);
-extern const char *(*sym_p11_kit_uri_message)(int code);
-extern P11KitUri *(*sym_p11_kit_uri_new)(void);
-extern int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri);
+DLSYM_PROTOTYPE(p11_kit_module_get_name);
+DLSYM_PROTOTYPE(p11_kit_modules_finalize_and_release);
+DLSYM_PROTOTYPE(p11_kit_modules_load_and_initialize);
+DLSYM_PROTOTYPE(p11_kit_strerror);
+DLSYM_PROTOTYPE(p11_kit_uri_format);
+DLSYM_PROTOTYPE(p11_kit_uri_free);
+DLSYM_PROTOTYPE(p11_kit_uri_get_attributes);
+DLSYM_PROTOTYPE(p11_kit_uri_get_attribute);
+DLSYM_PROTOTYPE(p11_kit_uri_set_attribute);
+DLSYM_PROTOTYPE(p11_kit_uri_get_module_info);
+DLSYM_PROTOTYPE(p11_kit_uri_get_slot_info);
+DLSYM_PROTOTYPE(p11_kit_uri_get_token_info);
+DLSYM_PROTOTYPE(p11_kit_uri_match_token_info);
+DLSYM_PROTOTYPE(p11_kit_uri_message);
+DLSYM_PROTOTYPE(p11_kit_uri_new);
+DLSYM_PROTOTYPE(p11_kit_uri_parse);
int uri_from_string(const char *p, P11KitUri **ret);
@@ -48,10 +53,12 @@ char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info);
int pkcs11_token_login_by_pin(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, const CK_TOKEN_INFO *token_info, const char *token_label, const void *pin, size_t pin_size);
-int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_used_pin);
+int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, char **ret_used_pin);
+int pkcs11_token_find_related_object(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE prototype, CK_OBJECT_CLASS class, CK_OBJECT_HANDLE *ret_object);
int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object);
#if HAVE_OPENSSL
+int pkcs11_token_read_public_key(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, EVP_PKEY **ret_pkey);
int pkcs11_token_read_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert);
#endif
@@ -64,7 +71,7 @@ typedef int (*pkcs11_find_token_callback_t)(CK_FUNCTION_LIST *m, CK_SESSION_HAND
int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata);
#if HAVE_OPENSSL
-int pkcs11_acquire_certificate(const char *uri, const char *askpw_friendly_name, const char *askpw_icon_name, X509 **ret_cert, char **ret_pin_used);
+int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, char **ret_pin_used);
#endif
typedef struct {
@@ -75,7 +82,7 @@ typedef struct {
void *decrypted_key;
size_t decrypted_key_size;
bool free_encrypted_key;
- bool headless;
+ const char *askpw_credential;
AskPasswordFlags askpw_flags;
} pkcs11_crypt_device_callback_data;
@@ -103,7 +110,7 @@ static inline int dlopen_p11kit(void) {
typedef struct {
const char *friendly_name;
usec_t until;
- bool headless;
+ const char *askpw_credential;
AskPasswordFlags askpw_flags;
} systemd_pkcs11_plugin_params;
diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c
index 2833063..c75f74a 100644
--- a/src/shared/pretty-print.c
+++ b/src/shared/pretty-print.c
@@ -5,6 +5,7 @@
#include <stdio.h>
#include "alloc-util.h"
+#include "color-util.h"
#include "conf-files.h"
#include "constants.h"
#include "env-util.h"
@@ -140,16 +141,8 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) {
if (isempty(text))
text = path;
- if (!urlify_enabled()) {
- char *n;
-
- n = strdup(text);
- if (!n)
- return -ENOMEM;
-
- *ret = n;
- return 0;
- }
+ if (!urlify_enabled())
+ return strdup_to(ret, text);
r = file_url_from_path(path, &url);
if (r < 0)
@@ -213,7 +206,7 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) {
break;
LineType line_type = classify_line_type(line, flags);
- if (flags & CAT_TLDR) {
+ if (FLAGS_SET(flags, CAT_TLDR)) {
if (line_type == LINE_SECTION) {
/* The start of a section, let's not print it yet. */
free_and_replace(section, line);
@@ -236,6 +229,28 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) {
}
}
+ /* Highlight the left side (directive) of a Foo=bar assignment */
+ if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) {
+ const char *p = strchr(line, '=');
+ if (p) {
+ _cleanup_free_ char *highlighted = NULL, *directive = NULL;
+
+ directive = strndup(line, p - line);
+ if (!directive)
+ return log_oom();
+
+ highlighted = strjoin(ansi_highlight_green(),
+ directive,
+ "=",
+ ansi_normal(),
+ p + 1);
+ if (!highlighted)
+ return log_oom();
+
+ free_and_replace(line, highlighted);
+ }
+ }
+
printf("%s%s%s\n",
line_type == LINE_SECTION ? ansi_highlight_cyan() :
line_type == LINE_COMMENT ? ansi_highlight_grey() :
@@ -271,14 +286,12 @@ void print_separator(void) {
* one line filled with spaces with ANSI underline set, followed by a second (empty) line. */
if (underline_enabled()) {
- size_t i, c;
-
- c = columns();
+ size_t c = columns();
flockfile(stdout);
fputs_unlocked(ANSI_UNDERLINE, stdout);
- for (i = 0; i < c; i++)
+ for (size_t i = 0; i < c; i++)
fputc_unlocked(' ', stdout);
fputs_unlocked(ANSI_NORMAL "\n\n", stdout);
@@ -287,7 +300,7 @@ void print_separator(void) {
fputs("\n\n", stdout);
}
-static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) {
+static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) {
/* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/,
* i.e. a collection of directories without a main config file.
* Incidentally, all those formats don't use sections. So we return a single
@@ -295,11 +308,10 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection,
*/
_cleanup_free_ char *n = NULL;
- bool usr = false, run = false, coll = false;
+ bool run = false, coll = false;
const char *ext = ".conf";
/* This is static so that the array doesn't get deallocated when we exit the function */
static const char* const std_prefixes[] = { CONF_PATHS(""), NULL };
- static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL };
static const char* const run_prefixes[] = { "/run/", NULL };
if (path_equal(*name, "environment.d"))
@@ -311,50 +323,33 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection,
if (!n)
return log_oom();
- /* All systemd-style config files should support the /usr-/etc-/run split and
- * dropins. Let's add a blanket rule that allows us to support them without keeping
- * an explicit list. */
- if (path_startswith(n, "systemd") && endswith(n, ".conf"))
- usr = true;
-
delete_trailing_chars(n, "/");
+ /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */
+
if (endswith(n, ".d"))
coll = true;
- if (path_equal(n, "environment"))
- usr = true;
-
if (path_equal(n, "udev/hwdb.d"))
ext = ".hwdb";
-
- if (path_equal(n, "udev/rules.d"))
+ else if (path_equal(n, "udev/rules.d"))
ext = ".rules";
-
- if (path_equal(n, "kernel/install.d"))
+ else if (path_equal(n, "kernel/install.d"))
ext = ".install";
-
- if (path_equal(n, "systemd/ntp-units.d")) {
+ else if (path_equal(n, "systemd/ntp-units.d")) {
coll = true;
ext = ".list";
- }
-
- if (path_equal(n, "systemd/relabel-extra.d")) {
+ } else if (path_equal(n, "systemd/relabel-extra.d")) {
coll = run = true;
ext = ".relabel";
- }
-
- if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) {
+ } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) {
coll = true;
ext = ".preset";
}
- if (path_equal(n, "systemd/user-preset"))
- usr = true;
-
- *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes);
- *is_collection = coll;
- *extension = ext;
+ *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes);
+ *ret_is_collection = coll;
+ *ret_extension = ext;
return 0;
}
@@ -419,3 +414,116 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) {
return cat_files(path, files, flags);
}
+
+int terminal_tint_color(double hue, char **ret) {
+ double red, green, blue;
+ int r;
+
+ assert(ret);
+
+ r = get_default_background_color(&red, &green, &blue);
+ if (r < 0)
+ return log_debug_errno(r, "Unable to get terminal background color: %m");
+
+ double s, v;
+ rgb_to_hsv(red, green, blue, /* h= */ NULL, &s, &v);
+
+ if (v > 50) /* If the background is bright, then pull down saturation */
+ s = 25;
+ else /* otherwise pump it up */
+ s = 75;
+
+ v = MAX(20, v); /* Make sure we don't hide the color in black */
+
+ uint8_t r8, g8, b8;
+ hsv_to_rgb(hue, s, v, &r8, &g8, &b8);
+
+ if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+bool shall_tint_background(void) {
+ static int cache = -1;
+
+ if (cache >= 0)
+ return cache;
+
+ cache = getenv_bool("SYSTEMD_TINT_BACKGROUND");
+ if (cache == -ENXIO)
+ return (cache = true);
+ if (cache < 0)
+ log_debug_errno(cache, "Failed to parse $SYSTEMD_TINT_BACKGROUND, leaving background tinting enabled: %m");
+
+ return cache != 0;
+}
+
+void draw_progress_bar(const char *prefix, double percentage) {
+
+ fputc('\r', stderr);
+ if (prefix)
+ fputs(prefix, stderr);
+
+ if (!terminal_is_dumb()) {
+ size_t cols = columns();
+ size_t prefix_length = strlen_ptr(prefix);
+ size_t length = cols > prefix_length + 6 ? cols - prefix_length - 6 : 0;
+
+ if (length > 5 && percentage >= 0.0 && percentage <= 100.0) {
+ size_t p = (size_t) (length * percentage / 100.0);
+ bool separator_done = false;
+
+ fputs(ansi_highlight_green(), stderr);
+
+ for (size_t i = 0; i < length; i++) {
+
+ if (i <= p) {
+ if (get_color_mode() == COLOR_24BIT) {
+ uint8_t r8, g8, b8;
+ double z = i == 0 ? 0 : (((double) i / p) * 100);
+ hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8);
+ fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8);
+ }
+
+ fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT), stderr);
+ } else if (i+1 < length && !separator_done) {
+ fputs(ansi_normal(), stderr);
+ fputc(' ', stderr);
+ separator_done = true;
+ fputs(ansi_grey(), stderr);
+ } else
+ fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED), stderr);
+ }
+
+ fputs(ansi_normal(), stderr);
+ fputc(' ', stderr);
+ }
+ }
+
+ fprintf(stderr,
+ "%s%3.0f%%%s",
+ ansi_highlight(),
+ percentage,
+ ansi_normal());
+
+ if (!terminal_is_dumb())
+ fputs(ANSI_ERASE_TO_END_OF_LINE, stderr);
+
+ fputc('\r', stderr);
+ fflush(stderr);
+}
+
+void clear_progress_bar(const char *prefix) {
+
+ fputc('\r', stderr);
+
+ if (terminal_is_dumb())
+ fputs(strrepa(" ", strlen_ptr(prefix) + 4), /* 4: %3.0f%% */
+ stderr);
+ else
+ fputs(ANSI_ERASE_TO_END_OF_LINE, stderr);
+
+ fputc('\r', stderr);
+ fflush(stderr);
+}
diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h
index c17e976..c166054 100644
--- a/src/shared/pretty-print.h
+++ b/src/shared/pretty-print.h
@@ -47,3 +47,10 @@ static inline const char *green_check_mark_internal(char buffer[static GREEN_CHE
#define GREEN_CHECK_MARK() green_check_mark_internal((char[GREEN_CHECK_MARK_MAX]) {})
#define COLOR_MARK_BOOL(b) ((b) ? GREEN_CHECK_MARK() : RED_CROSS_MARK())
+
+int terminal_tint_color(double hue, char **ret);
+
+bool shall_tint_background(void);
+
+void draw_progress_bar(const char *prefix, double percentage);
+void clear_progress_bar(const char *prefix);
diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c
index 195e603..998ce96 100644
--- a/src/shared/ptyfwd.c
+++ b/src/shared/ptyfwd.c
@@ -18,13 +18,28 @@
#include "alloc-util.h"
#include "errno-util.h"
+#include "extract-word.h"
#include "fd-util.h"
+#include "io-util.h"
#include "log.h"
#include "macro.h"
#include "ptyfwd.h"
+#include "stat-util.h"
+#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
+typedef enum AnsiColorState {
+ ANSI_COLOR_STATE_TEXT,
+ ANSI_COLOR_STATE_ESC,
+ ANSI_COLOR_STATE_CSI_SEQUENCE,
+ ANSI_COLOR_STATE_OSC_SEQUENCE,
+ ANSI_COLOR_STATE_NEWLINE,
+ ANSI_COLOR_STATE_CARRIAGE_RETURN,
+ _ANSI_COLOR_STATE_MAX,
+ _ANSI_COLOR_STATE_INVALID = -EINVAL,
+} AnsiColorState;
+
struct PTYForward {
sd_event *event;
@@ -65,7 +80,8 @@ struct PTYForward {
bool last_char_set:1;
char last_char;
- char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
+ char in_buffer[LINE_MAX], *out_buffer;
+ size_t out_buffer_size;
size_t in_buffer_full, out_buffer_full;
usec_t escape_timestamp;
@@ -73,6 +89,14 @@ struct PTYForward {
PTYForwardHandler handler;
void *userdata;
+
+ char *background_color;
+ AnsiColorState ansi_color_state;
+ char *csi_sequence;
+ char *osc_sequence;
+
+ char *title; /* Window title to show by default */
+ char *title_prefix; /* If terminal client overrides window title, prefix this string */
};
#define ESCAPE_USEC (1*USEC_PER_SEC)
@@ -95,6 +119,14 @@ static void pty_forward_disconnect(PTYForward *f) {
/* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
(void) fd_nonblock(f->output_fd, false);
+
+ if (colors_enabled()) {
+ (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX);
+
+ if (f->title)
+ (void) loop_write(f->output_fd, ANSI_WINDOW_TITLE_POP, SIZE_MAX);
+ }
+
if (f->close_output_fd)
f->output_fd = safe_close(f->output_fd);
}
@@ -109,6 +141,15 @@ static void pty_forward_disconnect(PTYForward *f) {
}
f->saved_stdout = f->saved_stdin = false;
+
+ f->out_buffer = mfree(f->out_buffer);
+ f->out_buffer_size = 0;
+ f->out_buffer_full = 0;
+ f->in_buffer_full = 0;
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ f->osc_sequence = mfree(f->osc_sequence);
+ f->ansi_color_state = _ANSI_COLOR_STATE_INVALID;
}
static int pty_forward_done(PTYForward *f, int rcode) {
@@ -142,7 +183,7 @@ static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
if (*p == 0x1D) {
usec_t nw = now(CLOCK_MONOTONIC);
- if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
+ if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
f->escape_timestamp = nw;
f->escape_counter = 1;
} else {
@@ -196,11 +237,321 @@ static bool drained(PTYForward *f) {
return true;
}
-static int shovel(PTYForward *f) {
+static char *background_color_sequence(PTYForward *f) {
+ assert(f);
+ assert(f->background_color);
+
+ return strjoin("\x1B[", f->background_color, "m");
+}
+
+static int insert_string(PTYForward *f, size_t offset, const char *s) {
+ assert(f);
+ assert(offset <= f->out_buffer_full);
+ assert(s);
+
+ size_t l = strlen(s);
+ assert(l <= INT_MAX); /* Make sure we can still return this */
+
+ void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX));
+ if (!p)
+ return -ENOMEM;
+
+ f->out_buffer = p;
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
+
+ memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset);
+ memcpy(f->out_buffer + offset, s, l);
+ f->out_buffer_full += l;
+
+ return (int) l;
+}
+
+static int insert_background_color(PTYForward *f, size_t offset) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(f);
+
+ if (!f->background_color)
+ return 0;
+
+ s = background_color_sequence(f);
+ if (!s)
+ return -ENOMEM;
+
+ return insert_string(f, offset, s);
+}
+
+static int is_csi_background_reset_sequence(const char *seq) {
+ enum {
+ COLOR_TOKEN_NO,
+ COLOR_TOKEN_START,
+ COLOR_TOKEN_8BIT,
+ COLOR_TOKEN_24BIT_R,
+ COLOR_TOKEN_24BIT_G,
+ COLOR_TOKEN_24BIT_B,
+ } token_state = COLOR_TOKEN_NO;
+
+ bool b = false;
+ int r;
+
+ assert(seq);
+
+ /* This parses CSI "m" sequences, and determines if they reset the background color. If so returns
+ * 1. This can then be used to insert another sequence that sets the color to the desired
+ * replacement. */
+
+ for (;;) {
+ _cleanup_free_ char *token = NULL;
+
+ r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ switch (token_state) {
+
+ case COLOR_TOKEN_NO:
+
+ if (STR_IN_SET(token, "", "0", "00", "49"))
+ b = true; /* These tokens set the background back to normal */
+ else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48"))
+ b = false; /* And these tokens set them to something other than normal */
+
+ if (STR_IN_SET(token, "38", "48", "58"))
+ token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */
+ break;
+
+ case COLOR_TOKEN_START:
+
+ if (STR_IN_SET(token, "5", "05"))
+ token_state = COLOR_TOKEN_8BIT; /* 8bit color */
+ else if (STR_IN_SET(token, "2", "02"))
+ token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */
+ else
+ token_state = COLOR_TOKEN_NO; /* something weird? */
+ break;
+
+ case COLOR_TOKEN_24BIT_R:
+ token_state = COLOR_TOKEN_24BIT_G;
+ break;
+
+ case COLOR_TOKEN_24BIT_G:
+ token_state = COLOR_TOKEN_24BIT_B;
+ break;
+
+ case COLOR_TOKEN_8BIT:
+ case COLOR_TOKEN_24BIT_B:
+ token_state = COLOR_TOKEN_NO;
+ break;
+ }
+ }
+
+ return b;
+}
+
+static int insert_background_fix(PTYForward *f, size_t offset) {
+ assert(f);
+
+ if (!f->background_color)
+ return 0;
+
+ if (!is_csi_background_reset_sequence(strempty(f->csi_sequence)))
+ return 0;
+
+ _cleanup_free_ char *s = NULL;
+ s = strjoin(";", f->background_color);
+ if (!s)
+ return -ENOMEM;
+
+ return insert_string(f, offset, s);
+}
+
+static int insert_window_title_fix(PTYForward *f, size_t offset) {
+ assert(f);
+
+ if (!f->title_prefix)
+ return 0;
+
+ if (!f->osc_sequence)
+ return 0;
+
+ const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/
+ if (!t)
+ return 0;
+
+ _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a");
+ if (!joined)
+ return -ENOMEM;
+
+ return insert_string(f, offset, joined);
+}
+
+static int pty_forward_ansi_process(PTYForward *f, size_t offset) {
+ int r;
+
+ assert(f);
+ assert(offset <= f->out_buffer_full);
+
+ if (!f->background_color && !f->title_prefix)
+ return 0;
+
+ if (FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL))
+ return 0;
+
+ for (size_t i = offset; i < f->out_buffer_full; i++) {
+ char c = f->out_buffer[i];
+
+ switch (f->ansi_color_state) {
+
+ case ANSI_COLOR_STATE_TEXT:
+ break;
+
+ case ANSI_COLOR_STATE_NEWLINE:
+ case ANSI_COLOR_STATE_CARRIAGE_RETURN:
+ /* Immediately after a newline (ASCII 10) or carriage return (ASCII 13) insert an
+ * ANSI sequence set the background color back. */
+
+ r = insert_background_color(f, i);
+ if (r < 0)
+ return r;
+
+ i += r;
+ break;
+
+ case ANSI_COLOR_STATE_ESC:
+
+ if (c == '[') {
+ f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
+ continue;
+ } else if (c == ']') {
+ f->ansi_color_state = ANSI_COLOR_STATE_OSC_SEQUENCE;
+ continue;
+ }
+ break;
+
+ case ANSI_COLOR_STATE_CSI_SEQUENCE:
+
+ if (c >= 0x20 && c <= 0x3F) {
+ /* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and
+ * 0x30…0x3F) then we are still in the CSI sequence */
+
+ if (strlen_ptr(f->csi_sequence) >= 64) {
+ /* Safety check: lets not accept unbounded CSI sequences */
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ break;
+ } else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
+ return -ENOMEM;
+ } else {
+ /* Otherwise, the CSI sequence is over */
+
+ if (c == 'm') {
+ /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
+ r = insert_background_fix(f, i);
+ if (r < 0)
+ return r;
+
+ i += r;
+ }
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+ continue;
+
+ case ANSI_COLOR_STATE_OSC_SEQUENCE:
+
+ if ((uint8_t) c >= ' ') {
+ if (strlen_ptr(f->osc_sequence) >= 64) {
+ /* Safety check: lets not accept unbounded OSC sequences */
+ f->osc_sequence = mfree(f->osc_sequence);
+ break;
+ } else if (!strextend(&f->osc_sequence, CHAR_TO_STR(c)))
+ return -ENOMEM;
+ } else {
+ /* Otherwise, the OSC sequence is over
+ *
+ * There are two allowed ways to end an OSC sequence:
+ * BEL '\x07'
+ * String Terminator (ST): <Esc>\ - "\x1b\x5c"
+ * since we cannot lookahead to see if the Esc is followed by a \
+ * we cut a corner here and assume it will be \. */
+
+ if (IN_SET(c, '\x07', '\x1b')) {
+ r = insert_window_title_fix(f, i+1);
+ if (r < 0)
+ return r;
+
+ i += r;
+ }
+
+ f->osc_sequence = mfree(f->osc_sequence);
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+ continue;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (c == '\n')
+ f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
+ else if (c == '\r')
+ f->ansi_color_state = ANSI_COLOR_STATE_CARRIAGE_RETURN;
+ else if (c == 0x1B) /* ESC */
+ f->ansi_color_state = ANSI_COLOR_STATE_ESC;
+ else
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+
+ return 0;
+}
+
+static int do_shovel(PTYForward *f) {
ssize_t k;
+ int r;
assert(f);
+ if (f->out_buffer_size == 0 && !FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) {
+ /* If the output hasn't been allocated yet, we are at the beginning of the first
+ * shovelling. Hence, possibly send some initial ANSI sequences. But do so only if we are
+ * talking to an actual TTY. */
+
+ if (f->background_color) {
+ /* Erase the first line when we start */
+ f->out_buffer = background_color_sequence(f);
+ if (!f->out_buffer)
+ return log_oom();
+
+ if (!strextend(&f->out_buffer, ANSI_ERASE_TO_END_OF_LINE))
+ return log_oom();
+ }
+
+ if (f->title) {
+ if (!strextend(&f->out_buffer,
+ ANSI_WINDOW_TITLE_PUSH
+ "\x1b]2;", f->title, "\a"))
+ return log_oom();
+ }
+
+ if (f->out_buffer) {
+ f->out_buffer_full = strlen(f->out_buffer);
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
+ }
+ }
+
+ if (f->out_buffer_size < LINE_MAX) {
+ /* Make sure we always have room for at least one "line" */
+ void *p = realloc(f->out_buffer, LINE_MAX);
+ if (!p)
+ return log_oom();
+
+ f->out_buffer = p;
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
+ }
+
while ((f->stdin_readable && f->in_buffer_full <= 0) ||
(f->master_writable && f->in_buffer_full > 0) ||
(f->master_readable && f->out_buffer_full <= 0) ||
@@ -218,21 +569,19 @@ static int shovel(PTYForward *f) {
f->stdin_hangup = true;
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
- } else {
- log_error_errno(errno, "read(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "Failed to read from pty input fd: %m");
} else if (k == 0) {
/* EOF on stdin */
f->stdin_readable = false;
f->stdin_hangup = true;
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
- } else {
+ } else {
/* Check if ^] has been pressed three times within one second. If we get this we quite
* immediately. */
if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
- return pty_forward_done(f, -ECANCELED);
+ return -ECANCELED;
f->in_buffer_full += (size_t) k;
}
@@ -250,10 +599,8 @@ static int shovel(PTYForward *f) {
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
- } else {
- log_error_errno(errno, "write(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "write(): %m");
} else {
assert(f->in_buffer_full >= (size_t) k);
memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
@@ -261,17 +608,14 @@ static int shovel(PTYForward *f) {
}
}
- if (f->master_readable && f->out_buffer_full < LINE_MAX) {
+ if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) {
- k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
+ k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full);
if (k < 0) {
- /* Note that EIO on the master device
- * might be caused by vhangup() or
- * temporary closing of everything on
- * the other side, we treat it like
- * EAGAIN here and try again, unless
- * ignore_vhangup is off. */
+ /* Note that EIO on the master device might be caused by vhangup() or
+ * temporary closing of everything on the other side, we treat it like EAGAIN
+ * here and try again, unless ignore_vhangup is off. */
if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
f->master_readable = false;
@@ -280,13 +624,16 @@ static int shovel(PTYForward *f) {
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
- } else {
- log_error_errno(errno, "read(): %m");
- return pty_forward_done(f, -errno);
- }
- } else {
+ } else
+ return log_error_errno(errno, "Failed to read from pty master fd: %m");
+ } else {
f->read_from_master = true;
+ size_t scan_index = f->out_buffer_full;
f->out_buffer_full += (size_t) k;
+
+ r = pty_forward_ansi_process(f, scan_index);
+ if (r < 0)
+ return log_error_errno(r, "Failed to scan for ANSI sequences: %m");
}
}
@@ -301,10 +648,8 @@ static int shovel(PTYForward *f) {
f->stdout_writable = false;
f->stdout_hangup = true;
f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
- } else {
- log_error_errno(errno, "write(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "Failed to write to pty output fd: %m");
} else {
@@ -337,6 +682,18 @@ static int shovel(PTYForward *f) {
return 0;
}
+static int shovel(PTYForward *f) {
+ int r;
+
+ assert(f);
+
+ r = do_shovel(f);
+ if (r < 0)
+ return pty_forward_done(f, r);
+
+ return r;
+}
+
static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
PTYForward *f = ASSERT_PTR(userdata);
@@ -406,11 +763,14 @@ int pty_forward_new(
struct winsize ws;
int r;
+ assert(master >= 0);
+ assert(ret);
+
f = new(PTYForward, 1);
if (!f)
return -ENOMEM;
- *f = (struct PTYForward) {
+ *f = (PTYForward) {
.flags = flags,
.master = -EBADF,
.input_fd = -EBADF,
@@ -430,13 +790,17 @@ int pty_forward_new(
else {
/* If we shall be invoked in interactive mode, let's switch on non-blocking mode, so that we
* never end up staving one direction while we block on the other. However, let's be careful
- * here and not turn on O_NONBLOCK for stdin/stdout directly, but of re-opened copies of
+ * here and not turn on O_NONBLOCK for stdin/stdout directly, but of reopened copies of
* them. This has two advantages: when we are killed abruptly the stdin/stdout fds won't be
* left in O_NONBLOCK state for the next process using them. In addition, if some process
* running in the background wants to continue writing to our stdout it can do so without
- * being confused by O_NONBLOCK. */
+ * being confused by O_NONBLOCK.
+ * We keep O_APPEND (if present) on the output FD and (try to) keep current file position on
+ * both input and output FD (principle of least surprise).
+ */
- f->input_fd = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ f->input_fd = fd_reopen_propagate_append_and_position(
+ STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (f->input_fd < 0) {
/* Handle failures gracefully, after all certain fd types cannot be reopened
* (sockets, …) */
@@ -450,7 +814,8 @@ int pty_forward_new(
} else
f->close_input_fd = true;
- f->output_fd = fd_reopen(STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ f->output_fd = fd_reopen_propagate_append_and_position(
+ STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (f->output_fd < 0) {
log_debug_errno(f->output_fd, "Failed to reopen stdout, using original fd: %m");
@@ -469,6 +834,10 @@ int pty_forward_new(
f->master = master;
+ /* Disable color/window title setting unless we talk to a good TTY */
+ if (!isatty_safe(f->output_fd) || get_color_mode() == COLOR_OFF)
+ f->flags |= PTY_FORWARD_DUMB_TERMINAL;
+
if (ioctl(f->output_fd, TIOCGWINSZ, &ws) < 0)
/* If we can't get the resolution from the output fd, then use our internal, regular width/height,
* i.e. something derived from $COLUMNS and $LINES if set. */
@@ -479,9 +848,16 @@ int pty_forward_new(
(void) ioctl(master, TIOCSWINSZ, &ws);
- if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) {
+ bool same;
+
assert(f->input_fd >= 0);
+ r = fd_inode_same(f->input_fd, f->output_fd);
+ if (r < 0)
+ return r;
+ same = r > 0;
+
if (tcgetattr(f->input_fd, &f->saved_stdin_attr) >= 0) {
struct termios raw_stdin_attr;
@@ -489,11 +865,14 @@ int pty_forward_new(
raw_stdin_attr = f->saved_stdin_attr;
cfmakeraw(&raw_stdin_attr);
- raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
- tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr);
+
+ if (!same)
+ raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
+
+ (void) tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr);
}
- if (tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) {
+ if (!same && tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) {
struct termios raw_stdout_attr;
f->saved_stdout = true;
@@ -502,7 +881,7 @@ int pty_forward_new(
cfmakeraw(&raw_stdout_attr);
raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
- tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr);
+ (void) tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr);
}
r = sd_event_add_io(f->event, &f->stdin_event_source, f->input_fd, EPOLLIN|EPOLLET, on_stdin_event, f);
@@ -540,7 +919,14 @@ int pty_forward_new(
}
PTYForward *pty_forward_free(PTYForward *f) {
+ if (!f)
+ return NULL;
+
pty_forward_disconnect(f);
+ free(f->background_color);
+ free(f->title);
+ free(f->title_prefix);
+
return mfree(f);
}
@@ -560,15 +946,14 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
assert(f);
- if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
+ if (FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP) == b)
return 0;
SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
if (!ignore_vhangup(f)) {
- /* We shall now react to vhangup()s? Let's check
- * immediately if we might be in one */
+ /* We shall now react to vhangup()s? Let's check immediately if we might be in one. */
f->master_readable = true;
r = shovel(f);
@@ -582,7 +967,7 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
bool pty_forward_get_ignore_vhangup(PTYForward *f) {
assert(f);
- return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
+ return FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP);
}
bool pty_forward_is_done(PTYForward *f) {
@@ -614,6 +999,7 @@ bool pty_forward_drain(PTYForward *f) {
int pty_forward_set_priority(PTYForward *f, int64_t priority) {
int r;
+
assert(f);
if (f->stdin_event_source) {
@@ -675,3 +1061,45 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height)
return 0;
}
+
+int pty_forward_set_background_color(PTYForward *f, const char *color) {
+ assert(f);
+
+ return free_and_strdup(&f->background_color, color);
+}
+
+int pty_forward_set_title(PTYForward *f, const char *title) {
+ assert(f);
+
+ /* Refuse accepting a title when we already started shoveling */
+ if (f->out_buffer_size > 0)
+ return -EBUSY;
+
+ return free_and_strdup(&f->title, title);
+}
+
+int pty_forward_set_titlef(PTYForward *f, const char *format, ...) {
+ _cleanup_free_ char *title = NULL;
+ va_list ap;
+ int r;
+
+ assert(f);
+ assert(format);
+
+ if (f->out_buffer_size > 0)
+ return -EBUSY;
+
+ va_start(ap, format);
+ r = vasprintf(&title, format, ap);
+ va_end(ap);
+ if (r < 0)
+ return -ENOMEM;
+
+ return free_and_replace(f->title, title);
+}
+
+int pty_forward_set_title_prefix(PTYForward *f, const char *title_prefix) {
+ assert(f);
+
+ return free_and_strdup(&f->title_prefix, title_prefix);
+}
diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h
index f0ae6e9..248646d 100644
--- a/src/shared/ptyfwd.h
+++ b/src/shared/ptyfwd.h
@@ -10,13 +10,17 @@
typedef struct PTYForward PTYForward;
typedef enum PTYForwardFlags {
- PTY_FORWARD_READ_ONLY = 1,
+ /* Only output to STDOUT, never try to read from STDIN */
+ PTY_FORWARD_READ_ONLY = 1 << 0,
/* Continue reading after hangup? */
- PTY_FORWARD_IGNORE_VHANGUP = 2,
+ PTY_FORWARD_IGNORE_VHANGUP = 1 << 1,
/* Continue reading after hangup but only if we never read anything else? */
- PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4,
+ PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 1 << 2,
+
+ /* Don't tint the background, or set window title */
+ PTY_FORWARD_DUMB_TERMINAL = 1 << 3,
} PTYForwardFlags;
typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void *userdata);
@@ -39,4 +43,11 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority);
int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height);
+int pty_forward_set_background_color(PTYForward *f, const char *color);
+
+int pty_forward_set_title(PTYForward *f, const char *title);
+int pty_forward_set_titlef(PTYForward *f, const char *format, ...) _printf_(2,3);
+
+int pty_forward_set_title_prefix(PTYForward *f, const char *prefix);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);
diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c
index b0dd90a..e62a5a8 100644
--- a/src/shared/qrcode-util.c
+++ b/src/shared/qrcode-util.c
@@ -18,12 +18,17 @@
static void *qrcode_dl = NULL;
-static QRcode* (*sym_QRcode_encodeString)(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) = NULL;
-static void (*sym_QRcode_free)(QRcode *qrcode) = NULL;
+static DLSYM_FUNCTION(QRcode_encodeString);
+static DLSYM_FUNCTION(QRcode_free);
int dlopen_qrencode(void) {
int r;
+ ELF_NOTE_DLOPEN("qrencode",
+ "Support for generating QR codes",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libqrencode.so.4", "libqrencode.so.3");
+
FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") {
r = dlopen_many_sym_or_warn(
&qrcode_dl, s, LOG_DEBUG,
diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c
index 62ff697..b1c1aa7 100644
--- a/src/shared/reboot-util.c
+++ b/src/shared/reboot-util.c
@@ -23,8 +23,15 @@
#include "reboot-util.h"
#include "string-util.h"
#include "umask-util.h"
+#include "utf8.h"
#include "virt.h"
+bool reboot_parameter_is_valid(const char *parameter) {
+ assert(parameter);
+
+ return ascii_is_valid(parameter) && strlen(parameter) <= NAME_MAX;
+}
+
int update_reboot_parameter_and_warn(const char *parameter, bool keep) {
int r;
@@ -42,6 +49,9 @@ int update_reboot_parameter_and_warn(const char *parameter, bool keep) {
return 0;
}
+ if (!reboot_parameter_is_valid(parameter))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid reboot parameter '%s'.", parameter);
+
WITH_UMASK(0022) {
r = write_string_file("/run/systemd/reboot-param", parameter,
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h
index ccd15c7..2ac478f 100644
--- a/src/shared/reboot-util.h
+++ b/src/shared/reboot-util.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+bool reboot_parameter_is_valid(const char *parameter);
int update_reboot_parameter_and_warn(const char *parameter, bool keep);
typedef enum RebootFlags {
diff --git a/src/shared/resize-fs.c b/src/shared/resize-fs.c
index 178aefa..b1c4a49 100644
--- a/src/shared/resize-fs.c
+++ b/src/shared/resize-fs.c
@@ -61,7 +61,7 @@ int resize_fs(int fd, uint64_t sz, uint64_t *ret_size) {
if (ret_size)
*ret_size = sz;
- } else if (is_fs_type(&sfs, XFS_SB_MAGIC)) {
+ } else if (is_fs_type(&sfs, XFS_SUPER_MAGIC)) {
xfs_fsop_geom_t geo;
xfs_growfs_data_t d;
@@ -95,7 +95,7 @@ uint64_t minimal_size_by_fs_magic(statfs_f_type_t magic) {
case (statfs_f_type_t) EXT4_SUPER_MAGIC:
return EXT4_MINIMAL_SIZE;
- case (statfs_f_type_t) XFS_SB_MAGIC:
+ case (statfs_f_type_t) XFS_SUPER_MAGIC:
return XFS_MINIMAL_SIZE;
case (statfs_f_type_t) BTRFS_SUPER_MAGIC:
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 4664215..767db48 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -326,7 +326,7 @@ static int rm_rf_children_impl(
dirname = mfree(dirname);
/* And now let's back out one level up */
- n_todo --;
+ n_todo--;
d = TAKE_PTR(todos[n_todo].dir);
dirname = TAKE_PTR(todos[n_todo].dirname);
old_mode = todos[n_todo].old_mode;
diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c
index 00a8ced..2469e24 100644
--- a/src/shared/seccomp-util.c
+++ b/src/shared/seccomp-util.c
@@ -298,7 +298,7 @@ bool is_seccomp_available(void) {
if (cached_enabled < 0) {
int b;
- b = getenv_bool_secure("SYSTEMD_SECCOMP");
+ b = secure_getenv_bool("SYSTEMD_SECCOMP");
if (b != 0) {
if (b < 0 && b != -ENXIO) /* ENXIO: env var unset */
log_debug_errno(b, "Failed to parse $SYSTEMD_SECCOMP value, ignoring.");
diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c
index 2fef29c..d2b1a3e 100644
--- a/src/shared/selinux-util.c
+++ b/src/shared/selinux-util.c
@@ -39,8 +39,6 @@ typedef enum Initialized {
LAZY_INITIALIZED,
} Initialized;
-static int mac_selinux_reload(int seqno);
-
static int cached_use = -1;
static Initialized initialized = UNINITIALIZED;
static int last_policyload = 0;
@@ -214,6 +212,16 @@ int mac_selinux_init_lazy(void) {
return 0;
}
+#if HAVE_SELINUX
+static int mac_selinux_reload(int seqno) {
+ log_debug("SELinux reload %d", seqno);
+
+ (void) open_label_db();
+
+ return 0;
+}
+#endif
+
void mac_selinux_maybe_reload(void) {
#if HAVE_SELINUX
int policyload;
@@ -257,16 +265,6 @@ void mac_selinux_finish(void) {
}
#if HAVE_SELINUX
-static int mac_selinux_reload(int seqno) {
- log_debug("SELinux reload %d", seqno);
-
- (void) open_label_db();
-
- return 0;
-}
-#endif
-
-#if HAVE_SELINUX
static int selinux_fix_fd(
int fd,
const char *label_path,
diff --git a/src/shared/serialize.c b/src/shared/serialize.c
index 344b102..735caf4 100644
--- a/src/shared/serialize.c
+++ b/src/shared/serialize.c
@@ -485,7 +485,7 @@ int deserialize_pidref(FDSet *fds, const char *value, PidRef *ret) {
_cleanup_free_ char *fdstr = NULL, *pidstr = NULL;
_cleanup_close_ int fd = -EBADF;
- r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr, NULL);
+ r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr);
if (r < 0)
return log_debug_errno(r, "Failed to deserialize pidref '%s': %m", e);
if (r == 0)
diff --git a/src/shared/service-util.c b/src/shared/service-util.c
index b0585ba..7d46ee7 100644
--- a/src/shared/service-util.c
+++ b/src/shared/service-util.c
@@ -17,19 +17,22 @@ static int help(const char *program_path, const char *service, const char *descr
if (r < 0)
return log_oom();
- printf("%s [OPTIONS...]\n\n"
- "%s%s%s\n\n"
- "This program takes no positional arguments.\n\n"
- "%sOptions%s:\n"
+ printf("%1$s [OPTIONS...]\n"
+ "\n%5$s%7$s%6$s\n"
+ "\nThis program takes no positional arguments.\n"
+ "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
- " --bus-introspect=PATH Write D-Bus XML introspection data\n"
- "\nSee the %s for details.\n"
- , program_path
- , ansi_highlight(), description, ansi_normal()
- , ansi_underline(), ansi_normal()
- , link
- );
+ "%8$s"
+ "\nSee the %2$s for details.\n",
+ program_path,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal(),
+ description,
+ bus_introspect ? " --bus-introspect=PATH Write D-Bus XML introspection data\n" : "");
return 0; /* No further action */
}
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
index 7282111..3d4d331 100644
--- a/src/shared/sleep-config.c
+++ b/src/shared/sleep-config.c
@@ -54,6 +54,8 @@ SleepConfig* sleep_config_free(SleepConfig *sc) {
strv_free(sc->modes[i]);
}
+ strv_free(sc->mem_modes);
+
return mfree(sc);
}
@@ -69,8 +71,8 @@ static int config_parse_sleep_mode(
void *data,
void *userdata) {
- _cleanup_strv_free_ char **modes = NULL;
char ***sv = ASSERT_PTR(data);
+ _cleanup_strv_free_ char **modes = NULL;
int r;
assert(filename);
@@ -87,7 +89,7 @@ static int config_parse_sleep_mode(
return log_oom();
}
- return free_and_replace(*sv, modes);
+ return strv_free_and_replace(*sv, modes);
}
static void sleep_config_validate_state_and_mode(SleepConfig *sc) {
@@ -140,14 +142,19 @@ int parse_sleep_config(SleepConfig **ret) {
{ "Sleep", "HybridSleepState", config_parse_warn_compat, DISABLED_LEGACY, NULL },
{ "Sleep", "HybridSleepMode", config_parse_warn_compat, DISABLED_LEGACY, NULL },
+ { "Sleep", "MemorySleepMode", config_parse_sleep_mode, 0, &sc->mem_modes },
+
{ "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec },
{ "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec },
{}
};
- (void) config_parse_config_file("sleep.conf", "Sleep\0",
- config_item_table_lookup, items,
- CONFIG_PARSE_WARN, NULL);
+ (void) config_parse_standard_file_with_dropins(
+ "systemd/sleep.conf",
+ "Sleep\0",
+ config_item_table_lookup, items,
+ CONFIG_PARSE_WARN,
+ /* userdata= */ NULL);
/* use default values unless set */
sc->allow[SLEEP_SUSPEND] = allow_suspend != 0;
@@ -180,7 +187,7 @@ int parse_sleep_config(SleepConfig **ret) {
return 0;
}
-int sleep_state_supported(char **states) {
+int sleep_state_supported(char * const *states) {
_cleanup_free_ char *supported_sysfs = NULL;
const char *found;
int r;
@@ -210,22 +217,24 @@ int sleep_state_supported(char **states) {
return false;
}
-int sleep_mode_supported(char **modes) {
+int sleep_mode_supported(const char *path, char * const *modes) {
_cleanup_free_ char *supported_sysfs = NULL;
int r;
+ assert(path);
+
/* Unlike state, kernel has its own default choice if not configured */
if (strv_isempty(modes)) {
- log_debug("No sleep mode configured, using kernel default.");
+ log_debug("No sleep mode configured, using kernel default for %s.", path);
return true;
}
- if (access("/sys/power/disk", W_OK) < 0)
- return log_debug_errno(errno, "/sys/power/disk is not writable: %m");
+ if (access(path, W_OK) < 0)
+ return log_debug_errno(errno, "%s is not writable: %m", path);
- r = read_one_line_file("/sys/power/disk", &supported_sysfs);
+ r = read_one_line_file(path, &supported_sysfs);
if (r < 0)
- return log_debug_errno(r, "Failed to read /sys/power/disk: %m");
+ return log_debug_errno(r, "Failed to read %s: %m", path);
for (const char *p = supported_sysfs;;) {
_cleanup_free_ char *word = NULL;
@@ -234,7 +243,7 @@ int sleep_mode_supported(char **modes) {
r = extract_first_word(&p, &word, NULL, 0);
if (r < 0)
- return log_debug_errno(r, "Failed to parse /sys/power/disk: %m");
+ return log_debug_errno(r, "Failed to parse %s: %m", path);
if (r == 0)
break;
@@ -247,14 +256,15 @@ int sleep_mode_supported(char **modes) {
}
if (strv_contains(modes, mode)) {
- log_debug("Disk sleep mode '%s' is supported by kernel.", mode);
+ log_debug("Sleep mode '%s' is supported by kernel (%s).", mode, path);
return true;
}
}
if (DEBUG_LOGGING) {
_cleanup_free_ char *joined = strv_join(modes, " ");
- log_debug("None of the configured hibernation power modes are supported by kernel: %s", strnull(joined));
+ log_debug("None of the configured modes are supported by kernel (%s): %s",
+ path, strnull(joined));
}
return false;
}
@@ -284,7 +294,7 @@ static int s2h_supported(const SleepConfig *sleep_config, SleepSupport *ret_supp
return false;
}
- FOREACH_ARRAY(i, operations, ELEMENTSOF(operations)) {
+ FOREACH_ELEMENT(i, operations) {
r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support);
if (r < 0)
return r;
@@ -338,8 +348,18 @@ static int sleep_supported_internal(
return false;
}
- if (sleep_operation_is_hibernation(operation)) {
- r = sleep_mode_supported(sleep_config->modes[operation]);
+ if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) {
+ r = sleep_mode_supported("/sys/power/mem_sleep", sleep_config->mem_modes);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED;
+ return false;
+ }
+ }
+
+ if (SLEEP_OPERATION_IS_HIBERNATION(operation)) {
+ r = sleep_mode_supported("/sys/power/disk", sleep_config->modes[operation]);
if (r < 0)
return r;
if (r == 0) {
@@ -348,16 +368,28 @@ static int sleep_supported_internal(
}
r = hibernation_is_safe();
- if (r == -ENOTRECOVERABLE) {
+ switch (r) {
+
+ case -ENOTRECOVERABLE:
*ret_support = SLEEP_RESUME_NOT_SUPPORTED;
return false;
- }
- if (r == -ENOSPC) {
+
+ case -ESTALE:
+ *ret_support = SLEEP_RESUME_DEVICE_MISSING;
+ return false;
+
+ case -ENOMEDIUM:
+ *ret_support = SLEEP_RESUME_MISCONFIGURED;
+ return false;
+
+ case -ENOSPC:
*ret_support = SLEEP_NOT_ENOUGH_SWAP_SPACE;
return false;
+
+ default:
+ if (r < 0)
+ return r;
}
- if (r < 0)
- return r;
} else
assert(!sleep_config->modes[operation]);
diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h
index bc5aeb9..b59bce8 100644
--- a/src/shared/sleep-config.h
+++ b/src/shared/sleep-config.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "strv.h"
#include "time-util.h"
typedef enum SleepOperation {
@@ -20,7 +21,7 @@ typedef enum SleepOperation {
const char* sleep_operation_to_string(SleepOperation s) _const_;
SleepOperation sleep_operation_from_string(const char *s) _pure_;
-static inline bool sleep_operation_is_hibernation(SleepOperation operation) {
+static inline bool SLEEP_OPERATION_IS_HIBERNATION(SleepOperation operation) {
return IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP);
}
@@ -28,7 +29,8 @@ typedef struct SleepConfig {
bool allow[_SLEEP_OPERATION_MAX];
char **states[_SLEEP_OPERATION_CONFIG_MAX];
- char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image */
+ char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image (/sys/power/disk) */
+ char **mem_modes; /* /sys/power/mem_sleep */
usec_t hibernate_delay_usec;
usec_t suspend_estimation_usec;
@@ -39,12 +41,26 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, sleep_config_free);
int parse_sleep_config(SleepConfig **sleep_config);
+static inline bool SLEEP_NEEDS_MEM_SLEEP(const SleepConfig *sc, SleepOperation operation) {
+ assert(sc);
+ assert(operation >= 0 && operation < _SLEEP_OPERATION_CONFIG_MAX);
+
+ /* As per https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation,
+ * /sys/power/mem_sleep is honored if /sys/power/state is set to "mem" (common for suspend)
+ * or /sys/power/disk is set to "suspend" (hybrid-sleep). */
+
+ return strv_contains(sc->states[operation], "mem") ||
+ strv_contains(sc->modes[operation], "suspend");
+}
+
typedef enum SleepSupport {
SLEEP_SUPPORTED,
SLEEP_DISABLED, /* Disabled in SleepConfig.allow */
SLEEP_NOT_CONFIGURED, /* SleepConfig.states is not configured */
SLEEP_STATE_OR_MODE_NOT_SUPPORTED, /* SleepConfig.states/modes are not supported by kernel */
SLEEP_RESUME_NOT_SUPPORTED,
+ SLEEP_RESUME_DEVICE_MISSING, /* resume= is specified, but the device cannot be found in /proc/swaps */
+ SLEEP_RESUME_MISCONFIGURED, /* resume= is not set yet resume_offset= is configured */
SLEEP_NOT_ENOUGH_SWAP_SPACE,
SLEEP_ALARM_NOT_SUPPORTED, /* CLOCK_BOOTTIME_ALARM is unsupported by kernel (only used by s2h) */
} SleepSupport;
@@ -55,5 +71,5 @@ static inline int sleep_supported(SleepOperation operation) {
}
/* Only for test-sleep-config */
-int sleep_state_supported(char **states);
-int sleep_mode_supported(char **modes);
+int sleep_state_supported(char * const *states);
+int sleep_mode_supported(const char *path, char * const *modes);
diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c
index 0ba5762..a126d5d 100644
--- a/src/shared/socket-netlink.c
+++ b/src/shared/socket-netlink.c
@@ -1,15 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* Make sure the net/if.h header is included before any linux/ one */
+#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
-#include <net/if.h>
+#include <linux/net_namespace.h>
#include <string.h>
#include "alloc-util.h"
#include "errno-util.h"
#include "extract-word.h"
+#include "fd-util.h"
#include "log.h"
#include "memory-util.h"
+#include "namespace-util.h"
#include "netlink-util.h"
#include "parse-util.h"
#include "socket-netlink.h"
@@ -407,3 +411,69 @@ const char *in_addr_full_to_string(struct in_addr_full *a) {
return a->cached_server_string;
}
+
+int netns_get_nsid(int netnsfd, uint32_t *ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_close_ int _netns_fd = -EBADF;
+ int r;
+
+ if (netnsfd < 0) {
+ r = namespace_open(
+ 0,
+ /* ret_pidns_fd = */ NULL,
+ /* ret_mntns_fd = */ NULL,
+ &_netns_fd,
+ /* ret_userns_fd = */ NULL,
+ /* ret_root_fd = */ NULL);
+ if (r < 0)
+ return r;
+
+ netnsfd = _netns_fd;
+ }
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return r;
+
+ r = sd_rtnl_message_new_nsid(rtnl, &req, RTM_GETNSID);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_s32(req, NETNSA_FD, netnsfd);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+ uint16_t type;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_get_type(m, &type);
+ if (r < 0)
+ return r;
+ if (type != RTM_NEWNSID)
+ continue;
+
+ uint32_t u;
+ r = sd_netlink_message_read_u32(m, NETNSA_NSID, &u);
+ if (r < 0)
+ return r;
+
+ if (u == UINT32_MAX) /* no NSID assigned yet */
+ return -ENODATA;
+
+ if (ret)
+ *ret = u;
+
+ return 0;
+ }
+
+ return -ENXIO;
+}
diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h
index 6256a83..2c06fbe 100644
--- a/src/shared/socket-netlink.h
+++ b/src/shared/socket-netlink.h
@@ -42,3 +42,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free);
int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret);
int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret);
const char *in_addr_full_to_string(struct in_addr_full *a);
+
+int netns_get_nsid(int netnsfd, uint32_t *ret);
diff --git a/src/shared/specifier.c b/src/shared/specifier.c
index e5a1f94..f6739f2 100644
--- a/src/shared/specifier.c
+++ b/src/shared/specifier.c
@@ -78,8 +78,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[
if (!GREEDY_REALLOC(result, j + k + l + 1))
return -ENOMEM;
- memcpy(result + j, w, k);
- t = result + j + k;
+ t = mempcpy(result + j, w, k);
} else if (strchr(POSSIBLE_SPECIFIERS, *f))
/* Oops, an unknown specifier. */
return -EBADSLT;
@@ -112,18 +111,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[
/* Generic handler for simple string replacements */
int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
- char *n = NULL;
-
- assert(ret);
-
- if (!isempty(data)) {
- n = strdup(data);
- if (!n)
- return -ENOMEM;
- }
-
- *ret = n;
- return 0;
+ return strdup_to(ASSERT_PTR(ret), empty_to_null(data));
}
int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
@@ -250,32 +238,18 @@ int specifier_pretty_hostname(char specifier, const void *data, const char *root
int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
struct utsname uts;
- char *n;
assert(ret);
if (uname(&uts) < 0)
return -errno;
- n = strdup(uts.release);
- if (!n)
- return -ENOMEM;
-
- *ret = n;
- return 0;
+ return strdup_to(ret, uts.release);
}
int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
- char *t;
-
- assert(ret);
-
- t = strdup(architecture_to_string(uname_architecture()));
- if (!t)
- return -ENOMEM;
-
- *ret = t;
- return 0;
+ return strdup_to(ASSERT_PTR(ret),
+ architecture_to_string(uname_architecture()));
}
/* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid
@@ -421,7 +395,6 @@ int specifier_user_shell(char specifier, const void *data, const char *root, con
int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
const char *p;
- char *copy;
int r;
assert(ret);
@@ -433,17 +406,12 @@ int specifier_tmp_dir(char specifier, const void *data, const char *root, const
if (r < 0)
return r;
}
- copy = strdup(p);
- if (!copy)
- return -ENOMEM;
- *ret = copy;
- return 0;
+ return strdup_to(ret, p);
}
int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
const char *p;
- char *copy;
int r;
assert(ret);
@@ -455,12 +423,8 @@ int specifier_var_tmp_dir(char specifier, const void *data, const char *root, co
if (r < 0)
return r;
}
- copy = strdup(p);
- if (!copy)
- return -ENOMEM;
- *ret = copy;
- return 0;
+ return strdup_to(ret, p);
}
int specifier_escape_strv(char **l, char ***ret) {
diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c
index b620156..e64b6f6 100644
--- a/src/shared/switch-root.c
+++ b/src/shared/switch-root.c
@@ -30,8 +30,8 @@ int switch_root(const char *new_root,
const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */
SwitchRootFlags flags) {
- /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance, i.e.
- * credentials, removable media and such, we rather want that the new boot mounts this fresh. But on
+ /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance,
+ * e.g. removable media and such. We rather want that the new boot mounts this fresh. But on
* the switch from initrd we do use MS_REC, as it is expected that mounts set up in /run/ are
* maintained. */
static const struct {
@@ -39,13 +39,12 @@ int switch_root(const char *new_root,
unsigned long mount_flags; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is unset */
unsigned long mount_flags_recursive_run; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is set (0 if shall be skipped) */
} transfer_table[] = {
- { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */
- { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */
- { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */
- { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */
- { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Credentials passed into the system should survive */
- { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Similar */
- { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */
+ { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */
+ { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */
+ { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */
+ { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */
+ { "/run/credentials", MS_BIND|MS_REC, 0 /* skip! */ }, /* Credential mounts should survive */
+ { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */
};
_cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF;
@@ -63,11 +62,11 @@ int switch_root(const char *new_root,
if (new_root_fd < 0)
return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root);
- r = inode_same_at(old_root_fd, "", new_root_fd, "", AT_EMPTY_PATH);
+ r = fds_are_same_mount(old_root_fd, new_root_fd);
if (r < 0)
- return log_error_errno(r, "Failed to determine if old and new root directory are the same: %m");
+ return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m");
if (r > 0) {
- log_debug("Skipping switch root, as old and new root directory are the same.");
+ log_debug("Skipping switch root, as old and new root directories/mounts are the same.");
return 0;
}
@@ -126,7 +125,7 @@ int switch_root(const char *new_root,
* and switch_root() nevertheless. */
(void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID);
- FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) {
+ FOREACH_ELEMENT(transfer, transfer_table) {
_cleanup_free_ char *chased = NULL;
unsigned long mount_flags;
@@ -144,7 +143,7 @@ int switch_root(const char *new_root,
return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, transfer->path);
/* Let's see if it is a mount point already. */
- r = path_is_mount_point(chased, NULL, 0);
+ r = path_is_mount_point(chased);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased);
if (r > 0) /* If it is already mounted, then do nothing */
diff --git a/src/shared/tests.c b/src/shared/tests.c
index 3882a18..9169513 100644
--- a/src/shared/tests.c
+++ b/src/shared/tests.c
@@ -21,6 +21,7 @@
#include "fd-util.h"
#include "fs-util.h"
#include "log.h"
+#include "mountpoint-util.h"
#include "namespace-util.h"
#include "path-util.h"
#include "process-util.h"
@@ -113,9 +114,9 @@ bool slow_tests_enabled(void) {
}
void test_setup_logging(int level) {
+ log_set_assert_return_is_critical(true);
log_set_max_level(level);
- log_parse_environment();
- log_open();
+ log_setup();
}
int write_tmpfile(char *pattern, const char *contents) {
@@ -188,12 +189,16 @@ static int allocate_scope(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- _cleanup_free_ char *scope = NULL;
+ _cleanup_free_ char *scope = NULL, *cgroup_root = NULL;
const char *object;
int r;
/* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't
* interfere with our cgroup management. */
+ if (cg_pid_get_path(NULL, 0, &cgroup_root) >= 0 && cg_is_delegated(cgroup_root) && stderr_is_journal()) {
+ log_debug("Already running as a unit with delegated cgroup, not allocating a cgroup subroot.");
+ return 0;
+ }
r = sd_bus_default_system(&bus);
if (r < 0)
@@ -249,7 +254,7 @@ static int allocate_scope(void) {
if (r < 0)
return bus_log_parse_error(r);
- r = bus_wait_for_jobs_one(w, object, false, NULL);
+ r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL);
if (r < 0)
return r;
@@ -266,7 +271,7 @@ static int enter_cgroup(char **ret_cgroup, bool enter_subroot) {
log_warning_errno(r, "Couldn't allocate a scope unit for this test, proceeding without.");
r = cg_pid_get_path(NULL, 0, &cgroup_root);
- if (r == -ENOMEDIUM)
+ if (IN_SET(r, -ENOMEDIUM, -ENOENT))
return log_warning_errno(r, "cg_pid_get_path(NULL, 0, ...) failed: %m");
assert(r >= 0);
diff --git a/src/shared/tests.h b/src/shared/tests.h
index d76cf2e..09fdfd6 100644
--- a/src/shared/tests.h
+++ b/src/shared/tests.h
@@ -2,14 +2,41 @@
#pragma once
#include <stdbool.h>
+#include <sys/prctl.h>
+#include <unistd.h>
#include "sd-daemon.h"
#include "argv-util.h"
+#include "errno-util.h"
#include "macro.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "signal-util.h"
#include "static-destruct.h"
#include "strv.h"
+static inline void log_set_assert_return_is_criticalp(bool *p) {
+ log_set_assert_return_is_critical(*p);
+}
+
+#define _SAVE_ASSERT_RETURN_IS_CRITICAL(b) \
+ _unused_ _cleanup_(log_set_assert_return_is_criticalp) bool b = \
+ log_get_assert_return_is_critical()
+
+#define SAVE_ASSERT_RETURN_IS_CRITICAL \
+ _SAVE_ASSERT_RETURN_IS_CRITICAL(UNIQ_T(saved, UNIQ))
+
+#define ASSERT_RETURN_IS_CRITICAL(b, expr) \
+ ({ \
+ SAVE_ASSERT_RETURN_IS_CRITICAL; \
+ log_set_assert_return_is_critical(b); \
+ expr; \
+ })
+
+#define ASSERT_RETURN_EXPECTED(expr) ASSERT_RETURN_IS_CRITICAL(false, expr)
+#define ASSERT_RETURN_EXPECTED_SE(expr) ASSERT_RETURN_EXPECTED(assert_se(expr));
+
static inline bool manager_errno_skip_test(int r) {
return IN_SET(abs(r),
EPERM,
@@ -58,7 +85,7 @@ bool can_memlock(void);
#define DEFINE_HEX_PTR(name, hex) \
_cleanup_free_ void *name = NULL; \
size_t name##_len = 0; \
- assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0);
+ assert_se(unhexmem_full(hex, strlen_ptr(hex), false, &name, &name##_len) >= 0);
#define TEST_REQ_RUNNING_SYSTEMD(x) \
if (sd_booted() > 0) { \
@@ -179,3 +206,216 @@ static inline int run_test_table(void) {
DEFINE_TEST_MAIN_FULL(log_level, intro, NULL)
#define DEFINE_TEST_MAIN(log_level) \
DEFINE_TEST_MAIN_FULL(log_level, NULL, NULL)
+
+#define ASSERT_OK(expr) \
+ ({ \
+ typeof(expr) _result = (expr); \
+ if (_result < 0) { \
+ log_error_errno(_result, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_OK_ERRNO(expr) \
+ ({ \
+ typeof(expr) _result = (expr); \
+ if (_result < 0) { \
+ log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_ERROR(expr1, expr2) \
+ ({ \
+ int _expr1 = (expr1); \
+ int _expr2 = (expr2); \
+ if (_expr1 >= 0) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } else if (-_expr1 != _expr2) { \
+ log_error_errno(_expr1, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_ERROR_ERRNO(expr1, expr2) \
+ ({ \
+ int _expr1 = (expr1); \
+ int _expr2 = (expr2); \
+ if (_expr1 >= 0) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \
+ abort(); \
+ } else if (errno != _expr2) { \
+ log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \
+ PROJECT_FILE, __LINE__, #expr1, STRERROR(errno)); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_TRUE(expr) \
+ ({ \
+ if (!(expr)) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be true", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_FALSE(expr) \
+ ({ \
+ if ((expr)) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be false", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NULL(expr) \
+ ({ \
+ typeof(expr) _result = (expr); \
+ if (_result != NULL) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be NULL, but \"%p\" != NULL", \
+ PROJECT_FILE, __LINE__, #expr, _result); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NOT_NULL(expr) \
+ ({ \
+ if ((expr) == NULL) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be not NULL", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_STREQ(expr1, expr2) \
+ ({ \
+ const char *_expr1 = (expr1), *_expr2 = (expr2); \
+ if (!streq_ptr(_expr1, _expr2)) { \
+ log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \
+ abort(); \
+ } \
+ })
+
+/* DECIMAL_STR_FMT() uses _Generic which cannot be used in string concatenation so we have to format the
+ * input into strings first and then format those into the final assertion message. */
+
+#define ASSERT_EQ(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 != _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_GE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 < _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s >= %s\", but \"%s < %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_LE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 > _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s <= %s\", but \"%s > %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 == _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s != %s\", but \"%s == %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_GT(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (!(_expr1 > _expr2)) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s > %s\", but \"%s <= %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_LT(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (!(_expr1 < _expr2)) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s < %s\", but \"%s >= %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_SIGNAL(expr, signal) \
+ ({ \
+ ASSERT_TRUE(SIGNAL_VALID(signal)); \
+ siginfo_t _siginfo = {}; \
+ int _pid = fork(); \
+ ASSERT_OK(_pid); \
+ if (_pid == 0) { \
+ /* Speed things up by never even attempting to generate a coredump */ \
+ (void) prctl(PR_SET_DUMPABLE, 0); \
+ /* But still set an rlimit just in case */ \
+ (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0)); \
+ expr; \
+ _exit(EXIT_SUCCESS); \
+ } \
+ (void) wait_for_terminate(_pid, &_siginfo); \
+ if (_siginfo.si_status != signal) { \
+ log_error("%s:%i: Assertion failed: \"%s\" died with signal %s, but %s was expected", \
+ PROJECT_FILE, __LINE__, #expr, signal_to_string(_siginfo.si_status), \
+ signal_to_string(signal)); \
+ abort(); \
+ } \
+ })
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index c7e0b24..87ce53c 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -4,6 +4,7 @@
#include "alloc-util.h"
#include "constants.h"
+#include "creds-util.h"
#include "cryptsetup-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
@@ -25,8 +26,10 @@
#include "nulstr-util.h"
#include "parse-util.h"
#include "random-util.h"
+#include "recurse-dir.h"
#include "sha256.h"
#include "sort-util.h"
+#include "sparse-endian.h"
#include "stat-util.h"
#include "string-table.h"
#include "sync-util.h"
@@ -34,76 +37,87 @@
#include "tpm2-util.h"
#include "virt.h"
+#if HAVE_OPENSSL
+# include <openssl/hmac.h>
+#endif
+
#if HAVE_TPM2
static void *libtss2_esys_dl = NULL;
static void *libtss2_rc_dl = NULL;
static void *libtss2_mu_dl = NULL;
-static TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
-static TSS2_RC (*sym_Esys_CreateLoaded)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_TEMPLATE *inPublic, ESYS_TR *objectHandle, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic) = NULL;
-static TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
-static TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle) = NULL;
-static void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
-static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
-static void (*sym_Esys_Free)(void *ptr) = NULL;
-static 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) = NULL;
-static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
-static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL;
-static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
-static 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;
-static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL;
-static TSS2_RC (*sym_Esys_NV_DefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_AUTH *auth, const TPM2B_NV_PUBLIC *publicInfo, ESYS_TR *nvHandle);
-static TSS2_RC (*sym_Esys_NV_UndefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3);
-static TSS2_RC (*sym_Esys_NV_Write)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_MAX_NV_BUFFER *data, UINT16 offset);
-static TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests) = NULL;
-static TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues) = NULL;
-static TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
-static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL;
-static TSS2_RC (*sym_Esys_PolicyAuthorizeNV)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3);
-static TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
-static TSS2_RC (*sym_Esys_PolicyOR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST *pHashList) = NULL;
-static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
-static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL;
-static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
-static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
-static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL;
-static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = NULL;
-static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL;
-static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL;
-static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL;
-static TSS2_RC (*sym_Esys_TR_GetTpmHandle)(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, TPM2_HANDLE *tpm_handle) = NULL;
-static TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size) = NULL;
-static TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
-static TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags) = NULL;
-static TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask) = NULL;
-static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
-static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL;
-
-static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-
-static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
+static DLSYM_FUNCTION(Esys_Create);
+static DLSYM_FUNCTION(Esys_CreateLoaded);
+static DLSYM_FUNCTION(Esys_CreatePrimary);
+static DLSYM_FUNCTION(Esys_EvictControl);
+static DLSYM_FUNCTION(Esys_Finalize);
+static DLSYM_FUNCTION(Esys_FlushContext);
+static DLSYM_FUNCTION(Esys_Free);
+static DLSYM_FUNCTION(Esys_GetCapability);
+static DLSYM_FUNCTION(Esys_GetRandom);
+static DLSYM_FUNCTION(Esys_Import);
+static DLSYM_FUNCTION(Esys_Initialize);
+static DLSYM_FUNCTION(Esys_Load);
+static DLSYM_FUNCTION(Esys_LoadExternal);
+static DLSYM_FUNCTION(Esys_NV_DefineSpace);
+static DLSYM_FUNCTION(Esys_NV_UndefineSpace);
+static DLSYM_FUNCTION(Esys_NV_Write);
+static DLSYM_FUNCTION(Esys_PCR_Extend);
+static DLSYM_FUNCTION(Esys_PCR_Read);
+static DLSYM_FUNCTION(Esys_PolicyAuthValue);
+static DLSYM_FUNCTION(Esys_PolicyAuthorize);
+static DLSYM_FUNCTION(Esys_PolicyAuthorizeNV);
+static DLSYM_FUNCTION(Esys_PolicyGetDigest);
+static DLSYM_FUNCTION(Esys_PolicyOR);
+static DLSYM_FUNCTION(Esys_PolicyPCR);
+static DLSYM_FUNCTION(Esys_PolicySigned);
+static DLSYM_FUNCTION(Esys_ReadPublic);
+static DLSYM_FUNCTION(Esys_StartAuthSession);
+static DLSYM_FUNCTION(Esys_Startup);
+static DLSYM_FUNCTION(Esys_TestParms);
+static DLSYM_FUNCTION(Esys_TR_Close);
+static DLSYM_FUNCTION(Esys_TR_Deserialize);
+static DLSYM_FUNCTION(Esys_TR_FromTPMPublic);
+static DLSYM_FUNCTION(Esys_TR_GetName);
+static DLSYM_FUNCTION(Esys_TR_GetTpmHandle);
+static DLSYM_FUNCTION(Esys_TR_Serialize);
+static DLSYM_FUNCTION(Esys_TR_SetAuth);
+static DLSYM_FUNCTION(Esys_TRSess_GetAttributes);
+static DLSYM_FUNCTION(Esys_TRSess_GetNonceTPM);
+static DLSYM_FUNCTION(Esys_TRSess_SetAttributes);
+static DLSYM_FUNCTION(Esys_Unseal);
+static DLSYM_FUNCTION(Esys_VerifySignature);
+
+static DLSYM_FUNCTION(Tss2_MU_TPM2_CC_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2_HANDLE_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_DIGEST_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_NAME_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Unmarshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Unmarshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_SENSITIVE_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPML_PCR_SELECTION_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPMS_NV_PUBLIC_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal);
+static DLSYM_FUNCTION(Tss2_MU_TPMS_ECC_POINT_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPMT_HA_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_TPMT_PUBLIC_Marshal);
+static DLSYM_FUNCTION(Tss2_MU_UINT32_Marshal);
+
+static DLSYM_FUNCTION(Tss2_RC_Decode);
int dlopen_tpm2(void) {
int r;
+ ELF_NOTE_DLOPEN("tpm",
+ "Support for TPM",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libtss2-esys.so.0");
+
r = dlopen_many_sym_or_warn(
&libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG,
DLSYM_ARG(Esys_Create),
@@ -130,6 +144,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyOR),
DLSYM_ARG(Esys_PolicyPCR),
+ DLSYM_ARG(Esys_PolicySigned),
DLSYM_ARG(Esys_ReadPublic),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
@@ -141,6 +156,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_TR_Serialize),
DLSYM_ARG(Esys_TR_SetAuth),
DLSYM_ARG(Esys_TRSess_GetAttributes),
+ DLSYM_ARG(Esys_TRSess_GetNonceTPM),
DLSYM_ARG(Esys_TRSess_SetAttributes),
DLSYM_ARG(Esys_Unseal),
DLSYM_ARG(Esys_VerifySignature));
@@ -153,12 +169,22 @@ int dlopen_tpm2(void) {
if (r < 0)
log_debug("libtss2-esys too old, does not include Esys_TR_GetTpmHandle.");
+ ELF_NOTE_DLOPEN("tpm",
+ "Support for TPM",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libtss2-rc.so.0");
+
r = dlopen_many_sym_or_warn(
&libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG,
DLSYM_ARG(Tss2_RC_Decode));
if (r < 0)
return r;
+ ELF_NOTE_DLOPEN("tpm",
+ "Support for TPM",
+ ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
+ "libtss2-mu.so.0");
+
return dlopen_many_sym_or_warn(
&libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG,
DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal),
@@ -1895,7 +1921,7 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) {
_cleanup_free_ void *buf = NULL;
size_t buf_size = 0;
- r = unhexmem(p, SIZE_MAX, &buf, &buf_size);
+ r = unhexmem(p, &buf, &buf_size);
if (r < 0)
return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p);
@@ -2084,7 +2110,7 @@ int tpm2_create_primary(
session ? session->esys_handle : ESYS_TR_PASSWORD,
ESYS_TR_NONE,
ESYS_TR_NONE,
- sensitive ? sensitive : &(TPM2B_SENSITIVE_CREATE) {},
+ sensitive ?: &(TPM2B_SENSITIVE_CREATE) {},
template,
/* outsideInfo= */ NULL,
&(TPML_PCR_SELECTION) {},
@@ -2254,9 +2280,9 @@ static int tpm2_load_external(
#if HAVE_TSS2_ESYS3
/* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested
* hierarchy, older versions need TPM2_RH_* instead. */
- ESYS_TR_RH_OWNER,
+ private ? ESYS_TR_RH_NULL : ESYS_TR_RH_OWNER,
#else
- TPM2_RH_OWNER,
+ private ? TPM2_RH_NULL : TPM2_RH_OWNER,
#endif
&handle->esys_handle);
if (rc != TSS2_RC_SUCCESS)
@@ -3074,7 +3100,7 @@ static void tpm2_trim_auth_value(TPM2B_AUTH *auth) {
log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2.");
}
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) {
TPM2B_AUTH auth = {};
int r;
@@ -3121,7 +3147,7 @@ int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) {
CLEANUP_ERASE(auth);
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
if (r < 0)
return r;
@@ -3408,7 +3434,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name)
*
* The handle must reference a key already present in the TPM. It may be either a public key only, or a
* public/private keypair. */
-static int tpm2_get_name(
+int tpm2_get_name(
Tpm2Context *c,
const Tpm2Handle *handle,
TPM2B_NAME **ret_name) {
@@ -3546,6 +3572,150 @@ int tpm2_policy_auth_value(
return tpm2_get_policy_digest(c, session, ret_policy_digest);
}
+/* Extend 'digest' with the PolicySigned calculated hash. */
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) {
+ TPM2_CC command = TPM2_CC_PolicySigned;
+ TSS2_RC rc;
+ int r;
+
+ assert(digest);
+ assert(digest->size == SHA256_DIGEST_SIZE);
+ assert(name);
+
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 support not installed: %m");
+
+ uint8_t buf[sizeof(command)];
+ size_t offset = 0;
+
+ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc));
+
+ if (offset != sizeof(command))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Offset 0x%zx wrong after marshalling PolicySigned command", offset);
+
+ struct iovec data[] = {
+ IOVEC_MAKE(buf, offset),
+ IOVEC_MAKE(name->name, name->size),
+ };
+
+ r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */
+
+ r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true);
+ if (r < 0)
+ return r;
+
+ tpm2_log_debug_digest(digest, "PolicySigned calculated digest");
+
+ return 0;
+}
+
+int tpm2_policy_signed_hmac_sha256(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ const Tpm2Handle *hmac_key_handle,
+ const struct iovec *hmac_key,
+ TPM2B_DIGEST **ret_policy_digest) {
+
+#if HAVE_OPENSSL
+ TSS2_RC rc;
+ int r;
+
+ assert(c);
+ assert(session);
+ assert(hmac_key_handle);
+ assert(iovec_is_set(hmac_key));
+
+ /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key
+ * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and
+ * referenced in hmac_key_handle. */
+
+ log_debug("Submitting PolicySigned policy for HMAC-SHA256.");
+
+ /* Acquire the nonce from the TPM that we shall sign */
+ _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL;
+ rc = sym_Esys_TRSess_GetNonceTPM(
+ c->esys_context,
+ session->esys_handle,
+ &nonce);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to determine NoneTPM of auth session: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ be32_t expiration = htobe64(0);
+ const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */
+ const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */
+
+ /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */
+ struct iovec data_to_sign[] = {
+ IOVEC_MAKE(nonce->buffer, nonce->size),
+ IOVEC_MAKE(&expiration, sizeof(expiration)),
+ IOVEC_MAKE(cpHashA.buffer, cpHashA.size),
+ IOVEC_MAKE(policyRef.buffer, policyRef.size),
+ };
+
+ /* Now calculate the digest of the data we put together */
+ TPM2B_DIGEST digest_to_sign;
+ r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ unsigned char hmac_signature[SHA256_DIGEST_SIZE];
+ unsigned hmac_signature_size = sizeof(hmac_signature);
+
+ /* And sign this with our key */
+ if (!HMAC(EVP_sha256(),
+ hmac_key->iov_base,
+ hmac_key->iov_len,
+ digest_to_sign.buffer,
+ digest_to_sign.size,
+ hmac_signature,
+ &hmac_signature_size))
+ return -ENOTRECOVERABLE;
+
+ /* Now bring the signature into a format that the TPM understands */
+ TPMT_SIGNATURE sig = {
+ .sigAlg = TPM2_ALG_HMAC,
+ .signature.hmac.hashAlg = TPM2_ALG_SHA256,
+ };
+ assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256));
+ memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size);
+
+ /* And submit the whole shebang to the TPM */
+ rc = sym_Esys_PolicySigned(
+ c->esys_context,
+ hmac_key_handle->esys_handle,
+ session->esys_handle,
+ /* shandle1= */ ESYS_TR_NONE,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ nonce,
+ &cpHashA,
+ &policyRef,
+ expiration,
+ &sig,
+ /* timeout= */ NULL,
+ /* policyTicket= */ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to add PolicySigned policy to TPM: %s",
+ sym_Tss2_RC_Decode(rc));
+
+ return tpm2_get_policy_digest(c, session, ret_policy_digest);
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
int tpm2_calculate_policy_authorize_nv(
const TPM2B_NV_PUBLIC *public_info,
TPM2B_DIGEST *digest) {
@@ -4151,7 +4321,7 @@ static const struct {
static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_ECC_CURVE *ret) {
assert(ret);
- FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table))
+ FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table)
if (t->openssl_ecc_curve_id == openssl_ecc_curve_id) {
*ret = t->tpm2_ecc_curve_id;
return 0;
@@ -4164,7 +4334,7 @@ static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_E
static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE tpm2_ecc_curve_id, int *ret) {
assert(ret);
- FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table))
+ FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table)
if (t->tpm2_ecc_curve_id == tpm2_ecc_curve_id) {
*ret = t->openssl_ecc_curve_id;
return 0;
@@ -4801,7 +4971,7 @@ static int tpm2_calculate_seal_private(
TPM2B_AUTH auth = {};
if (pin) {
- r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth);
+ r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth);
if (r < 0)
return r;
}
@@ -5035,7 +5205,7 @@ static int tpm2_calculate_seal_ecc_seed(
size_t bits = (size_t) r * 8;
_cleanup_free_ void *seed = NULL;
- size_t seed_size;
+ size_t seed_size = 0; /* Explicit initialization to appease gcc */
r = tpm2_kdfe(parent->publicArea.nameAlg,
shared_secret,
shared_secret_size,
@@ -5072,7 +5242,7 @@ static int tpm2_calculate_seal_seed(
log_debug("Calculating encrypted seed for sealed object.");
_cleanup_free_ void *seed = NULL, *encrypted_seed = NULL;
- size_t seed_size, encrypted_seed_size;
+ size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */
if (parent->publicArea.type == TPM2_ALG_RSA)
r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size);
else if (parent->publicArea.type == TPM2_ALG_ECC)
@@ -5095,28 +5265,22 @@ int tpm2_calculate_seal(
TPM2_HANDLE parent_handle,
const TPM2B_PUBLIC *parent_public,
const TPMA_OBJECT *attributes,
- const void *secret,
- size_t secret_size,
+ const struct iovec *secret,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_serialized_parent,
- size_t *ret_serialized_parent_size) {
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
+ struct iovec *ret_serialized_parent) {
#if HAVE_OPENSSL
int r;
assert(parent_public);
- assert(secret || secret_size == 0);
+ assert(iovec_is_valid(secret));
assert(secret || ret_secret);
assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */
assert(ret_blob);
- assert(ret_blob_size);
assert(ret_serialized_parent);
- assert(ret_serialized_parent_size);
log_debug("Calculating sealed object.");
@@ -5137,27 +5301,27 @@ int tpm2_calculate_seal(
parent_handle);
}
- _cleanup_(erase_and_freep) void *generated_secret = NULL;
+ _cleanup_(iovec_done_erase) struct iovec generated_secret = {};
if (!secret) {
/* No secret provided, generate a random secret. We use SHA256 digest length, though it can
* be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash
* size. */
- secret_size = TPM2_SHA256_DIGEST_SIZE;
- generated_secret = malloc(secret_size);
- if (!generated_secret)
+ generated_secret.iov_len = TPM2_SHA256_DIGEST_SIZE;
+ generated_secret.iov_base = malloc(generated_secret.iov_len);
+ if (!generated_secret.iov_base)
return log_oom_debug();
- r = crypto_random_bytes(generated_secret, secret_size);
+ r = crypto_random_bytes(generated_secret.iov_base, generated_secret.iov_len);
if (r < 0)
return log_debug_errno(r, "Failed to generate secret key: %m");
- secret = generated_secret;
+ secret = &generated_secret;
}
- if (secret_size > TPM2_MAX_SEALED_DATA)
+ if (secret->iov_len > TPM2_MAX_SEALED_DATA)
return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW),
"Secret size %zu too large, limit is %d bytes.",
- secret_size, TPM2_MAX_SEALED_DATA);
+ secret->iov_len, TPM2_MAX_SEALED_DATA);
TPM2B_DIGEST random_seed;
TPM2B_ENCRYPTED_SECRET seed;
@@ -5166,7 +5330,7 @@ int tpm2_calculate_seal(
return r;
TPM2B_PUBLIC public;
- r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public);
+ r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret->iov_base, secret->iov_len, &public);
if (r < 0)
return r;
@@ -5176,13 +5340,12 @@ int tpm2_calculate_seal(
return r;
TPM2B_PRIVATE private;
- r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private);
+ r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret->iov_base, secret->iov_len, &private);
if (r < 0)
return r;
- _cleanup_free_ void *blob = NULL;
- size_t blob_size;
- r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(&public, &private, &seed, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
@@ -5191,25 +5354,20 @@ int tpm2_calculate_seal(
if (r < 0)
return r;
- _cleanup_free_ void *serialized_parent = NULL;
- size_t serialized_parent_size;
+ _cleanup_(iovec_done) struct iovec serialized_parent = {};
r = tpm2_calculate_serialize(
parent_handle,
&parent_name,
parent_public,
- &serialized_parent,
- &serialized_parent_size);
+ &serialized_parent.iov_base,
+ &serialized_parent.iov_len);
if (r < 0)
return r;
if (ret_secret)
- *ret_secret = TAKE_PTR(generated_secret);
- if (ret_secret_size)
- *ret_secret_size = secret_size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_serialized_parent = TAKE_PTR(serialized_parent);
- *ret_serialized_parent_size = serialized_parent_size;
+ *ret_secret = TAKE_STRUCT(generated_secret);
+ *ret_blob = TAKE_STRUCT(blob);
+ *ret_serialized_parent = TAKE_STRUCT(serialized_parent);
return 0;
#else /* HAVE_OPENSSL */
@@ -5221,21 +5379,16 @@ int tpm2_seal(Tpm2Context *c,
uint32_t seal_key_handle,
const TPM2B_DIGEST *policy,
const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
+ struct iovec *ret_secret,
+ struct iovec *ret_blob,
uint16_t *ret_primary_alg,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size) {
+ struct iovec *ret_srk) {
uint16_t primary_alg = 0;
int r;
assert(ret_secret);
- assert(ret_secret_size);
assert(ret_blob);
- assert(ret_blob_size);
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
@@ -5255,13 +5408,22 @@ int tpm2_seal(Tpm2Context *c,
usec_t start = now(CLOCK_MONOTONIC);
+ TPMA_OBJECT hmac_attributes =
+ TPMA_OBJECT_FIXEDTPM |
+ TPMA_OBJECT_FIXEDPARENT;
+
+ /* If protected by PIN, a user-selected low-entropy password, enable DA protection.
+ Without a PIN, the key's left protected only by a PCR policy, which does not benefit
+ from DA protection. */
+ hmac_attributes |= pin ? 0 : TPMA_OBJECT_NODA;
+
/* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the
* LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it
* because it's a key type that is universally supported and suitable for symmetric binary blobs. */
TPMT_PUBLIC hmac_template = {
.type = TPM2_ALG_KEYEDHASH,
.nameAlg = TPM2_ALG_SHA256,
- .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
+ .objectAttributes = hmac_attributes,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
.unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
@@ -5274,7 +5436,7 @@ int tpm2_seal(Tpm2Context *c,
CLEANUP_ERASE(hmac_sensitive);
if (pin) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth);
if (r < 0)
return r;
}
@@ -5290,7 +5452,7 @@ int tpm2_seal(Tpm2Context *c,
return log_debug_errno(r, "Failed to generate secret key: %m");
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (ret_srk_buf) {
+ if (ret_srk) {
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) {
@@ -5328,7 +5490,7 @@ int tpm2_seal(Tpm2Context *c,
if (seal_key_handle != 0)
log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle.");
- /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */
+ /* TODO: force all callers to provide ret_srk, so we can stop sealing with the legacy templates. */
primary_alg = TPM2_ALG_ECC;
TPM2B_PUBLIC template = {
@@ -5372,47 +5534,46 @@ int tpm2_seal(Tpm2Context *c,
if (r < 0)
return r;
- _cleanup_(erase_and_freep) void *secret = NULL;
- secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
- if (!secret)
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = hmac_sensitive.data.size;
log_debug("Marshalling private and public part of HMAC key.");
- _cleanup_free_ void *blob = NULL;
- size_t blob_size = 0;
- r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size);
+ _cleanup_(iovec_done) struct iovec blob = {};
+ r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- _cleanup_free_ void *srk_buf = NULL;
- size_t srk_buf_size = 0;
- if (ret_srk_buf) {
+ if (ret_srk) {
+ _cleanup_(iovec_done) struct iovec srk = {};
_cleanup_(Esys_Freep) void *tmp = NULL;
- r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size);
+ size_t tmp_size;
+
+ r = tpm2_serialize(c, primary_handle, &tmp, &tmp_size);
if (r < 0)
return r;
/*
* make a copy since we don't want the caller to understand that
* ESYS allocated the pointer. It would make tracking what deallocator
- * to use for srk_buf in which context a PITA.
+ * to use for srk in which context a PITA.
*/
- srk_buf = memdup(tmp, srk_buf_size);
- if (!srk_buf)
+ srk.iov_base = memdup(tmp, tmp_size);
+ if (!srk.iov_base)
return log_oom_debug();
+ srk.iov_len = tmp_size;
- *ret_srk_buf = TAKE_PTR(srk_buf);
- *ret_srk_buf_size = srk_buf_size;
+ *ret_srk = TAKE_STRUCT(srk);
}
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = hmac_sensitive.data.size;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
+ *ret_secret = TAKE_STRUCT(secret);
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
@@ -5425,31 +5586,24 @@ int tpm2_seal(Tpm2Context *c,
int tpm2_unseal(Tpm2Context *c,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
JsonVariant *signature,
const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *known_policy_hash,
- size_t known_policy_hash_size,
- const void *srk_buf,
- size_t srk_buf_size,
- void **ret_secret,
- size_t *ret_secret_size) {
+ const struct iovec *blob,
+ const struct iovec *known_policy_hash,
+ const struct iovec *srk,
+ struct iovec *ret_secret) {
TSS2_RC rc;
int r;
- assert(blob);
- assert(blob_size > 0);
- assert(known_policy_hash_size == 0 || known_policy_hash);
- assert(pubkey_size == 0 || pubkey);
+ assert(iovec_is_set(blob));
+ assert(iovec_is_valid(known_policy_hash));
+ assert(iovec_is_valid(pubkey));
assert(ret_secret);
- assert(ret_secret_size);
assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
@@ -5467,7 +5621,7 @@ int tpm2_unseal(Tpm2Context *c,
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
TPM2B_ENCRYPTED_SECRET seed = {};
- r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed);
+ r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
@@ -5480,8 +5634,8 @@ int tpm2_unseal(Tpm2Context *c,
}
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
- if (srk_buf) {
- r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle);
+ if (iovec_is_set(srk)) {
+ r = tpm2_deserialize(c, srk->iov_base, srk->iov_len, &primary_handle);
if (r < 0)
return r;
} else if (primary_alg != 0) {
@@ -5537,14 +5691,13 @@ int tpm2_unseal(Tpm2Context *c,
return r;
TPM2B_PUBLIC pubkey_tpm2b;
- _cleanup_free_ void *fp = NULL;
- size_t fp_size = 0;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b);
+ _cleanup_(iovec_done) struct iovec fp = {};
+ if (iovec_is_set(pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey->iov_base, pubkey->iov_len, &pubkey_tpm2b);
if (r < 0)
return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m");
- r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size);
+ r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp.iov_base, &fp.iov_len);
if (r < 0)
return log_debug_errno(r, "Could not get key fingerprint: %m");
}
@@ -5582,8 +5735,8 @@ int tpm2_unseal(Tpm2Context *c,
policy_session,
hash_pcr_mask,
pcr_bank,
- pubkey ? &pubkey_tpm2b : NULL,
- fp, fp_size,
+ iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
+ fp.iov_base, fp.iov_len,
pubkey_pcr_mask,
signature,
!!pin,
@@ -5594,11 +5747,12 @@ int tpm2_unseal(Tpm2Context *c,
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
- if (known_policy_hash_size > 0 &&
- memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) {
-
+ if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer,
+ policy_digest->size,
+ known_policy_hash->iov_base,
+ known_policy_hash->iov_len) != 0) {
#if HAVE_OPENSSL
- if (pubkey_size > 0 &&
+ if (iovec_is_set(pubkey) &&
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may
@@ -5630,23 +5784,24 @@ int tpm2_unseal(Tpm2Context *c,
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
}
- _cleanup_(erase_and_freep) char *secret = NULL;
- secret = memdup(unsealed->buffer, unsealed->size);
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ secret.iov_base = memdup(unsealed->buffer, unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
- if (!secret)
+ if (!secret.iov_base)
return log_oom_debug();
+ secret.iov_len = unsealed->size;
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
- *ret_secret = TAKE_PTR(secret);
- *ret_secret_size = unsealed->size;
+ *ret_secret = TAKE_STRUCT(secret);
return 0;
}
static TPM2_HANDLE generate_random_nv_index(void) {
- return TPM2_NV_INDEX_FIRST + (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_LAST - TPM2_NV_INDEX_FIRST + 1);
+ return TPM2_NV_INDEX_UNASSIGNED_FIRST +
+ (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_UNASSIGNED_LAST - TPM2_NV_INDEX_UNASSIGNED_FIRST + 1);
}
int tpm2_define_policy_nv_index(
@@ -5654,8 +5809,6 @@ int tpm2_define_policy_nv_index(
const Tpm2Handle *session,
TPM2_HANDLE requested_nv_index,
const TPM2B_DIGEST *write_policy,
- const char *pin,
- const TPM2B_AUTH *auth,
TPM2_HANDLE *ret_nv_index,
Tpm2Handle **ret_nv_handle,
TPM2B_NV_PUBLIC *ret_nv_public) {
@@ -5665,7 +5818,10 @@ int tpm2_define_policy_nv_index(
int r;
assert(c);
- assert(pin || auth);
+
+ /* Allocates an nvindex to store a policy for use in PolicyAuthorizeNV in. This is where pcrlock then
+ * stores its predicted PCR policies in. If 'requested_nv_index' will try to allocate the specified
+ * nvindex, otherwise will find a free one, and use that. */
r = tpm2_handle_new(c, &new_handle);
if (r < 0)
@@ -5673,17 +5829,6 @@ int tpm2_define_policy_nv_index(
new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
- TPM2B_AUTH _auth = {};
- CLEANUP_ERASE(_auth);
-
- if (!auth) {
- r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &_auth);
- if (r < 0)
- return r;
-
- auth = &_auth;
- }
-
for (unsigned try = 0; try < 25U; try++) {
TPM2_HANDLE nv_index;
@@ -5711,7 +5856,7 @@ int tpm2_define_policy_nv_index(
/* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
/* shandle2= */ ESYS_TR_NONE,
/* shandle3= */ ESYS_TR_NONE,
- auth,
+ /* auth= */ NULL,
&public_info,
&new_handle->esys_handle);
@@ -5937,10 +6082,7 @@ int tpm2_unseal_data(
"Failed to unseal data: %s", sym_Tss2_RC_Decode(rc));
_cleanup_(iovec_done) struct iovec d = {};
- d = (struct iovec) {
- .iov_base = memdup(unsealed->buffer, unsealed->size),
- .iov_len = unsealed->size,
- };
+ d = IOVEC_MAKE(memdup(unsealed->buffer, unsealed->size), unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
@@ -6011,7 +6153,7 @@ int tpm2_list_devices(void) {
}
}
- if (table_get_rows(t) <= 1) {
+ if (table_isempty(t)) {
log_info("No suitable TPM2 devices found.");
return 0;
}
@@ -6839,6 +6981,43 @@ int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path)
return 0;
}
+int tpm2_pcrlock_policy_from_json(
+ JsonVariant *v,
+ Tpm2PCRLockPolicy *ret_policy) {
+
+ /* We use a type check of _JSON_VARIANT_TYPE_INVALID for the integer fields to allow
+ * json_dispatch_uint32() to parse strings as integers to work around the integer type weakness of
+ * JSON's design. */
+ JsonDispatch policy_dispatch[] = {
+ { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY },
+ { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY },
+ { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY },
+ { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY },
+ { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY },
+ { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY },
+ { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY },
+ { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY },
+ {}
+ };
+
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
+ int r;
+
+ assert(v);
+ assert(ret_policy);
+
+ r = json_dispatch(v, policy_dispatch, JSON_LOG, &policy);
+ if (r < 0)
+ return r;
+
+ r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json);
+ if (r < 0)
+ return r;
+
+ *ret_policy = TAKE_STRUCT(policy);
+ return 1;
+}
+
int tpm2_pcrlock_policy_load(
const char *path,
Tpm2PCRLockPolicy *ret_policy) {
@@ -6855,41 +7034,141 @@ int tpm2_pcrlock_policy_load(
if (r < 0)
return log_error_errno(r, "Failed to load TPM2 pcrlock policy file: %m");
- _cleanup_(json_variant_unrefp) JsonVariant *configuration_json = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = json_parse_file(
f,
discovered_path,
/* flags = */ 0,
- &configuration_json,
+ &v,
/* ret_line= */ NULL,
/* ret_column= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse existing pcrlock policy file '%s': %m", discovered_path);
- JsonDispatch policy_dispatch[] = {
- { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY },
- { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY },
- { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY },
- { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY },
- { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY },
- { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY },
- { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY },
- { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY },
- {}
- };
+ return tpm2_pcrlock_policy_from_json(v, ret_policy);
+}
- _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
+static int pcrlock_policy_load_credential(
+ const char *name,
+ const struct iovec *data,
+ Tpm2PCRLockPolicy *ret) {
+
+ _cleanup_free_ char *c = NULL;
+ int r;
+
+ assert(name);
- r = json_dispatch(configuration_json, policy_dispatch, JSON_LOG, &policy);
+ c = strdup(name);
+ if (!c)
+ return log_oom();
+
+ ascii_strlower(c); /* Lowercase, to match what we did at encryption time */
+
+ _cleanup_(iovec_done) struct iovec decoded = {};
+ r = decrypt_credential_and_warn(
+ c,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ UID_INVALID,
+ data,
+ CREDENTIAL_ALLOW_NULL,
+ &decoded);
if (r < 0)
return r;
- r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json);
+ if (memchr(decoded.iov_base, 0, decoded.iov_len))
+ return log_error_errno(r, "Credential '%s' contains embedded NUL byte, refusing.", name);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ r = json_parse(decoded.iov_base,
+ /* flags= */ 0,
+ &v,
+ /* ret_line= */ NULL,
+ /* ret_column= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse pcrlock policy: %m");
+
+ r = tpm2_pcrlock_policy_from_json(v, ret);
if (r < 0)
return r;
- *ret_policy = TAKE_STRUCT(policy);
- return 1;
+ return 0;
+}
+
+int tpm2_pcrlock_policy_from_credentials(
+ const struct iovec *srk,
+ const struct iovec *nv,
+ Tpm2PCRLockPolicy *ret) {
+
+ _cleanup_close_ int dfd = -EBADF;
+ int r;
+
+ /* During boot we'll not have access to the pcrlock.json file in /var/. In order to support
+ * pcrlock-bound root file systems we'll store a copy of the JSON data, wrapped in an (plaintext)
+ * credential in the ESP or XBOOTLDR partition. There might be multiple of those however (because of
+ * multi-boot), hence we use the SRK and NV data from the LUKS2 header as search key, and parse all
+ * such JSON policies until we find a matching one. */
+
+ const char *cp = secure_getenv("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY") ?: ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY;
+
+ dfd = open(cp, O_CLOEXEC|O_DIRECTORY);
+ if (dfd < 0) {
+ if (errno == ENOENT) {
+ log_debug("No encrypted system credentials passed.");
+ return 0;
+ }
+
+ return log_error_errno(errno, "Failed to open system credentials directory.");
+ }
+
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate system credentials: %m");
+
+ FOREACH_ARRAY(i, de->entries, de->n_entries) {
+ _cleanup_(iovec_done) struct iovec data = {};
+ struct dirent *d = *i;
+
+ if (!startswith_no_case(d->d_name, "pcrlock.")) /* VFAT is case-insensitive, hence don't be too strict here */
+ continue;
+
+ r = read_full_file_full(
+ dfd, d->d_name,
+ /* offset= */ UINT64_MAX,
+ /* size= */ CREDENTIAL_ENCRYPTED_SIZE_MAX,
+ READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
+ /* bind_name= */ NULL,
+ (char**) &data.iov_base,
+ &data.iov_len);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
+ continue;
+ }
+
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy loaded_policy = {};
+ r = pcrlock_policy_load_credential(
+ d->d_name,
+ &data,
+ &loaded_policy);
+ if (r < 0) {
+ log_warning_errno(r, "Loading of pcrlock policy from credential '%s/%s' failed, skipping.", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
+ continue;
+ }
+
+ if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) &&
+ (!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) {
+ *ret = TAKE_STRUCT(loaded_policy);
+ return 1;
+ }
+ }
+
+ log_info("No pcrlock policy found among system credentials.");
+ *ret = (Tpm2PCRLockPolicy) {};
+ return 0;
}
int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) {
@@ -6929,6 +7208,75 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) {
*ret = device_key_public;
return 0;
}
+
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) {
+ int r;
+
+ assert(c);
+ assert(pin);
+ assert(ret);
+
+ log_debug("Converting PIN into TPM2 HMAC-SHA256 object.");
+
+ /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that
+ * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of
+ * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize
+ * it based on the provided PIN data. */
+
+ TPM2B_PUBLIC auth_hmac_public = {
+ .publicArea = {
+ .type = TPM2_ALG_KEYEDHASH,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT,
+ .parameters.keyedHashDetail.scheme = {
+ .scheme = TPM2_ALG_HMAC,
+ .details.hmac.hashAlg = TPM2_ALG_SHA256,
+ },
+ .unique.keyedHash.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ TPM2B_SENSITIVE auth_hmac_private = {
+ .sensitiveArea = {
+ .sensitiveType = TPM2_ALG_KEYEDHASH,
+ .sensitive.bits.size = pin->size,
+ .seedValue.size = SHA256_DIGEST_SIZE,
+ },
+ };
+
+ /* Copy in the key data */
+ memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size);
+
+ /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable
+ * "name" of the PIN object */
+
+ /* Now calculate the "unique" field for the public area, based on the sensitive data, according to
+ * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */
+ struct iovec sensitive_data[] = {
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size),
+ IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size),
+ };
+ r = tpm2_digest_many(
+ auth_hmac_public.publicArea.nameAlg,
+ &auth_hmac_public.publicArea.unique.keyedHash,
+ sensitive_data,
+ ELEMENTSOF(sensitive_data),
+ /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ /* And now load the public/private parts into the TPM and get a handle back */
+ r = tpm2_load_external(
+ c,
+ session,
+ &auth_hmac_public,
+ &auth_hmac_private,
+ ret);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load PIN into TPM2: %m");
+
+ return 0;
+}
#endif
char *tpm2_pcr_mask_to_string(uint32_t mask) {
@@ -7002,18 +7350,14 @@ int tpm2_make_luks2_json(
int keyslot,
uint32_t hash_pcr_mask,
uint16_t pcr_bank,
- const void *pubkey,
- size_t pubkey_size,
+ const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
- const void *blob,
- size_t blob_size,
- const void *policy_hash,
- size_t policy_hash_size,
- const void *salt,
- size_t salt_size,
- const void *srk_buf,
- size_t srk_buf_size,
+ const struct iovec *blob,
+ const struct iovec *policy_hash,
+ const struct iovec *salt,
+ const struct iovec *srk,
+ const struct iovec *pcrlock_nv,
TPM2Flags flags,
JsonVariant **ret) {
@@ -7021,9 +7365,9 @@ int tpm2_make_luks2_json(
_cleanup_free_ char *keyslot_as_string = NULL;
int r;
- assert(blob || blob_size == 0);
- assert(policy_hash || policy_hash_size == 0);
- assert(pubkey || pubkey_size == 0);
+ assert(iovec_is_valid(pubkey));
+ assert(iovec_is_valid(blob));
+ assert(iovec_is_valid(policy_hash));
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
@@ -7046,17 +7390,18 @@ int tpm2_make_luks2_json(
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
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-blob", JSON_BUILD_IOVEC_BASE64(blob)),
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)),
- JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
- JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
- JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
- JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
- JSON_BUILD_PAIR("tpm2_pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)),
+ JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
+ JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
+ JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)),
+ JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", JSON_BUILD_BOOLEAN(true)),
+ JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", JSON_BUILD_BOOLEAN(true)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
- JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
- JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(pubkey), "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)),
+ JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv))));
if (r < 0)
return r;
@@ -7071,22 +7416,17 @@ int tpm2_parse_luks2_json(
int *ret_keyslot,
uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
- void **ret_pubkey,
- size_t *ret_pubkey_size,
+ struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_policy_hash,
- size_t *ret_policy_hash_size,
- void **ret_salt,
- size_t *ret_salt_size,
- void **ret_srk_buf,
- size_t *ret_srk_buf_size,
+ struct iovec *ret_blob,
+ struct iovec *ret_policy_hash,
+ struct iovec *ret_salt,
+ struct iovec *ret_srk,
+ struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
+ _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@@ -7151,7 +7491,7 @@ int tpm2_parse_luks2_json(
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
- r = json_variant_unbase64(w, &blob, &blob_size);
+ r = json_variant_unbase64_iovec(w, &blob);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
@@ -7159,7 +7499,7 @@ int tpm2_parse_luks2_json(
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = json_variant_unhex(w, &policy_hash, &policy_hash_size);
+ r = json_variant_unhex_iovec(w, &policy_hash);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
@@ -7181,7 +7521,7 @@ int tpm2_parse_luks2_json(
w = json_variant_by_key(v, "tpm2_salt");
if (w) {
- r = json_variant_unbase64(w, &salt, &salt_size);
+ r = json_variant_unbase64_iovec(w, &salt);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field.");
}
@@ -7195,7 +7535,7 @@ int tpm2_parse_luks2_json(
w = json_variant_by_key(v, "tpm2_pubkey");
if (w) {
- r = json_variant_unbase64(w, &pubkey, &pubkey_size);
+ r = json_variant_unbase64_iovec(w, &pubkey);
if (r < 0)
return log_debug_errno(r, "Failed to decode PCR public key.");
} else if (pubkey_pcr_mask != 0)
@@ -7203,11 +7543,18 @@ int tpm2_parse_luks2_json(
w = json_variant_by_key(v, "tpm2_srk");
if (w) {
- r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
+ r = json_variant_unbase64_iovec(w, &srk);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
}
+ w = json_variant_by_key(v, "tpm2_pcrlock_nv");
+ if (w) {
+ r = json_variant_unbase64_iovec(w, &pcrlock_nv);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2_pcrlock_nv' field.");
+ }
+
if (ret_keyslot)
*ret_keyslot = keyslot;
if (ret_hash_pcr_mask)
@@ -7215,32 +7562,23 @@ int tpm2_parse_luks2_json(
if (ret_pcr_bank)
*ret_pcr_bank = pcr_bank;
if (ret_pubkey)
- *ret_pubkey = TAKE_PTR(pubkey);
- if (ret_pubkey_size)
- *ret_pubkey_size = pubkey_size;
+ *ret_pubkey = TAKE_STRUCT(pubkey);
if (ret_pubkey_pcr_mask)
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
if (ret_blob)
- *ret_blob = TAKE_PTR(blob);
- if (ret_blob_size)
- *ret_blob_size = blob_size;
+ *ret_blob = TAKE_STRUCT(blob);
if (ret_policy_hash)
- *ret_policy_hash = TAKE_PTR(policy_hash);
- if (ret_policy_hash_size)
- *ret_policy_hash_size = policy_hash_size;
+ *ret_policy_hash = TAKE_STRUCT(policy_hash);
if (ret_salt)
- *ret_salt = TAKE_PTR(salt);
- if (ret_salt_size)
- *ret_salt_size = salt_size;
+ *ret_salt = TAKE_STRUCT(salt);
+ if (ret_srk)
+ *ret_srk = TAKE_STRUCT(srk);
+ if (ret_pcrlock_nv)
+ *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv);
if (ret_flags)
*ret_flags = flags;
- if (ret_srk_buf)
- *ret_srk_buf = TAKE_PTR(srk_buf);
- if (ret_srk_buf_size)
- *ret_srk_buf_size = srk_buf_size;
-
return 0;
}
@@ -7553,7 +7891,7 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) {
/* Tries to load a JSON PCR signature file. Takes an absolute path, a simple file name or NULL. In
* the latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */
- search = strv_split_nulstr(CONF_PATHS_NULSTR("systemd"));
+ search = strv_new(CONF_PATHS("systemd"));
if (!search)
return log_oom_debug();
@@ -7618,7 +7956,7 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass,
size_t saltlen,
uint8_t ret_key[static SHA256_DIGEST_SIZE]) {
- uint8_t _cleanup_(erase_and_freep) *buffer = NULL;
+ _cleanup_(erase_and_freep) uint8_t *buffer = NULL;
uint8_t u[SHA256_DIGEST_SIZE];
/* To keep this simple, since derived KeyLen (dkLen in docs)
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 911a3c7..ed306d4 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -132,6 +132,7 @@ int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t
int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size);
int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed);
+int tpm2_get_name(Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name);
bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg);
bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command);
@@ -246,8 +247,10 @@ typedef struct Tpm2PCRLockPolicy {
} Tpm2PCRLockPolicy;
void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data);
+int tpm2_pcrlock_policy_from_json(JsonVariant *v, Tpm2PCRLockPolicy *ret_policy);
int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path);
int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy);
+int tpm2_pcrlock_policy_from_credentials(const struct iovec *srk, const struct iovec *nv, Tpm2PCRLockPolicy *ret);
int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index);
@@ -255,7 +258,7 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE
int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values);
int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values);
-int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
+int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth);
int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin);
int tpm2_set_auth_binary(Tpm2Context *c, const Tpm2Handle *handle, const TPM2B_AUTH *auth);
@@ -266,6 +269,7 @@ int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tp
int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm);
+int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest);
int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name);
int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name);
@@ -276,9 +280,10 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
+int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name);
int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
-int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size);
+int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent);
int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template);
int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
@@ -286,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
-int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
-int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk);
+int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret);
#if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
@@ -297,7 +302,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret)
int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret);
int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size);
-int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, const char *pin, const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
+int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest);
int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
@@ -309,6 +314,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s
int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret);
+int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret);
+
/* The tpm2-tss library has many structs that are simply a combination of an array (or object) and
* size. These macros allow easily initializing or assigning instances of such structs from an existing
* buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the
@@ -384,8 +391,8 @@ int tpm2_find_device_auto(char **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)
@@ -477,3 +484,14 @@ enum {
int tpm2_pcr_index_from_string(const char *s) _pure_;
const char *tpm2_pcr_index_to_string(int pcr) _const_;
+
+/* The first and last NV index handle that is not registered to any company, as per TCG's "Registry of
+ * Reserved TPM 2.0 Handles and Localities", section 2.2.2. */
+#define TPM2_NV_INDEX_UNASSIGNED_FIRST UINT32_C(0x01800000)
+#define TPM2_NV_INDEX_UNASSIGNED_LAST UINT32_C(0x01BFFFFF)
+
+#if HAVE_TPM2
+/* Verify that the above is indeed a subset of the general NV Index range */
+assert_cc(TPM2_NV_INDEX_UNASSIGNED_FIRST >= TPM2_NV_INDEX_FIRST);
+assert_cc(TPM2_NV_INDEX_UNASSIGNED_LAST <= TPM2_NV_INDEX_LAST);
+#endif
diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c
index cf28ba8..15996ca 100644
--- a/src/shared/udev-util.c
+++ b/src/shared/udev-util.c
@@ -14,6 +14,7 @@
#include "id128-util.h"
#include "log.h"
#include "macro.h"
+#include "missing_threads.h"
#include "parse-util.h"
#include "path-util.h"
#include "signal-util.h"
@@ -22,43 +23,35 @@
#include "udev-util.h"
#include "utf8.h"
-int udev_set_max_log_level(char *str) {
- size_t n;
+int udev_parse_config_full(const ConfigTableItem config_table[]) {
+ int r;
- /* This may modify input string. */
+ assert(config_table);
- if (isempty(str))
+ r = config_parse_standard_file_with_dropins(
+ "udev/udev.conf",
+ /* sections = */ NULL,
+ config_item_table_lookup, config_table,
+ CONFIG_PARSE_WARN,
+ /* userdata = */ NULL);
+ if (r == -ENOENT)
return 0;
-
- /* unquote */
- n = strlen(str);
- if (n >= 2 &&
- ((str[0] == '"' && str[n - 1] == '"') ||
- (str[0] == '\'' && str[n - 1] == '\''))) {
- str[n - 1] = '\0';
- str++;
- }
-
- /* we set the udev log level here explicitly, this is supposed
- * to regulate the code in libudev/ and udev/. */
- return log_set_max_level_from_string(str);
+ return r;
}
int udev_parse_config(void) {
- _cleanup_free_ char *log_val = NULL;
- int r;
+ int r, log_val = -1;
+ const ConfigTableItem config_table[] = {
+ { NULL, "udev_log", config_parse_log_level, 0, &log_val },
+ {}
+ };
- r = parse_env_file(NULL, "/etc/udev/udev.conf",
- "udev_log", &log_val);
- if (r == -ENOENT)
- return 0;
+ r = udev_parse_config_full(config_table);
if (r < 0)
return r;
- r = udev_set_max_log_level(log_val);
- if (r < 0)
- log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r,
- "Failed to set udev log level '%s', ignoring: %m", log_val);
+ if (log_val >= 0)
+ log_set_max_level(log_val);
return 0;
}
@@ -146,7 +139,7 @@ static int device_wait_for_initialization_internal(
}
if (device) {
- if (sd_device_get_is_initialized(device) > 0) {
+ if (device_is_processed(device) > 0) {
if (ret)
*ret = sd_device_ref(device);
return 0;
@@ -209,7 +202,7 @@ static int device_wait_for_initialization_internal(
if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r))
return log_error_errno(r, "Failed to create sd-device object from %s: %m", devlink);
}
- if (device && sd_device_get_is_initialized(device) > 0) {
+ if (device && device_is_processed(device) > 0) {
if (ret)
*ret = sd_device_ref(device);
return 0;
@@ -237,13 +230,34 @@ int device_is_renaming(sd_device *dev) {
assert(dev);
- r = sd_device_get_property_value(dev, "ID_RENAMING", NULL);
+ r = device_get_property_bool(dev, "ID_RENAMING");
if (r == -ENOENT)
- return false;
+ return false; /* defaults to false */
+
+ return r;
+}
+
+int device_is_processed(sd_device *dev) {
+ int r;
+
+ assert(dev);
+
+ /* sd_device_get_is_initialized() only checks if the udev database file exists. However, even if the
+ * database file exist, systemd-udevd may be still processing the device, e.g. when the udev rules
+ * for the device have RUN tokens. See issue #30056. Hence, to check if the device is really
+ * processed by systemd-udevd, we also need to read ID_PROCESSING property. */
+
+ r = sd_device_get_is_initialized(dev);
+ if (r <= 0)
+ return r;
+
+ r = device_get_property_bool(dev, "ID_PROCESSING");
+ if (r == -ENOENT)
+ return true; /* If the property does not exist, then it means that the device is processed. */
if (r < 0)
return r;
- return true;
+ return !r;
}
bool device_for_action(sd_device *dev, sd_device_action_t a) {
@@ -369,18 +383,22 @@ int udev_queue_is_empty(void) {
(errno == ENOENT ? true : -errno) : false;
}
-bool udev_available(void) {
- static int cache = -1;
+static int cached_udev_availability = -1;
+void reset_cached_udev_availability(void) {
+ cached_udev_availability = -1;
+}
+
+bool udev_available(void) {
/* The service systemd-udevd is started only when /sys is read write.
* See systemd-udevd.service: ConditionPathIsReadWrite=/sys
* Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must
* be mounted in read-only mode in containers. */
- if (cache >= 0)
- return cache;
+ if (cached_udev_availability >= 0)
+ return cached_udev_availability;
- return (cache = (path_is_read_only_fs("/sys/") <= 0));
+ return (cached_udev_availability = (path_is_read_only_fs("/sys/") <= 0));
}
int device_get_vendor_string(sd_device *device, const char **ret) {
diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h
index 651d335..c21c4c1 100644
--- a/src/shared/udev-util.h
+++ b/src/shared/udev-util.h
@@ -3,15 +3,17 @@
#include "sd-device.h"
+#include "conf-parser.h"
#include "hashmap.h"
#include "time-util.h"
-int udev_set_max_log_level(char *str);
+int udev_parse_config_full(const ConfigTableItem config_table[]);
int udev_parse_config(void);
int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret);
int device_wait_for_devlink(const char *path, const char *subsystem, usec_t timeout_usec, sd_device **ret);
int device_is_renaming(sd_device *dev);
+int device_is_processed(sd_device *dev);
bool device_for_action(sd_device *dev, sd_device_action_t action);
@@ -22,6 +24,7 @@ size_t udev_replace_chars(char *str, const char *allow);
int udev_queue_is_empty(void);
+void reset_cached_udev_availability(void);
bool udev_available(void);
int device_get_vendor_string(sd_device *device, const char **ret);
diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c
index 414a493..ffb5721 100644
--- a/src/shared/user-record-nss.c
+++ b/src/shared/user-record-nss.c
@@ -208,39 +208,17 @@ int nss_user_record_by_name(
bool with_shadow,
UserRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct passwd pwd, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct passwd *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct spwd spwd, *sresult = NULL;
int r;
assert(name);
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getpwnam_r(name, &pwd, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
- if (r != ERANGE)
- return -r;
-
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getpwnam_malloc(name, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
@@ -266,36 +244,15 @@ int nss_user_record_by_uid(
bool with_shadow,
UserRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct passwd pwd, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct passwd *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct spwd spwd, *sresult = NULL;
int r;
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getpwuid_r(uid, &pwd, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
- if (r != ERANGE)
- return -r;
-
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getpwuid_malloc(uid, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
@@ -422,38 +379,17 @@ int nss_group_record_by_name(
bool with_shadow,
GroupRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct group grp, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct group *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(name);
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getgrnam_r(name, &grp, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
-
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
- if (r != ERANGE)
- return -r;
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getgrnam_malloc(name, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
@@ -479,35 +415,15 @@ int nss_group_record_by_gid(
bool with_shadow,
GroupRecord **ret) {
- _cleanup_free_ char *buf = NULL, *sbuf = NULL;
- struct group grp, *result;
+ _cleanup_free_ char *sbuf = NULL;
+ _cleanup_free_ struct group *result = NULL;
bool incomplete = false;
- size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
- for (;;) {
- buf = malloc(buflen);
- if (!buf)
- return -ENOMEM;
-
- r = getgrgid_r(gid, &grp, buf, buflen, &result);
- if (r == 0) {
- if (!result)
- return -ESRCH;
- break;
- }
-
- if (r < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
- if (r != ERANGE)
- return -r;
- if (buflen > SIZE_MAX / 2)
- return -ERANGE;
-
- buflen *= 2;
- buf = mfree(buf);
- }
+ r = getgrgid_malloc(gid, &result);
+ if (r < 0)
+ return r;
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c
index 28fa7a8..23a4153 100644
--- a/src/shared/user-record-show.c
+++ b/src/shared/user-record-show.c
@@ -3,8 +3,14 @@
#include "cap-list.h"
#include "format-util.h"
#include "fs-util.h"
+#include "glyph-util.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "path-util.h"
+#include "pretty-print.h"
#include "process-util.h"
#include "rlimit-util.h"
+#include "sha256.h"
#include "strv.h"
#include "terminal-util.h"
#include "user-record-show.h"
@@ -23,6 +29,7 @@ const char *user_record_state_color(const char *state) {
}
void user_record_show(UserRecord *hr, bool show_full_group_info) {
+ _cleanup_strv_free_ char **langs = NULL;
const char *hd, *ip, *shell;
UserStorage storage;
usec_t t;
@@ -203,8 +210,45 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
printf(" Real Name: %s\n", hr->real_name);
hd = user_record_home_directory(hr);
- if (hd)
- printf(" Directory: %s\n", hd);
+ if (hd) {
+ printf(" Directory: %s", hd);
+
+ if (hr->fallback_home_directory && hr->use_fallback)
+ printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal());
+
+ printf("\n");
+ }
+
+ if (hr->blob_directory) {
+ _cleanup_free_ char **filenames = NULL;
+ size_t n_filenames = 0;
+
+ r = hashmap_dump_keys_sorted(hr->blob_manifest, (void***) &filenames, &n_filenames);
+ if (r < 0) {
+ errno = -r;
+ printf(" Blob Dir.: %s (can't iterate: %m)\n", hr->blob_directory);
+ } else
+ printf(" Blob Dir.: %s\n", hr->blob_directory);
+
+ for (size_t i = 0; i < n_filenames; i++) {
+ _cleanup_free_ char *path = NULL, *link = NULL, *hash = NULL;
+ const char *filename = filenames[i];
+ const uint8_t *hash_bytes = hashmap_get(hr->blob_manifest, filename);
+ bool last = i == n_filenames - 1;
+
+ path = path_join(hr->blob_directory, filename);
+ if (path)
+ (void) terminal_urlify_path(path, filename, &link);
+ hash = hexmem(hash_bytes, SHA256_DIGEST_SIZE);
+
+ printf(" %s %s %s(%s)%s\n",
+ special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH),
+ link ?: filename,
+ ansi_grey(),
+ hash ?: "can't display hash",
+ ansi_normal());
+ }
+ }
storage = user_record_storage(hr);
if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
@@ -222,8 +266,14 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
printf(" Removable: %s\n", yes_no(b));
shell = user_record_shell(hr);
- if (shell)
- printf(" Shell: %s\n", shell);
+ if (shell) {
+ printf(" Shell: %s", shell);
+
+ if (hr->fallback_shell && hr->use_fallback)
+ printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal());
+
+ printf("\n");
+ }
if (hr->email_address)
printf(" Email: %s\n", hr->email_address);
@@ -237,15 +287,15 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->time_zone)
printf(" Time Zone: %s\n", hr->time_zone);
- if (hr->preferred_language)
- printf(" Language: %s\n", hr->preferred_language);
-
- if (!strv_isempty(hr->environment))
- STRV_FOREACH(i, hr->environment) {
- printf(i == hr->environment ?
- " Environment: %s\n" :
- " %s\n", *i);
- }
+ r = user_record_languages(hr, &langs);
+ if (r < 0) {
+ errno = -r;
+ printf(" Languages: (can't acquire: %m)\n");
+ } else if (!strv_isempty(langs)) {
+ STRV_FOREACH(i, langs)
+ printf(i == langs ? " Languages: %s" : ", %s", *i);
+ printf("\n");
+ }
if (hr->locked >= 0)
printf(" Locked: %s\n", yes_no(hr->locked));
@@ -525,6 +575,11 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->auto_login >= 0)
printf("Autom. Login: %s\n", yes_no(hr->auto_login));
+ if (hr->preferred_session_launcher)
+ printf("Sess. Launch: %s\n", hr->preferred_session_launcher);
+ if (hr->preferred_session_type)
+ printf("Session Type: %s\n", hr->preferred_session_type);
+
if (hr->kill_processes >= 0)
printf(" Kill Proc.: %s\n", yes_no(hr->kill_processes));
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index 3fe3e80..ec084de 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -10,15 +10,18 @@
#include "glyph-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "locale-util.h"
#include "memory-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rlimit-util.h"
+#include "sha256.h"
#include "string-table.h"
#include "strv.h"
-#include "uid-alloc-range.h"
+#include "uid-classification.h"
#include "user-record.h"
#include "user-util.h"
+#include "utf8.h"
#define DEFAULT_RATELIMIT_BURST 30
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
@@ -141,11 +144,15 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->location);
free(h->icon_name);
+ free(h->blob_directory);
+ hashmap_free(h->blob_manifest);
+
free(h->shell);
strv_free(h->environment);
free(h->time_zone);
free(h->preferred_language);
+ strv_free(h->additional_languages);
rlimit_free_all(h->rlimits);
free(h->skeleton_directory);
@@ -165,6 +172,9 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->home_directory);
free(h->home_directory_auto);
+ free(h->fallback_shell);
+ free(h->fallback_home_directory);
+
strv_free(h->member_of);
strv_free(h->capability_bounding_set);
strv_free(h->capability_ambient_set);
@@ -179,6 +189,9 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->state);
free(h->service);
+ free(h->preferred_session_type);
+ free(h->preferred_session_launcher);
+
strv_free(h->pkcs11_token_uri);
for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
@@ -535,44 +548,65 @@ static int json_dispatch_environment(const char *name, JsonVariant *variant, Jso
return strv_free_and_replace(*l, n);
}
-int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserDisposition *disposition = userdata, k;
+static int json_dispatch_locale(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ char **s = userdata;
+ const char *n;
+ int r;
if (json_variant_is_null(variant)) {
- *disposition = _USER_DISPOSITION_INVALID;
+ *s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- k = user_disposition_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Disposition type '%s' not known.", json_variant_string(variant));
+ n = json_variant_string(variant);
+
+ if (!locale_is_valid(n))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name));
+
+ r = free_and_strdup(s, n);
+ if (r < 0)
+ return json_log(variant, flags, r, "Failed to allocate string: %m");
- *disposition = k;
return 0;
}
-static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
- UserStorage *storage = userdata, k;
+static int json_dispatch_locales(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ _cleanup_strv_free_ char **n = NULL;
+ char ***l = userdata;
+ const char *locale;
+ JsonVariant *e;
+ int r;
if (json_variant_is_null(variant)) {
- *storage = _USER_STORAGE_INVALID;
+ *l = strv_free(*l);
return 0;
}
- if (!json_variant_is_string(variant))
- return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
+ if (!json_variant_is_array(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
- k = user_storage_from_string(json_variant_string(variant));
- if (k < 0)
- return json_log(variant, flags, k, "Storage type '%s' not known.", json_variant_string(variant));
+ JSON_VARIANT_ARRAY_FOREACH(e, variant) {
+ if (!json_variant_is_string(e))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
- *storage = k;
- return 0;
+ locale = json_variant_string(e);
+ if (!locale_is_valid(locale))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name));
+
+ r = strv_extend(&n, locale);
+ if (r < 0)
+ return json_log_oom(variant, flags);
+ }
+
+ return strv_free_and_replace(*l, n);
}
+JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string);
+static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string);
+
static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *limit = userdata, k;
@@ -746,7 +780,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
@@ -813,7 +847,7 @@ static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
@@ -843,7 +877,7 @@ static int dispatch_fido2_hmac_credential_array(const char *name, JsonVariant *v
if (!array)
return log_oom();
- r = unbase64mem(json_variant_string(e), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(e), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
@@ -873,7 +907,7 @@ static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
- r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l);
+ r = unbase64mem(json_variant_string(variant), &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
@@ -1048,6 +1082,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch binding_dispatch_table[] = {
+ { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
@@ -1055,7 +1090,7 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
@@ -1084,6 +1119,52 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch
return json_dispatch(m, binding_dispatch_table, flags, userdata);
}
+static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ _cleanup_hashmap_free_ Hashmap *manifest = NULL;
+ Hashmap **ret = ASSERT_PTR(userdata);
+ JsonVariant *value;
+ const char *key;
+ int r;
+
+ if (!variant)
+ return 0;
+
+ if (!json_variant_is_object(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
+
+ JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
+ _cleanup_free_ char *filename = NULL;
+ _cleanup_free_ uint8_t *hash = NULL;
+
+ if (!json_variant_is_string(value))
+ return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
+
+ if (!suitable_blob_filename(key))
+ return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
+
+ filename = strdup(key);
+ if (!filename)
+ return json_log_oom(value, flags);
+
+ hash = malloc(SHA256_DIGEST_SIZE);
+ if (!hash)
+ return json_log_oom(value, flags);
+
+ r = parse_sha256(json_variant_string(value), hash);
+ if (r < 0)
+ return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value));
+
+ r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
+ if (r < 0)
+ return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
+ TAKE_PTR(filename); /* Ownership transfers to hashmap */
+ TAKE_PTR(hash);
+ }
+
+ hashmap_free_and_replace(*ret, manifest);
+ return 0;
+}
+
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) {
sd_id128_t mid;
int r;
@@ -1168,24 +1249,54 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
return false;
}
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) {
+ JsonVariant *m;
+ int r;
+
+ assert(json_variant_is_object(entry));
+
+ m = json_variant_by_key(entry, "matchMachineId");
+ if (m) {
+ r = per_machine_id_match(m, flags);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return true;
+ }
+
+ m = json_variant_by_key(entry, "matchHostname");
+ if (m) {
+ r = per_machine_hostname_match(m, flags);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return true;
+ }
+
+ return false;
+}
+
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch per_machine_dispatch_table[] = {
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
+ { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
+ { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
- { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
+ { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
+ { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
@@ -1232,6 +1343,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
+ { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE },
+ { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE },
{ "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
@@ -1254,33 +1367,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
- bool matching = false;
- JsonVariant *m;
-
if (!json_variant_is_object(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
- m = json_variant_by_key(e, "matchMachineId");
- if (m) {
- r = per_machine_id_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
-
- if (!matching) {
- m = json_variant_by_key(e, "matchHostname");
- if (m) {
- r = per_machine_hostname_match(m, flags);
- if (r < 0)
- return r;
-
- matching = r > 0;
- }
- }
-
- if (!matching)
+ r = per_machine_match(e, flags);
+ if (r < 0)
+ return r;
+ if (r == 0)
continue;
r = json_dispatch(e, per_machine_dispatch_table, flags, userdata);
@@ -1294,23 +1387,26 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch status_dispatch_table[] = {
- { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
- { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
- { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
- { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
- { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
- { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
- { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
- { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
- { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
- { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
- { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
- { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
- { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
- { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
- { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 },
- { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
- { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
+ { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
+ { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
+ { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
+ { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
+ { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
+ { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
+ { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
+ { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
+ { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
+ { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
+ { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
+ { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
+ { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
+ { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
+ { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, removable), 0 },
+ { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
+ { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
+ { "fallbackShell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell), 0 },
+ { "fallbackHomeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, fallback_home_directory), 0 },
+ { "useFallback", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, use_fallback), 0 },
{},
};
@@ -1523,6 +1619,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
static const JsonDispatch user_dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
+ { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
+ { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
@@ -1534,13 +1632,14 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
- { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
+ { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 },
+ { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
- { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
+ { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
@@ -1589,6 +1688,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
+ { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE },
+ { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE },
{ "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
@@ -1625,7 +1726,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
if (r < 0)
return r;
- r = json_dispatch(h->json, user_dispatch_table, json_flags, h);
+ r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h);
if (r < 0)
return r;
@@ -1720,7 +1821,7 @@ mode_t user_record_access_mode(UserRecord *h) {
return h->access_mode != MODE_INVALID ? h->access_mode : 0700;
}
-const char* user_record_home_directory(UserRecord *h) {
+static const char *user_record_home_directory_real(UserRecord *h) {
assert(h);
if (h->home_directory)
@@ -1735,6 +1836,15 @@ const char* user_record_home_directory(UserRecord *h) {
return "/";
}
+const char* user_record_home_directory(UserRecord *h) {
+ assert(h);
+
+ if (h->use_fallback && h->fallback_home_directory)
+ return h->fallback_home_directory;
+
+ return user_record_home_directory_real(h);
+}
+
const char *user_record_image_path(UserRecord *h) {
assert(h);
@@ -1743,7 +1853,9 @@ const char *user_record_image_path(UserRecord *h) {
if (h->image_path_auto)
return h->image_path_auto;
- return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL;
+ /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */
+ return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ?
+ user_record_home_directory_real(h) : NULL;
}
const char *user_record_cifs_user_name(UserRecord *h) {
@@ -1760,7 +1872,7 @@ unsigned long user_record_mount_flags(UserRecord *h) {
(h->nodev ? MS_NODEV : 0);
}
-const char *user_record_shell(UserRecord *h) {
+static const char *user_record_shell_real(UserRecord *h) {
assert(h);
if (h->shell)
@@ -1775,6 +1887,21 @@ const char *user_record_shell(UserRecord *h) {
return NOLOGIN;
}
+const char *user_record_shell(UserRecord *h) {
+ const char *shell;
+
+ assert(h);
+
+ shell = user_record_shell_real(h);
+
+ /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell,
+ * then let's not risk anything. */
+ if (h->use_fallback && h->fallback_shell)
+ return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell;
+
+ return shell;
+}
+
const char *user_record_real_name(UserRecord *h) {
assert(h);
@@ -2062,6 +2189,27 @@ uint64_t user_record_capability_ambient_set(UserRecord *h) {
return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h);
}
+int user_record_languages(UserRecord *h, char ***ret) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
+
+ assert(h);
+ assert(ret);
+
+ if (h->preferred_language) {
+ l = strv_new(h->preferred_language);
+ if (!l)
+ return -ENOMEM;
+ }
+
+ r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);
@@ -2288,6 +2436,13 @@ int user_record_test_password_change_required(UserRecord *h) {
return change_permitted ? 0 : -EROFS;
}
+int suitable_blob_filename(const char *name) {
+ /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
+ return filename_is_valid(name) &&
+ in_charset(name, URI_UNRESERVED) &&
+ name[0] != '.';
+}
+
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
[USER_CLASSIC] = "classic",
[USER_LUKS] = "luks",
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index 298dc24..cca112f 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -6,6 +6,7 @@
#include "sd-id128.h"
+#include "hashmap.h"
#include "json.h"
#include "missing_resource.h"
#include "time-util.h"
@@ -243,6 +244,9 @@ typedef struct UserRecord {
char *icon_name;
char *location;
+ char *blob_directory;
+ Hashmap *blob_manifest;
+
UserDisposition disposition;
uint64_t last_change_usec;
uint64_t last_password_change_usec;
@@ -252,6 +256,7 @@ typedef struct UserRecord {
char **environment;
char *time_zone;
char *preferred_language;
+ char **additional_languages;
int nice_level;
struct rlimit *rlimits[_RLIMIT_MAX];
@@ -292,6 +297,10 @@ typedef struct UserRecord {
char *home_directory;
char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */
+ /* fallback shell and home dir */
+ char *fallback_shell;
+ char *fallback_home_directory;
+
uid_t uid;
gid_t gid;
@@ -321,6 +330,8 @@ typedef struct UserRecord {
uint64_t disk_ceiling;
uint64_t disk_floor;
+ bool use_fallback; /* if true → use fallback_shell + fallback_home_directory instead of the regular ones */
+
char *state;
char *service;
int signed_locally;
@@ -340,6 +351,9 @@ typedef struct UserRecord {
int auto_login;
int drop_caches;
+ char *preferred_session_type;
+ char *preferred_session_launcher;
+
uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */
int kill_processes; /* Whether to kill user processes forcibly on log-out */
@@ -415,6 +429,7 @@ AutoResizeMode user_record_auto_resize_mode(UserRecord *h);
uint64_t user_record_rebalance_weight(UserRecord *h);
uint64_t user_record_capability_bounding_set(UserRecord *h);
uint64_t user_record_capability_ambient_set(UserRecord *h);
+int user_record_languages(UserRecord *h, char ***ret);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);
@@ -438,8 +453,12 @@ int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonD
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags);
int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
+int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags);
int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
+#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB)
+int suitable_blob_filename(const char *name);
+
const char* user_storage_to_string(UserStorage t) _const_;
UserStorage user_storage_from_string(const char *s) _pure_;
diff --git a/src/shared/userdb.c b/src/shared/userdb.c
index f60d48a..75dece3 100644
--- a/src/shared/userdb.c
+++ b/src/shared/userdb.c
@@ -199,7 +199,7 @@ static int userdb_on_query_reply(
assert_se(!iterator->found_user);
- r = json_dispatch(parameters, dispatch_table, 0, &user_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data);
if (r < 0)
goto finish;
@@ -256,7 +256,7 @@ static int userdb_on_query_reply(
assert_se(!iterator->found_group);
- r = json_dispatch(parameters, dispatch_table, 0, &group_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data);
if (r < 0)
goto finish;
@@ -309,7 +309,7 @@ static int userdb_on_query_reply(
assert(!iterator->found_user_name);
assert(!iterator->found_group_name);
- r = json_dispatch(parameters, dispatch_table, 0, &membership_data);
+ r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data);
if (r < 0)
goto finish;
@@ -1455,7 +1455,9 @@ int userdb_block_nss_systemd(int b) {
return 0;
}
- call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
+ log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
+
+ call = dlsym(dl, "_nss_systemd_block");
if (!call)
/* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c
index 655324c..6748343 100644
--- a/src/shared/varlink-idl.c
+++ b/src/shared/varlink-idl.c
@@ -17,6 +17,9 @@ enum {
_COLOR_MAX,
};
+#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__)
+#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__)
+
static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]);
static int varlink_idl_format_enum_values(
@@ -401,7 +404,7 @@ static int varlink_interface_realloc(VarlinkInterface **interface, size_t n_symb
assert(interface);
- n_symbols ++; /* Space for trailing NULL end marker symbol */
+ n_symbols++; /* Space for trailing NULL end marker symbol */
/* Overflow check */
if (n_symbols > (SIZE_MAX - offsetof(VarlinkInterface, symbols)) / sizeof(VarlinkSymbol*))
@@ -420,7 +423,7 @@ static int varlink_symbol_realloc(VarlinkSymbol **symbol, size_t n_fields) {
assert(symbol);
- n_fields ++; /* Space for trailing end marker field */
+ n_fields++; /* Space for trailing end marker field */
/* Overflow check */
if (n_fields > (SIZE_MAX - offsetof(VarlinkSymbol, fields)) / sizeof(VarlinkField))
@@ -512,7 +515,7 @@ static int varlink_idl_subparse_token(
l = token_match(*p, allowed_delimiters, allowed_chars);
if (l == 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
}
t = strndup(*p, l);
@@ -662,7 +665,7 @@ static int varlink_idl_subparse_field_type(
if (r < 0)
return r;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
field->named_type = TAKE_PTR(token);
field->field_type = VARLINK_NAMED_TYPE;
@@ -704,7 +707,7 @@ static int varlink_idl_subparse_struct_or_enum(
assert(n_fields);
if (depth > DEPTH_MAX)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX);
while (state != STATE_DONE) {
_cleanup_free_ char *token = NULL;
@@ -723,9 +726,9 @@ static int varlink_idl_subparse_struct_or_enum(
case STATE_OPEN:
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (!streq(token, "("))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
state = STATE_NAME;
allowed_delimiters = ")";
@@ -736,7 +739,7 @@ static int varlink_idl_subparse_struct_or_enum(
assert(!field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ")"))
state = STATE_DONE;
else {
@@ -752,7 +755,7 @@ static int varlink_idl_subparse_struct_or_enum(
assert(field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ":")) {
VarlinkField *field;
@@ -760,7 +763,7 @@ static int varlink_idl_subparse_struct_or_enum(
if ((*symbol)->symbol_type < 0)
(*symbol)->symbol_type = VARLINK_STRUCT_TYPE;
if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column);
r = varlink_symbol_realloc(symbol, *n_fields + 1);
if (r < 0)
@@ -787,7 +790,7 @@ static int varlink_idl_subparse_struct_or_enum(
if ((*symbol)->symbol_type < 0)
(*symbol)->symbol_type = VARLINK_ENUM_TYPE;
if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column);
r = varlink_symbol_realloc(symbol, *n_fields + 1);
if (r < 0)
@@ -808,7 +811,7 @@ static int varlink_idl_subparse_struct_or_enum(
state = STATE_DONE;
}
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
@@ -816,7 +819,7 @@ static int varlink_idl_subparse_struct_or_enum(
assert(!field_name);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, ",")) {
state = STATE_NAME;
allowed_delimiters = NULL;
@@ -824,7 +827,7 @@ static int varlink_idl_subparse_struct_or_enum(
} else if (streq(token, ")"))
state = STATE_DONE;
else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
default:
@@ -835,7 +838,7 @@ static int varlink_idl_subparse_struct_or_enum(
/* If we don't know the type of the symbol by now it was an empty () which doesn't allow us to
* determine if we look at an enum or a struct */
if ((*symbol)->symbol_type < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column);
return 0;
}
@@ -854,14 +857,14 @@ static int varlink_idl_resolve_symbol_types(VarlinkInterface *interface, Varlink
continue;
if (!field->named_type)
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type);
if (!found)
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE))
- return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
field->symbol = found;
}
@@ -932,7 +935,7 @@ int varlink_idl_parse(
case STATE_PRE_INTERFACE:
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (streq(token, "#")) {
r = varlink_idl_subparse_comment(&text, line, column);
if (r < 0)
@@ -942,7 +945,7 @@ int varlink_idl_parse(
allowed_delimiters = NULL;
allowed_chars = VALID_CHARS_INTERFACE_NAME;
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
case STATE_INTERFACE:
@@ -950,7 +953,7 @@ int varlink_idl_parse(
assert(n_symbols == 0);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_interface_realloc(&interface, n_symbols);
if (r < 0)
@@ -982,7 +985,7 @@ int varlink_idl_parse(
state = STATE_ERROR;
allowed_chars = VALID_CHARS_IDENTIFIER;
} else
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
break;
@@ -991,7 +994,7 @@ int varlink_idl_parse(
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
@@ -1012,10 +1015,10 @@ int varlink_idl_parse(
assert(symbol);
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
if (!streq(token, "->"))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token);
r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT, 0);
if (r < 0)
@@ -1036,7 +1039,7 @@ int varlink_idl_parse(
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
@@ -1064,7 +1067,7 @@ int varlink_idl_parse(
n_fields = 0;
if (!token)
- return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column);
r = varlink_symbol_realloc(&symbol, n_fields);
if (r < 0)
@@ -1213,48 +1216,48 @@ static int varlink_idl_field_consistent(
symbol_name = symbol->name ?: "<anonymous>";
if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
if (field->field_type == VARLINK_ENUM_VALUE) {
if (symbol->symbol_type != VARLINK_ENUM_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
if (field->field_flags != 0)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
} else {
if (symbol->symbol_type == VARLINK_ENUM_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
}
if (symbol->symbol_type != VARLINK_METHOD) {
if (field->field_direction != VARLINK_REGULAR)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
} else {
if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
}
if (field->symbol) {
if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
if (field->field_type == VARLINK_NAMED_TYPE) {
const VarlinkSymbol *found;
if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do not match, refusing.", field->name, symbol_name);
/* If this is a named type, then check if it's properly part of the interface */
found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name);
if (!found)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
} else {
/* If this is an anonymous type, then we recursively check if it's consistent, since
* it's not part of the interface, and hence we won't validate it from there. */
@@ -1266,18 +1269,18 @@ static int varlink_idl_field_consistent(
} else {
if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
if (field->named_type)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
}
if (field->named_type) {
if (field->field_type != VARLINK_NAMED_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
} else {
if (field->field_type == VARLINK_NAMED_TYPE)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
}
return 0;
@@ -1304,19 +1307,19 @@ static int varlink_idl_symbol_consistent(
symbol_name = symbol->name ?: "<anonymous>";
if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX)
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */
if (!varlink_idl_field_name_is_valid(field->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
if (set_contains(*name_set, field->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0)
return log_oom();
@@ -1336,15 +1339,15 @@ int varlink_idl_consistent(const VarlinkInterface *interface, int level) {
assert(interface);
if (!varlink_idl_interface_name_is_valid(interface->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) {
if (!varlink_idl_symbol_name_is_valid((*symbol)->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
if (set_contains(name_set, (*symbol)->name))
- return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0)
return log_oom();
@@ -1371,31 +1374,32 @@ static int varlink_idl_validate_field_element_type(const VarlinkField *field, Js
case VARLINK_BOOL:
if (!json_variant_is_boolean(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
break;
case VARLINK_INT:
- if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
+ /* Allow strings here too, since integers with > 53 bits are often passed in as strings */
+ if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v) && !json_variant_is_string(v))
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
break;
case VARLINK_FLOAT:
if (!json_variant_is_number(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
break;
case VARLINK_STRING:
if (!json_variant_is_string(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
break;
case VARLINK_OBJECT:
if (!json_variant_is_object(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
break;
@@ -1414,13 +1418,13 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v)
if (!v || json_variant_is_null(v)) {
if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
} else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) {
JsonVariant *i;
if (!json_variant_is_array(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
JSON_VARIANT_ARRAY_FOREACH(i, v) {
r = varlink_idl_validate_field_element_type(field, i);
@@ -1433,7 +1437,7 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v)
JsonVariant *e;
if (!json_variant_is_object(v))
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
JSON_VARIANT_OBJECT_FOREACH(k, e, v) {
r = varlink_idl_validate_field_element_type(field, e);
@@ -1458,7 +1462,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
if (!v) {
if (bad_field)
*bad_field = NULL;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
}
switch (symbol->symbol_type) {
@@ -1470,7 +1474,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
if (!json_variant_is_string(v)) {
if (bad_field)
*bad_field = symbol->name;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
}
assert_se(s = json_variant_string(v));
@@ -1488,7 +1492,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
if (!found) {
if (bad_field)
*bad_field = s;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
}
break;
@@ -1500,7 +1504,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
if (!json_variant_is_object(v)) {
if (bad_field)
*bad_field = symbol->name;
- return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
+ return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
}
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
@@ -1522,7 +1526,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant
if (!varlink_idl_find_field(symbol, name)) {
if (bad_field)
*bad_field = name;
- return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
+ return varlink_idl_log(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
}
}
diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c
new file mode 100644
index 0000000..500e072
--- /dev/null
+++ b/src/shared/varlink-io.systemd.BootControl.c
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.BootControl.h"
+
+static VARLINK_DEFINE_ENUM_TYPE(
+ BootEntryType,
+ VARLINK_DEFINE_ENUM_VALUE(type1),
+ VARLINK_DEFINE_ENUM_VALUE(type2),
+ VARLINK_DEFINE_ENUM_VALUE(loader),
+ VARLINK_DEFINE_ENUM_VALUE(auto));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ BootEntry,
+ VARLINK_DEFINE_FIELD_BY_TYPE(type, BootEntryType, 0),
+ VARLINK_DEFINE_FIELD(id, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(path, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(root, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(title, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(showTitle, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(sortKey, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(version, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(machineId, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(options, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(linux, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(efi, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(initrd, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(devicetree, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(devicetreeOverlay, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(isReported, VARLINK_BOOL, 0),
+ VARLINK_DEFINE_FIELD(triesLeft, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(triesDone, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(isDefault, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(isSelected, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+ ListBootEntries,
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(entry, BootEntry, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+ SetRebootToFirmware,
+ VARLINK_DEFINE_INPUT(state, VARLINK_BOOL, 0));
+
+static VARLINK_DEFINE_METHOD(
+ GetRebootToFirmware,
+ VARLINK_DEFINE_OUTPUT(state, VARLINK_BOOL, 0));
+
+static VARLINK_DEFINE_ERROR(
+ RebootToFirmwareNotSupported);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_BootControl,
+ "io.systemd.BootControl",
+ &vl_type_BootEntryType,
+ &vl_type_BootEntry,
+ &vl_method_ListBootEntries,
+ &vl_method_SetRebootToFirmware,
+ &vl_method_GetRebootToFirmware,
+ &vl_error_RebootToFirmwareNotSupported);
diff --git a/src/shared/varlink-io.systemd.BootControl.h b/src/shared/varlink-io.systemd.BootControl.h
new file mode 100644
index 0000000..fa72b70
--- /dev/null
+++ b/src/shared/varlink-io.systemd.BootControl.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_BootControl;
diff --git a/src/shared/varlink-io.systemd.Credentials.c b/src/shared/varlink-io.systemd.Credentials.c
new file mode 100644
index 0000000..03db0b3
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Credentials.c
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.Credentials.h"
+
+static VARLINK_DEFINE_METHOD(
+ Encrypt,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(notAfter, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(blob, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_METHOD(
+ Decrypt,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(blob, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(data, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_ERROR(BadFormat);
+static VARLINK_DEFINE_ERROR(NameMismatch);
+static VARLINK_DEFINE_ERROR(TimeMismatch);
+static VARLINK_DEFINE_ERROR(NoSuchUser);
+static VARLINK_DEFINE_ERROR(BadScope);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Credentials,
+ "io.systemd.Credentials",
+ &vl_method_Encrypt,
+ &vl_method_Decrypt,
+ &vl_error_BadFormat,
+ &vl_error_NameMismatch,
+ &vl_error_TimeMismatch,
+ &vl_error_NoSuchUser,
+ &vl_error_BadScope);
diff --git a/src/shared/varlink-io.systemd.Credentials.h b/src/shared/varlink-io.systemd.Credentials.h
new file mode 100644
index 0000000..c0ecc3d
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Credentials.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Credentials;
diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c
new file mode 100644
index 0000000..a6c6aec
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Hostname.c
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.Credentials.h"
+
+static VARLINK_DEFINE_METHOD(
+ Describe,
+ VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperatingSystemReleaseData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_OUTPUT(MachineInformationData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(VSockCID, VARLINK_INT, VARLINK_NULLABLE));
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Hostname,
+ "io.systemd.Hostname",
+ &vl_method_Describe);
diff --git a/src/shared/varlink-io.systemd.Hostname.h b/src/shared/varlink-io.systemd.Hostname.h
new file mode 100644
index 0000000..29bb20e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Hostname.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Hostname;
diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c
new file mode 100644
index 0000000..2d25a34
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Machine.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-idl.h"
+#include "varlink-io.systemd.Machine.h"
+
+static VARLINK_DEFINE_METHOD(
+ Register,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_ERROR(MachineExists);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Machine,
+ "io.systemd.Machine",
+ &vl_method_Register,
+ &vl_error_MachineExists);
diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h
new file mode 100644
index 0000000..c9fc85f
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Machine.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Machine;
diff --git a/src/shared/varlink-io.systemd.MountFileSystem.c b/src/shared/varlink-io.systemd.MountFileSystem.c
new file mode 100644
index 0000000..4a33578
--- /dev/null
+++ b/src/shared/varlink-io.systemd.MountFileSystem.c
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.MountFileSystem.h"
+
+static VARLINK_DEFINE_ENUM_TYPE(
+ PartitionDesignator,
+ VARLINK_DEFINE_ENUM_VALUE(root),
+ VARLINK_DEFINE_ENUM_VALUE(usr),
+ VARLINK_DEFINE_ENUM_VALUE(home),
+ VARLINK_DEFINE_ENUM_VALUE(srv),
+ VARLINK_DEFINE_ENUM_VALUE(esp),
+ VARLINK_DEFINE_ENUM_VALUE(xbootldr),
+ VARLINK_DEFINE_ENUM_VALUE(swap),
+ VARLINK_DEFINE_ENUM_VALUE(root_verity),
+ VARLINK_DEFINE_ENUM_VALUE(usr_verity),
+ VARLINK_DEFINE_ENUM_VALUE(root_verity_sig),
+ VARLINK_DEFINE_ENUM_VALUE(usr_verity_sig),
+ VARLINK_DEFINE_ENUM_VALUE(tmp),
+ VARLINK_DEFINE_ENUM_VALUE(var));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ PartitionInfo,
+ VARLINK_DEFINE_FIELD(designator, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(writable, VARLINK_BOOL, 0),
+ VARLINK_DEFINE_FIELD(growFileSystem, VARLINK_BOOL, 0),
+ VARLINK_DEFINE_FIELD(partitionNumber, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(partitionUuid, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(fileSystemType, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(partitionLabel, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(offset, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(mountFileDescriptor, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ MountImage,
+ VARLINK_DEFINE_INPUT(imageFileDescriptor, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(growFileSystems, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(password, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(imagePolicy, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, VARLINK_ARRAY),
+ VARLINK_DEFINE_OUTPUT(imagePolicy, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(imageSize, VARLINK_INT, 0),
+ VARLINK_DEFINE_OUTPUT(sectorSize, VARLINK_INT, 0),
+ VARLINK_DEFINE_OUTPUT(imageName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(imageUuid, VARLINK_STRING, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_ERROR(IncompatibleImage);
+static VARLINK_DEFINE_ERROR(MultipleRootPartitionsFound);
+static VARLINK_DEFINE_ERROR(RootPartitionNotFound);
+static VARLINK_DEFINE_ERROR(DeniedByImagePolicy);
+static VARLINK_DEFINE_ERROR(KeyNotFound);
+static VARLINK_DEFINE_ERROR(VerityFailure);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_MountFileSystem,
+ "io.systemd.MountFileSystem",
+ &vl_type_PartitionDesignator,
+ &vl_type_PartitionInfo,
+ &vl_method_MountImage,
+ &vl_error_IncompatibleImage,
+ &vl_error_MultipleRootPartitionsFound,
+ &vl_error_RootPartitionNotFound,
+ &vl_error_DeniedByImagePolicy,
+ &vl_error_KeyNotFound,
+ &vl_error_VerityFailure);
diff --git a/src/shared/varlink-io.systemd.MountFileSystem.h b/src/shared/varlink-io.systemd.MountFileSystem.h
new file mode 100644
index 0000000..dc75957
--- /dev/null
+++ b/src/shared/varlink-io.systemd.MountFileSystem.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_MountFileSystem;
diff --git a/src/shared/varlink-io.systemd.NamespaceResource.c b/src/shared/varlink-io.systemd.NamespaceResource.c
new file mode 100644
index 0000000..e98c6c6
--- /dev/null
+++ b/src/shared/varlink-io.systemd.NamespaceResource.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.NamespaceResource.h"
+
+static VARLINK_DEFINE_METHOD(
+ AllocateUserRange,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(size, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(target, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ RegisterUserNamespace,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ AddMountToUserNamespace,
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(mountFileDescriptor, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ AddControlGroupToUserNamespace,
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(controlGroupFileDescriptor, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ AddNetworkToUserNamespace,
+ VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(networkNamespaceFileDescriptor, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(namespaceInterfaceName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(mode, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(hostInterfaceName, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(namespaceInterfaceName, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_ERROR(UserNamespaceInterfaceNotSupported);
+static VARLINK_DEFINE_ERROR(NameExists);
+static VARLINK_DEFINE_ERROR(UserNamespaceExists);
+static VARLINK_DEFINE_ERROR(DynamicRangeUnavailable);
+static VARLINK_DEFINE_ERROR(NoDynamicRange);
+static VARLINK_DEFINE_ERROR(UserNamespaceNotRegistered);
+static VARLINK_DEFINE_ERROR(UserNamespaceWithoutUserRange);
+static VARLINK_DEFINE_ERROR(TooManyControlGroups);
+static VARLINK_DEFINE_ERROR(ControlGroupAlreadyAdded);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_NamespaceResource,
+ "io.systemd.NamespaceResource",
+ &vl_method_AllocateUserRange,
+ &vl_method_RegisterUserNamespace,
+ &vl_method_AddMountToUserNamespace,
+ &vl_method_AddControlGroupToUserNamespace,
+ &vl_method_AddNetworkToUserNamespace,
+ &vl_error_UserNamespaceInterfaceNotSupported,
+ &vl_error_NameExists,
+ &vl_error_UserNamespaceExists,
+ &vl_error_DynamicRangeUnavailable,
+ &vl_error_NoDynamicRange,
+ &vl_error_UserNamespaceNotRegistered,
+ &vl_error_UserNamespaceWithoutUserRange,
+ &vl_error_TooManyControlGroups,
+ &vl_error_ControlGroupAlreadyAdded);
diff --git a/src/shared/varlink-io.systemd.NamespaceResource.h b/src/shared/varlink-io.systemd.NamespaceResource.h
new file mode 100644
index 0000000..443cb97
--- /dev/null
+++ b/src/shared/varlink-io.systemd.NamespaceResource.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_NamespaceResource;
diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c
new file mode 100644
index 0000000..394cc33
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Network.c
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.Network.h"
+
+static VARLINK_DEFINE_METHOD(
+ GetStates,
+ VARLINK_DEFINE_OUTPUT(AddressState, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(IPv4AddressState, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(IPv6AddressState, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(CarrierState, VARLINK_STRING, 0),
+ VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(OperationalState, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_METHOD(
+ GetNamespaceId,
+ VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0),
+ VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ LLDPNeighbor,
+ VARLINK_DEFINE_FIELD(ChassisID, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(RawChassisID, VARLINK_INT, VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(PortID, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(RawPortID, VARLINK_INT, VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(PortDescription, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(SystemName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(SystemDescription, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(EnabledCapabilities, VARLINK_INT, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ LLDPNeighborsByInterface,
+ VARLINK_DEFINE_FIELD(InterfaceIndex, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(InterfaceName, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(InterfaceAlternativeNames, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD_BY_TYPE(Neighbors, LLDPNeighbor, VARLINK_ARRAY));
+
+static VARLINK_DEFINE_METHOD(
+ GetLLDPNeighbors,
+ VARLINK_DEFINE_INPUT(InterfaceIndex, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(InterfaceName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(Neighbors, LLDPNeighborsByInterface, VARLINK_ARRAY));
+
+static VARLINK_DEFINE_METHOD(
+ SetPersistentStorage,
+ VARLINK_DEFINE_INPUT(Ready, VARLINK_BOOL, 0));
+
+static VARLINK_DEFINE_ERROR(StorageReadOnly);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_Network,
+ "io.systemd.Network",
+ &vl_method_GetStates,
+ &vl_method_GetNamespaceId,
+ &vl_method_GetLLDPNeighbors,
+ &vl_method_SetPersistentStorage,
+ &vl_type_LLDPNeighbor,
+ &vl_type_LLDPNeighborsByInterface,
+ &vl_error_StorageReadOnly);
diff --git a/src/shared/varlink-io.systemd.Network.h b/src/shared/varlink-io.systemd.Network.h
new file mode 100644
index 0000000..12d532a
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Network.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_Network;
diff --git a/src/shared/varlink-io.systemd.PCRLock.c b/src/shared/varlink-io.systemd.PCRLock.c
new file mode 100644
index 0000000..22c6532
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRLock.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.PCRLock.h"
+
+static VARLINK_DEFINE_METHOD(
+ ReadEventLog,
+ VARLINK_DEFINE_OUTPUT(record, VARLINK_OBJECT, 0));
+
+static VARLINK_DEFINE_METHOD(
+ MakePolicy,
+ VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+ RemovePolicy);
+
+static VARLINK_DEFINE_ERROR(
+ NoChange);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_PCRLock,
+ "io.systemd.PCRLock",
+ &vl_method_ReadEventLog,
+ &vl_method_MakePolicy,
+ &vl_method_RemovePolicy,
+ &vl_error_NoChange);
diff --git a/src/shared/varlink-io.systemd.PCRLock.h b/src/shared/varlink-io.systemd.PCRLock.h
new file mode 100644
index 0000000..687f09e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRLock.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "varlink-idl.h"
+
+extern const VarlinkInterface vl_interface_io_systemd_PCRLock;
diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c
index d95b613..ba928fa 100644
--- a/src/shared/varlink-io.systemd.Resolve.Monitor.c
+++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c
@@ -2,96 +2,44 @@
#include "varlink-io.systemd.Resolve.Monitor.h"
-VARLINK_DEFINE_STRUCT_TYPE(
- ResourceKey,
- VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0),
- VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0),
- VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0));
-
-VARLINK_DEFINE_STRUCT_TYPE(
- ResourceRecord,
- VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0),
- VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
- VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
- VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
- VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE),
- VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE));
-
-VARLINK_DEFINE_STRUCT_TYPE(
+/* We want to reuse the ResourceKey and ResourceRecord structures from the io.systemd.Resolve interface,
+ * hence import them here. */
+#include "varlink-io.systemd.Resolve.h"
+
+static VARLINK_DEFINE_STRUCT_TYPE(
ResourceRecordArray,
VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
Answer,
VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE));
-VARLINK_DEFINE_METHOD(
+static VARLINK_DEFINE_METHOD(
SubscribeQueryResults,
/* First reply */
VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE),
/* Subsequent replies */
VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
CacheEntry,
VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(rrs, ResourceRecordArray, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(type, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(until, VARLINK_INT, 0));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
ScopeCache,
VARLINK_DEFINE_FIELD(protocol, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(family, VARLINK_INT, VARLINK_NULLABLE),
@@ -99,11 +47,11 @@ VARLINK_DEFINE_STRUCT_TYPE(
VARLINK_DEFINE_FIELD(ifname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD_BY_TYPE(cache, CacheEntry, VARLINK_ARRAY));
-VARLINK_DEFINE_METHOD(
+static VARLINK_DEFINE_METHOD(
DumpCache,
VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ScopeCache, VARLINK_ARRAY));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
ServerState,
VARLINK_DEFINE_FIELD(Server, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(Type, VARLINK_STRING, 0),
@@ -122,11 +70,11 @@ VARLINK_DEFINE_STRUCT_TYPE(
VARLINK_DEFINE_FIELD(PacketInvalid, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(PacketDoOff, VARLINK_BOOL, 0));
-VARLINK_DEFINE_METHOD(
+static VARLINK_DEFINE_METHOD(
DumpServerState,
VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ServerState, VARLINK_ARRAY));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
TransactionStatistics,
VARLINK_DEFINE_FIELD(currentTransactions, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalTransactions, VARLINK_INT, 0),
@@ -135,26 +83,26 @@ VARLINK_DEFINE_STRUCT_TYPE(
VARLINK_DEFINE_FIELD(totalFailedResponses, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalFailedResponsesServedStale, VARLINK_INT, 0));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
CacheStatistics,
VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(hits, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(misses, VARLINK_INT, 0));
-VARLINK_DEFINE_STRUCT_TYPE(
+static VARLINK_DEFINE_STRUCT_TYPE(
DnssecStatistics,
VARLINK_DEFINE_FIELD(secure, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(insecure, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(bogus, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(indeterminate, VARLINK_INT, 0));
-VARLINK_DEFINE_METHOD(
+static VARLINK_DEFINE_METHOD(
DumpStatistics,
VARLINK_DEFINE_OUTPUT_BY_TYPE(transactions, TransactionStatistics, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(cache, CacheStatistics, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(dnssec, DnssecStatistics, 0));
-VARLINK_DEFINE_METHOD(ResetStatistics);
+static VARLINK_DEFINE_METHOD(ResetStatistics);
VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve_Monitor,
diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c
index 0d8ad28..71f4dc8 100644
--- a/src/shared/varlink-io.systemd.Resolve.c
+++ b/src/shared/varlink-io.systemd.Resolve.c
@@ -2,6 +2,73 @@
#include "varlink-io.systemd.Resolve.h"
+VARLINK_DEFINE_STRUCT_TYPE(
+ ResourceKey,
+ VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0));
+
+VARLINK_DEFINE_STRUCT_TYPE(
+ ResourceRecord,
+ VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0),
+ VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
+ VARLINK_DEFINE_FIELD(order, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(preference, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(naptrFlags, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(services, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(regexp, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(replacement, VARLINK_STRING, VARLINK_NULLABLE));
+
static VARLINK_DEFINE_STRUCT_TYPE(
ResolvedAddress,
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE),
@@ -32,6 +99,50 @@ static VARLINK_DEFINE_METHOD(
VARLINK_DEFINE_OUTPUT_BY_TYPE(names, ResolvedName, VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
+static VARLINK_DEFINE_STRUCT_TYPE(
+ ResolvedService,
+ VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(weight, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(port, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(hostname, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(canonicalName, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD_BY_TYPE(addresses, ResolvedAddress, VARLINK_ARRAY|VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ ResolvedCanonical,
+ VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(type, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(domain, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_METHOD(
+ ResolveService,
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(type, VARLINK_STRING, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(domain, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(family, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(services, ResolvedService, VARLINK_ARRAY),
+ VARLINK_DEFINE_OUTPUT(txt, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(canonical, ResolvedCanonical, 0),
+ VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+ ResolvedRecord,
+ VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_METHOD(
+ ResolveRecord,
+ VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
+ VARLINK_DEFINE_INPUT(class, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_INPUT(type, VARLINK_INT, 0),
+ VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, VARLINK_ARRAY),
+ VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
+
static VARLINK_DEFINE_ERROR(NoNameServers);
static VARLINK_DEFINE_ERROR(NoSuchResourceRecord);
static VARLINK_DEFINE_ERROR(QueryTimedOut);
@@ -40,7 +151,9 @@ static VARLINK_DEFINE_ERROR(InvalidReply);
static VARLINK_DEFINE_ERROR(QueryAborted);
static VARLINK_DEFINE_ERROR(
DNSSECValidationFailed,
- VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0));
+ VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(NoTrustAnchor);
static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported);
static VARLINK_DEFINE_ERROR(NetworkDown);
@@ -48,17 +161,29 @@ static VARLINK_DEFINE_ERROR(NoSource);
static VARLINK_DEFINE_ERROR(StubLoop);
static VARLINK_DEFINE_ERROR(
DNSError,
- VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0));
+ VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE),
+ VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(CNAMELoop);
static VARLINK_DEFINE_ERROR(BadAddressSize);
+static VARLINK_DEFINE_ERROR(ResourceRecordTypeInvalidForQuery);
+static VARLINK_DEFINE_ERROR(ZoneTransfersNotPermitted);
+static VARLINK_DEFINE_ERROR(ResourceRecordTypeObsolete);
VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve,
"io.systemd.Resolve",
&vl_method_ResolveHostname,
&vl_method_ResolveAddress,
+ &vl_method_ResolveService,
+ &vl_method_ResolveRecord,
&vl_type_ResolvedAddress,
&vl_type_ResolvedName,
+ &vl_type_ResolvedService,
+ &vl_type_ResolvedCanonical,
+ &vl_type_ResourceKey,
+ &vl_type_ResourceRecord,
+ &vl_type_ResolvedRecord,
&vl_error_NoNameServers,
&vl_error_NoSuchResourceRecord,
&vl_error_QueryTimedOut,
@@ -73,4 +198,7 @@ VARLINK_DEFINE_INTERFACE(
&vl_error_StubLoop,
&vl_error_DNSError,
&vl_error_CNAMELoop,
- &vl_error_BadAddressSize);
+ &vl_error_BadAddressSize,
+ &vl_error_ResourceRecordTypeInvalidForQuery,
+ &vl_error_ZoneTransfersNotPermitted,
+ &vl_error_ResourceRecordTypeObsolete);
diff --git a/src/shared/varlink-io.systemd.Resolve.h b/src/shared/varlink-io.systemd.Resolve.h
index 5c7ed39..48a9241 100644
--- a/src/shared/varlink-io.systemd.Resolve.h
+++ b/src/shared/varlink-io.systemd.Resolve.h
@@ -3,4 +3,7 @@
#include "varlink-idl.h"
+extern const VarlinkSymbol vl_type_ResourceKey;
+extern const VarlinkSymbol vl_type_ResourceRecord;
+
extern const VarlinkInterface vl_interface_io_systemd_Resolve;
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
index 749b644..034e72b 100644
--- a/src/shared/varlink.c
+++ b/src/shared/varlink.c
@@ -37,6 +37,7 @@
#define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC)
#define VARLINK_BUFFER_MAX (16U*1024U*1024U)
#define VARLINK_READ_SIZE (64U*1024U)
+#define VARLINK_COLLECT_MAX 1024U
typedef enum VarlinkState {
/* Client side states */
@@ -45,6 +46,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE,
VARLINK_CALLING,
VARLINK_CALLED,
+ VARLINK_COLLECTING,
+ VARLINK_COLLECTING_REPLY,
VARLINK_PROCESSING_REPLY,
/* Server side states */
@@ -79,6 +82,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE, \
VARLINK_CALLING, \
VARLINK_CALLED, \
+ VARLINK_COLLECTING, \
+ VARLINK_COLLECTING_REPLY, \
VARLINK_PROCESSING_REPLY, \
VARLINK_IDLE_SERVER, \
VARLINK_PROCESSING_METHOD, \
@@ -169,8 +174,11 @@ struct Varlink {
VarlinkReply reply_callback;
JsonVariant *current;
+ JsonVariant *current_collected;
+ VarlinkReplyFlags current_reply_flags;
VarlinkSymbol *current_method;
+ int peer_pidfd;
struct ucred ucred;
bool ucred_acquired:1;
@@ -183,6 +191,7 @@ struct Varlink {
bool allow_fd_passing_output:1;
bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */
+ bool input_sensitive:1; /* Whether incoming messages might be sensitive */
int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */
@@ -241,18 +250,14 @@ struct VarlinkServer {
bool exit_on_idle;
};
-typedef struct VarlinkCollectContext {
- JsonVariant *parameters;
- const char *error_id;
- VarlinkReplyFlags flags;
-} VarlinkCollectContext ;
-
static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
[VARLINK_IDLE_CLIENT] = "idle-client",
[VARLINK_AWAITING_REPLY] = "awaiting-reply",
[VARLINK_AWAITING_REPLY_MORE] = "awaiting-reply-more",
[VARLINK_CALLING] = "calling",
[VARLINK_CALLED] = "called",
+ [VARLINK_COLLECTING] = "collecting",
+ [VARLINK_COLLECTING_REPLY] = "collecting-reply",
[VARLINK_PROCESSING_REPLY] = "processing-reply",
[VARLINK_IDLE_SERVER] = "idle-server",
[VARLINK_PROCESSING_METHOD] = "processing-method",
@@ -361,6 +366,8 @@ static int varlink_new(Varlink **ret) {
.timeout = VARLINK_DEFAULT_TIMEOUT_USEC,
.af = -1,
+
+ .peer_pidfd = -EBADF,
};
*ret = v;
@@ -447,6 +454,10 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) {
if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+ r = fd_nonblock(pair[1], false);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m");
+
r = safe_fork_full(
"(sd-vlexec)",
/* stdio_fds= */ NULL,
@@ -504,39 +515,120 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) {
return 0;
}
+static int varlink_connect_ssh(Varlink **ret, const char *where) {
+ _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
+ _cleanup_(sigkill_waitp) pid_t pid = 0;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(where, -EINVAL);
+
+ /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For
+ * now we do not expose this function directly, but only via varlink_connect_url(). */
+
+ const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
+ if (!path_is_valid(ssh))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh);
+
+ const char *e = strchr(where, ':');
+ if (!e)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where);
+
+ _cleanup_free_ char *h = strndup(where, e - where);
+ if (!h)
+ return log_oom_debug();
+
+ _cleanup_free_ char *c = strdup(e + 1);
+ if (!c)
+ return log_oom_debug();
+
+ if (!path_is_absolute(c))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c);
+
+ _cleanup_free_ char *p = NULL;
+ r = path_simplify_alloc(c, &p);
+ if (r < 0)
+ return r;
+
+ if (!path_is_normalized(p))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p);
+
+ log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
+ return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+
+ r = safe_fork_full(
+ "(sd-vlssh)",
+ /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO },
+ /* except_fds= */ NULL,
+ /* n_except_fds= */ 0,
+ FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO,
+ &pid);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to spawn process: %m");
+ if (r == 0) {
+ /* Child */
+
+ execlp(ssh, "ssh", "-W", p, h, NULL);
+ log_debug_errno(errno, "Failed to invoke %s: %m", ssh);
+ _exit(EXIT_FAILURE);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ Varlink *v;
+ r = varlink_new(&v);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to create varlink object: %m");
+
+ v->fd = TAKE_FD(pair[0]);
+ v->af = AF_UNIX;
+ v->exec_pid = TAKE_PID(pid);
+ varlink_set_state(v, VARLINK_IDLE_CLIENT);
+
+ *ret = v;
+ return 0;
+}
+
int varlink_connect_url(Varlink **ret, const char *url) {
_cleanup_free_ char *c = NULL;
const char *p;
- bool exec;
+ enum {
+ SCHEME_UNIX,
+ SCHEME_EXEC,
+ SCHEME_SSH,
+ } scheme;
int r;
assert_return(ret, -EINVAL);
assert_return(url, -EINVAL);
- // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH
- // 9.4's -W switch for referencing remote AF_UNIX sockets.)
+ // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here.
- /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an
- * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since
- * it has nothing to do with Internet URLs by RFC. */
+ /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for
+ * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be
+ * called "URL", since it has nothing to do with Internet URLs by RFC. */
p = startswith(url, "unix:");
if (p)
- exec = false;
- else {
- p = startswith(url, "exec:");
- if (!p)
- return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
-
- exec = true;
- }
+ scheme = SCHEME_UNIX;
+ else if ((p = startswith(url, "exec:")))
+ scheme = SCHEME_EXEC;
+ else if ((p = startswith(url, "ssh:")))
+ scheme = SCHEME_SSH;
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
/* The varlink.org reference C library supports more than just file system paths. We might want to
* support that one day too. For now simply refuse that. */
if (p[strcspn(p, ";?#")] != '\0')
return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported.");
- if (exec || p[0] != '@') { /* no validity checks for abstract namespace */
+ if (scheme == SCHEME_SSH)
+ return varlink_connect_ssh(ret, p);
+
+ if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */
if (!path_is_absolute(p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing.");
@@ -549,7 +641,7 @@ int varlink_connect_url(Varlink **ret, const char *url) {
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing.");
}
- if (exec)
+ if (scheme == SCHEME_EXEC)
return varlink_connect_exec(ret, c, NULL);
return varlink_connect_address(ret, c ?: p);
@@ -599,7 +691,9 @@ static void varlink_clear_current(Varlink *v) {
/* Clears the currently processed incoming message */
v->current = json_variant_unref(v->current);
+ v->current_collected = json_variant_unref(v->current_collected);
v->current_method = NULL;
+ v->current_reply_flags = 0;
close_many(v->input_fds, v->n_input_fds);
v->input_fds = mfree(v->input_fds);
@@ -615,7 +709,7 @@ static void varlink_clear(Varlink *v) {
varlink_clear_current(v);
- v->input_buffer = mfree(v->input_buffer);
+ v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer);
v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer);
v->input_control_buffer = mfree(v->input_control_buffer);
@@ -638,6 +732,8 @@ static void varlink_clear(Varlink *v) {
sigterm_wait(v->exec_pid);
v->exec_pid = 0;
}
+
+ v->peer_pidfd = safe_close(v->peer_pidfd);
}
static Varlink* varlink_destroy(Varlink *v) {
@@ -680,7 +776,7 @@ static int varlink_test_disconnect(Varlink *v) {
goto disconnect;
/* If we are waiting for incoming data but the read side is shut down, disconnect. */
- if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected)
+ if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected)
goto disconnect;
/* Similar, if are a client that hasn't written anything yet but the write side is dead, also
@@ -803,7 +899,7 @@ static int varlink_read(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER))
return 0;
if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */
return 0;
@@ -932,7 +1028,8 @@ static int varlink_read(Varlink *v) {
}
static int varlink_parse_message(Varlink *v) {
- const char *e, *begin;
+ const char *e;
+ char *begin;
size_t sz;
int r;
@@ -956,11 +1053,9 @@ static int varlink_parse_message(Varlink *v) {
sz = e - begin + 1;
- varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation?
- * This may produce a non-printable journal entry if the message
- * is invalid. We may also expose privileged information. */
-
r = json_parse(begin, 0, &v->current, NULL, NULL);
+ if (v->input_sensitive)
+ explicit_bzero_safe(begin, sz);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
* hence drop all buffered data now. */
@@ -968,6 +1063,24 @@ static int varlink_parse_message(Varlink *v) {
return varlink_log_errno(v, r, "Failed to parse JSON: %m");
}
+ if (v->input_sensitive) {
+ /* Mark the parameters subfield as sensitive right-away, if that's requested */
+ JsonVariant *parameters = json_variant_by_key(v->current, "parameters");
+ if (parameters)
+ json_variant_sensitive(parameters);
+ }
+
+ if (DEBUG_LOGGING) {
+ _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+ /* Suppress sensitive fields in the debug output */
+ r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+ if (r < 0)
+ return r;
+
+ varlink_log(v, "Received message: %s", censored_text);
+ }
+
v->input_buffer_size -= sz;
if (v->input_buffer_size == 0)
@@ -982,7 +1095,7 @@ static int varlink_parse_message(Varlink *v) {
static int varlink_test_timeout(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (v->timeout == USEC_INFINITY)
return 0;
@@ -1006,7 +1119,7 @@ static int varlink_dispatch_local_error(Varlink *v, const char *error) {
r = v->reply_callback(v, NULL, error, VARLINK_REPLY_ERROR|VARLINK_REPLY_LOCAL, v->userdata);
if (r < 0)
- log_debug_errno(r, "Reply callback returned error, ignoring: %m");
+ varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m");
return 1;
}
@@ -1072,7 +1185,7 @@ static int varlink_dispatch_reply(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (!v->current)
return 0;
@@ -1115,7 +1228,7 @@ static int varlink_dispatch_reply(Varlink *v) {
}
/* Replies with 'continue' set are only OK if we set 'more' when the method call was initiated */
- if (v->state != VARLINK_AWAITING_REPLY_MORE && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY_MORE, VARLINK_COLLECTING) && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
goto invalid;
/* An error is final */
@@ -1126,19 +1239,20 @@ static int varlink_dispatch_reply(Varlink *v) {
if (r < 0)
goto invalid;
+ v->current_reply_flags = flags;
+
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE)) {
varlink_set_state(v, VARLINK_PROCESSING_REPLY);
if (v->reply_callback) {
r = v->reply_callback(v, parameters, error, flags, v->userdata);
if (r < 0)
- log_debug_errno(r, "Reply callback returned error, ignoring: %m");
+ varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m");
}
varlink_clear_current(v);
if (v->state == VARLINK_PROCESSING_REPLY) {
-
assert(v->n_pending > 0);
if (!FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
@@ -1148,7 +1262,9 @@ static int varlink_dispatch_reply(Varlink *v) {
FLAGS_SET(flags, VARLINK_REPLY_CONTINUES) ? VARLINK_AWAITING_REPLY_MORE :
v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY);
}
- } else {
+ } else if (v->state == VARLINK_COLLECTING)
+ varlink_set_state(v, VARLINK_COLLECTING_REPLY);
+ else {
assert(v->state == VARLINK_CALLING);
varlink_set_state(v, VARLINK_CALLED);
}
@@ -1176,9 +1292,7 @@ static int generic_method_get_info(
assert(link);
if (json_variant_elements(parameters) != 0)
- return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER,
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR_VARIANT("parameter", json_variant_by_index(parameters, 0))));
+ return varlink_error_invalid_parameter(link, parameters);
product = strjoin("systemd (", program_invocation_short_name, ")");
if (!product)
@@ -1196,7 +1310,7 @@ static int generic_method_get_info(
return varlink_replyb(link, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("vendor", "The systemd Project"),
JSON_BUILD_PAIR_STRING("product", product),
- JSON_BUILD_PAIR_STRING("version", STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"),
+ JSON_BUILD_PAIR_STRING("version", PROJECT_VERSION_FULL " (" GIT_VERSION ")"),
JSON_BUILD_PAIR_STRING("url", "https://systemd.io/"),
JSON_BUILD_PAIR_STRV("interfaces", interfaces)));
}
@@ -1327,16 +1441,18 @@ static int varlink_dispatch_method(Varlink *v) {
v->current_method = hashmap_get(v->server->symbols, method);
if (!v->current_method)
- log_debug("No interface description defined for method '%s', not validating.", method);
+ varlink_log(v, "No interface description defined for method '%s', not validating.", method);
else {
const char *bad_field;
r = varlink_idl_validate_method_call(v->current_method, parameters, &bad_field);
if (r < 0) {
- log_debug_errno(r, "Parameters for method %s() didn't pass validation on field '%s': %m", method, strna(bad_field));
+ /* Please adjust test/units/end.sh when updating the log message. */
+ varlink_log_errno(v, r, "Parameters for method %s() didn't pass validation on field '%s': %m",
+ method, strna(bad_field));
- if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
- r = varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", bad_field)));
+ if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) {
+ r = varlink_error_invalid_parameter_name(v, bad_field);
if (r < 0)
return r;
}
@@ -1347,24 +1463,21 @@ static int varlink_dispatch_method(Varlink *v) {
if (!invalid) {
r = callback(v, parameters, flags, v->userdata);
if (r < 0) {
- log_debug_errno(r, "Callback for %s returned error: %m", method);
+ varlink_log_errno(v, r, "Callback for %s returned error: %m", method);
/* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */
- if (v->state == VARLINK_PROCESSED_METHOD)
- r = 0; /* already processed */
- else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
+ if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) {
r = varlink_error_errno(v, r);
if (r < 0)
return r;
}
}
}
- } else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
+ } else if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) {
r = varlink_errorb(v, VARLINK_ERROR_METHOD_NOT_FOUND, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method))));
if (r < 0)
return r;
- } else
- r = 0;
+ }
switch (v->state) {
@@ -1386,7 +1499,7 @@ static int varlink_dispatch_method(Varlink *v) {
assert_not_reached();
}
- return r;
+ return 1;
invalid:
r = -EINVAL;
@@ -1482,6 +1595,48 @@ finish:
return r;
}
+int varlink_dispatch_again(Varlink *v) {
+ int r;
+
+ assert_return(v, -EINVAL);
+
+ /* If a method call handler could not process the method call just yet (for example because it needed
+ * some Polkit authentication first), then it can leave the call unanswered, do its thing, and then
+ * ask to be dispatched a second time, via this call. It will then be called again, for the same
+ * message */
+
+ if (v->state == VARLINK_DISCONNECTED)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
+ if (v->state != VARLINK_PENDING_METHOD)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection has no pending method.");
+
+ varlink_set_state(v, VARLINK_IDLE_SERVER);
+
+ r = sd_event_source_set_enabled(v->defer_event_source, SD_EVENT_ON);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to enable deferred event source: %m");
+
+ return 0;
+}
+
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret) {
+ JsonVariant *p;
+
+ assert_return(v, -EINVAL);
+
+ if (!v->current)
+ return -ENODATA;
+
+ p = json_variant_by_key(v->current, "parameters");
+ if (!p)
+ return -ENODATA;
+
+ if (ret)
+ *ret = json_variant_ref(p);
+
+ return 0;
+}
+
static void handle_revents(Varlink *v, int revents) {
assert(v);
@@ -1587,7 +1742,7 @@ int varlink_get_events(Varlink *v) {
return EPOLLOUT;
if (!v->read_disconnected &&
- IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) &&
+ IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) &&
!v->current &&
v->input_buffer_unscanned <= 0)
ret |= EPOLLIN;
@@ -1605,7 +1760,7 @@ int varlink_get_timeout(Varlink *v, usec_t *ret) {
if (v->state == VARLINK_DISCONNECTED)
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
- if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING) &&
+ if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) &&
v->timeout != USEC_INFINITY) {
if (ret)
*ret = usec_add(v->timestamp, v->timeout);
@@ -1726,51 +1881,60 @@ Varlink* varlink_flush_close_unref(Varlink *v) {
static int varlink_format_json(Varlink *v, JsonVariant *m) {
_cleanup_(erase_and_freep) char *text = NULL;
- int r;
+ int sz, r;
assert(v);
assert(m);
- r = json_variant_format(m, 0, &text);
- if (r < 0)
- return r;
- assert(text[r] == '\0');
+ sz = json_variant_format(m, /* flags= */ 0, &text);
+ if (sz < 0)
+ return sz;
+ assert(text[sz] == '\0');
- if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
+ if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX)
return -ENOBUFS;
- varlink_log(v, "Sending message: %s", text);
+ if (DEBUG_LOGGING) {
+ _cleanup_(erase_and_freep) char *censored_text = NULL;
+
+ /* Suppress sensitive fields in the debug output */
+ r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text);
+ if (r < 0)
+ return r;
+
+ varlink_log(v, "Sending message: %s", censored_text);
+ }
if (v->output_buffer_size == 0) {
free_and_replace(v->output_buffer, text);
- v->output_buffer_size = r + 1;
+ v->output_buffer_size = sz + 1;
v->output_buffer_index = 0;
} else if (v->output_buffer_index == 0) {
- if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1))
+ if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1))
return -ENOMEM;
- memcpy(v->output_buffer + v->output_buffer_size, text, r + 1);
- v->output_buffer_size += r + 1;
+ memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1);
+ v->output_buffer_size += sz + 1;
} else {
char *n;
- const size_t new_size = v->output_buffer_size + r + 1;
+ const size_t new_size = v->output_buffer_size + sz + 1;
n = new(char, new_size);
if (!n)
return -ENOMEM;
- memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1);
+ memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1);
free_and_replace(v->output_buffer, n);
v->output_buffer_size = new_size;
v->output_buffer_index = 0;
}
- if (json_variant_is_sensitive(m))
+ if (json_variant_is_sensitive_recursive(m))
v->output_buffer_sensitive = true; /* Propagate sensitive flag */
else
text = mfree(text); /* No point in the erase_and_free() destructor declared above */
@@ -1999,7 +2163,7 @@ int varlink_observeb(Varlink *v, const char *method, ...) {
return varlink_observe(v, method, parameters);
}
-int varlink_call(
+int varlink_call_full(
Varlink *v,
const char *method,
JsonVariant *parameters,
@@ -2043,7 +2207,6 @@ int varlink_call(
v->timestamp = now(CLOCK_MONOTONIC);
while (v->state == VARLINK_CALLING) {
-
r = varlink_process(v);
if (r < 0)
return r;
@@ -2057,21 +2220,29 @@ int varlink_call(
switch (v->state) {
- case VARLINK_CALLED:
+ case VARLINK_CALLED: {
assert(v->current);
varlink_set_state(v, VARLINK_IDLE_CLIENT);
assert(v->n_pending == 1);
v->n_pending--;
+ JsonVariant *e = json_variant_by_key(v->current, "error"),
+ *p = json_variant_by_key(v->current, "parameters");
+
+ /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
+ if (!ret_error_id && e)
+ return varlink_error_to_errno(json_variant_string(e), p);
+
if (ret_parameters)
- *ret_parameters = json_variant_by_key(v->current, "parameters");
+ *ret_parameters = p;
if (ret_error_id)
- *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error"));
+ *ret_error_id = e ? json_variant_string(e) : NULL;
if (ret_flags)
- *ret_flags = 0;
+ *ret_flags = v->current_reply_flags;
return 1;
+ }
case VARLINK_PENDING_DISCONNECT:
case VARLINK_DISCONNECTED:
@@ -2085,63 +2256,76 @@ int varlink_call(
}
}
-int varlink_callb(
+int varlink_callb_ap(
Varlink *v,
const char *method,
JsonVariant **ret_parameters,
const char **ret_error_id,
- VarlinkReplyFlags *ret_flags, ...) {
+ VarlinkReplyFlags *ret_flags,
+ va_list ap) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
- va_list ap;
int r;
assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
- va_start(ap, ret_flags);
r = json_buildv(&parameters, ap);
- va_end(ap);
-
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
- return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+ return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
}
-static void varlink_collect_context_free(VarlinkCollectContext *cc) {
- assert(cc);
+int varlink_call_and_log(
+ Varlink *v,
+ const char *method,
+ JsonVariant *parameters,
+ JsonVariant **ret_parameters) {
+
+ JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
+
+ r = varlink_call(v, method, parameters, &reply, &error_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue %s() varlink call: %m", method);
+ if (error_id)
+ return log_error_errno(varlink_error_to_errno(error_id, reply),
+ "Failed to issue %s() varlink call: %s", method, error_id);
- json_variant_unref(cc->parameters);
- free((char *)cc->error_id);
+ if (ret_parameters)
+ *ret_parameters = TAKE_PTR(reply);
+
+ return 0;
}
-static int collect_callback(
+int varlink_callb_and_log(
Varlink *v,
- JsonVariant *parameters,
- const char *error_id,
- VarlinkReplyFlags flags,
- void *userdata) {
+ const char *method,
+ JsonVariant **ret_parameters,
+ ...) {
- VarlinkCollectContext *context = ASSERT_PTR(userdata);
+ _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
+ va_list ap;
int r;
- assert(v);
-
- context->flags = flags;
- /* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */
- if (error_id) {
- context->error_id = error_id;
- return 0;
- }
+ assert_return(v, -EINVAL);
+ assert_return(method, -EINVAL);
- r = json_variant_append_array(&context->parameters, parameters);
+ va_start(ap, ret_parameters);
+ r = json_buildv(&parameters, ap);
+ va_end(ap);
if (r < 0)
- return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
+ return log_error_errno(r, "Failed to build JSON message: %m");
- return 1;
+ return varlink_call_and_log(v, method, parameters, ret_parameters);
}
-int varlink_collect(
+int varlink_collect_full(
Varlink *v,
const char *method,
JsonVariant *parameters,
@@ -2149,7 +2333,7 @@ int varlink_collect(
const char **ret_error_id,
VarlinkReplyFlags *ret_flags) {
- _cleanup_(varlink_collect_context_free) VarlinkCollectContext context = {};
+ _cleanup_(json_variant_unrefp) JsonVariant *m = NULL, *collected = NULL;
int r;
assert_return(v, -EINVAL);
@@ -2166,61 +2350,105 @@ int varlink_collect(
* that we can assign a new reply shortly. */
varlink_clear_current(v);
- r = varlink_bind_reply(v, collect_callback);
+ r = varlink_sanitize_parameters(&parameters);
if (r < 0)
- return varlink_log_errno(v, r, "Failed to bind collect callback");
+ return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
- varlink_set_userdata(v, &context);
- r = varlink_observe(v, method, parameters);
+ r = json_build(&m, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
+ JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)),
+ JSON_BUILD_PAIR("more", JSON_BUILD_BOOLEAN(true))));
if (r < 0)
- return varlink_log_errno(v, r, "Failed to collect varlink method: %m");
+ return varlink_log_errno(v, r, "Failed to build json message: %m");
- while (v->state == VARLINK_AWAITING_REPLY_MORE) {
+ r = varlink_enqueue_json(v, m);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
- r = varlink_process(v);
- if (r < 0)
- return r;
+ varlink_set_state(v, VARLINK_COLLECTING);
+ v->n_pending++;
+ v->timestamp = now(CLOCK_MONOTONIC);
- /* If we get an error from any of the replies, return immediately with just the error_id and flags*/
- if (context.error_id) {
- if (ret_error_id)
- *ret_error_id = TAKE_PTR(context.error_id);
- if (ret_flags)
- *ret_flags = context.flags;
- return 0;
+ for (;;) {
+ while (v->state == VARLINK_COLLECTING) {
+ r = varlink_process(v);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = varlink_wait(v, USEC_INFINITY);
+ if (r < 0)
+ return r;
}
- if (r > 0)
- continue;
+ switch (v->state) {
- r = varlink_wait(v, USEC_INFINITY);
- if (r < 0)
- return r;
- }
+ case VARLINK_COLLECTING_REPLY: {
+ assert(v->current);
- switch (v->state) {
+ JsonVariant *e = json_variant_by_key(v->current, "error"),
+ *p = json_variant_by_key(v->current, "parameters");
- case VARLINK_IDLE_CLIENT:
- break;
+ /* Unless there is more to collect we reset state to idle */
+ if (!FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) {
+ varlink_set_state(v, VARLINK_IDLE_CLIENT);
+ assert(v->n_pending == 1);
+ v->n_pending--;
+ }
- case VARLINK_PENDING_DISCONNECT:
- case VARLINK_DISCONNECTED:
- return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
+ if (e) {
+ if (!ret_error_id)
+ return varlink_error_to_errno(json_variant_string(e), p);
- case VARLINK_PENDING_TIMEOUT:
- return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
+ if (ret_parameters)
+ *ret_parameters = p;
+ if (ret_error_id)
+ *ret_error_id = json_variant_string(e);
+ if (ret_flags)
+ *ret_flags = v->current_reply_flags;
- default:
- assert_not_reached();
- }
+ return 1;
+ }
- if (ret_parameters)
- *ret_parameters = TAKE_PTR(context.parameters);
- if (ret_error_id)
- *ret_error_id = TAKE_PTR(context.error_id);
- if (ret_flags)
- *ret_flags = context.flags;
- return 1;
+ if (json_variant_elements(collected) >= VARLINK_COLLECT_MAX)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", json_variant_elements(collected));
+
+ r = json_variant_append_array(&collected, p);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
+
+ if (FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) {
+ /* There's more to collect, continue */
+ varlink_clear_current(v);
+ varlink_set_state(v, VARLINK_COLLECTING);
+ continue;
+ }
+
+ if (ret_parameters)
+ /* Install the collection array in the connection object, so that we can hand
+ * out a pointer to it without passing over ownership, to make it work more
+ * alike regular method call replies */
+ *ret_parameters = v->current_collected = TAKE_PTR(collected);
+ if (ret_error_id)
+ *ret_error_id = NULL;
+ if (ret_flags)
+ *ret_flags = v->current_reply_flags;
+
+ return 1;
+ }
+
+ case VARLINK_PENDING_DISCONNECT:
+ case VARLINK_DISCONNECTED:
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
+
+ case VARLINK_PENDING_TIMEOUT:
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
+
+ default:
+ assert_not_reached();
+ }
+ }
}
int varlink_collectb(
@@ -2228,7 +2456,7 @@ int varlink_collectb(
const char *method,
JsonVariant **ret_parameters,
const char **ret_error_id,
- VarlinkReplyFlags *ret_flags, ...) {
+ ...) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
va_list ap;
@@ -2236,14 +2464,14 @@ int varlink_collectb(
assert_return(v, -EINVAL);
- va_start(ap, ret_flags);
+ va_start(ap, ret_error_id);
r = json_buildv(&parameters, ap);
va_end(ap);
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
- return varlink_collect(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+ return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
}
int varlink_reply(Varlink *v, JsonVariant *parameters) {
@@ -2272,7 +2500,9 @@ int varlink_reply(Varlink *v, JsonVariant *parameters) {
r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field);
if (r < 0)
- log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field));
+ /* Please adjust test/units/end.sh when updating the log message. */
+ varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m",
+ v->current_method->name, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
@@ -2343,13 +2573,15 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) {
VarlinkSymbol *symbol = hashmap_get(v->server->symbols, error_id);
if (!symbol)
- log_debug("No interface description defined for error '%s', not validating.", error_id);
+ varlink_log(v, "No interface description defined for error '%s', not validating.", error_id);
else {
const char *bad_field = NULL;
r = varlink_idl_validate_error(symbol, parameters, &bad_field);
if (r < 0)
- log_debug_errno(r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", error_id, strna(bad_field));
+ /* Please adjust test/units/end.sh when updating the log message. */
+ varlink_log_errno(v, r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m",
+ error_id, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
@@ -2425,6 +2657,13 @@ int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters) {
return -EINVAL;
}
+int varlink_error_invalid_parameter_name(Varlink *v, const char *name) {
+ return varlink_errorb(
+ v,
+ VARLINK_ERROR_INVALID_PARAMETER,
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(name))));
+}
+
int varlink_error_errno(Varlink *v, int error) {
return varlink_errorb(
v,
@@ -2464,7 +2703,9 @@ int varlink_notify(Varlink *v, JsonVariant *parameters) {
r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field);
if (r < 0)
- log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field));
+ /* Please adjust test/units/end.sh when updating the log message. */
+ varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m",
+ v->current_method->name, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
@@ -2504,8 +2745,7 @@ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch tab
r = json_dispatch_full(parameters, table, /* bad= */ NULL, /* flags= */ 0, userdata, &bad_field);
if (r < 0) {
if (bad_field)
- return varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER,
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(bad_field))));
+ return varlink_error_invalid_parameter_name(v, bad_field);
return r;
}
@@ -2567,12 +2807,29 @@ int varlink_get_peer_uid(Varlink *v, uid_t *ret) {
return varlink_log_errno(v, r, "Failed to acquire credentials: %m");
if (!uid_is_valid(v->ucred.uid))
- return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid.");
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid.");
*ret = v->ucred.uid;
return 0;
}
+int varlink_get_peer_gid(Varlink *v, gid_t *ret) {
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = varlink_acquire_ucred(v);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to acquire credentials: %m");
+
+ if (!gid_is_valid(v->ucred.gid))
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid.");
+
+ *ret = v->ucred.gid;
+ return 0;
+}
+
int varlink_get_peer_pid(Varlink *v, pid_t *ret) {
int r;
@@ -2590,6 +2847,54 @@ int varlink_get_peer_pid(Varlink *v, pid_t *ret) {
return 0;
}
+static int varlink_acquire_pidfd(Varlink *v) {
+ assert(v);
+
+ if (v->peer_pidfd >= 0)
+ return 0;
+
+ v->peer_pidfd = getpeerpidfd(v->fd);
+ if (v->peer_pidfd < 0)
+ return v->peer_pidfd;
+
+ return 0;
+}
+
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret) {
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ /* Returns r > 0 if we acquired the pidref via SO_PEERPIDFD (i.e. if we can use it for
+ * authentication). Returns == 0 if we didn't, and the pidref should not be used for
+ * authentication. */
+
+ r = varlink_acquire_pidfd(v);
+ if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
+ return r;
+
+ if (v->peer_pidfd < 0) {
+ pid_t pid;
+
+ r = varlink_get_peer_pid(v, &pid);
+ if (r < 0)
+ return r;
+
+ r = pidref_set_pid(ret, pid);
+ if (r < 0)
+ return r;
+
+ return 0; /* didn't get pidfd securely */
+ }
+
+ r = pidref_set_pidfd(ret, v->peer_pidfd);
+ if (r < 0)
+ return r;
+
+ return 1; /* got pidfd securely */
+}
+
int varlink_set_relative_timeout(Varlink *v, usec_t timeout) {
assert_return(v, -EINVAL);
assert_return(timeout > 0, -EINVAL);
@@ -2788,7 +3093,7 @@ int varlink_push_fd(Varlink *v, int fd) {
return i;
}
-int varlink_dup_fd(Varlink *v, int fd) {
+int varlink_push_dup_fd(Varlink *v, int fd) {
_cleanup_close_ int dp = -1;
int r;
@@ -2836,6 +3141,16 @@ int varlink_peek_fd(Varlink *v, size_t i) {
return v->input_fds[i];
}
+int varlink_peek_dup_fd(Varlink *v, size_t i) {
+ int fd;
+
+ fd = varlink_peek_fd(v, i);
+ if (fd < 0)
+ return fd;
+
+ return RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3));
+}
+
int varlink_take_fd(Varlink *v, size_t i) {
assert_return(v, -EINVAL);
@@ -2855,6 +3170,16 @@ int varlink_take_fd(Varlink *v, size_t i) {
static int verify_unix_socket(Varlink *v) {
assert(v);
+ /* Returns:
+ * • 0 if this is an AF_UNIX socket
+ * • -ENOTSOCK if this is not a socket at all
+ * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket
+ *
+ * Reminder:
+ * • v->af is < 0 if we haven't checked what kind of address family the thing is yet.
+ * • v->af == AF_UNSPEC if we checked but it's not a socket
+ * • otherwise: v->af contains the address family we determined */
+
if (v->af < 0) {
struct stat st;
@@ -2870,7 +3195,8 @@ static int verify_unix_socket(Varlink *v) {
return v->af;
}
- return v->af == AF_UNIX ? 0 : -ENOMEDIUM;
+ return v->af == AF_UNIX ? 0 :
+ v->af == AF_UNSPEC ? -ENOTSOCK : -ENOMEDIUM;
}
int varlink_set_allow_fd_passing_input(Varlink *v, bool b) {
@@ -2915,6 +3241,13 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
return 0;
}
+int varlink_set_input_sensitive(Varlink *v) {
+ assert_return(v, -EINVAL);
+
+ v->input_sensitive = true;
+ return 0;
+}
+
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
@@ -3021,9 +3354,11 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) {
server->n_connections++;
if (FLAGS_SET(server->flags, VARLINK_SERVER_ACCOUNT_UID)) {
+ assert(uid_is_valid(ucred->uid));
+
r = hashmap_ensure_allocated(&server->by_uid, NULL);
if (r < 0)
- return log_debug_errno(r, "Failed to allocate UID hash table: %m");
+ return varlink_server_log_errno(server, r, "Failed to allocate UID hash table: %m");
c = PTR_TO_UINT(hashmap_get(server->by_uid, UID_TO_PTR(ucred->uid)));
@@ -3032,7 +3367,7 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) {
r = hashmap_replace(server->by_uid, UID_TO_PTR(ucred->uid), UINT_TO_PTR(c + 1));
if (r < 0)
- return log_debug_errno(r, "Failed to increment counter in UID hash table: %m");
+ return varlink_server_log_errno(server, r, "Failed to increment counter in UID hash table: %m");
}
return 0;
@@ -3080,7 +3415,7 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret)
}
_cleanup_free_ char *desc = NULL;
- if (asprintf(&desc, "%s-%i", server->description ?: "varlink", v->fd) >= 0)
+ if (asprintf(&desc, "%s-%i", varlink_server_description(server), v->fd) >= 0)
v->description = TAKE_PTR(desc);
/* Link up the server and the connection, and take reference in both directions. Note that the
@@ -3141,6 +3476,9 @@ static int connect_callback(sd_event_source *source, int fd, uint32_t revents, v
TAKE_FD(cfd);
+ if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE))
+ varlink_set_input_sensitive(v);
+
if (ss->server->connect_callback) {
r = ss->server->connect_callback(ss->server, v, ss->server->userdata);
if (r < 0) {
@@ -3293,6 +3631,14 @@ int varlink_server_listen_auto(VarlinkServer *s) {
n++;
}
+ /* For debug purposes let's listen on an explicitly specified address */
+ const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN");
+ if (e) {
+ r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666);
+ if (r < 0)
+ return r;
+ }
+
return n;
}
@@ -3436,7 +3782,7 @@ int varlink_server_detach_event(VarlinkServer *s) {
LIST_FOREACH(sockets, ss, s->sockets)
ss->event_source = sd_event_source_disable_unref(ss->event_source);
- sd_event_unref(s->event);
+ s->event = sd_event_unref(s->event);
return 0;
}
@@ -3472,7 +3818,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth
if (varlink_symbol_in_interface(method, "org.varlink.service") ||
varlink_symbol_in_interface(method, "io.systemd"))
- return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method);
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method);
m = strdup(method);
if (!m)
@@ -3482,7 +3828,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth
if (r == -ENOMEM)
return log_oom_debug();
if (r < 0)
- return log_debug_errno(r, "Failed to register callback: %m");
+ return varlink_server_log_errno(s, r, "Failed to register callback: %m");
if (r > 0)
TAKE_PTR(m);
@@ -3519,7 +3865,7 @@ int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) {
assert_return(s, -EINVAL);
if (callback && s->connect_callback && callback != s->connect_callback)
- return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set.");
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set.");
s->connect_callback = callback;
return 0;
@@ -3529,7 +3875,7 @@ int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback)
assert_return(s, -EINVAL);
if (callback && s->disconnect_callback && callback != s->disconnect_callback)
- return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set.");
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set.");
s->disconnect_callback = callback;
return 0;
@@ -3543,7 +3889,7 @@ int varlink_server_add_interface(VarlinkServer *s, const VarlinkInterface *inter
assert_return(interface->name, -EINVAL);
if (hashmap_contains(s->interfaces, interface->name))
- return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name);
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name);
r = hashmap_ensure_put(&s->interfaces, &string_hash_ops, interface->name, (void*) interface);
if (r < 0)
@@ -3696,11 +4042,11 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f
return log_oom_debug();
if (v[n] != ' ')
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL),
"Failed to deserialize VarlinkServerSocket: %s: %m", value);
v = startswith(v + n + 1, "varlink-server-socket-fd=");
if (!v)
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL),
"Failed to deserialize VarlinkServerSocket fd %s: %m", value);
n = strcspn(v, " ");
@@ -3708,9 +4054,9 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f
fd = parse_fd(buf);
if (fd < 0)
- return log_debug_errno(fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf);
+ return varlink_server_log_errno(s, fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf);
if (!fdset_contains(fds, fd))
- return log_debug_errno(SYNTHETIC_ERRNO(EBADF),
+ return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBADF),
"VarlinkServerSocket varlink-server-socket-fd= has unknown fd %d: %m", fd);
ss = new(VarlinkServerSocket, 1);
@@ -3725,7 +4071,7 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f
r = varlink_server_add_socket_event_source(s, ss, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
- return log_debug_errno(r, "Failed to add VarlinkServerSocket event source to the event loop: %m");
+ return varlink_server_log_errno(s, r, "Failed to add VarlinkServerSocket event source to the event loop: %m");
LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss));
return 0;
@@ -3738,6 +4084,10 @@ int varlink_invocation(VarlinkInvocationFlags flags) {
/* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */
+ const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */
+ if (e)
+ return true;
+
r = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
if (r < 0)
return r;
@@ -3765,3 +4115,42 @@ int varlink_invocation(VarlinkInvocationFlags flags) {
return true;
}
+
+int varlink_error_to_errno(const char *error, JsonVariant *parameters) {
+ static const struct {
+ const char *error;
+ int value;
+ } table[] = {
+ { VARLINK_ERROR_DISCONNECTED, -ECONNRESET },
+ { VARLINK_ERROR_TIMEOUT, -ETIMEDOUT },
+ { VARLINK_ERROR_PROTOCOL, -EPROTO },
+ { VARLINK_ERROR_INTERFACE_NOT_FOUND, -EADDRNOTAVAIL },
+ { VARLINK_ERROR_METHOD_NOT_FOUND, -ENXIO },
+ { VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, -ENOTTY },
+ { VARLINK_ERROR_INVALID_PARAMETER, -EINVAL },
+ { VARLINK_ERROR_PERMISSION_DENIED, -EACCES },
+ { VARLINK_ERROR_EXPECTED_MORE, -EBADE },
+ };
+
+ if (!error)
+ return 0;
+
+ FOREACH_ELEMENT(t, table)
+ if (streq(error, t->error))
+ return t->value;
+
+ if (streq(error, VARLINK_ERROR_SYSTEM) && parameters) {
+ JsonVariant *e;
+
+ e = json_variant_by_key(parameters, "errno");
+ if (json_variant_is_integer(e)) {
+ int64_t i;
+
+ i = json_variant_integer(e);
+ if (i > 0 && i < ERRNO_MAX)
+ return -i;
+ }
+ }
+
+ return -EBADR; /* Catch-all */
+}
diff --git a/src/shared/varlink.h b/src/shared/varlink.h
index 6ec708a..f6dda63 100644
--- a/src/shared/varlink.h
+++ b/src/shared/varlink.h
@@ -4,6 +4,7 @@
#include "sd-event.h"
#include "json.h"
+#include "pidref.h"
#include "time-util.h"
#include "varlink-idl.h"
@@ -46,7 +47,8 @@ typedef enum VarlinkServerFlags {
VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */
VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */
VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */
- _VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1,
+ VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark al connection input as sensitive */
+ _VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1,
} VarlinkServerFlags;
typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
@@ -86,12 +88,39 @@ int varlink_send(Varlink *v, const char *method, JsonVariant *parameters);
int varlink_sendb(Varlink *v, const char *method, ...);
/* Send method call and wait for reply */
-int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
-int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+int varlink_call_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+static inline int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
+ return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
+}
+int varlink_call_and_log(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters);
+
+int varlink_callb_ap(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, va_list ap);
+static inline int varlink_callb_full(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, ret_flags);
+ r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, ret_flags, ap);
+ va_end(ap);
+ return r;
+}
+static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...) {
+ va_list ap;
+ int r;
+
+ va_start(ap, ret_error_id);
+ r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, NULL, ap);
+ va_end(ap);
+ return r;
+}
+int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...);
/* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */
-int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
-int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+int varlink_collect_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+static inline int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
+ return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
+}
+int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...);
/* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */
int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters);
@@ -109,22 +138,30 @@ int varlink_replyb(Varlink *v, ...);
int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters);
int varlink_errorb(Varlink *v, const char *error_id, ...);
int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters);
+int varlink_error_invalid_parameter_name(Varlink *v, const char *name);
int varlink_error_errno(Varlink *v, int error);
/* Enqueue a "more" reply */
int varlink_notify(Varlink *v, JsonVariant *parameters);
int varlink_notifyb(Varlink *v, ...);
+/* Ask for the current message to be dispatched again */
+int varlink_dispatch_again(Varlink *v);
+
+/* Get the currently processed incoming message */
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret);
+
/* Parsing incoming data via json_dispatch() and generate a nice error on parse errors */
int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch table[], void *userdata);
/* Write outgoing fds into the socket (to be associated with the next enqueued message) */
int varlink_push_fd(Varlink *v, int fd);
-int varlink_dup_fd(Varlink *v, int fd);
+int varlink_push_dup_fd(Varlink *v, int fd);
int varlink_reset_fds(Varlink *v);
/* Read incoming fds from the socket (associated with the currently handled message) */
int varlink_peek_fd(Varlink *v, size_t i);
+int varlink_peek_dup_fd(Varlink *v, size_t i);
int varlink_take_fd(Varlink *v, size_t i);
int varlink_set_allow_fd_passing_input(Varlink *v, bool b);
@@ -137,7 +174,9 @@ void* varlink_set_userdata(Varlink *v, void *userdata);
void* varlink_get_userdata(Varlink *v);
int varlink_get_peer_uid(Varlink *v, uid_t *ret);
+int varlink_get_peer_gid(Varlink *v, gid_t *ret);
int varlink_get_peer_pid(Varlink *v, pid_t *ret);
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret);
int varlink_set_relative_timeout(Varlink *v, usec_t usec);
@@ -145,6 +184,9 @@ VarlinkServer* varlink_get_server(Varlink *v);
int varlink_set_description(Varlink *v, const char *d);
+/* Automatically mark the parameters part of incoming messages as security sensitive */
+int varlink_set_input_sensitive(Varlink *v);
+
/* Create a varlink server */
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags);
VarlinkServer *varlink_server_ref(VarlinkServer *s);
@@ -200,6 +242,8 @@ typedef enum VarlinkInvocationFlags {
int varlink_invocation(VarlinkInvocationFlags flags);
+int varlink_error_to_errno(const char *error, JsonVariant *parameters);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
@@ -213,12 +257,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
/* This one we invented, and use for generically propagating system errors (errno) to clients */
#define VARLINK_ERROR_SYSTEM "io.systemd.System"
+/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */
+#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired"
+
/* These are errors defined in the Varlink spec */
#define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
#define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
#define VARLINK_ERROR_METHOD_NOT_IMPLEMENTED "org.varlink.service.MethodNotImplemented"
#define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter"
-
-/* These are errors we came up with and squatted the namespace with */
#define VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied"
#define VARLINK_ERROR_EXPECTED_MORE "org.varlink.service.ExpectedMore"
diff --git a/src/shared/verbs.c b/src/shared/verbs.c
index a38591d..7e7ac2a 100644
--- a/src/shared/verbs.c
+++ b/src/shared/verbs.c
@@ -177,4 +177,4 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
return verb->dispatch(1, STRV_MAKE(verb->verb), userdata);
return verb->dispatch(left, argv, userdata);
- }
+}
diff --git a/src/shared/vpick.c b/src/shared/vpick.c
new file mode 100644
index 0000000..4720e74
--- /dev/null
+++ b/src/shared/vpick.c
@@ -0,0 +1,699 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/stat.h>
+
+#include "architecture.h"
+#include "chase.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "recurse-dir.h"
+#include "vpick.h"
+
+void pick_result_done(PickResult *p) {
+ assert(p);
+
+ free(p->path);
+ safe_close(p->fd);
+ free(p->version);
+
+ *p = PICK_RESULT_NULL;
+}
+
+static int format_fname(
+ const PickFilter *filter,
+ PickFlags flags,
+ char **ret) {
+
+ _cleanup_free_ char *fn = NULL;
+ int r;
+
+ assert(filter);
+ assert(ret);
+
+ if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */
+ return -ENOEXEC;
+ if (strv_length(filter->suffix) > 1) /* suffix is not deterministic? */
+ return -ENOEXEC;
+
+ /* The format for names we match goes like this:
+ *
+ * <basename><suffix>
+ * or:
+ * <basename>_<version><suffix>
+ * or:
+ * <basename>_<version>_<architecture><suffix>
+ * or:
+ * <basename>_<architecture><suffix>
+ *
+ * (Note that basename can be empty, in which case the leading "_" is suppressed)
+ *
+ * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw
+ *
+ * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used
+ * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this
+ * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating
+ * fields).
+ *
+ * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the
+ * architecture field redundant.
+ *
+ * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix).
+ */
+
+ if (filter->basename) {
+ fn = strdup(filter->basename);
+ if (!fn)
+ return -ENOMEM;
+ }
+
+ if (filter->version) {
+ if (isempty(fn)) {
+ r = free_and_strdup(&fn, filter->version);
+ if (r < 0)
+ return r;
+ } else if (!strextend(&fn, "_", filter->version))
+ return -ENOMEM;
+ }
+
+ if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) {
+ const char *as = ASSERT_PTR(architecture_to_string(filter->architecture));
+ if (isempty(fn)) {
+ r = free_and_strdup(&fn, as);
+ if (r < 0)
+ return r;
+ } else if (!strextend(&fn, "_", as))
+ return -ENOMEM;
+ }
+
+ if (!strv_isempty(filter->suffix))
+ if (!strextend(&fn, filter->suffix[0]))
+ return -ENOMEM;
+
+ if (!filename_is_valid(fn))
+ return -EINVAL;
+
+ *ret = TAKE_PTR(fn);
+ return 0;
+}
+
+static int errno_from_mode(uint32_t type_mask, mode_t found) {
+ /* Returns the most appropriate error code if we are lookging for an inode of type of those in the
+ * 'type_mask' but found 'found' instead.
+ *
+ * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, …
+ * mode value. */
+
+ if (type_mask == 0) /* type doesn't matter */
+ return 0;
+
+ if (FLAGS_SET(type_mask, UINT32_C(1) << IFTODT(found)))
+ return 0;
+
+ if (type_mask == (UINT32_C(1) << DT_BLK))
+ return -ENOTBLK;
+ if (type_mask == (UINT32_C(1) << DT_DIR))
+ return -ENOTDIR;
+ if (type_mask == (UINT32_C(1) << DT_SOCK))
+ return -ENOTSOCK;
+
+ if (S_ISLNK(found))
+ return -ELOOP;
+ if (S_ISDIR(found))
+ return -EISDIR;
+
+ return -EBADF;
+}
+
+static int pin_choice(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *inode_path,
+ int _inode_fd, /* we always take ownership of the fd, even on failure */
+ unsigned tries_left,
+ unsigned tries_done,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd);
+ _cleanup_free_ char *resolved_path = NULL;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(inode_path);
+ assert(filter);
+ assert(ret);
+
+ if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) {
+ r = chaseat(toplevel_fd,
+ inode_path,
+ CHASE_AT_RESOLVE_IN_ROOT,
+ FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : 0,
+ inode_fd < 0 ? &inode_fd : NULL);
+ if (r < 0)
+ return r;
+
+ if (resolved_path)
+ inode_path = resolved_path;
+ }
+
+ struct stat st;
+ if (fstat(inode_fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to stat discovered inode '%s': %m", prefix_roota(toplevel_path, inode_path));
+
+ if (filter->type_mask != 0 &&
+ !FLAGS_SET(filter->type_mask, UINT32_C(1) << IFTODT(st.st_mode)))
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)),
+ "Inode '%s' has wrong type, found '%s'.",
+ prefix_roota(toplevel_path, inode_path),
+ inode_type_to_string(st.st_mode));
+
+ _cleanup_(pick_result_done) PickResult result = {
+ .fd = TAKE_FD(inode_fd),
+ .st = st,
+ .architecture = filter->architecture,
+ .tries_left = tries_left,
+ .tries_done = tries_done,
+ };
+
+ result.path = strdup(inode_path);
+ if (!result.path)
+ return log_oom_debug();
+
+ r = strdup_to(&result.version, filter->version);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PICK_RESULT(result);
+ return 1;
+}
+
+static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) {
+ unsigned left, done;
+ size_t n;
+
+ assert(s);
+ assert(ret_tries_left);
+ assert(ret_tries_done);
+
+ if (s[0] != '+')
+ goto nomatch;
+
+ s++;
+
+ n = strspn(s, DIGITS);
+ if (n == 0)
+ goto nomatch;
+
+ if (s[n] == 0) {
+ if (safe_atou(s, &left) < 0)
+ goto nomatch;
+
+ done = 0;
+ } else if (s[n] == '-') {
+ _cleanup_free_ char *c = NULL;
+
+ c = strndup(s, n);
+ if (!c)
+ return -ENOMEM;
+
+ if (safe_atou(c, &left) < 0)
+ goto nomatch;
+
+ s += n + 1;
+
+ if (!in_charset(s, DIGITS))
+ goto nomatch;
+
+ if (safe_atou(s, &done) < 0)
+ goto nomatch;
+ } else
+ goto nomatch;
+
+ *ret_tries_left = left;
+ *ret_tries_done = done;
+ return 1;
+
+nomatch:
+ *ret_tries_left = *ret_tries_done = UINT_MAX;
+ return 0;
+}
+
+static int make_choice(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *inode_path,
+ int _inode_fd, /* we always take ownership of the fd, even on failure */
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ static const Architecture local_architectures[] = {
+ /* In order of preference */
+ native_architecture(),
+#ifdef ARCHITECTURE_SECONDARY
+ ARCHITECTURE_SECONDARY,
+#endif
+ _ARCHITECTURE_INVALID, /* accept any arch, as last resort */
+ };
+
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL;
+ _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd);
+ const Architecture *architectures;
+ unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX;
+ size_t n_architectures, best_architecture_index = SIZE_MAX;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(inode_path);
+ assert(filter);
+ assert(ret);
+
+ if (inode_fd < 0) {
+ r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd);
+ if (r < 0)
+ return r;
+ }
+
+ /* Maybe the filter is fully specified? Then we can generate the file name directly */
+ r = format_fname(filter, flags, &j);
+ if (r >= 0) {
+ _cleanup_free_ char *object_path = NULL;
+
+ /* Yay! This worked! */
+ p = path_join(inode_path, j);
+ if (!p)
+ return log_oom_debug();
+
+ r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd);
+ if (r == -ENOENT) {
+ *ret = PICK_RESULT_NULL;
+ return 0;
+ }
+ if (r < 0)
+ return log_debug_errno(r, "Failed to open '%s': %m", prefix_roota(toplevel_path, p));
+
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p,
+ TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */
+ /* tries_left= */ UINT_MAX,
+ /* tries_done= */ UINT_MAX,
+ filter,
+ flags & ~PICK_RESOLVE,
+ ret);
+
+ } else if (r != -ENOEXEC)
+ return log_debug_errno(r, "Failed to format file name: %m");
+
+ /* Underspecified, so we do our enumeration dance */
+
+ /* Convert O_PATH to a regular directory fd */
+ dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ if (dir_fd < 0)
+ return log_debug_errno(dir_fd, "Failed to reopen '%s' as directory: %m", prefix_roota(toplevel_path, inode_path));
+
+ r = readdir_all(dir_fd, 0, &de);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read directory '%s': %m", prefix_roota(toplevel_path, inode_path));
+
+ if (filter->architecture < 0) {
+ architectures = local_architectures;
+ n_architectures = ELEMENTSOF(local_architectures);
+ } else {
+ architectures = &filter->architecture;
+ n_architectures = 1;
+ }
+
+ FOREACH_ARRAY(entry, de->entries, de->n_entries) {
+ unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX;
+ _cleanup_free_ char *dname = NULL;
+ size_t found_architecture_index = SIZE_MAX;
+ const char *e;
+
+ dname = strdup((*entry)->d_name);
+ if (!dname)
+ return log_oom_debug();
+
+ if (!isempty(filter->basename)) {
+ e = startswith(dname, filter->basename);
+ if (!e)
+ continue;
+
+ if (e[0] != '_')
+ continue;
+
+ e++;
+ } else
+ e = dname;
+
+ if (!strv_isempty(filter->suffix)) {
+ char *sfx = endswith_strv(e, filter->suffix);
+ if (!sfx)
+ continue;
+
+ *sfx = 0;
+ }
+
+ if (FLAGS_SET(flags, PICK_TRIES)) {
+ char *plus = strrchr(e, '+');
+ if (plus) {
+ r = parse_tries(plus, &found_tries_left, &found_tries_done);
+ if (r < 0)
+ return r;
+ if (r > 0) /* Found and parsed, now chop off */
+ *plus = 0;
+ }
+ }
+
+ if (FLAGS_SET(flags, PICK_ARCHITECTURE)) {
+ char *underscore = strrchr(e, '_');
+ Architecture a;
+
+ a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID;
+
+ for (size_t i = 0; i < n_architectures; i++)
+ if (architectures[i] == a) {
+ found_architecture_index = i;
+ break;
+ }
+
+ if (found_architecture_index == SIZE_MAX) { /* No matching arch found */
+ log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a));
+ continue;
+ }
+
+ /* Chop off architecture from string */
+ if (underscore)
+ *underscore = 0;
+ }
+
+ if (!version_is_valid(e)) {
+ log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name);
+ continue;
+ }
+
+ if (filter->version && !streq(filter->version, e)) {
+ log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version);
+ continue;
+ }
+
+ if (best_filename) { /* Already found one matching entry? Then figure out the better one */
+ int d = 0;
+
+ /* First, prefer entries with tries left over those without */
+ if (FLAGS_SET(flags, PICK_TRIES))
+ d = CMP(found_tries_left != 0, best_tries_left != 0);
+
+ /* Second, prefer newer versions */
+ if (d == 0)
+ d = strverscmp_improved(e, best_version);
+
+ /* Third, prefer native architectures over secondary architectures */
+ if (d == 0 &&
+ FLAGS_SET(flags, PICK_ARCHITECTURE) &&
+ found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX)
+ d = -CMP(found_architecture_index, best_architecture_index);
+
+ /* Fourth, prefer entries with more tries left */
+ if (FLAGS_SET(flags, PICK_TRIES)) {
+ if (d == 0)
+ d = CMP(found_tries_left, best_tries_left);
+
+ /* Fifth, prefer entries with fewer attempts done so far */
+ if (d == 0)
+ d = -CMP(found_tries_done, best_tries_done);
+ }
+
+ /* Last, just compare the filenames as strings */
+ if (d == 0)
+ d = strcmp((*entry)->d_name, best_filename);
+
+ if (d < 0) {
+ log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename);
+ continue;
+ }
+ }
+
+ r = free_and_strdup_warn(&best_version, e);
+ if (r < 0)
+ return r;
+
+ r = free_and_strdup_warn(&best_filename, (*entry)->d_name);
+ if (r < 0)
+ return r;
+
+ best_architecture_index = found_architecture_index;
+ best_tries_left = found_tries_left;
+ best_tries_done = found_tries_done;
+ }
+
+ if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */
+ *ret = PICK_RESULT_NULL;
+ return 0;
+ }
+
+ p = path_join(inode_path, best_filename);
+ if (!p)
+ return log_oom_debug();
+
+ object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
+ if (object_fd < 0)
+ return log_debug_errno(errno, "Failed to open '%s': %m", prefix_roota(toplevel_path, p));
+
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ p,
+ TAKE_FD(object_fd),
+ best_tries_left,
+ best_tries_done,
+ &(const PickFilter) {
+ .type_mask = filter->type_mask,
+ .basename = filter->basename,
+ .version = empty_to_null(best_version),
+ .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID,
+ .suffix = filter->suffix,
+ },
+ flags,
+ ret);
+}
+
+int path_pick(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret) {
+
+ _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL;
+ char * const *filter_suffix_strv = NULL;
+ const char *filter_suffix = NULL, *enumeration_path;
+ uint32_t filter_type_mask;
+ int r;
+
+ assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD);
+ assert(path);
+ assert(filter);
+ assert(ret);
+
+ /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports
+ * three ways to be called:
+ *
+ * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in
+ * which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files.
+ *
+ * • with no filter→basename explicitly specified and a path referring to a directory named in format
+ * "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir
+ * is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for
+ * "/foo/bar/baz.suffix.v/baz_*.suffix".
+ *
+ * • with a path whose penultimate component ends in ".v/". In this case the final component of the
+ * path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for
+ * "/foo/bar/baz.v/waldo_*.suffix".
+ */
+
+ /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */
+ if (filter->basename)
+ return make_choice(
+ toplevel_path,
+ toplevel_fd,
+ path,
+ /* inode_fd= */ -EBADF,
+ filter,
+ flags,
+ ret);
+
+ r = path_extract_filename(path, &fname);
+ if (r < 0) {
+ if (r != -EADDRNOTAVAIL) /* root dir or "." */
+ return r;
+
+ /* If there's no path element we can derive a pattern off, the don't */
+ goto bypass;
+ }
+
+ /* Remember if the path ends in a dash suffix */
+ bool slash_suffix = r == O_DIRECTORY;
+
+ const char *e = endswith(fname, ".v");
+ if (e) {
+ /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is
+ * "baz", possibly with a suffix chopped off if there's one specified. */
+ filter_bname = strndup(fname, e - fname);
+ if (!filter_bname)
+ return -ENOMEM;
+
+ /* Chop off suffix, if specified */
+ char *f = endswith_strv(filter_bname, filter->suffix);
+ if (f)
+ *f = 0;
+
+ filter_suffix_strv = filter->suffix;
+ filter_type_mask = filter->type_mask;
+
+ enumeration_path = path;
+ } else {
+ /* The path does not end in '.v', hence see if the last element is a pattern. */
+
+ char *wildcard = strrstr(fname, "___");
+ if (!wildcard)
+ goto bypass; /* Not a pattern, then bypass */
+
+ /* We found the '___' wildcard, hence everything after it is our filter suffix, and
+ * everything before is our filter basename */
+ *wildcard = 0;
+ filter_suffix = empty_to_null(wildcard + 3);
+
+ filter_bname = TAKE_PTR(fname);
+
+ r = path_extract_directory(path, &dir);
+ if (r < 0) {
+ if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */
+ return r;
+
+ goto bypass; /* No dir extractable, can't check if parent ends in ".v" */
+ }
+
+ r = path_extract_filename(dir, &parent);
+ if (r < 0) {
+ if (r != -EADDRNOTAVAIL) /* root dir or "." */
+ return r;
+
+ goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */
+ }
+
+ e = endswith(parent, ".v");
+ if (!e)
+ goto bypass; /* Doesn't end in ".v", shortcut */
+
+ filter_type_mask = filter->type_mask;
+ if (slash_suffix) {
+ /* If the pattern is suffixed by a / then we are looking for directories apparently. */
+ if (filter_type_mask != 0 && !FLAGS_SET(filter_type_mask, UINT32_C(1) << DT_DIR))
+ return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)),
+ "Specified pattern ends in '/', but not looking for directories, refusing.");
+ filter_type_mask = UINT32_C(1) << DT_DIR;
+ }
+
+ enumeration_path = dir;
+ }
+
+ return make_choice(
+ toplevel_path,
+ toplevel_fd,
+ enumeration_path,
+ /* inode_fd= */ -EBADF,
+ &(const PickFilter) {
+ .type_mask = filter_type_mask,
+ .basename = filter_bname,
+ .version = filter->version,
+ .architecture = filter->architecture,
+ .suffix = filter_suffix_strv ?: STRV_MAKE(filter_suffix),
+ },
+ flags,
+ ret);
+
+bypass:
+ /* Don't make any choice, but just use the passed path literally */
+ return pin_choice(
+ toplevel_path,
+ toplevel_fd,
+ path,
+ /* inode_fd= */ -EBADF,
+ /* tries_left= */ UINT_MAX,
+ /* tries_done= */ UINT_MAX,
+ filter,
+ flags,
+ ret);
+}
+
+int path_pick_update_warn(
+ char **path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret_result) {
+
+ _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
+ int r;
+
+ assert(path);
+ assert(*path);
+ assert(filter);
+
+ /* This updates the first argument if needed! */
+
+ r = path_pick(/* toplevel_path= */ NULL,
+ /* toplevel_fd= */ AT_FDCWD,
+ *path,
+ filter,
+ flags,
+ &result);
+ if (r == -ENOENT) {
+ log_debug("Path '%s' doesn't exist, leaving as is.", *path);
+
+ if (ret_result)
+ *ret_result = PICK_RESULT_NULL;
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to pick version on path '%s': %m", *path);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path);
+
+ log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version));
+
+ if (ret_result) {
+ r = free_and_strdup_warn(path, result.path);
+ if (r < 0)
+ return r;
+
+ *ret_result = TAKE_PICK_RESULT(result);
+ } else
+ free_and_replace(*path, result.path);
+
+ return 1;
+}
+
+const PickFilter pick_filter_image_raw = {
+ .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK),
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = STRV_MAKE(".raw"),
+};
+
+const PickFilter pick_filter_image_dir = {
+ .type_mask = UINT32_C(1) << DT_DIR,
+ .architecture = _ARCHITECTURE_INVALID,
+};
+
+const PickFilter pick_filter_image_any = {
+ .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR),
+ .architecture = _ARCHITECTURE_INVALID,
+ .suffix = STRV_MAKE(".raw", ""),
+};
diff --git a/src/shared/vpick.h b/src/shared/vpick.h
new file mode 100644
index 0000000..38251c8
--- /dev/null
+++ b/src/shared/vpick.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "architecture.h"
+
+typedef enum PickFlags {
+ PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */
+ PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */
+ PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */
+} PickFlags;
+
+typedef struct PickFilter {
+ uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */
+ const char *basename; /* Can be overridden by search pattern */
+ const char *version;
+ Architecture architecture;
+ char * const *suffix; /* Can be overridden by search pattern */
+} PickFilter;
+
+typedef struct PickResult {
+ char *path;
+ int fd; /* O_PATH */
+ struct stat st;
+ char *version;
+ Architecture architecture;
+ unsigned tries_left;
+ unsigned tries_done;
+} PickResult;
+
+#define PICK_RESULT_NULL \
+ (const PickResult) { \
+ .fd = -EBADF, \
+ .st.st_mode = MODE_INVALID, \
+ .architecture = _ARCHITECTURE_INVALID, \
+ .tries_left = UINT_MAX, \
+ .tries_done = UINT_MAX, \
+ }
+
+#define TAKE_PICK_RESULT(pick) TAKE_GENERIC(pick, PickResult, PICK_RESULT_NULL)
+
+void pick_result_done(PickResult *p);
+
+int path_pick(
+ const char *toplevel_path,
+ int toplevel_fd,
+ const char *path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret);
+
+int path_pick_update_warn(
+ char **path,
+ const PickFilter *filter,
+ PickFlags flags,
+ PickResult *ret);
+
+extern const PickFilter pick_filter_image_raw;
+extern const PickFilter pick_filter_image_dir;
+extern const PickFilter pick_filter_image_any;
diff --git a/src/shared/wall.c b/src/shared/wall.c
index d5900ef..c5d6439 100644
--- a/src/shared/wall.c
+++ b/src/shared/wall.c
@@ -30,8 +30,9 @@ static int write_to_terminal(const char *tty, const char *message) {
fd = open(tty, O_WRONLY|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return -errno;
- if (!isatty(fd))
- return -ENOTTY;
+
+ if (!isatty_safe(fd))
+ return -errno;
return loop_write_full(fd, message, SIZE_MAX, TIMEOUT_USEC);
}
diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c
index 99ccefb..810c5b5 100644
--- a/src/shared/watchdog.c
+++ b/src/shared/watchdog.c
@@ -41,7 +41,7 @@ static int saturated_usec_to_sec(usec_t val) {
return MIN(t, (usec_t) WATCHDOG_TIMEOUT_MAX_SEC); /* Saturate to watchdog max */
}
-static int get_watchdog_sysfs_path(const char *filename, char **ret_path) {
+static int watchdog_get_sysfs_path(const char *filename, char **ret_path) {
struct stat st;
if (watchdog_fd < 0)
@@ -59,11 +59,11 @@ static int get_watchdog_sysfs_path(const char *filename, char **ret_path) {
return 0;
}
-static int get_pretimeout_governor(char **ret_gov) {
+static int watchdog_get_pretimeout_governor(char **ret_gov) {
_cleanup_free_ char *sys_fn = NULL;
int r;
- r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn);
+ r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn);
if (r < 0)
return r;
@@ -78,14 +78,14 @@ static int get_pretimeout_governor(char **ret_gov) {
return 0;
}
-static int set_pretimeout_governor(const char *governor) {
+static int watchdog_set_pretimeout_governor(const char *governor) {
_cleanup_free_ char *sys_fn = NULL;
int r;
if (isempty(governor))
return 0; /* Nothing to do */
- r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn);
+ r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn);
if (r < 0)
return r;
@@ -205,7 +205,7 @@ static int watchdog_ping_now(void) {
return 0;
}
-static int update_pretimeout(void) {
+static int watchdog_update_pretimeout(void) {
_cleanup_free_ char *governor = NULL;
int r, t_sec, pt_sec;
@@ -223,9 +223,9 @@ static int update_pretimeout(void) {
watchdog_supports_pretimeout = false;
/* Update the pretimeout governor as well */
- (void) set_pretimeout_governor(watchdog_pretimeout_governor);
+ (void) watchdog_set_pretimeout_governor(watchdog_pretimeout_governor);
- r = get_pretimeout_governor(&governor);
+ r = watchdog_get_pretimeout_governor(&governor);
if (r < 0)
return log_warning_errno(r, "Watchdog: failed to read pretimeout governor: %m");
if (isempty(governor))
@@ -259,7 +259,7 @@ static int update_pretimeout(void) {
return r;
}
-static int update_timeout(void) {
+static int watchdog_update_timeout(void) {
int r;
usec_t previous_timeout;
@@ -296,7 +296,7 @@ static int update_timeout(void) {
* changed as well by the driver or the kernel so we need to update the
* pretimeout now. Or if the watchdog is being configured for the first
* time, we want to configure the pretimeout before it is enabled. */
- (void) update_pretimeout();
+ (void) watchdog_update_pretimeout();
r = watchdog_set_enable(true);
if (r < 0)
@@ -307,7 +307,7 @@ static int update_timeout(void) {
return watchdog_ping_now();
}
-static int open_watchdog(void) {
+static int watchdog_open(void) {
struct watchdog_info ident;
char **try_order;
int r;
@@ -338,7 +338,9 @@ static int open_watchdog(void) {
}
if (watchdog_fd < 0)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s: %m", watchdog_device ?: "auto");
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s.", watchdog_device ?: "auto");
+
+ watchdog_last_ping = USEC_INFINITY;
if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) < 0)
log_debug_errno(errno, "Hardware watchdog %s does not support WDIOC_GETSUPPORT ioctl, ignoring: %m", watchdog_device);
@@ -348,7 +350,7 @@ static int open_watchdog(void) {
ident.firmware_version,
watchdog_device);
- r = update_timeout();
+ r = watchdog_update_timeout();
if (r < 0)
goto close_and_fail;
@@ -396,9 +398,9 @@ int watchdog_setup(usec_t timeout) {
watchdog_timeout = timeout;
if (watchdog_fd < 0)
- return open_watchdog();
+ return watchdog_open();
- r = update_timeout();
+ r = watchdog_update_timeout();
if (r < 0)
watchdog_timeout = previous_timeout;
@@ -415,17 +417,17 @@ int watchdog_setup_pretimeout(usec_t timeout) {
* even if it fails to update the timeout. */
watchdog_pretimeout = timeout;
- return update_pretimeout();
+ return watchdog_update_pretimeout();
}
int watchdog_setup_pretimeout_governor(const char *governor) {
if (free_and_strdup(&watchdog_pretimeout_governor, governor) < 0)
return -ENOMEM;
- return set_pretimeout_governor(watchdog_pretimeout_governor);
+ return watchdog_set_pretimeout_governor(watchdog_pretimeout_governor);
}
-static usec_t calc_timeout(void) {
+static usec_t watchdog_calc_timeout(void) {
/* Calculate the effective timeout which accounts for the watchdog
* pretimeout if configured and supported. */
if (watchdog_supports_pretimeout && timestamp_is_set(watchdog_pretimeout) && watchdog_timeout >= watchdog_pretimeout)
@@ -435,7 +437,7 @@ static usec_t calc_timeout(void) {
}
usec_t watchdog_runtime_wait(void) {
- usec_t timeout = calc_timeout();
+ usec_t timeout = watchdog_calc_timeout();
if (!timestamp_is_set(timeout))
return USEC_INFINITY;
@@ -458,10 +460,10 @@ int watchdog_ping(void) {
if (watchdog_fd < 0)
/* open_watchdog() will automatically ping the device for us if necessary */
- return open_watchdog();
+ return watchdog_open();
ntime = now(CLOCK_BOOTTIME);
- timeout = calc_timeout();
+ timeout = watchdog_calc_timeout();
/* Never ping earlier than watchdog_timeout/4 and try to ping
* by watchdog_timeout/2 plus scheduling latencies at the latest */
diff --git a/src/shared/wifi-util.c b/src/shared/wifi-util.c
index d4e6dca..052f560 100644
--- a/src/shared/wifi-util.c
+++ b/src/shared/wifi-util.c
@@ -55,7 +55,7 @@ int wifi_get_interface(sd_netlink *genl, int ifindex, enum nl80211_iftype *ret_i
if (r < 0)
return log_debug_errno(r, "Failed to get NL80211_ATTR_IFTYPE attribute: %m");
- r = sd_netlink_message_read_data_suffix0(reply, NL80211_ATTR_SSID, &len, (void**) &ssid);
+ r = sd_netlink_message_read_data(reply, NL80211_ATTR_SSID, &len, (void**) &ssid);
if (r < 0 && r != -ENODATA)
return log_debug_errno(r, "Failed to get NL80211_ATTR_SSID attribute: %m");
if (r >= 0) {