importd: add simple varlink API

For now, let's just support Pull() and ListTransfers(), but this is just
a start.
This commit is contained in:
Lennart Poettering 2024-05-08 10:42:48 +02:00
parent 71613cd59a
commit 17a6043a14
5 changed files with 422 additions and 5 deletions

View file

@ -22,6 +22,7 @@
#include "hostname-util.h"
#include "import-common.h"
#include "import-util.h"
#include "json-util.h"
#include "machine-pool.h"
#include "main-func.h"
#include "missing_capability.h"
@ -39,6 +40,8 @@
#include "strv.h"
#include "syslog-util.h"
#include "user-util.h"
#include "varlink.h"
#include "varlink-io.systemd.Import.h"
#include "web-util.h"
typedef struct Transfer Transfer;
@ -87,11 +90,14 @@ struct Transfer {
int stdin_fd;
int stdout_fd;
Set *varlink_subscribed;
};
struct Manager {
sd_event *event;
sd_bus *bus;
VarlinkServer *varlink_server;
uint32_t current_transfer_id;
Hashmap *transfers;
@ -120,6 +126,8 @@ static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(varlink_hash_ops, void, trivial_hash_func, trivial_compare_func, Varlink, varlink_unref);
static Transfer *transfer_unref(Transfer *t) {
if (!t)
return NULL;
@ -141,6 +149,8 @@ static Transfer *transfer_unref(Transfer *t) {
safe_close(t->stdin_fd);
safe_close(t->stdout_fd);
set_free(t->varlink_subscribed);
return mfree(t);
}
@ -218,7 +228,16 @@ static void transfer_send_log_line(Transfer *t, const char *line) {
priority,
line);
if (r < 0)
log_warning_errno(r, "Cannot emit log message signal, ignoring: %m");
log_warning_errno(r, "Cannot emit log message bus signal, ignoring: %m");
r = varlink_many_notifybo(
t->varlink_subscribed,
SD_JSON_BUILD_PAIR("log",
SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_UNSIGNED("priority", priority),
SD_JSON_BUILD_PAIR_STRING("message", line))));
if (r < 0)
log_warning_errno(r, "Cannot emit log message varlink message, ignoring: %m");
}
static void transfer_send_progress_update(Transfer *t) {
@ -229,15 +248,23 @@ static void transfer_send_progress_update(Transfer *t) {
if (t->progress_percent_sent == t->progress_percent)
return;
double progress = transfer_percent_as_double(t);
r = sd_bus_emit_signal(
t->manager->bus,
t->object_path,
"org.freedesktop.import1.Transfer",
"ProgressUpdate",
"d",
transfer_percent_as_double(t));
progress);
if (r < 0)
log_warning_errno(r, "Cannot emit progress update signal, ignoring: %m");
log_warning_errno(r, "Cannot emit progress update bus signal, ignoring: %m");
r = varlink_many_notifybo(
t->varlink_subscribed,
SD_JSON_BUILD_PAIR_REAL("progress", progress));
if (r < 0)
log_warning_errno(r, "Cannot emit progress update varlink message, ignoring: %m");
t->progress_percent_sent = t->progress_percent;
}
@ -314,10 +341,18 @@ static int transfer_finalize(Transfer *t, bool success) {
t->object_path,
success ? "done" :
t->n_canceled > 0 ? "canceled" : "failed");
if (r < 0)
log_error_errno(r, "Cannot emit message: %m");
if (success)
r = varlink_many_reply(t->varlink_subscribed, NULL);
else if (t->n_canceled > 0)
r = varlink_many_error(t->varlink_subscribed, "io.systemd.Import.TransferCancelled", NULL);
else
r = varlink_many_error(t->varlink_subscribed, "io.systemd.Import.TransferFailed", NULL);
if (r < 0)
log_warning_errno(r, "Cannot emit varlink reply, ignoring: %m");
transfer_unref(t);
return 0;
}
@ -587,6 +622,8 @@ static Manager *manager_unref(Manager *m) {
hashmap_free(m->polkit_registry);
m->bus = sd_bus_flush_close_unref(m->bus);
m->varlink_server = varlink_server_unref(m->varlink_server);
sd_event_unref(m->event);
return mfree(m);
@ -1729,11 +1766,248 @@ static int manager_connect_bus(Manager *m) {
return 0;
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_image_class, ImageClass, image_class_from_string);
static int make_transfer_json(Transfer *t, sd_json_variant **ret) {
int r;
assert(t);
r = sd_json_buildo(ret,
SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id)),
SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(transfer_type_to_string(t->type))),
SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(t->remote)),
SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(t->local)),
SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(t->class))),
SD_JSON_BUILD_PAIR("percent", SD_JSON_BUILD_REAL(transfer_percent_as_double(t))));
if (r < 0)
return log_error_errno(r, "Failed to build transfer JSON data: %m");
return 0;
}
static int vl_method_list_transfers(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) {
struct p {
ImageClass class;
} p = {
.class = _IMAGE_CLASS_INVALID,
};
static const sd_json_dispatch_field dispatch_table[] = {
{ "class", SD_JSON_VARIANT_STRING, json_dispatch_image_class, offsetof(struct p, class), 0 },
{},
};
Manager *m = ASSERT_PTR(userdata);
int r;
assert(link);
assert(parameters);
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!FLAGS_SET(flags, VARLINK_METHOD_MORE))
return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL);
Transfer *previous = NULL, *t;
HASHMAP_FOREACH(t, m->transfers) {
if (p.class >= 0 && p.class != t->class)
continue;
if (previous) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = make_transfer_json(previous, &v);
if (r < 0)
return r;
r = varlink_notify(link, v);
if (r < 0)
return r;
}
previous = t;
}
if (previous) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = make_transfer_json(previous, &v);
if (r < 0)
return r;
return varlink_reply(link, v);
}
return varlink_error(link, "io.systemd.Import.NoTransfers", NULL);
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_import_verify, ImportVerify, import_verify_from_string);
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_import_type, ImportType, import_type_from_string);
static int vl_method_pull(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) {
struct p {
const char *remote, *local;
ImageClass class;
ImportType type;
ImportVerify verify;
bool force;
bool read_only;
bool keep_download;
} p = {
.class = _IMAGE_CLASS_INVALID,
.verify = IMPORT_VERIFY_SIGNATURE,
};
static const sd_json_dispatch_field dispatch_table[] = {
{ "remote", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct p, remote), SD_JSON_MANDATORY },
{ "local", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct p, local), 0 },
{ "class", SD_JSON_VARIANT_STRING, json_dispatch_image_class, offsetof(struct p, class), SD_JSON_MANDATORY },
{ "type", SD_JSON_VARIANT_STRING, json_dispatch_import_type, offsetof(struct p, type), SD_JSON_MANDATORY },
{ "verify", SD_JSON_VARIANT_STRING, json_dispatch_import_verify, offsetof(struct p, verify), SD_JSON_STRICT },
{ "force", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, force), 0 },
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, read_only), 0 },
{ "keepDownload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, keep_download), 0 },
VARLINK_DISPATCH_POLKIT_FIELD,
{},
};
Manager *m = ASSERT_PTR(userdata);
int r;
assert(link);
assert(parameters);
r = varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!http_url_is_valid(p.remote) && !file_url_is_valid(p.remote))
return varlink_error_invalid_parameter_name(link, "remote");
if (p.local && !image_name_is_valid(p.local))
return varlink_error_invalid_parameter_name(link, "local");
uint64_t transfer_flags = (p.force * IMPORT_FORCE) | (p.read_only * IMPORT_READ_ONLY) | (p.keep_download * IMPORT_PULL_KEEP_DOWNLOAD);
TransferType tt =
p.type == IMPORT_TAR ? TRANSFER_PULL_TAR :
p.type == IMPORT_RAW ? TRANSFER_PULL_RAW : _TRANSFER_TYPE_INVALID;
assert(tt >= 0);
if (manager_find(m, tt, p.remote))
return varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote));
r = varlink_verify_polkit_async(
link,
m->bus,
"org.freedesktop.import1.pull",
(const char**) STRV_MAKE(
"remote", p.remote,
"local", p.local,
"class", image_class_to_string(p.class),
"type", import_type_to_string(p.type),
"verify", import_verify_to_string(p.verify)),
&m->polkit_registry);
if (r <= 0)
return r;
_cleanup_(transfer_unrefp) Transfer *t = NULL;
r = transfer_new(m, &t);
if (r < 0)
return r;
t->type = tt;
t->verify = p.verify;
t->flags = transfer_flags;
t->class = p.class;
t->remote = strdup(p.remote);
if (!t->remote)
return -ENOMEM;
if (p.local) {
t->local = strdup(p.local);
if (!t->local)
return -ENOMEM;
}
r = transfer_start(t);
if (r < 0)
return r;
/* If more was not set, just return the download id, and be done with it */
if (!FLAGS_SET(flags, VARLINK_METHOD_MORE))
return varlink_replybo(link, SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id)));
/* Otherwise add this connection to the set of subscriptions, return the id, but keep the thing running */
r = set_ensure_put(&t->varlink_subscribed, &varlink_hash_ops, link);
if (r < 0)
return r;
varlink_ref(link);
r = varlink_notifybo(link, SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id)));
if (r < 0)
return r;
TAKE_PTR(t);
return 0;
}
static int manager_connect_varlink(Manager *m) {
int r;
assert(m);
assert(m->event);
assert(!m->varlink_server);
r = varlink_server_new(&m->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(m->varlink_server, m);
r = varlink_server_add_interface(m->varlink_server, &vl_interface_io_systemd_Import);
if (r < 0)
return log_error_errno(r, "Failed to add Import interface to varlink server: %m");
r = varlink_server_bind_method_many(
m->varlink_server,
"io.systemd.Import.ListTransfers", vl_method_list_transfers,
"io.systemd.Import.Pull", vl_method_pull);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink method calls: %m");
r = varlink_server_attach_event(m->varlink_server, m->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(m->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(m->varlink_server, "/run/systemd/io.systemd.Import", 0666);
if (r < 0)
return log_error_errno(r, "Failed to bind to Varlink socket: %m");
}
return 0;
}
static bool manager_check_idle(void *userdata) {
Manager *m = ASSERT_PTR(userdata);
return hashmap_isempty(m->transfers) &&
hashmap_isempty(m->polkit_registry);
hashmap_isempty(m->polkit_registry) &&
varlink_server_current_connections(m->varlink_server) == 0;
}
static void manager_parse_env(Manager *m) {
@ -1786,6 +2060,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
r = manager_connect_varlink(m);
if (r < 0)
return r;
r = sd_notify(false, NOTIFY_READY);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");

View file

@ -180,6 +180,7 @@ shared_sources = files(
'varlink-io.systemd.BootControl.c',
'varlink-io.systemd.Credentials.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.Machine.c',
'varlink-io.systemd.ManagedOOM.c',

View file

@ -0,0 +1,129 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Import.h"
static VARLINK_DEFINE_ENUM_TYPE(
ImageClass,
VARLINK_FIELD_COMMENT("An image to boot as a system on baremetal, in a VM or as a container"),
VARLINK_DEFINE_ENUM_VALUE(machine),
VARLINK_FIELD_COMMENT("An portable service image"),
VARLINK_DEFINE_ENUM_VALUE(portable),
VARLINK_FIELD_COMMENT("A system extension image"),
VARLINK_DEFINE_ENUM_VALUE(sysext),
VARLINK_FIELD_COMMENT("A configuration extension image"),
VARLINK_DEFINE_ENUM_VALUE(confext));
static VARLINK_DEFINE_ENUM_TYPE(
RemoteType,
VARLINK_FIELD_COMMENT("Raw binary disk images, typically in a GPT envelope"),
VARLINK_DEFINE_ENUM_VALUE(raw),
VARLINK_FIELD_COMMENT("A tarball, optionally compressed"),
VARLINK_DEFINE_ENUM_VALUE(tar));
static VARLINK_DEFINE_ENUM_TYPE(
TransferType,
VARLINK_FIELD_COMMENT("A local import of a tarball"),
VARLINK_DEFINE_ENUM_VALUE(import_tar),
VARLINK_FIELD_COMMENT("A local import of a raw disk image"),
VARLINK_DEFINE_ENUM_VALUE(import_raw),
VARLINK_FIELD_COMMENT("A local import of a file system tree"),
VARLINK_DEFINE_ENUM_VALUE(import_fs),
VARLINK_FIELD_COMMENT("A local export of a tarball"),
VARLINK_DEFINE_ENUM_VALUE(export_tar),
VARLINK_FIELD_COMMENT("A local export of a raw disk image"),
VARLINK_DEFINE_ENUM_VALUE(export_raw),
VARLINK_FIELD_COMMENT("A download of a tarball"),
VARLINK_DEFINE_ENUM_VALUE(pull_tar),
VARLINK_FIELD_COMMENT("A download of a raw disk image"),
VARLINK_DEFINE_ENUM_VALUE(pull_raw));
static VARLINK_DEFINE_ENUM_TYPE(
ImageVerify,
VARLINK_FIELD_COMMENT("No verification"),
VARLINK_DEFINE_ENUM_VALUE(no),
VARLINK_FIELD_COMMENT("Verify that downloads match checksum file (SHA256SUMS), but do not check signature of checksum file"),
VARLINK_DEFINE_ENUM_VALUE(checksum),
VARLINK_FIELD_COMMENT("Verify that downloads match checksum file (SHA256SUMS), and check signature of checksum file."),
VARLINK_DEFINE_ENUM_VALUE(signature));
static VARLINK_DEFINE_STRUCT_TYPE(
LogMessage,
VARLINK_FIELD_COMMENT("The log message"),
VARLINK_DEFINE_FIELD(message, VARLINK_STRING, 0),
VARLINK_FIELD_COMMENT("The priority of the log message, using the BSD syslog priority levels"),
VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0));
static VARLINK_DEFINE_METHOD(
ListTransfers,
VARLINK_FIELD_COMMENT("Image class to filter by"),
VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("A unique numeric identifier for the ongoing transfer"),
VARLINK_DEFINE_OUTPUT(id, VARLINK_INT, 0),
VARLINK_FIELD_COMMENT("The type of transfer"),
VARLINK_DEFINE_OUTPUT_BY_TYPE(type, TransferType, 0),
VARLINK_FIELD_COMMENT("The remote URL"),
VARLINK_DEFINE_OUTPUT(remote, VARLINK_STRING, 0),
VARLINK_FIELD_COMMENT("The local image name"),
VARLINK_DEFINE_OUTPUT(local, VARLINK_STRING, 0),
VARLINK_FIELD_COMMENT("The class of the image"),
VARLINK_DEFINE_OUTPUT_BY_TYPE(class, ImageClass, 0),
VARLINK_FIELD_COMMENT("Progress in percent"),
VARLINK_DEFINE_OUTPUT(percent, VARLINK_FLOAT, 0));
static VARLINK_DEFINE_METHOD(
Pull,
VARLINK_FIELD_COMMENT("The remote URL to download from"),
VARLINK_DEFINE_INPUT(remote, VARLINK_STRING, 0),
VARLINK_FIELD_COMMENT("The local image name to download to"),
VARLINK_DEFINE_INPUT(local, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("The type of the resource"),
VARLINK_DEFINE_INPUT_BY_TYPE(type, RemoteType, 0),
VARLINK_FIELD_COMMENT("The image class"),
VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, 0),
VARLINK_FIELD_COMMENT("The whether and how thoroughly to verify the download before installing it locally. Defauts to 'signature'."),
VARLINK_DEFINE_INPUT_BY_TYPE(verify, ImageVerify, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("If true, an existing image by the local name is deleted. Defaults to false."),
VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults ot false."),
VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("Whether to keep a pristine copy of the download separate from the locally installed image. Defaults to false."),
VARLINK_DEFINE_INPUT(keepDownload, VARLINK_BOOL, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("Whether to permit interactive authentication. Defaults to false."),
VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("A progress update, as percent value"),
VARLINK_DEFINE_OUTPUT(progress, VARLINK_FLOAT, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("A log message about the ongoing transfer"),
VARLINK_DEFINE_OUTPUT_BY_TYPE(log, LogMessage, VARLINK_NULLABLE),
VARLINK_FIELD_COMMENT("The numeric ID of this download"),
VARLINK_DEFINE_OUTPUT(id, VARLINK_INT, VARLINK_NULLABLE));
static VARLINK_DEFINE_ERROR(AlreadyInProgress);
static VARLINK_DEFINE_ERROR(TransferCancelled);
static VARLINK_DEFINE_ERROR(TransferFailed);
static VARLINK_DEFINE_ERROR(NoTransfers);
VARLINK_DEFINE_INTERFACE(
io_systemd_Import,
"io.systemd.Import",
VARLINK_SYMBOL_COMMENT("Describes the class of images"),
&vl_type_ImageClass,
VARLINK_SYMBOL_COMMENT("Describes the type of a images to transfer"),
&vl_type_RemoteType,
VARLINK_SYMBOL_COMMENT("Describes the type of a transfer"),
&vl_type_TransferType,
VARLINK_SYMBOL_COMMENT("Describes whether and how thoroughly to verify the download before installing it locally"),
&vl_type_ImageVerify,
VARLINK_SYMBOL_COMMENT("Structure for log messages associated with a transfer operation"),
&vl_type_LogMessage,
VARLINK_SYMBOL_COMMENT("List ongoing transfers, or query details about specific transfers"),
&vl_method_ListTransfers,
VARLINK_SYMBOL_COMMENT("Download a .tar or .raw file. This must be called with the 'more' flag enabled. It will immediately return the numeric ID of the transfer, and then follow up with progress and log message updates, until the transfer is complete."),
&vl_method_Pull,
VARLINK_SYMBOL_COMMENT("A transfer for the specified file is already ongoing"),
&vl_error_AlreadyInProgress,
VARLINK_SYMBOL_COMMENT("The transfer has been cancelled on user request"),
&vl_error_TransferCancelled,
VARLINK_SYMBOL_COMMENT("The transfer failed"),
&vl_error_TransferFailed,
VARLINK_SYMBOL_COMMENT("No currently ongoing transfer"),
&vl_error_NoTransfers);

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_Import;

View file

@ -10,6 +10,7 @@
#include "varlink-io.systemd.h"
#include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.Credentials.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.ManagedOOM.h"
#include "varlink-io.systemd.MountFileSystem.h"
@ -182,6 +183,8 @@ TEST(parse_format) {
print_separator();
test_parse_format_one(&vl_interface_io_systemd_BootControl);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Import);
print_separator();
test_parse_format_one(&vl_interface_xyz_test);
}