diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml
index c70258459c..3d98b88ebc 100644
--- a/man/org.freedesktop.hostname1.xml
+++ b/man/org.freedesktop.hostname1.xml
@@ -60,6 +60,8 @@ node /org/freedesktop/hostname1 {
out ay uuid);
GetHardwareSerial(out s serial);
Describe(out s json);
+ GetMachineInfo(in s field,
+ out s value);
properties:
readonly s Hostname = '...';
readonly s StaticHostname = '...';
@@ -146,6 +148,8 @@ node /org/freedesktop/hostname1 {
+
+
@@ -377,6 +381,13 @@ node /org/freedesktop/hostname1 {
access to unprivileged clients through the polkit framework.
Describe() returns a JSON representation of all properties in one.
+
+ GetMachineInfo() returns the value of the given field from
+ /etc/machine-info. For well-known fields, this method reads values from the same
+ internal cache used by the corresponding D-Bus property getters, but returns only the raw
+ /etc/machine-info values (i.e. without property-level fallback logic such as
+ DMI/chassis-based detection). Custom (non-standard) fields are read directly from the file.
+ An error is returned if the field name is empty, invalid, or not set in the file.
@@ -494,6 +505,7 @@ node /org/freedesktop/hostname1 {
OperatingSystemImageVersion, HardwareSKU, and
HardwareVersion were added in version 258.
OperatingSystemFancyName was added in version 260.
+ GetMachineInfo() was added in version 261.
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index 04c72e8137..ce71871618 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -1652,6 +1652,65 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_
return sd_bus_reply_method_return(m, "s", serial);
}
+static int method_get_machine_info(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ static const struct {
+ const char *name;
+ HostProperty prop;
+ } field_table[] = {
+ { "PRETTY_HOSTNAME", PROP_PRETTY_HOSTNAME },
+ { "ICON_NAME", PROP_ICON_NAME },
+ { "CHASSIS", PROP_CHASSIS },
+ { "DEPLOYMENT", PROP_DEPLOYMENT },
+ { "LOCATION", PROP_LOCATION },
+ { "HARDWARE_VENDOR", PROP_HARDWARE_VENDOR },
+ { "HARDWARE_MODEL", PROP_HARDWARE_MODEL },
+ { "HARDWARE_SKU", PROP_HARDWARE_SKU },
+ { "HARDWARE_VERSION", PROP_HARDWARE_VERSION },
+ };
+
+ Context *c = ASSERT_PTR(userdata);
+ const char *field;
+ int r;
+
+ assert(m);
+
+ r = sd_bus_message_read(m, "s", &field);
+ if (r < 0)
+ return r;
+
+ if (isempty(field))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Field name must not be empty.");
+
+ if (!env_name_is_valid(field))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid field name '%s'.", field);
+
+ FOREACH_ELEMENT(e, field_table)
+ if (streq(field, e->name)) {
+ /* For fields that are also exposed as D-Bus properties, use the same Context cache as the
+ * property getters. Note that this returns the raw /etc/machine-info value only: property-level
+ * fallback logic (e.g. DMI/chassis-based synthesis) is not applied here. For custom/unknown
+ * fields, fall back to reading the file directly. */
+ context_read_machine_info(c);
+
+ if (isempty(c->data[e->prop]))
+ return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field);
+
+ return sd_bus_reply_method_return(m, "s", c->data[e->prop]);
+ }
+
+ _cleanup_free_ char *value = NULL;
+
+ r = parse_env_file(NULL, etc_machine_info(),
+ field, &value);
+ if (r < 0 && r != -ENOENT)
+ return sd_bus_error_set_errnof(error, r, "Failed to read /etc/machine-info: %m");
+
+ if (isempty(value))
+ return sd_bus_error_setf(error, BUS_ERROR_FIELD_NOT_SET, "Field '%s' is not set or empty in /etc/machine-info.", field);
+
+ return sd_bus_reply_method_return(m, "s", value);
+}
+
static int build_describe_response(Context *c, bool privileged, sd_json_variant **ret) {
_cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL,
*chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL,
@@ -1883,6 +1942,11 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_RESULT("s", json),
method_describe,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("GetMachineInfo",
+ SD_BUS_ARGS("s", field),
+ SD_BUS_RESULT("s", value),
+ method_get_machine_info,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
index f0fc09f8a9..6c2b0fd881 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.c
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -111,6 +111,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_HARDWARE_SERIAL, EOPNOTSUPP),
+ SD_BUS_ERROR_MAP(BUS_ERROR_FIELD_NOT_SET, ENODATA),
SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES),
SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS),
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
index 36676e83db..b4788433bf 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.h
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -112,6 +112,7 @@
#define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID"
#define BUS_ERROR_NO_HARDWARE_SERIAL "org.freedesktop.hostname1.NoHardwareSerial"
+#define BUS_ERROR_FIELD_NOT_SET "org.freedesktop.hostname1.FieldNotSet"
#define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected"
#define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem"
diff --git a/test/units/TEST-71-HOSTNAME.sh b/test/units/TEST-71-HOSTNAME.sh
index ffb110be68..9bd743dcaa 100755
--- a/test/units/TEST-71-HOSTNAME.sh
+++ b/test/units/TEST-71-HOSTNAME.sh
@@ -320,6 +320,74 @@ EOF
assert_in "CHASSIS=watch" "$(cat /run/alternate-path/mymachine-info)"
}
+testcase_get_machine_info() {
+ if [[ -f /etc/machine-info ]]; then
+ cp /etc/machine-info /tmp/machine-info.bak
+ fi
+
+ trap restore_machine_info RETURN
+
+ # Test all standard cached fields
+ cat >/etc/machine-info </etc/machine-info <