• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 24697298032

20 Apr 2026 09:22PM UTC coverage: 72.23% (+1.6%) from 70.661%
24697298032

push

github

bluca
sysupdate: Emit READY=1 status when installing

`READY=1` is already correctly emitted when acquiring an update, but was
forgotten to be emitted when subsequently installing that update.

That meant that the state tracking code in `sysupdated` and hence
`updatectl` could not properly report the progress of the install
operation, and hence printed “Already up-to-date” after a successful
update installation, rather than “Done”.

Add a test to catch this in future.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Fixes: https://github.com/systemd/systemd/issues/41502

322667 of 446720 relevant lines covered (72.23%)

1191594.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.34
/src/sysupdate/sysupdated.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdlib.h>
4
#include <sys/stat.h>
5
#include <unistd.h>
6

7
#include "sd-bus.h"
8
#include "sd-json.h"
9

10
#include "build-path.h"
11
#include "bus-common-errors.h"
12
#include "bus-error.h"
13
#include "bus-get-properties.h"
14
#include "bus-label.h"
15
#include "bus-log-control-api.h"
16
#include "bus-object.h"
17
#include "bus-polkit.h"
18
#include "bus-util.h"
19
#include "common-signal.h"
20
#include "constants.h"
21
#include "discover-image.h"
22
#include "dropin.h"
23
#include "env-util.h"
24
#include "escape.h"
25
#include "event-util.h"
26
#include "fd-util.h"
27
#include "fileio.h"
28
#include "format-util.h"
29
#include "hashmap.h"
30
#include "log.h"
31
#include "main-func.h"
32
#include "memfd-util.h"
33
#include "notify-recv.h"
34
#include "os-util.h"
35
#include "parse-util.h"
36
#include "path-util.h"
37
#include "pidref.h"
38
#include "process-util.h"
39
#include "runtime-scope.h"
40
#include "service-util.h"
41
#include "signal-util.h"
42
#include "string-table.h"
43
#include "strv.h"
44
#include "sysupdate-util.h"
45
#include "utf8.h"
46

47
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
48

49
typedef struct Manager {
50
        sd_event *event;
51
        sd_bus *bus;
52

53
        Hashmap *targets;
54

55
        uint64_t last_job_id;
56
        Hashmap *jobs;
57

58
        Hashmap *polkit_registry;
59

60
        char *notify_socket_path;
61

62
        RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
63
} Manager;
64

65
/* Forward declare so that jobs can call it on exit */
66
static void manager_check_idle(Manager *m);
67

68
typedef enum TargetClass {
69
        /* These should try to match ImageClass from src/basic/os-util.h */
70
        TARGET_MACHINE  = IMAGE_MACHINE,
71
        TARGET_PORTABLE = IMAGE_PORTABLE,
72
        TARGET_SYSEXT   = IMAGE_SYSEXT,
73
        TARGET_CONFEXT  = IMAGE_CONFEXT,
74
        _TARGET_CLASS_IS_IMAGE_CLASS_MAX,
75

76
        /* sysupdate-specific classes */
77
        TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX,
78
        TARGET_COMPONENT,
79

80
        _TARGET_CLASS_MAX,
81
        _TARGET_CLASS_INVALID = -EINVAL,
82
} TargetClass;
83

84
/* Let's ensure when the number of classes is updated things are updated here too */
85
assert_cc((int) _IMAGE_CLASS_MAX == (int) _TARGET_CLASS_IS_IMAGE_CLASS_MAX);
86

87
typedef struct Target {
88
        Manager *manager;
89

90
        TargetClass class;
91
        char *name;
92
        char *path;
93

94
        char *id;
95
        ImageType image_type;
96
        bool busy;
97
} Target;
98

99
typedef enum JobType {
100
        JOB_LIST,
101
        JOB_DESCRIBE,
102
        JOB_CHECK_NEW,
103
        JOB_ACQUIRE,
104
        JOB_INSTALL,
105
        JOB_VACUUM,
106
        JOB_DESCRIBE_FEATURE,
107
        _JOB_TYPE_MAX,
108
        _JOB_TYPE_INVALID = -EINVAL,
109
} JobType;
110

111
typedef struct Job Job;
112

113
typedef int (*JobReady)(sd_bus_message *msg, const Job *job);
114
typedef int (*JobComplete)(sd_bus_message *msg, const Job *job, sd_json_variant *response, sd_bus_error *error);
115

116
struct Job {
117
        Manager *manager;
118
        Target *target;
119

120
        uint64_t id;
121
        char *object_path;
122

123
        JobType type;
124
        bool offline;
125
        char *version; /* Passed into sysupdate for JOB_DESCRIBE, JOB_ACQUIRE and JOB_INSTALL */
126
        char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
127

128
        unsigned progress_percent;
129

130
        sd_event_source *child;
131
        int stdout_fd;
132
        int status_errno;
133
        unsigned n_cancelled;
134

135
        sd_json_variant *json;
136

137
        JobComplete complete_cb; /* Callback called on job exit */
138
        sd_bus_message *dbus_msg;
139
        JobReady detach_cb; /* Callback called when job has started.  Detaches the job to run in the background */
140
};
141

142
static const char* const target_class_table[_TARGET_CLASS_MAX] = {
143
        [TARGET_MACHINE]   = "machine",
144
        [TARGET_PORTABLE]  = "portable",
145
        [TARGET_SYSEXT]    = "sysext",
146
        [TARGET_CONFEXT]   = "confext",
147
        [TARGET_COMPONENT] = "component",
148
        [TARGET_HOST]      = "host",
149
};
150

151
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
216✔
152

153
static const char* const job_type_table[_JOB_TYPE_MAX] = {
154
        [JOB_LIST]             = "list",
155
        [JOB_DESCRIBE]         = "describe",
156
        [JOB_CHECK_NEW]        = "check-new",
157
        [JOB_ACQUIRE]          = "acquire",
158
        [JOB_INSTALL]          = "install",
159
        [JOB_VACUUM]           = "vacuum",
160
        [JOB_DESCRIBE_FEATURE] = "describe-feature",
161
};
162

163
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
164

165
static Job *job_free(Job *j) {
116✔
166
        if (!j)
116✔
167
                return NULL;
168

169
        if (j->manager)
116✔
170
                assert_se(hashmap_remove(j->manager->jobs, &j->id) == j);
116✔
171

172
        free(j->object_path);
116✔
173
        free(j->version);
116✔
174
        free(j->feature);
116✔
175

176
        sd_json_variant_unref(j->json);
116✔
177

178
        sd_bus_message_unref(j->dbus_msg);
116✔
179

180
        sd_event_source_disable_unref(j->child);
116✔
181
        safe_close(j->stdout_fd);
116✔
182

183
        return mfree(j);
116✔
184
}
185

186
DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free);
232✔
187
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops,
×
188
                                              uint64_t, uint64_hash_func, uint64_compare_func,
189
                                              Job, job_free);
190

191
static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) {
116✔
192
        _cleanup_(job_freep) Job *j = NULL;
116✔
193
        int r;
116✔
194

195
        assert(t);
116✔
196
        assert(ret);
116✔
197

198
        j = new(Job, 1);
116✔
199
        if (!j)
116✔
200
                return -ENOMEM;
201

202
        *j = (Job) {
232✔
203
                .type = type,
204
                .target = t,
205
                .id = t->manager->last_job_id + 1,
116✔
206
                .stdout_fd = -EBADF,
207
                .complete_cb = complete_cb,
208
                .dbus_msg = sd_bus_message_ref(msg),
116✔
209
        };
210

211
        if (asprintf(&j->object_path, "/org/freedesktop/sysupdate1/job/_%" PRIu64, j->id) < 0)
116✔
212
                return -ENOMEM;
213

214
        r = hashmap_ensure_put(&t->manager->jobs, &job_hash_ops, &j->id, j);
116✔
215
        if (r < 0)
116✔
216
                return r;
217

218
        j->manager = t->manager;
116✔
219

220
        t->manager->last_job_id = j->id;
116✔
221

222
        *ret = TAKE_PTR(j);
116✔
223
        return 0;
116✔
224
}
225

226
/* Is Job in the set of jobs which require Target.busy to be set so they run exclusively? */
227
static bool job_requires_busy(Job *j) {
348✔
228
        return IN_SET(j->type, JOB_ACQUIRE, JOB_INSTALL, JOB_VACUUM);
348✔
229
}
230

231
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
116✔
232
        _cleanup_close_ int fd = ASSERT_FD(_fd); /* Take ownership of the passed fd */
232✔
233
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
116✔
234
        struct stat st;
116✔
235
        int r;
116✔
236

237
        assert(ret);
116✔
238

239
        if (fstat(fd, &st) < 0)
116✔
240
                return log_debug_errno(errno, "Failed to stat stdout fd: %m");
×
241

242
        assert(S_ISREG(st.st_mode));
116✔
243

244
        if (st.st_size == 0) {
116✔
245
                log_warning("No output from child job, ignoring");
22✔
246
                return 0;
22✔
247
        }
248

249
        r = sd_json_parse_file_at(/* f= */ NULL, fd, /* path= */ NULL, /* flags= */ 0,
94✔
250
                                  &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
251
        if (r < 0)
94✔
252
                return log_debug_errno(r, "Failed to parse child output as JSON: %m");
×
253

254
        *ret = TAKE_PTR(v);
94✔
255
        return 0;
94✔
256
}
257

258
static void job_on_ready(Job *j) {
44✔
259
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *msg = NULL;
44✔
260
        int r;
44✔
261

262
        assert(j);
44✔
263

264
        /* Some jobs run in the background as we return the job ID to the dbus caller (i.e. for the Update
265
         * method). However, the worker will perform some sanity-checks on startup which would be valuable
266
         * as dbus errors. So, we wait for the worker to signal via READY=1 that it has completed its sanity
267
         * checks and we should continue the job in the background. */
268

269
        if (!j->detach_cb)
44✔
270
                return;
271

272
        log_debug("Got READY=1 from job %" PRIu64", detaching.", j->id);
44✔
273

274
        assert(j->dbus_msg);
44✔
275
        msg = TAKE_PTR(j->dbus_msg);
44✔
276

277
        j->complete_cb = NULL;
44✔
278

279
        r = j->detach_cb(msg, j);
44✔
280
        if (r < 0)
44✔
281
                log_warning_errno(r, "Failed to detach job %" PRIu64 ", ignoring: %m", j->id);
44✔
282
}
283

284
static void job_on_errno(Job *j, const char *buf) {
×
285
        int r;
×
286

287
        assert(j);
×
288
        assert(buf);
×
289

290
        r = parse_errno(buf);
×
291
        if (r < 0) {
×
292
                log_warning_errno(r, "Got invalid errno value from job %" PRIu64 ", ignoring: %m", j->id);
×
293
                return;
×
294
        }
295

296
        j->status_errno = r;
×
297

298
        log_debug_errno(r, "Got errno from job %" PRIu64 ": %i (%m)", j->id, r);
×
299
}
300

301
static void job_on_progress(Job *j, const char *buf) {
79✔
302
        unsigned progress;
79✔
303
        int r;
79✔
304

305
        assert(j);
79✔
306
        assert(buf);
79✔
307

308
        r = safe_atou(buf, &progress);
79✔
309
        if (r < 0 || progress > 100) {
79✔
310
                log_warning("Got invalid percent value, ignoring.");
×
311
                return;
×
312
        }
313

314
        j->progress_percent = progress;
79✔
315
        (void) sd_bus_emit_properties_changed(j->manager->bus, j->object_path,
79✔
316
                                              "org.freedesktop.sysupdate1.Job",
317
                                              "Progress", NULL);
318

319
        log_debug("Got percentage from job %" PRIu64 ": %u%%", j->id, j->progress_percent);
79✔
320
}
321

322
static void job_on_version(Job *j, const char *version) {
44✔
323
        assert(j);
44✔
324
        assert(version);
44✔
325

326
        if (free_and_strdup_warn(&j->version, version) < 0)
44✔
327
                return;
328

329
        log_debug("Got version from job %" PRIu64 ": %s ", j->id, j->version);
44✔
330
}
331

332
static int job_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
116✔
333
        Job *j = ASSERT_PTR(userdata);
116✔
334
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
335
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
116✔
336
        Manager *manager = j->manager;
116✔
337
        int r;
116✔
338

339
        assert(j);
116✔
340
        assert(s);
116✔
341
        assert(si);
116✔
342

343
        if (job_requires_busy(j)) {
116✔
344
                assert(j->target->busy);
44✔
345
                j->target->busy = false;
44✔
346
        }
347

348
        if (si->si_code != CLD_EXITED) {
116✔
349
                assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
×
350
                sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
351
                                  "Job terminated abnormally with signal %s.",
352
                                  signal_to_string(si->si_status));
×
353
        } else if (si->si_status != EXIT_SUCCESS)
116✔
354
                if (j->status_errno != 0)
×
355
                        sd_bus_error_set_errno(&error, j->status_errno);
×
356
                else
357
                        sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
358
                                          "Job failed with exit code %i.", si->si_status);
359
        else {
360
                r = job_parse_child_output(TAKE_FD(j->stdout_fd), &json);
116✔
361
                if (r < 0)
116✔
362
                        sd_bus_error_set_errnof(&error, r, "Failed to parse job worker output: %m");
×
363
        }
364

365
        /* Only send notification of exit if the job was actually detached */
366
        if (j->detach_cb) {
116✔
367
                r = sd_bus_emit_signal(
44✔
368
                                j->manager->bus,
44✔
369
                                "/org/freedesktop/sysupdate1",
370
                                "org.freedesktop.sysupdate1.Manager",
371
                                "JobRemoved",
372
                                "toi",
373
                                j->id,
374
                                j->object_path,
375
                                j->status_errno != 0 ? -j->status_errno : si->si_status);
44✔
376
                if (r < 0)
44✔
377
                        log_warning_errno(r,
×
378
                                          "Cannot emit JobRemoved message for job %" PRIu64 ", ignoring: %m",
379
                                          j->id);
380
        }
381

382
        if (j->dbus_msg && j->complete_cb) {
116✔
383
                if (sd_bus_error_is_set(&error)) {
72✔
384
                        log_warning("Job %" PRIu64 " failed with bus error, ignoring callback: %s",
×
385
                                    j->id, error.message);
386
                        sd_bus_reply_method_error(j->dbus_msg, &error);
×
387
                } else {
388
                        r = j->complete_cb(j->dbus_msg, j, json, &error);
72✔
389
                        if (r < 0) {
72✔
390
                                log_warning_errno(r,
×
391
                                                  "Error during execution of job callback for job %" PRIu64 ": %s",
392
                                                  j->id,
393
                                                  bus_error_message(&error, r));
394
                                sd_bus_reply_method_errno(j->dbus_msg, r, &error);
×
395
                        }
396
                }
397
        }
398

399
        job_free(j);
116✔
400

401
        if (manager)
116✔
402
                manager_check_idle(manager);
116✔
403

404
        return 0;
232✔
405
}
406

407
static inline const char* sysupdate_binary_path(void) {
230✔
408
        return secure_getenv("SYSTEMD_SYSUPDATE_PATH") ?: SYSTEMD_SYSUPDATE_PATH;
230✔
409
}
410

411
static int target_get_argument(Target *t, char **ret) {
154✔
412
        _cleanup_free_ char *target_arg = NULL;
308✔
413

414
        assert(t);
154✔
415
        assert(ret);
154✔
416

417
        if (t->class != TARGET_HOST) {
154✔
418
                if (t->class == TARGET_COMPONENT)
8✔
419
                        target_arg = strjoin("--component=", t->name);
8✔
420
                else if (IN_SET(t->image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME))
×
421
                        target_arg = strjoin("--root=", t->path);
×
422
                else if (IN_SET(t->image_type, IMAGE_RAW, IMAGE_BLOCK))
×
423
                        target_arg = strjoin("--image=", t->path);
×
424
                else
425
                        assert_not_reached();
×
426
                if (!target_arg)
8✔
427
                        return -ENOMEM;
428
        }
429

430
        *ret = TAKE_PTR(target_arg);
154✔
431
        return 0;
154✔
432
}
433

434
static int job_start(Job *j) {
116✔
435
        _cleanup_close_ int stdout_fd = -EBADF;
116✔
436
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
116✔
437
        int r;
116✔
438

439
        assert(j);
116✔
440

441
        if (job_requires_busy(j) && j->target->busy)
116✔
442
                return log_notice_errno(SYNTHETIC_ERRNO(EBUSY), "Target %s busy, ignoring job.", j->target->name);
×
443

444
        stdout_fd = memfd_new("sysupdate-stdout");
116✔
445
        if (stdout_fd < 0)
116✔
446
                return log_error_errno(stdout_fd, "Failed to create memfd: %m");
×
447

448
        r = pidref_safe_fork_full("(sd-sysupdate)",
348✔
449
                                  (int[]) { -EBADF, stdout_fd, STDERR_FILENO }, NULL, 0,
116✔
450
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
451
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
452
        if (r < 0)
232✔
453
                return r; /* FORK_LOG means pidref_safe_fork_full will handle the logging */
454
        if (r == 0) {
232✔
455
                /* Child */
456

457
                _cleanup_free_ char *target_arg = NULL;
×
458
                const char *cmd[] = {
116✔
459
                        "systemd-sysupdate",
460
                        "--json=short",
461
                        NULL, /* maybe --verify=no */
462
                        NULL, /* maybe --component=, --root=, or --image= */
463
                        NULL, /* maybe --offline */
464
                        NULL, /* list, check-new, acquire, update, vacuum, features */
465
                        NULL, /* maybe version (for list, acquire, update), maybe feature (features) */
466
                        NULL
467
                };
468
                size_t k = 2;
116✔
469

470
                if (setenv("NOTIFY_SOCKET", j->manager->notify_socket_path, /* overwrite= */ 1) < 0) {
116✔
471
                        log_error_errno(errno, "setenv() failed: %m");
×
472
                        _exit(EXIT_FAILURE);
×
473
                }
474

475
                if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
116✔
476
                        cmd[k++] = "--verify=no"; /* For testing */
116✔
477

478
                r = setenv_systemd_exec_pid(true);
116✔
479
                if (r < 0)
116✔
480
                        log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
×
481

482
                r = target_get_argument(j->target, &target_arg);
116✔
483
                if (r < 0) {
116✔
484
                        log_oom();
×
485
                        _exit(EXIT_FAILURE);
×
486
                }
487
                if (target_arg)
116✔
488
                        cmd[k++] = target_arg;
8✔
489

490
                if (j->offline || j->type == JOB_INSTALL)  /* install is implemented as `update --offline` */
116✔
491
                        cmd[k++] = "--offline";
22✔
492

493
                switch (j->type) {
116✔
494
                case JOB_LIST:
8✔
495
                        cmd[k++] = "list";
8✔
496
                        break;
8✔
497

498
                case JOB_DESCRIBE:
48✔
499
                        cmd[k++] = "list";
48✔
500
                        assert(!isempty(j->version));
48✔
501
                        cmd[k++] = j->version;
48✔
502
                        break;
48✔
503

504
                case JOB_CHECK_NEW:
16✔
505
                        cmd[k++] = "check-new";
16✔
506
                        break;
16✔
507

508
                case JOB_ACQUIRE:
22✔
509
                        cmd[k++] = "acquire";
22✔
510
                        cmd[k++] = empty_to_null(j->version);
44✔
511
                        break;
22✔
512

513
                case JOB_INSTALL:
22✔
514
                        cmd[k++] = "update";  /* install is implemented as `update --offline` */
22✔
515
                        cmd[k++] = empty_to_null(j->version);
44✔
516
                        break;
22✔
517

518
                case JOB_VACUUM:
×
519
                        cmd[k++] = "vacuum";
×
520
                        break;
×
521

522
                case JOB_DESCRIBE_FEATURE:
×
523
                        cmd[k++] = "features";
×
524
                        assert(!isempty(j->feature));
×
525
                        cmd[k++] = j->feature;
×
526
                        break;
×
527

528
                default:
×
529
                        assert_not_reached();
×
530
                }
531

532
                if (DEBUG_LOGGING) {
116✔
533
                        _cleanup_free_ char *s = NULL;
116✔
534

535
                        s = quote_command_line((char**) cmd, SHELL_ESCAPE_EMPTY);
116✔
536
                        if (!s) {
116✔
537
                                log_oom();
×
538
                                _exit(EXIT_FAILURE);
×
539
                        }
540

541
                        log_debug("Spawning worker for job %" PRIu64 ": %s", j->id, s);
116✔
542
                }
543

544
                r = invoke_callout_binary(sysupdate_binary_path(), (char *const *) cmd);
232✔
545
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
546
                _exit(EXIT_FAILURE);
×
547
        }
548

549
        log_info("Started job %" PRIu64 " with worker PID " PID_FMT,
116✔
550
                 j->id, pid.pid);
551

552
        r = event_add_child_pidref(j->manager->event, &j->child, &pid, WEXITED, job_on_exit, j);
116✔
553
        if (r < 0)
116✔
554
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
555

556
        r = sd_event_source_set_child_process_own(j->child, true);
116✔
557
        if (r < 0)
116✔
558
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
559
        pidref_done(&pid); /* disarm sigkill_wait */
116✔
560

561
        j->stdout_fd = TAKE_FD(stdout_fd);
116✔
562

563
        if (job_requires_busy(j))
116✔
564
                j->target->busy = true;
44✔
565

566
        return 0;
567
}
568

569
static int job_cancel(Job *j) {
×
570
        int r;
×
571

572
        assert(j);
×
573

574
        r = sd_event_source_send_child_signal(j->child, j->n_cancelled < 3 ? SIGTERM : SIGKILL,
×
575
                                              NULL, 0);
576
        if (r < 0)
×
577
                return r;
578

579
        j->n_cancelled++;
×
580
        return 0;
×
581
}
582

583
static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
584
        Job *j = ASSERT_PTR(userdata);
×
585
        const char *action;
×
586
        int r;
×
587

588
        assert(msg);
×
589

590
        switch (j->type) {
×
591
        case JOB_LIST:
592
        case JOB_DESCRIBE:
593
        case JOB_CHECK_NEW:
594
                action = "org.freedesktop.sysupdate1.check";
595
                break;
596

597
        case JOB_ACQUIRE:
×
598
        case JOB_INSTALL:
599
                if (j->version)
×
600
                        action = "org.freedesktop.sysupdate1.update-to-version";
601
                else
602
                        action = "org.freedesktop.sysupdate1.update";
×
603
                break;
604

605
        case JOB_VACUUM:
606
                action = "org.freedesktop.sysupdate1.vacuum";
×
607
                break;
×
608

609
        case JOB_DESCRIBE_FEATURE:
610
                action = NULL;
611
                break;
612

613
        default:
×
614
                assert_not_reached();
×
615
        }
616

617
        if (action) {
×
618
                r = bus_verify_polkit_async(
×
619
                                msg,
620
                                action,
621
                                /* details= */ NULL,
622
                                &j->manager->polkit_registry,
×
623
                                error);
624
                if (r < 0)
×
625
                        return r;
626
                if (r == 0)
×
627
                        return 1; /* Will call us back */
628
        }
629

630
        r = job_cancel(j);
×
631
        if (r < 0)
×
632
                return r;
633

634
        return sd_bus_reply_method_return(msg, NULL);
×
635
}
636

637
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
638

639
static int job_object_find(
79✔
640
                sd_bus *bus,
641
                const char *path,
642
                const char *iface,
643
                void *userdata,
644
                void **ret,
645
                sd_bus_error *error) {
646

647
        Manager *m = ASSERT_PTR(userdata);
79✔
648
        Job *j;
79✔
649
        const char *p;
79✔
650
        uint64_t id;
79✔
651
        int r;
79✔
652

653
        assert(bus);
79✔
654
        assert(path);
79✔
655
        assert(ret);
79✔
656

657
        p = startswith(path, "/org/freedesktop/sysupdate1/job/_");
79✔
658
        if (!p)
79✔
659
                return 0;
79✔
660

661
        r = safe_atou64(p, &id);
79✔
662
        if (r < 0 || id == 0)
79✔
663
                return 0;
664

665
        j = hashmap_get(m->jobs, &id);
79✔
666
        if (!j)
79✔
667
                return 0;
668

669
        *ret = j;
79✔
670
        return 1;
79✔
671
}
672

673
static int job_node_enumerator(
×
674
                sd_bus *bus,
675
                const char *path,
676
                void *userdata,
677
                char ***nodes,
678
                sd_bus_error *error) {
679

680
        _cleanup_strv_free_ char **l = NULL;
×
681
        Manager *m = ASSERT_PTR(userdata);
×
682
        Job *j;
×
683
        unsigned k = 0;
×
684

685
        l = new0(char*, hashmap_size(m->jobs) + 1);
×
686
        if (!l)
×
687
                return -ENOMEM;
688

689
        HASHMAP_FOREACH(j, m->jobs) {
×
690
                l[k] = strdup(j->object_path);
×
691
                if (!l[k])
×
692
                        return -ENOMEM;
×
693
                k++;
×
694
        }
695

696
        *nodes = TAKE_PTR(l);
×
697
        return 1;
×
698
}
699

700
static const sd_bus_vtable job_vtable[] = {
701
        SD_BUS_VTABLE_START(0),
702

703
        SD_BUS_PROPERTY("Id", "t", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
704
        SD_BUS_PROPERTY("Type", "s", job_property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
705
        SD_BUS_PROPERTY("Offline", "b", bus_property_get_bool, offsetof(Job, offline), SD_BUS_VTABLE_PROPERTY_CONST),
706
        SD_BUS_PROPERTY("Progress", "u", bus_property_get_unsigned, offsetof(Job, progress_percent), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
707

708
        SD_BUS_METHOD("Cancel", NULL, NULL, job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
709

710
        SD_BUS_VTABLE_END
711
};
712

713
static const BusObjectImplementation job_object = {
714
        "/org/freedesktop/sysupdate1/job",
715
        "org.freedesktop.sysupdate1.Job",
716
        .fallback_vtables = BUS_FALLBACK_VTABLES({job_vtable, job_object_find}),
717
        .node_enumerator = job_node_enumerator,
718
};
719

720
static Target *target_free(Target *t) {
100✔
721
        if (!t)
100✔
722
                return NULL;
723

724
        free(t->name);
100✔
725
        free(t->path);
100✔
726
        free(t->id);
100✔
727

728
        return mfree(t);
100✔
729
}
730

731
DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free);
276✔
732
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops,
100✔
733
                                              char, string_hash_func, string_compare_func,
734
                                              Target, target_free);
735

736
static int target_new(Manager *m, TargetClass class, const char *name, const char *path, Target **ret) {
100✔
737
        _cleanup_(target_freep) Target *t = NULL;
100✔
738

739
        assert(m);
100✔
740
        assert(ret);
100✔
741

742
        t = new(Target, 1);
100✔
743
        if (!t)
100✔
744
                return -ENOMEM;
745

746
        *t = (Target) {
100✔
747
                .manager = m,
748
                .class = class,
749
                .image_type = _IMAGE_TYPE_INVALID,
750
        };
751

752
        t->name = strdup(name);
100✔
753
        if (!t->name)
100✔
754
                return -ENOMEM;
755

756
        t->path = strdup(path);
100✔
757
        if (!t->path)
100✔
758
                return -ENOMEM;
759

760
        if (class == TARGET_HOST)
100✔
761
                t->id = strdup("host"); /* This is what appears in the object path */
76✔
762
        else
763
                t->id = strjoin(target_class_to_string(class), ":", name);
24✔
764
        if (!t->id)
100✔
765
                return -ENOMEM;
766

767
        *ret = TAKE_PTR(t);
100✔
768
        return 0;
100✔
769
}
770

771
static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) {
114✔
772
        _cleanup_close_pair_ int pipe[2] = EBADF_PAIR;
114✔
773
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
×
774
        _cleanup_fclose_ FILE *f = NULL;
114✔
775
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
114✔
776
        _cleanup_free_ char *target_arg = NULL;
114✔
777
        int r;
114✔
778

779
        if (t) {
114✔
780
                r = target_get_argument(t, &target_arg);
38✔
781
                if (r < 0)
38✔
782
                        return r;
783
        }
784

785
        r = pipe2(pipe, O_CLOEXEC);
114✔
786
        if (r < 0)
114✔
787
                return -errno;
×
788

789
        r = pidref_safe_fork_full("(sd-sysupdate)",
342✔
790
                                  (int[]) { -EBADF, pipe[1], STDERR_FILENO },
114✔
791
                                  NULL, 0,
792
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
793
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG,
794
                                  &pid);
795
        if (r < 0)
228✔
796
                return r;
797
        if (r == 0) {
228✔
798
                /* Child */
799
                va_list ap;
114✔
800
                char *arg;
114✔
801
                _cleanup_strv_free_ char **args = NULL;
×
802

803
                if (strv_extend(&args, "systemd-sysupdate") < 0) {
114✔
804
                        log_oom();
×
805
                        _exit(EXIT_FAILURE);
×
806
                }
807

808
                if (strv_extend(&args, "--json=short") < 0) {
114✔
809
                        log_oom();
×
810
                        _exit(EXIT_FAILURE);
×
811
                }
812

813
                if (target_arg && strv_extend(&args, target_arg) < 0) {
114✔
814
                        log_oom();
×
815
                        _exit(EXIT_FAILURE);
×
816
                }
817

818
                va_start(ap, t);
114✔
819
                while ((arg = va_arg(ap, char*))) {
266✔
820
                        r = strv_extend(&args, arg);
152✔
821
                        if (r < 0)
152✔
822
                                break;
823
                }
824
                va_end(ap);
114✔
825
                if (r < 0) {
114✔
826
                        log_oom();
×
827
                        _exit(EXIT_FAILURE);
×
828
                }
829

830
                if (DEBUG_LOGGING) {
114✔
831
                        _cleanup_free_ char *s = NULL;
114✔
832

833
                        s = quote_command_line(args, SHELL_ESCAPE_EMPTY);
114✔
834
                        if (!s) {
114✔
835
                                log_oom();
×
836
                                _exit(EXIT_FAILURE);
×
837
                        }
838

839
                        log_debug("Spawning sysupdate: %s", s);
114✔
840
                }
841

842
                r = invoke_callout_binary(sysupdate_binary_path(), args);
228✔
843
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
844
                _exit(EXIT_FAILURE);
×
845
        }
846

847
        pipe[1] = safe_close(pipe[1]);
114✔
848
        f = take_fdopen(&pipe[0], "r");
114✔
849
        if (!f)
114✔
850
                return -errno;
×
851

852
        r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
114✔
853
        if (r < 0)
114✔
854
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
855

856
        *ret = TAKE_PTR(v);
114✔
857
        return 0;
114✔
858
}
859

860
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
861

862
#define log_sysupdate_bad_json_full(lvl, r, verb, msg) \
863
        log_full_errno((lvl), (r), "Invalid JSON response from 'systemd-sysupdate %s': %s", (verb), (msg))
864
#define log_sysupdate_bad_json(r, verb, msg) \
865
        log_sysupdate_bad_json_full(LOG_ERR, (r), (verb), (msg))
866
#define log_sysupdate_bad_json_debug(r, verb, msg) \
867
        log_sysupdate_bad_json_full(LOG_DEBUG, (r), (verb), (msg))
868

869
static int target_method_list_finish(
8✔
870
                sd_bus_message *msg,
871
                const Job *j,
872
                sd_json_variant *json,
873
                sd_bus_error *error) {
874

875
        sd_json_variant *v;
8✔
876
        _cleanup_strv_free_ char **versions = NULL;
×
877
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
8✔
878
        int r;
8✔
879

880
        assert(json);
8✔
881

882
        v = sd_json_variant_by_key(json, "all");
8✔
883
        if (!v)
8✔
884
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'all'");
×
885

886
        r = sd_json_variant_strv(v, &versions);
8✔
887
        if (r < 0)
8✔
888
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'all' should be strv");
×
889

890
        r = sd_bus_message_new_method_return(msg, &reply);
8✔
891
        if (r < 0)
8✔
892
                return r;
893

894
        r = sd_bus_message_append_strv(reply, versions);
8✔
895
        if (r < 0)
8✔
896
                return r;
897

898
        return sd_bus_message_send(reply);
8✔
899
}
900

901
static int target_method_list(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
8✔
902
        Target *t = ASSERT_PTR(userdata);
8✔
903
        _cleanup_(job_freep) Job *j = NULL;
8✔
904
        uint64_t flags;
8✔
905
        int r;
8✔
906

907
        assert(msg);
8✔
908

909
        r = sd_bus_message_read(msg, "t", &flags);
8✔
910
        if (r < 0)
8✔
911
                return r;
912

913
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
8✔
914
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
915

916
        const char *details[] = {
24✔
917
                "class", target_class_to_string(t->class),
8✔
918
                "name", t->name,
8✔
919
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
8✔
920
                NULL
921
        };
922

923
        r = bus_verify_polkit_async(
16✔
924
                msg,
925
                "org.freedesktop.sysupdate1.check",
926
                details,
927
                &t->manager->polkit_registry,
8✔
928
                error);
929
        if (r < 0)
8✔
930
                return r;
931
        if (r == 0)
8✔
932
                return 1; /* Will call us back */
933

934
        r = job_new(JOB_LIST, t, msg, target_method_list_finish, &j);
8✔
935
        if (r < 0)
8✔
936
                return r;
937

938
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
8✔
939

940
        r = job_start(j);
8✔
941
        if (r < 0)
8✔
942
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
943
        TAKE_PTR(j); /* Avoid job from being killed & freed */
8✔
944

945
        return 1;
8✔
946
}
947

948
static int target_method_describe_finish(
48✔
949
                sd_bus_message *msg,
950
                const Job *j,
951
                sd_json_variant *json,
952
                sd_bus_error *error) {
953
        _cleanup_free_ char *text = NULL;
48✔
954
        int r;
48✔
955

956
        /* NOTE: This is also reused by target_method_describe_feature */
957

958
        assert(json);
48✔
959

960
        r = sd_json_variant_format(json, 0, &text);
48✔
961
        if (r < 0)
48✔
962
                return r;
963

964
        return sd_bus_reply_method_return(msg, "s", text);
48✔
965
}
966

967
static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
48✔
968
        Target *t = ASSERT_PTR(userdata);
48✔
969
        _cleanup_(job_freep) Job *j = NULL;
48✔
970
        const char *version;
48✔
971
        uint64_t flags;
48✔
972
        int r;
48✔
973

974
        assert(msg);
48✔
975

976
        r = sd_bus_message_read(msg, "st", &version, &flags);
48✔
977
        if (r < 0)
48✔
978
                return r;
979

980
        if (!version_is_valid(version))
48✔
981
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
982

983
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
48✔
984
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
985

986
        const char *details[] = {
144✔
987
                "class", target_class_to_string(t->class),
48✔
988
                "name", t->name,
48✔
989
                "version", version,
990
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
48✔
991
                NULL
992
        };
993

994
        r = bus_verify_polkit_async(
96✔
995
                msg,
996
                "org.freedesktop.sysupdate1.check",
997
                details,
998
                &t->manager->polkit_registry,
48✔
999
                error);
1000
        if (r < 0)
48✔
1001
                return r;
1002
        if (r == 0)
48✔
1003
                return 1; /* Will call us back */
1004

1005
        r = job_new(JOB_DESCRIBE, t, msg, target_method_describe_finish, &j);
48✔
1006
        if (r < 0)
48✔
1007
                return r;
1008

1009
        j->version = strdup(version);
48✔
1010
        if (!j->version)
48✔
1011
                return log_oom();
×
1012

1013
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
48✔
1014

1015
        r = job_start(j);
48✔
1016
        if (r < 0)
48✔
1017
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1018
        TAKE_PTR(j); /* Avoid job from being killed & freed */
48✔
1019

1020
        return 1;
48✔
1021
}
1022

1023
static int target_method_check_new_finish(
16✔
1024
                sd_bus_message *msg,
1025
                const Job *j,
1026
                sd_json_variant *json,
1027
                sd_bus_error *error) {
1028
        const char *reply;
16✔
1029

1030
        assert(json);
16✔
1031

1032
        sd_json_variant *v = sd_json_variant_by_key(json, "available");
16✔
1033
        if (!v)
16✔
1034
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "check-new", "Missing key 'available'");
×
1035

1036
        if (sd_json_variant_is_null(v))
16✔
1037
                reply = "";
1038
        else
1039
                reply = sd_json_variant_string(v);
×
1040
        if (!reply)
×
1041
                return -EINVAL;
1042

1043
        return sd_bus_reply_method_return(msg, "s", reply);
16✔
1044
}
1045

1046
static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
16✔
1047
        Target *t = ASSERT_PTR(userdata);
16✔
1048
        _cleanup_(job_freep) Job *j = NULL;
16✔
1049
        int r;
16✔
1050

1051
        assert(msg);
16✔
1052

1053
        const char *details[] = {
32✔
1054
                "class", target_class_to_string(t->class),
16✔
1055
                "name", t->name,
16✔
1056
                "offline", "0",
1057
                NULL
1058
        };
1059

1060
        r = bus_verify_polkit_async(
32✔
1061
                        msg,
1062
                        "org.freedesktop.sysupdate1.check",
1063
                        details,
1064
                        &t->manager->polkit_registry,
16✔
1065
                        error);
1066
        if (r < 0)
16✔
1067
                return r;
1068
        if (r == 0)
16✔
1069
                return 1; /* Will call us back */
1070

1071
        r = job_new(JOB_CHECK_NEW, t, msg, target_method_check_new_finish, &j);
16✔
1072
        if (r < 0)
16✔
1073
                return r;
1074

1075
        r = job_start(j);
16✔
1076
        if (r < 0)
16✔
1077
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1078
        TAKE_PTR(j); /* Avoid job from being killed & freed */
16✔
1079

1080
        return 1;
16✔
1081
}
1082

1083
static int target_method_acquire_finished_early(
×
1084
                sd_bus_message *msg,
1085
                const Job *j,
1086
                sd_json_variant *json,
1087
                sd_bus_error *error) {
1088

1089
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1090
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1091
         * specified update is already acquired. */
1092
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1093
                                 "Job exited successfully with no work to do, assume already acquired");
1094
}
1095

1096
static int target_method_acquire_detach(sd_bus_message *msg, const Job *j) {
22✔
1097
        int r;
22✔
1098

1099
        assert(msg);
22✔
1100
        assert(j);
22✔
1101

1102
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
22✔
1103
        if (r < 0)
22✔
1104
                return bus_log_parse_error(r);
×
1105

1106
        return 0;
1107
}
1108

1109
static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
22✔
1110
        Target *t = ASSERT_PTR(userdata);
22✔
1111
        _cleanup_(job_freep) Job *j = NULL;
22✔
1112
        const char *version, *action;
22✔
1113
        uint64_t flags;
22✔
1114
        int r;
22✔
1115

1116
        assert(msg);
22✔
1117

1118
        r = sd_bus_message_read(msg, "st", &version, &flags);
22✔
1119
        if (r < 0)
22✔
1120
                return r;
1121

1122
        if (flags != 0)
22✔
1123
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1124

1125
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1126
         * an update anyway. */
1127
        if (isempty(version))
22✔
1128
                action = "org.freedesktop.sysupdate1.update";
1129
        else {
1130
                if (!version_is_valid(version))
×
1131
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1132

1133
                action = "org.freedesktop.sysupdate1.update-to-version";
1134
        }
1135

1136
        const char *details[] = {
44✔
1137
                "class", target_class_to_string(t->class),
22✔
1138
                "name", t->name,
22✔
1139
                "version", version,
1140
                NULL
1141
        };
1142

1143
        r = bus_verify_polkit_async(
44✔
1144
                        msg,
1145
                        action,
1146
                        details,
1147
                        &t->manager->polkit_registry,
22✔
1148
                        error);
1149
        if (r < 0)
22✔
1150
                return r;
1151
        if (r == 0)
22✔
1152
                return 1; /* Will call us back */
1153

1154
        r = job_new(JOB_ACQUIRE, t, msg, target_method_acquire_finished_early, &j);
22✔
1155
        if (r < 0)
22✔
1156
                return r;
1157
        j->detach_cb = target_method_acquire_detach;
22✔
1158

1159
        j->version = strdup(version);
22✔
1160
        if (!j->version)
22✔
1161
                return -ENOMEM;
1162

1163
        r = job_start(j);
22✔
1164
        if (r < 0)
22✔
1165
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1166
        TAKE_PTR(j);
22✔
1167

1168
        return 1;
22✔
1169
}
1170

1171
static int target_method_install_finished_early(
×
1172
                sd_bus_message *msg,
1173
                const Job *j,
1174
                sd_json_variant *json,
1175
                sd_bus_error *error) {
1176

1177
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1178
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1179
         * specified update is already installed. */
1180
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1181
                                 "Job exited successfully with no work to do, assume already installed");
1182
}
1183

1184
static int target_method_install_detach(sd_bus_message *msg, const Job *j) {
22✔
1185
        int r;
22✔
1186

1187
        assert(msg);
22✔
1188
        assert(j);
22✔
1189

1190
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
22✔
1191
        if (r < 0)
22✔
1192
                return bus_log_parse_error(r);
×
1193

1194
        return 0;
1195
}
1196

1197
static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
22✔
1198
        Target *t = ASSERT_PTR(userdata);
22✔
1199
        _cleanup_(job_freep) Job *j = NULL;
22✔
1200
        const char *version, *action;
22✔
1201
        uint64_t flags;
22✔
1202
        int r;
22✔
1203

1204
        assert(msg);
22✔
1205

1206
        r = sd_bus_message_read(msg, "st", &version, &flags);
22✔
1207
        if (r < 0)
22✔
1208
                return r;
1209

1210
        if (flags != 0)
22✔
1211
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1212

1213
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1214
         * an update anyway. */
1215
        if (isempty(version))
22✔
1216
                action = "org.freedesktop.sysupdate1.update";
1217
        else {
1218
                if (!version_is_valid(version))
×
1219
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1220

1221
                action = "org.freedesktop.sysupdate1.update-to-version";
1222
        }
1223

1224
        const char *details[] = {
44✔
1225
                "class", target_class_to_string(t->class),
22✔
1226
                "name", t->name,
22✔
1227
                "version", version,
1228
                NULL
1229
        };
1230

1231
        r = bus_verify_polkit_async(
44✔
1232
                        msg,
1233
                        action,
1234
                        details,
1235
                        &t->manager->polkit_registry,
22✔
1236
                        error);
1237
        if (r < 0)
22✔
1238
                return r;
1239
        if (r == 0)
22✔
1240
                return 1; /* Will call us back */
1241

1242
        r = job_new(JOB_INSTALL, t, msg, target_method_install_finished_early, &j);
22✔
1243
        if (r < 0)
22✔
1244
                return r;
1245
        j->detach_cb = target_method_install_detach;
22✔
1246

1247
        j->version = strdup(version);
22✔
1248
        if (!j->version)
22✔
1249
                return -ENOMEM;
1250

1251
        r = job_start(j);
22✔
1252
        if (r < 0)
22✔
1253
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1254
        TAKE_PTR(j);
22✔
1255

1256
        return 1;
22✔
1257
}
1258

1259
static int target_method_vacuum_finish(
×
1260
                sd_bus_message *msg,
1261
                const Job *j,
1262
                sd_json_variant *json,
1263
                sd_bus_error *error) {
1264

1265
        sd_json_variant *v;
×
1266
        uint64_t instances, disabled;
×
1267

1268
        assert(json);
×
1269

1270
        v = sd_json_variant_by_key(json, "removed");
×
1271
        if (!v)
×
1272
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'removed'");
×
1273
        if (!sd_json_variant_is_unsigned(v))
×
1274
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'removed' should be an unsigned int");
×
1275
        instances = sd_json_variant_unsigned(v);
×
1276
        assert(instances <= UINT32_MAX);
×
1277

1278
        v = sd_json_variant_by_key(json, "disabledTransfers");
×
1279
        if (!v)
×
1280
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
×
1281
        if (!sd_json_variant_is_unsigned(v))
×
1282
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
×
1283
        disabled = sd_json_variant_unsigned(v);
×
1284
        assert(disabled <= UINT32_MAX);
×
1285

1286
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1287
}
1288

1289
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1290
        Target *t = ASSERT_PTR(userdata);
×
1291
        _cleanup_(job_freep) Job *j = NULL;
×
1292
        int r;
×
1293

1294
        assert(msg);
×
1295

1296
        const char *details[] = {
×
1297
                "class", target_class_to_string(t->class),
×
1298
                "name", t->name,
×
1299
                NULL
1300
        };
1301

1302
        r = bus_verify_polkit_async(
×
1303
                msg,
1304
                "org.freedesktop.sysupdate1.vacuum",
1305
                details,
1306
                &t->manager->polkit_registry,
×
1307
                error);
1308
        if (r < 0)
×
1309
                return r;
1310
        if (r == 0)
×
1311
                return 1; /* Will call us back */
1312

1313
        r = job_new(JOB_VACUUM, t, msg, target_method_vacuum_finish, &j);
×
1314
        if (r < 0)
×
1315
                return r;
1316

1317
        r = job_start(j);
×
1318
        if (r < 0)
×
1319
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1320
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1321

1322
        return 1;
×
1323
}
1324

1325
static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
38✔
1326
        Target *t = ASSERT_PTR(userdata);
38✔
1327
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
38✔
1328
        sd_json_variant *version_json;
38✔
1329
        int r;
38✔
1330

1331
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
38✔
1332
        if (r < 0)
38✔
1333
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1334

1335
        version_json = sd_json_variant_by_key(v, "current");
38✔
1336
        if (!version_json)
38✔
1337
                version_json = sd_json_variant_by_key(v, "current+pending");
×
1338
        if (!version_json)
×
1339
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current' or 'current+pending'");
×
1340

1341
        if (sd_json_variant_is_null(version_json))
38✔
1342
                return sd_bus_reply_method_return(msg, "s", "");
2✔
1343

1344
        if (!sd_json_variant_is_string(version_json))
36✔
1345
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'current' should be a string");
×
1346

1347
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
36✔
1348
}
1349

1350
static int target_get_appstream(Target *t, char ***ret) {
×
1351
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1352
        sd_json_variant *appstream_url_json;
×
1353
        int r;
×
1354

1355
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
×
1356
        if (r < 0)
×
1357
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1358

1359
        appstream_url_json = sd_json_variant_by_key(v, "appstreamUrls");
×
1360
        if (!appstream_url_json)
×
1361
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'appstreamUrls'");
×
1362

1363
        r = sd_json_variant_strv(appstream_url_json, ret);
×
1364
        if (r < 0)
×
1365
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'appstreamUrls' should be strv");
×
1366

1367
        return 0;
1368
}
1369

1370
static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1371
        Target *t = ASSERT_PTR(userdata);
×
1372
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1373
        _cleanup_strv_free_ char **appstream_urls = NULL;
×
1374
        int r;
×
1375

1376
        r = target_get_appstream(t, &appstream_urls);
×
1377
        if (r < 0)
×
1378
                return r;
1379

1380
        r = sd_bus_message_new_method_return(msg, &reply);
×
1381
        if (r < 0)
×
1382
                return r;
1383

1384
        r = sd_bus_message_append_strv(reply, appstream_urls);
×
1385
        if (r < 0)
×
1386
                return r;
1387

1388
        return sd_bus_message_send(reply);
×
1389
}
1390

1391
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1392
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1393
        _cleanup_strv_free_ char **features = NULL;
×
1394
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1395
        Target *t = ASSERT_PTR(userdata);
×
1396
        sd_json_variant *v;
×
1397
        uint64_t flags;
×
1398
        int r;
×
1399

1400
        assert(msg);
×
1401

1402
        r = sd_bus_message_read(msg, "t", &flags);
×
1403
        if (r < 0)
×
1404
                return r;
1405
        if (flags != 0)
×
1406
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1407

1408
        r = sysupdate_run_simple(&json, t, "features", NULL);
×
1409
        if (r < 0)
×
1410
                return r;
1411

1412
        v = sd_json_variant_by_key(json, "features");
×
1413
        if (!v)
×
1414
                return -EINVAL;
1415
        r = sd_json_variant_strv(v, &features);
×
1416
        if (r < 0)
×
1417
                return r;
1418

1419
        r = sd_bus_message_new_method_return(msg, &reply);
×
1420
        if (r < 0)
×
1421
                return r;
1422

1423
        r = sd_bus_message_append_strv(reply, features);
×
1424
        if (r < 0)
×
1425
                return r;
1426

1427
        return sd_bus_message_send(reply);
×
1428
}
1429

1430
static bool feature_name_is_valid(const char *name) {
×
1431
        if (isempty(name))
×
1432
                return false;
1433

1434
        if (!ascii_is_valid(name))
×
1435
                return false;
1436

1437
        if (!filename_is_valid(strjoina(name, ".feature.d")))
×
1438
                return false;
×
1439

1440
        return true;
1441
}
1442

1443
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1444
        Target *t = ASSERT_PTR(userdata);
×
1445
        _cleanup_(job_freep) Job *j = NULL;
×
1446
        const char *feature;
×
1447
        uint64_t flags;
×
1448
        int r;
×
1449

1450
        assert(msg);
×
1451

1452
        r = sd_bus_message_read(msg, "st", &feature, &flags);
×
1453
        if (r < 0)
×
1454
                return r;
1455

1456
        if (!feature_name_is_valid(feature))
×
1457
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid feature name");
×
1458

1459
        if (flags != 0)
×
1460
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1461

1462
        r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
×
1463
        if (r < 0)
×
1464
                return r;
1465

1466
        j->feature = strdup(feature);
×
1467
        if (!j->feature)
×
1468
                return log_oom();
×
1469

1470
        r = job_start(j);
×
1471
        if (r < 0)
×
1472
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1473
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1474

1475
        return 1;
×
1476
}
1477

1478
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1479
        _cleanup_free_ char *feature_ext = NULL;
×
1480
        Target *t = ASSERT_PTR(userdata);
×
1481
        const char *feature;
×
1482
        uint64_t flags;
×
1483
        int32_t enabled;
×
1484
        int r;
×
1485

1486
        assert(msg);
×
1487

1488
        if (t->class != TARGET_HOST)
×
1489
                return sd_bus_reply_method_errorf(msg,
×
1490
                                                  SD_BUS_ERROR_NOT_SUPPORTED,
1491
                                                  "For now, features can only be managed on the host system.");
1492

1493
        r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
×
1494
        if (r < 0)
×
1495
                return r;
1496
        if (!feature_name_is_valid(feature))
×
1497
                return sd_bus_reply_method_errorf(msg,
×
1498
                                                  SD_BUS_ERROR_INVALID_ARGS,
1499
                                                  "The specified feature is invalid");
1500
        if (flags != 0)
×
1501
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1502

1503
        if (!endswith(feature, ".feature")) {
×
1504
                feature_ext = strjoin(feature, ".feature");
×
1505
                if (!feature_ext)
×
1506
                        return -ENOMEM;
1507
                feature = feature_ext;
×
1508
        }
1509

1510
        const char *details[] = {
×
1511
                "class", target_class_to_string(t->class),
×
1512
                "name", t->name,
×
1513
                "feature", feature,
1514
                "enabled", enabled >= 0 ? true_false(enabled) : "unset",
×
1515
                NULL
1516
        };
1517

1518
        r = bus_verify_polkit_async(
×
1519
                msg,
1520
                "org.freedesktop.sysupdate1.manage-features",
1521
                details,
1522
                &t->manager->polkit_registry,
×
1523
                error);
1524
        if (r < 0)
×
1525
                return r;
1526
        if (r == 0)
×
1527
                return 1; /* Will call us back */
1528

1529
        /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
1530
        if (enabled < 0) { /* Reset -> delete the drop-in file */
×
1531
                _cleanup_free_ char *path = NULL;
×
1532

1533
                r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
×
1534
                if (r < 0)
×
1535
                        return r;
1536

1537
                if (unlink(path) < 0)
×
1538
                        return -errno;
×
1539
        } else { /* otherwise, create the drop-in with the right settings */
1540
                r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
×
1541
                                         "# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
1542
                                         "[Feature]\n"
1543
                                         "Enabled=%s\n",
1544
                                         yes_no(enabled));
1545
                if (r < 0)
×
1546
                        return r;
1547
        }
1548

1549
        return sd_bus_reply_method_return(msg, NULL);
×
1550
}
1551

1552
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
76✔
1553
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
76✔
1554
        _cleanup_strv_free_ char **components = NULL;
76✔
1555
        sd_json_variant *v;
76✔
1556
        bool have_default;
76✔
1557
        int r;
76✔
1558

1559
        r = sysupdate_run_simple(&json, t, "components", NULL);
76✔
1560
        if (r < 0)
76✔
1561
                return log_debug_errno(r, "Failed to run 'systemd-sysupdate components': %m");
×
1562

1563
        v = sd_json_variant_by_key(json, "default");
76✔
1564
        if (!v)
76✔
1565
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'default'");
×
1566
        have_default = sd_json_variant_boolean(v);
76✔
1567

1568
        v = sd_json_variant_by_key(json, "components");
76✔
1569
        if (!v)
76✔
1570
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'components'");
×
1571
        r = sd_json_variant_strv(v, &components);
76✔
1572
        if (r < 0)
76✔
1573
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Key 'components' should be a strv");
×
1574

1575
        if (ret_components)
76✔
1576
                *ret_components = TAKE_PTR(components);
76✔
1577
        if (ret_have_default)
76✔
1578
                *ret_have_default = have_default;
76✔
1579
        return 0;
1580
}
1581

1582
static int manager_ensure_targets(Manager *m);
1583

1584
static int target_object_find(
192✔
1585
                sd_bus *bus,
1586
                const char *path,
1587
                const char *iface,
1588
                void *userdata,
1589
                void **found,
1590
                sd_bus_error *error) {
1591

1592
        Manager *m = ASSERT_PTR(userdata);
192✔
1593
        Target *t;
192✔
1594
        _cleanup_free_ char *e = NULL;
192✔
1595
        const char *p;
192✔
1596
        int r;
192✔
1597

1598
        assert(bus);
192✔
1599
        assert(path);
192✔
1600
        assert(found);
192✔
1601

1602
        p = startswith(path, "/org/freedesktop/sysupdate1/target/");
192✔
1603
        if (!p)
192✔
1604
                return 0;
1605

1606
        e = bus_label_unescape(p);
192✔
1607
        if (!e)
192✔
1608
                return -ENOMEM;
1609

1610
        r = manager_ensure_targets(m);
192✔
1611
        if (r < 0)
192✔
1612
                return r;
1613

1614
        t = hashmap_get(m->targets, e);
192✔
1615
        if (!t)
192✔
1616
                return 0;
1617

1618
        *found = t;
192✔
1619
        return 1;
192✔
1620
}
1621

1622
static char *target_bus_path(Target *t) {
76✔
1623
        _cleanup_free_ char *e = NULL;
76✔
1624

1625
        assert(t);
76✔
1626

1627
        e = bus_label_escape(t->id);
76✔
1628
        if (!e)
76✔
1629
                return NULL;
1630

1631
        return strjoin("/org/freedesktop/sysupdate1/target/", e);
76✔
1632
}
1633

1634
static int target_node_enumerator(
×
1635
                sd_bus *bus,
1636
                const char *path,
1637
                void *userdata,
1638
                char ***nodes,
1639
                sd_bus_error *error) {
1640

1641
        _cleanup_strv_free_ char **l = NULL;
×
1642
        Manager *m = ASSERT_PTR(userdata);
×
1643
        Target *t;
×
1644
        unsigned k = 0;
×
1645
        int r;
×
1646

1647
        r = manager_ensure_targets(m);
×
1648
        if (r < 0)
×
1649
                return r;
1650

1651
        l = new0(char*, hashmap_size(m->targets) + 1);
×
1652
        if (!l)
×
1653
                return -ENOMEM;
1654

1655
        HASHMAP_FOREACH(t, m->targets) {
×
1656
                l[k] = target_bus_path(t);
×
1657
                if (!l[k])
×
1658
                        return -ENOMEM;
×
1659
                k++;
×
1660
        }
1661

1662
        *nodes = TAKE_PTR(l);
×
1663
        return 1;
×
1664
}
1665

1666
static const sd_bus_vtable target_vtable[] = {
1667
        SD_BUS_VTABLE_START(0),
1668

1669
        SD_BUS_PROPERTY("Class", "s", target_property_get_class,
1670
                        offsetof(Target, class), SD_BUS_VTABLE_PROPERTY_CONST),
1671
        SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Target, name),
1672
                        SD_BUS_VTABLE_PROPERTY_CONST),
1673
        SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Target, path),
1674
                        SD_BUS_VTABLE_PROPERTY_CONST),
1675

1676
        SD_BUS_METHOD_WITH_ARGS("List",
1677
                                SD_BUS_ARGS("t", flags),
1678
                                SD_BUS_RESULT("as", versions),
1679
                                target_method_list,
1680
                                SD_BUS_VTABLE_UNPRIVILEGED),
1681

1682
        SD_BUS_METHOD_WITH_ARGS("Describe",
1683
                                SD_BUS_ARGS("s", version, "t", flags),
1684
                                SD_BUS_RESULT("s", json),
1685
                                target_method_describe,
1686
                                SD_BUS_VTABLE_UNPRIVILEGED),
1687

1688
        SD_BUS_METHOD_WITH_ARGS("CheckNew",
1689
                                SD_BUS_NO_ARGS,
1690
                                SD_BUS_RESULT("s", new_version),
1691
                                target_method_check_new,
1692
                                SD_BUS_VTABLE_UNPRIVILEGED),
1693

1694
        SD_BUS_METHOD_WITH_ARGS("Acquire",
1695
                                SD_BUS_ARGS("s", new_version, "t", flags),
1696
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1697
                                target_method_acquire,
1698
                                SD_BUS_VTABLE_UNPRIVILEGED),
1699

1700
        SD_BUS_METHOD_WITH_ARGS("Install",
1701
                                SD_BUS_ARGS("s", new_version, "t", flags),
1702
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1703
                                target_method_install,
1704
                                SD_BUS_VTABLE_UNPRIVILEGED),
1705

1706
        SD_BUS_METHOD_WITH_ARGS("Vacuum",
1707
                                SD_BUS_NO_ARGS,
1708
                                SD_BUS_RESULT("u", instances, "u", disabled_transfers),
1709
                                target_method_vacuum,
1710
                                SD_BUS_VTABLE_UNPRIVILEGED),
1711

1712
        SD_BUS_METHOD_WITH_ARGS("GetAppStream",
1713
                                SD_BUS_NO_ARGS,
1714
                                SD_BUS_RESULT("as", appstream),
1715
                                target_method_get_appstream,
1716
                                SD_BUS_VTABLE_UNPRIVILEGED),
1717

1718
        SD_BUS_METHOD_WITH_ARGS("GetVersion",
1719
                                SD_BUS_NO_ARGS,
1720
                                SD_BUS_RESULT("s", version),
1721
                                target_method_get_version,
1722
                                SD_BUS_VTABLE_UNPRIVILEGED),
1723

1724
        SD_BUS_METHOD_WITH_ARGS("ListFeatures",
1725
                                SD_BUS_ARGS("t", flags),
1726
                                SD_BUS_RESULT("as", features),
1727
                                target_method_list_features,
1728
                                SD_BUS_VTABLE_UNPRIVILEGED),
1729

1730
        SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
1731
                                SD_BUS_ARGS("s", feature, "t", flags),
1732
                                SD_BUS_RESULT("s", json),
1733
                                target_method_describe_feature,
1734
                                SD_BUS_VTABLE_UNPRIVILEGED),
1735

1736
        SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
1737
                                SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
1738
                                SD_BUS_NO_RESULT,
1739
                                target_method_set_feature_enabled,
1740
                                SD_BUS_VTABLE_UNPRIVILEGED),
1741

1742
        SD_BUS_VTABLE_END
1743
};
1744

1745
static const BusObjectImplementation target_object = {
1746
        "/org/freedesktop/sysupdate1/target",
1747
        "org.freedesktop.sysupdate1.Target",
1748
        .fallback_vtables = BUS_FALLBACK_VTABLES({target_vtable, target_object_find}),
1749
        .node_enumerator = target_node_enumerator,
1750
};
1751

1752
static Manager *manager_free(Manager *m) {
1✔
1753
        if (!m)
1✔
1754
                return NULL;
1755

1756
        hashmap_free(m->targets);
1✔
1757
        hashmap_free(m->jobs);
1✔
1758

1759
        m->bus = sd_bus_flush_close_unref(m->bus);
1✔
1760
        free(m->notify_socket_path);
1✔
1761
        sd_event_unref(m->event);
1✔
1762

1763
        return mfree(m);
1✔
1764
}
1765

1766
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1767

1768
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
283✔
1769
        Manager *m = ASSERT_PTR(userdata);
283✔
1770
        int r;
283✔
1771

1772
        assert(fd >= 0);
283✔
1773

1774
        _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
×
1775
        _cleanup_free_ char *buf = NULL;
283✔
1776
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
283✔
1777
        if (r == -EAGAIN)
283✔
1778
                return 0;
1779
        if (r < 0)
283✔
1780
                return r;
1781

1782
        Job *j;
283✔
1783
        HASHMAP_FOREACH(j, m->jobs) {
361✔
1784
                PidRef child_pidref = PIDREF_NULL;
361✔
1785

1786
                r = event_source_get_child_pidref(j->child, &child_pidref);
361✔
1787
                if (r < 0)
361✔
1788
                        return log_warning_errno(r, "Failed to get child pidref: %m");
×
1789

1790
                if (pidref_equal(&sender_pidref, &child_pidref))
361✔
1791
                        break;
1792
        }
1793
        if (!j) {
283✔
1794
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1795
                return 0;
×
1796
        }
1797

1798
        char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
283✔
1799
        char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
283✔
1800
        char *errno_str = find_line_startswith(buf, "ERRNO=");
283✔
1801
        const char *ready = find_line_startswith(buf, "READY=1");
283✔
1802

1803
        if (version)
283✔
1804
                job_on_version(j, truncate_nl(version));
44✔
1805

1806
        if (progress)
283✔
1807
                job_on_progress(j, truncate_nl(progress));
79✔
1808

1809
        if (errno_str)
283✔
1810
                job_on_errno(j, truncate_nl(errno_str));
×
1811

1812
        /* Should come last, since this might actually detach the job */
1813
        if (ready)
283✔
1814
                job_on_ready(j);
44✔
1815

1816
        return 0;
1817
}
1818

1819
static int manager_new(Manager **ret) {
1✔
1820
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
1821
        int r;
1✔
1822

1823
        assert(ret);
1✔
1824

1825
        m = new(Manager, 1);
1✔
1826
        if (!m)
1✔
1827
                return -ENOMEM;
1828

1829
        *m = (Manager) {
1✔
1830
                .runtime_scope = RUNTIME_SCOPE_SYSTEM,
1831
        };
1832

1833
        r = sd_event_default(&m->event);
1✔
1834
        if (r < 0)
1✔
1835
                return r;
1836

1837
        (void) sd_event_set_watchdog(m->event, true);
1✔
1838

1839
        r = sd_event_set_signal_exit(m->event, true);
1✔
1840
        if (r < 0)
1✔
1841
                return r;
1842

1843
        r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK,
1✔
1844
                                sigrtmin18_handler, NULL);
1845
        if (r < 0)
1✔
1846
                return r;
1847

1848
        r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
1✔
1849
        if (r < 0)
1✔
1850
                log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m");
×
1851

1852
        r = sd_bus_default_system(&m->bus);
1✔
1853
        if (r < 0)
1✔
1854
                return r;
1855

1856
        r = notify_socket_prepare(
2✔
1857
                        m->event,
1858
                        SD_EVENT_PRIORITY_NORMAL - 1, /* Make this processed before worker exit. */
1859
                        manager_on_notify,
1860
                        m,
1861
                        &m->notify_socket_path);
1✔
1862
        if (r < 0)
1✔
1863
                return r;
1864

1865
        *ret = TAKE_PTR(m);
1✔
1866
        return 0;
1✔
1867
}
1868

1869
static int manager_enumerate_image_class(Manager *m, TargetClass class) {
304✔
1870
        _cleanup_hashmap_free_ Hashmap *images = NULL;
304✔
1871
        Image *image;
304✔
1872
        int r;
304✔
1873

1874
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, &images);
304✔
1875
        if (r < 0)
304✔
1876
                return r;
1877

1878
        HASHMAP_FOREACH(image, images) {
380✔
1879
                _cleanup_(target_freep) Target *t = NULL;
76✔
1880
                bool have = false;
76✔
1881

1882
                if (image_is_host(image))
76✔
1883
                        continue; /* We already enroll the host ourselves */
76✔
1884

1885
                if (image->type == IMAGE_MSTACK)
×
1886
                        continue; /* systemd-sysupdate doesn't support mstack images yet */
×
1887

1888
                r = target_new(m, class, image->name, image->path, &t);
×
1889
                if (r < 0)
×
1890
                        return r;
1891
                t->image_type = image->type;
×
1892

1893
                r = target_list_components(t, NULL, &have);
×
1894
                if (r < 0)
×
1895
                        return r;
1896
                if (!have) {
×
1897
                        log_debug("Skipping %s because it has no default component", image->path);
×
1898
                        continue;
×
1899
                }
1900

1901
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
1902
                if (r < 0)
×
1903
                        return r;
1904
                TAKE_PTR(t);
×
1905
        }
1906

1907
        return 0;
304✔
1908
}
1909

1910
static int manager_enumerate_components(Manager *m) {
76✔
1911
        _cleanup_strv_free_ char **components = NULL;
76✔
1912
        bool have_default;
76✔
1913
        int r;
76✔
1914

1915
        r = target_list_components(NULL, &components, &have_default);
76✔
1916
        if (r < 0)
76✔
1917
                return r;
1918

1919
        if (have_default) {
76✔
1920
                _cleanup_(target_freep) Target *t = NULL;
×
1921

1922
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
76✔
1923
                if (r < 0)
76✔
1924
                        return r;
1925

1926
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
76✔
1927
                if (r < 0)
76✔
1928
                        return r;
1929
                TAKE_PTR(t);
76✔
1930
        }
1931

1932
        STRV_FOREACH(component, components) {
100✔
1933
                _cleanup_free_ char *path = NULL;
24✔
1934
                _cleanup_(target_freep) Target *t = NULL;
24✔
1935

1936
                path = strjoin("sysupdate.", *component, ".d");
24✔
1937
                if (!path)
24✔
1938
                        return -ENOMEM;
1939

1940
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
24✔
1941
                if (r < 0)
24✔
1942
                        return r;
1943

1944
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
24✔
1945
                if (r < 0)
24✔
1946
                        return r;
1947
                TAKE_PTR(t);
24✔
1948
        }
1949

1950
        return 0;
1951
}
1952

1953
static int manager_enumerate_targets(Manager *m) {
76✔
1954
        static const TargetClass discoverable_classes[] = {
76✔
1955
                TARGET_MACHINE,
1956
                TARGET_PORTABLE,
1957
                TARGET_SYSEXT,
1958
                TARGET_CONFEXT,
1959
        };
1960
        int r;
76✔
1961

1962
        assert(m);
76✔
1963

1964
        FOREACH_ARRAY(class, discoverable_classes, ELEMENTSOF(discoverable_classes)) {
380✔
1965
                r = manager_enumerate_image_class(m, *class);
304✔
1966
                if (r < 0)
304✔
1967
                        log_warning_errno(r, "Failed to enumerate %ss, ignoring: %m",
304✔
1968
                                          target_class_to_string(*class));
1969
        }
1970

1971
        r = manager_enumerate_components(m);
76✔
1972
        if (r < 0)
76✔
1973
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1974

1975
        return 0;
76✔
1976
}
1977

1978
static int manager_ensure_targets(Manager *m) {
260✔
1979
        assert(m);
260✔
1980

1981
        if (!hashmap_isempty(m->targets))
260✔
1982
                return 0;
1983

1984
        return manager_enumerate_targets(m);
76✔
1985
}
1986

1987
static int method_list_targets(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
68✔
1988
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
68✔
1989
        Manager *m = ASSERT_PTR(userdata);
68✔
1990
        Target *t;
68✔
1991
        int r;
68✔
1992

1993
        assert(msg);
68✔
1994

1995
        r = manager_ensure_targets(m);
68✔
1996
        if (r < 0)
68✔
1997
                return r;
1998

1999
        r = sd_bus_message_new_method_return(msg, &reply);
68✔
2000
        if (r < 0)
68✔
2001
                return r;
2002

2003
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
68✔
2004
        if (r < 0)
68✔
2005
                return r;
2006

2007
        HASHMAP_FOREACH(t, m->targets) {
144✔
2008
                _cleanup_free_ char *bus_path = NULL;
76✔
2009

2010
                bus_path = target_bus_path(t);
76✔
2011
                if (!bus_path)
76✔
2012
                        return -ENOMEM;
2013

2014
                r = sd_bus_message_append(reply, "(sso)",
76✔
2015
                                          target_class_to_string(t->class),
2016
                                          t->name,
76✔
2017
                                          bus_path);
2018
                if (r < 0)
76✔
2019
                        return r;
2020
        }
2021

2022
        r = sd_bus_message_close_container(reply);
68✔
2023
        if (r < 0)
68✔
2024
                return r;
2025

2026
        return sd_bus_message_send(reply);
68✔
2027
}
2028

2029
static int method_list_jobs(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
2030
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
2031
        Manager *m = ASSERT_PTR(userdata);
×
2032
        Job *j;
×
2033
        int r;
×
2034

2035
        assert(msg);
×
2036

2037
        r = sd_bus_message_new_method_return(msg, &reply);
×
2038
        if (r < 0)
×
2039
                return r;
2040

2041
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
2042
        if (r < 0)
×
2043
                return r;
2044

2045
        HASHMAP_FOREACH(j, m->jobs) {
×
2046
                r = sd_bus_message_append(reply, "(tsuo)",
×
2047
                                          j->id,
2048
                                          job_type_to_string(j->type),
2049
                                          j->progress_percent,
2050
                                          j->object_path);
×
2051
                if (r < 0)
×
2052
                        return r;
×
2053
        }
2054

2055
        r = sd_bus_message_close_container(reply);
×
2056
        if (r < 0)
×
2057
                return r;
2058

2059
        return sd_bus_message_send(reply);
×
2060
}
2061

2062
static int method_list_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
2063
        _cleanup_strv_free_ char **urls = NULL;
×
2064
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
2065
        Manager *m = ASSERT_PTR(userdata);
×
2066
        Target *t;
×
2067
        int r;
×
2068

2069
        assert(msg);
×
2070

2071
        r = manager_ensure_targets(m);
×
2072
        if (r < 0)
×
2073
                return r;
2074

2075
        HASHMAP_FOREACH(t, m->targets) {
×
2076
                char **target_appstream;
×
2077

2078
                r = target_get_appstream(t, &target_appstream);
×
2079
                if (r < 0)
×
2080
                        return r;
×
2081

2082
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates= */ true);
×
2083
                if (r < 0)
×
2084
                        return r;
2085
        }
2086

2087
        r = sd_bus_message_new_method_return(msg, &reply);
×
2088
        if (r < 0)
×
2089
                return r;
2090

2091
        r = sd_bus_message_append_strv(reply, urls);
×
2092
        if (r < 0)
×
2093
                return r;
2094

2095
        return sd_bus_message_send(reply);
×
2096
}
2097

2098
static const sd_bus_vtable manager_vtable[] = {
2099
        SD_BUS_VTABLE_START(0),
2100

2101
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
2102
                                SD_BUS_NO_ARGS,
2103
                                SD_BUS_RESULT("a(sso)", targets),
2104
                                method_list_targets,
2105
                                SD_BUS_VTABLE_UNPRIVILEGED),
2106

2107
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
2108
                                SD_BUS_NO_ARGS,
2109
                                SD_BUS_RESULT("a(tsuo)", jobs),
2110
                                method_list_jobs,
2111
                                SD_BUS_VTABLE_UNPRIVILEGED),
2112

2113
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
2114
                                SD_BUS_NO_ARGS,
2115
                                SD_BUS_RESULT("as", urls),
2116
                                method_list_appstream,
2117
                                SD_BUS_VTABLE_UNPRIVILEGED),
2118

2119
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2120
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2121
                                0),
2122

2123
        SD_BUS_VTABLE_END
2124
};
2125

2126
static const BusObjectImplementation manager_object = {
2127
        "/org/freedesktop/sysupdate1",
2128
        "org.freedesktop.sysupdate1.Manager",
2129
        .vtables = BUS_VTABLES(manager_vtable),
2130
        .children = BUS_IMPLEMENTATIONS(&job_object, &target_object),
2131
};
2132

2133
static int manager_add_bus_objects(Manager *m) {
1✔
2134
        int r;
1✔
2135

2136
        assert(m);
1✔
2137

2138
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2139
        if (r < 0)
1✔
2140
                return r;
2141

2142
        r = bus_log_control_api_register(m->bus);
1✔
2143
        if (r < 0)
1✔
2144
                return r;
2145

2146
        r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.sysupdate1", 0, NULL, NULL);
1✔
2147
        if (r < 0)
1✔
2148
                return log_error_errno(r, "Failed to request name: %m");
×
2149

2150
        r = sd_bus_attach_event(m->bus, m->event, 0);
1✔
2151
        if (r < 0)
1✔
2152
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
2153

2154
        return 0;
2155
}
2156

2157
static bool manager_is_idle(void *userdata) {
661✔
2158
        Manager *m = ASSERT_PTR(userdata);
661✔
2159

2160
        return hashmap_isempty(m->jobs);
661✔
2161
}
2162

2163
static void manager_check_idle(Manager *m) {
116✔
2164
        assert(m);
116✔
2165

2166
        if (!hashmap_isempty(m->jobs))
116✔
2167
                return;
2168

2169
        hashmap_clear(m->targets);
76✔
2170
        log_debug("Cleared target cache");
76✔
2171
}
2172

2173
static int manager_run(Manager *m) {
1✔
2174
        assert(m);
1✔
2175

2176
        return bus_event_loop_with_idle(m->event,
1✔
2177
                                        m->bus,
2178
                                        "org.freedesktop.sysupdate1",
2179
                                        DEFAULT_EXIT_USEC,
2180
                                        manager_is_idle,
2181
                                        m);
2182
}
2183

2184
static int run(int argc, char *argv[]) {
1✔
2185
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
2186
        int r;
1✔
2187

2188
        log_setup();
1✔
2189

2190
        r = service_parse_argv("systemd-sysupdated.service",
2✔
2191
                               "System update management service.",
2192
                               BUS_IMPLEMENTATIONS(&manager_object,
1✔
2193
                                                   &log_control_object),
2194
                               /* runtime_scope= */ NULL,
2195
                               argc, argv);
2196
        if (r <= 0)
1✔
2197
                return r;
2198

2199
        umask(0022);
1✔
2200

2201
        r = manager_new(&m);
1✔
2202
        if (r < 0)
1✔
2203
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2204

2205
        r = manager_add_bus_objects(m);
1✔
2206
        if (r < 0)
1✔
2207
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2208

2209
        r = manager_run(m);
1✔
2210
        if (r < 0)
1✔
2211
                return log_error_errno(r, "Failed to run event loop: %m");
×
2212

2213
        return 0;
2214
}
2215

2216
DEFINE_MAIN_FUNCTION(run);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc