hostname: add API for getting custom fields from machine-info

This commit is contained in:
seaeunlee
2026-03-18 20:47:00 +00:00
committed by Luca Boccassi
parent 4bc110154d
commit 59aed18d69
5 changed files with 146 additions and 0 deletions
+12
View File
@@ -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 {
<variablelist class="dbus-method" generated="True" extra-ref="Describe()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetMachineInfo()"/>
<variablelist class="dbus-property" generated="True" extra-ref="Hostname"/>
<variablelist class="dbus-property" generated="True" extra-ref="StaticHostname"/>
@@ -377,6 +381,13 @@ node /org/freedesktop/hostname1 {
access to unprivileged clients through the polkit framework.</para>
<para><function>Describe()</function> returns a JSON representation of all properties in one.</para>
<para><function>GetMachineInfo()</function> returns the value of the given field from
<filename>/etc/machine-info</filename>. 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
<filename>/etc/machine-info</filename> 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.</para>
</refsect2>
<refsect2>
@@ -494,6 +505,7 @@ node /org/freedesktop/hostname1 {
<varname>OperatingSystemImageVersion</varname>, <varname>HardwareSKU</varname>, and
<varname>HardwareVersion</varname> were added in version 258.</para>
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
<para><function>GetMachineInfo()</function> was added in version 261.</para>
</refsect2>
</refsect1>
+64
View File
@@ -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,
};
@@ -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),
@@ -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"
+68
View File
@@ -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 <<EOF
PRETTY_HOSTNAME="Pretty Test"
ICON_NAME=computer-laptop
CHASSIS=laptop
DEPLOYMENT=production
LOCATION="Server Room 42"
HARDWARE_VENDOR="Test Vendor"
HARDWARE_MODEL="Test Model"
HARDWARE_SKU="SKU-001"
HARDWARE_VERSION="v1.0"
TEST_CUSTOM_FIELD_1=still-here
EOF
# Custom field should still work alongside standard fields
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s TEST_CUSTOM_FIELD_1 | cut -d'"' -f2)" "still-here"
# Should fail for a field that doesn't exist
assert_rc 1 busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s NONEXISTENT_FIELD
# Should fail for an empty field name
assert_rc 1 busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s ""
# Should fail for an invalid field name
assert_rc 1 busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s "invalid-name!"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s PRETTY_HOSTNAME | cut -d'"' -f2)" "Pretty Test"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s ICON_NAME | cut -d'"' -f2)" "computer-laptop"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s CHASSIS | cut -d'"' -f2)" "laptop"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s DEPLOYMENT | cut -d'"' -f2)" "production"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s LOCATION | cut -d'"' -f2)" "Server Room 42"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_VENDOR | cut -d'"' -f2)" "Test Vendor"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_MODEL | cut -d'"' -f2)" "Test Model"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_SKU | cut -d'"' -f2)" "SKU-001"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_VERSION | cut -d'"' -f2)" "v1.0"
# Verify cache consistency for all standard fields against D-Bus properties
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s PRETTY_HOSTNAME | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 PrettyHostname | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s ICON_NAME | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 IconName | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s CHASSIS | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 Chassis | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s DEPLOYMENT | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 Deployment | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s LOCATION | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 Location | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_VENDOR | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 HardwareVendor | cut -d'"' -f2)"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s HARDWARE_MODEL | cut -d'"' -f2)" \
"$(busctl get-property org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 HardwareModel | cut -d'"' -f2)"
# Test file modification is reflected (cache invalidation via stat)
cat >/etc/machine-info <<EOF
PRETTY_HOSTNAME="Updated Host"
TEST_CUSTOM_FIELD_1=updated-value
EOF
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s PRETTY_HOSTNAME | cut -d'"' -f2)" "Updated Host"
assert_eq "$(busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s TEST_CUSTOM_FIELD_1 | cut -d'"' -f2)" "updated-value"
# Fields removed from the file should now fail
assert_rc 1 busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s CHASSIS
}
run_testcases
touch /testok