Merge pull request #33398 from AdrianVovk/sysupdate-optional
sysupdate: Add support for optional features
This commit is contained in:
+228
-20
@@ -13,6 +13,7 @@
|
||||
#include "bus-util.h"
|
||||
#include "common-signal.h"
|
||||
#include "discover-image.h"
|
||||
#include "dropin.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "event-util.h"
|
||||
@@ -31,6 +32,8 @@
|
||||
#include "string-table.h"
|
||||
#include "sysupdate-util.h"
|
||||
|
||||
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
|
||||
|
||||
typedef struct Manager {
|
||||
sd_event *event;
|
||||
sd_bus *bus;
|
||||
@@ -85,6 +88,7 @@ typedef enum JobType {
|
||||
JOB_CHECK_NEW,
|
||||
JOB_UPDATE,
|
||||
JOB_VACUUM,
|
||||
JOB_DESCRIBE_FEATURE,
|
||||
_JOB_TYPE_MAX,
|
||||
_JOB_TYPE_INVALID = -EINVAL,
|
||||
} JobType;
|
||||
@@ -104,6 +108,7 @@ struct Job {
|
||||
JobType type;
|
||||
bool offline;
|
||||
char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */
|
||||
char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
|
||||
|
||||
unsigned progress_percent;
|
||||
|
||||
@@ -131,11 +136,12 @@ static const char* const target_class_table[_TARGET_CLASS_MAX] = {
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
|
||||
|
||||
static const char* const job_type_table[_JOB_TYPE_MAX] = {
|
||||
[JOB_LIST] = "list",
|
||||
[JOB_DESCRIBE] = "describe",
|
||||
[JOB_CHECK_NEW] = "check-new",
|
||||
[JOB_UPDATE] = "update",
|
||||
[JOB_VACUUM] = "vacuum",
|
||||
[JOB_LIST] = "list",
|
||||
[JOB_DESCRIBE] = "describe",
|
||||
[JOB_CHECK_NEW] = "check-new",
|
||||
[JOB_UPDATE] = "update",
|
||||
[JOB_VACUUM] = "vacuum",
|
||||
[JOB_DESCRIBE_FEATURE] = "describe-feature",
|
||||
};
|
||||
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
|
||||
@@ -149,6 +155,7 @@ static Job *job_free(Job *j) {
|
||||
|
||||
free(j->object_path);
|
||||
free(j->version);
|
||||
free(j->feature);
|
||||
|
||||
sd_json_variant_unref(j->json);
|
||||
|
||||
@@ -440,8 +447,8 @@ static int job_start(Job *j) {
|
||||
NULL, /* maybe --verify=no */
|
||||
NULL, /* maybe --component=, --root=, or --image= */
|
||||
NULL, /* maybe --offline */
|
||||
NULL, /* list, check-new, update, vacuum */
|
||||
NULL, /* maybe version (for list, update) */
|
||||
NULL, /* list, check-new, update, vacuum, features */
|
||||
NULL, /* maybe version (for list, update), maybe feature (features) */
|
||||
NULL
|
||||
};
|
||||
size_t k = 2;
|
||||
@@ -493,6 +500,12 @@ static int job_start(Job *j) {
|
||||
cmd[k++] = "vacuum";
|
||||
break;
|
||||
|
||||
case JOB_DESCRIBE_FEATURE:
|
||||
cmd[k++] = "features";
|
||||
assert(!isempty(j->feature));
|
||||
cmd[k++] = j->feature;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
@@ -573,20 +586,26 @@ static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *
|
||||
action = "org.freedesktop.sysupdate1.vacuum";
|
||||
break;
|
||||
|
||||
case JOB_DESCRIBE_FEATURE:
|
||||
action = NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
msg,
|
||||
action,
|
||||
/* details= */ NULL,
|
||||
&j->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
if (action) {
|
||||
r = bus_verify_polkit_async(
|
||||
msg,
|
||||
action,
|
||||
/* details= */ NULL,
|
||||
&j->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
}
|
||||
|
||||
r = job_cancel(j);
|
||||
if (r < 0)
|
||||
@@ -918,6 +937,8 @@ static int target_method_describe_finish(
|
||||
_cleanup_free_ char *text = NULL;
|
||||
int r;
|
||||
|
||||
/* NOTE: This is also reused by target_method_describe_feature */
|
||||
|
||||
assert(json);
|
||||
|
||||
r = sd_json_variant_format(json, 0, &text);
|
||||
@@ -1132,7 +1153,7 @@ static int target_method_vacuum_finish(
|
||||
sd_bus_error *error) {
|
||||
|
||||
sd_json_variant *v;
|
||||
uint64_t instances;
|
||||
uint64_t instances, disabled;
|
||||
|
||||
assert(json);
|
||||
|
||||
@@ -1144,7 +1165,15 @@ static int target_method_vacuum_finish(
|
||||
instances = sd_json_variant_unsigned(v);
|
||||
assert(instances <= UINT32_MAX);
|
||||
|
||||
return sd_bus_reply_method_return(msg, "u", (uint32_t) instances);
|
||||
v = sd_json_variant_by_key(json, "disabledTransfers");
|
||||
if (!v)
|
||||
return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
|
||||
if (!sd_json_variant_is_unsigned(v))
|
||||
return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
|
||||
disabled = sd_json_variant_unsigned(v);
|
||||
assert(disabled <= UINT32_MAX);
|
||||
|
||||
return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
|
||||
}
|
||||
|
||||
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
|
||||
@@ -1247,6 +1276,167 @@ static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_b
|
||||
return sd_bus_send(NULL, reply, NULL);
|
||||
}
|
||||
|
||||
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_strv_free_ char **features = NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
Target *t = ASSERT_PTR(userdata);
|
||||
sd_json_variant *v;
|
||||
uint64_t flags;
|
||||
int r;
|
||||
|
||||
assert(msg);
|
||||
|
||||
r = sd_bus_message_read(msg, "t", &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (flags != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
|
||||
|
||||
r = sysupdate_run_simple(&json, t, "features", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = sd_json_variant_by_key(json, "features");
|
||||
if (!v)
|
||||
return -EINVAL;
|
||||
r = sd_json_variant_strv(v, &features);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_new_method_return(msg, &reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_append_strv(reply, features);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_send(NULL, reply, NULL);
|
||||
}
|
||||
|
||||
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
|
||||
Target *t = ASSERT_PTR(userdata);
|
||||
_cleanup_(job_freep) Job *j = NULL;
|
||||
const char *feature;
|
||||
uint64_t flags;
|
||||
int r;
|
||||
|
||||
assert(msg);
|
||||
|
||||
r = sd_bus_message_read(msg, "st", &feature, &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (isempty(feature))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified");
|
||||
|
||||
if (flags != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
|
||||
|
||||
r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
j->feature = strdup(feature);
|
||||
if (!j->feature)
|
||||
return log_oom();
|
||||
|
||||
r = job_start(j);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
|
||||
TAKE_PTR(j); /* Avoid job from being killed & freed */
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool feature_name_is_valid(const char *name) {
|
||||
if (isempty(name))
|
||||
return false;
|
||||
|
||||
if (!ascii_is_valid(name))
|
||||
return false;
|
||||
|
||||
if (!filename_is_valid(strjoina(name, ".feature.d")))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_free_ char *feature_ext = NULL;
|
||||
Target *t = ASSERT_PTR(userdata);
|
||||
const char *feature;
|
||||
uint64_t flags;
|
||||
int32_t enabled;
|
||||
int r;
|
||||
|
||||
assert(msg);
|
||||
|
||||
if (t->class != TARGET_HOST)
|
||||
return sd_bus_reply_method_errorf(msg,
|
||||
SD_BUS_ERROR_NOT_SUPPORTED,
|
||||
"For now, features can only be managed on the host system.");
|
||||
|
||||
r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!feature_name_is_valid(feature))
|
||||
return sd_bus_reply_method_errorf(msg,
|
||||
SD_BUS_ERROR_INVALID_ARGS,
|
||||
"The specified feature is invalid");
|
||||
if (flags != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
|
||||
|
||||
if (!endswith(feature, ".feature")) {
|
||||
feature_ext = strjoin(feature, ".feature");
|
||||
if (!feature_ext)
|
||||
return -ENOMEM;
|
||||
feature = feature_ext;
|
||||
}
|
||||
|
||||
const char *details[] = {
|
||||
"class", target_class_to_string(t->class),
|
||||
"name", t->name,
|
||||
"feature", feature,
|
||||
"enabled", enabled >= 0 ? true_false(enabled) : "unset",
|
||||
NULL
|
||||
};
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
msg,
|
||||
"org.freedesktop.sysupdate1.manage-features",
|
||||
details,
|
||||
&t->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
/* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
|
||||
if (enabled < 0) { /* Reset -> delete the drop-in file */
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (unlink(path) < 0)
|
||||
return -errno;
|
||||
} else { /* otherwise, create the drop-in with the right settings */
|
||||
r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
|
||||
"# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
|
||||
"[Feature]\n"
|
||||
"Enabled=%s\n",
|
||||
yes_no(enabled));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_reply_method_return(msg, NULL);
|
||||
}
|
||||
|
||||
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_strv_free_ char **components = NULL;
|
||||
@@ -1397,7 +1587,7 @@ static const sd_bus_vtable target_vtable[] = {
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("Vacuum",
|
||||
SD_BUS_NO_ARGS,
|
||||
SD_BUS_RESULT("u", count),
|
||||
SD_BUS_RESULT("u", instances, "u", disabled_transfers),
|
||||
target_method_vacuum,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
@@ -1413,6 +1603,24 @@ static const sd_bus_vtable target_vtable[] = {
|
||||
target_method_get_version,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("ListFeatures",
|
||||
SD_BUS_ARGS("t", flags),
|
||||
SD_BUS_RESULT("as", features),
|
||||
target_method_list_features,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
|
||||
SD_BUS_ARGS("s", feature, "t", flags),
|
||||
SD_BUS_RESULT("s", json),
|
||||
target_method_describe_feature,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
|
||||
SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
target_method_set_feature_enabled,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user