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

systemd / systemd / 15057632786

15 May 2025 09:01PM UTC coverage: 72.267% (+0.02%) from 72.244%
15057632786

push

github

bluca
man: document how to hook stuff into system wakeup

Fixes: #6364

298523 of 413084 relevant lines covered (72.27%)

738132.88 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 "parse-util.h"
30
#include "process-util.h"
31
#include "service-util.h"
32
#include "signal-util.h"
33
#include "socket-util.h"
34
#include "string-table.h"
35
#include "sysupdate-util.h"
36
#include "utf8.h"
37

38
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
39

40
typedef struct Manager {
41
        sd_event *event;
42
        sd_bus *bus;
43

44
        Hashmap *targets;
45

46
        uint64_t last_job_id;
47
        Hashmap *jobs;
48

49
        Hashmap *polkit_registry;
50

51
        char *notify_socket_path;
52

53
        RuntimeScope runtime_scope; /* For now only RUNTIME_SCOPE_SYSTEM */
54
} Manager;
55

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

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

67
        /* sysupdate-specific classes */
68
        TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX,
69
        TARGET_COMPONENT,
70

71
        _TARGET_CLASS_MAX,
72
        _TARGET_CLASS_INVALID = -EINVAL,
73
} TargetClass;
74

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

78
typedef struct Target {
79
        Manager *manager;
80

81
        TargetClass class;
82
        char *name;
83
        char *path;
84

85
        char *id;
86
        ImageType image_type;
87
        bool busy;
88
} Target;
89

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

101
typedef struct Job Job;
102

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

106
struct Job {
107
        Manager *manager;
108
        Target *target;
109

110
        uint64_t id;
111
        char *object_path;
112

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

118
        unsigned progress_percent;
119

120
        sd_event_source *child;
121
        int stdout_fd;
122
        int status_errno;
123
        unsigned n_cancelled;
124

125
        sd_json_variant *json;
126

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

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

141
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
38✔
142

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

152
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
153

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

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

161
        free(j->object_path);
20✔
162
        free(j->version);
20✔
163
        free(j->feature);
20✔
164

165
        sd_json_variant_unref(j->json);
20✔
166

167
        sd_bus_message_unref(j->dbus_msg);
20✔
168

169
        sd_event_source_disable_unref(j->child);
20✔
170
        safe_close(j->stdout_fd);
20✔
171

172
        return mfree(j);
20✔
173
}
174

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

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

183
        assert(t);
20✔
184
        assert(ret);
20✔
185

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

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

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

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

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

208
        t->manager->last_job_id = j->id;
20✔
209

210
        *ret = TAKE_PTR(j);
20✔
211
        return 0;
20✔
212
}
213

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

222
        assert(ret);
20✔
223

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

227
        assert(S_ISREG(st.st_mode));
20✔
228

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

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

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

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

245
        *ret = TAKE_PTR(v);
20✔
246
        return 0;
20✔
247
}
248

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

253
        assert(j);
2✔
254

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

260
        if (!j->detach_cb)
2✔
261
                return;
262

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

265
        assert(j->dbus_msg);
2✔
266
        msg = TAKE_PTR(j->dbus_msg);
2✔
267

268
        j->complete_cb = NULL;
2✔
269

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

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

278
        assert(j);
×
279
        assert(buf);
×
280

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

287
        j->status_errno = r;
×
288

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

292
static void job_on_progress(Job *j, const char *buf) {
6✔
293
        unsigned progress;
6✔
294
        int r;
6✔
295

296
        assert(j);
6✔
297
        assert(buf);
6✔
298

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

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

310
        log_debug("Got percentage from job %" PRIu64 ": %u%%", j->id, j->progress_percent);
6✔
311
}
312

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

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

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

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

330
        assert(j);
20✔
331
        assert(s);
20✔
332
        assert(si);
20✔
333

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

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

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

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

390
        job_free(j);
20✔
391

392
        if (manager)
20✔
393
                manager_check_idle(manager);
20✔
394

395
        return 0;
40✔
396
}
397

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

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

405
        assert(t);
24✔
406
        assert(ret);
24✔
407

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

421
        *ret = TAKE_PTR(target_arg);
24✔
422
        return 0;
24✔
423
}
424

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

430
        assert(j);
20✔
431

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

514
                default:
×
515
                        assert_not_reached();
×
516
                }
517

518
                if (DEBUG_LOGGING) {
20✔
519
                        _cleanup_free_ char *s = NULL;
20✔
520

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

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

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

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

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

542
        r = sd_event_source_set_child_process_own(j->child, true);
20✔
543
        if (r < 0)
20✔
544
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
545
        TAKE_PIDREF(pid);
20✔
546

547
        j->stdout_fd = TAKE_FD(stdout_fd);
20✔
548

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

552
        return 0;
553
}
554

555
static int job_cancel(Job *j) {
×
556
        int r;
×
557

558
        assert(j);
×
559

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

565
        j->n_cancelled++;
×
566
        return 0;
×
567
}
568

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

574
        assert(msg);
×
575

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

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

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

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

598
        default:
×
599
                assert_not_reached();
×
600
        }
601

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

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

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

622
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
623

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

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

638
        assert(bus);
6✔
639
        assert(path);
6✔
640
        assert(ret);
6✔
641

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

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

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

654
        *ret = j;
6✔
655
        return 1;
6✔
656
}
657

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

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

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

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

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

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

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

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

695
        SD_BUS_VTABLE_END
696
};
697

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

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

709
        free(t->name);
18✔
710
        free(t->path);
18✔
711
        free(t->id);
18✔
712

713
        return mfree(t);
18✔
714
}
715

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

814
                if (DEBUG_LOGGING) {
14✔
815
                        _cleanup_free_ char *s = NULL;
14✔
816

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

823
                        log_debug("Spawning sysupdate: %s", s);
14✔
824
                }
825

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

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

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

840
        *ret = TAKE_PTR(v);
14✔
841
        return 0;
14✔
842
}
843

844
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
845

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

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

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

864
        assert(json);
2✔
865

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

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

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

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

882
        return sd_bus_send(NULL, reply, NULL);
2✔
883
}
884

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

891
        assert(msg);
2✔
892

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

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

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

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

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

922
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
2✔
923

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

929
        return 1;
2✔
930
}
931

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

940
        /* NOTE: This is also reused by target_method_describe_feature */
941

942
        assert(json);
12✔
943

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

948
        return sd_bus_reply_method_return(msg, "s", text);
12✔
949
}
950

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

958
        assert(msg);
12✔
959

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

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

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

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

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

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

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

997
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
12✔
998

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

1004
        return 1;
12✔
1005
}
1006

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

1014
        assert(json);
4✔
1015

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

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

1027
        return sd_bus_reply_method_return(msg, "s", reply);
4✔
1028
}
1029

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

1035
        assert(msg);
4✔
1036

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

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

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

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

1064
        return 1;
4✔
1065
}
1066

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

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

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

1083
        assert(msg);
2✔
1084
        assert(j);
2✔
1085

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

1090
        return 0;
1091
}
1092

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

1100
        assert(msg);
2✔
1101

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

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

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

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

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

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

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

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

1146
        return 1;
2✔
1147
}
1148

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

1155
        sd_json_variant *v;
×
1156
        uint64_t instances, disabled;
×
1157

1158
        assert(json);
×
1159

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

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

1176
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1177
}
1178

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

1184
        assert(msg);
×
1185

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

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

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

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

1212
        return 1;
×
1213
}
1214

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

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

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

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

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

1235
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
4✔
1236
}
1237

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

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

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

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

1255
        return 0;
1256
}
1257

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

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

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

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

1276
        return sd_bus_send(NULL, reply, NULL);
×
1277
}
1278

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

1288
        assert(msg);
×
1289

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

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

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

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

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

1315
        return sd_bus_send(NULL, reply, NULL);
×
1316
}
1317

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

1325
        assert(msg);
×
1326

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

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

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

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

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

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

1350
        return 1;
×
1351
}
1352

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

1357
        if (!ascii_is_valid(name))
×
1358
                return false;
1359

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

1363
        return true;
1364
}
1365

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

1374
        assert(msg);
×
1375

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

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

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

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

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

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

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

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

1437
        return sd_bus_reply_method_return(msg, NULL);
×
1438
}
1439

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

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

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

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

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

1470
static int manager_ensure_targets(Manager *m);
1471

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

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

1486
        assert(bus);
28✔
1487
        assert(path);
28✔
1488
        assert(found);
28✔
1489

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

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

1498
        r = manager_ensure_targets(m);
28✔
1499
        if (r < 0)
28✔
1500
                return r;
1501

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

1506
        *found = t;
28✔
1507
        return 1;
28✔
1508
}
1509

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

1513
        assert(t);
10✔
1514

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

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

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

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

1535
        r = manager_ensure_targets(m);
×
1536
        if (r < 0)
×
1537
                return r;
1538

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

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

1550
        *nodes = TAKE_PTR(l);
×
1551
        return 1;
×
1552
}
1553

1554
static const sd_bus_vtable target_vtable[] = {
1555
        SD_BUS_VTABLE_START(0),
1556

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

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

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

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

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

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

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

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

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

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

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

1624
        SD_BUS_VTABLE_END
1625
};
1626

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

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

1638
        hashmap_free(m->targets);
1✔
1639
        hashmap_free(m->jobs);
1✔
1640

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

1645
        return mfree(m);
1✔
1646
}
1647

1648
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1649

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

1654
        assert(fd >= 0);
32✔
1655

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

1664
        Job *j;
32✔
1665
        HASHMAP_FOREACH(j, m->jobs) {
50✔
1666
                PidRef child_pidref = PIDREF_NULL;
50✔
1667

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

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

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

1685
        if (version)
32✔
1686
                job_on_version(j, truncate_nl(version));
2✔
1687

1688
        if (progress)
32✔
1689
                job_on_progress(j, truncate_nl(progress));
6✔
1690

1691
        if (errno_str)
32✔
1692
                job_on_errno(j, truncate_nl(errno_str));
×
1693

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

1698
        return 0;
1699
}
1700

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

1705
        assert(ret);
1✔
1706

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

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

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

1719
        (void) sd_event_set_watchdog(m->event, true);
1✔
1720

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

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

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

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

1738
        r = notify_socket_prepare(
2✔
1739
                        m->event,
1740
                        SD_EVENT_PRIORITY_NORMAL,
1741
                        manager_on_notify,
1742
                        m,
1743
                        &m->notify_socket_path,
1✔
1744
                        /* ret_event_source= */ NULL);
1745
        if (r < 0)
1✔
1746
                return r;
1747

1748
        *ret = TAKE_PTR(m);
1✔
1749
        return 0;
1✔
1750
}
1751

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

1757
        images = hashmap_new(&image_hash_ops);
40✔
1758
        if (!images)
40✔
1759
                return -ENOMEM;
1760

1761
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, images);
40✔
1762
        if (r < 0)
40✔
1763
                return r;
1764

1765
        HASHMAP_FOREACH(image, images) {
50✔
1766
                _cleanup_(target_freep) Target *t = NULL;
10✔
1767
                bool have = false;
10✔
1768

1769
                if (image_is_host(image))
10✔
1770
                        continue; /* We already enroll the host ourselves */
10✔
1771

1772
                r = target_new(m, class, image->name, image->path, &t);
×
1773
                if (r < 0)
×
1774
                        return r;
1775
                t->image_type = image->type;
×
1776

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

1785
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
1786
                if (r < 0)
×
1787
                        return r;
1788
                TAKE_PTR(t);
×
1789
        }
1790

1791
        return 0;
40✔
1792
}
1793

1794
static int manager_enumerate_components(Manager *m) {
10✔
1795
        _cleanup_strv_free_ char **components = NULL;
10✔
1796
        bool have_default;
10✔
1797
        int r;
10✔
1798

1799
        r = target_list_components(NULL, &components, &have_default);
10✔
1800
        if (r < 0)
10✔
1801
                return r;
1802

1803
        if (have_default) {
10✔
1804
                _cleanup_(target_freep) Target *t = NULL;
×
1805

1806
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
10✔
1807
                if (r < 0)
10✔
1808
                        return r;
1809

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

1816
        STRV_FOREACH(component, components) {
18✔
1817
                _cleanup_free_ char *path = NULL;
8✔
1818
                _cleanup_(target_freep) Target *t = NULL;
8✔
1819

1820
                path = strjoin("sysupdate.", *component, ".d");
8✔
1821
                if (!path)
8✔
1822
                        return -ENOMEM;
1823

1824
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
8✔
1825
                if (r < 0)
8✔
1826
                        return r;
1827

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

1834
        return 0;
1835
}
1836

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

1846
        assert(m);
10✔
1847

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

1855
        r = manager_enumerate_components(m);
10✔
1856
        if (r < 0)
10✔
1857
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1858

1859
        return 0;
10✔
1860
}
1861

1862
static int manager_ensure_targets(Manager *m) {
34✔
1863
        assert(m);
34✔
1864

1865
        if (!hashmap_isempty(m->targets))
34✔
1866
                return 0;
1867

1868
        return manager_enumerate_targets(m);
10✔
1869
}
1870

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

1877
        assert(msg);
6✔
1878

1879
        r = manager_ensure_targets(m);
6✔
1880
        if (r < 0)
6✔
1881
                return r;
1882

1883
        r = sd_bus_message_new_method_return(msg, &reply);
6✔
1884
        if (r < 0)
6✔
1885
                return r;
1886

1887
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
6✔
1888
        if (r < 0)
6✔
1889
                return r;
1890

1891
        HASHMAP_FOREACH(t, m->targets) {
16✔
1892
                _cleanup_free_ char *bus_path = NULL;
10✔
1893

1894
                bus_path = target_bus_path(t);
10✔
1895
                if (!bus_path)
10✔
1896
                        return -ENOMEM;
1897

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

1906
        r = sd_bus_message_close_container(reply);
6✔
1907
        if (r < 0)
6✔
1908
                return r;
1909

1910
        return sd_bus_send(NULL, reply, NULL);
6✔
1911
}
1912

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

1919
        assert(msg);
×
1920

1921
        r = sd_bus_message_new_method_return(msg, &reply);
×
1922
        if (r < 0)
×
1923
                return r;
1924

1925
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
1926
        if (r < 0)
×
1927
                return r;
1928

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

1939
        r = sd_bus_message_close_container(reply);
×
1940
        if (r < 0)
×
1941
                return r;
1942

1943
        return sd_bus_send(NULL, reply, NULL);
×
1944
}
1945

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

1953
        assert(msg);
×
1954

1955
        r = manager_ensure_targets(m);
×
1956
        if (r < 0)
×
1957
                return r;
1958

1959
        HASHMAP_FOREACH(t, m->targets) {
×
1960
                char **target_appstream;
×
1961

1962
                r = target_get_appstream(t, &target_appstream);
×
1963
                if (r < 0)
×
1964
                        return r;
×
1965

1966
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates = */ true);
×
1967
                if (r < 0)
×
1968
                        return r;
1969
        }
1970

1971
        r = sd_bus_message_new_method_return(msg, &reply);
×
1972
        if (r < 0)
×
1973
                return r;
1974

1975
        r = sd_bus_message_append_strv(reply, urls);
×
1976
        if (r < 0)
×
1977
                return r;
1978

1979
        return sd_bus_send(NULL, reply, NULL);
×
1980
}
1981

1982
static const sd_bus_vtable manager_vtable[] = {
1983
        SD_BUS_VTABLE_START(0),
1984

1985
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
1986
                                SD_BUS_NO_ARGS,
1987
                                SD_BUS_RESULT("a(sso)", targets),
1988
                                method_list_targets,
1989
                                SD_BUS_VTABLE_UNPRIVILEGED),
1990

1991
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
1992
                                SD_BUS_NO_ARGS,
1993
                                SD_BUS_RESULT("a(tsuo)", jobs),
1994
                                method_list_jobs,
1995
                                SD_BUS_VTABLE_UNPRIVILEGED),
1996

1997
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
1998
                                SD_BUS_NO_ARGS,
1999
                                SD_BUS_RESULT("as", urls),
2000
                                method_list_appstream,
2001
                                SD_BUS_VTABLE_UNPRIVILEGED),
2002

2003
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2004
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2005
                                0),
2006

2007
        SD_BUS_VTABLE_END
2008
};
2009

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

2017
static int manager_add_bus_objects(Manager *m) {
1✔
2018
        int r;
1✔
2019

2020
        assert(m);
1✔
2021

2022
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2023
        if (r < 0)
1✔
2024
                return r;
2025

2026
        r = bus_log_control_api_register(m->bus);
1✔
2027
        if (r < 0)
1✔
2028
                return r;
2029

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

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

2038
        return 0;
2039
}
2040

2041
static bool manager_is_idle(void *userdata) {
89✔
2042
        Manager *m = ASSERT_PTR(userdata);
89✔
2043

2044
        return hashmap_isempty(m->jobs);
89✔
2045
}
2046

2047
static void manager_check_idle(Manager *m) {
20✔
2048
        assert(m);
20✔
2049

2050
        if (!hashmap_isempty(m->jobs))
20✔
2051
                return;
2052

2053
        hashmap_clear(m->targets);
10✔
2054
        log_debug("Cleared target cache");
10✔
2055
}
2056

2057
static int manager_run(Manager *m) {
1✔
2058
        assert(m);
1✔
2059

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

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

2072
        log_setup();
1✔
2073

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

2082
        umask(0022);
1✔
2083

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

2087
        r = manager_new(&m);
1✔
2088
        if (r < 0)
1✔
2089
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2090

2091
        r = manager_add_bus_objects(m);
1✔
2092
        if (r < 0)
1✔
2093
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2094

2095
        r = manager_run(m);
1✔
2096
        if (r < 0)
1✔
2097
                return log_error_errno(r, "Failed to run event loop: %m");
×
2098

2099
        return 0;
2100
}
2101

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

© 2026 Coveralls, Inc