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

systemd / systemd / 28272947092

26 Jun 2026 08:38PM UTC coverage: 72.893% (+0.2%) from 72.703%
28272947092

push

github

poettering
sysupdate: Address review feedback on CheckNew varlink scaffolding

Follow-up to #42422:

 - Rename process_image() to context_process_image(), since it now
   operates on a Context object.
 - Use IN_SET() in image_type_can_sysupdate() instead of a switch.
 - Name the return parameters of context_list_components() ret_xyz, per
   our coding style.
 - Drop a redundant "else" after a return in vl_method_check_new().

9 of 11 new or added lines in 1 file covered. (81.82%)

12567 existing lines in 144 files now uncovered.

341026 of 467845 relevant lines covered (72.89%)

1339355.33 hits per line

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

63.58
/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 "format-util.h"
28
#include "hashmap.h"
29
#include "log.h"
30
#include "main-func.h"
31
#include "memfd-util.h"
32
#include "notify-recv.h"
33
#include "os-util.h"
34
#include "parse-util.h"
35
#include "path-util.h"
36
#include "pidref.h"
37
#include "process-util.h"
38
#include "runtime-scope.h"
39
#include "service-util.h"
40
#include "signal-util.h"
41
#include "string-table.h"
42
#include "strv.h"
43
#include "sysupdate-target.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 struct Target {
69
        Manager *manager;
70

71
        TargetClass class;
72
        char *name;
73
        char *path;
74

75
        char *id;
76
        ImageType image_type;
77
        bool busy;
78
} Target;
79

80
typedef enum JobType {
81
        JOB_LIST,
82
        JOB_DESCRIBE,
83
        JOB_CHECK_NEW,
84
        JOB_ACQUIRE,
85
        JOB_INSTALL,
86
        JOB_VACUUM,
87
        JOB_DESCRIBE_FEATURE,
88
        _JOB_TYPE_MAX,
89
        _JOB_TYPE_INVALID = -EINVAL,
90
} JobType;
91

92
typedef struct Job Job;
93

94
typedef int (*JobReady)(sd_bus_message *msg, const Job *job);
95
typedef int (*JobComplete)(sd_bus_message *msg, const Job *job, sd_json_variant *response, sd_bus_error *error);
96

97
struct Job {
98
        Manager *manager;
99
        Target *target;
100

101
        uint64_t id;
102
        char *object_path;
103

104
        JobType type;
105
        bool offline;
106
        char *version; /* Passed into sysupdate for JOB_DESCRIBE, JOB_ACQUIRE and JOB_INSTALL */
107
        char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
108

109
        unsigned progress_percent;
110

111
        sd_event_source *child;
112
        int stdout_fd;
113
        int status_errno;
114
        unsigned n_cancelled;
115

116
        sd_json_variant *json;
117

118
        JobComplete complete_cb; /* Callback called on job exit */
119
        sd_bus_message *dbus_msg;
120
        JobReady detach_cb; /* Callback called when job has started.  Detaches the job to run in the background */
121
};
122

123
static const char* const job_type_table[_JOB_TYPE_MAX] = {
124
        [JOB_LIST]             = "list",
125
        [JOB_DESCRIBE]         = "describe",
126
        [JOB_CHECK_NEW]        = "check-new",
127
        [JOB_ACQUIRE]          = "acquire",
128
        [JOB_INSTALL]          = "install",
129
        [JOB_VACUUM]           = "vacuum",
130
        [JOB_DESCRIBE_FEATURE] = "describe-feature",
131
};
132

UNCOV
133
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
134

135
static Job *job_free(Job *j) {
252✔
136
        if (!j)
252✔
137
                return NULL;
138

139
        if (j->manager)
252✔
140
                assert_se(hashmap_remove(j->manager->jobs, &j->id) == j);
252✔
141

142
        free(j->object_path);
252✔
143
        free(j->version);
252✔
144
        free(j->feature);
252✔
145

146
        sd_json_variant_unref(j->json);
252✔
147

148
        sd_bus_message_unref(j->dbus_msg);
252✔
149

150
        sd_event_source_disable_unref(j->child);
252✔
151
        safe_close(j->stdout_fd);
252✔
152

153
        return mfree(j);
252✔
154
}
155

156
DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free);
504✔
UNCOV
157
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops,
×
158
                                              uint64_t, uint64_hash_func, uint64_compare_func,
159
                                              Job, job_free);
160

161
static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) {
252✔
162
        _cleanup_(job_freep) Job *j = NULL;
252✔
163
        int r;
252✔
164

165
        assert(t);
252✔
166
        assert(ret);
252✔
167

168
        j = new(Job, 1);
252✔
169
        if (!j)
252✔
170
                return -ENOMEM;
171

172
        *j = (Job) {
504✔
173
                .type = type,
174
                .target = t,
175
                .id = t->manager->last_job_id + 1,
252✔
176
                .stdout_fd = -EBADF,
177
                .complete_cb = complete_cb,
178
                .dbus_msg = sd_bus_message_ref(msg),
252✔
179
        };
180

181
        if (asprintf(&j->object_path, "/org/freedesktop/sysupdate1/job/_%" PRIu64, j->id) < 0)
252✔
182
                return -ENOMEM;
183

184
        r = hashmap_ensure_put(&t->manager->jobs, &job_hash_ops, &j->id, j);
252✔
185
        if (r < 0)
252✔
186
                return r;
187

188
        j->manager = t->manager;
252✔
189

190
        t->manager->last_job_id = j->id;
252✔
191

192
        *ret = TAKE_PTR(j);
252✔
193
        return 0;
252✔
194
}
195

196
/* Is Job in the set of jobs which require Target.busy to be set so they run exclusively? */
197
static bool job_requires_busy(Job *j) {
756✔
198
        return IN_SET(j->type, JOB_ACQUIRE, JOB_INSTALL, JOB_VACUUM);
756✔
199
}
200

201
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
240✔
202
        _cleanup_close_ int fd = ASSERT_FD(_fd); /* Take ownership of the passed fd */
480✔
203
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
240✔
204
        struct stat st;
240✔
205
        int r;
240✔
206

207
        assert(ret);
240✔
208

209
        if (fstat(fd, &st) < 0)
240✔
UNCOV
210
                return log_debug_errno(errno, "Failed to stat stdout fd: %m");
×
211

212
        assert(S_ISREG(st.st_mode));
240✔
213

214
        if (st.st_size == 0) {
240✔
215
                log_warning("No output from child job, ignoring");
48✔
216
                return 0;
217
        }
218

219
        r = sd_json_parse_fd(
384✔
220
                        /* path= */ "stdout",
221
                        TAKE_FD(fd),
192✔
222
                        SD_JSON_PARSE_DONATE_FD|SD_JSON_PARSE_SEEK0,
223
                        &v,
224
                        /* reterr_line= */ NULL,
225
                        /* reterr_column= */ NULL);
226
        if (r < 0)
192✔
UNCOV
227
                return log_debug_errno(r, "Failed to parse child output as JSON: %m");
×
228

229
        *ret = TAKE_PTR(v);
192✔
230
        return 0;
192✔
231
}
232

233
static void job_on_ready(Job *j) {
100✔
234
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *msg = NULL;
100✔
235
        int r;
100✔
236

237
        assert(j);
100✔
238

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

244
        if (!j->detach_cb)
100✔
245
                return;
246

247
        log_debug("Got READY=1 from job %" PRIu64", detaching.", j->id);
100✔
248

249
        assert(j->dbus_msg);
100✔
250
        msg = TAKE_PTR(j->dbus_msg);
100✔
251

252
        j->complete_cb = NULL;
100✔
253

254
        r = j->detach_cb(msg, j);
100✔
255
        if (r < 0)
100✔
256
                log_warning_errno(r, "Failed to detach job %" PRIu64 ", ignoring: %m", j->id);
100✔
257
}
258

259
static void job_on_errno(Job *j, const char *buf) {
12✔
260
        int r;
12✔
261

262
        assert(j);
12✔
263
        assert(buf);
12✔
264

265
        r = parse_errno(buf);
12✔
266
        if (r < 0) {
12✔
UNCOV
267
                log_warning_errno(r, "Got invalid errno value from job %" PRIu64 ", ignoring: %m", j->id);
×
268
                return;
269
        }
270

271
        j->status_errno = r;
12✔
272

273
        log_debug_errno(r, "Got errno from job %" PRIu64 ": %i (%m)", j->id, r);
12✔
274
}
275

276
static void job_on_progress(Job *j, const char *buf) {
258✔
277
        unsigned progress;
258✔
278
        int r;
258✔
279

280
        assert(j);
258✔
281
        assert(buf);
258✔
282

283
        r = safe_atou(buf, &progress);
258✔
284
        if (r < 0 || progress > 100) {
258✔
UNCOV
285
                log_warning("Got invalid percent value, ignoring.");
×
UNCOV
286
                return;
×
287
        }
288

289
        j->progress_percent = progress;
258✔
290
        (void) sd_bus_emit_properties_changed(j->manager->bus, j->object_path,
258✔
291
                                              "org.freedesktop.sysupdate1.Job",
292
                                              "Progress", NULL);
293

294
        log_debug("Got percentage from job %" PRIu64 ": %u%%", j->id, j->progress_percent);
258✔
295
}
296

297
static void job_on_version(Job *j, const char *version) {
100✔
298
        assert(j);
100✔
299
        assert(version);
100✔
300

301
        if (free_and_strdup_warn(&j->version, version) < 0)
100✔
302
                return;
303

304
        log_debug("Got version from job %" PRIu64 ": %s ", j->id, j->version);
100✔
305
}
306

307
static int job_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
252✔
308
        Job *j = ASSERT_PTR(userdata);
252✔
UNCOV
309
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
310
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
252✔
311
        Manager *manager = j->manager;
252✔
312
        int r;
252✔
313

314
        assert(j);
252✔
315
        assert(s);
252✔
316
        assert(si);
252✔
317

318
        if (job_requires_busy(j)) {
252✔
319
                assert(j->target->busy);
108✔
320
                j->target->busy = false;
108✔
321
        }
322

323
        if (si->si_code != CLD_EXITED) {
252✔
UNCOV
324
                assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
×
UNCOV
325
                sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
326
                                  "Job terminated abnormally with signal %s.",
UNCOV
327
                                  signal_to_string(si->si_status));
×
328
        } else if (si->si_status != EXIT_SUCCESS)
252✔
329
                if (j->status_errno != 0)
12✔
330
                        sd_bus_error_set_errno(&error, j->status_errno);
12✔
331
                else
UNCOV
332
                        sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
333
                                          "Job failed with exit code %i.", si->si_status);
334
        else {
335
                r = job_parse_child_output(TAKE_FD(j->stdout_fd), &json);
240✔
336
                if (r < 0)
240✔
UNCOV
337
                        sd_bus_error_set_errnof(&error, r, "Failed to parse job worker output: %m");
×
338
        }
339

340
        /* Only send notification of exit if the job was actually detached */
341
        if (j->detach_cb) {
252✔
342
                r = sd_bus_emit_signal(
108✔
343
                                j->manager->bus,
108✔
344
                                "/org/freedesktop/sysupdate1",
345
                                "org.freedesktop.sysupdate1.Manager",
346
                                "JobRemoved",
347
                                "toi",
348
                                j->id,
349
                                j->object_path,
350
                                j->status_errno != 0 ? -j->status_errno : si->si_status);
108✔
351
                if (r < 0)
108✔
UNCOV
352
                        log_warning_errno(r,
×
353
                                          "Cannot emit JobRemoved message for job %" PRIu64 ", ignoring: %m",
354
                                          j->id);
355
        }
356

357
        if (j->dbus_msg && j->complete_cb) {
252✔
358
                if (sd_bus_error_is_set(&error)) {
152✔
359
                        log_warning("Job %" PRIu64 " failed with bus error, ignoring callback: %s",
8✔
360
                                    j->id, error.message);
361
                        sd_bus_reply_method_error(j->dbus_msg, &error);
8✔
362
                } else {
363
                        r = j->complete_cb(j->dbus_msg, j, json, &error);
144✔
364
                        if (r < 0) {
144✔
UNCOV
365
                                log_warning_errno(r,
×
366
                                                  "Error during execution of job callback for job %" PRIu64 ": %s",
367
                                                  j->id,
368
                                                  bus_error_message(&error, r));
UNCOV
369
                                sd_bus_reply_method_errno(j->dbus_msg, r, &error);
×
370
                        }
371
                }
372
        }
373

374
        job_free(j);
252✔
375

376
        if (manager)
252✔
377
                manager_check_idle(manager);
252✔
378

379
        return 0;
504✔
380
}
381

382
static inline const char* sysupdate_binary_path(void) {
456✔
383
        return secure_getenv("SYSTEMD_SYSUPDATE_PATH") ?: SYSTEMD_SYSUPDATE_PATH;
456✔
384
}
385

386
static int target_get_argument(Target *t, char **ret) {
284✔
387
        _cleanup_free_ char *target_arg = NULL;
568✔
388

389
        assert(t);
284✔
390
        assert(ret);
284✔
391

392
        if (t->class != TARGET_HOST) {
284✔
393
                if (t->class == TARGET_COMPONENT)
32✔
394
                        target_arg = strjoin("--component=", t->name);
32✔
UNCOV
395
                else if (IN_SET(t->image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME))
×
UNCOV
396
                        target_arg = strjoin("--root=", t->path);
×
UNCOV
397
                else if (IN_SET(t->image_type, IMAGE_RAW, IMAGE_BLOCK))
×
398
                        target_arg = strjoin("--image=", t->path);
×
399
                else
UNCOV
400
                        assert_not_reached();
×
401
                if (!target_arg)
32✔
402
                        return -ENOMEM;
403
        }
404

405
        *ret = TAKE_PTR(target_arg);
284✔
406
        return 0;
284✔
407
}
408

409
static int job_start(Job *j) {
252✔
410
        _cleanup_close_ int stdout_fd = -EBADF;
252✔
411
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
252✔
412
        int r;
252✔
413

414
        assert(j);
252✔
415

416
        if (job_requires_busy(j) && j->target->busy)
252✔
UNCOV
417
                return log_notice_errno(SYNTHETIC_ERRNO(EBUSY), "Target %s busy, ignoring job.", j->target->name);
×
418

419
        stdout_fd = memfd_new("sysupdate-stdout");
252✔
420
        if (stdout_fd < 0)
252✔
UNCOV
421
                return log_error_errno(stdout_fd, "Failed to create memfd: %m");
×
422

423
        r = pidref_safe_fork_full("(sd-sysupdate)",
756✔
424
                                  (int[]) { -EBADF, stdout_fd, STDERR_FILENO }, NULL, 0,
252✔
425
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
426
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
427
        if (r < 0)
504✔
428
                return r; /* FORK_LOG means pidref_safe_fork_full will handle the logging */
429
        if (r == 0) {
504✔
430
                /* Child */
431

UNCOV
432
                _cleanup_free_ char *target_arg = NULL;
×
433
                const char *cmd[] = {
252✔
434
                        "systemd-sysupdate",
435
                        "--json=short",
436
                        NULL, /* maybe --verify=no */
437
                        NULL, /* maybe --component=, --root=, or --image= */
438
                        NULL, /* maybe --offline */
439
                        NULL, /* list, check-new, acquire, update, vacuum, features */
440
                        NULL, /* maybe version (for list, acquire, update), maybe feature (features) */
441
                        NULL
442
                };
443
                size_t k = 2;
252✔
444

445
                if (setenv("NOTIFY_SOCKET", j->manager->notify_socket_path, /* overwrite= */ 1) < 0) {
252✔
446
                        log_error_errno(errno, "setenv() failed: %m");
×
UNCOV
447
                        _exit(EXIT_FAILURE);
×
448
                }
449

450
                if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
252✔
451
                        cmd[k++] = "--verify=no"; /* For testing */
252✔
452

453
                r = setenv_systemd_exec_pid(true);
252✔
454
                if (r < 0)
252✔
UNCOV
455
                        log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
×
456

457
                r = target_get_argument(j->target, &target_arg);
252✔
458
                if (r < 0) {
252✔
UNCOV
459
                        log_oom();
×
UNCOV
460
                        _exit(EXIT_FAILURE);
×
461
                }
462
                if (target_arg)
252✔
463
                        cmd[k++] = target_arg;
16✔
464

465
                if (j->offline || j->type == JOB_INSTALL)  /* install is implemented as `update --offline` */
252✔
466
                        cmd[k++] = "--offline";
52✔
467

468
                switch (j->type) {
252✔
469
                case JOB_LIST:
16✔
470
                        cmd[k++] = "list";
16✔
471
                        break;
16✔
472

473
                case JOB_DESCRIBE:
96✔
474
                        cmd[k++] = "list";
96✔
475
                        assert(!isempty(j->version));
96✔
476
                        cmd[k++] = j->version;
96✔
477
                        break;
96✔
478

479
                case JOB_CHECK_NEW:
32✔
480
                        cmd[k++] = "check-new";
32✔
481
                        break;
32✔
482

483
                case JOB_ACQUIRE:
56✔
484
                        cmd[k++] = "acquire";
56✔
485
                        cmd[k++] = empty_to_null(j->version);
112✔
486
                        break;
56✔
487

488
                case JOB_INSTALL:
52✔
489
                        cmd[k++] = "update";  /* install is implemented as `update --offline` */
52✔
490
                        cmd[k++] = empty_to_null(j->version);
104✔
491
                        break;
52✔
492

UNCOV
493
                case JOB_VACUUM:
×
UNCOV
494
                        cmd[k++] = "vacuum";
×
UNCOV
495
                        break;
×
496

UNCOV
497
                case JOB_DESCRIBE_FEATURE:
×
UNCOV
498
                        cmd[k++] = "features";
×
UNCOV
499
                        assert(!isempty(j->feature));
×
UNCOV
500
                        cmd[k++] = j->feature;
×
UNCOV
501
                        break;
×
502

UNCOV
503
                default:
×
UNCOV
504
                        assert_not_reached();
×
505
                }
506

507
                if (DEBUG_LOGGING) {
252✔
508
                        _cleanup_free_ char *s = NULL;
252✔
509

510
                        s = quote_command_line((char**) cmd, SHELL_ESCAPE_EMPTY);
252✔
511
                        if (!s) {
252✔
UNCOV
512
                                log_oom();
×
UNCOV
513
                                _exit(EXIT_FAILURE);
×
514
                        }
515

516
                        log_debug("Spawning worker for job %" PRIu64 ": %s", j->id, s);
252✔
517
                }
518

519
                r = invoke_callout_binary(sysupdate_binary_path(), (char *const *) cmd);
504✔
UNCOV
520
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
UNCOV
521
                _exit(EXIT_FAILURE);
×
522
        }
523

524
        log_info("Started job %" PRIu64 " with worker PID " PID_FMT,
252✔
525
                 j->id, pid.pid);
526

527
        r = event_add_child_pidref(j->manager->event, &j->child, &pid, WEXITED, job_on_exit, j);
252✔
528
        if (r < 0)
252✔
529
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
530

531
        r = sd_event_source_set_child_process_own(j->child, true);
252✔
532
        if (r < 0)
252✔
533
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
534
        pidref_done(&pid); /* disarm sigkill_wait */
252✔
535

536
        j->stdout_fd = TAKE_FD(stdout_fd);
252✔
537

538
        if (job_requires_busy(j))
252✔
539
                j->target->busy = true;
108✔
540

541
        return 0;
542
}
543

UNCOV
544
static int job_cancel(Job *j) {
×
UNCOV
545
        int r;
×
546

UNCOV
547
        assert(j);
×
548

549
        r = sd_event_source_send_child_signal(j->child, j->n_cancelled < 3 ? SIGTERM : SIGKILL,
×
550
                                              NULL, 0);
UNCOV
551
        if (r < 0)
×
552
                return r;
553

UNCOV
554
        j->n_cancelled++;
×
UNCOV
555
        return 0;
×
556
}
557

558
static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
559
        Job *j = ASSERT_PTR(userdata);
×
UNCOV
560
        const char *action;
×
UNCOV
561
        int r;
×
562

UNCOV
563
        assert(msg);
×
564

UNCOV
565
        switch (j->type) {
×
566
        case JOB_LIST:
567
        case JOB_DESCRIBE:
568
        case JOB_CHECK_NEW:
569
                action = "org.freedesktop.sysupdate1.cancel-check";
570
                break;
571

UNCOV
572
        case JOB_ACQUIRE:
×
573
        case JOB_INSTALL:
574
                if (j->version)
×
575
                        action = "org.freedesktop.sysupdate1.cancel-update-to-version";
576
                else
UNCOV
577
                        action = "org.freedesktop.sysupdate1.cancel-update";
×
578
                break;
579

580
        case JOB_VACUUM:
UNCOV
581
                action = "org.freedesktop.sysupdate1.cancel-vacuum";
×
UNCOV
582
                break;
×
583

584
        case JOB_DESCRIBE_FEATURE:
585
                action = NULL;
586
                break;
587

588
        default:
×
589
                assert_not_reached();
×
590
        }
591

592
        if (action) {
×
UNCOV
593
                r = bus_verify_polkit_async(
×
594
                                msg,
595
                                action,
596
                                /* details= */ NULL,
UNCOV
597
                                &j->manager->polkit_registry,
×
598
                                error);
UNCOV
599
                if (r < 0)
×
600
                        return r;
601
                if (r == 0)
×
602
                        return 1; /* Will call us back */
603
        }
604

UNCOV
605
        r = job_cancel(j);
×
606
        if (r < 0)
×
607
                return r;
608

UNCOV
609
        return sd_bus_reply_method_return(msg, NULL);
×
610
}
611

UNCOV
612
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
613

614
static int job_object_find(
258✔
615
                sd_bus *bus,
616
                const char *path,
617
                const char *iface,
618
                void *userdata,
619
                void **ret,
620
                sd_bus_error *error) {
621

622
        Manager *m = ASSERT_PTR(userdata);
258✔
623
        Job *j;
258✔
624
        const char *p;
258✔
625
        uint64_t id;
258✔
626
        int r;
258✔
627

628
        assert(bus);
258✔
629
        assert(path);
258✔
630
        assert(ret);
258✔
631

632
        p = startswith(path, "/org/freedesktop/sysupdate1/job/_");
258✔
633
        if (!p)
258✔
634
                return 0;
258✔
635

636
        r = safe_atou64(p, &id);
258✔
637
        if (r < 0 || id == 0)
258✔
638
                return 0;
639

640
        j = hashmap_get(m->jobs, &id);
258✔
641
        if (!j)
258✔
642
                return 0;
643

644
        *ret = j;
258✔
645
        return 1;
258✔
646
}
647

UNCOV
648
static int job_node_enumerator(
×
649
                sd_bus *bus,
650
                const char *path,
651
                void *userdata,
652
                char ***nodes,
653
                sd_bus_error *error) {
654

UNCOV
655
        _cleanup_strv_free_ char **l = NULL;
×
UNCOV
656
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
657
        Job *j;
×
UNCOV
658
        unsigned k = 0;
×
659

UNCOV
660
        l = new0(char*, hashmap_size(m->jobs) + 1);
×
UNCOV
661
        if (!l)
×
662
                return -ENOMEM;
663

UNCOV
664
        HASHMAP_FOREACH(j, m->jobs) {
×
UNCOV
665
                l[k] = strdup(j->object_path);
×
UNCOV
666
                if (!l[k])
×
UNCOV
667
                        return -ENOMEM;
×
UNCOV
668
                k++;
×
669
        }
670

UNCOV
671
        *nodes = TAKE_PTR(l);
×
UNCOV
672
        return 1;
×
673
}
674

675
static const sd_bus_vtable job_vtable[] = {
676
        SD_BUS_VTABLE_START(0),
677

678
        SD_BUS_PROPERTY("Id", "t", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
679
        SD_BUS_PROPERTY("Type", "s", job_property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
680
        SD_BUS_PROPERTY("Offline", "b", bus_property_get_bool, offsetof(Job, offline), SD_BUS_VTABLE_PROPERTY_CONST),
681
        SD_BUS_PROPERTY("Progress", "u", bus_property_get_unsigned, offsetof(Job, progress_percent), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
682

683
        SD_BUS_METHOD("Cancel", NULL, NULL, job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
684

685
        SD_BUS_VTABLE_END
686
};
687

688
static const BusObjectImplementation job_object = {
689
        "/org/freedesktop/sysupdate1/job",
690
        "org.freedesktop.sysupdate1.Job",
691
        .fallback_vtables = BUS_FALLBACK_VTABLES({job_vtable, job_object_find}),
692
        .node_enumerator = job_node_enumerator,
693
};
694

695
static Target *target_free(Target *t) {
236✔
696
        if (!t)
236✔
697
                return NULL;
698

699
        free(t->name);
236✔
700
        free(t->path);
236✔
701
        free(t->id);
236✔
702

703
        return mfree(t);
236✔
704
}
705

706
DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free);
644✔
707
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops,
236✔
708
                                              char, string_hash_func, string_compare_func,
709
                                              Target, target_free);
710

711
static int target_new(Manager *m, TargetClass class, const char *name, const char *path, Target **ret) {
236✔
712
        _cleanup_(target_freep) Target *t = NULL;
236✔
713

714
        assert(m);
236✔
715
        assert(ret);
236✔
716

717
        t = new(Target, 1);
236✔
718
        if (!t)
236✔
719
                return -ENOMEM;
720

721
        *t = (Target) {
236✔
722
                .manager = m,
723
                .class = class,
724
                .image_type = _IMAGE_TYPE_INVALID,
725
        };
726

727
        t->name = strdup(name);
236✔
728
        if (!t->name)
236✔
729
                return -ENOMEM;
730

731
        t->path = strdup(path);
236✔
732
        if (!t->path)
236✔
733
                return -ENOMEM;
734

735
        if (class == TARGET_HOST)
236✔
736
                t->id = strdup("host"); /* This is what appears in the object path */
172✔
737
        else
738
                t->id = strjoin(target_class_to_string(class), ":", name);
64✔
739
        if (!t->id)
236✔
740
                return -ENOMEM;
741

742
        *ret = TAKE_PTR(t);
236✔
743
        return 0;
236✔
744
}
745

746
static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) {
204✔
747
        _cleanup_close_pair_ int pipe[2] = EBADF_PAIR;
204✔
UNCOV
748
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
×
749
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
204✔
750
        _cleanup_free_ char *target_arg = NULL;
204✔
751
        int r;
204✔
752

753
        if (t) {
204✔
754
                r = target_get_argument(t, &target_arg);
32✔
755
                if (r < 0)
32✔
756
                        return r;
757
        }
758

759
        r = pipe2(pipe, O_CLOEXEC);
204✔
760
        if (r < 0)
204✔
UNCOV
761
                return -errno;
×
762

763
        r = pidref_safe_fork_full("(sd-sysupdate)",
612✔
764
                                  (int[]) { -EBADF, pipe[1], STDERR_FILENO },
204✔
765
                                  NULL, 0,
766
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
767
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG,
768
                                  &pid);
769
        if (r < 0)
408✔
770
                return r;
771
        if (r == 0) {
408✔
772
                /* Child */
773
                va_list ap;
204✔
774
                char *arg;
204✔
UNCOV
775
                _cleanup_strv_free_ char **args = NULL;
×
776

777
                if (strv_extend(&args, "systemd-sysupdate") < 0) {
204✔
UNCOV
778
                        log_oom();
×
UNCOV
779
                        _exit(EXIT_FAILURE);
×
780
                }
781

782
                if (strv_extend(&args, "--json=short") < 0) {
204✔
UNCOV
783
                        log_oom();
×
UNCOV
784
                        _exit(EXIT_FAILURE);
×
785
                }
786

787
                if (target_arg && strv_extend(&args, target_arg) < 0) {
204✔
UNCOV
788
                        log_oom();
×
UNCOV
789
                        _exit(EXIT_FAILURE);
×
790
                }
791

792
                va_start(ap, t);
204✔
793
                while ((arg = va_arg(ap, char*))) {
440✔
794
                        r = strv_extend(&args, arg);
236✔
795
                        if (r < 0)
236✔
796
                                break;
797
                }
798
                va_end(ap);
204✔
799
                if (r < 0) {
204✔
UNCOV
800
                        log_oom();
×
UNCOV
801
                        _exit(EXIT_FAILURE);
×
802
                }
803

804
                if (DEBUG_LOGGING) {
204✔
805
                        _cleanup_free_ char *s = NULL;
204✔
806

807
                        s = quote_command_line(args, SHELL_ESCAPE_EMPTY);
204✔
808
                        if (!s) {
204✔
UNCOV
809
                                log_oom();
×
UNCOV
810
                                _exit(EXIT_FAILURE);
×
811
                        }
812

813
                        log_debug("Spawning sysupdate: %s", s);
204✔
814
                }
815

816
                r = invoke_callout_binary(sysupdate_binary_path(), args);
408✔
817
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
818
                _exit(EXIT_FAILURE);
×
819
        }
820

821
        pipe[1] = safe_close(pipe[1]);
204✔
822

823
        r = sd_json_parse_fd(
408✔
824
                        "stdout",
825
                        TAKE_FD(pipe[0]),
204✔
826
                        SD_JSON_PARSE_DONATE_FD,
827
                        &v,
828
                        /* reterr_line= */ NULL,
829
                        /* reterr_column= */ NULL);
830
        if (r < 0)
204✔
UNCOV
831
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
832

833
        *ret = TAKE_PTR(v);
204✔
834
        return 0;
204✔
835
}
836

UNCOV
837
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
838

839
#define log_sysupdate_bad_json_full(lvl, r, verb, msg) \
840
        log_full_errno((lvl), (r), "Invalid JSON response from 'systemd-sysupdate %s': %s", (verb), (msg))
841
#define log_sysupdate_bad_json(r, verb, msg) \
842
        log_sysupdate_bad_json_full(LOG_ERR, (r), (verb), (msg))
843
#define log_sysupdate_bad_json_debug(r, verb, msg) \
844
        log_sysupdate_bad_json_full(LOG_DEBUG, (r), (verb), (msg))
845

846
static int target_method_list_finish(
16✔
847
                sd_bus_message *msg,
848
                const Job *j,
849
                sd_json_variant *json,
850
                sd_bus_error *error) {
851

852
        sd_json_variant *v;
16✔
UNCOV
853
        _cleanup_strv_free_ char **versions = NULL;
×
854
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
16✔
855
        int r;
16✔
856

857
        assert(json);
16✔
858

859
        v = sd_json_variant_by_key(json, "all");
16✔
860
        if (!v)
16✔
UNCOV
861
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'all'");
×
862

863
        r = sd_json_variant_strv(v, &versions);
16✔
864
        if (r < 0)
16✔
UNCOV
865
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'all' should be strv");
×
866

867
        r = sd_bus_message_new_method_return(msg, &reply);
16✔
868
        if (r < 0)
16✔
869
                return r;
870

871
        r = sd_bus_message_append_strv(reply, versions);
16✔
872
        if (r < 0)
16✔
873
                return r;
874

875
        return sd_bus_message_send(reply);
16✔
876
}
877

878
static int target_method_list(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
16✔
879
        Target *t = ASSERT_PTR(userdata);
16✔
880
        _cleanup_(job_freep) Job *j = NULL;
16✔
881
        uint64_t flags;
16✔
882
        int r;
16✔
883

884
        assert(msg);
16✔
885

886
        r = sd_bus_message_read(msg, "t", &flags);
16✔
887
        if (r < 0)
16✔
888
                return r;
889

890
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
16✔
UNCOV
891
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
892

893
        const char *details[] = {
48✔
894
                "class", target_class_to_string(t->class),
16✔
895
                "name", t->name,
16✔
896
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
16✔
897
                NULL
898
        };
899

900
        r = bus_verify_polkit_async(
32✔
901
                msg,
902
                "org.freedesktop.sysupdate1.check",
903
                details,
904
                &t->manager->polkit_registry,
16✔
905
                error);
906
        if (r < 0)
16✔
907
                return r;
908
        if (r == 0)
16✔
909
                return 1; /* Will call us back */
910

911
        r = job_new(JOB_LIST, t, msg, target_method_list_finish, &j);
16✔
912
        if (r < 0)
16✔
913
                return r;
914

915
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
16✔
916

917
        r = job_start(j);
16✔
918
        if (r < 0)
16✔
UNCOV
919
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
920
        TAKE_PTR(j); /* Avoid job from being killed & freed */
16✔
921

922
        return 1;
16✔
923
}
924

925
static int target_method_describe_finish(
96✔
926
                sd_bus_message *msg,
927
                const Job *j,
928
                sd_json_variant *json,
929
                sd_bus_error *error) {
930
        _cleanup_free_ char *text = NULL;
96✔
931
        int r;
96✔
932

933
        /* NOTE: This is also reused by target_method_describe_feature */
934

935
        assert(json);
96✔
936

937
        r = sd_json_variant_format(json, 0, &text);
96✔
938
        if (r < 0)
96✔
939
                return r;
940

941
        return sd_bus_reply_method_return(msg, "s", text);
96✔
942
}
943

944
static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
96✔
945
        Target *t = ASSERT_PTR(userdata);
96✔
946
        _cleanup_(job_freep) Job *j = NULL;
96✔
947
        const char *version;
96✔
948
        uint64_t flags;
96✔
949
        int r;
96✔
950

951
        assert(msg);
96✔
952

953
        r = sd_bus_message_read(msg, "st", &version, &flags);
96✔
954
        if (r < 0)
96✔
955
                return r;
956

957
        if (!version_is_valid(version))
96✔
UNCOV
958
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
959

960
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
96✔
UNCOV
961
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
962

963
        const char *details[] = {
288✔
964
                "class", target_class_to_string(t->class),
96✔
965
                "name", t->name,
96✔
966
                "version", version,
967
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
96✔
968
                NULL
969
        };
970

971
        r = bus_verify_polkit_async(
192✔
972
                msg,
973
                "org.freedesktop.sysupdate1.check",
974
                details,
975
                &t->manager->polkit_registry,
96✔
976
                error);
977
        if (r < 0)
96✔
978
                return r;
979
        if (r == 0)
96✔
980
                return 1; /* Will call us back */
981

982
        r = job_new(JOB_DESCRIBE, t, msg, target_method_describe_finish, &j);
96✔
983
        if (r < 0)
96✔
984
                return r;
985

986
        j->version = strdup(version);
96✔
987
        if (!j->version)
96✔
UNCOV
988
                return log_oom();
×
989

990
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
96✔
991

992
        r = job_start(j);
96✔
993
        if (r < 0)
96✔
UNCOV
994
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
995
        TAKE_PTR(j); /* Avoid job from being killed & freed */
96✔
996

997
        return 1;
96✔
998
}
999

1000
static int target_method_check_new_finish(
32✔
1001
                sd_bus_message *msg,
1002
                const Job *j,
1003
                sd_json_variant *json,
1004
                sd_bus_error *error) {
1005
        const char *reply;
32✔
1006

1007
        assert(json);
32✔
1008

1009
        sd_json_variant *v = sd_json_variant_by_key(json, "available");
32✔
1010
        if (!v)
32✔
UNCOV
1011
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "check-new", "Missing key 'available'");
×
1012

1013
        if (sd_json_variant_is_null(v))
32✔
1014
                reply = "";
1015
        else
UNCOV
1016
                reply = sd_json_variant_string(v);
×
1017
        if (!reply)
×
1018
                return -EINVAL;
1019

1020
        return sd_bus_reply_method_return(msg, "s", reply);
32✔
1021
}
1022

1023
static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
32✔
1024
        Target *t = ASSERT_PTR(userdata);
32✔
1025
        _cleanup_(job_freep) Job *j = NULL;
32✔
1026
        int r;
32✔
1027

1028
        assert(msg);
32✔
1029

1030
        const char *details[] = {
64✔
1031
                "class", target_class_to_string(t->class),
32✔
1032
                "name", t->name,
32✔
1033
                "offline", "0",
1034
                NULL
1035
        };
1036

1037
        r = bus_verify_polkit_async(
64✔
1038
                        msg,
1039
                        "org.freedesktop.sysupdate1.check",
1040
                        details,
1041
                        &t->manager->polkit_registry,
32✔
1042
                        error);
1043
        if (r < 0)
32✔
1044
                return r;
1045
        if (r == 0)
32✔
1046
                return 1; /* Will call us back */
1047

1048
        r = job_new(JOB_CHECK_NEW, t, msg, target_method_check_new_finish, &j);
32✔
1049
        if (r < 0)
32✔
1050
                return r;
1051

1052
        r = job_start(j);
32✔
1053
        if (r < 0)
32✔
UNCOV
1054
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1055
        TAKE_PTR(j); /* Avoid job from being killed & freed */
32✔
1056

1057
        return 1;
32✔
1058
}
1059

UNCOV
1060
static int target_method_acquire_finished_early(
×
1061
                sd_bus_message *msg,
1062
                const Job *j,
1063
                sd_json_variant *json,
1064
                sd_bus_error *error) {
1065

1066
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1067
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1068
         * specified update is already acquired. */
UNCOV
1069
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1070
                                 "Job exited successfully with no work to do, assume already acquired");
1071
}
1072

1073
static int target_method_acquire_detach(sd_bus_message *msg, const Job *j) {
52✔
1074
        int r;
52✔
1075

1076
        assert(msg);
52✔
1077
        assert(j);
52✔
1078

1079
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
52✔
1080
        if (r < 0)
52✔
UNCOV
1081
                return bus_log_parse_error(r);
×
1082

1083
        return 0;
1084
}
1085

1086
static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
56✔
1087
        Target *t = ASSERT_PTR(userdata);
56✔
1088
        _cleanup_(job_freep) Job *j = NULL;
56✔
1089
        const char *version, *action;
56✔
1090
        uint64_t flags;
56✔
1091
        int r;
56✔
1092

1093
        assert(msg);
56✔
1094

1095
        r = sd_bus_message_read(msg, "st", &version, &flags);
56✔
1096
        if (r < 0)
56✔
1097
                return r;
1098

1099
        if (flags != 0)
56✔
UNCOV
1100
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1101

1102
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1103
         * an update anyway. */
1104
        if (isempty(version))
56✔
1105
                action = "org.freedesktop.sysupdate1.update";
1106
        else {
UNCOV
1107
                if (!version_is_valid(version))
×
UNCOV
1108
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1109

1110
                action = "org.freedesktop.sysupdate1.update-to-version";
1111
        }
1112

1113
        const char *details[] = {
112✔
1114
                "class", target_class_to_string(t->class),
56✔
1115
                "name", t->name,
56✔
1116
                "version", version,
1117
                NULL
1118
        };
1119

1120
        r = bus_verify_polkit_async(
112✔
1121
                        msg,
1122
                        action,
1123
                        details,
1124
                        &t->manager->polkit_registry,
56✔
1125
                        error);
1126
        if (r < 0)
56✔
1127
                return r;
1128
        if (r == 0)
56✔
1129
                return 1; /* Will call us back */
1130

1131
        r = job_new(JOB_ACQUIRE, t, msg, target_method_acquire_finished_early, &j);
56✔
1132
        if (r < 0)
56✔
1133
                return r;
1134
        j->detach_cb = target_method_acquire_detach;
56✔
1135

1136
        j->version = strdup(version);
56✔
1137
        if (!j->version)
56✔
1138
                return -ENOMEM;
1139

1140
        r = job_start(j);
56✔
1141
        if (r < 0)
56✔
UNCOV
1142
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1143
        TAKE_PTR(j);
56✔
1144

1145
        return 1;
56✔
1146
}
1147

UNCOV
1148
static int target_method_install_finished_early(
×
1149
                sd_bus_message *msg,
1150
                const Job *j,
1151
                sd_json_variant *json,
1152
                sd_bus_error *error) {
1153

1154
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1155
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1156
         * specified update is already installed. */
UNCOV
1157
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1158
                                 "Job exited successfully with no work to do, assume already installed");
1159
}
1160

1161
static int target_method_install_detach(sd_bus_message *msg, const Job *j) {
48✔
1162
        int r;
48✔
1163

1164
        assert(msg);
48✔
1165
        assert(j);
48✔
1166

1167
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
48✔
1168
        if (r < 0)
48✔
UNCOV
1169
                return bus_log_parse_error(r);
×
1170

1171
        return 0;
1172
}
1173

1174
static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
52✔
1175
        Target *t = ASSERT_PTR(userdata);
52✔
1176
        _cleanup_(job_freep) Job *j = NULL;
52✔
1177
        const char *version, *action;
52✔
1178
        uint64_t flags;
52✔
1179
        int r;
52✔
1180

1181
        assert(msg);
52✔
1182

1183
        r = sd_bus_message_read(msg, "st", &version, &flags);
52✔
1184
        if (r < 0)
52✔
1185
                return r;
1186

1187
        if (flags != 0)
52✔
UNCOV
1188
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1189

1190
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1191
         * an update anyway. */
1192
        if (isempty(version))
52✔
1193
                action = "org.freedesktop.sysupdate1.update";
1194
        else {
UNCOV
1195
                if (!version_is_valid(version))
×
UNCOV
1196
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1197

1198
                action = "org.freedesktop.sysupdate1.update-to-version";
1199
        }
1200

1201
        const char *details[] = {
104✔
1202
                "class", target_class_to_string(t->class),
52✔
1203
                "name", t->name,
52✔
1204
                "version", version,
1205
                NULL
1206
        };
1207

1208
        r = bus_verify_polkit_async(
104✔
1209
                        msg,
1210
                        action,
1211
                        details,
1212
                        &t->manager->polkit_registry,
52✔
1213
                        error);
1214
        if (r < 0)
52✔
1215
                return r;
1216
        if (r == 0)
52✔
1217
                return 1; /* Will call us back */
1218

1219
        r = job_new(JOB_INSTALL, t, msg, target_method_install_finished_early, &j);
52✔
1220
        if (r < 0)
52✔
1221
                return r;
1222
        j->detach_cb = target_method_install_detach;
52✔
1223

1224
        j->version = strdup(version);
52✔
1225
        if (!j->version)
52✔
1226
                return -ENOMEM;
1227

1228
        r = job_start(j);
52✔
1229
        if (r < 0)
52✔
UNCOV
1230
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1231
        TAKE_PTR(j);
52✔
1232

1233
        return 1;
52✔
1234
}
1235

UNCOV
1236
static int target_method_vacuum_finish(
×
1237
                sd_bus_message *msg,
1238
                const Job *j,
1239
                sd_json_variant *json,
1240
                sd_bus_error *error) {
1241

UNCOV
1242
        sd_json_variant *v;
×
UNCOV
1243
        uint64_t instances, disabled;
×
1244

UNCOV
1245
        assert(json);
×
1246

UNCOV
1247
        v = sd_json_variant_by_key(json, "removed");
×
UNCOV
1248
        if (!v)
×
UNCOV
1249
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'removed'");
×
UNCOV
1250
        if (!sd_json_variant_is_unsigned(v))
×
UNCOV
1251
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'removed' should be an unsigned int");
×
UNCOV
1252
        instances = sd_json_variant_unsigned(v);
×
UNCOV
1253
        assert(instances <= UINT32_MAX);
×
1254

UNCOV
1255
        v = sd_json_variant_by_key(json, "disabledTransfers");
×
UNCOV
1256
        if (!v)
×
UNCOV
1257
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
×
UNCOV
1258
        if (!sd_json_variant_is_unsigned(v))
×
1259
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
×
UNCOV
1260
        disabled = sd_json_variant_unsigned(v);
×
UNCOV
1261
        assert(disabled <= UINT32_MAX);
×
1262

UNCOV
1263
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1264
}
1265

UNCOV
1266
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
1267
        Target *t = ASSERT_PTR(userdata);
×
UNCOV
1268
        _cleanup_(job_freep) Job *j = NULL;
×
UNCOV
1269
        int r;
×
1270

1271
        assert(msg);
×
1272

UNCOV
1273
        const char *details[] = {
×
1274
                "class", target_class_to_string(t->class),
×
UNCOV
1275
                "name", t->name,
×
1276
                NULL
1277
        };
1278

1279
        r = bus_verify_polkit_async(
×
1280
                msg,
1281
                "org.freedesktop.sysupdate1.vacuum",
1282
                details,
UNCOV
1283
                &t->manager->polkit_registry,
×
1284
                error);
1285
        if (r < 0)
×
1286
                return r;
1287
        if (r == 0)
×
1288
                return 1; /* Will call us back */
1289

1290
        r = job_new(JOB_VACUUM, t, msg, target_method_vacuum_finish, &j);
×
UNCOV
1291
        if (r < 0)
×
1292
                return r;
1293

UNCOV
1294
        r = job_start(j);
×
1295
        if (r < 0)
×
1296
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1297
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1298

UNCOV
1299
        return 1;
×
1300
}
1301

1302
static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
32✔
1303
        Target *t = ASSERT_PTR(userdata);
32✔
1304
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
32✔
1305
        sd_json_variant *version_json;
32✔
1306
        int r;
32✔
1307

1308
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
32✔
1309
        if (r < 0)
32✔
UNCOV
1310
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1311

1312
        version_json = sd_json_variant_by_key(v, "current");
32✔
1313
        if (!version_json)
32✔
1314
                version_json = sd_json_variant_by_key(v, "current+pending");
×
UNCOV
1315
        if (!version_json)
×
1316
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current' or 'current+pending'");
×
1317

1318
        if (sd_json_variant_is_null(version_json))
32✔
1319
                return sd_bus_reply_method_return(msg, "s", "");
×
1320

1321
        if (!sd_json_variant_is_string(version_json))
32✔
UNCOV
1322
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'current' should be a string");
×
1323

1324
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
32✔
1325
}
1326

UNCOV
1327
static int target_get_appstream(Target *t, char ***ret) {
×
1328
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
UNCOV
1329
        sd_json_variant *appstream_url_json;
×
UNCOV
1330
        int r;
×
1331

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

UNCOV
1336
        appstream_url_json = sd_json_variant_by_key(v, "appstreamUrls");
×
UNCOV
1337
        if (!appstream_url_json)
×
UNCOV
1338
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'appstreamUrls'");
×
1339

UNCOV
1340
        r = sd_json_variant_strv(appstream_url_json, ret);
×
UNCOV
1341
        if (r < 0)
×
UNCOV
1342
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'appstreamUrls' should be strv");
×
1343

1344
        return 0;
1345
}
1346

UNCOV
1347
static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1348
        Target *t = ASSERT_PTR(userdata);
×
UNCOV
1349
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
1350
        _cleanup_strv_free_ char **appstream_urls = NULL;
×
1351
        int r;
×
1352

UNCOV
1353
        r = target_get_appstream(t, &appstream_urls);
×
UNCOV
1354
        if (r < 0)
×
1355
                return r;
1356

1357
        r = sd_bus_message_new_method_return(msg, &reply);
×
1358
        if (r < 0)
×
1359
                return r;
1360

1361
        r = sd_bus_message_append_strv(reply, appstream_urls);
×
1362
        if (r < 0)
×
1363
                return r;
1364

1365
        return sd_bus_message_send(reply);
×
1366
}
1367

UNCOV
1368
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1369
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1370
        _cleanup_strv_free_ char **features = NULL;
×
1371
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
1372
        Target *t = ASSERT_PTR(userdata);
×
UNCOV
1373
        sd_json_variant *v;
×
UNCOV
1374
        uint64_t flags;
×
UNCOV
1375
        int r;
×
1376

1377
        assert(msg);
×
1378

1379
        r = sd_bus_message_read(msg, "t", &flags);
×
1380
        if (r < 0)
×
1381
                return r;
1382
        if (flags != 0)
×
1383
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1384

UNCOV
1385
        r = sysupdate_run_simple(&json, t, "features", NULL);
×
1386
        if (r < 0)
×
1387
                return r;
1388

UNCOV
1389
        v = sd_json_variant_by_key(json, "features");
×
1390
        if (!v)
×
1391
                return -EINVAL;
UNCOV
1392
        r = sd_json_variant_strv(v, &features);
×
UNCOV
1393
        if (r < 0)
×
1394
                return r;
1395

UNCOV
1396
        r = sd_bus_message_new_method_return(msg, &reply);
×
1397
        if (r < 0)
×
1398
                return r;
1399

1400
        r = sd_bus_message_append_strv(reply, features);
×
1401
        if (r < 0)
×
1402
                return r;
1403

1404
        return sd_bus_message_send(reply);
×
1405
}
1406

UNCOV
1407
static bool feature_name_is_valid(const char *name) {
×
1408
        if (isempty(name))
×
1409
                return false;
1410

1411
        if (!ascii_is_valid(name))
×
1412
                return false;
1413

1414
        if (!filename_is_valid(strjoina(name, ".feature.d")))
×
1415
                return false;
×
1416

1417
        return true;
1418
}
1419

UNCOV
1420
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1421
        Target *t = ASSERT_PTR(userdata);
×
1422
        _cleanup_(job_freep) Job *j = NULL;
×
UNCOV
1423
        const char *feature;
×
UNCOV
1424
        uint64_t flags;
×
1425
        int r;
×
1426

UNCOV
1427
        assert(msg);
×
1428

1429
        r = sd_bus_message_read(msg, "st", &feature, &flags);
×
1430
        if (r < 0)
×
1431
                return r;
1432

1433
        if (!feature_name_is_valid(feature))
×
UNCOV
1434
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid feature name");
×
1435

1436
        if (flags != 0)
×
1437
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1438

UNCOV
1439
        r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
×
1440
        if (r < 0)
×
1441
                return r;
1442

1443
        j->feature = strdup(feature);
×
1444
        if (!j->feature)
×
UNCOV
1445
                return log_oom();
×
1446

UNCOV
1447
        r = job_start(j);
×
UNCOV
1448
        if (r < 0)
×
1449
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1450
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1451

1452
        return 1;
×
1453
}
1454

UNCOV
1455
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1456
        _cleanup_free_ char *feature_ext = NULL;
×
UNCOV
1457
        Target *t = ASSERT_PTR(userdata);
×
1458
        const char *feature;
×
1459
        uint64_t flags;
×
UNCOV
1460
        int32_t enabled;
×
UNCOV
1461
        int r;
×
1462

1463
        assert(msg);
×
1464

1465
        if (t->class != TARGET_HOST)
×
1466
                return sd_bus_reply_method_errorf(msg,
×
1467
                                                  SD_BUS_ERROR_NOT_SUPPORTED,
1468
                                                  "For now, features can only be managed on the host system.");
1469

UNCOV
1470
        r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
×
UNCOV
1471
        if (r < 0)
×
1472
                return r;
1473
        if (!feature_name_is_valid(feature))
×
1474
                return sd_bus_reply_method_errorf(msg,
×
1475
                                                  SD_BUS_ERROR_INVALID_ARGS,
1476
                                                  "The specified feature is invalid");
1477
        if (flags != 0)
×
1478
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1479

UNCOV
1480
        if (!endswith(feature, ".feature")) {
×
1481
                feature_ext = strjoin(feature, ".feature");
×
UNCOV
1482
                if (!feature_ext)
×
1483
                        return -ENOMEM;
1484
                feature = feature_ext;
×
1485
        }
1486

1487
        const char *details[] = {
×
1488
                "class", target_class_to_string(t->class),
×
1489
                "name", t->name,
×
1490
                "feature", feature,
UNCOV
1491
                "enabled", enabled >= 0 ? true_false(enabled) : "unset",
×
1492
                NULL
1493
        };
1494

1495
        r = bus_verify_polkit_async(
×
1496
                msg,
1497
                "org.freedesktop.sysupdate1.manage-features",
1498
                details,
1499
                &t->manager->polkit_registry,
×
1500
                error);
UNCOV
1501
        if (r < 0)
×
1502
                return r;
1503
        if (r == 0)
×
1504
                return 1; /* Will call us back */
1505

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

1510
                r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
×
1511
                if (r < 0)
×
1512
                        return r;
1513

UNCOV
1514
                if (unlink(path) < 0)
×
UNCOV
1515
                        return -errno;
×
1516
        } else { /* otherwise, create the drop-in with the right settings */
1517
                r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
×
1518
                                         "# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
1519
                                         "[Feature]\n"
1520
                                         "Enabled=%s\n",
1521
                                         yes_no(enabled));
UNCOV
1522
                if (r < 0)
×
1523
                        return r;
1524
        }
1525

UNCOV
1526
        return sd_bus_reply_method_return(msg, NULL);
×
1527
}
1528

1529
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
172✔
1530
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
172✔
1531
        _cleanup_strv_free_ char **components = NULL;
172✔
1532
        sd_json_variant *v;
172✔
1533
        bool have_default;
172✔
1534
        int r;
172✔
1535

1536
        r = sysupdate_run_simple(&json, t, "components", NULL);
172✔
1537
        if (r < 0)
172✔
UNCOV
1538
                return log_debug_errno(r, "Failed to run 'systemd-sysupdate components': %m");
×
1539

1540
        v = sd_json_variant_by_key(json, "default");
172✔
1541
        if (!v)
172✔
UNCOV
1542
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'default'");
×
1543
        have_default = sd_json_variant_boolean(v);
172✔
1544

1545
        v = sd_json_variant_by_key(json, "components");
172✔
1546
        if (!v)
172✔
UNCOV
1547
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'components'");
×
1548
        r = sd_json_variant_strv(v, &components);
172✔
1549
        if (r < 0)
172✔
UNCOV
1550
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Key 'components' should be a strv");
×
1551

1552
        if (ret_components)
172✔
1553
                *ret_components = TAKE_PTR(components);
172✔
1554
        if (ret_have_default)
172✔
1555
                *ret_have_default = have_default;
172✔
1556
        return 0;
1557
}
1558

1559
static int manager_ensure_targets(Manager *m);
1560

1561
static int target_object_find(
316✔
1562
                sd_bus *bus,
1563
                const char *path,
1564
                const char *iface,
1565
                void *userdata,
1566
                void **found,
1567
                sd_bus_error *error) {
1568

1569
        Manager *m = ASSERT_PTR(userdata);
316✔
1570
        Target *t;
316✔
1571
        _cleanup_free_ char *e = NULL;
316✔
1572
        const char *p;
316✔
1573
        int r;
316✔
1574

1575
        assert(bus);
316✔
1576
        assert(path);
316✔
1577
        assert(found);
316✔
1578

1579
        p = startswith(path, "/org/freedesktop/sysupdate1/target/");
316✔
1580
        if (!p)
316✔
1581
                return 0;
1582

1583
        e = bus_label_unescape(p);
316✔
1584
        if (!e)
316✔
1585
                return -ENOMEM;
1586

1587
        r = manager_ensure_targets(m);
316✔
1588
        if (r < 0)
316✔
1589
                return r;
1590

1591
        t = hashmap_get(m->targets, e);
316✔
1592
        if (!t)
316✔
1593
                return 0;
1594

1595
        *found = t;
316✔
1596
        return 1;
316✔
1597
}
1598

1599
static char *target_bus_path(Target *t) {
120✔
1600
        _cleanup_free_ char *e = NULL;
120✔
1601

1602
        assert(t);
120✔
1603

1604
        e = bus_label_escape(t->id);
120✔
1605
        if (!e)
120✔
1606
                return NULL;
1607

1608
        return strjoin("/org/freedesktop/sysupdate1/target/", e);
120✔
1609
}
1610

UNCOV
1611
static int target_node_enumerator(
×
1612
                sd_bus *bus,
1613
                const char *path,
1614
                void *userdata,
1615
                char ***nodes,
1616
                sd_bus_error *error) {
1617

UNCOV
1618
        _cleanup_strv_free_ char **l = NULL;
×
UNCOV
1619
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
1620
        Target *t;
×
UNCOV
1621
        unsigned k = 0;
×
UNCOV
1622
        int r;
×
1623

UNCOV
1624
        r = manager_ensure_targets(m);
×
UNCOV
1625
        if (r < 0)
×
1626
                return r;
1627

UNCOV
1628
        l = new0(char*, hashmap_size(m->targets) + 1);
×
UNCOV
1629
        if (!l)
×
1630
                return -ENOMEM;
1631

UNCOV
1632
        HASHMAP_FOREACH(t, m->targets) {
×
UNCOV
1633
                l[k] = target_bus_path(t);
×
UNCOV
1634
                if (!l[k])
×
UNCOV
1635
                        return -ENOMEM;
×
UNCOV
1636
                k++;
×
1637
        }
1638

UNCOV
1639
        *nodes = TAKE_PTR(l);
×
1640
        return 1;
×
1641
}
1642

1643
static const sd_bus_vtable target_vtable[] = {
1644
        SD_BUS_VTABLE_START(0),
1645

1646
        SD_BUS_PROPERTY("Class", "s", target_property_get_class,
1647
                        offsetof(Target, class), SD_BUS_VTABLE_PROPERTY_CONST),
1648
        SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Target, name),
1649
                        SD_BUS_VTABLE_PROPERTY_CONST),
1650
        SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Target, path),
1651
                        SD_BUS_VTABLE_PROPERTY_CONST),
1652

1653
        SD_BUS_METHOD_WITH_ARGS("List",
1654
                                SD_BUS_ARGS("t", flags),
1655
                                SD_BUS_RESULT("as", versions),
1656
                                target_method_list,
1657
                                SD_BUS_VTABLE_UNPRIVILEGED),
1658

1659
        SD_BUS_METHOD_WITH_ARGS("Describe",
1660
                                SD_BUS_ARGS("s", version, "t", flags),
1661
                                SD_BUS_RESULT("s", json),
1662
                                target_method_describe,
1663
                                SD_BUS_VTABLE_UNPRIVILEGED),
1664

1665
        SD_BUS_METHOD_WITH_ARGS("CheckNew",
1666
                                SD_BUS_NO_ARGS,
1667
                                SD_BUS_RESULT("s", new_version),
1668
                                target_method_check_new,
1669
                                SD_BUS_VTABLE_UNPRIVILEGED),
1670

1671
        SD_BUS_METHOD_WITH_ARGS("Acquire",
1672
                                SD_BUS_ARGS("s", new_version, "t", flags),
1673
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1674
                                target_method_acquire,
1675
                                SD_BUS_VTABLE_UNPRIVILEGED),
1676

1677
        SD_BUS_METHOD_WITH_ARGS("Install",
1678
                                SD_BUS_ARGS("s", new_version, "t", flags),
1679
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1680
                                target_method_install,
1681
                                SD_BUS_VTABLE_UNPRIVILEGED),
1682

1683
        SD_BUS_METHOD_WITH_ARGS("Vacuum",
1684
                                SD_BUS_NO_ARGS,
1685
                                SD_BUS_RESULT("u", instances, "u", disabled_transfers),
1686
                                target_method_vacuum,
1687
                                SD_BUS_VTABLE_UNPRIVILEGED),
1688

1689
        SD_BUS_METHOD_WITH_ARGS("GetAppStream",
1690
                                SD_BUS_NO_ARGS,
1691
                                SD_BUS_RESULT("as", appstream),
1692
                                target_method_get_appstream,
1693
                                SD_BUS_VTABLE_UNPRIVILEGED),
1694

1695
        SD_BUS_METHOD_WITH_ARGS("GetVersion",
1696
                                SD_BUS_NO_ARGS,
1697
                                SD_BUS_RESULT("s", version),
1698
                                target_method_get_version,
1699
                                SD_BUS_VTABLE_UNPRIVILEGED),
1700

1701
        SD_BUS_METHOD_WITH_ARGS("ListFeatures",
1702
                                SD_BUS_ARGS("t", flags),
1703
                                SD_BUS_RESULT("as", features),
1704
                                target_method_list_features,
1705
                                SD_BUS_VTABLE_UNPRIVILEGED),
1706

1707
        SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
1708
                                SD_BUS_ARGS("s", feature, "t", flags),
1709
                                SD_BUS_RESULT("s", json),
1710
                                target_method_describe_feature,
1711
                                SD_BUS_VTABLE_UNPRIVILEGED),
1712

1713
        SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
1714
                                SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
1715
                                SD_BUS_NO_RESULT,
1716
                                target_method_set_feature_enabled,
1717
                                SD_BUS_VTABLE_UNPRIVILEGED),
1718

1719
        SD_BUS_VTABLE_END
1720
};
1721

1722
static const BusObjectImplementation target_object = {
1723
        "/org/freedesktop/sysupdate1/target",
1724
        "org.freedesktop.sysupdate1.Target",
1725
        .fallback_vtables = BUS_FALLBACK_VTABLES({target_vtable, target_object_find}),
1726
        .node_enumerator = target_node_enumerator,
1727
};
1728

1729
static Manager *manager_free(Manager *m) {
1✔
1730
        if (!m)
1✔
1731
                return NULL;
1732

1733
        hashmap_free(m->targets);
1✔
1734
        hashmap_free(m->jobs);
1✔
1735

1736
        m->bus = sd_bus_flush_close_unref(m->bus);
1✔
1737
        free(m->notify_socket_path);
1✔
1738
        sd_event_unref(m->event);
1✔
1739

1740
        return mfree(m);
1✔
1741
}
1742

1743
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1744

1745
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
722✔
1746
        Manager *m = ASSERT_PTR(userdata);
722✔
1747
        int r;
722✔
1748

1749
        assert(fd >= 0);
722✔
1750

UNCOV
1751
        _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
×
1752
        _cleanup_free_ char *buf = NULL;
722✔
1753
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
722✔
1754
        if (r == -EAGAIN)
722✔
1755
                return 0;
1756
        if (r < 0)
722✔
1757
                return r;
1758

1759
        Job *j;
722✔
1760
        HASHMAP_FOREACH(j, m->jobs) {
898✔
1761
                PidRef child_pidref = PIDREF_NULL;
898✔
1762

1763
                r = event_source_get_child_pidref(j->child, &child_pidref);
898✔
1764
                if (r < 0)
898✔
UNCOV
1765
                        return log_warning_errno(r, "Failed to get child pidref: %m");
×
1766

1767
                if (pidref_equal(&sender_pidref, &child_pidref))
898✔
1768
                        break;
1769
        }
1770
        if (!j) {
722✔
UNCOV
1771
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1772
                return 0;
1773
        }
1774

1775
        char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
722✔
1776
        char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
722✔
1777
        char *errno_str = find_line_startswith(buf, "ERRNO=");
722✔
1778
        const char *ready = find_line_startswith(buf, "READY=1");
722✔
1779

1780
        if (version)
722✔
1781
                job_on_version(j, truncate_nl(version));
100✔
1782

1783
        if (progress)
722✔
1784
                job_on_progress(j, truncate_nl(progress));
258✔
1785

1786
        if (errno_str)
722✔
1787
                job_on_errno(j, truncate_nl(errno_str));
12✔
1788

1789
        /* Should come last, since this might actually detach the job */
1790
        if (ready)
722✔
1791
                job_on_ready(j);
100✔
1792

1793
        return 0;
1794
}
1795

1796
static int manager_new(Manager **ret) {
1✔
1797
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
1798
        int r;
1✔
1799

1800
        assert(ret);
1✔
1801

1802
        m = new(Manager, 1);
1✔
1803
        if (!m)
1✔
1804
                return -ENOMEM;
1805

1806
        *m = (Manager) {
1✔
1807
                .runtime_scope = RUNTIME_SCOPE_SYSTEM,
1808
        };
1809

1810
        r = sd_event_default(&m->event);
1✔
1811
        if (r < 0)
1✔
1812
                return r;
1813

1814
        (void) sd_event_set_watchdog(m->event, true);
1✔
1815

1816
        r = sd_event_set_signal_exit(m->event, true);
1✔
1817
        if (r < 0)
1✔
1818
                return r;
1819

1820
        r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK,
1✔
1821
                                sigrtmin18_handler, NULL);
1822
        if (r < 0)
1✔
1823
                return r;
1824

1825
        r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
1✔
1826
        if (r < 0)
1✔
UNCOV
1827
                log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m");
×
1828

1829
        r = sd_bus_default_system(&m->bus);
1✔
1830
        if (r < 0)
1✔
1831
                return r;
1832

1833
        r = notify_socket_prepare(
2✔
1834
                        m->event,
1835
                        SD_EVENT_PRIORITY_NORMAL - 1, /* Make this processed before worker exit. */
1836
                        manager_on_notify,
1837
                        m,
1838
                        &m->notify_socket_path);
1✔
1839
        if (r < 0)
1✔
1840
                return r;
1841

1842
        *ret = TAKE_PTR(m);
1✔
1843
        return 0;
1✔
1844
}
1845

1846
static int manager_enumerate_image_class(Manager *m, TargetClass class) {
688✔
1847
        _cleanup_hashmap_free_ Hashmap *images = NULL;
688✔
1848
        Image *image;
688✔
1849
        int r;
688✔
1850

1851
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, &images);
688✔
1852
        if (r < 0)
688✔
1853
                return r;
1854

1855
        HASHMAP_FOREACH(image, images) {
860✔
1856
                _cleanup_(target_freep) Target *t = NULL;
172✔
1857
                bool have = false;
172✔
1858

1859
                if (image_is_host(image))
172✔
1860
                        continue; /* We already enroll the host ourselves */
172✔
1861

UNCOV
1862
                if (image->type == IMAGE_MSTACK)
×
UNCOV
1863
                        continue; /* systemd-sysupdate doesn't support mstack images yet */
×
1864

UNCOV
1865
                r = target_new(m, class, image->name, image->path, &t);
×
UNCOV
1866
                if (r < 0)
×
1867
                        return r;
UNCOV
1868
                t->image_type = image->type;
×
1869

UNCOV
1870
                r = target_list_components(t, NULL, &have);
×
UNCOV
1871
                if (r < 0)
×
1872
                        return r;
UNCOV
1873
                if (!have) {
×
UNCOV
1874
                        log_debug("Skipping %s because it has no default component", image->path);
×
UNCOV
1875
                        continue;
×
1876
                }
1877

UNCOV
1878
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
UNCOV
1879
                if (r < 0)
×
1880
                        return r;
UNCOV
1881
                TAKE_PTR(t);
×
1882
        }
1883

1884
        return 0;
688✔
1885
}
1886

1887
static int manager_enumerate_components(Manager *m) {
172✔
1888
        _cleanup_strv_free_ char **components = NULL;
172✔
1889
        bool have_default;
172✔
1890
        int r;
172✔
1891

1892
        r = target_list_components(NULL, &components, &have_default);
172✔
1893
        if (r < 0)
172✔
1894
                return r;
1895

1896
        if (have_default) {
172✔
1897
                _cleanup_(target_freep) Target *t = NULL;
×
1898

1899
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
172✔
1900
                if (r < 0)
172✔
1901
                        return r;
1902

1903
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
172✔
1904
                if (r < 0)
172✔
1905
                        return r;
1906
                TAKE_PTR(t);
172✔
1907
        }
1908

1909
        STRV_FOREACH(component, components) {
236✔
1910
                _cleanup_free_ char *path = NULL;
64✔
1911
                _cleanup_(target_freep) Target *t = NULL;
64✔
1912

1913
                path = strjoin("sysupdate.", *component, ".d");
64✔
1914
                if (!path)
64✔
1915
                        return -ENOMEM;
1916

1917
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
64✔
1918
                if (r < 0)
64✔
1919
                        return r;
1920

1921
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
64✔
1922
                if (r < 0)
64✔
1923
                        return r;
1924
                TAKE_PTR(t);
64✔
1925
        }
1926

1927
        return 0;
1928
}
1929

1930
static int manager_enumerate_targets(Manager *m) {
172✔
1931
        static const TargetClass discoverable_classes[] = {
172✔
1932
                TARGET_MACHINE,
1933
                TARGET_PORTABLE,
1934
                TARGET_SYSEXT,
1935
                TARGET_CONFEXT,
1936
        };
1937
        int r;
172✔
1938

1939
        assert(m);
172✔
1940

1941
        FOREACH_ARRAY(class, discoverable_classes, ELEMENTSOF(discoverable_classes)) {
860✔
1942
                r = manager_enumerate_image_class(m, *class);
688✔
1943
                if (r < 0)
688✔
1944
                        log_warning_errno(r, "Failed to enumerate %ss, ignoring: %m",
688✔
1945
                                          target_class_to_string(*class));
1946
        }
1947

1948
        r = manager_enumerate_components(m);
172✔
1949
        if (r < 0)
172✔
UNCOV
1950
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1951

1952
        return 0;
172✔
1953
}
1954

1955
static int manager_ensure_targets(Manager *m) {
404✔
1956
        assert(m);
404✔
1957

1958
        if (!hashmap_isempty(m->targets))
404✔
1959
                return 0;
1960

1961
        return manager_enumerate_targets(m);
172✔
1962
}
1963

1964
static int method_list_targets(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
88✔
1965
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
88✔
1966
        Manager *m = ASSERT_PTR(userdata);
88✔
1967
        Target *t;
88✔
1968
        int r;
88✔
1969

1970
        assert(msg);
88✔
1971

1972
        r = manager_ensure_targets(m);
88✔
1973
        if (r < 0)
88✔
1974
                return r;
1975

1976
        r = sd_bus_message_new_method_return(msg, &reply);
88✔
1977
        if (r < 0)
88✔
1978
                return r;
1979

1980
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
88✔
1981
        if (r < 0)
88✔
1982
                return r;
1983

1984
        HASHMAP_FOREACH(t, m->targets) {
208✔
1985
                _cleanup_free_ char *bus_path = NULL;
120✔
1986

1987
                bus_path = target_bus_path(t);
120✔
1988
                if (!bus_path)
120✔
1989
                        return -ENOMEM;
1990

1991
                r = sd_bus_message_append(reply, "(sso)",
120✔
1992
                                          target_class_to_string(t->class),
1993
                                          t->name,
120✔
1994
                                          bus_path);
1995
                if (r < 0)
120✔
1996
                        return r;
1997
        }
1998

1999
        r = sd_bus_message_close_container(reply);
88✔
2000
        if (r < 0)
88✔
2001
                return r;
2002

2003
        return sd_bus_message_send(reply);
88✔
2004
}
2005

UNCOV
2006
static int method_list_jobs(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
2007
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
2008
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
2009
        Job *j;
×
UNCOV
2010
        int r;
×
2011

UNCOV
2012
        assert(msg);
×
2013

UNCOV
2014
        r = sd_bus_message_new_method_return(msg, &reply);
×
UNCOV
2015
        if (r < 0)
×
2016
                return r;
2017

UNCOV
2018
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
UNCOV
2019
        if (r < 0)
×
2020
                return r;
2021

UNCOV
2022
        HASHMAP_FOREACH(j, m->jobs) {
×
UNCOV
2023
                r = sd_bus_message_append(reply, "(tsuo)",
×
2024
                                          j->id,
2025
                                          job_type_to_string(j->type),
2026
                                          j->progress_percent,
UNCOV
2027
                                          j->object_path);
×
UNCOV
2028
                if (r < 0)
×
UNCOV
2029
                        return r;
×
2030
        }
2031

UNCOV
2032
        r = sd_bus_message_close_container(reply);
×
UNCOV
2033
        if (r < 0)
×
2034
                return r;
2035

2036
        return sd_bus_message_send(reply);
×
2037
}
2038

2039
static int method_list_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
2040
        _cleanup_strv_free_ char **urls = NULL;
×
2041
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
2042
        Manager *m = ASSERT_PTR(userdata);
×
2043
        Target *t;
×
2044
        int r;
×
2045

UNCOV
2046
        assert(msg);
×
2047

2048
        r = manager_ensure_targets(m);
×
UNCOV
2049
        if (r < 0)
×
2050
                return r;
2051

2052
        HASHMAP_FOREACH(t, m->targets) {
×
UNCOV
2053
                char **target_appstream;
×
2054

UNCOV
2055
                r = target_get_appstream(t, &target_appstream);
×
2056
                if (r < 0)
×
2057
                        return r;
×
2058

UNCOV
2059
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates= */ true);
×
UNCOV
2060
                if (r < 0)
×
2061
                        return r;
2062
        }
2063

UNCOV
2064
        r = sd_bus_message_new_method_return(msg, &reply);
×
2065
        if (r < 0)
×
2066
                return r;
2067

2068
        r = sd_bus_message_append_strv(reply, urls);
×
2069
        if (r < 0)
×
2070
                return r;
2071

2072
        return sd_bus_message_send(reply);
×
2073
}
2074

2075
static const sd_bus_vtable manager_vtable[] = {
2076
        SD_BUS_VTABLE_START(0),
2077

2078
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
2079
                                SD_BUS_NO_ARGS,
2080
                                SD_BUS_RESULT("a(sso)", targets),
2081
                                method_list_targets,
2082
                                SD_BUS_VTABLE_UNPRIVILEGED),
2083

2084
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
2085
                                SD_BUS_NO_ARGS,
2086
                                SD_BUS_RESULT("a(tsuo)", jobs),
2087
                                method_list_jobs,
2088
                                SD_BUS_VTABLE_UNPRIVILEGED),
2089

2090
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
2091
                                SD_BUS_NO_ARGS,
2092
                                SD_BUS_RESULT("as", urls),
2093
                                method_list_appstream,
2094
                                SD_BUS_VTABLE_UNPRIVILEGED),
2095

2096
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2097
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2098
                                0),
2099

2100
        SD_BUS_VTABLE_END
2101
};
2102

2103
static const BusObjectImplementation manager_object = {
2104
        "/org/freedesktop/sysupdate1",
2105
        "org.freedesktop.sysupdate1.Manager",
2106
        .vtables = BUS_VTABLES(manager_vtable),
2107
        .children = BUS_IMPLEMENTATIONS(&job_object, &target_object),
2108
};
2109

2110
static int manager_add_bus_objects(Manager *m) {
1✔
2111
        int r;
1✔
2112

2113
        assert(m);
1✔
2114

2115
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2116
        if (r < 0)
1✔
2117
                return r;
2118

2119
        r = bus_log_control_api_register(m->bus);
1✔
2120
        if (r < 0)
1✔
2121
                return r;
2122

2123
        r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.sysupdate1", 0, NULL, NULL);
1✔
2124
        if (r < 0)
1✔
UNCOV
2125
                return log_error_errno(r, "Failed to request name: %m");
×
2126

2127
        r = sd_bus_attach_event(m->bus, m->event, 0);
1✔
2128
        if (r < 0)
1✔
UNCOV
2129
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
2130

2131
        return 0;
2132
}
2133

2134
static bool manager_is_idle(void *userdata) {
1,381✔
2135
        Manager *m = ASSERT_PTR(userdata);
1,381✔
2136

2137
        return hashmap_isempty(m->jobs);
1,381✔
2138
}
2139

2140
static void manager_check_idle(Manager *m) {
252✔
2141
        assert(m);
252✔
2142

2143
        if (!hashmap_isempty(m->jobs))
252✔
2144
                return;
2145

2146
        hashmap_clear(m->targets);
172✔
2147
        log_debug("Cleared target cache");
172✔
2148
}
2149

2150
static int manager_run(Manager *m) {
1✔
2151
        assert(m);
1✔
2152

2153
        return bus_event_loop_with_idle(m->event,
1✔
2154
                                        m->bus,
2155
                                        "org.freedesktop.sysupdate1",
2156
                                        DEFAULT_EXIT_USEC,
2157
                                        manager_is_idle,
2158
                                        m);
2159
}
2160

2161
static int run(int argc, char *argv[]) {
1✔
2162
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
2163
        int r;
1✔
2164

2165
        log_setup();
1✔
2166

2167
        r = service_parse_argv("systemd-sysupdated.service",
2✔
2168
                               "System update management service.",
2169
                               BUS_IMPLEMENTATIONS(&manager_object,
1✔
2170
                                                   &log_control_object),
2171
                               /* runtime_scope= */ NULL,
2172
                               argc, argv);
2173
        if (r <= 0)
1✔
2174
                return r;
2175

2176
        umask(0022);
1✔
2177

2178
        r = manager_new(&m);
1✔
2179
        if (r < 0)
1✔
UNCOV
2180
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2181

2182
        r = manager_add_bus_objects(m);
1✔
2183
        if (r < 0)
1✔
UNCOV
2184
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2185

2186
        r = manager_run(m);
1✔
2187
        if (r < 0)
1✔
UNCOV
2188
                return log_error_errno(r, "Failed to run event loop: %m");
×
2189

2190
        return 0;
2191
}
2192

2193
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