out s new_version,
out t job_id,
out o job_path);
+ Acquire(in s new_version,
+ in t flags,
+ out s new_version,
+ out t job_id,
+ out o job_path);
+ Install(in s new_version,
+ in t flags,
+ out s new_version,
+ out t job_id,
+ out o job_path);
Vacuum(out u instances,
out u disabled_transfers);
GetAppStream(out as appstream);
<variablelist class="dbus-method" generated="True" extra-ref="Update()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="Acquire()"/>
+
+ <variablelist class="dbus-method" generated="True" extra-ref="Install()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="Vacuum()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetAppStream()"/>
by the caller. This method pulls both metadata and payload data from the network. Listen for the
Manager's <function>JobRemoved()</function> signal to detect when the job is complete.</para>
+ <para><function>Acquire()</function> downloads an update for this target, if one is available. If a
+ <varname>new_version</varname> is specified, that is the version that gets downloaded. Otherwise, the
+ latest version is downloaded. Call <function>Install()</function> to install the acquired update.
+ The <varname>flags</varname> argument is added for future extensibility. No flags are currently
+ defined, and the argument is required to be set to <literal>0</literal>. This method pulls both
+ metadata and payload data from the network.</para>
+
+ <para><function>Install()</function> installs an already-acquired update for this target. If a
+ <varname>new_version</varname> is specified, that is the version that gets installed, assuming it has
+ already been acquired. Otherwise, the latest acquired version is installed. The
+ <varname>flags</varname> argument is added for future extensibility. No flags are currently defined,
+ and the argument is required to be set to <literal>0</literal>.</para>
+
+ <para>Unlike all the other methods in this interface, <function>Acquire()</function> and
+ <function>Install()</function> do not wait for their jobs to complete. Instead, they return the job's
+ numeric ID and object path as soon as the job begins, so that the caller can listen for progress
+ updates or cancel the operation. These methods also return the version the target will be updated to,
+ for cases where no version was specified by the caller. Listen for the Manager's
+ <function>JobRemoved()</function> signal to detect when the job is complete.</para>
+
<para><function>Vacuum()</function> deletes old installed versions of this target to free up space.
It returns the number of instances that have been deleted.</para>
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>.
+ call <function>Update()</function> (or <function>Acquire()</function> and <function>Install()</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>).
use the polkit action <interfacename>org.freedesktop.sysupdate1.check</interfacename>.
By default, this action is permitted without administrator authentication.</para>
- <para><function>Update()</function> uses the polkit action
+ <para><function>Update()</function>, <function>Acquire()</function> and <function>Install()</function>
+ use the polkit action
<interfacename>org.freedesktop.sysupdate1.update</interfacename> when no version is specified.
By default, this action is permitted without administrator authentication. When a version is
specified, <interfacename>org.freedesktop.sysupdate1.update-to-version</interfacename> is
<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>
+ <literal>update</literal>, <literal>acquire</literal>, <literal>install</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>
<para>The <varname>Progress</varname> property exposes the current progress of the job as a value
- between 0 and 100. It is only available for <literal>update</literal> jobs; for all other jobs
- it is always 0.</para>
+ between 0 and 100. It is only available for <literal>update</literal>, <literal>acquire</literal> and
+ <literal>install</literal> jobs; for all other jobs it is always 0.</para>
</refsect2>
<refsect2>
<function>Describe()</function>,
<function>CheckNew()</function>,
<function>Update()</function>,
+ <function>Acquire()</function>,
+ <function>Install()</function>,
<function>Vacuum()</function>,
<function>GetAppStream()</function>,
<function>GetVersion()</function>,
JOB_DESCRIBE,
JOB_CHECK_NEW,
JOB_UPDATE,
+ JOB_ACQUIRE,
+ JOB_INSTALL,
JOB_VACUUM,
JOB_DESCRIBE_FEATURE,
_JOB_TYPE_MAX,
JobType type;
bool offline;
- char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */
+ char *version; /* Passed into sysupdate for JOB_DESCRIBE, JOB_UPDATE, JOB_ACQUIRE and JOB_INSTALL */
char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
unsigned progress_percent;
[JOB_DESCRIBE] = "describe",
[JOB_CHECK_NEW] = "check-new",
[JOB_UPDATE] = "update",
+ [JOB_ACQUIRE] = "acquire",
+ [JOB_INSTALL] = "install",
[JOB_VACUUM] = "vacuum",
[JOB_DESCRIBE_FEATURE] = "describe-feature",
};
/* Is Job in the set of jobs which require Target.busy to be set so they run exclusively? */
static bool job_requires_busy(Job *j) {
- return IN_SET(j->type, JOB_UPDATE, JOB_VACUUM);
+ return IN_SET(j->type, JOB_UPDATE, JOB_ACQUIRE, JOB_INSTALL, JOB_VACUUM);
}
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
NULL, /* maybe --verify=no */
NULL, /* maybe --component=, --root=, or --image= */
NULL, /* maybe --offline */
- NULL, /* list, check-new, update, vacuum, features */
- NULL, /* maybe version (for list, update), maybe feature (features) */
+ NULL, /* list, check-new, acquire, update, vacuum, features */
+ NULL, /* maybe version (for list, acquire, update), maybe feature (features) */
NULL
};
size_t k = 2;
if (target_arg)
cmd[k++] = target_arg;
- if (j->offline)
+ if (j->offline || j->type == JOB_INSTALL) /* install is implemented as `update --offline` */
cmd[k++] = "--offline";
switch (j->type) {
cmd[k++] = empty_to_null(j->version);
break;
+ case JOB_ACQUIRE:
+ cmd[k++] = "acquire";
+ cmd[k++] = empty_to_null(j->version);
+ break;
+
+ case JOB_INSTALL:
+ cmd[k++] = "update"; /* install is implemented as `update --offline` */
+ cmd[k++] = empty_to_null(j->version);
+ break;
+
case JOB_VACUUM:
cmd[k++] = "vacuum";
break;
break;
case JOB_UPDATE:
+ case JOB_ACQUIRE:
+ case JOB_INSTALL:
if (j->version)
action = "org.freedesktop.sysupdate1.update-to-version";
else
return 1;
}
+static int target_method_acquire_finished_early(
+ sd_bus_message *msg,
+ const Job *j,
+ sd_json_variant *json,
+ sd_bus_error *error) {
+
+ /* Called when job finishes w/ a successful exit code, but before any work begins.
+ * This happens when there is no candidate (i.e. we're already up-to-date), or
+ * specified update is already acquired. */
+ return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
+ "Job exited successfully with no work to do, assume already acquired");
+}
+
+static int target_method_acquire_detach(sd_bus_message *msg, const Job *j) {
+ int r;
+
+ assert(msg);
+ assert(j);
+
+ r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 0;
+}
+
+static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
+ Target *t = ASSERT_PTR(userdata);
+ _cleanup_(job_freep) Job *j = NULL;
+ const char *version, *action;
+ uint64_t flags;
+ int r;
+
+ assert(msg);
+
+ r = sd_bus_message_read(msg, "st", &version, &flags);
+ if (r < 0)
+ return r;
+
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
+
+ /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
+ * an update anyway. */
+ if (isempty(version))
+ action = "org.freedesktop.sysupdate1.update";
+ else
+ action = "org.freedesktop.sysupdate1.update-to-version";
+
+ const char *details[] = {
+ "class", target_class_to_string(t->class),
+ "name", t->name,
+ "version", version,
+ NULL
+ };
+
+ r = bus_verify_polkit_async(
+ msg,
+ action,
+ details,
+ &t->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = job_new(JOB_ACQUIRE, t, msg, target_method_acquire_finished_early, &j);
+ if (r < 0)
+ return r;
+ j->detach_cb = target_method_acquire_detach;
+
+ j->version = strdup(version);
+ if (!j->version)
+ return -ENOMEM;
+
+ r = job_start(j);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
+ TAKE_PTR(j);
+
+ return 1;
+}
+
+static int target_method_install_finished_early(
+ sd_bus_message *msg,
+ const Job *j,
+ sd_json_variant *json,
+ sd_bus_error *error) {
+
+ /* Called when job finishes w/ a successful exit code, but before any work begins.
+ * This happens when there is no candidate (i.e. we're already up-to-date), or
+ * specified update is already installed. */
+ return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
+ "Job exited successfully with no work to do, assume already installed");
+}
+
+static int target_method_install_detach(sd_bus_message *msg, const Job *j) {
+ int r;
+
+ assert(msg);
+ assert(j);
+
+ r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return 0;
+}
+
+static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
+ Target *t = ASSERT_PTR(userdata);
+ _cleanup_(job_freep) Job *j = NULL;
+ const char *version, *action;
+ uint64_t flags;
+ int r;
+
+ assert(msg);
+
+ r = sd_bus_message_read(msg, "st", &version, &flags);
+ if (r < 0)
+ return r;
+
+ if (flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
+
+ /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
+ * an update anyway. */
+ if (isempty(version))
+ action = "org.freedesktop.sysupdate1.update";
+ else
+ action = "org.freedesktop.sysupdate1.update-to-version";
+
+ const char *details[] = {
+ "class", target_class_to_string(t->class),
+ "name", t->name,
+ "version", version,
+ NULL
+ };
+
+ r = bus_verify_polkit_async(
+ msg,
+ action,
+ details,
+ &t->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = job_new(JOB_INSTALL, t, msg, target_method_install_finished_early, &j);
+ if (r < 0)
+ return r;
+ j->detach_cb = target_method_install_detach;
+
+ j->version = strdup(version);
+ if (!j->version)
+ return -ENOMEM;
+
+ r = job_start(j);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
+ TAKE_PTR(j);
+
+ return 1;
+}
+
static int target_method_vacuum_finish(
sd_bus_message *msg,
const Job *j,
target_method_update,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("Acquire",
+ SD_BUS_ARGS("s", new_version, "t", flags),
+ SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
+ target_method_acquire,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
+ SD_BUS_METHOD_WITH_ARGS("Install",
+ SD_BUS_ARGS("s", new_version, "t", flags),
+ SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
+ target_method_install,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+
SD_BUS_METHOD_WITH_ARGS("Vacuum",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("u", instances, "u", disabled_transfers),