out s new_version,
out t job_id,
out o job_path);
- Vacuum(out u count);
+ Vacuum(out u instances,
+ out u disabled_transfers);
GetAppStream(out as appstream);
GetVersion(out s version);
+ ListFeatures(in t flags,
+ out as features);
+ DescribeFeature(in s feature,
+ in t flags,
+ out s json);
+ SetFeatureEnabled(in s feature,
+ in i enabled,
+ in t flags);
properties:
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s Class = '...';
<variablelist class="dbus-method" generated="True" extra-ref="GetVersion()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="ListFeatures()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="DescribeFeature()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="SetFeatureEnabled()"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="Class"/>
<variablelist class="dbus-property" generated="True" extra-ref="Name"/>
<varname>IMAGE_VERSION</varname> in <filename>/etc/os-release</filename>. If the target has no current
version, the function will return an empty string.</para>
+ <para><function>ListFeatures()</function> returns a list of this target's optional features, by ID.
+ The <varname>flags</varname> argument is added for future extensibility, and must be set to 0.
+ If the target has no optional features, the method returns an empty array.</para>
+
+ <para><function>DescribeFeature()</function> returns all known information about a given optional feature.
+ The <varname>feature</varname> argument is used to pass the ID of the feature to be described.
+ The <varname>flags</varname> argument is added for future extensibility, and must be set to 0.
+ The returned JSON object contains several known keys. More keys may be added in the future.
+ The currently known keys are as follows:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>name</literal></term>
+ <listitem><para>A string containing the feature's name.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>description</literal></term>
+ <listitem><para>An optional string that contains a user-presentable description that identifies
+ this feature</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>enabled</literal></term>
+ <listitem><para>A boolean indicating whether this feature is enabled.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>documentationUrl</literal></term>
+ <listitem><para>An optional string that contains a user-presentable HTTP/HTTPS URL to documentation
+ about this feature.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>appstreamUrl</literal></term>
+ <listitem><para>An optional string that contains an HTTP/HTTPS URL to an
+ <ulink url="https://wwww.freedesktop.org/software/appstream/docs/chap-CatalogData.html">appstream
+ catalog</ulink> XML file containing metadata about this feature.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>transfers</literal></term>
+ <listitem><para>An optional array of strings that list which transfer definitions belong to this
+ feature.</para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para><function>SetFeatureEnabled()</function> writes an appropriate drop-in file to enable or disable
+ the specified optional feature.
+ If <varname>enable</varname> is zero, the feature is disabled. When greater than zero, the feature is
+ enabled. When less than zero, the feature is reset to the distribution's default.
+ The <varname>flags</varname> argument is added for future extensibility, and must be set to 0.
+ The feature does not have to exist; this allows for graceful handling of masked features, and for
+ preemptive decisions to be made about features that are planned to appear in future releases of the OS.
+ The drop-in will have a filename of <literal>50-systemd-sysupdate-enabled.conf</literal>.
+ This method only changes configuration files; to actually apply the changes, clients will need to
+ call <function>Update()</function>.
+ Depending on the exact needs of the client, it can choose to update the system to the latest available
+ version, or it can extend the newest existing installation in-place (by passing in the version returned
+ by <varname>GetVersion()</varname>).
+ For now, this method only works with the <literal>host</literal> target.</para>
+
</refsect2>
<refsect2>
<interfacename>org.freedesktop.sysupdate1.vacuum</interfacename>. By default, this action requires
administrator authentication.</para>
- <para><function>GetAppStream()</function> and <function>GetVersion()</function> are unauthenticated and
- may be called by anybody.</para>
+ <para><function>SetFeatureEnabled()</function> uses the polkit action
+ <interfacename>org.freedesktop.sysupdate1.manage-features</interfacename>. By default, this action
+ requires administrator authentication.</para>
+
+ <para><function>GetAppStream()</function>, <function>GetVersion()</function>,
+ <function>ListFeatures()</function>, and <function>DescribeFeature()</function>
+ are unauthenticated and may be called by anybody.</para>
<para>All methods called on this interface expose additional variables to the polkit rules.
<literal>class</literal> contains the class of the Target being acted upon, and <literal>name</literal>
<para>The <varname>Id</varname> property exposes the numeric job ID of the job object.</para>
- <para>The <varname>Type</varname> property exposes the type of operation (one of: <literal>list</literal>,
- <literal>describe</literal>, <literal>check-new</literal>, <literal>update</literal>, or <literal>vacuum</literal>).
- </para>
+ <para>The <varname>Type</varname> property exposes the type of operation (one of:
+ <literal>list</literal>, <literal>describe</literal>, <literal>check-new</literal>,
+ <literal>update</literal>, <literal>vacuum</literal>, or <literal>describe-feature</literal>).</para>
<para>The <varname>Offline</varname> property exposes whether the job is permitted to access
the network or not.</para>
<function>Vacuum()</function>,
<function>GetAppStream()</function>,
<function>GetVersion()</function>,
+ <function>ListFeatures()</function>,
+ <function>DescribeFeature()</function>,
+ <function>SetFeatureEnabled()</function>,
<varname>Class</varname>,
<varname>Name</varname>, and
<varname>Path</varname> were added in version 257.</para>
#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"
#include "string-table.h"
#include "sysupdate-util.h"
+#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
+
typedef struct Manager {
sd_event *event;
sd_bus *bus;
JOB_CHECK_NEW,
JOB_UPDATE,
JOB_VACUUM,
+ JOB_DESCRIBE_FEATURE,
_JOB_TYPE_MAX,
_JOB_TYPE_INVALID = -EINVAL,
} JobType;
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;
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);
free(j->object_path);
free(j->version);
+ free(j->feature);
sd_json_variant_unref(j->json);
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;
cmd[k++] = "vacuum";
break;
+ case JOB_DESCRIBE_FEATURE:
+ cmd[k++] = "features";
+ assert(!isempty(j->feature));
+ cmd[k++] = j->feature;
+ break;
+
default:
assert_not_reached();
}
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)
_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);
sd_bus_error *error) {
sd_json_variant *v;
- uint64_t instances;
+ uint64_t instances, disabled;
assert(json);
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) {
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;
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),
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
};