Merge pull request #30833 from poettering/hostnamed-varlink

hostnamed: add simple varlink interface
This commit is contained in:
Lennart Poettering 2024-01-09 12:38:58 +01:00 committed by GitHub
commit bed41c4084
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 246 additions and 50 deletions

View file

@ -38,6 +38,7 @@
#include "string-table.h"
#include "strv.h"
#include "user-util.h"
#include "varlink-io.systemd.Hostname.h"
#include "virt.h"
#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
@ -75,6 +76,9 @@ typedef struct Context {
struct stat etc_os_release_stat;
struct stat etc_machine_info_stat;
sd_event *event;
sd_bus *bus;
VarlinkServer *varlink_server;
Hashmap *polkit_registry;
} Context;
@ -94,6 +98,9 @@ static void context_destroy(Context *c) {
context_reset(c, UINT64_MAX);
hashmap_free(c->polkit_registry);
sd_event_unref(c->event);
sd_bus_flush_close_unref(c->bus);
varlink_server_unref(c->varlink_server);
}
static void context_read_etc_hostname(Context *c) {
@ -1343,34 +1350,19 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_
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,
static int build_describe_response(Context *c, bool privileged, JsonVariant **ret) {
_cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL,
*chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL,
*firmware_vendor = NULL;
usec_t firmware_date = USEC_INFINITY, eol = USEC_INFINITY;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
sd_id128_t machine_id, boot_id, product_uuid = SD_ID128_NULL;
unsigned local_cid = VMADDR_CID_ANY;
Context *c = ASSERT_PTR(userdata);
bool privileged;
struct utsname u;
int r;
assert(m);
r = bus_verify_polkit_async(
m,
"org.freedesktop.hostname1.get-description",
/* details= */ NULL,
&c->polkit_registry,
error);
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;
assert(c);
assert(ret);
context_read_etc_hostname(c);
context_read_machine_info(c);
@ -1452,11 +1444,42 @@ static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *erro
JSON_BUILD_PAIR_ID128("BootID", boot_id),
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),
JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid))));
JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)),
JSON_BUILD_PAIR_CONDITION(local_cid == VMADDR_CID_ANY, "VSockCID", JSON_BUILD_NULL)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON data: %m");
*ret = TAKE_PTR(v);
return 0;
}
static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
Context *c = ASSERT_PTR(userdata);
_cleanup_free_ char *text = NULL;
bool privileged;
int r;
assert(m);
r = bus_verify_polkit_async(
m,
"org.freedesktop.hostname1.get-description",
/* details= */ NULL,
&c->polkit_registry,
error);
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;
r = build_describe_response(c, privileged, &v);
if (r < 0)
return r;
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_error_errno(r, "Failed to format JSON data: %m");
@ -1559,35 +1582,114 @@ static const BusObjectImplementation manager_object = {
.vtables = BUS_VTABLES(hostname_vtable),
};
static int connect_bus(Context *c, sd_event *event, sd_bus **ret) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
static int connect_bus(Context *c) {
int r;
assert(c);
assert(event);
assert(ret);
assert(c->event);
assert(!c->bus);
r = sd_bus_default_system(&bus);
r = sd_bus_default_system(&c->bus);
if (r < 0)
return log_error_errno(r, "Failed to get system bus connection: %m");
r = bus_add_implementation(bus, &manager_object, c);
r = bus_add_implementation(c->bus, &manager_object, c);
if (r < 0)
return r;
r = bus_log_control_api_register(bus);
r = bus_log_control_api_register(c->bus);
if (r < 0)
return r;
r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
r = sd_bus_request_name_async(c->bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
r = sd_bus_attach_event(bus, event, 0);
r = sd_bus_attach_event(c->bus, c->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
*ret = TAKE_PTR(bus);
return 0;
}
static int vl_method_describe(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
static const JsonDispatch dispatch_table[] = {
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
Context *c = ASSERT_PTR(userdata);
bool privileged;
int r;
assert(link);
assert(parameters);
r = varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL);
if (r != 0)
return r;
r = varlink_verify_polkit_async(
link,
c->bus,
"org.freedesktop.hostname1.get-hardware-serial",
/* details= */ NULL,
/* good_user= */ UID_INVALID,
&c->polkit_registry);
if (r == 0)
return 0; /* 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;
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = build_describe_response(c, privileged, &v);
if (r < 0)
return r;
return varlink_reply(link, v);
}
static int connect_varlink(Context *c) {
int r;
assert(c);
assert(c->event);
assert(!c->varlink_server);
r = varlink_server_new(&c->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
varlink_server_set_userdata(c->varlink_server, c);
r = varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname);
if (r < 0)
return log_error_errno(r, "Failed to add Hostname interface to varlink server: %m");
r = varlink_server_bind_method_many(
c->varlink_server,
"io.systemd.Hostname.Describe", vl_method_describe);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink method calls: %m");
r = varlink_server_attach_event(c->varlink_server, c->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach Varlink server to event loop: %m");
r = varlink_server_listen_auto(c->varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m");
if (r == 0) {
r = varlink_server_listen_address(c->varlink_server, "/run/systemd/io.systemd.Hostname", 0666);
if (r < 0)
return log_error_errno(r, "Failed to bind to Varlink socket: %m");
}
return 0;
}
@ -1595,8 +1697,6 @@ static int run(int argc, char *argv[]) {
_cleanup_(context_destroy) Context context = {
.hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */
};
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
log_setup();
@ -1615,27 +1715,31 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
r = sd_event_default(&event);
r = sd_event_default(&context.event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
(void) sd_event_set_watchdog(event, true);
(void) sd_event_set_watchdog(context.event, true);
r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
r = sd_event_set_signal_exit(context.event, true);
if (r < 0)
return log_error_errno(r, "Failed to install SIGINT handler: %m");
return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to install SIGTERM handler: %m");
r = connect_bus(&context, event, &bus);
r = connect_bus(&context);
if (r < 0)
return r;
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
r = connect_varlink(&context);
if (r < 0)
return r;
r = bus_event_loop_with_idle(
context.event,
context.bus,
"org.freedesktop.hostname1",
DEFAULT_EXIT_USEC,
/* check_idle= */ NULL,
/* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");

View file

@ -13,6 +13,7 @@
#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;
@ -223,6 +224,13 @@ int id128_get_product(sd_id128_t *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);

View file

@ -174,6 +174,7 @@ shared_sources = files(
'varlink-idl.c',
'varlink-io.systemd.c',
'varlink-io.systemd.Credentials.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.Network.c',

View file

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Credentials.h"
static VARLINK_DEFINE_METHOD(
Describe,
VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE));
VARLINK_DEFINE_INTERFACE(
io_systemd_Hostname,
"io.systemd.Hostname",
&vl_method_Describe);

View file

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_Hostname;

View file

@ -61,6 +61,11 @@ get_chassis() (
echo "$CHASSIS"
)
stop_hostnamed() {
systemctl stop systemd-hostnamed.service
systemctl reset-failed systemd-hostnamed # reset trigger limit
}
testcase_chassis() {
local i
@ -80,7 +85,7 @@ testcase_chassis() {
assert_eq "$(get_chassis)" "$i"
done
systemctl stop systemd-hostnamed.service
stop_hostnamed
rm -f /etc/machine-info
# fallback chassis type
@ -95,7 +100,7 @@ restore_sysfs_dmi() {
umount /sys/class/dmi/id
rm -rf /run/systemd/system/systemd-hostnamed.service.d
systemctl daemon-reload
systemctl stop systemd-hostnamed
stop_hostnamed
}
testcase_firmware_date() {
@ -120,15 +125,15 @@ EOF
echo '1' >/sys/class/dmi/id/uevent
echo '09/08/2000' >/sys/class/dmi/id/bios_date
systemctl stop systemd-hostnamed
stop_hostnamed
assert_in '2000-09-08' "$(hostnamectl)"
echo '2022' >/sys/class/dmi/id/bios_date
systemctl stop systemd-hostnamed
stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
echo 'garbage' >/sys/class/dmi/id/bios_date
systemctl stop systemd-hostnamed
stop_hostnamed
assert_not_in 'Firmware Date' "$(hostnamectl)"
}
@ -223,6 +228,14 @@ testcase_nss-myhostname() {
(! getent hosts -s myhostname fd00:dead:beef:cafe::1)
}
test_varlink() {
A="$(mktemp -u)"
B="$(mktemp -u)"
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' --json=short > "$A"
hostnamectl --json=short > "$B"
cmp "$A" "$B"
}
run_testcases
touch /testok

View file

@ -113,3 +113,7 @@ done
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord </dev/null)
(! varlinkctl validate-idl "")
(! varlinkctl validate-idl </dev/null)
varlinkctl info /run/systemd/io.systemd.Hostname
varlinkctl introspect /run/systemd/io.systemd.Hostname io.systemd.Hostname
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}'

View file

@ -321,6 +321,11 @@ units = [
'conditions' : ['ENABLE_HOSTNAMED'],
'symlinks' : ['dbus-org.freedesktop.hostname1.service'],
},
{
'file' : 'systemd-hostnamed.socket',
'conditions' : ['ENABLE_HOSTNAMED'],
'symlinks' : ['sockets.target.wants/'],
},
{
'file' : 'systemd-hwdb-update.service.in',
'conditions' : ['ENABLE_HWDB'],

View file

@ -0,0 +1,19 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Hostname Service Varlink Socket
Documentation=man:systemd-hostnamed.service(8)
Documentation=man:hostname(5)
Documentation=man:machine-info(5)
[Socket]
ListenStream=/run/systemd/io.systemd.Hostname
FileDescriptorName=varlink
SocketMode=0666