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 = '...';
<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"/>
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>
<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>
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,
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,
};
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),
#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"
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