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

systemd / systemd / 14208534426

01 Apr 2025 06:15PM UTC coverage: 71.955% (+0.04%) from 71.916%
14208534426

push

github

web-flow
unit: don't bother determining unit install state for transient or perpetual units (#36504)

17 of 20 new or added lines in 1 file covered. (85.0%)

1170 existing lines in 45 files now uncovered.

296855 of 412557 relevant lines covered (71.95%)

653293.17 hits per line

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

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

3
#include "sd-bus.h"
4
#include "sd-json.h"
5

6
#include "build-path.h"
7
#include "bus-common-errors.h"
8
#include "bus-error.h"
9
#include "bus-get-properties.h"
10
#include "bus-label.h"
11
#include "bus-log-control-api.h"
12
#include "bus-polkit.h"
13
#include "bus-util.h"
14
#include "common-signal.h"
15
#include "discover-image.h"
16
#include "dropin.h"
17
#include "env-util.h"
18
#include "escape.h"
19
#include "event-util.h"
20
#include "fd-util.h"
21
#include "fileio.h"
22
#include "hashmap.h"
23
#include "log.h"
24
#include "main-func.h"
25
#include "memfd-util.h"
26
#include "mkdir-label.h"
27
#include "notify-recv.h"
28
#include "os-util.h"
29
#include "process-util.h"
30
#include "service-util.h"
31
#include "signal-util.h"
32
#include "socket-util.h"
33
#include "string-table.h"
34
#include "sysupdate-util.h"
35

36
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
37

38
typedef struct Manager {
39
        sd_event *event;
40
        sd_bus *bus;
41

42
        Hashmap *targets;
43

44
        uint64_t last_job_id;
45
        Hashmap *jobs;
46

47
        Hashmap *polkit_registry;
48

49
        char *notify_socket_path;
50

51
        RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
52
} Manager;
53

54
/* Forward declare so that jobs can call it on exit */
55
static void manager_check_idle(Manager *m);
56

57
typedef enum TargetClass {
58
        /* These should try to match ImageClass from src/basic/os-util.h */
59
        TARGET_MACHINE  = IMAGE_MACHINE,
60
        TARGET_PORTABLE = IMAGE_PORTABLE,
61
        TARGET_SYSEXT   = IMAGE_SYSEXT,
62
        TARGET_CONFEXT  = IMAGE_CONFEXT,
63
        _TARGET_CLASS_IS_IMAGE_CLASS_MAX,
64

65
        /* sysupdate-specific classes */
66
        TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX,
67
        TARGET_COMPONENT,
68

69
        _TARGET_CLASS_MAX,
70
        _TARGET_CLASS_INVALID = -EINVAL,
71
} TargetClass;
72

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

76
typedef struct Target {
77
        Manager *manager;
78

79
        TargetClass class;
80
        char *name;
81
        char *path;
82

83
        char *id;
84
        ImageType image_type;
85
        bool busy;
86
} Target;
87

88
typedef enum JobType {
89
        JOB_LIST,
90
        JOB_DESCRIBE,
91
        JOB_CHECK_NEW,
92
        JOB_UPDATE,
93
        JOB_VACUUM,
94
        JOB_DESCRIBE_FEATURE,
95
        _JOB_TYPE_MAX,
96
        _JOB_TYPE_INVALID = -EINVAL,
97
} JobType;
98

99
typedef struct Job Job;
100

101
typedef int (*JobReady)(sd_bus_message *msg, const Job *job);
102
typedef int (*JobComplete)(sd_bus_message *msg, const Job *job, sd_json_variant *response, sd_bus_error *error);
103

104
struct Job {
105
        Manager *manager;
106
        Target *target;
107

108
        uint64_t id;
109
        char *object_path;
110

111
        JobType type;
112
        bool offline;
113
        char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */
114
        char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
115

116
        unsigned progress_percent;
117

118
        sd_event_source *child;
119
        int stdout_fd;
120
        int status_errno;
121
        unsigned n_cancelled;
122

123
        sd_json_variant *json;
124

125
        JobComplete complete_cb; /* Callback called on job exit */
126
        sd_bus_message *dbus_msg;
127
        JobReady detach_cb; /* Callback called when job has started.  Detaches the job to run in the background */
128
};
129

130
static const char* const target_class_table[_TARGET_CLASS_MAX] = {
131
        [TARGET_MACHINE]   = "machine",
132
        [TARGET_PORTABLE]  = "portable",
133
        [TARGET_SYSEXT]    = "sysext",
134
        [TARGET_CONFEXT]   = "confext",
135
        [TARGET_COMPONENT] = "component",
136
        [TARGET_HOST]      = "host",
137
};
138

139
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
38✔
140

141
static const char* const job_type_table[_JOB_TYPE_MAX] = {
142
        [JOB_LIST]             = "list",
143
        [JOB_DESCRIBE]         = "describe",
144
        [JOB_CHECK_NEW]        = "check-new",
145
        [JOB_UPDATE]           = "update",
146
        [JOB_VACUUM]           = "vacuum",
147
        [JOB_DESCRIBE_FEATURE] = "describe-feature",
148
};
149

150
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
151

152
static Job *job_free(Job *j) {
20✔
153
        if (!j)
20✔
154
                return NULL;
155

156
        if (j->manager)
20✔
157
                assert_se(hashmap_remove(j->manager->jobs, &j->id) == j);
20✔
158

159
        free(j->object_path);
20✔
160
        free(j->version);
20✔
161
        free(j->feature);
20✔
162

163
        sd_json_variant_unref(j->json);
20✔
164

165
        sd_bus_message_unref(j->dbus_msg);
20✔
166

167
        sd_event_source_disable_unref(j->child);
20✔
168
        safe_close(j->stdout_fd);
20✔
169

170
        return mfree(j);
20✔
171
}
172

173
DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free);
40✔
174
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func,
×
175
                                      Job, job_free);
176

177
static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) {
20✔
178
        _cleanup_(job_freep) Job *j = NULL;
20✔
179
        int r;
20✔
180

181
        assert(t);
20✔
182
        assert(ret);
20✔
183

184
        j = new(Job, 1);
20✔
185
        if (!j)
20✔
186
                return -ENOMEM;
187

188
        *j = (Job) {
40✔
189
                .type = type,
190
                .target = t,
191
                .id = t->manager->last_job_id + 1,
20✔
192
                .stdout_fd = -EBADF,
193
                .complete_cb = complete_cb,
194
                .dbus_msg = sd_bus_message_ref(msg),
20✔
195
        };
196

197
        if (asprintf(&j->object_path, "/org/freedesktop/sysupdate1/job/_%" PRIu64, j->id) < 0)
20✔
198
                return -ENOMEM;
199

200
        r = hashmap_ensure_put(&t->manager->jobs, &job_hash_ops, &j->id, j);
20✔
201
        if (r < 0)
20✔
202
                return r;
203

204
        j->manager = t->manager;
20✔
205

206
        t->manager->last_job_id = j->id;
20✔
207

208
        *ret = TAKE_PTR(j);
20✔
209
        return 0;
20✔
210
}
211

212
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
20✔
213
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
20✔
214
        /* Take ownership of the passed fd */
215
        _cleanup_close_ int fd = _fd;
20✔
216
        _cleanup_fclose_ FILE *f = NULL;
20✔
217
        struct stat st;
20✔
218
        int r;
20✔
219

220
        assert(ret);
20✔
221

222
        if (fstat(fd, &st) < 0)
20✔
223
                return log_debug_errno(errno, "Failed to stat stdout fd: %m");
×
224

225
        assert(S_ISREG(st.st_mode));
20✔
226

227
        if (st.st_size == 0) {
20✔
228
                log_warning("No output from child job, ignoring");
×
229
                return 0;
×
230
        }
231

232
        if (lseek(fd, SEEK_SET, 0) == (off_t) -1)
20✔
233
                return log_debug_errno(errno, "Failed to seek to beginning of memfd: %m");
×
234

235
        f = take_fdopen(&fd, "r");
20✔
236
        if (!f)
20✔
237
                return log_debug_errno(errno, "Failed to reopen memfd: %m");
×
238

239
        r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
20✔
240
        if (r < 0)
20✔
241
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
242

243
        *ret = TAKE_PTR(v);
20✔
244
        return 0;
20✔
245
}
246

247
static void job_on_ready(Job *j) {
2✔
248
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *msg = NULL;
2✔
249
        int r;
2✔
250

251
        assert(j);
2✔
252

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

258
        if (!j->detach_cb)
2✔
259
                return;
260

261
        log_debug("Got READY=1 from job %" PRIu64", detaching.", j->id);
2✔
262

263
        assert(j->dbus_msg);
2✔
264
        msg = TAKE_PTR(j->dbus_msg);
2✔
265

266
        j->complete_cb = NULL;
2✔
267

268
        r = j->detach_cb(msg, j);
2✔
269
        if (r < 0)
2✔
270
                log_warning_errno(r, "Failed to detach job %" PRIu64 ", ignoring: %m", j->id);
2✔
271
}
272

273
static void job_on_errno(Job *j, const char *buf) {
×
274
        int r;
×
275

276
        assert(j);
×
277
        assert(buf);
×
278

279
        r = parse_errno(buf);
×
280
        if (r < 0) {
×
281
                log_warning_errno(r, "Got invalid errno value from job %" PRIu64 ", ignoring: %m", j->id);
×
282
                return;
×
283
        }
284

285
        j->status_errno = r;
×
286

287
        log_debug_errno(r, "Got errno from job %" PRIu64 ": %i (%m)", j->id, r);
×
288
}
289

290
static void job_on_progress(Job *j, const char *buf) {
8✔
291
        unsigned progress;
8✔
292
        int r;
8✔
293

294
        assert(j);
8✔
295
        assert(buf);
8✔
296

297
        r = safe_atou(buf, &progress);
8✔
298
        if (r < 0 || progress > 100) {
8✔
299
                log_warning("Got invalid percent value, ignoring.");
×
300
                return;
×
301
        }
302

303
        j->progress_percent = progress;
8✔
304
        (void) sd_bus_emit_properties_changed(j->manager->bus, j->object_path,
8✔
305
                                              "org.freedesktop.sysupdate1.Job",
306
                                              "Progress", NULL);
307

308
        log_debug("Got percentage from job %" PRIu64 ": %u%%", j->id, j->progress_percent);
8✔
309
}
310

311
static void job_on_version(Job *j, const char *version) {
2✔
312
        assert(j);
2✔
313
        assert(version);
2✔
314

315
        if (free_and_strdup_warn(&j->version, version) < 0)
2✔
316
                return;
317

318
        log_debug("Got version from job %" PRIu64 ": %s ", j->id, j->version);
2✔
319
}
320

321
static int job_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
20✔
322
        Job *j = ASSERT_PTR(userdata);
20✔
323
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
×
324
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
20✔
325
        Manager *manager = j->manager;
20✔
326
        int r;
20✔
327

328
        assert(j);
20✔
329
        assert(s);
20✔
330
        assert(si);
20✔
331

332
        if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM)) {
20✔
333
                assert(j->target->busy);
2✔
334
                j->target->busy = false;
2✔
335
        }
336

337
        if (si->si_code != CLD_EXITED) {
20✔
338
                assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
×
339
                sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
340
                                  "Job terminated abnormally with signal %s.",
341
                                  signal_to_string(si->si_status));
×
342
        } else if (si->si_status != EXIT_SUCCESS)
20✔
343
                if (j->status_errno != 0)
×
344
                        sd_bus_error_set_errno(&error, j->status_errno);
×
345
                else
346
                        sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED,
×
347
                                          "Job failed with exit code %i.", si->si_status);
348
        else {
349
                r = job_parse_child_output(TAKE_FD(j->stdout_fd), &json);
20✔
350
                if (r < 0)
20✔
351
                        sd_bus_error_set_errnof(&error, r, "Failed to parse job worker output: %m");
×
352
        }
353

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

371
        if (j->dbus_msg && j->complete_cb) {
20✔
372
                if (sd_bus_error_is_set(&error)) {
18✔
373
                        log_warning("Job %" PRIu64 " failed with bus error, ignoring callback: %s",
×
374
                                    j->id, error.message);
375
                        sd_bus_reply_method_error(j->dbus_msg, &error);
×
376
                } else {
377
                        r = j->complete_cb(j->dbus_msg, j, json, &error);
18✔
378
                        if (r < 0) {
18✔
379
                                log_warning_errno(r,
×
380
                                                  "Error during execution of job callback for job %" PRIu64 ": %s",
381
                                                  j->id,
382
                                                  bus_error_message(&error, r));
383
                                sd_bus_reply_method_errno(j->dbus_msg, r, &error);
×
384
                        }
385
                }
386
        }
387

388
        job_free(j);
20✔
389

390
        if (manager)
20✔
391
                manager_check_idle(manager);
20✔
392

393
        return 0;
40✔
394
}
395

396
static inline const char* sysupdate_binary_path(void) {
34✔
397
        return secure_getenv("SYSTEMD_SYSUPDATE_PATH") ?: SYSTEMD_SYSUPDATE_PATH;
34✔
398
}
399

400
static int target_get_argument(Target *t, char **ret) {
24✔
401
        _cleanup_free_ char *target_arg = NULL;
48✔
402

403
        assert(t);
24✔
404
        assert(ret);
24✔
405

406
        if (t->class != TARGET_HOST) {
24✔
407
                if (t->class == TARGET_COMPONENT)
4✔
408
                        target_arg = strjoin("--component=", t->name);
4✔
409
                else if (IN_SET(t->image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME))
×
410
                        target_arg = strjoin("--root=", t->path);
×
411
                else if (IN_SET(t->image_type, IMAGE_RAW, IMAGE_BLOCK))
×
412
                        target_arg = strjoin("--image=", t->path);
×
413
                else
414
                        assert_not_reached();
×
415
                if (!target_arg)
4✔
416
                        return -ENOMEM;
417
        }
418

419
        *ret = TAKE_PTR(target_arg);
24✔
420
        return 0;
24✔
421
}
422

423
static int job_start(Job *j) {
20✔
424
        _cleanup_close_ int stdout_fd = -EBADF;
20✔
425
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
20✔
426
        int r;
20✔
427

428
        assert(j);
20✔
429

430
        if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM) && j->target->busy)
20✔
431
                return log_notice_errno(SYNTHETIC_ERRNO(EBUSY), "Target %s busy, ignoring job.", j->target->name);
×
432

433
        stdout_fd = memfd_new("sysupdate-stdout");
20✔
434
        if (stdout_fd < 0)
20✔
435
                return log_error_errno(stdout_fd, "Failed to create memfd: %m");
×
436

437
        r = pidref_safe_fork_full("(sd-sysupdate)",
60✔
438
                                  (int[]) { -EBADF, stdout_fd, STDERR_FILENO }, NULL, 0,
20✔
439
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
440
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
441
        if (r < 0)
40✔
442
                return r; /* FORK_LOG means pidref_safe_fork_full will handle the logging */
443
        if (r == 0) {
40✔
444
                /* Child */
445

446
                _cleanup_free_ char *target_arg = NULL;
×
447
                const char *cmd[] = {
20✔
448
                        "systemd-sysupdate",
449
                        "--json=short",
450
                        NULL, /* maybe --verify=no */
451
                        NULL, /* maybe --component=, --root=, or --image= */
452
                        NULL, /* maybe --offline */
453
                        NULL, /* list, check-new, update, vacuum, features */
454
                        NULL, /* maybe version (for list, update), maybe feature (features) */
455
                        NULL
456
                };
457
                size_t k = 2;
20✔
458

459
                if (setenv("NOTIFY_SOCKET", j->manager->notify_socket_path, /* overwrite= */ 1) < 0) {
20✔
460
                        log_error_errno(errno, "setenv() failed: %m");
×
461
                        _exit(EXIT_FAILURE);
×
462
                }
463

464
                if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
20✔
465
                        cmd[k++] = "--verify=no"; /* For testing */
20✔
466

467
                r = setenv_systemd_exec_pid(true);
20✔
468
                if (r < 0)
20✔
469
                        log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
×
470

471
                r = target_get_argument(j->target, &target_arg);
20✔
472
                if (r < 0) {
20✔
473
                        log_oom();
×
474
                        _exit(EXIT_FAILURE);
×
475
                }
476
                if (target_arg)
20✔
477
                        cmd[k++] = target_arg;
2✔
478

479
                if (j->offline)
20✔
480
                        cmd[k++] = "--offline";
×
481

482
                switch (j->type) {
20✔
483
                case JOB_LIST:
2✔
484
                        cmd[k++] = "list";
2✔
485
                        break;
2✔
486

487
                case JOB_DESCRIBE:
12✔
488
                        cmd[k++] = "list";
12✔
489
                        assert(!isempty(j->version));
12✔
490
                        cmd[k++] = j->version;
12✔
491
                        break;
12✔
492

493
                case JOB_CHECK_NEW:
4✔
494
                        cmd[k++] = "check-new";
4✔
495
                        break;
4✔
496

497
                case JOB_UPDATE:
2✔
498
                        cmd[k++] = "update";
2✔
499
                        cmd[k++] = empty_to_null(j->version);
4✔
500
                        break;
2✔
501

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

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

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

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

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

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

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

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

536
        r = event_add_child_pidref(j->manager->event, &j->child, &pid, WEXITED, job_on_exit, j);
20✔
537
        if (r < 0)
20✔
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);
20✔
541
        if (r < 0)
20✔
542
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
543
        TAKE_PIDREF(pid);
20✔
544

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

547
        if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM))
20✔
548
                j->target->busy = true;
2✔
549

550
        return 0;
551
}
552

553
static int job_cancel(Job *j) {
×
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);
560
        if (r < 0)
×
561
                return r;
562

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

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

572
        assert(msg);
×
573

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

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

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

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

596
        default:
×
597
                assert_not_reached();
×
598
        }
599

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

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

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

620
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
621

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

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

636
        assert(bus);
8✔
637
        assert(path);
8✔
638
        assert(ret);
8✔
639

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

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

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

652
        *ret = j;
8✔
653
        return 1;
8✔
654
}
655

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

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

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

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

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

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

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

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

693
        SD_BUS_VTABLE_END
694
};
695

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

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

707
        free(t->name);
18✔
708
        free(t->path);
18✔
709
        free(t->id);
18✔
710

711
        return mfree(t);
18✔
712
}
713

714
DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free);
46✔
715
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, char, string_hash_func, string_compare_func,
18✔
716
                                      Target, target_free);
717

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

721
        assert(m);
18✔
722
        assert(ret);
18✔
723

724
        t = new(Target, 1);
18✔
725
        if (!t)
18✔
726
                return -ENOMEM;
727

728
        *t = (Target) {
18✔
729
                .manager = m,
730
                .class = class,
731
                .image_type = _IMAGE_TYPE_INVALID,
732
        };
733

734
        t->name = strdup(name);
18✔
735
        if (!t->name)
18✔
736
                return -ENOMEM;
737

738
        t->path = strdup(path);
18✔
739
        if (!t->path)
18✔
740
                return -ENOMEM;
741

742
        if (class == TARGET_HOST)
18✔
743
                t->id = strdup("host"); /* This is what appears in the object path */
10✔
744
        else
745
                t->id = strjoin(target_class_to_string(class), ":", name);
8✔
746
        if (!t->id)
18✔
747
                return -ENOMEM;
748

749
        *ret = TAKE_PTR(t);
18✔
750
        return 0;
18✔
751
}
752

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

761
        if (t) {
14✔
762
                r = target_get_argument(t, &target_arg);
4✔
763
                if (r < 0)
4✔
764
                        return r;
765
        }
766

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

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

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

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

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

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

812
                if (DEBUG_LOGGING) {
14✔
813
                        _cleanup_free_ char *s = NULL;
14✔
814

815
                        s = quote_command_line((char**) args, SHELL_ESCAPE_EMPTY);
14✔
816
                        if (!s) {
14✔
817
                                log_oom();
×
818
                                _exit(EXIT_FAILURE);
×
819
                        }
820

821
                        log_debug("Spawning sysupdate: %s", s);
14✔
822
                }
823

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

829
        pipe[1] = safe_close(pipe[1]);
14✔
830
        f = take_fdopen(&pipe[0], "r");
14✔
831
        if (!f)
14✔
832
                return -errno;
×
833

834
        r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
14✔
835
        if (r < 0)
14✔
836
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
837

838
        *ret = TAKE_PTR(v);
14✔
839
        return 0;
14✔
840
}
841

842
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
843

844
#define log_sysupdate_bad_json_full(lvl, r, verb, msg) \
845
        log_full_errno((lvl), (r), "Invalid JSON response from 'systemd-sysupdate %s': %s", (verb), (msg))
846
#define log_sysupdate_bad_json(r, verb, msg) \
847
        log_sysupdate_bad_json_full(LOG_ERR, (r), (verb), (msg))
848
#define log_sysupdate_bad_json_debug(r, verb, msg) \
849
        log_sysupdate_bad_json_full(LOG_DEBUG, (r), (verb), (msg))
850

851
static int target_method_list_finish(
2✔
852
                sd_bus_message *msg,
853
                const Job *j,
854
                sd_json_variant *json,
855
                sd_bus_error *error) {
856

857
        sd_json_variant *v;
2✔
858
        _cleanup_strv_free_ char **versions = NULL;
×
859
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2✔
860
        int r;
2✔
861

862
        assert(json);
2✔
863

864
        v = sd_json_variant_by_key(json, "all");
2✔
865
        if (!v)
2✔
866
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'all'");
×
867

868
        r = sd_json_variant_strv(v, &versions);
2✔
869
        if (r < 0)
2✔
870
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'all' should be strv");
×
871

872
        r = sd_bus_message_new_method_return(msg, &reply);
2✔
873
        if (r < 0)
2✔
874
                return r;
875

876
        r = sd_bus_message_append_strv(reply, versions);
2✔
877
        if (r < 0)
2✔
878
                return r;
879

880
        return sd_bus_send(NULL, reply, NULL);
2✔
881
}
882

883
static int target_method_list(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
2✔
884
        Target *t = ASSERT_PTR(userdata);
2✔
885
        _cleanup_(job_freep) Job *j = NULL;
2✔
886
        uint64_t flags;
2✔
887
        int r;
2✔
888

889
        assert(msg);
2✔
890

891
        r = sd_bus_message_read(msg, "t", &flags);
2✔
892
        if (r < 0)
2✔
893
                return r;
894

895
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
2✔
896
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
897

898
        const char *details[] = {
4✔
899
                "class", target_class_to_string(t->class),
2✔
900
                "name", t->name,
2✔
901
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
2✔
902
                NULL
903
        };
904

905
        r = bus_verify_polkit_async(
4✔
906
                msg,
907
                "org.freedesktop.sysupdate1.check",
908
                details,
909
                &t->manager->polkit_registry,
2✔
910
                error);
911
        if (r < 0)
2✔
912
                return r;
913
        if (r == 0)
2✔
914
                return 1; /* Will call us back */
915

916
        r = job_new(JOB_LIST, t, msg, target_method_list_finish, &j);
2✔
917
        if (r < 0)
2✔
918
                return r;
919

920
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
2✔
921

922
        r = job_start(j);
2✔
923
        if (r < 0)
2✔
924
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
925
        TAKE_PTR(j); /* Avoid job from being killed & freed */
2✔
926

927
        return 1;
2✔
928
}
929

930
static int target_method_describe_finish(
12✔
931
                sd_bus_message *msg,
932
                const Job *j,
933
                sd_json_variant *json,
934
                sd_bus_error *error) {
935
        _cleanup_free_ char *text = NULL;
12✔
936
        int r;
12✔
937

938
        /* NOTE: This is also reused by target_method_describe_feature */
939

940
        assert(json);
12✔
941

942
        r = sd_json_variant_format(json, 0, &text);
12✔
943
        if (r < 0)
12✔
944
                return r;
945

946
        return sd_bus_reply_method_return(msg, "s", text);
12✔
947
}
948

949
static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
12✔
950
        Target *t = ASSERT_PTR(userdata);
12✔
951
        _cleanup_(job_freep) Job *j = NULL;
12✔
952
        const char *version;
12✔
953
        uint64_t flags;
12✔
954
        int r;
12✔
955

956
        assert(msg);
12✔
957

958
        r = sd_bus_message_read(msg, "st", &version, &flags);
12✔
959
        if (r < 0)
12✔
960
                return r;
961

962
        if (isempty(version))
12✔
963
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Version must be specified");
×
964

965
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
12✔
966
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
967

968
        const char *details[] = {
24✔
969
                "class", target_class_to_string(t->class),
12✔
970
                "name", t->name,
12✔
971
                "version", version,
972
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
12✔
973
                NULL
974
        };
975

976
        r = bus_verify_polkit_async(
24✔
977
                msg,
978
                "org.freedesktop.sysupdate1.check",
979
                details,
980
                &t->manager->polkit_registry,
12✔
981
                error);
982
        if (r < 0)
12✔
983
                return r;
984
        if (r == 0)
12✔
985
                return 1; /* Will call us back */
986

987
        r = job_new(JOB_DESCRIBE, t, msg, target_method_describe_finish, &j);
12✔
988
        if (r < 0)
12✔
989
                return r;
990

991
        j->version = strdup(version);
12✔
992
        if (!j->version)
12✔
993
                return log_oom();
×
994

995
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
12✔
996

997
        r = job_start(j);
12✔
998
        if (r < 0)
12✔
999
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1000
        TAKE_PTR(j); /* Avoid job from being killed & freed */
12✔
1001

1002
        return 1;
12✔
1003
}
1004

1005
static int target_method_check_new_finish(
4✔
1006
                sd_bus_message *msg,
1007
                const Job *j,
1008
                sd_json_variant *json,
1009
                sd_bus_error *error) {
1010
        const char *reply;
4✔
1011

1012
        assert(json);
4✔
1013

1014
        sd_json_variant *v = sd_json_variant_by_key(json, "available");
4✔
1015
        if (!v)
4✔
1016
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "check-new", "Missing key 'available'");
×
1017

1018
        if (sd_json_variant_is_null(v))
4✔
1019
                reply = "";
1020
        else
1021
                reply = sd_json_variant_string(v);
×
1022
        if (!reply)
×
1023
                return -EINVAL;
1024

1025
        return sd_bus_reply_method_return(msg, "s", reply);
4✔
1026
}
1027

1028
static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
4✔
1029
        Target *t = ASSERT_PTR(userdata);
4✔
1030
        _cleanup_(job_freep) Job *j = NULL;
4✔
1031
        int r;
4✔
1032

1033
        assert(msg);
4✔
1034

1035
        const char *details[] = {
4✔
1036
                "class", target_class_to_string(t->class),
4✔
1037
                "name", t->name,
4✔
1038
                "offline", "0",
1039
                NULL
1040
        };
1041

1042
        r = bus_verify_polkit_async(
8✔
1043
                        msg,
1044
                        "org.freedesktop.sysupdate1.check",
1045
                        details,
1046
                        &t->manager->polkit_registry,
4✔
1047
                        error);
1048
        if (r < 0)
4✔
1049
                return r;
1050
        if (r == 0)
4✔
1051
                return 1; /* Will call us back */
1052

1053
        r = job_new(JOB_CHECK_NEW, t, msg, target_method_check_new_finish, &j);
4✔
1054
        if (r < 0)
4✔
1055
                return r;
1056

1057
        r = job_start(j);
4✔
1058
        if (r < 0)
4✔
1059
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1060
        TAKE_PTR(j); /* Avoid job from being killed & freed */
4✔
1061

1062
        return 1;
4✔
1063
}
1064

1065
static int target_method_update_finished_early(
×
1066
                sd_bus_message *msg,
1067
                const Job *j,
1068
                sd_json_variant *json,
1069
                sd_bus_error *error) {
1070

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

1078
static int target_method_update_detach(sd_bus_message *msg, const Job *j) {
2✔
1079
        int r;
2✔
1080

1081
        assert(msg);
2✔
1082
        assert(j);
2✔
1083

1084
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
2✔
1085
        if (r < 0)
2✔
1086
                return bus_log_parse_error(r);
×
1087

1088
        return 0;
1089
}
1090

1091
static int target_method_update(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
2✔
1092
        Target *t = ASSERT_PTR(userdata);
2✔
1093
        _cleanup_(job_freep) Job *j = NULL;
2✔
1094
        const char *version, *action;
2✔
1095
        uint64_t flags;
2✔
1096
        int r;
2✔
1097

1098
        assert(msg);
2✔
1099

1100
        r = sd_bus_message_read(msg, "st", &version, &flags);
2✔
1101
        if (r < 0)
2✔
1102
                return r;
1103

1104
        if (flags != 0)
2✔
1105
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1106

1107
        if (isempty(version))
4✔
1108
                action = "org.freedesktop.sysupdate1.update";
1109
        else
1110
                action = "org.freedesktop.sysupdate1.update-to-version";
1111

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

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

1130
        r = job_new(JOB_UPDATE, t, msg, target_method_update_finished_early, &j);
2✔
1131
        if (r < 0)
2✔
1132
                return r;
1133
        j->detach_cb = target_method_update_detach;
2✔
1134

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

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

1144
        return 1;
2✔
1145
}
1146

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

1153
        sd_json_variant *v;
×
1154
        uint64_t instances, disabled;
×
1155

1156
        assert(json);
×
1157

1158
        v = sd_json_variant_by_key(json, "removed");
×
1159
        if (!v)
×
1160
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'removed'");
×
1161
        if (!sd_json_variant_is_unsigned(v))
×
1162
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'removed' should be an unsigned int");
×
1163
        instances = sd_json_variant_unsigned(v);
×
1164
        assert(instances <= UINT32_MAX);
×
1165

1166
        v = sd_json_variant_by_key(json, "disabledTransfers");
×
1167
        if (!v)
×
1168
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
×
1169
        if (!sd_json_variant_is_unsigned(v))
×
1170
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
×
1171
        disabled = sd_json_variant_unsigned(v);
×
1172
        assert(disabled <= UINT32_MAX);
×
1173

1174
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1175
}
1176

1177
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1178
        Target *t = ASSERT_PTR(userdata);
×
1179
        _cleanup_(job_freep) Job *j = NULL;
×
1180
        int r;
×
1181

1182
        assert(msg);
×
1183

1184
        const char *details[] = {
×
1185
                "class", target_class_to_string(t->class),
×
1186
                "name", t->name,
×
1187
                NULL
1188
        };
1189

1190
        r = bus_verify_polkit_async(
×
1191
                msg,
1192
                "org.freedesktop.sysupdate1.vacuum",
1193
                details,
1194
                &t->manager->polkit_registry,
×
1195
                error);
1196
        if (r < 0)
×
1197
                return r;
1198
        if (r == 0)
×
1199
                return 1; /* Will call us back */
1200

1201
        r = job_new(JOB_VACUUM, t, msg, target_method_vacuum_finish, &j);
×
1202
        if (r < 0)
×
1203
                return r;
1204

1205
        r = job_start(j);
×
1206
        if (r < 0)
×
1207
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1208
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1209

1210
        return 1;
×
1211
}
1212

1213
static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
4✔
1214
        Target *t = ASSERT_PTR(userdata);
4✔
1215
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
4✔
1216
        sd_json_variant *version_json;
4✔
1217
        int r;
4✔
1218

1219
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
4✔
1220
        if (r < 0)
4✔
1221
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1222

1223
        version_json = sd_json_variant_by_key(v, "current");
4✔
1224
        if (!version_json)
4✔
1225
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current'");
×
1226

1227
        if (sd_json_variant_is_null(version_json))
4✔
1228
                return sd_bus_reply_method_return(msg, "s", "");
×
1229

1230
        if (!sd_json_variant_is_string(version_json))
4✔
1231
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'current' should be a string");
×
1232

1233
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
4✔
1234
}
1235

1236
static int target_get_appstream(Target *t, char ***ret) {
×
1237
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1238
        sd_json_variant *appstream_url_json;
×
1239
        int r;
×
1240

1241
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
×
1242
        if (r < 0)
×
1243
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1244

1245
        appstream_url_json = sd_json_variant_by_key(v, "appstreamUrls");
×
1246
        if (!appstream_url_json)
×
1247
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'appstreamUrls'");
×
1248

1249
        r = sd_json_variant_strv(appstream_url_json, ret);
×
1250
        if (r < 0)
×
1251
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'appstreamUrls' should be strv");
×
1252

1253
        return 0;
1254
}
1255

1256
static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1257
        Target *t = ASSERT_PTR(userdata);
×
1258
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1259
        _cleanup_strv_free_ char **appstream_urls = NULL;
×
1260
        int r;
×
1261

1262
        r = target_get_appstream(t, &appstream_urls);
×
1263
        if (r < 0)
×
1264
                return r;
1265

1266
        r = sd_bus_message_new_method_return(msg, &reply);
×
1267
        if (r < 0)
×
1268
                return r;
1269

1270
        r = sd_bus_message_append_strv(reply, appstream_urls);
×
1271
        if (r < 0)
×
1272
                return r;
1273

1274
        return sd_bus_send(NULL, reply, NULL);
×
1275
}
1276

1277
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1278
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1279
        _cleanup_strv_free_ char **features = NULL;
×
1280
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1281
        Target *t = ASSERT_PTR(userdata);
×
1282
        sd_json_variant *v;
×
1283
        uint64_t flags;
×
1284
        int r;
×
1285

1286
        assert(msg);
×
1287

1288
        r = sd_bus_message_read(msg, "t", &flags);
×
1289
        if (r < 0)
×
1290
                return r;
1291
        if (flags != 0)
×
1292
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1293

1294
        r = sysupdate_run_simple(&json, t, "features", NULL);
×
1295
        if (r < 0)
×
1296
                return r;
1297

1298
        v = sd_json_variant_by_key(json, "features");
×
1299
        if (!v)
×
1300
                return -EINVAL;
1301
        r = sd_json_variant_strv(v, &features);
×
1302
        if (r < 0)
×
1303
                return r;
1304

1305
        r = sd_bus_message_new_method_return(msg, &reply);
×
1306
        if (r < 0)
×
1307
                return r;
1308

1309
        r = sd_bus_message_append_strv(reply, features);
×
1310
        if (r < 0)
×
1311
                return r;
1312

1313
        return sd_bus_send(NULL, reply, NULL);
×
1314
}
1315

1316
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1317
        Target *t = ASSERT_PTR(userdata);
×
1318
        _cleanup_(job_freep) Job *j = NULL;
×
1319
        const char *feature;
×
1320
        uint64_t flags;
×
1321
        int r;
×
1322

1323
        assert(msg);
×
1324

1325
        r = sd_bus_message_read(msg, "st", &feature, &flags);
×
1326
        if (r < 0)
×
1327
                return r;
1328

1329
        if (isempty(feature))
×
1330
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified");
×
1331

1332
        if (flags != 0)
×
1333
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1334

1335
        r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
×
1336
        if (r < 0)
×
1337
                return r;
1338

1339
        j->feature = strdup(feature);
×
1340
        if (!j->feature)
×
1341
                return log_oom();
×
1342

1343
        r = job_start(j);
×
1344
        if (r < 0)
×
1345
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1346
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1347

1348
        return 1;
×
1349
}
1350

1351
static bool feature_name_is_valid(const char *name) {
×
1352
        if (isempty(name))
×
1353
                return false;
1354

1355
        if (!ascii_is_valid(name))
×
1356
                return false;
1357

1358
        if (!filename_is_valid(strjoina(name, ".feature.d")))
×
1359
                return false;
×
1360

1361
        return true;
1362
}
1363

1364
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1365
        _cleanup_free_ char *feature_ext = NULL;
×
1366
        Target *t = ASSERT_PTR(userdata);
×
1367
        const char *feature;
×
1368
        uint64_t flags;
×
1369
        int32_t enabled;
×
1370
        int r;
×
1371

1372
        assert(msg);
×
1373

1374
        if (t->class != TARGET_HOST)
×
1375
                return sd_bus_reply_method_errorf(msg,
×
1376
                                                  SD_BUS_ERROR_NOT_SUPPORTED,
1377
                                                  "For now, features can only be managed on the host system.");
1378

1379
        r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
×
1380
        if (r < 0)
×
1381
                return r;
1382
        if (!feature_name_is_valid(feature))
×
1383
                return sd_bus_reply_method_errorf(msg,
×
1384
                                                  SD_BUS_ERROR_INVALID_ARGS,
1385
                                                  "The specified feature is invalid");
1386
        if (flags != 0)
×
1387
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1388

1389
        if (!endswith(feature, ".feature")) {
×
1390
                feature_ext = strjoin(feature, ".feature");
×
1391
                if (!feature_ext)
×
1392
                        return -ENOMEM;
1393
                feature = feature_ext;
×
1394
        }
1395

1396
        const char *details[] = {
×
1397
                "class", target_class_to_string(t->class),
×
1398
                "name", t->name,
×
1399
                "feature", feature,
1400
                "enabled", enabled >= 0 ? true_false(enabled) : "unset",
×
1401
                NULL
1402
        };
1403

1404
        r = bus_verify_polkit_async(
×
1405
                msg,
1406
                "org.freedesktop.sysupdate1.manage-features",
1407
                details,
1408
                &t->manager->polkit_registry,
×
1409
                error);
1410
        if (r < 0)
×
1411
                return r;
1412
        if (r == 0)
×
1413
                return 1; /* Will call us back */
1414

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

1419
                r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
×
1420
                if (r < 0)
×
1421
                        return r;
1422

1423
                if (unlink(path) < 0)
×
1424
                        return -errno;
×
1425
        } else { /* otherwise, create the drop-in with the right settings */
1426
                r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
×
1427
                                         "# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
1428
                                         "[Feature]\n"
1429
                                         "Enabled=%s\n",
1430
                                         yes_no(enabled));
1431
                if (r < 0)
×
1432
                        return r;
1433
        }
1434

1435
        return sd_bus_reply_method_return(msg, NULL);
×
1436
}
1437

1438
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
10✔
1439
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
10✔
1440
        _cleanup_strv_free_ char **components = NULL;
10✔
1441
        sd_json_variant *v;
10✔
1442
        bool have_default;
10✔
1443
        int r;
10✔
1444

1445
        r = sysupdate_run_simple(&json, t, "components", NULL);
10✔
1446
        if (r < 0)
10✔
1447
                return log_debug_errno(r, "Failed to run 'systemd-sysupdate components': %m");
×
1448

1449
        v = sd_json_variant_by_key(json, "default");
10✔
1450
        if (!v)
10✔
1451
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'default'");
×
1452
        have_default = sd_json_variant_boolean(v);
10✔
1453

1454
        v = sd_json_variant_by_key(json, "components");
10✔
1455
        if (!v)
10✔
1456
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'components'");
×
1457
        r = sd_json_variant_strv(v, &components);
10✔
1458
        if (r < 0)
10✔
1459
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Key 'components' should be a strv");
×
1460

1461
        if (ret_components)
10✔
1462
                *ret_components = TAKE_PTR(components);
10✔
1463
        if (ret_have_default)
10✔
1464
                *ret_have_default = have_default;
10✔
1465
        return 0;
1466
}
1467

1468
static int manager_ensure_targets(Manager *m);
1469

1470
static int target_object_find(
28✔
1471
                sd_bus *bus,
1472
                const char *path,
1473
                const char *iface,
1474
                void *userdata,
1475
                void **found,
1476
                sd_bus_error *error) {
1477

1478
        Manager *m = ASSERT_PTR(userdata);
28✔
1479
        Target *t;
28✔
1480
        _cleanup_free_ char *e = NULL;
28✔
1481
        const char *p;
28✔
1482
        int r;
28✔
1483

1484
        assert(bus);
28✔
1485
        assert(path);
28✔
1486
        assert(found);
28✔
1487

1488
        p = startswith(path, "/org/freedesktop/sysupdate1/target/");
28✔
1489
        if (!p)
28✔
1490
                return 0;
1491

1492
        e = bus_label_unescape(p);
28✔
1493
        if (!e)
28✔
1494
                return -ENOMEM;
1495

1496
        r = manager_ensure_targets(m);
28✔
1497
        if (r < 0)
28✔
1498
                return r;
1499

1500
        t = hashmap_get(m->targets, e);
28✔
1501
        if (!t)
28✔
1502
                return 0;
1503

1504
        *found = t;
28✔
1505
        return 1;
28✔
1506
}
1507

1508
static char *target_bus_path(Target *t) {
10✔
1509
        _cleanup_free_ char *e = NULL;
10✔
1510

1511
        assert(t);
10✔
1512

1513
        e = bus_label_escape(t->id);
10✔
1514
        if (!e)
10✔
1515
                return NULL;
1516

1517
        return strjoin("/org/freedesktop/sysupdate1/target/", e);
10✔
1518
}
1519

1520
static int target_node_enumerator(
×
1521
                sd_bus *bus,
1522
                const char *path,
1523
                void *userdata,
1524
                char ***nodes,
1525
                sd_bus_error *error) {
1526

1527
        _cleanup_strv_free_ char **l = NULL;
×
1528
        Manager *m = ASSERT_PTR(userdata);
×
1529
        Target *t;
×
1530
        unsigned k = 0;
×
1531
        int r;
×
1532

1533
        r = manager_ensure_targets(m);
×
1534
        if (r < 0)
×
1535
                return r;
1536

1537
        l = new0(char*, hashmap_size(m->targets) + 1);
×
1538
        if (!l)
×
1539
                return -ENOMEM;
1540

1541
        HASHMAP_FOREACH(t, m->targets) {
×
1542
                l[k] = target_bus_path(t);
×
1543
                if (!l[k])
×
1544
                        return -ENOMEM;
×
1545
                k++;
×
1546
        }
1547

1548
        *nodes = TAKE_PTR(l);
×
1549
        return 1;
×
1550
}
1551

1552
static const sd_bus_vtable target_vtable[] = {
1553
        SD_BUS_VTABLE_START(0),
1554

1555
        SD_BUS_PROPERTY("Class", "s", target_property_get_class,
1556
                        offsetof(Target, class), SD_BUS_VTABLE_PROPERTY_CONST),
1557
        SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Target, name),
1558
                        SD_BUS_VTABLE_PROPERTY_CONST),
1559
        SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Target, path),
1560
                        SD_BUS_VTABLE_PROPERTY_CONST),
1561

1562
        SD_BUS_METHOD_WITH_ARGS("List",
1563
                                SD_BUS_ARGS("t", flags),
1564
                                SD_BUS_RESULT("as", versions),
1565
                                target_method_list,
1566
                                SD_BUS_VTABLE_UNPRIVILEGED),
1567

1568
        SD_BUS_METHOD_WITH_ARGS("Describe",
1569
                                SD_BUS_ARGS("s", version, "t", flags),
1570
                                SD_BUS_RESULT("s", json),
1571
                                target_method_describe,
1572
                                SD_BUS_VTABLE_UNPRIVILEGED),
1573

1574
        SD_BUS_METHOD_WITH_ARGS("CheckNew",
1575
                                SD_BUS_NO_ARGS,
1576
                                SD_BUS_RESULT("s", new_version),
1577
                                target_method_check_new,
1578
                                SD_BUS_VTABLE_UNPRIVILEGED),
1579

1580
        SD_BUS_METHOD_WITH_ARGS("Update",
1581
                                SD_BUS_ARGS("s", new_version, "t", flags),
1582
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1583
                                target_method_update,
1584
                                SD_BUS_VTABLE_UNPRIVILEGED),
1585

1586
        SD_BUS_METHOD_WITH_ARGS("Vacuum",
1587
                                SD_BUS_NO_ARGS,
1588
                                SD_BUS_RESULT("u", instances, "u", disabled_transfers),
1589
                                target_method_vacuum,
1590
                                SD_BUS_VTABLE_UNPRIVILEGED),
1591

1592
        SD_BUS_METHOD_WITH_ARGS("GetAppStream",
1593
                                SD_BUS_NO_ARGS,
1594
                                SD_BUS_RESULT("as", appstream),
1595
                                target_method_get_appstream,
1596
                                SD_BUS_VTABLE_UNPRIVILEGED),
1597

1598
        SD_BUS_METHOD_WITH_ARGS("GetVersion",
1599
                                SD_BUS_NO_ARGS,
1600
                                SD_BUS_RESULT("s", version),
1601
                                target_method_get_version,
1602
                                SD_BUS_VTABLE_UNPRIVILEGED),
1603

1604
        SD_BUS_METHOD_WITH_ARGS("ListFeatures",
1605
                                SD_BUS_ARGS("t", flags),
1606
                                SD_BUS_RESULT("as", features),
1607
                                target_method_list_features,
1608
                                SD_BUS_VTABLE_UNPRIVILEGED),
1609

1610
        SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
1611
                                SD_BUS_ARGS("s", feature, "t", flags),
1612
                                SD_BUS_RESULT("s", json),
1613
                                target_method_describe_feature,
1614
                                SD_BUS_VTABLE_UNPRIVILEGED),
1615

1616
        SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
1617
                                SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
1618
                                SD_BUS_NO_RESULT,
1619
                                target_method_set_feature_enabled,
1620
                                SD_BUS_VTABLE_UNPRIVILEGED),
1621

1622
        SD_BUS_VTABLE_END
1623
};
1624

1625
static const BusObjectImplementation target_object = {
1626
        "/org/freedesktop/sysupdate1/target",
1627
        "org.freedesktop.sysupdate1.Target",
1628
        .fallback_vtables = BUS_FALLBACK_VTABLES({target_vtable, target_object_find}),
1629
        .node_enumerator = target_node_enumerator,
1630
};
1631

1632
static Manager *manager_free(Manager *m) {
1✔
1633
        if (!m)
1✔
1634
                return NULL;
1635

1636
        hashmap_free(m->targets);
1✔
1637
        hashmap_free(m->jobs);
1✔
1638

1639
        m->bus = sd_bus_flush_close_unref(m->bus);
1✔
1640
        free(m->notify_socket_path);
1✔
1641
        sd_event_unref(m->event);
1✔
1642

1643
        return mfree(m);
1✔
1644
}
1645

1646
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1647

1648
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
34✔
1649
        Manager *m = ASSERT_PTR(userdata);
34✔
1650
        int r;
34✔
1651

1652
        assert(fd >= 0);
34✔
1653

1654
        _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
×
1655
        _cleanup_free_ char *buf = NULL;
34✔
1656
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
34✔
1657
        if (r == -EAGAIN)
34✔
1658
                return 0;
1659
        if (r < 0)
34✔
1660
                return r;
1661

1662
        Job *j;
34✔
1663
        HASHMAP_FOREACH(j, m->jobs) {
53✔
1664
                PidRef child_pidref = PIDREF_NULL;
53✔
1665

1666
                r = event_source_get_child_pidref(j->child, &child_pidref);
53✔
1667
                if (r < 0)
53✔
1668
                        return log_warning_errno(r, "Failed to get child pidref: %m");
×
1669

1670
                if (pidref_equal(&sender_pidref, &child_pidref))
53✔
1671
                        break;
1672
        }
1673
        if (!j) {
34✔
1674
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1675
                return 0;
×
1676
        }
1677

1678
        char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
34✔
1679
        char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
34✔
1680
        char *errno_str = find_line_startswith(buf, "ERRNO=");
34✔
1681
        const char *ready = find_line_startswith(buf, "READY=1");
34✔
1682

1683
        if (version)
34✔
1684
                job_on_version(j, truncate_nl(version));
2✔
1685

1686
        if (progress)
34✔
1687
                job_on_progress(j, truncate_nl(progress));
8✔
1688

1689
        if (errno_str)
34✔
1690
                job_on_errno(j, truncate_nl(errno_str));
×
1691

1692
        /* Should come last, since this might actually detach the job */
1693
        if (ready)
34✔
1694
                job_on_ready(j);
2✔
1695

1696
        return 0;
1697
}
1698

1699
static int manager_new(Manager **ret) {
1✔
1700
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
1701
        int r;
1✔
1702

1703
        assert(ret);
1✔
1704

1705
        m = new(Manager, 1);
1✔
1706
        if (!m)
1✔
1707
                return -ENOMEM;
1708

1709
        *m = (Manager) {
1✔
1710
                .runtime_scope = RUNTIME_SCOPE_SYSTEM,
1711
        };
1712

1713
        r = sd_event_default(&m->event);
1✔
1714
        if (r < 0)
1✔
1715
                return r;
1716

1717
        (void) sd_event_set_watchdog(m->event, true);
1✔
1718

1719
        r = sd_event_set_signal_exit(m->event, true);
1✔
1720
        if (r < 0)
1✔
1721
                return r;
1722

1723
        r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK,
1✔
1724
                                sigrtmin18_handler, NULL);
1725
        if (r < 0)
1✔
1726
                return r;
1727

1728
        r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
1✔
1729
        if (r < 0)
1✔
UNCOV
1730
                log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m");
×
1731

1732
        r = sd_bus_default_system(&m->bus);
1✔
1733
        if (r < 0)
1✔
1734
                return r;
1735

1736
        r = notify_socket_prepare(
2✔
1737
                        m->event,
1738
                        SD_EVENT_PRIORITY_NORMAL,
1739
                        manager_on_notify,
1740
                        m,
1741
                        &m->notify_socket_path);
1✔
1742
        if (r < 0)
1✔
1743
                return r;
1744

1745
        *ret = TAKE_PTR(m);
1✔
1746
        return 0;
1✔
1747
}
1748

1749
static int manager_enumerate_image_class(Manager *m, TargetClass class) {
40✔
1750
        _cleanup_hashmap_free_ Hashmap *images = NULL;
40✔
1751
        Image *image;
40✔
1752
        int r;
40✔
1753

1754
        images = hashmap_new(&image_hash_ops);
40✔
1755
        if (!images)
40✔
1756
                return -ENOMEM;
1757

1758
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, images);
40✔
1759
        if (r < 0)
40✔
1760
                return r;
1761

1762
        HASHMAP_FOREACH(image, images) {
50✔
1763
                _cleanup_(target_freep) Target *t = NULL;
10✔
1764
                bool have = false;
10✔
1765

1766
                if (IMAGE_IS_HOST(image))
10✔
1767
                        continue; /* We already enroll the host ourselves */
10✔
1768

UNCOV
1769
                r = target_new(m, class, image->name, image->path, &t);
×
UNCOV
1770
                if (r < 0)
×
1771
                        return r;
UNCOV
1772
                t->image_type = image->type;
×
1773

UNCOV
1774
                r = target_list_components(t, NULL, &have);
×
UNCOV
1775
                if (r < 0)
×
1776
                        return r;
UNCOV
1777
                if (!have) {
×
UNCOV
1778
                        log_debug("Skipping %s because it has no default component", image->path);
×
UNCOV
1779
                        continue;
×
1780
                }
1781

UNCOV
1782
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
UNCOV
1783
                if (r < 0)
×
1784
                        return r;
UNCOV
1785
                TAKE_PTR(t);
×
1786
        }
1787

1788
        return 0;
40✔
1789
}
1790

1791
static int manager_enumerate_components(Manager *m) {
10✔
1792
        _cleanup_strv_free_ char **components = NULL;
10✔
1793
        bool have_default;
10✔
1794
        int r;
10✔
1795

1796
        r = target_list_components(NULL, &components, &have_default);
10✔
1797
        if (r < 0)
10✔
1798
                return r;
1799

1800
        if (have_default) {
10✔
UNCOV
1801
                _cleanup_(target_freep) Target *t = NULL;
×
1802

1803
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
10✔
1804
                if (r < 0)
10✔
1805
                        return r;
1806

1807
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
10✔
1808
                if (r < 0)
10✔
1809
                        return r;
1810
                TAKE_PTR(t);
10✔
1811
        }
1812

1813
        STRV_FOREACH(component, components) {
18✔
1814
                _cleanup_free_ char *path = NULL;
8✔
1815
                _cleanup_(target_freep) Target *t = NULL;
8✔
1816

1817
                path = strjoin("sysupdate.", *component, ".d");
8✔
1818
                if (!path)
8✔
1819
                        return -ENOMEM;
1820

1821
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
8✔
1822
                if (r < 0)
8✔
1823
                        return r;
1824

1825
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
8✔
1826
                if (r < 0)
8✔
1827
                        return r;
1828
                TAKE_PTR(t);
8✔
1829
        }
1830

1831
        return 0;
1832
}
1833

1834
static int manager_enumerate_targets(Manager *m) {
10✔
1835
        static const TargetClass discoverable_classes[] = {
10✔
1836
                TARGET_MACHINE,
1837
                TARGET_PORTABLE,
1838
                TARGET_SYSEXT,
1839
                TARGET_CONFEXT,
1840
        };
1841
        int r;
10✔
1842

1843
        assert(m);
10✔
1844

1845
        FOREACH_ARRAY(class, discoverable_classes, ELEMENTSOF(discoverable_classes)) {
50✔
1846
                r = manager_enumerate_image_class(m, *class);
40✔
1847
                if (r < 0)
40✔
1848
                        log_warning_errno(r, "Failed to enumerate %ss, ignoring: %m",
40✔
1849
                                          target_class_to_string(*class));
1850
        }
1851

1852
        r = manager_enumerate_components(m);
10✔
1853
        if (r < 0)
10✔
UNCOV
1854
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1855

1856
        return 0;
10✔
1857
}
1858

1859
static int manager_ensure_targets(Manager *m) {
34✔
1860
        assert(m);
34✔
1861

1862
        if (!hashmap_isempty(m->targets))
34✔
1863
                return 0;
1864

1865
        return manager_enumerate_targets(m);
10✔
1866
}
1867

1868
static int method_list_targets(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
6✔
1869
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
6✔
1870
        Manager *m = ASSERT_PTR(userdata);
6✔
1871
        Target *t;
6✔
1872
        int r;
6✔
1873

1874
        assert(msg);
6✔
1875

1876
        r = manager_ensure_targets(m);
6✔
1877
        if (r < 0)
6✔
1878
                return r;
1879

1880
        r = sd_bus_message_new_method_return(msg, &reply);
6✔
1881
        if (r < 0)
6✔
1882
                return r;
1883

1884
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
6✔
1885
        if (r < 0)
6✔
1886
                return r;
1887

1888
        HASHMAP_FOREACH(t, m->targets) {
16✔
1889
                _cleanup_free_ char *bus_path = NULL;
10✔
1890

1891
                bus_path = target_bus_path(t);
10✔
1892
                if (!bus_path)
10✔
1893
                        return -ENOMEM;
1894

1895
                r = sd_bus_message_append(reply, "(sso)",
20✔
1896
                                          target_class_to_string(t->class),
1897
                                          t->name,
10✔
1898
                                          bus_path);
1899
                if (r < 0)
10✔
1900
                        return r;
1901
        }
1902

1903
        r = sd_bus_message_close_container(reply);
6✔
1904
        if (r < 0)
6✔
1905
                return r;
1906

1907
        return sd_bus_send(NULL, reply, NULL);
6✔
1908
}
1909

UNCOV
1910
static int method_list_jobs(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
UNCOV
1911
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
1912
        Manager *m = ASSERT_PTR(userdata);
×
UNCOV
1913
        Job *j;
×
UNCOV
1914
        int r;
×
1915

UNCOV
1916
        assert(msg);
×
1917

UNCOV
1918
        r = sd_bus_message_new_method_return(msg, &reply);
×
UNCOV
1919
        if (r < 0)
×
1920
                return r;
1921

UNCOV
1922
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
UNCOV
1923
        if (r < 0)
×
1924
                return r;
1925

UNCOV
1926
        HASHMAP_FOREACH(j, m->jobs) {
×
UNCOV
1927
                r = sd_bus_message_append(reply, "(tsuo)",
×
1928
                                          j->id,
1929
                                          job_type_to_string(j->type),
1930
                                          j->progress_percent,
UNCOV
1931
                                          j->object_path);
×
UNCOV
1932
                if (r < 0)
×
UNCOV
1933
                        return r;
×
1934
        }
1935

1936
        r = sd_bus_message_close_container(reply);
×
1937
        if (r < 0)
×
1938
                return r;
1939

UNCOV
1940
        return sd_bus_send(NULL, reply, NULL);
×
1941
}
1942

1943
static int method_list_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1944
        _cleanup_strv_free_ char **urls = NULL;
×
UNCOV
1945
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
UNCOV
1946
        Manager *m = ASSERT_PTR(userdata);
×
1947
        Target *t;
×
1948
        int r;
×
1949

UNCOV
1950
        assert(msg);
×
1951

1952
        r = manager_ensure_targets(m);
×
UNCOV
1953
        if (r < 0)
×
1954
                return r;
1955

1956
        HASHMAP_FOREACH(t, m->targets) {
×
1957
                char **target_appstream;
×
1958

UNCOV
1959
                r = target_get_appstream(t, &target_appstream);
×
UNCOV
1960
                if (r < 0)
×
1961
                        return r;
×
1962

UNCOV
1963
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates = */ true);
×
UNCOV
1964
                if (r < 0)
×
1965
                        return r;
1966
        }
1967

1968
        r = sd_bus_message_new_method_return(msg, &reply);
×
1969
        if (r < 0)
×
1970
                return r;
1971

1972
        r = sd_bus_message_append_strv(reply, urls);
×
1973
        if (r < 0)
×
1974
                return r;
1975

UNCOV
1976
        return sd_bus_send(NULL, reply, NULL);
×
1977
}
1978

1979
static const sd_bus_vtable manager_vtable[] = {
1980
        SD_BUS_VTABLE_START(0),
1981

1982
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
1983
                                SD_BUS_NO_ARGS,
1984
                                SD_BUS_RESULT("a(sso)", targets),
1985
                                method_list_targets,
1986
                                SD_BUS_VTABLE_UNPRIVILEGED),
1987

1988
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
1989
                                SD_BUS_NO_ARGS,
1990
                                SD_BUS_RESULT("a(tsuo)", jobs),
1991
                                method_list_jobs,
1992
                                SD_BUS_VTABLE_UNPRIVILEGED),
1993

1994
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
1995
                                SD_BUS_NO_ARGS,
1996
                                SD_BUS_RESULT("as", urls),
1997
                                method_list_appstream,
1998
                                SD_BUS_VTABLE_UNPRIVILEGED),
1999

2000
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2001
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2002
                                0),
2003

2004
        SD_BUS_VTABLE_END
2005
};
2006

2007
static const BusObjectImplementation manager_object = {
2008
        "/org/freedesktop/sysupdate1",
2009
        "org.freedesktop.sysupdate1.Manager",
2010
        .vtables = BUS_VTABLES(manager_vtable),
2011
        .children = BUS_IMPLEMENTATIONS(&job_object, &target_object),
2012
};
2013

2014
static int manager_add_bus_objects(Manager *m) {
1✔
2015
        int r;
1✔
2016

2017
        assert(m);
1✔
2018

2019
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2020
        if (r < 0)
1✔
2021
                return r;
2022

2023
        r = bus_log_control_api_register(m->bus);
1✔
2024
        if (r < 0)
1✔
2025
                return r;
2026

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

2031
        r = sd_bus_attach_event(m->bus, m->event, 0);
1✔
2032
        if (r < 0)
1✔
UNCOV
2033
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
2034

2035
        return 0;
2036
}
2037

2038
static bool manager_is_idle(void *userdata) {
91✔
2039
        Manager *m = ASSERT_PTR(userdata);
91✔
2040

2041
        return hashmap_isempty(m->jobs);
91✔
2042
}
2043

2044
static void manager_check_idle(Manager *m) {
20✔
2045
        assert(m);
20✔
2046

2047
        if (!hashmap_isempty(m->jobs))
20✔
2048
                return;
2049

2050
        hashmap_clear(m->targets);
10✔
2051
        log_debug("Cleared target cache");
10✔
2052
}
2053

2054
static int manager_run(Manager *m) {
1✔
2055
        assert(m);
1✔
2056

2057
        return bus_event_loop_with_idle(m->event,
1✔
2058
                                        m->bus,
2059
                                        "org.freedesktop.sysupdate1",
2060
                                        DEFAULT_EXIT_USEC,
2061
                                        manager_is_idle,
2062
                                        m);
2063
}
2064

2065
static int run(int argc, char *argv[]) {
1✔
2066
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
2067
        int r;
1✔
2068

2069
        log_setup();
1✔
2070

2071
        r = service_parse_argv("systemd-sysupdated.service",
2✔
2072
                               "System update management service.",
2073
                               BUS_IMPLEMENTATIONS(&manager_object,
1✔
2074
                                                   &log_control_object),
2075
                               argc, argv);
2076
        if (r <= 0)
1✔
2077
                return r;
2078

2079
        umask(0022);
1✔
2080

2081
        /* SIGCHLD signal must be blocked for sd_event_add_child to work */
2082
        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
1✔
2083

2084
        r = manager_new(&m);
1✔
2085
        if (r < 0)
1✔
UNCOV
2086
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2087

2088
        r = manager_add_bus_objects(m);
1✔
2089
        if (r < 0)
1✔
UNCOV
2090
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2091

2092
        r = manager_run(m);
1✔
2093
        if (r < 0)
1✔
UNCOV
2094
                return log_error_errno(r, "Failed to run event loop: %m");
×
2095

2096
        return 0;
2097
}
2098

2099
DEFINE_MAIN_FUNCTION(run);
2✔
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