summaryrefslogtreecommitdiffstats
path: root/src/libsystemd/sd-id128
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd/sd-id128')
-rw-r--r--src/libsystemd/sd-id128/id128-util.c265
-rw-r--r--src/libsystemd/sd-id128/id128-util.h58
-rw-r--r--src/libsystemd/sd-id128/sd-id128.c382
3 files changed, 705 insertions, 0 deletions
diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c
new file mode 100644
index 0000000..94bfd70
--- /dev/null
+++ b/src/libsystemd/sd-id128/id128-util.c
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "sha256.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "sync-util.h"
+#include "virt.h"
+
+int id128_from_string_nonzero(const char *s, sd_id128_t *ret) {
+ sd_id128_t t;
+ int r;
+
+ assert(ret);
+
+ r = sd_id128_from_string(ASSERT_PTR(s), &t);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_is_null(t))
+ return -ENXIO;
+
+ *ret = t;
+ return 0;
+}
+
+bool id128_is_valid(const char *s) {
+ size_t l;
+
+ assert(s);
+
+ l = strlen(s);
+
+ if (l == SD_ID128_STRING_MAX - 1)
+ /* Plain formatted 128-bit hex string */
+ return in_charset(s, HEXDIGITS);
+
+ if (l == SD_ID128_UUID_STRING_MAX - 1) {
+ /* Formatted UUID */
+ for (size_t i = 0; i < l; i++) {
+ char c = s[i];
+
+ if (IN_SET(i, 8, 13, 18, 23)) {
+ if (c != '-')
+ return false;
+ } else if (!ascii_ishex(c))
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) {
+ char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
+ sd_id128_t id;
+ ssize_t l;
+ int r;
+
+ assert(fd >= 0);
+
+ /* Reads an 128-bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both
+ * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they
+ * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you
+ * accept".
+ *
+ * This returns the following:
+ * -ENOMEDIUM: an empty string,
+ * -ENOPKG: "uninitialized" or "uninitialized\n",
+ * -EUCLEAN: other invalid strings. */
+
+ l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */
+ if (l < 0)
+ return (int) l;
+ if (l == 0) /* empty? */
+ return -ENOMEDIUM;
+
+ switch (l) {
+
+ case STRLEN("uninitialized"):
+ case STRLEN("uninitialized\n"):
+ return strneq(buffer, "uninitialized\n", l) ? -ENOPKG : -EINVAL;
+
+ case SD_ID128_STRING_MAX: /* plain UUID with trailing newline */
+ if (buffer[SD_ID128_STRING_MAX-1] != '\n')
+ return -EUCLEAN;
+
+ _fallthrough_;
+ case SD_ID128_STRING_MAX-1: /* plain UUID without trailing newline */
+ if (!FLAGS_SET(f, ID128_FORMAT_PLAIN))
+ return -EUCLEAN;
+
+ buffer[SD_ID128_STRING_MAX-1] = 0;
+ break;
+
+ case SD_ID128_UUID_STRING_MAX: /* RFC UUID with trailing newline */
+ if (buffer[SD_ID128_UUID_STRING_MAX-1] != '\n')
+ return -EUCLEAN;
+
+ _fallthrough_;
+ case SD_ID128_UUID_STRING_MAX-1: /* RFC UUID without trailing newline */
+ if (!FLAGS_SET(f, ID128_FORMAT_UUID))
+ return -EUCLEAN;
+
+ buffer[SD_ID128_UUID_STRING_MAX-1] = 0;
+ break;
+
+ default:
+ return -EUCLEAN;
+ }
+
+ r = sd_id128_from_string(buffer, &id);
+ if (r == -EINVAL)
+ return -EUCLEAN;
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+ return -ENOMEDIUM;
+
+ if (ret)
+ *ret = id;
+ return 0;
+}
+
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0);
+ if (fd < 0)
+ return fd;
+
+ return id128_read_fd(fd, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
+ char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
+ size_t sz;
+ int r;
+
+ assert(fd >= 0);
+ assert(IN_SET((f & ID128_FORMAT_ANY), ID128_FORMAT_PLAIN, ID128_FORMAT_UUID));
+
+ if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+ return -ENOMEDIUM;
+
+ if (FLAGS_SET(f, ID128_FORMAT_PLAIN)) {
+ assert_se(sd_id128_to_string(id, buffer));
+ sz = SD_ID128_STRING_MAX;
+ } else {
+ assert_se(sd_id128_to_uuid_string(id, buffer));
+ sz = SD_ID128_UUID_STRING_MAX;
+ }
+
+ buffer[sz - 1] = '\n';
+ r = loop_write(fd, buffer, sz);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(f, ID128_SYNC_ON_WRITE)) {
+ r = fsync_full(fd);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444);
+ if (fd < 0)
+ return fd;
+
+ return id128_write_fd(fd, f, id);
+}
+
+void id128_hash_func(const sd_id128_t *p, struct siphash *state) {
+ siphash24_compress(p, sizeof(sd_id128_t), state);
+}
+
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
+ return memcmp(a, b, 16);
+}
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
+ /* Stolen from generate_random_uuid() of drivers/char/random.c
+ * in the kernel sources */
+
+ /* Set UUID version to 4 --- truly random generation */
+ id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
+
+ /* Set the UUID variant to DCE */
+ id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
+
+ return id;
+}
+
+DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(id128_hash_ops_free, sd_id128_t, id128_hash_func, id128_compare_func, free);
+
+int id128_get_product(sd_id128_t *ret) {
+ sd_id128_t uuid;
+ int r;
+
+ assert(ret);
+
+ /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
+ * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
+
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0) /* Refuse returning this in containers, as this is not a property of our system then, but
+ * of the host */
+ return -ENOENT;
+
+ r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
+ if (r == -ENOENT)
+ r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
+ if (r == -ENOENT)
+ r = id128_read("/sys/hypervisor/uuid", ID128_FORMAT_UUID, &uuid);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_is_null(uuid) || sd_id128_is_allf(uuid))
+ return -EADDRNOTAVAIL; /* Recognizable error */
+
+ *ret = uuid;
+ return 0;
+}
+
+sd_id128_t id128_digest(const void *data, size_t size) {
+ assert(data || size == 0);
+
+ /* Hashes a UUID from some arbitrary data */
+
+ if (size == SIZE_MAX)
+ size = strlen(data);
+
+ uint8_t h[SHA256_DIGEST_SIZE];
+ sd_id128_t id;
+
+ /* Take the first half of the SHA256 result */
+ assert_cc(sizeof(h) >= sizeof(id.bytes));
+ memcpy(id.bytes, sha256_direct(data, size, h), sizeof(id.bytes));
+
+ return id128_make_v4_uuid(id);
+}
diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h
new file mode 100644
index 0000000..53ba50a
--- /dev/null
+++ b/src/libsystemd/sd-id128/id128-util.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include "sd-id128.h"
+
+#include "errno-util.h"
+#include "hash-funcs.h"
+#include "macro.h"
+
+bool id128_is_valid(const char *s) _pure_;
+
+typedef enum Id128Flag {
+ ID128_FORMAT_PLAIN = 1 << 0, /* formatted as 32 hex chars as-is */
+ ID128_FORMAT_UUID = 1 << 1, /* formatted as 36 character uuid string */
+ ID128_FORMAT_ANY = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
+
+ ID128_SYNC_ON_WRITE = 1 << 2, /* Sync the file after write. Used only when writing an ID. */
+ ID128_REFUSE_NULL = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
+} Id128Flag;
+
+int id128_from_string_nonzero(const char *s, sd_id128_t *ret);
+
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
+static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
+ return id128_read_at(AT_FDCWD, path, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id);
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id);
+static inline int id128_write(const char *path, Id128Flag f, sd_id128_t id) {
+ return id128_write_at(AT_FDCWD, path, f, id);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret);
+int id128_get_machine_at(int rfd, sd_id128_t *ret);
+
+void id128_hash_func(const sd_id128_t *p, struct siphash *state);
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
+extern const struct hash_ops id128_hash_ops;
+extern const struct hash_ops id128_hash_ops_free;
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id);
+
+int id128_get_product(sd_id128_t *ret);
+
+sd_id128_t id128_digest(const void *data, size_t size);
+
+/* A helper to check for the three relevant cases of "machine ID not initialized" */
+#define ERRNO_IS_NEG_MACHINE_ID_UNSET(r) \
+ IN_SET(r, \
+ -ENOENT, \
+ -ENOMEDIUM, \
+ -ENOPKG)
+_DEFINE_ABS_WRAPPER(MACHINE_ID_UNSET);
diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c
new file mode 100644
index 0000000..9fda79a
--- /dev/null
+++ b/src/libsystemd/sd-id128/sd-id128.c
@@ -0,0 +1,382 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "chase.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "hmac.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "macro.h"
+#include "missing_syscall.h"
+#include "missing_threads.h"
+#include "path-util.h"
+#include "random-util.h"
+#include "stat-util.h"
+#include "user-util.h"
+
+_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) {
+ size_t k = 0;
+
+ assert_return(s, NULL);
+
+ for (size_t n = 0; n < sizeof(sd_id128_t); n++) {
+ s[k++] = hexchar(id.bytes[n] >> 4);
+ s[k++] = hexchar(id.bytes[n] & 0xF);
+ }
+
+ assert(k == SD_ID128_STRING_MAX - 1);
+ s[k] = 0;
+
+ return s;
+}
+
+_public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]) {
+ size_t k = 0;
+
+ assert_return(s, NULL);
+
+ /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
+
+ for (size_t n = 0; n < sizeof(sd_id128_t); n++) {
+
+ if (IN_SET(n, 4, 6, 8, 10))
+ s[k++] = '-';
+
+ s[k++] = hexchar(id.bytes[n] >> 4);
+ s[k++] = hexchar(id.bytes[n] & 0xF);
+ }
+
+ assert(k == SD_ID128_UUID_STRING_MAX - 1);
+ s[k] = 0;
+
+ return s;
+}
+
+_public_ int sd_id128_from_string(const char *s, sd_id128_t *ret) {
+ size_t n, i;
+ sd_id128_t t;
+ bool is_guid = false;
+
+ assert_return(s, -EINVAL);
+
+ for (n = 0, i = 0; n < sizeof(sd_id128_t);) {
+ int a, b;
+
+ if (s[i] == '-') {
+ /* Is this a GUID? Then be nice, and skip over
+ * the dashes */
+
+ if (i == 8)
+ is_guid = true;
+ else if (IN_SET(i, 13, 18, 23)) {
+ if (!is_guid)
+ return -EINVAL;
+ } else
+ return -EINVAL;
+
+ i++;
+ continue;
+ }
+
+ a = unhexchar(s[i++]);
+ if (a < 0)
+ return -EINVAL;
+
+ b = unhexchar(s[i++]);
+ if (b < 0)
+ return -EINVAL;
+
+ t.bytes[n++] = (a << 4) | b;
+ }
+
+ if (i != (is_guid ? SD_ID128_UUID_STRING_MAX : SD_ID128_STRING_MAX) - 1)
+ return -EINVAL;
+
+ if (s[i] != 0)
+ return -EINVAL;
+
+ if (ret)
+ *ret = t;
+ return 0;
+}
+
+_public_ int sd_id128_string_equal(const char *s, sd_id128_t id) {
+ sd_id128_t parsed;
+ int r;
+
+ if (!s)
+ return false;
+
+ /* Checks if the specified string matches a valid string representation of the specified 128 bit ID/uuid */
+
+ r = sd_id128_from_string(s, &parsed);
+ if (r < 0)
+ return r;
+
+ return sd_id128_equal(parsed, id);
+}
+
+_public_ int sd_id128_get_machine(sd_id128_t *ret) {
+ static thread_local sd_id128_t saved_machine_id = {};
+ int r;
+
+ if (sd_id128_is_null(saved_machine_id)) {
+ r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &saved_machine_id);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret)
+ *ret = saved_machine_id;
+ return 0;
+}
+
+int id128_get_machine_at(int rfd, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ r = dir_fd_is_root_or_cwd(rfd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return sd_id128_get_machine(ret);
+
+ fd = chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (fd < 0)
+ return fd;
+
+ return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -EBADF;
+
+ if (empty_or_root(root))
+ return sd_id128_get_machine(ret);
+
+ fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (fd < 0)
+ return fd;
+
+ return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+_public_ int sd_id128_get_boot(sd_id128_t *ret) {
+ static thread_local sd_id128_t saved_boot_id = {};
+ int r;
+
+ if (sd_id128_is_null(saved_boot_id)) {
+ r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id);
+ if (r == -ENOENT && proc_mounted() == 0)
+ return -ENOSYS;
+ if (r < 0)
+ return r;
+ }
+
+ if (ret)
+ *ret = saved_boot_id;
+ return 0;
+}
+
+static int get_invocation_from_keyring(sd_id128_t *ret) {
+ _cleanup_free_ char *description = NULL;
+ char *d, *p, *g, *u, *e;
+ unsigned long perms;
+ key_serial_t key;
+ size_t sz = 256;
+ uid_t uid;
+ gid_t gid;
+ int r, c;
+
+#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
+ KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
+
+ assert(ret);
+
+ key = request_key("user", "invocation_id", NULL, 0);
+ if (key == -1) {
+ /* Keyring support not available? No invocation key stored? */
+ if (IN_SET(errno, ENOSYS, ENOKEY))
+ return -ENXIO;
+
+ return -errno;
+ }
+
+ for (;;) {
+ description = new(char, sz);
+ if (!description)
+ return -ENOMEM;
+
+ c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
+ if (c < 0)
+ return -errno;
+
+ if ((size_t) c <= sz)
+ break;
+
+ sz = c;
+ free(description);
+ }
+
+ /* The kernel returns a final NUL in the string, verify that. */
+ assert(description[c-1] == 0);
+
+ /* Chop off the final description string */
+ d = strrchr(description, ';');
+ if (!d)
+ return -EUCLEAN;
+ *d = 0;
+
+ /* Look for the permissions */
+ p = strrchr(description, ';');
+ if (!p)
+ return -EUCLEAN;
+
+ errno = 0;
+ perms = strtoul(p + 1, &e, 16);
+ if (errno > 0)
+ return -errno;
+ if (e == p + 1) /* Read at least one character */
+ return -EUCLEAN;
+ if (e != d) /* Must reached the end */
+ return -EUCLEAN;
+
+ if ((perms & ~MAX_PERMS) != 0)
+ return -EPERM;
+
+ *p = 0;
+
+ /* Look for the group ID */
+ g = strrchr(description, ';');
+ if (!g)
+ return -EUCLEAN;
+ r = parse_gid(g + 1, &gid);
+ if (r < 0)
+ return r;
+ if (gid != 0)
+ return -EPERM;
+ *g = 0;
+
+ /* Look for the user ID */
+ u = strrchr(description, ';');
+ if (!u)
+ return -EUCLEAN;
+ r = parse_uid(u + 1, &uid);
+ if (r < 0)
+ return r;
+ if (uid != 0)
+ return -EPERM;
+
+ c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
+ if (c < 0)
+ return -errno;
+ if (c != sizeof(sd_id128_t))
+ return -EUCLEAN;
+
+ return 0;
+}
+
+static int get_invocation_from_environment(sd_id128_t *ret) {
+ const char *e;
+ int r;
+
+ assert(ret);
+
+ e = secure_getenv("INVOCATION_ID");
+ if (!e)
+ return -ENXIO;
+
+ r = sd_id128_from_string(e, ret);
+ return r == -EINVAL ? -EUCLEAN : r;
+}
+
+_public_ int sd_id128_get_invocation(sd_id128_t *ret) {
+ static thread_local sd_id128_t saved_invocation_id = {};
+ int r;
+
+ if (sd_id128_is_null(saved_invocation_id)) {
+ /* We first check the environment. The environment variable is primarily relevant for user
+ * services, and sufficiently safe as long as no privilege boundary is involved. */
+ r = get_invocation_from_environment(&saved_invocation_id);
+ if (r == -ENXIO)
+ /* The kernel keyring is relevant for system services (as for user services we don't
+ * store the invocation ID in the keyring, as there'd be no trust benefit in that). */
+ r = get_invocation_from_keyring(&saved_invocation_id);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_is_null(saved_invocation_id))
+ return -ENOMEDIUM;
+ }
+
+ if (ret)
+ *ret = saved_invocation_id;
+ return 0;
+}
+
+_public_ int sd_id128_randomize(sd_id128_t *ret) {
+ sd_id128_t t;
+
+ assert_return(ret, -EINVAL);
+
+ random_bytes(&t, sizeof(t));
+
+ /* Turn this into a valid v4 UUID, to be nice. Note that we
+ * only guarantee this for newly generated UUIDs, not for
+ * pre-existing ones. */
+
+ *ret = id128_make_v4_uuid(t);
+ return 0;
+}
+
+_public_ int sd_id128_get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
+ assert_cc(sizeof(sd_id128_t) < SHA256_DIGEST_SIZE); /* Check that we don't need to pad with zeros. */
+ union {
+ uint8_t hmac[SHA256_DIGEST_SIZE];
+ sd_id128_t result;
+ } buf;
+
+ assert_return(ret, -EINVAL);
+ assert_return(!sd_id128_is_null(app_id), -ENXIO);
+
+ hmac_sha256(&base, sizeof(base), &app_id, sizeof(app_id), buf.hmac);
+
+ /* Take only the first half. */
+ *ret = id128_make_v4_uuid(buf.result);
+ return 0;
+}
+
+_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+ sd_id128_t id;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return r;
+
+ return sd_id128_get_app_specific(id, app_id, ret);
+}
+
+_public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+ sd_id128_t id;
+ int r;
+
+ assert_return(ret, -EINVAL);
+
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return r;
+
+ return sd_id128_get_app_specific(id, app_id, ret);
+}