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

systemd / systemd / 29968961921

22 Jul 2026 03:11PM UTC coverage: 73.067% (+0.2%) from 72.837%
29968961921

push

github

keszybz
homed: verify privileged worker changes

Workers may return a newer embedded identity while processing
an owner update. Accept self-modifiable changes, but require
privileged changes to carry a trusted signature.

Follow-up for 70a5db582

16 of 36 new or added lines in 1 file covered. (44.44%)

1697 existing lines in 49 files now uncovered.

346625 of 474391 relevant lines covered (73.07%)

1335649.2 hits per line

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

64.04
/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 "pidref.h"
36
#include "process-util.h"
37
#include "runtime-scope.h"
38
#include "service-util.h"
39
#include "signal-util.h"
40
#include "string-table.h"
41
#include "strv.h"
42
#include "sysupdate-target.h"
43
#include "sysupdate-util.h"
44

45
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
46

47
typedef struct Manager {
48
        sd_event *event;
49
        sd_bus *bus;
50

51
        Hashmap *targets;
52

53
        uint64_t last_job_id;
54
        Hashmap *jobs;
55

56
        Hashmap *polkit_registry;
57

58
        char *notify_socket_path;
59

60
        RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
61
} Manager;
62

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

66
typedef struct Target {
67
        Manager *manager;
68

69
        TargetClass class;
70
        char *name;
71
        char *path;
72

73
        char *id;
74
        ImageType image_type;
75
        bool busy;
76
} Target;
77

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

90
typedef struct Job Job;
91

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

95
struct Job {
96
        Manager *manager;
97
        Target *target;
98

99
        uint64_t id;
100
        char *object_path;
101

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

107
        unsigned progress_percent;
108

109
        sd_event_source *child;
110
        int stdout_fd;
111
        int status_errno;
112
        unsigned n_cancelled;
113

114
        sd_json_variant *json;
115

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

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

131
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
132

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

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

140
        free(j->object_path);
252✔
141
        free(j->version);
252✔
142
        free(j->feature);
252✔
143

144
        sd_json_variant_unref(j->json);
252✔
145

146
        sd_bus_message_unref(j->dbus_msg);
252✔
147

148
        sd_event_source_disable_unref(j->child);
252✔
149
        safe_close(j->stdout_fd);
252✔
150

151
        return mfree(j);
252✔
152
}
153

154
DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free);
504✔
155
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops,
×
156
                                              uint64_t, uint64_hash_func, uint64_compare_func,
157
                                              Job, job_free);
158

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

163
        assert(t);
252✔
164
        assert(ret);
252✔
165

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

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

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

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

186
        j->manager = t->manager;
252✔
187

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

190
        *ret = TAKE_PTR(j);
252✔
191
        return 0;
252✔
192
}
193

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

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

205
        assert(ret);
240✔
206

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

210
        assert(S_ISREG(st.st_mode));
240✔
211

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

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

227
        *ret = TAKE_PTR(v);
192✔
228
        return 0;
192✔
229
}
230

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

235
        assert(j);
100✔
236

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

242
        if (!j->detach_cb)
100✔
243
                return;
244

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

247
        assert(j->dbus_msg);
100✔
248
        msg = TAKE_PTR(j->dbus_msg);
100✔
249

250
        j->complete_cb = NULL;
100✔
251

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

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

260
        assert(j);
12✔
261
        assert(buf);
12✔
262

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

269
        j->status_errno = r;
12✔
270

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

274
static void job_on_progress(Job *j, const char *buf) {
249✔
275
        unsigned progress;
249✔
276
        int r;
249✔
277

278
        assert(j);
249✔
279
        assert(buf);
249✔
280

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

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

292
        log_debug("Got percentage from job %" PRIu64 ": %u%%", j->id, j->progress_percent);
249✔
293
}
294

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

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

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

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

312
        assert(j);
252✔
313
        assert(s);
252✔
314
        assert(si);
252✔
315

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

321
        if (si->si_code != CLD_EXITED) {
252✔
322
                assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
×
323
                sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
324
                                  "Job terminated abnormally with signal %s.",
325
                                  signal_to_string(si->si_status));
×
326
        } else if (si->si_status != EXIT_SUCCESS) {
252✔
327
                bool check_new_no_update = false;
44✔
328

329
                if (j->type == JOB_CHECK_NEW &&
44✔
330
                    si->si_status == EXIT_FAILURE &&
32✔
331
                    job_parse_child_output(TAKE_FD(j->stdout_fd), &json) >= 0) {
32✔
332
                        sd_json_variant *v = sd_json_variant_by_key(json, "available");
32✔
333
                        check_new_no_update = v && sd_json_variant_is_null(v);
32✔
334
                }
335

336
                if (!check_new_no_update) {
337
                        if (j->status_errno != 0)
12✔
338
                                sd_bus_error_set_errno(&error, j->status_errno);
12✔
339
                        else
UNCOV
340
                                sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
UNCOV
341
                                                  "Job failed with exit code %i.", si->si_status);
×
342
                }
343
        } else {
344
                r = job_parse_child_output(TAKE_FD(j->stdout_fd), &json);
208✔
345
                if (r < 0)
208✔
UNCOV
346
                        sd_bus_error_set_errnof(&error, r, "Failed to parse job worker output: %m");
×
347
        }
348

349
        /* Only send notification of exit if the job was actually detached */
350
        if (j->detach_cb) {
252✔
351
                r = sd_bus_emit_signal(
108✔
352
                                j->manager->bus,
108✔
353
                                "/org/freedesktop/sysupdate1",
354
                                "org.freedesktop.sysupdate1.Manager",
355
                                "JobRemoved",
356
                                "toi",
357
                                j->id,
358
                                j->object_path,
359
                                j->status_errno != 0 ? -j->status_errno : si->si_status);
108✔
360
                if (r < 0)
108✔
UNCOV
361
                        log_warning_errno(r,
×
362
                                          "Cannot emit JobRemoved message for job %" PRIu64 ", ignoring: %m",
363
                                          j->id);
364
        }
365

366
        if (j->dbus_msg && j->complete_cb) {
252✔
367
                if (sd_bus_error_is_set(&error)) {
152✔
368
                        log_warning("Job %" PRIu64 " failed with bus error, ignoring callback: %s",
8✔
369
                                    j->id, error.message);
370
                        sd_bus_reply_method_error(j->dbus_msg, &error);
8✔
371
                } else {
372
                        r = j->complete_cb(j->dbus_msg, j, json, &error);
144✔
373
                        if (r < 0) {
144✔
UNCOV
374
                                log_warning_errno(r,
×
375
                                                  "Error during execution of job callback for job %" PRIu64 ": %s",
376
                                                  j->id,
377
                                                  bus_error_message(&error, r));
UNCOV
378
                                sd_bus_reply_method_errno(j->dbus_msg, r, &error);
×
379
                        }
380
                }
381
        }
382

383
        job_free(j);
252✔
384

385
        if (manager)
252✔
386
                manager_check_idle(manager);
252✔
387

388
        return 0;
504✔
389
}
390

391
static inline const char* sysupdate_binary_path(void) {
456✔
392
        return secure_getenv("SYSTEMD_SYSUPDATE_PATH") ?: SYSTEMD_SYSUPDATE_PATH;
456✔
393
}
394

395
static int target_get_argument(Target *t, char **ret) {
284✔
396
        _cleanup_free_ char *target_arg = NULL;
568✔
397

398
        assert(t);
284✔
399
        assert(ret);
284✔
400

401
        if (t->class != TARGET_HOST) {
284✔
402
                if (t->class == TARGET_COMPONENT)
32✔
403
                        target_arg = strjoin("--component=", t->name);
32✔
UNCOV
404
                else if (IN_SET(t->image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME))
×
UNCOV
405
                        target_arg = strjoin("--root=", t->path);
×
UNCOV
406
                else if (IN_SET(t->image_type, IMAGE_RAW, IMAGE_BLOCK))
×
UNCOV
407
                        target_arg = strjoin("--image=", t->path);
×
408
                else
UNCOV
409
                        assert_not_reached();
×
410
                if (!target_arg)
32✔
411
                        return -ENOMEM;
412
        }
413

414
        *ret = TAKE_PTR(target_arg);
284✔
415
        return 0;
284✔
416
}
417

418
static int job_start(Job *j) {
252✔
419
        _cleanup_close_ int stdout_fd = -EBADF;
252✔
420
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
252✔
421
        int r;
252✔
422

423
        assert(j);
252✔
424

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

428
        stdout_fd = memfd_new("sysupdate-stdout");
252✔
429
        if (stdout_fd < 0)
252✔
430
                return log_error_errno(stdout_fd, "Failed to create memfd: %m");
×
431

432
        r = pidref_safe_fork_full("(sd-sysupdate)",
756✔
433
                                  (int[]) { -EBADF, stdout_fd, STDERR_FILENO }, NULL, 0,
252✔
434
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
435
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
436
        if (r < 0)
504✔
437
                return r; /* FORK_LOG means pidref_safe_fork_full will handle the logging */
438
        if (r == 0) {
504✔
439
                /* Child */
440

UNCOV
441
                _cleanup_free_ char *target_arg = NULL;
×
442
                const char *cmd[] = {
252✔
443
                        "systemd-sysupdate",
444
                        "--json=short",
445
                        NULL, /* maybe --verify=no */
446
                        NULL, /* maybe --component=, --root=, or --image= */
447
                        NULL, /* maybe --offline */
448
                        NULL, /* list, check-new, acquire, update, vacuum, features */
449
                        NULL, /* maybe version (for list, acquire, update), maybe feature (features) */
450
                        NULL
451
                };
452
                size_t k = 2;
252✔
453

454
                if (setenv("NOTIFY_SOCKET", j->manager->notify_socket_path, /* overwrite= */ 1) < 0) {
252✔
UNCOV
455
                        log_error_errno(errno, "setenv() failed: %m");
×
UNCOV
456
                        _exit(EXIT_FAILURE);
×
457
                }
458

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

462
                r = setenv_systemd_exec_pid(true);
252✔
463
                if (r < 0)
252✔
UNCOV
464
                        log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
×
465

466
                r = target_get_argument(j->target, &target_arg);
252✔
467
                if (r < 0) {
252✔
UNCOV
468
                        log_oom();
×
UNCOV
469
                        _exit(EXIT_FAILURE);
×
470
                }
471
                if (target_arg)
252✔
472
                        cmd[k++] = target_arg;
16✔
473

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

477
                switch (j->type) {
252✔
478
                case JOB_LIST:
16✔
479
                        cmd[k++] = "list";
16✔
480
                        break;
16✔
481

482
                case JOB_DESCRIBE:
96✔
483
                        cmd[k++] = "list";
96✔
484
                        assert(!isempty(j->version));
96✔
485
                        cmd[k++] = j->version;
96✔
486
                        break;
96✔
487

488
                case JOB_CHECK_NEW:
32✔
489
                        cmd[k++] = "check-new";
32✔
490
                        break;
32✔
491

492
                case JOB_ACQUIRE:
56✔
493
                        cmd[k++] = "acquire";
56✔
494
                        cmd[k++] = empty_to_null(j->version);
112✔
495
                        break;
56✔
496

497
                case JOB_INSTALL:
52✔
498
                        cmd[k++] = "update";  /* install is implemented as `update --offline` */
52✔
499
                        cmd[k++] = empty_to_null(j->version);
104✔
500
                        break;
52✔
501

502
                case JOB_VACUUM:
×
UNCOV
503
                        cmd[k++] = "vacuum";
×
UNCOV
504
                        break;
×
505

UNCOV
506
                case JOB_DESCRIBE_FEATURE:
×
UNCOV
507
                        cmd[k++] = "features";
×
UNCOV
508
                        assert(!isempty(j->feature));
×
UNCOV
509
                        cmd[k++] = j->feature;
×
510
                        break;
×
511

UNCOV
512
                default:
×
UNCOV
513
                        assert_not_reached();
×
514
                }
515

516
                if (DEBUG_LOGGING) {
252✔
517
                        _cleanup_free_ char *s = NULL;
252✔
518

519
                        s = quote_command_line((char**) cmd, SHELL_ESCAPE_EMPTY);
252✔
520
                        if (!s) {
252✔
UNCOV
521
                                log_oom();
×
UNCOV
522
                                _exit(EXIT_FAILURE);
×
523
                        }
524

525
                        log_debug("Spawning worker for job %" PRIu64 ": %s", j->id, s);
252✔
526
                }
527

528
                r = invoke_callout_binary(sysupdate_binary_path(), (char *const *) cmd);
504✔
UNCOV
529
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
UNCOV
530
                _exit(EXIT_FAILURE);
×
531
        }
532

533
        log_info("Started job %" PRIu64 " with worker PID " PID_FMT,
252✔
534
                 j->id, pid.pid);
535

536
        r = event_add_child_pidref(j->manager->event, &j->child, &pid, WEXITED, job_on_exit, j);
252✔
537
        if (r < 0)
252✔
UNCOV
538
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
539

540
        r = sd_event_source_set_child_process_own(j->child, true);
252✔
541
        if (r < 0)
252✔
542
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
543
        pidref_done(&pid); /* disarm sigkill_wait */
252✔
544

545
        j->stdout_fd = TAKE_FD(stdout_fd);
252✔
546

547
        if (job_requires_busy(j))
252✔
548
                j->target->busy = true;
108✔
549

550
        return 0;
551
}
552

553
static int job_cancel(Job *j) {
×
UNCOV
554
        int r;
×
555

556
        assert(j);
×
557

558
        r = sd_event_source_send_child_signal(j->child, j->n_cancelled < 3 ? SIGTERM : SIGKILL,
×
559
                                              NULL, 0);
UNCOV
560
        if (r < 0)
×
561
                return r;
562

563
        j->n_cancelled++;
×
UNCOV
564
        return 0;
×
565
}
566

UNCOV
567
static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
568
        Job *j = ASSERT_PTR(userdata);
×
UNCOV
569
        const char *action;
×
570
        int r;
×
571

572
        assert(msg);
×
573

UNCOV
574
        switch (j->type) {
×
575
        case JOB_LIST:
576
        case JOB_DESCRIBE:
577
        case JOB_CHECK_NEW:
578
                action = "org.freedesktop.sysupdate1.cancel-check";
579
                break;
580

UNCOV
581
        case JOB_ACQUIRE:
×
582
        case JOB_INSTALL:
UNCOV
583
                if (j->version)
×
584
                        action = "org.freedesktop.sysupdate1.cancel-update-to-version";
585
                else
586
                        action = "org.freedesktop.sysupdate1.cancel-update";
×
587
                break;
588

589
        case JOB_VACUUM:
590
                action = "org.freedesktop.sysupdate1.cancel-vacuum";
×
591
                break;
×
592

593
        case JOB_DESCRIBE_FEATURE:
594
                action = NULL;
595
                break;
596

597
        default:
×
UNCOV
598
                assert_not_reached();
×
599
        }
600

UNCOV
601
        if (action) {
×
UNCOV
602
                r = bus_verify_polkit_async(
×
603
                                msg,
604
                                action,
605
                                /* details= */ NULL,
UNCOV
606
                                &j->manager->polkit_registry,
×
607
                                error);
UNCOV
608
                if (r < 0)
×
609
                        return r;
610
                if (r == 0)
×
611
                        return 1; /* Will call us back */
612
        }
613

UNCOV
614
        r = job_cancel(j);
×
UNCOV
615
        if (r < 0)
×
616
                return r;
617

UNCOV
618
        return sd_bus_reply_method_return(msg, NULL);
×
619
}
620

UNCOV
621
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
622

623
static int job_object_find(
249✔
624
                sd_bus *bus,
625
                const char *path,
626
                const char *iface,
627
                void *userdata,
628
                void **ret,
629
                sd_bus_error *error) {
630

631
        Manager *m = ASSERT_PTR(userdata);
249✔
632
        Job *j;
249✔
633
        const char *p;
249✔
634
        uint64_t id;
249✔
635
        int r;
249✔
636

637
        assert(bus);
249✔
638
        assert(path);
249✔
639
        assert(ret);
249✔
640

641
        p = startswith(path, "/org/freedesktop/sysupdate1/job/_");
249✔
642
        if (!p)
249✔
643
                return 0;
249✔
644

645
        r = safe_atou64(p, &id);
249✔
646
        if (r < 0 || id == 0)
249✔
647
                return 0;
648

649
        j = hashmap_get(m->jobs, &id);
249✔
650
        if (!j)
249✔
651
                return 0;
652

653
        *ret = j;
249✔
654
        return 1;
249✔
655
}
656

UNCOV
657
static int job_node_enumerator(
×
658
                sd_bus *bus,
659
                const char *path,
660
                void *userdata,
661
                char ***nodes,
662
                sd_bus_error *error) {
663

664
        _cleanup_strv_free_ char **l = NULL;
×
665
        Manager *m = ASSERT_PTR(userdata);
×
666
        Job *j;
×
UNCOV
667
        unsigned k = 0;
×
668

669
        l = new0(char*, hashmap_size(m->jobs) + 1);
×
670
        if (!l)
×
671
                return -ENOMEM;
672

UNCOV
673
        HASHMAP_FOREACH(j, m->jobs) {
×
UNCOV
674
                l[k] = strdup(j->object_path);
×
UNCOV
675
                if (!l[k])
×
UNCOV
676
                        return -ENOMEM;
×
UNCOV
677
                k++;
×
678
        }
679

UNCOV
680
        *nodes = TAKE_PTR(l);
×
UNCOV
681
        return 1;
×
682
}
683

684
static const sd_bus_vtable job_vtable[] = {
685
        SD_BUS_VTABLE_START(0),
686

687
        SD_BUS_PROPERTY("Id", "t", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
688
        SD_BUS_PROPERTY("Type", "s", job_property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
689
        SD_BUS_PROPERTY("Offline", "b", bus_property_get_bool, offsetof(Job, offline), SD_BUS_VTABLE_PROPERTY_CONST),
690
        SD_BUS_PROPERTY("Progress", "u", bus_property_get_unsigned, offsetof(Job, progress_percent), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
691

692
        SD_BUS_METHOD("Cancel", NULL, NULL, job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
693

694
        SD_BUS_VTABLE_END
695
};
696

697
static const BusObjectImplementation job_object = {
698
        "/org/freedesktop/sysupdate1/job",
699
        "org.freedesktop.sysupdate1.Job",
700
        .fallback_vtables = BUS_FALLBACK_VTABLES({job_vtable, job_object_find}),
701
        .node_enumerator = job_node_enumerator,
702
};
703

704
static Target *target_free(Target *t) {
236✔
705
        if (!t)
236✔
706
                return NULL;
707

708
        free(t->name);
236✔
709
        free(t->path);
236✔
710
        free(t->id);
236✔
711

712
        return mfree(t);
236✔
713
}
714

715
DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free);
644✔
716
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops,
236✔
717
                                              char, string_hash_func, string_compare_func,
718
                                              Target, target_free);
719

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

723
        assert(m);
236✔
724
        assert(ret);
236✔
725

726
        t = new(Target, 1);
236✔
727
        if (!t)
236✔
728
                return -ENOMEM;
729

730
        *t = (Target) {
236✔
731
                .manager = m,
732
                .class = class,
733
                .image_type = _IMAGE_TYPE_INVALID,
734
        };
735

736
        t->name = strdup(name);
236✔
737
        if (!t->name)
236✔
738
                return -ENOMEM;
739

740
        t->path = strdup(path);
236✔
741
        if (!t->path)
236✔
742
                return -ENOMEM;
743

744
        if (class == TARGET_HOST)
236✔
745
                t->id = strdup("host"); /* This is what appears in the object path */
172✔
746
        else
747
                t->id = strjoin(target_class_to_string(class), ":", name);
64✔
748
        if (!t->id)
236✔
749
                return -ENOMEM;
750

751
        *ret = TAKE_PTR(t);
236✔
752
        return 0;
236✔
753
}
754

755
static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) {
204✔
756
        _cleanup_close_pair_ int pipe[2] = EBADF_PAIR;
204✔
UNCOV
757
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
×
758
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
204✔
759
        _cleanup_free_ char *target_arg = NULL;
204✔
760
        int r;
204✔
761

762
        if (t) {
204✔
763
                r = target_get_argument(t, &target_arg);
32✔
764
                if (r < 0)
32✔
765
                        return r;
766
        }
767

768
        r = pipe2(pipe, O_CLOEXEC);
204✔
769
        if (r < 0)
204✔
UNCOV
770
                return -errno;
×
771

772
        r = pidref_safe_fork_full("(sd-sysupdate)",
612✔
773
                                  (int[]) { -EBADF, pipe[1], STDERR_FILENO },
204✔
774
                                  NULL, 0,
775
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
776
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG,
777
                                  &pid);
778
        if (r < 0)
408✔
779
                return r;
780
        if (r == 0) {
408✔
781
                /* Child */
782
                va_list ap;
204✔
783
                char *arg;
204✔
UNCOV
784
                _cleanup_strv_free_ char **args = NULL;
×
785

786
                if (strv_extend(&args, "systemd-sysupdate") < 0) {
204✔
787
                        log_oom();
×
UNCOV
788
                        _exit(EXIT_FAILURE);
×
789
                }
790

791
                if (strv_extend(&args, "--json=short") < 0) {
204✔
UNCOV
792
                        log_oom();
×
UNCOV
793
                        _exit(EXIT_FAILURE);
×
794
                }
795

796
                if (target_arg && strv_extend(&args, target_arg) < 0) {
204✔
UNCOV
797
                        log_oom();
×
798
                        _exit(EXIT_FAILURE);
×
799
                }
800

801
                va_start(ap, t);
204✔
802
                while ((arg = va_arg(ap, char*))) {
440✔
803
                        r = strv_extend(&args, arg);
236✔
804
                        if (r < 0)
236✔
805
                                break;
806
                }
807
                va_end(ap);
204✔
808
                if (r < 0) {
204✔
UNCOV
809
                        log_oom();
×
UNCOV
810
                        _exit(EXIT_FAILURE);
×
811
                }
812

813
                if (DEBUG_LOGGING) {
204✔
814
                        _cleanup_free_ char *s = NULL;
204✔
815

816
                        s = quote_command_line(args, SHELL_ESCAPE_EMPTY);
204✔
817
                        if (!s) {
204✔
UNCOV
818
                                log_oom();
×
UNCOV
819
                                _exit(EXIT_FAILURE);
×
820
                        }
821

822
                        log_debug("Spawning sysupdate: %s", s);
204✔
823
                }
824

825
                r = invoke_callout_binary(sysupdate_binary_path(), args);
408✔
UNCOV
826
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
UNCOV
827
                _exit(EXIT_FAILURE);
×
828
        }
829

830
        pipe[1] = safe_close(pipe[1]);
204✔
831

832
        r = sd_json_parse_fd(
408✔
833
                        "stdout",
834
                        TAKE_FD(pipe[0]),
204✔
835
                        SD_JSON_PARSE_DONATE_FD,
836
                        &v,
837
                        /* reterr_line= */ NULL,
838
                        /* reterr_column= */ NULL);
839
        if (r < 0)
204✔
UNCOV
840
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
841

842
        *ret = TAKE_PTR(v);
204✔
843
        return 0;
204✔
844
}
845

UNCOV
846
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
847

848
#define log_sysupdate_bad_json_full(lvl, r, verb, msg) \
849
        log_full_errno((lvl), (r), "Invalid JSON response from 'systemd-sysupdate %s': %s", (verb), (msg))
850
#define log_sysupdate_bad_json(r, verb, msg) \
851
        log_sysupdate_bad_json_full(LOG_ERR, (r), (verb), (msg))
852
#define log_sysupdate_bad_json_debug(r, verb, msg) \
853
        log_sysupdate_bad_json_full(LOG_DEBUG, (r), (verb), (msg))
854

855
static int target_method_list_finish(
16✔
856
                sd_bus_message *msg,
857
                const Job *j,
858
                sd_json_variant *json,
859
                sd_bus_error *error) {
860

861
        sd_json_variant *v;
16✔
UNCOV
862
        _cleanup_strv_free_ char **versions = NULL;
×
863
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
16✔
864
        int r;
16✔
865

866
        assert(json);
16✔
867

868
        v = sd_json_variant_by_key(json, "all");
16✔
869
        if (!v)
16✔
UNCOV
870
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'all'");
×
871

872
        r = sd_json_variant_strv(v, &versions);
16✔
873
        if (r < 0)
16✔
UNCOV
874
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'all' should be strv");
×
875

876
        r = sd_bus_message_new_method_return(msg, &reply);
16✔
877
        if (r < 0)
16✔
878
                return r;
879

880
        r = sd_bus_message_append_strv(reply, versions);
16✔
881
        if (r < 0)
16✔
882
                return r;
883

884
        return sd_bus_message_send(reply);
16✔
885
}
886

887
static int target_method_list(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
16✔
888
        Target *t = ASSERT_PTR(userdata);
16✔
889
        _cleanup_(job_freep) Job *j = NULL;
16✔
890
        uint64_t flags;
16✔
891
        int r;
16✔
892

893
        assert(msg);
16✔
894

895
        r = sd_bus_message_read(msg, "t", &flags);
16✔
896
        if (r < 0)
16✔
897
                return r;
898

899
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
16✔
UNCOV
900
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
901

902
        const char *details[] = {
48✔
903
                "class", target_class_to_string(t->class),
16✔
904
                "name", t->name,
16✔
905
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
16✔
906
                NULL
907
        };
908

909
        r = bus_verify_polkit_async(
32✔
910
                msg,
911
                "org.freedesktop.sysupdate1.check",
912
                details,
913
                &t->manager->polkit_registry,
16✔
914
                error);
915
        if (r < 0)
16✔
916
                return r;
917
        if (r == 0)
16✔
918
                return 1; /* Will call us back */
919

920
        r = job_new(JOB_LIST, t, msg, target_method_list_finish, &j);
16✔
921
        if (r < 0)
16✔
922
                return r;
923

924
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
16✔
925

926
        r = job_start(j);
16✔
927
        if (r < 0)
16✔
UNCOV
928
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
929
        TAKE_PTR(j); /* Avoid job from being killed & freed */
16✔
930

931
        return 1;
16✔
932
}
933

934
static int target_method_describe_finish(
96✔
935
                sd_bus_message *msg,
936
                const Job *j,
937
                sd_json_variant *json,
938
                sd_bus_error *error) {
939
        _cleanup_free_ char *text = NULL;
96✔
940
        int r;
96✔
941

942
        /* NOTE: This is also reused by target_method_describe_feature */
943

944
        assert(json);
96✔
945

946
        r = sd_json_variant_format(json, 0, &text);
96✔
947
        if (r < 0)
96✔
948
                return r;
949

950
        return sd_bus_reply_method_return(msg, "s", text);
96✔
951
}
952

953
static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
96✔
954
        Target *t = ASSERT_PTR(userdata);
96✔
955
        _cleanup_(job_freep) Job *j = NULL;
96✔
956
        const char *version;
96✔
957
        uint64_t flags;
96✔
958
        int r;
96✔
959

960
        assert(msg);
96✔
961

962
        r = sd_bus_message_read(msg, "st", &version, &flags);
96✔
963
        if (r < 0)
96✔
964
                return r;
965

966
        if (!version_is_valid(version, VERSION_ALLOW_UNDERSCORE|VERSION_ALLOW_PLUS))
96✔
UNCOV
967
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
968

969
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
96✔
UNCOV
970
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
971

972
        const char *details[] = {
288✔
973
                "class", target_class_to_string(t->class),
96✔
974
                "name", t->name,
96✔
975
                "version", version,
976
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
96✔
977
                NULL
978
        };
979

980
        r = bus_verify_polkit_async(
192✔
981
                msg,
982
                "org.freedesktop.sysupdate1.check",
983
                details,
984
                &t->manager->polkit_registry,
96✔
985
                error);
986
        if (r < 0)
96✔
987
                return r;
988
        if (r == 0)
96✔
989
                return 1; /* Will call us back */
990

991
        r = job_new(JOB_DESCRIBE, t, msg, target_method_describe_finish, &j);
96✔
992
        if (r < 0)
96✔
993
                return r;
994

995
        j->version = strdup(version);
96✔
996
        if (!j->version)
96✔
UNCOV
997
                return log_oom();
×
998

999
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
96✔
1000

1001
        r = job_start(j);
96✔
1002
        if (r < 0)
96✔
UNCOV
1003
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1004
        TAKE_PTR(j); /* Avoid job from being killed & freed */
96✔
1005

1006
        return 1;
96✔
1007
}
1008

1009
static int target_method_check_new_finish(
32✔
1010
                sd_bus_message *msg,
1011
                const Job *j,
1012
                sd_json_variant *json,
1013
                sd_bus_error *error) {
1014
        const char *reply;
32✔
1015

1016
        assert(json);
32✔
1017

1018
        sd_json_variant *v = sd_json_variant_by_key(json, "available");
32✔
1019
        if (!v)
32✔
UNCOV
1020
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "check-new", "Missing key 'available'");
×
1021

1022
        if (sd_json_variant_is_null(v))
32✔
1023
                reply = "";
1024
        else
UNCOV
1025
                reply = sd_json_variant_string(v);
×
UNCOV
1026
        if (!reply)
×
1027
                return -EINVAL;
1028

1029
        return sd_bus_reply_method_return(msg, "s", reply);
32✔
1030
}
1031

1032
static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
32✔
1033
        Target *t = ASSERT_PTR(userdata);
32✔
1034
        _cleanup_(job_freep) Job *j = NULL;
32✔
1035
        int r;
32✔
1036

1037
        assert(msg);
32✔
1038

1039
        const char *details[] = {
64✔
1040
                "class", target_class_to_string(t->class),
32✔
1041
                "name", t->name,
32✔
1042
                "offline", "0",
1043
                NULL
1044
        };
1045

1046
        r = bus_verify_polkit_async(
64✔
1047
                        msg,
1048
                        "org.freedesktop.sysupdate1.check",
1049
                        details,
1050
                        &t->manager->polkit_registry,
32✔
1051
                        error);
1052
        if (r < 0)
32✔
1053
                return r;
1054
        if (r == 0)
32✔
1055
                return 1; /* Will call us back */
1056

1057
        r = job_new(JOB_CHECK_NEW, t, msg, target_method_check_new_finish, &j);
32✔
1058
        if (r < 0)
32✔
1059
                return r;
1060

1061
        r = job_start(j);
32✔
1062
        if (r < 0)
32✔
UNCOV
1063
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1064
        TAKE_PTR(j); /* Avoid job from being killed & freed */
32✔
1065

1066
        return 1;
32✔
1067
}
1068

UNCOV
1069
static int target_method_acquire_finished_early(
×
1070
                sd_bus_message *msg,
1071
                const Job *j,
1072
                sd_json_variant *json,
1073
                sd_bus_error *error) {
1074

1075
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1076
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1077
         * specified update is already acquired. */
UNCOV
1078
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1079
                                 "Job exited successfully with no work to do, assume already acquired");
1080
}
1081

1082
static int target_method_acquire_detach(sd_bus_message *msg, const Job *j) {
52✔
1083
        int r;
52✔
1084

1085
        assert(msg);
52✔
1086
        assert(j);
52✔
1087

1088
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
52✔
1089
        if (r < 0)
52✔
UNCOV
1090
                return bus_log_parse_error(r);
×
1091

1092
        return 0;
1093
}
1094

1095
static int target_method_acquire(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
56✔
1096
        Target *t = ASSERT_PTR(userdata);
56✔
1097
        _cleanup_(job_freep) Job *j = NULL;
56✔
1098
        const char *version, *action;
56✔
1099
        uint64_t flags;
56✔
1100
        int r;
56✔
1101

1102
        assert(msg);
56✔
1103

1104
        r = sd_bus_message_read(msg, "st", &version, &flags);
56✔
1105
        if (r < 0)
56✔
1106
                return r;
1107

1108
        if (flags != 0)
56✔
UNCOV
1109
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1110

1111
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1112
         * an update anyway. */
1113
        if (isempty(version))
56✔
1114
                action = "org.freedesktop.sysupdate1.update";
1115
        else {
UNCOV
1116
                if (!version_is_valid(version, VERSION_ALLOW_UNDERSCORE|VERSION_ALLOW_PLUS))
×
UNCOV
1117
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1118

1119
                action = "org.freedesktop.sysupdate1.update-to-version";
1120
        }
1121

1122
        const char *details[] = {
112✔
1123
                "class", target_class_to_string(t->class),
56✔
1124
                "name", t->name,
56✔
1125
                "version", version,
1126
                NULL
1127
        };
1128

1129
        r = bus_verify_polkit_async(
112✔
1130
                        msg,
1131
                        action,
1132
                        details,
1133
                        &t->manager->polkit_registry,
56✔
1134
                        error);
1135
        if (r < 0)
56✔
1136
                return r;
1137
        if (r == 0)
56✔
1138
                return 1; /* Will call us back */
1139

1140
        r = job_new(JOB_ACQUIRE, t, msg, target_method_acquire_finished_early, &j);
56✔
1141
        if (r < 0)
56✔
1142
                return r;
1143
        j->detach_cb = target_method_acquire_detach;
56✔
1144

1145
        j->version = strdup(version);
56✔
1146
        if (!j->version)
56✔
1147
                return -ENOMEM;
1148

1149
        r = job_start(j);
56✔
1150
        if (r < 0)
56✔
UNCOV
1151
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1152
        TAKE_PTR(j);
56✔
1153

1154
        return 1;
56✔
1155
}
1156

UNCOV
1157
static int target_method_install_finished_early(
×
1158
                sd_bus_message *msg,
1159
                const Job *j,
1160
                sd_json_variant *json,
1161
                sd_bus_error *error) {
1162

1163
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1164
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1165
         * specified update is already installed. */
UNCOV
1166
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1167
                                 "Job exited successfully with no work to do, assume already installed");
1168
}
1169

1170
static int target_method_install_detach(sd_bus_message *msg, const Job *j) {
48✔
1171
        int r;
48✔
1172

1173
        assert(msg);
48✔
1174
        assert(j);
48✔
1175

1176
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
48✔
1177
        if (r < 0)
48✔
UNCOV
1178
                return bus_log_parse_error(r);
×
1179

1180
        return 0;
1181
}
1182

1183
static int target_method_install(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
52✔
1184
        Target *t = ASSERT_PTR(userdata);
52✔
1185
        _cleanup_(job_freep) Job *j = NULL;
52✔
1186
        const char *version, *action;
52✔
1187
        uint64_t flags;
52✔
1188
        int r;
52✔
1189

1190
        assert(msg);
52✔
1191

1192
        r = sd_bus_message_read(msg, "st", &version, &flags);
52✔
1193
        if (r < 0)
52✔
1194
                return r;
1195

1196
        if (flags != 0)
52✔
UNCOV
1197
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1198

1199
        /* We don’t have a separate polkit action for acquire/install as they are both effectively (part of)
1200
         * an update anyway. */
1201
        if (isempty(version))
52✔
1202
                action = "org.freedesktop.sysupdate1.update";
1203
        else {
UNCOV
1204
                if (!version_is_valid(version, VERSION_ALLOW_UNDERSCORE|VERSION_ALLOW_PLUS))
×
UNCOV
1205
                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid version");
×
1206

1207
                action = "org.freedesktop.sysupdate1.update-to-version";
1208
        }
1209

1210
        const char *details[] = {
104✔
1211
                "class", target_class_to_string(t->class),
52✔
1212
                "name", t->name,
52✔
1213
                "version", version,
1214
                NULL
1215
        };
1216

1217
        r = bus_verify_polkit_async(
104✔
1218
                        msg,
1219
                        action,
1220
                        details,
1221
                        &t->manager->polkit_registry,
52✔
1222
                        error);
1223
        if (r < 0)
52✔
1224
                return r;
1225
        if (r == 0)
52✔
1226
                return 1; /* Will call us back */
1227

1228
        r = job_new(JOB_INSTALL, t, msg, target_method_install_finished_early, &j);
52✔
1229
        if (r < 0)
52✔
1230
                return r;
1231
        j->detach_cb = target_method_install_detach;
52✔
1232

1233
        j->version = strdup(version);
52✔
1234
        if (!j->version)
52✔
1235
                return -ENOMEM;
1236

1237
        r = job_start(j);
52✔
1238
        if (r < 0)
52✔
UNCOV
1239
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1240
        TAKE_PTR(j);
52✔
1241

1242
        return 1;
52✔
1243
}
1244

1245
static int target_method_vacuum_finish(
×
1246
                sd_bus_message *msg,
1247
                const Job *j,
1248
                sd_json_variant *json,
1249
                sd_bus_error *error) {
1250

1251
        sd_json_variant *v;
×
UNCOV
1252
        uint64_t instances, disabled;
×
1253

1254
        assert(json);
×
1255

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

1264
        v = sd_json_variant_by_key(json, "disabledTransfers");
×
1265
        if (!v)
×
1266
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
×
1267
        if (!sd_json_variant_is_unsigned(v))
×
UNCOV
1268
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
×
1269
        disabled = sd_json_variant_unsigned(v);
×
UNCOV
1270
        assert(disabled <= UINT32_MAX);
×
1271

1272
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1273
}
1274

UNCOV
1275
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
1276
        Target *t = ASSERT_PTR(userdata);
×
1277
        _cleanup_(job_freep) Job *j = NULL;
×
UNCOV
1278
        int r;
×
1279

UNCOV
1280
        assert(msg);
×
1281

UNCOV
1282
        const char *details[] = {
×
1283
                "class", target_class_to_string(t->class),
×
UNCOV
1284
                "name", t->name,
×
1285
                NULL
1286
        };
1287

1288
        r = bus_verify_polkit_async(
×
1289
                msg,
1290
                "org.freedesktop.sysupdate1.vacuum",
1291
                details,
1292
                &t->manager->polkit_registry,
×
1293
                error);
1294
        if (r < 0)
×
1295
                return r;
UNCOV
1296
        if (r == 0)
×
1297
                return 1; /* Will call us back */
1298

UNCOV
1299
        r = job_new(JOB_VACUUM, t, msg, target_method_vacuum_finish, &j);
×
UNCOV
1300
        if (r < 0)
×
1301
                return r;
1302

UNCOV
1303
        r = job_start(j);
×
UNCOV
1304
        if (r < 0)
×
UNCOV
1305
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
UNCOV
1306
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1307

1308
        return 1;
×
1309
}
1310

1311
static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
32✔
1312
        Target *t = ASSERT_PTR(userdata);
32✔
1313
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
32✔
1314
        sd_json_variant *version_json;
32✔
1315
        int r;
32✔
1316

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

1321
        version_json = sd_json_variant_by_key(v, "current");
32✔
1322
        if (!version_json)
32✔
UNCOV
1323
                version_json = sd_json_variant_by_key(v, "current+pending");
×
UNCOV
1324
        if (!version_json)
×
1325
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current' or 'current+pending'");
×
1326

1327
        if (sd_json_variant_is_null(version_json))
32✔
1328
                return sd_bus_reply_method_return(msg, "s", "");
×
1329

1330
        if (!sd_json_variant_is_string(version_json))
32✔
1331
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'current' should be a string");
×
1332

1333
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
32✔
1334
}
1335

1336
static int target_get_appstream(Target *t, char ***ret) {
×
UNCOV
1337
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1338
        sd_json_variant *appstream_url_json;
×
1339
        int r;
×
1340

UNCOV
1341
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
×
UNCOV
1342
        if (r < 0)
×
UNCOV
1343
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1344

1345
        appstream_url_json = sd_json_variant_by_key(v, "appstreamUrls");
×
1346
        if (!appstream_url_json)
×
1347
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'appstreamUrls'");
×
1348

1349
        r = sd_json_variant_strv(appstream_url_json, ret);
×
UNCOV
1350
        if (r < 0)
×
1351
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'appstreamUrls' should be strv");
×
1352

1353
        return 0;
1354
}
1355

1356
static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
1357
        Target *t = ASSERT_PTR(userdata);
×
UNCOV
1358
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1359
        _cleanup_strv_free_ char **appstream_urls = NULL;
×
1360
        int r;
×
1361

UNCOV
1362
        r = target_get_appstream(t, &appstream_urls);
×
1363
        if (r < 0)
×
1364
                return r;
1365

1366
        r = sd_bus_message_new_method_return(msg, &reply);
×
1367
        if (r < 0)
×
1368
                return r;
1369

1370
        r = sd_bus_message_append_strv(reply, appstream_urls);
×
1371
        if (r < 0)
×
1372
                return r;
1373

UNCOV
1374
        return sd_bus_message_send(reply);
×
1375
}
1376

1377
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1378
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
UNCOV
1379
        _cleanup_strv_free_ char **features = NULL;
×
1380
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1381
        Target *t = ASSERT_PTR(userdata);
×
UNCOV
1382
        sd_json_variant *v;
×
1383
        uint64_t flags;
×
1384
        int r;
×
1385

UNCOV
1386
        assert(msg);
×
1387

1388
        r = sd_bus_message_read(msg, "t", &flags);
×
UNCOV
1389
        if (r < 0)
×
1390
                return r;
1391
        if (flags != 0)
×
UNCOV
1392
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1393

1394
        r = sysupdate_run_simple(&json, t, "--offline", "features", NULL);
×
1395
        if (r < 0)
×
1396
                return r;
1397

1398
        v = sd_json_variant_by_key(json, "features");
×
1399
        if (!v)
×
1400
                return -EINVAL;
UNCOV
1401
        r = sd_json_variant_strv(v, &features);
×
1402
        if (r < 0)
×
1403
                return r;
1404

1405
        r = sd_bus_message_new_method_return(msg, &reply);
×
1406
        if (r < 0)
×
1407
                return r;
1408

1409
        r = sd_bus_message_append_strv(reply, features);
×
1410
        if (r < 0)
×
1411
                return r;
1412

UNCOV
1413
        return sd_bus_message_send(reply);
×
1414
}
1415

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

UNCOV
1423
        assert(msg);
×
1424

1425
        r = sd_bus_message_read(msg, "st", &feature, &flags);
×
UNCOV
1426
        if (r < 0)
×
1427
                return r;
1428

1429
        if (!feature_name_valid(feature))
×
1430
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid feature name");
×
1431

1432
        if (flags != 0)
×
1433
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1434

1435
        r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
×
UNCOV
1436
        if (r < 0)
×
1437
                return r;
1438

UNCOV
1439
        j->feature = strdup(feature);
×
1440
        if (!j->feature)
×
1441
                return log_oom();
×
1442

1443
        r = job_start(j);
×
1444
        if (r < 0)
×
1445
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1446
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1447

1448
        return 1;
×
1449
}
1450

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

1459
        assert(msg);
×
1460

UNCOV
1461
        if (t->class != TARGET_HOST)
×
1462
                return sd_bus_reply_method_errorf(msg,
×
1463
                                                  SD_BUS_ERROR_NOT_SUPPORTED,
1464
                                                  "For now, features can only be managed on the host system.");
1465

1466
        r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
×
1467
        if (r < 0)
×
1468
                return r;
1469
        if (!feature_name_valid(feature))
×
UNCOV
1470
                return sd_bus_reply_method_errorf(msg,
×
1471
                                                  SD_BUS_ERROR_INVALID_ARGS,
1472
                                                  "The specified feature is invalid");
1473
        if (flags != 0)
×
1474
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1475

1476
        if (!endswith(feature, ".feature")) {
×
UNCOV
1477
                feature_ext = strjoin(feature, ".feature");
×
UNCOV
1478
                if (!feature_ext)
×
1479
                        return -ENOMEM;
1480
                feature = feature_ext;
×
1481
        }
1482

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

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

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

UNCOV
1506
                r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
×
1507
                if (r < 0)
×
1508
                        return r;
1509

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

UNCOV
1522
        return sd_bus_reply_method_return(msg, NULL);
×
1523
}
1524

1525
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
172✔
1526
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
172✔
1527
        _cleanup_strv_free_ char **components = NULL;
172✔
1528
        sd_json_variant *v;
172✔
1529
        bool have_default;
172✔
1530
        int r;
172✔
1531

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

1536
        v = sd_json_variant_by_key(json, "default");
172✔
1537
        if (!v)
172✔
UNCOV
1538
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'default'");
×
1539
        have_default = sd_json_variant_boolean(v);
172✔
1540

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

1548
        if (ret_components)
172✔
1549
                *ret_components = TAKE_PTR(components);
172✔
1550
        if (ret_have_default)
172✔
1551
                *ret_have_default = have_default;
172✔
1552
        return 0;
1553
}
1554

1555
static int manager_ensure_targets(Manager *m);
1556

1557
static int target_object_find(
316✔
1558
                sd_bus *bus,
1559
                const char *path,
1560
                const char *iface,
1561
                void *userdata,
1562
                void **found,
1563
                sd_bus_error *error) {
1564

1565
        Manager *m = ASSERT_PTR(userdata);
316✔
1566
        Target *t;
316✔
1567
        _cleanup_free_ char *e = NULL;
316✔
1568
        const char *p;
316✔
1569
        int r;
316✔
1570

1571
        assert(bus);
316✔
1572
        assert(path);
316✔
1573
        assert(found);
316✔
1574

1575
        p = startswith(path, "/org/freedesktop/sysupdate1/target/");
316✔
1576
        if (!p)
316✔
1577
                return 0;
1578

1579
        e = bus_label_unescape(p);
316✔
1580
        if (!e)
316✔
1581
                return -ENOMEM;
1582

1583
        r = manager_ensure_targets(m);
316✔
1584
        if (r < 0)
316✔
1585
                return r;
1586

1587
        t = hashmap_get(m->targets, e);
316✔
1588
        if (!t)
316✔
1589
                return 0;
1590

1591
        *found = t;
316✔
1592
        return 1;
316✔
1593
}
1594

1595
static char *target_bus_path(Target *t) {
120✔
1596
        _cleanup_free_ char *e = NULL;
120✔
1597

1598
        assert(t);
120✔
1599

1600
        e = bus_label_escape(t->id);
120✔
1601
        if (!e)
120✔
1602
                return NULL;
1603

1604
        return strjoin("/org/freedesktop/sysupdate1/target/", e);
120✔
1605
}
1606

1607
static int target_node_enumerator(
×
1608
                sd_bus *bus,
1609
                const char *path,
1610
                void *userdata,
1611
                char ***nodes,
1612
                sd_bus_error *error) {
1613

1614
        _cleanup_strv_free_ char **l = NULL;
×
UNCOV
1615
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
1616
        Target *t;
×
1617
        unsigned k = 0;
×
1618
        int r;
×
1619

1620
        r = manager_ensure_targets(m);
×
1621
        if (r < 0)
×
1622
                return r;
1623

1624
        l = new0(char*, hashmap_size(m->targets) + 1);
×
1625
        if (!l)
×
1626
                return -ENOMEM;
1627

UNCOV
1628
        HASHMAP_FOREACH(t, m->targets) {
×
UNCOV
1629
                l[k] = target_bus_path(t);
×
UNCOV
1630
                if (!l[k])
×
UNCOV
1631
                        return -ENOMEM;
×
UNCOV
1632
                k++;
×
1633
        }
1634

UNCOV
1635
        *nodes = TAKE_PTR(l);
×
UNCOV
1636
        return 1;
×
1637
}
1638

1639
static const sd_bus_vtable target_vtable[] = {
1640
        SD_BUS_VTABLE_START(0),
1641

1642
        SD_BUS_PROPERTY("Class", "s", target_property_get_class,
1643
                        offsetof(Target, class), SD_BUS_VTABLE_PROPERTY_CONST),
1644
        SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Target, name),
1645
                        SD_BUS_VTABLE_PROPERTY_CONST),
1646
        SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Target, path),
1647
                        SD_BUS_VTABLE_PROPERTY_CONST),
1648

1649
        SD_BUS_METHOD_WITH_ARGS("List",
1650
                                SD_BUS_ARGS("t", flags),
1651
                                SD_BUS_RESULT("as", versions),
1652
                                target_method_list,
1653
                                SD_BUS_VTABLE_UNPRIVILEGED),
1654

1655
        SD_BUS_METHOD_WITH_ARGS("Describe",
1656
                                SD_BUS_ARGS("s", version, "t", flags),
1657
                                SD_BUS_RESULT("s", json),
1658
                                target_method_describe,
1659
                                SD_BUS_VTABLE_UNPRIVILEGED),
1660

1661
        SD_BUS_METHOD_WITH_ARGS("CheckNew",
1662
                                SD_BUS_NO_ARGS,
1663
                                SD_BUS_RESULT("s", new_version),
1664
                                target_method_check_new,
1665
                                SD_BUS_VTABLE_UNPRIVILEGED),
1666

1667
        SD_BUS_METHOD_WITH_ARGS("Acquire",
1668
                                SD_BUS_ARGS("s", new_version, "t", flags),
1669
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1670
                                target_method_acquire,
1671
                                SD_BUS_VTABLE_UNPRIVILEGED),
1672

1673
        SD_BUS_METHOD_WITH_ARGS("Install",
1674
                                SD_BUS_ARGS("s", new_version, "t", flags),
1675
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1676
                                target_method_install,
1677
                                SD_BUS_VTABLE_UNPRIVILEGED),
1678

1679
        SD_BUS_METHOD_WITH_ARGS("Vacuum",
1680
                                SD_BUS_NO_ARGS,
1681
                                SD_BUS_RESULT("u", instances, "u", disabled_transfers),
1682
                                target_method_vacuum,
1683
                                SD_BUS_VTABLE_UNPRIVILEGED),
1684

1685
        SD_BUS_METHOD_WITH_ARGS("GetAppStream",
1686
                                SD_BUS_NO_ARGS,
1687
                                SD_BUS_RESULT("as", appstream),
1688
                                target_method_get_appstream,
1689
                                SD_BUS_VTABLE_UNPRIVILEGED),
1690

1691
        SD_BUS_METHOD_WITH_ARGS("GetVersion",
1692
                                SD_BUS_NO_ARGS,
1693
                                SD_BUS_RESULT("s", version),
1694
                                target_method_get_version,
1695
                                SD_BUS_VTABLE_UNPRIVILEGED),
1696

1697
        SD_BUS_METHOD_WITH_ARGS("ListFeatures",
1698
                                SD_BUS_ARGS("t", flags),
1699
                                SD_BUS_RESULT("as", features),
1700
                                target_method_list_features,
1701
                                SD_BUS_VTABLE_UNPRIVILEGED),
1702

1703
        SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
1704
                                SD_BUS_ARGS("s", feature, "t", flags),
1705
                                SD_BUS_RESULT("s", json),
1706
                                target_method_describe_feature,
1707
                                SD_BUS_VTABLE_UNPRIVILEGED),
1708

1709
        SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
1710
                                SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
1711
                                SD_BUS_NO_RESULT,
1712
                                target_method_set_feature_enabled,
1713
                                SD_BUS_VTABLE_UNPRIVILEGED),
1714

1715
        SD_BUS_VTABLE_END
1716
};
1717

1718
static const BusObjectImplementation target_object = {
1719
        "/org/freedesktop/sysupdate1/target",
1720
        "org.freedesktop.sysupdate1.Target",
1721
        .fallback_vtables = BUS_FALLBACK_VTABLES({target_vtable, target_object_find}),
1722
        .node_enumerator = target_node_enumerator,
1723
};
1724

1725
static Manager *manager_free(Manager *m) {
1✔
1726
        if (!m)
1✔
1727
                return NULL;
1728

1729
        hashmap_free(m->targets);
1✔
1730
        hashmap_free(m->jobs);
1✔
1731

1732
        m->bus = sd_bus_flush_close_unref(m->bus);
1✔
1733
        free(m->notify_socket_path);
1✔
1734
        sd_event_unref(m->event);
1✔
1735

1736
        return mfree(m);
1✔
1737
}
1738

1739
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1740

1741
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
713✔
1742
        Manager *m = ASSERT_PTR(userdata);
713✔
1743
        int r;
713✔
1744

1745
        assert(fd >= 0);
713✔
1746

UNCOV
1747
        _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
×
1748
        _cleanup_free_ char *buf = NULL;
713✔
1749
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
713✔
1750
        if (r == -EAGAIN)
713✔
1751
                return 0;
1752
        if (r < 0)
713✔
1753
                return r;
1754

1755
        Job *j;
713✔
1756
        HASHMAP_FOREACH(j, m->jobs) {
889✔
1757
                PidRef child_pidref = PIDREF_NULL;
889✔
1758

1759
                r = event_source_get_child_pidref(j->child, &child_pidref);
889✔
1760
                if (r < 0)
889✔
UNCOV
1761
                        return log_warning_errno(r, "Failed to get child pidref: %m");
×
1762

1763
                if (pidref_equal(&sender_pidref, &child_pidref))
889✔
1764
                        break;
1765
        }
1766
        if (!j) {
713✔
UNCOV
1767
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1768
                return 0;
1769
        }
1770

1771
        char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
713✔
1772
        char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
713✔
1773
        char *errno_str = find_line_startswith(buf, "ERRNO=");
713✔
1774
        const char *ready = find_line_startswith(buf, "READY=1");
713✔
1775

1776
        if (version)
713✔
1777
                job_on_version(j, truncate_nl(version));
100✔
1778

1779
        if (progress)
713✔
1780
                job_on_progress(j, truncate_nl(progress));
249✔
1781

1782
        if (errno_str)
713✔
1783
                job_on_errno(j, truncate_nl(errno_str));
12✔
1784

1785
        /* Should come last, since this might actually detach the job */
1786
        if (ready)
713✔
1787
                job_on_ready(j);
100✔
1788

1789
        return 0;
1790
}
1791

1792
static int manager_new(Manager **ret) {
1✔
1793
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
1794
        int r;
1✔
1795

1796
        assert(ret);
1✔
1797

1798
        m = new(Manager, 1);
1✔
1799
        if (!m)
1✔
1800
                return -ENOMEM;
1801

1802
        *m = (Manager) {
1✔
1803
                .runtime_scope = RUNTIME_SCOPE_SYSTEM,
1804
        };
1805

1806
        r = sd_event_default(&m->event);
1✔
1807
        if (r < 0)
1✔
1808
                return r;
1809

1810
        (void) sd_event_set_watchdog(m->event, true);
1✔
1811

1812
        r = sd_event_set_signal_exit(m->event, true);
1✔
1813
        if (r < 0)
1✔
1814
                return r;
1815

1816
        r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK,
1✔
1817
                                sigrtmin18_handler, NULL);
1818
        if (r < 0)
1✔
1819
                return r;
1820

1821
        r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
1✔
1822
        if (r < 0)
1✔
UNCOV
1823
                log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m");
×
1824

1825
        r = sd_bus_default_system(&m->bus);
1✔
1826
        if (r < 0)
1✔
1827
                return r;
1828

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

1838
        *ret = TAKE_PTR(m);
1✔
1839
        return 0;
1✔
1840
}
1841

1842
static int manager_enumerate_image_class(Manager *m, TargetClass class) {
688✔
1843
        _cleanup_hashmap_free_ Hashmap *images = NULL;
688✔
1844
        Image *image;
688✔
1845
        int r;
688✔
1846

1847
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, &images);
688✔
1848
        if (r < 0)
688✔
1849
                return r;
1850

1851
        HASHMAP_FOREACH(image, images) {
860✔
1852
                _cleanup_(target_freep) Target *t = NULL;
172✔
1853
                bool have = false;
172✔
1854

1855
                if (image_is_host(image))
172✔
1856
                        continue; /* We already enroll the host ourselves */
172✔
1857

1858
                if (image->type == IMAGE_MSTACK)
×
1859
                        continue; /* systemd-sysupdate doesn't support mstack images yet */
×
1860

UNCOV
1861
                r = target_new(m, class, image->name, image->path, &t);
×
UNCOV
1862
                if (r < 0)
×
1863
                        return r;
1864
                t->image_type = image->type;
×
1865

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

UNCOV
1874
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
UNCOV
1875
                if (r < 0)
×
1876
                        return r;
UNCOV
1877
                TAKE_PTR(t);
×
1878
        }
1879

1880
        return 0;
688✔
1881
}
1882

1883
static int manager_enumerate_components(Manager *m) {
172✔
1884
        _cleanup_strv_free_ char **components = NULL;
172✔
1885
        bool have_default;
172✔
1886
        int r;
172✔
1887

1888
        r = target_list_components(NULL, &components, &have_default);
172✔
1889
        if (r < 0)
172✔
1890
                return r;
1891

1892
        if (have_default) {
172✔
UNCOV
1893
                _cleanup_(target_freep) Target *t = NULL;
×
1894

1895
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
172✔
1896
                if (r < 0)
172✔
1897
                        return r;
1898

1899
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
172✔
1900
                if (r < 0)
172✔
1901
                        return r;
1902
                TAKE_PTR(t);
172✔
1903
        }
1904

1905
        STRV_FOREACH(component, components) {
236✔
1906
                _cleanup_free_ char *path = NULL;
64✔
1907
                _cleanup_(target_freep) Target *t = NULL;
64✔
1908

1909
                path = strjoin("sysupdate.", *component, ".d");
64✔
1910
                if (!path)
64✔
1911
                        return -ENOMEM;
1912

1913
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
64✔
1914
                if (r < 0)
64✔
1915
                        return r;
1916

1917
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
64✔
1918
                if (r < 0)
64✔
1919
                        return r;
1920
                TAKE_PTR(t);
64✔
1921
        }
1922

1923
        return 0;
1924
}
1925

1926
static int manager_enumerate_targets(Manager *m) {
172✔
1927
        static const TargetClass discoverable_classes[] = {
172✔
1928
                TARGET_MACHINE,
1929
                TARGET_PORTABLE,
1930
                TARGET_SYSEXT,
1931
                TARGET_CONFEXT,
1932
        };
1933
        int r;
172✔
1934

1935
        assert(m);
172✔
1936

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

1944
        r = manager_enumerate_components(m);
172✔
1945
        if (r < 0)
172✔
UNCOV
1946
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1947

1948
        return 0;
172✔
1949
}
1950

1951
static int manager_ensure_targets(Manager *m) {
404✔
1952
        assert(m);
404✔
1953

1954
        if (!hashmap_isempty(m->targets))
404✔
1955
                return 0;
1956

1957
        return manager_enumerate_targets(m);
172✔
1958
}
1959

1960
static int method_list_targets(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
88✔
1961
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
88✔
1962
        Manager *m = ASSERT_PTR(userdata);
88✔
1963
        Target *t;
88✔
1964
        int r;
88✔
1965

1966
        assert(msg);
88✔
1967

1968
        r = manager_ensure_targets(m);
88✔
1969
        if (r < 0)
88✔
1970
                return r;
1971

1972
        r = sd_bus_message_new_method_return(msg, &reply);
88✔
1973
        if (r < 0)
88✔
1974
                return r;
1975

1976
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
88✔
1977
        if (r < 0)
88✔
1978
                return r;
1979

1980
        HASHMAP_FOREACH(t, m->targets) {
208✔
1981
                _cleanup_free_ char *bus_path = NULL;
120✔
1982

1983
                bus_path = target_bus_path(t);
120✔
1984
                if (!bus_path)
120✔
1985
                        return -ENOMEM;
1986

1987
                r = sd_bus_message_append(reply, "(sso)",
120✔
1988
                                          target_class_to_string(t->class),
1989
                                          t->name,
120✔
1990
                                          bus_path);
1991
                if (r < 0)
120✔
1992
                        return r;
1993
        }
1994

1995
        r = sd_bus_message_close_container(reply);
88✔
1996
        if (r < 0)
88✔
1997
                return r;
1998

1999
        return sd_bus_message_send(reply);
88✔
2000
}
2001

UNCOV
2002
static int method_list_jobs(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
2003
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
2004
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
2005
        Job *j;
×
UNCOV
2006
        int r;
×
2007

2008
        assert(msg);
×
2009

UNCOV
2010
        r = sd_bus_message_new_method_return(msg, &reply);
×
UNCOV
2011
        if (r < 0)
×
2012
                return r;
2013

2014
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
UNCOV
2015
        if (r < 0)
×
2016
                return r;
2017

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

2028
        r = sd_bus_message_close_container(reply);
×
2029
        if (r < 0)
×
2030
                return r;
2031

UNCOV
2032
        return sd_bus_message_send(reply);
×
2033
}
2034

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

2042
        assert(msg);
×
2043

2044
        r = manager_ensure_targets(m);
×
2045
        if (r < 0)
×
2046
                return r;
2047

UNCOV
2048
        HASHMAP_FOREACH(t, m->targets) {
×
2049
                char **target_appstream;
×
2050

UNCOV
2051
                r = target_get_appstream(t, &target_appstream);
×
UNCOV
2052
                if (r < 0)
×
2053
                        return r;
×
2054

UNCOV
2055
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates= */ true);
×
UNCOV
2056
                if (r < 0)
×
2057
                        return r;
2058
        }
2059

UNCOV
2060
        r = sd_bus_message_new_method_return(msg, &reply);
×
UNCOV
2061
        if (r < 0)
×
2062
                return r;
2063

UNCOV
2064
        r = sd_bus_message_append_strv(reply, urls);
×
UNCOV
2065
        if (r < 0)
×
2066
                return r;
2067

UNCOV
2068
        return sd_bus_message_send(reply);
×
2069
}
2070

2071
static const sd_bus_vtable manager_vtable[] = {
2072
        SD_BUS_VTABLE_START(0),
2073

2074
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
2075
                                SD_BUS_NO_ARGS,
2076
                                SD_BUS_RESULT("a(sso)", targets),
2077
                                method_list_targets,
2078
                                SD_BUS_VTABLE_UNPRIVILEGED),
2079

2080
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
2081
                                SD_BUS_NO_ARGS,
2082
                                SD_BUS_RESULT("a(tsuo)", jobs),
2083
                                method_list_jobs,
2084
                                SD_BUS_VTABLE_UNPRIVILEGED),
2085

2086
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
2087
                                SD_BUS_NO_ARGS,
2088
                                SD_BUS_RESULT("as", urls),
2089
                                method_list_appstream,
2090
                                SD_BUS_VTABLE_UNPRIVILEGED),
2091

2092
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2093
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2094
                                0),
2095

2096
        SD_BUS_VTABLE_END
2097
};
2098

2099
static const BusObjectImplementation manager_object = {
2100
        "/org/freedesktop/sysupdate1",
2101
        "org.freedesktop.sysupdate1.Manager",
2102
        .vtables = BUS_VTABLES(manager_vtable),
2103
        .children = BUS_IMPLEMENTATIONS(&job_object, &target_object),
2104
};
2105

2106
static int manager_add_bus_objects(Manager *m) {
1✔
2107
        int r;
1✔
2108

2109
        assert(m);
1✔
2110

2111
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2112
        if (r < 0)
1✔
2113
                return r;
2114

2115
        r = bus_log_control_api_register(m->bus);
1✔
2116
        if (r < 0)
1✔
2117
                return r;
2118

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

2123
        r = sd_bus_attach_event(m->bus, m->event, 0);
1✔
2124
        if (r < 0)
1✔
UNCOV
2125
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
2126

2127
        return 0;
2128
}
2129

2130
static bool manager_is_idle(void *userdata) {
1,371✔
2131
        Manager *m = ASSERT_PTR(userdata);
1,371✔
2132

2133
        return hashmap_isempty(m->jobs);
1,371✔
2134
}
2135

2136
static void manager_check_idle(Manager *m) {
252✔
2137
        assert(m);
252✔
2138

2139
        if (!hashmap_isempty(m->jobs))
252✔
2140
                return;
2141

2142
        hashmap_clear(m->targets);
172✔
2143
        log_debug("Cleared target cache");
172✔
2144
}
2145

2146
static int manager_run(Manager *m) {
1✔
2147
        assert(m);
1✔
2148

2149
        return bus_event_loop_with_idle(m->event,
1✔
2150
                                        m->bus,
2151
                                        "org.freedesktop.sysupdate1",
2152
                                        DEFAULT_EXIT_USEC,
2153
                                        manager_is_idle,
2154
                                        m);
2155
}
2156

2157
static int run(int argc, char *argv[]) {
1✔
2158
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
2159
        int r;
1✔
2160

2161
        log_setup();
1✔
2162

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

2172
        umask(0022);
1✔
2173

2174
        r = manager_new(&m);
1✔
2175
        if (r < 0)
1✔
UNCOV
2176
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2177

2178
        r = manager_add_bus_objects(m);
1✔
2179
        if (r < 0)
1✔
UNCOV
2180
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2181

2182
        r = manager_run(m);
1✔
2183
        if (r < 0)
1✔
UNCOV
2184
                return log_error_errno(r, "Failed to run event loop: %m");
×
2185

2186
        return 0;
2187
}
2188

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

© 2026 Coveralls, Inc