diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml
index 8be8972907..ed2dabef3b 100644
--- a/man/hostnamectl.xml
+++ b/man/hostnamectl.xml
@@ -195,6 +195,7 @@
+
diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml
index 3a5088eded..cc61caca71 100644
--- a/man/org.freedesktop.hostname1.xml
+++ b/man/org.freedesktop.hostname1.xml
@@ -58,6 +58,7 @@ node /org/freedesktop/hostname1 {
in b interactive);
GetProductUUID(in b interactive,
out ay uuid);
+ Describe(out s UNNAMED);
properties:
readonly s Hostname = '...';
readonly s StaticHostname = '...';
@@ -118,6 +119,8 @@ node /org/freedesktop/hostname1 {
+
+
@@ -232,6 +235,17 @@ node /org/freedesktop/hostname1 {
machine-info3
for that. For more information on these files and syscalls see the respective man pages.
+ KernelName, KernelRelease, and
+ KernelVersion expose the kernel name (e.g. Linux), release
+ (e.g. 5.0.0-11), and version (i.e. the build number, e.g. #11) as
+ reported by uname2.
+ OperatingSystemPrettyName, OperatingSystemCPEName, and
+ HomeURL expose the PRETTY_NAME=, CPE_NAME= and
+ HOME_URL= fields from
+ os-release5. The
+ purpose of those properties is to allow remote clients to access this information over D-Bus. Local
+ clients can access the information directly.
+
Methods
@@ -261,22 +275,12 @@ node /org/freedesktop/hostname1 {
machine-info5 for
the semantics of those settings.
- GetProductUUID() returns the "product uuid" as exposed by the kernel based
+ GetProductUUID() returns the "product UUID" as exposed by the kernel based
on DMI information in /sys/class/dmi/id/product_uuid. Reading the file directly
requires root privileges, and this method allows access to unprivileged clients through the polkit
framework.
- KernelName, KernelRelease, and
- KernelVersion expose the kernel name (e.g. Linux), release
- (e.g. 5.0.0-11), and version (i.e. the build number, e.g. #11) as
- reported by
- uname2.
- OperatingSystemPrettyName, OperatingSystemCPEName, and
- HomeURL expose the PRETTY_NAME=, CPE_NAME= and
- HOME_URL= fields from
- os-release5. The
- purpose of those properties is to allow remote clients to access this information over D-Bus. Local
- clients can access the information directly.
+ Describe() returns a JSON representation of all properties in one.
diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c
index e7cb015ef2..19c04869dd 100644
--- a/src/hostname/hostnamectl.c
+++ b/src/hostname/hostnamectl.c
@@ -17,7 +17,9 @@
#include "format-table.h"
#include "hostname-setup.h"
#include "hostname-util.h"
+#include "json.h"
#include "main-func.h"
+#include "parse-argument.h"
#include "pretty-print.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
@@ -30,6 +32,7 @@ static char *arg_host = NULL;
static bool arg_transient = false;
static bool arg_pretty = false;
static bool arg_static = false;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
typedef struct StatusInfo {
const char *hostname;
@@ -317,6 +320,37 @@ static int show_all_names(sd_bus *bus) {
static int show_status(int argc, char **argv, void *userdata) {
sd_bus *bus = userdata;
+ int r;
+
+ if (arg_json_format_flags != JSON_FORMAT_OFF) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ const char *text = NULL;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ "Describe",
+ &error,
+ &reply,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not get description: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "s", &text);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = json_parse(text, 0, &v, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse JSON: %m");
+
+ json_variant_dump(v, arg_json_format_flags, NULL, NULL);
+ return 0;
+ }
if (arg_pretty || arg_static || arg_transient) {
const char *attr;
@@ -489,6 +523,8 @@ static int help(void) {
" --transient Only set transient hostname\n"
" --static Only set static hostname\n"
" --pretty Only set pretty hostname\n"
+ " --json=pretty|short|off\n"
+ " Generate JSON output\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -509,7 +545,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_ASK_PASSWORD,
ARG_TRANSIENT,
ARG_STATIC,
- ARG_PRETTY
+ ARG_PRETTY,
+ ARG_JSON,
};
static const struct option options[] = {
@@ -521,10 +558,11 @@ static int parse_argv(int argc, char *argv[]) {
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+ { "json", required_argument, NULL, ARG_JSON },
{}
};
- int c;
+ int c, r;
assert(argc >= 0);
assert(argv);
@@ -565,6 +603,13 @@ static int parse_argv(int argc, char *argv[]) {
arg_ask_password = false;
break;
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index 2ece8b2fbd..bd535ddc4d 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -20,6 +20,7 @@
#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
+#include "json.h"
#include "main-func.h"
#include "missing_capability.h"
#include "nscd-flush.h"
@@ -40,6 +41,8 @@
#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
+/* Properties we cache are indexed by an enum, to make invalidation easy and systematic (as we can iterate
+ * through them all, and they are uniformly strings). */
enum {
/* Read from /etc/hostname */
PROP_STATIC_HOSTNAME,
@@ -51,9 +54,6 @@ enum {
PROP_DEPLOYMENT,
PROP_LOCATION,
- PROP_HARDWARE_VENDOR,
- PROP_HARDWARE_MODEL,
-
/* Read from /etc/os-release (or /usr/lib/os-release) */
PROP_OS_PRETTY_NAME,
PROP_OS_CPE_NAME,
@@ -449,6 +449,41 @@ static int context_write_data_machine_info(Context *c) {
return 0;
}
+static int get_dmi_data(const char *database_key, const char *regular_key, char **ret) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _cleanup_free_ char *b = NULL;
+ const char *s = NULL;
+ int r;
+
+ r = sd_device_new_from_syspath(&device, "/sys/class/dmi/id");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to open /sys/class/dmi/id device, ignoring: %m");
+
+ if (database_key)
+ (void) sd_device_get_property_value(device, database_key, &s);
+ if (!s && regular_key)
+ (void) sd_device_get_property_value(device, regular_key, &s);
+
+ if (s) {
+ b = strdup(s);
+ if (!b)
+ return -ENOMEM;
+ }
+
+ if (ret)
+ *ret = TAKE_PTR(b);
+
+ return !!s;
+}
+
+static int get_hardware_vendor(char **ret) {
+ return get_dmi_data("ID_VENDOR_FROM_DATABASE", "ID_VENDOR", ret);
+}
+
+static int get_hardware_model(char **ret) {
+ return get_dmi_data("ID_MODEL_FROM_DATABASE", "ID_MODEL", ret);
+}
+
static int property_get_hardware_vendor(
sd_bus *bus,
const char *path,
@@ -457,20 +492,11 @@ static int property_get_hardware_vendor(
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
- _cleanup_(sd_device_unrefp) sd_device *device = NULL;
- const char *hardware_vendor = NULL;
- int r;
- r = sd_device_new_from_syspath(&device, "/sys/class/dmi/id");
- if (r < 0) {
- log_warning_errno(r, "Failed to open /sys/class/dmi/id device, ignoring: %m");
- return sd_bus_message_append(reply, "s", NULL);
- }
+ _cleanup_free_ char *vendor = NULL;
- if (sd_device_get_property_value(device, "ID_VENDOR_FROM_DATABASE", &hardware_vendor) < 0)
- (void) sd_device_get_property_value(device, "ID_VENDOR", &hardware_vendor);
-
- return sd_bus_message_append(reply, "s", hardware_vendor);
+ (void) get_hardware_vendor(&vendor);
+ return sd_bus_message_append(reply, "s", vendor);
}
static int property_get_hardware_model(
@@ -481,20 +507,11 @@ static int property_get_hardware_model(
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
- _cleanup_(sd_device_unrefp) sd_device *device = NULL;
- const char *hardware_model = NULL;
- int r;
- r = sd_device_new_from_syspath(&device, "/sys/class/dmi/id");
- if (r < 0) {
- log_warning_errno(r, "Failed to open /sys/class/dmi/id device, ignoring: %m");
- return sd_bus_message_append(reply, "s", NULL);
- }
+ _cleanup_free_ char *model = NULL;
- if (sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &hardware_model) < 0)
- (void) sd_device_get_property_value(device, "ID_MODEL", &hardware_model);
-
- return sd_bus_message_append(reply, "s", hardware_model);
+ (void) get_hardware_model(&model);
+ return sd_bus_message_append(reply, "s", model);
}
static int property_get_hostname(
@@ -548,13 +565,46 @@ static int property_get_default_hostname(
void *userdata,
sd_bus_error *error) {
- _cleanup_free_ char *hn = get_default_hostname();
+ _cleanup_free_ char *hn = NULL;
+
+ hn = get_default_hostname();
if (!hn)
return log_oom();
return sd_bus_message_append(reply, "s", hn);
}
+static void context_determine_hostname_source(Context *c) {
+ char hostname[HOST_NAME_MAX + 1] = {};
+ _cleanup_free_ char *fallback = NULL;
+ int r;
+
+ assert(c);
+
+ if (c->hostname_source >= 0)
+ return;
+
+ (void) get_hostname_filtered(hostname);
+
+ if (streq_ptr(hostname, c->data[PROP_STATIC_HOSTNAME]))
+ c->hostname_source = HOSTNAME_STATIC;
+ else {
+ /* If the hostname was not set by us, try to figure out where it came from. If we set it to
+ * the default hostname, the file will tell us. We compare the string because it is possible
+ * that the hostname was set by an older version that had a different fallback, in the
+ * initramfs or before we reexecuted. */
+
+ r = read_one_line_file("/run/systemd/default-hostname", &fallback);
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read /run/systemd/default-hostname, ignoring: %m");
+
+ if (streq_ptr(fallback, hostname))
+ c->hostname_source = HOSTNAME_DEFAULT;
+ else
+ c->hostname_source = HOSTNAME_TRANSIENT;
+ }
+}
+
static int property_get_hostname_source(
sd_bus *bus,
const char *path,
@@ -565,36 +615,10 @@ static int property_get_hostname_source(
sd_bus_error *error) {
Context *c = userdata;
- int r;
assert(c);
context_read_etc_hostname(c);
-
- if (c->hostname_source < 0) {
- char hostname[HOST_NAME_MAX + 1] = {};
- _cleanup_free_ char *fallback = NULL;
-
- (void) get_hostname_filtered(hostname);
-
- if (streq_ptr(hostname, c->data[PROP_STATIC_HOSTNAME]))
- c->hostname_source = HOSTNAME_STATIC;
-
- else {
- /* If the hostname was not set by us, try to figure out where it came from. If we set
- * it to the default hostname, the file will tell us. We compare the string because
- * it is possible that the hostname was set by an older version that had a different
- * fallback, in the initramfs or before we reexecuted. */
-
- r = read_one_line_file("/run/systemd/default-hostname", &fallback);
- if (r < 0 && r != -ENOENT)
- log_warning_errno(r, "Failed to read /run/systemd/default-hostname, ignoring: %m");
-
- if (streq_ptr(fallback, hostname))
- c->hostname_source = HOSTNAME_DEFAULT;
- else
- c->hostname_source = HOSTNAME_TRANSIENT;
- }
- }
+ context_determine_hostname_source(c);
return sd_bus_message_append(reply, "s", hostname_source_to_string(c->hostname_source));
}
@@ -932,28 +956,12 @@ static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error *
static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Context *c = userdata;
- bool has_uuid = false;
int interactive, r;
sd_id128_t uuid;
assert(m);
assert(c);
- r = id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, &uuid);
- if (r == -ENOENT)
- r = id128_read("/sys/firmware/devicetree/base/vm,uuid", ID128_UUID, &uuid);
- if (r < 0)
- log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to read product UUID, ignoring: %m");
- else if (sd_id128_is_null(uuid) || sd_id128_is_allf(uuid))
- log_debug("DMI product UUID " SD_ID128_FORMAT_STR " is all 0x00 or all 0xFF, ignoring.", SD_ID128_FORMAT_VAL(uuid));
- else
- has_uuid = true;
-
- if (!has_uuid)
- return sd_bus_error_set(error, BUS_ERROR_NO_PRODUCT_UUID,
- "Failed to read product UUID from firmware.");
-
r = sd_bus_message_read(m, "b", &interactive);
if (r < 0)
return r;
@@ -972,11 +980,125 @@ static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_err
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ r = id128_get_product(&uuid);
+ if (r < 0) {
+ if (r == -EADDRNOTAVAIL)
+ log_debug_errno(r, "DMI product UUID is all 0x00 or all 0xFF, ignoring.");
+ else
+ log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
+ "Failed to read product UUID, ignoring: %m");
+
+ return sd_bus_error_set(error, BUS_ERROR_NO_PRODUCT_UUID,
+ "Failed to read product UUID from firmware.");
+ }
+
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
- r = sd_bus_message_append_array(reply, 'y', &uuid, sizeof(uuid));
+ r = sd_bus_message_append_array(reply, 'y', uuid.bytes, sizeof(uuid.bytes));
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL, *vendor = NULL, *model = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ sd_id128_t product_uuid = SD_ID128_NULL;
+ const char *chassis = NULL;
+ Context *c = userdata;
+ bool privileged;
+ struct utsname u;
+ int r;
+
+ assert(m);
+ assert(c);
+
+ r = bus_verify_polkit_async(
+ m,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.hostname1.get-product-uuid",
+ NULL,
+ false,
+ UID_INVALID,
+ &c->polkit_registry,
+ NULL);
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ /* We ignore all authentication errors here, since most data is unprivileged, the one exception being
+ * the product ID which we'll check explicitly. */
+ privileged = r > 0;
+
+ context_read_etc_hostname(c);
+ context_read_machine_info(c);
+ context_read_os_release(c);
+ context_determine_hostname_source(c);
+
+ r = gethostname_strict(&hn);
+ if (r < 0) {
+ if (r != -ENXIO)
+ return log_error_errno(r, "Failed to read local host name: %m");
+
+ hn = get_default_hostname();
+ if (!hn)
+ return log_oom();
+ }
+
+ dhn = get_default_hostname();
+ if (!dhn)
+ return log_oom();
+
+ if (isempty(c->data[PROP_ICON_NAME]))
+ in = context_fallback_icon_name(c);
+
+ if (isempty(c->data[PROP_CHASSIS]))
+ chassis = fallback_chassis();
+
+ assert_se(uname(&u) >= 0);
+
+ (void) get_hardware_vendor(&vendor);
+ (void) get_hardware_model(&model);
+
+ if (privileged) /* The product UUID is only available to privileged clients */
+ id128_get_product(&product_uuid);
+
+ r = json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("Hostname", JSON_BUILD_STRING(hn)),
+ JSON_BUILD_PAIR("StaticHostname", JSON_BUILD_STRING(c->data[PROP_STATIC_HOSTNAME])),
+ JSON_BUILD_PAIR("PrettyHostname", JSON_BUILD_STRING(c->data[PROP_PRETTY_HOSTNAME])),
+ JSON_BUILD_PAIR("DefaultHostname", JSON_BUILD_STRING(dhn)),
+ JSON_BUILD_PAIR("HostnameSource", JSON_BUILD_STRING(hostname_source_to_string(c->hostname_source))),
+ JSON_BUILD_PAIR("IconName", JSON_BUILD_STRING(in ?: c->data[PROP_ICON_NAME])),
+ JSON_BUILD_PAIR("Chassis", JSON_BUILD_STRING(chassis ?: c->data[PROP_CHASSIS])),
+ JSON_BUILD_PAIR("Deployment", JSON_BUILD_STRING(c->data[PROP_DEPLOYMENT])),
+ JSON_BUILD_PAIR("Location", JSON_BUILD_STRING(c->data[PROP_LOCATION])),
+ JSON_BUILD_PAIR("KernelName", JSON_BUILD_STRING(u.sysname)),
+ JSON_BUILD_PAIR("KernelRelease", JSON_BUILD_STRING(u.release)),
+ JSON_BUILD_PAIR("KernelVersion", JSON_BUILD_STRING(u.version)),
+ JSON_BUILD_PAIR("OperatingSystemPrettyName", JSON_BUILD_STRING(c->data[PROP_OS_PRETTY_NAME])),
+ JSON_BUILD_PAIR("OperatingSystemCPEName", JSON_BUILD_STRING(c->data[PROP_OS_CPE_NAME])),
+ JSON_BUILD_PAIR("OperatingSystemHomeURL", JSON_BUILD_STRING(c->data[PROP_OS_HOME_URL])),
+ JSON_BUILD_PAIR("HardwareVendor", JSON_BUILD_STRING(vendor)),
+ JSON_BUILD_PAIR("HardwareModel", JSON_BUILD_STRING(model)),
+ JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)),
+ JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL)));
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON data: %m");
+
+ r = json_variant_format(v, 0, &text);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format JSON data: %m");
+
+ r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", text);
if (r < 0)
return r;
@@ -1059,6 +1181,11 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_PARAM(uuid),
method_get_product_uuid,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Describe",
+ SD_BUS_NO_ARGS,
+ SD_BUS_RESULT("s", json),
+ method_describe,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c
index a3f6da6381..2074771a41 100644
--- a/src/libsystemd/sd-id128/id128-util.c
+++ b/src/libsystemd/sd-id128/id128-util.c
@@ -210,3 +210,25 @@ sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
}
DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
+
+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 = id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, &uuid);
+ if (r == -ENOENT)
+ r = id128_read("/sys/firmware/devicetree/base/vm,uuid", ID128_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;
+}
diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h
index 6b09bcd96a..053ef0a6a8 100644
--- a/src/libsystemd/sd-id128/id128-util.h
+++ b/src/libsystemd/sd-id128/id128-util.h
@@ -36,3 +36,5 @@ int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
extern const struct hash_ops id128_hash_ops;
sd_id128_t id128_make_v4_uuid(sd_id128_t id);
+
+int id128_get_product(sd_id128_t *ret);
diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c
index 6d15f9cd09..dd4c74b19c 100644
--- a/src/shared/machine-id-setup.c
+++ b/src/shared/machine-id-setup.c
@@ -62,16 +62,10 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) {
} else if (detect_vm() == VIRTUALIZATION_KVM) {
- /* If we are not running in a container, see if we are
- * running in qemu/kvm and a machine ID was passed in
- * via -uuid on the qemu/kvm command line */
+ /* If we are not running in a container, see if we are running in qemu/kvm and a
+ * machine ID was passed in via -uuid on the qemu/kvm command line */
- if (id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, ret) >= 0) {
- log_info("Initializing machine ID from KVM UUID.");
- return 0;
- }
- /* on POWER, it's exported here instead */
- if (id128_read("/sys/firmware/devicetree/base/vm,uuid", ID128_UUID, ret) >= 0) {
+ if (id128_get_product(ret) >= 0) {
log_info("Initializing machine ID from KVM UUID.");
return 0;
}