From 8fcf504b8c383930847140efd54f3795c626e15e Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Tue, 23 Apr 2024 15:03:34 +0100 Subject: [PATCH 1/5] shared/json: add json_dispatch_absolute_path --- src/shared/json.c | 22 ++++++++++++++++++++++ src/shared/json.h | 1 + 2 files changed, 23 insertions(+) diff --git a/src/shared/json.c b/src/shared/json.c index edfae5b47b..e1ee98ef2d 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -23,6 +23,7 @@ #include "math-util.h" #include "memory-util.h" #include "memstream-util.h" +#include "path-util.h" #include "set.h" #include "string-table.h" #include "string-util.h" @@ -5006,6 +5007,27 @@ int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDi return 0; } +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + const char *path; + char **p = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + path = json_variant_string(variant); + if (!path_is_valid(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid path.", strna(name)); + if (!path_is_absolute(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' must be an absolute path.", strna(name)); + + if (free_and_strdup(p, path) < 0) + return json_log_oom(variant, flags); + + return 0; +} + int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { sd_id128_t *uuid = userdata; int r; diff --git a/src/shared/json.h b/src/shared/json.h index ec6274ad8d..6a93530a3a 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -425,6 +425,7 @@ int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); From 71a15f37af762c374062eefde2d2280909b180d0 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Mon, 22 Apr 2024 16:23:13 +0100 Subject: [PATCH 2/5] machined: expose machine_freep in machine.h --- src/machine/machine.c | 2 -- src/machine/machine.h | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/machine.c b/src/machine/machine.c index 6e1da3744a..d1ebd910b3 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -36,8 +36,6 @@ #include "unit-name.h" #include "user-util.h" -DEFINE_TRIVIAL_CLEANUP_FUNC(Machine*, machine_free); - int machine_new(Manager *manager, MachineClass class, const char *name, Machine **ret) { _cleanup_(machine_freep) Machine *m = NULL; int r; diff --git a/src/machine/machine.h b/src/machine/machine.h index 30ef93b36e..606f117d86 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -78,6 +78,8 @@ int machine_save(Machine *m); int machine_load(Machine *m); int machine_kill(Machine *m, KillWho who, int signo); +DEFINE_TRIVIAL_CLEANUP_FUNC(Machine*, machine_free); + void machine_release_unit(Machine *m); MachineState machine_get_state(Machine *u); From 885317f1aba9754856c3047277413004622c79a4 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 24 Apr 2024 09:57:18 +0100 Subject: [PATCH 3/5] machined: split manager linking out of machine_new into machine_link --- src/machine/machine.c | 51 ++++++++++++++++++++++++------------- src/machine/machine.h | 3 ++- src/machine/machined-dbus.c | 6 ++++- src/machine/machined.c | 6 ++++- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/machine/machine.c b/src/machine/machine.c index d1ebd910b3..2b71ae9966 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -30,19 +30,17 @@ #include "special.h" #include "stdio-util.h" #include "string-table.h" +#include "string-util.h" #include "terminal-util.h" #include "tmpfile-util.h" #include "uid-range.h" #include "unit-name.h" #include "user-util.h" -int machine_new(Manager *manager, MachineClass class, const char *name, Machine **ret) { +int machine_new(MachineClass class, const char *name, Machine **ret) { _cleanup_(machine_freep) Machine *m = NULL; - int r; - assert(manager); assert(class < _MACHINE_CLASS_MAX); - assert(name); assert(ret); /* Passing class == _MACHINE_CLASS_INVALID here is fine. It @@ -57,25 +55,43 @@ int machine_new(Manager *manager, MachineClass class, const char *name, Machine .leader = PIDREF_NULL, }; - m->name = strdup(name); - if (!m->name) - return -ENOMEM; - - if (class != MACHINE_HOST) { - m->state_file = path_join("/run/systemd/machines", m->name); - if (!m->state_file) + if (name) { + m->name = strdup(name); + if (!m->name) return -ENOMEM; } m->class = class; - r = hashmap_put(manager->machines, m->name, m); + *ret = TAKE_PTR(m); + return 0; +} + +int machine_link(Manager *manager, Machine *machine) { + int r; + + assert(manager); + assert(machine); + + if (machine->manager) + return -EEXIST; + if (!machine->name) + return -EINVAL; + + if (machine->class != MACHINE_HOST) { + char *temp = path_join("/run/systemd/machines", machine->name); + if (!temp) + return -ENOMEM; + + free_and_replace(machine->state_file, temp); + } + + r = hashmap_put(manager->machines, machine->name, machine); if (r < 0) return r; - m->manager = manager; + machine->manager = manager; - *ret = TAKE_PTR(m); return 0; } @@ -91,11 +107,9 @@ Machine* machine_free(Machine *m) { LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); } - machine_release_unit(m); - - free(m->scope_job); - if (m->manager) { + machine_release_unit(m); + (void) hashmap_remove(m->manager->machines, m->name); if (m->manager->host_machine == m) @@ -111,6 +125,7 @@ Machine* machine_free(Machine *m) { sd_bus_message_unref(m->create_message); free(m->name); + free(m->scope_job); free(m->state_file); free(m->service); free(m->root_directory); diff --git a/src/machine/machine.h b/src/machine/machine.h index 606f117d86..24ef15894b 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -67,7 +67,8 @@ struct Machine { LIST_FIELDS(Machine, gc_queue); }; -int machine_new(Manager *manager, MachineClass class, const char *name, Machine **ret); +int machine_new(MachineClass class, const char *name, Machine **ret); +int machine_link(Manager *manager, Machine *machine); Machine* machine_free(Machine *m); bool machine_may_gc(Machine *m, bool drop_not_started); void machine_add_to_gc_queue(Machine *m); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 6b108dc009..e7c7f6fd69 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -1498,9 +1498,13 @@ int manager_add_machine(Manager *m, const char *name, Machine **_machine) { machine = hashmap_get(m->machines, name); if (!machine) { - r = machine_new(m, _MACHINE_CLASS_INVALID, name, &machine); + r = machine_new(_MACHINE_CLASS_INVALID, name, &machine); if (r < 0) return r; + + r = machine_link(m, machine); + if (r < 0) + return 0; } if (_machine) diff --git a/src/machine/machined.c b/src/machine/machined.c index 99301408d3..2638ed572e 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -132,10 +132,14 @@ static int manager_add_host_machine(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open reference to PID 1: %m"); - r = machine_new(m, MACHINE_HOST, ".host", &t); + r = machine_new(MACHINE_HOST, ".host", &t); if (r < 0) return log_error_errno(r, "Failed to create machine: %m"); + r = machine_link(m, t); + if (r < 0) + return log_error_errno(r, "Failed to link machine to manager: %m"); + t->leader = TAKE_PIDREF(pidref); t->id = mid; From 1f815bf1640c7a648d323f9331cd88f19cffc2c9 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 24 Apr 2024 18:08:13 +0100 Subject: [PATCH 4/5] machined: add GetMachineSSHInfo method Also adds three properties: - VsockCid: the VSOCK CID of the VM - SshAddress: the address of the VM in a format SSH can connect to - SshPrivateKeyPath: the path to the SSH private key to use to connect to the VM. GetMachineSSHInfo is essentially a convenience method to query both the SshAddress and SshPrivateKeyPath properties at once. --- man/org.freedesktop.machine1.xml | 43 +++++++++++++++++++++++++++++--- src/machine/machine-dbus.c | 29 +++++++++++++++++++++ src/machine/machine-dbus.h | 1 + src/machine/machine.c | 4 +++ src/machine/machine.h | 4 +++ src/machine/machined-dbus.c | 9 +++++++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/man/org.freedesktop.machine1.xml b/man/org.freedesktop.machine1.xml index cc4e61d2d4..24bef6a755 100644 --- a/man/org.freedesktop.machine1.xml +++ b/man/org.freedesktop.machine1.xml @@ -89,6 +89,9 @@ node /org/freedesktop/machine1 { in i signal); GetMachineAddresses(in s name, out a(iay) addresses); + GetMachineSSHInfo(in s name, + out s ssh_address, + out s ssh_private_key_path); GetMachineOSRelease(in s name, out a{ss} fields); @org.freedesktop.systemd1.Privileged("true") @@ -230,6 +233,8 @@ node /org/freedesktop/machine1 { + + @@ -378,6 +383,10 @@ node /org/freedesktop/machine1 { AF_INET6) and a byte array containing the addresses. This is only supported for containers that make use of network namespacing. + GetMachineSSHInfo() retrieves the SSH information of a machine. This method + returns two strings, the SSH address which can be used to tell SSH where to connect, and the path + to the SSH private key required for the connection to succeed. + GetMachineOSRelease() retrieves the OS release information of a container. This method returns an array of key value pairs read from the os-release5 file in @@ -459,6 +468,8 @@ node /org/freedesktop/machine1/machine/rawhide { Kill(in s who, in i signal); GetAddresses(out a(iay) addresses); + GetSSHInfo(out s ssh_address, + out s ssh_private_key_path); GetOSRelease(out a{ss} fields); GetUIDShift(out u shift); OpenPTY(out h pty, @@ -507,6 +518,12 @@ node /org/freedesktop/machine1/machine/rawhide { readonly s RootDirectory = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly ai NetworkInterfaces = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u VSockCID = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s SSHAddress = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s SSHPrivateKeyPath = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s State = '...'; }; @@ -548,6 +565,8 @@ node /org/freedesktop/machine1/machine/rawhide { + + @@ -590,6 +609,12 @@ node /org/freedesktop/machine1/machine/rawhide { + + + + + + @@ -601,9 +626,9 @@ node /org/freedesktop/machine1/machine/rawhide { take the same arguments as TerminateMachine() and KillMachine() on the Manager interface, respectively. - GetAddresses() and GetOSRelease() get the IP address and OS - release information from the machine. These methods take the same arguments as - GetMachineAddresses() and GetMachineOSRelease() of the + GetAddresses(), GetSSHInfo() and GetOSRelease() get the IP address, + SSH connection and OS release information from the machine. These methods take the same arguments as + GetMachineAddresses(), GetMachineSSHInfo() and GetMachineOSRelease() of the Manager interface, respectively. @@ -636,6 +661,15 @@ node /org/freedesktop/machine1/machine/rawhide { towards the container, the VM or the host. For details about this information see the description of CreateMachineWithNetwork() above. + VSockCID is the VSOCK CID of the VM if it is known, or + VMADDR_CID_ANY otherwise. + + SSHAddress is the address of the VM in a format ssh can understand + if it is known or the empty string. + + SSHPrivateKeyPath is the path to the SSH private key of the VM if it is known + or the empty string. + State is the state of the machine and is one of opening, running, or closing. Note that the state machine is not considered part of the API and states might be removed or added without this being considered API breakage. @@ -675,11 +709,14 @@ $ gdbus introspect --system \ The Manager Object CopyFromMachineWithFlags() and CopyToMachineWithFlags() were added in version 252. + GetMachineSSHInfo() was added in version 256. Machine Objects CopyFromWithFlags() and CopyToWithFlags() were added in version 252. + GetSSHInfo(), VSockCID, SSHAddress + and SSHPrivateKeyPath were added in version 256. diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index a7a53eacaf..a4f04c0b86 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -347,6 +347,27 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd return sd_bus_send(NULL, reply, NULL); } +int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Machine *m = ASSERT_PTR(userdata); + int r; + + assert(message); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + if (!m->ssh_address || !m->ssh_private_key_path) + return -ENOENT; + + r = sd_bus_message_append(reply, "ss", m->ssh_address, m->ssh_private_key_path); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + #define EXIT_NOT_FOUND 2 int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -1261,6 +1282,9 @@ static const sd_bus_vtable machine_vtable[] = { SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("VSockCID", "u", NULL, offsetof(Machine, vsock_cid), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SSHAddress", "s", NULL, offsetof(Machine, ssh_address), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SSHPrivateKeyPath", "s", NULL, offsetof(Machine, ssh_private_key_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), SD_BUS_METHOD("Terminate", @@ -1278,6 +1302,11 @@ static const sd_bus_vtable machine_vtable[] = { SD_BUS_RESULT("a(iay)", addresses), bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetSSHInfo", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("s", ssh_address, "s", ssh_private_key_path), + bus_machine_method_get_ssh_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetOSRelease", SD_BUS_NO_ARGS, SD_BUS_RESULT("a{ss}", fields), diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h index a0133451ab..fccad71d12 100644 --- a/src/machine/machine-dbus.h +++ b/src/machine/machine-dbus.h @@ -19,6 +19,7 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c index 2b71ae9966..5c21a039e5 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -27,6 +27,7 @@ #include "path-util.h" #include "process-util.h" #include "serialize.h" +#include "socket-util.h" #include "special.h" #include "stdio-util.h" #include "string-table.h" @@ -53,6 +54,7 @@ int machine_new(MachineClass class, const char *name, Machine **ret) { *m = (Machine) { .leader = PIDREF_NULL, + .vsock_cid = VMADDR_CID_ANY, }; if (name) { @@ -130,6 +132,8 @@ Machine* machine_free(Machine *m) { free(m->service); free(m->root_directory); free(m->netif); + free(m->ssh_address); + free(m->ssh_private_key_path); return mfree(m); } diff --git a/src/machine/machine.h b/src/machine/machine.h index 24ef15894b..f8146fb55b 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -62,6 +62,10 @@ struct Machine { int *netif; size_t n_netif; + unsigned vsock_cid; + char *ssh_address; + char *ssh_private_key_path; + LIST_HEAD(Operation, operations); LIST_FIELDS(Machine, gc_queue); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index e7c7f6fd69..dbb03f3d67 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -462,6 +462,10 @@ static int method_get_machine_addresses(sd_bus_message *message, void *userdata, return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_addresses); } +static int method_get_machine_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_ssh_info); +} + static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_os_release); } @@ -1067,6 +1071,11 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("a(iay)", addresses), method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetMachineSSHInfo", + SD_BUS_ARGS("s", name), + SD_BUS_RESULT("s", ssh_address, "s", ssh_private_key_path), + method_get_machine_ssh_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetMachineOSRelease", SD_BUS_ARGS("s", name), SD_BUS_RESULT("a{ss}", fields), From 5b44c81ff868a4d1b78a74e4770f7a8b2f1d0f91 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Tue, 23 Apr 2024 10:26:59 +0100 Subject: [PATCH 5/5] machined: add varlink interface for registering machines This commit adds the new varlink interface io.systemd.Machine at /run/systemd/machine/io.systemd.Machine with a single method Register It supports all combinations of RegisterMachine[WithSSH,WithNetwork] all under the same method. --- man/systemd-machined.service.xml | 6 +- src/machine/machine-varlink.c | 171 ++++++++++++++++++++++++ src/machine/machine-varlink.h | 6 + src/machine/machined-varlink.c | 62 ++++++++- src/machine/machined.c | 5 +- src/machine/machined.h | 3 +- src/machine/meson.build | 1 + src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Machine.c | 22 +++ src/shared/varlink-io.systemd.Machine.h | 6 + 10 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 src/machine/machine-varlink.c create mode 100644 src/machine/machine-varlink.h create mode 100644 src/shared/varlink-io.systemd.Machine.c create mode 100644 src/shared/varlink-io.systemd.Machine.h diff --git a/man/systemd-machined.service.xml b/man/systemd-machined.service.xml index f3d7755973..b2899ff0fd 100644 --- a/man/systemd-machined.service.xml +++ b/man/systemd-machined.service.xml @@ -100,10 +100,12 @@ The daemon provides both a C library interface (which is shared with systemd-logind.service8) - as well as a D-Bus interface. + as well as a D-Bus interface and a Varlink interface. The library interface may be used to introspect and watch the state of virtual machines/containers. The bus interface provides the same but in addition may also be used to register or terminate - machines. + machines. The Varlink interface may be used to register machines with optional extensions, e.g. with an + SSH key / address; it can be queried with + varlinkctl introspect /run/systemd/machine/io.systemd.Machine io.systemd.Machine. For more information please consult sd-login3 and diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c new file mode 100644 index 0000000000..377b3d3f0e --- /dev/null +++ b/src/machine/machine-varlink.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-id128.h" + +#include "hostname-util.h" +#include "json.h" +#include "machine-varlink.h" +#include "machine.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "varlink.h" + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_machine_class, MachineClass, machine_class_from_string); + +static int machine_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **m = ASSERT_PTR(userdata); + const char *hostname; + int r; + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + hostname = json_variant_string(variant); + if (!hostname_is_valid(hostname, /* flags= */ 0)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid machine name"); + + r = free_and_strdup(m, hostname); + if (r < 0) + return json_log_oom(variant, flags); + + return 0; +} + +static int machine_leader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + PidRef *leader = ASSERT_PTR(userdata); + _cleanup_(pidref_done) PidRef temp = PIDREF_NULL; + uint64_t k; + int r; + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + + k = json_variant_unsigned(variant); + if (k > PID_T_MAX || !pid_is_valid(k)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid PID.", strna(name)); + + if (k == 1) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid leader PID.", strna(name)); + + r = pidref_set_pid(&temp, k); + if (r < 0) + return json_log(variant, flags, r, "Failed to pin process " PID_FMT ": %m", leader->pid); + + pidref_done(leader); + + *leader = TAKE_PIDREF(temp); + + return 0; +} + +static int machine_ifindices(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + Machine *m = ASSERT_PTR(userdata); + _cleanup_free_ int *netif = NULL; + size_t n_netif, k = 0; + + assert(variant); + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + n_netif = json_variant_elements(variant); + + netif = new(int, n_netif); + if (!netif) + return json_log_oom(variant, flags); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + uint64_t b; + + if (!json_variant_is_unsigned(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name)); + + b = json_variant_unsigned(i); + if (b > INT_MAX || b <= 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid network interface index %"PRIu64, b); + + netif[k++] = (int) b; + } + assert(k == n_netif); + + free_and_replace(m->netif, netif); + m->n_netif = n_netif; + + return 0; +} + +static int machine_cid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + unsigned cid, *c = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + cid = json_variant_unsigned(variant); + if (!VSOCK_CID_IS_REGULAR(cid)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a regular VSOCK CID.", strna(name)); + + *c = cid; + + return 0; +} + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(machine_freep) Machine *machine = NULL; + int r; + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), JSON_MANDATORY }, + { "id", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(Machine, id), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, service), 0 }, + { "class", JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), JSON_MANDATORY }, + { "leader", JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, + { "rootDirectory", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, root_directory), 0 }, + { "ifIndices", JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, + { "vsockCid", JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, + { "sshAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, ssh_address), JSON_SAFE }, + { "sshPrivateKeyPath", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, ssh_private_key_path), 0 }, + {} + }; + + r = machine_new(_MACHINE_CLASS_INVALID, NULL, &machine); + if (r < 0) + return r; + + r = varlink_dispatch(link, parameters, dispatch_table, machine); + if (r != 0) + return r; + + if (!pidref_is_set(&machine->leader)) { + r = varlink_get_peer_pidref(link, &machine->leader); + if (r < 0) + return r; + } + + r = machine_link(manager, machine); + if (r < 0) + return r; + + r = cg_pidref_get_unit(&machine->leader, &machine->unit); + if (r < 0) + return r; + + r = machine_start(machine, NULL, NULL); + if (r < 0) + return r; + + /* the manager will free this machine */ + TAKE_PTR(machine); + + return varlink_reply(link, NULL); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h new file mode 100644 index 0000000000..ce4ec54dc1 --- /dev/null +++ b/src/machine/machine-varlink.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink.h" + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 6ca98e27cf..0d3ae627c1 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-util.h" +#include "machine-varlink.h" #include "machined-varlink.h" #include "mkdir.h" #include "user-util.h" #include "varlink.h" +#include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.UserDatabase.h" typedef struct LookupParameters { @@ -378,13 +380,13 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } -int manager_varlink_init(Manager *m) { +static int manager_varlink_init_userdb(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; assert(m); - if (m->varlink_server) + if (m->varlink_userdb_server) return 0; r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); @@ -415,12 +417,64 @@ int manager_varlink_init(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - m->varlink_server = TAKE_PTR(s); + m->varlink_userdb_server = TAKE_PTR(s); + return 0; +} + +static int manager_varlink_init_machine(Manager *m) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + + if (m->varlink_machine_server) + return 0; + + r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + r = varlink_server_add_interface(s, &vl_interface_io_systemd_Machine); + if (r < 0) + return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); + + r = varlink_server_bind_method(s, "io.systemd.Machine.Register", vl_method_register); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + (void) mkdir_p("/run/systemd/machine", 0755); + + r = varlink_server_listen_address(s, "/run/systemd/machine/io.systemd.Machine", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + m->varlink_machine_server = TAKE_PTR(s); + return 0; +} + +int manager_varlink_init(Manager *m) { + int r; + + r = manager_varlink_init_userdb(m); + if (r < 0) + return r; + + r = manager_varlink_init_machine(m); + if (r < 0) + return r; + return 0; } void manager_varlink_done(Manager *m) { assert(m); - m->varlink_server = varlink_server_unref(m->varlink_server); + m->varlink_userdb_server = varlink_server_unref(m->varlink_userdb_server); + m->varlink_machine_server = varlink_server_unref(m->varlink_machine_server); } diff --git a/src/machine/machined.c b/src/machine/machined.c index 2638ed572e..d7087e4672 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -316,7 +316,10 @@ static bool check_idle(void *userdata) { if (m->operations) return false; - if (varlink_server_current_connections(m->varlink_server) > 0) + if (varlink_server_current_connections(m->varlink_userdb_server) > 0) + return false; + + if (varlink_server_current_connections(m->varlink_machine_server) > 0) return false; manager_gc(m, true); diff --git a/src/machine/machined.h b/src/machine/machined.h index 280c32bab6..67abed0fd6 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -40,7 +40,8 @@ struct Manager { sd_event_source *nscd_cache_flush_event; #endif - VarlinkServer *varlink_server; + VarlinkServer *varlink_userdb_server; + VarlinkServer *varlink_machine_server; }; int manager_add_machine(Manager *m, const char *name, Machine **_machine); diff --git a/src/machine/meson.build b/src/machine/meson.build index c82a32589d..3150b33de5 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -3,6 +3,7 @@ libmachine_core_sources = files( 'image-dbus.c', 'machine-dbus.c', + 'machine-varlink.c', 'machine.c', 'machined-core.c', 'machined-dbus.c', diff --git a/src/shared/meson.build b/src/shared/meson.build index 17313aefed..d01367a159 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -180,6 +180,7 @@ shared_sources = files( 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.MountFileSystem.c', 'varlink-io.systemd.NamespaceResource.c', diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c new file mode 100644 index 0000000000..936f01f366 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-idl.h" +#include "varlink-io.systemd.Machine.h" + +static VARLINK_DEFINE_METHOD( + Register, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(vsockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Machine, + "io.systemd.Machine", + &vl_method_Register); diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h new file mode 100644 index 0000000000..c9fc85f150 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.h @@ -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_Machine;