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

systemd / systemd / 18052125394

26 Sep 2025 11:00PM UTC coverage: 72.224% (+0.02%) from 72.205%
18052125394

push

github

YHNdnzj
pam_systemd: correct alignment

Follow-up for cf2630aca

303350 of 420010 relevant lines covered (72.22%)

1058085.05 hits per line

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

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

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

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

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

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

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

53
        Hashmap *targets;
54

55
        uint64_t last_job_id;
56
        Hashmap *jobs;
57

58
        Hashmap *polkit_registry;
59

60
        char *notify_socket_path;
61

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

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

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

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

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

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

87
typedef struct Target {
88
        Manager *manager;
89

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

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

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

110
typedef struct Job Job;
111

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

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

119
        uint64_t id;
120
        char *object_path;
121

122
        JobType type;
123
        bool offline;
124
        char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */
125
        char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
126

127
        unsigned progress_percent;
128

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

134
        sd_json_variant *json;
135

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

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

150
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
38✔
151

152
static const char* const job_type_table[_JOB_TYPE_MAX] = {
153
        [JOB_LIST]             = "list",
154
        [JOB_DESCRIBE]         = "describe",
155
        [JOB_CHECK_NEW]        = "check-new",
156
        [JOB_UPDATE]           = "update",
157
        [JOB_VACUUM]           = "vacuum",
158
        [JOB_DESCRIBE_FEATURE] = "describe-feature",
159
};
160

161
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
162

163
static Job *job_free(Job *j) {
20✔
164
        if (!j)
20✔
165
                return NULL;
166

167
        if (j->manager)
20✔
168
                assert_se(hashmap_remove(j->manager->jobs, &j->id) == j);
20✔
169

170
        free(j->object_path);
20✔
171
        free(j->version);
20✔
172
        free(j->feature);
20✔
173

174
        sd_json_variant_unref(j->json);
20✔
175

176
        sd_bus_message_unref(j->dbus_msg);
20✔
177

178
        sd_event_source_disable_unref(j->child);
20✔
179
        safe_close(j->stdout_fd);
20✔
180

181
        return mfree(j);
20✔
182
}
183

184
DEFINE_TRIVIAL_CLEANUP_FUNC(Job*, job_free);
40✔
185
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(job_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func,
×
186
                                      Job, job_free);
187

188
static int job_new(JobType type, Target *t, sd_bus_message *msg, JobComplete complete_cb, Job **ret) {
20✔
189
        _cleanup_(job_freep) Job *j = NULL;
20✔
190
        int r;
20✔
191

192
        assert(t);
20✔
193
        assert(ret);
20✔
194

195
        j = new(Job, 1);
20✔
196
        if (!j)
20✔
197
                return -ENOMEM;
198

199
        *j = (Job) {
40✔
200
                .type = type,
201
                .target = t,
202
                .id = t->manager->last_job_id + 1,
20✔
203
                .stdout_fd = -EBADF,
204
                .complete_cb = complete_cb,
205
                .dbus_msg = sd_bus_message_ref(msg),
20✔
206
        };
207

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

211
        r = hashmap_ensure_put(&t->manager->jobs, &job_hash_ops, &j->id, j);
20✔
212
        if (r < 0)
20✔
213
                return r;
214

215
        j->manager = t->manager;
20✔
216

217
        t->manager->last_job_id = j->id;
20✔
218

219
        *ret = TAKE_PTR(j);
20✔
220
        return 0;
20✔
221
}
222

223
static int job_parse_child_output(int _fd, sd_json_variant **ret) {
20✔
224
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
20✔
225
        /* Take ownership of the passed fd */
226
        _cleanup_close_ int fd = _fd;
20✔
227
        _cleanup_fclose_ FILE *f = NULL;
20✔
228
        struct stat st;
20✔
229
        int r;
20✔
230

231
        assert(ret);
20✔
232

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

236
        assert(S_ISREG(st.st_mode));
20✔
237

238
        if (st.st_size == 0) {
20✔
239
                log_warning("No output from child job, ignoring");
×
240
                return 0;
×
241
        }
242

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

246
        f = take_fdopen(&fd, "r");
20✔
247
        if (!f)
20✔
248
                return log_debug_errno(errno, "Failed to reopen memfd: %m");
×
249

250
        r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
20✔
251
        if (r < 0)
20✔
252
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
253

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

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

262
        assert(j);
2✔
263

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

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

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

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

277
        j->complete_cb = NULL;
2✔
278

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

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

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

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

296
        j->status_errno = r;
×
297

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

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

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

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

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

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

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

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

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

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

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

343
        if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM)) {
20✔
344
                assert(j->target->busy);
2✔
345
                j->target->busy = false;
2✔
346
        }
347

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

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

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

399
        job_free(j);
20✔
400

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

404
        return 0;
40✔
405
}
406

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

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

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

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

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

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

439
        assert(j);
20✔
440

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

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

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

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

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

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

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

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

490
                if (j->offline)
20✔
491
                        cmd[k++] = "--offline";
×
492

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

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

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

508
                case JOB_UPDATE:
2✔
509
                        cmd[k++] = "update";
2✔
510
                        cmd[k++] = empty_to_null(j->version);
4✔
511
                        break;
2✔
512

513
                case JOB_VACUUM:
×
514
                        cmd[k++] = "vacuum";
×
515
                        break;
×
516

517
                case JOB_DESCRIBE_FEATURE:
×
518
                        cmd[k++] = "features";
×
519
                        assert(!isempty(j->feature));
×
520
                        cmd[k++] = j->feature;
×
521
                        break;
×
522

523
                default:
×
524
                        assert_not_reached();
×
525
                }
526

527
                if (DEBUG_LOGGING) {
20✔
528
                        _cleanup_free_ char *s = NULL;
20✔
529

530
                        s = quote_command_line((char**) cmd, SHELL_ESCAPE_EMPTY);
20✔
531
                        if (!s) {
20✔
532
                                log_oom();
×
533
                                _exit(EXIT_FAILURE);
×
534
                        }
535

536
                        log_debug("Spawning worker for job %" PRIu64 ": %s", j->id, s);
20✔
537
                }
538

539
                r = invoke_callout_binary(sysupdate_binary_path(), (char *const *) cmd);
40✔
540
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
541
                _exit(EXIT_FAILURE);
×
542
        }
543

544
        log_info("Started job %" PRIu64 " with worker PID " PID_FMT,
20✔
545
                 j->id, pid.pid);
546

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

551
        r = sd_event_source_set_child_process_own(j->child, true);
20✔
552
        if (r < 0)
20✔
553
                return log_error_errno(r, "Event loop failed to take ownership of child process: %m");
×
554
        TAKE_PIDREF(pid);
20✔
555

556
        j->stdout_fd = TAKE_FD(stdout_fd);
20✔
557

558
        if (IN_SET(j->type, JOB_UPDATE, JOB_VACUUM))
20✔
559
                j->target->busy = true;
2✔
560

561
        return 0;
562
}
563

564
static int job_cancel(Job *j) {
×
565
        int r;
×
566

567
        assert(j);
×
568

569
        r = sd_event_source_send_child_signal(j->child, j->n_cancelled < 3 ? SIGTERM : SIGKILL,
×
570
                                              NULL, 0);
571
        if (r < 0)
×
572
                return r;
573

574
        j->n_cancelled++;
×
575
        return 0;
×
576
}
577

578
static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
579
        Job *j = ASSERT_PTR(userdata);
×
580
        const char *action;
×
581
        int r;
×
582

583
        assert(msg);
×
584

585
        switch (j->type) {
×
586
        case JOB_LIST:
587
        case JOB_DESCRIBE:
588
        case JOB_CHECK_NEW:
589
                action = "org.freedesktop.sysupdate1.check";
590
                break;
591

592
        case JOB_UPDATE:
×
593
                if (j->version)
×
594
                        action = "org.freedesktop.sysupdate1.update-to-version";
595
                else
596
                        action = "org.freedesktop.sysupdate1.update";
×
597
                break;
598

599
        case JOB_VACUUM:
600
                action = "org.freedesktop.sysupdate1.vacuum";
×
601
                break;
×
602

603
        case JOB_DESCRIBE_FEATURE:
604
                action = NULL;
605
                break;
606

607
        default:
×
608
                assert_not_reached();
×
609
        }
610

611
        if (action) {
×
612
                r = bus_verify_polkit_async(
×
613
                                msg,
614
                                action,
615
                                /* details= */ NULL,
616
                                &j->manager->polkit_registry,
×
617
                                error);
618
                if (r < 0)
×
619
                        return r;
620
                if (r == 0)
×
621
                        return 1; /* Will call us back */
622
        }
623

624
        r = job_cancel(j);
×
625
        if (r < 0)
×
626
                return r;
627

628
        return sd_bus_reply_method_return(msg, NULL);
×
629
}
630

631
static BUS_DEFINE_PROPERTY_GET_ENUM(job_property_get_type, job_type, JobType);
×
632

633
static int job_object_find(
7✔
634
                sd_bus *bus,
635
                const char *path,
636
                const char *iface,
637
                void *userdata,
638
                void **ret,
639
                sd_bus_error *error) {
640

641
        Manager *m = ASSERT_PTR(userdata);
7✔
642
        Job *j;
7✔
643
        const char *p;
7✔
644
        uint64_t id;
7✔
645
        int r;
7✔
646

647
        assert(bus);
7✔
648
        assert(path);
7✔
649
        assert(ret);
7✔
650

651
        p = startswith(path, "/org/freedesktop/sysupdate1/job/_");
7✔
652
        if (!p)
7✔
653
                return 0;
7✔
654

655
        r = safe_atou64(p, &id);
7✔
656
        if (r < 0 || id == 0)
7✔
657
                return 0;
658

659
        j = hashmap_get(m->jobs, &id);
7✔
660
        if (!j)
7✔
661
                return 0;
662

663
        *ret = j;
7✔
664
        return 1;
7✔
665
}
666

667
static int job_node_enumerator(
×
668
                sd_bus *bus,
669
                const char *path,
670
                void *userdata,
671
                char ***nodes,
672
                sd_bus_error *error) {
673

674
        _cleanup_strv_free_ char **l = NULL;
×
675
        Manager *m = ASSERT_PTR(userdata);
×
676
        Job *j;
×
677
        unsigned k = 0;
×
678

679
        l = new0(char*, hashmap_size(m->jobs) + 1);
×
680
        if (!l)
×
681
                return -ENOMEM;
682

683
        HASHMAP_FOREACH(j, m->jobs) {
×
684
                l[k] = strdup(j->object_path);
×
685
                if (!l[k])
×
686
                        return -ENOMEM;
×
687
                k++;
×
688
        }
689

690
        *nodes = TAKE_PTR(l);
×
691
        return 1;
×
692
}
693

694
static const sd_bus_vtable job_vtable[] = {
695
        SD_BUS_VTABLE_START(0),
696

697
        SD_BUS_PROPERTY("Id", "t", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
698
        SD_BUS_PROPERTY("Type", "s", job_property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
699
        SD_BUS_PROPERTY("Offline", "b", bus_property_get_bool, offsetof(Job, offline), SD_BUS_VTABLE_PROPERTY_CONST),
700
        SD_BUS_PROPERTY("Progress", "u", bus_property_get_unsigned, offsetof(Job, progress_percent), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
701

702
        SD_BUS_METHOD("Cancel", NULL, NULL, job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
703

704
        SD_BUS_VTABLE_END
705
};
706

707
static const BusObjectImplementation job_object = {
708
        "/org/freedesktop/sysupdate1/job",
709
        "org.freedesktop.sysupdate1.Job",
710
        .fallback_vtables = BUS_FALLBACK_VTABLES({job_vtable, job_object_find}),
711
        .node_enumerator = job_node_enumerator,
712
};
713

714
static Target *target_free(Target *t) {
18✔
715
        if (!t)
18✔
716
                return NULL;
717

718
        free(t->name);
18✔
719
        free(t->path);
18✔
720
        free(t->id);
18✔
721

722
        return mfree(t);
18✔
723
}
724

725
DEFINE_TRIVIAL_CLEANUP_FUNC(Target*, target_free);
46✔
726
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_hash_ops, char, string_hash_func, string_compare_func,
18✔
727
                                      Target, target_free);
728

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

732
        assert(m);
18✔
733
        assert(ret);
18✔
734

735
        t = new(Target, 1);
18✔
736
        if (!t)
18✔
737
                return -ENOMEM;
738

739
        *t = (Target) {
18✔
740
                .manager = m,
741
                .class = class,
742
                .image_type = _IMAGE_TYPE_INVALID,
743
        };
744

745
        t->name = strdup(name);
18✔
746
        if (!t->name)
18✔
747
                return -ENOMEM;
748

749
        t->path = strdup(path);
18✔
750
        if (!t->path)
18✔
751
                return -ENOMEM;
752

753
        if (class == TARGET_HOST)
18✔
754
                t->id = strdup("host"); /* This is what appears in the object path */
10✔
755
        else
756
                t->id = strjoin(target_class_to_string(class), ":", name);
8✔
757
        if (!t->id)
18✔
758
                return -ENOMEM;
759

760
        *ret = TAKE_PTR(t);
18✔
761
        return 0;
18✔
762
}
763

764
static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) {
14✔
765
        _cleanup_close_pair_ int pipe[2] = EBADF_PAIR;
14✔
766
        _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL;
×
767
        _cleanup_fclose_ FILE *f = NULL;
14✔
768
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
14✔
769
        _cleanup_free_ char *target_arg = NULL;
14✔
770
        int r;
14✔
771

772
        if (t) {
14✔
773
                r = target_get_argument(t, &target_arg);
4✔
774
                if (r < 0)
4✔
775
                        return r;
776
        }
777

778
        r = pipe2(pipe, O_CLOEXEC);
14✔
779
        if (r < 0)
14✔
780
                return -errno;
×
781

782
        r = pidref_safe_fork_full("(sd-sysupdate)",
42✔
783
                                  (int[]) { -EBADF, pipe[1], STDERR_FILENO },
14✔
784
                                  NULL, 0,
785
                                  FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|
786
                                  FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG,
787
                                  &pid);
788
        if (r < 0)
28✔
789
                return r;
790
        if (r == 0) {
28✔
791
                /* Child */
792
                va_list ap;
14✔
793
                char *arg;
14✔
794
                _cleanup_strv_free_ char **args = NULL;
×
795

796
                if (strv_extend(&args, "systemd-sysupdate") < 0) {
14✔
797
                        log_oom();
×
798
                        _exit(EXIT_FAILURE);
×
799
                }
800

801
                if (strv_extend(&args, "--json=short") < 0) {
14✔
802
                        log_oom();
×
803
                        _exit(EXIT_FAILURE);
×
804
                }
805

806
                if (target_arg && strv_extend(&args, target_arg) < 0) {
14✔
807
                        log_oom();
×
808
                        _exit(EXIT_FAILURE);
×
809
                }
810

811
                va_start(ap, t);
14✔
812
                while ((arg = va_arg(ap, char*))) {
32✔
813
                        r = strv_extend(&args, arg);
18✔
814
                        if (r < 0)
18✔
815
                                break;
816
                }
817
                va_end(ap);
14✔
818
                if (r < 0) {
14✔
819
                        log_oom();
×
820
                        _exit(EXIT_FAILURE);
×
821
                }
822

823
                if (DEBUG_LOGGING) {
14✔
824
                        _cleanup_free_ char *s = NULL;
14✔
825

826
                        s = quote_command_line((char**) args, SHELL_ESCAPE_EMPTY);
14✔
827
                        if (!s) {
14✔
828
                                log_oom();
×
829
                                _exit(EXIT_FAILURE);
×
830
                        }
831

832
                        log_debug("Spawning sysupdate: %s", s);
14✔
833
                }
834

835
                r = invoke_callout_binary(sysupdate_binary_path(), args);
28✔
836
                log_error_errno(r, "Failed to execute systemd-sysupdate: %m");
×
837
                _exit(EXIT_FAILURE);
×
838
        }
839

840
        pipe[1] = safe_close(pipe[1]);
14✔
841
        f = take_fdopen(&pipe[0], "r");
14✔
842
        if (!f)
14✔
843
                return -errno;
×
844

845
        r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL);
14✔
846
        if (r < 0)
14✔
847
                return log_debug_errno(r, "Failed to parse JSON: %m");
×
848

849
        *ret = TAKE_PTR(v);
14✔
850
        return 0;
14✔
851
}
852

853
static BUS_DEFINE_PROPERTY_GET_ENUM(target_property_get_class, target_class, TargetClass);
×
854

855
#define log_sysupdate_bad_json_full(lvl, r, verb, msg) \
856
        log_full_errno((lvl), (r), "Invalid JSON response from 'systemd-sysupdate %s': %s", (verb), (msg))
857
#define log_sysupdate_bad_json(r, verb, msg) \
858
        log_sysupdate_bad_json_full(LOG_ERR, (r), (verb), (msg))
859
#define log_sysupdate_bad_json_debug(r, verb, msg) \
860
        log_sysupdate_bad_json_full(LOG_DEBUG, (r), (verb), (msg))
861

862
static int target_method_list_finish(
2✔
863
                sd_bus_message *msg,
864
                const Job *j,
865
                sd_json_variant *json,
866
                sd_bus_error *error) {
867

868
        sd_json_variant *v;
2✔
869
        _cleanup_strv_free_ char **versions = NULL;
×
870
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2✔
871
        int r;
2✔
872

873
        assert(json);
2✔
874

875
        v = sd_json_variant_by_key(json, "all");
2✔
876
        if (!v)
2✔
877
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'all'");
×
878

879
        r = sd_json_variant_strv(v, &versions);
2✔
880
        if (r < 0)
2✔
881
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'all' should be strv");
×
882

883
        r = sd_bus_message_new_method_return(msg, &reply);
2✔
884
        if (r < 0)
2✔
885
                return r;
886

887
        r = sd_bus_message_append_strv(reply, versions);
2✔
888
        if (r < 0)
2✔
889
                return r;
890

891
        return sd_bus_message_send(reply);
2✔
892
}
893

894
static int target_method_list(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
2✔
895
        Target *t = ASSERT_PTR(userdata);
2✔
896
        _cleanup_(job_freep) Job *j = NULL;
2✔
897
        uint64_t flags;
2✔
898
        int r;
2✔
899

900
        assert(msg);
2✔
901

902
        r = sd_bus_message_read(msg, "t", &flags);
2✔
903
        if (r < 0)
2✔
904
                return r;
905

906
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
2✔
907
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
908

909
        const char *details[] = {
6✔
910
                "class", target_class_to_string(t->class),
2✔
911
                "name", t->name,
2✔
912
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
2✔
913
                NULL
914
        };
915

916
        r = bus_verify_polkit_async(
4✔
917
                msg,
918
                "org.freedesktop.sysupdate1.check",
919
                details,
920
                &t->manager->polkit_registry,
2✔
921
                error);
922
        if (r < 0)
2✔
923
                return r;
924
        if (r == 0)
2✔
925
                return 1; /* Will call us back */
926

927
        r = job_new(JOB_LIST, t, msg, target_method_list_finish, &j);
2✔
928
        if (r < 0)
2✔
929
                return r;
930

931
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
2✔
932

933
        r = job_start(j);
2✔
934
        if (r < 0)
2✔
935
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
936
        TAKE_PTR(j); /* Avoid job from being killed & freed */
2✔
937

938
        return 1;
2✔
939
}
940

941
static int target_method_describe_finish(
12✔
942
                sd_bus_message *msg,
943
                const Job *j,
944
                sd_json_variant *json,
945
                sd_bus_error *error) {
946
        _cleanup_free_ char *text = NULL;
12✔
947
        int r;
12✔
948

949
        /* NOTE: This is also reused by target_method_describe_feature */
950

951
        assert(json);
12✔
952

953
        r = sd_json_variant_format(json, 0, &text);
12✔
954
        if (r < 0)
12✔
955
                return r;
956

957
        return sd_bus_reply_method_return(msg, "s", text);
12✔
958
}
959

960
static int target_method_describe(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
12✔
961
        Target *t = ASSERT_PTR(userdata);
12✔
962
        _cleanup_(job_freep) Job *j = NULL;
12✔
963
        const char *version;
12✔
964
        uint64_t flags;
12✔
965
        int r;
12✔
966

967
        assert(msg);
12✔
968

969
        r = sd_bus_message_read(msg, "st", &version, &flags);
12✔
970
        if (r < 0)
12✔
971
                return r;
972

973
        if (isempty(version))
12✔
974
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Version must be specified");
×
975

976
        if ((flags & ~SD_SYSUPDATE_FLAGS_ALL) != 0)
12✔
977
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags specified");
×
978

979
        const char *details[] = {
36✔
980
                "class", target_class_to_string(t->class),
12✔
981
                "name", t->name,
12✔
982
                "version", version,
983
                "offline", one_zero(FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE)),
12✔
984
                NULL
985
        };
986

987
        r = bus_verify_polkit_async(
24✔
988
                msg,
989
                "org.freedesktop.sysupdate1.check",
990
                details,
991
                &t->manager->polkit_registry,
12✔
992
                error);
993
        if (r < 0)
12✔
994
                return r;
995
        if (r == 0)
12✔
996
                return 1; /* Will call us back */
997

998
        r = job_new(JOB_DESCRIBE, t, msg, target_method_describe_finish, &j);
12✔
999
        if (r < 0)
12✔
1000
                return r;
1001

1002
        j->version = strdup(version);
12✔
1003
        if (!j->version)
12✔
1004
                return log_oom();
×
1005

1006
        j->offline = FLAGS_SET(flags, SD_SYSUPDATE_OFFLINE);
12✔
1007

1008
        r = job_start(j);
12✔
1009
        if (r < 0)
12✔
1010
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1011
        TAKE_PTR(j); /* Avoid job from being killed & freed */
12✔
1012

1013
        return 1;
12✔
1014
}
1015

1016
static int target_method_check_new_finish(
4✔
1017
                sd_bus_message *msg,
1018
                const Job *j,
1019
                sd_json_variant *json,
1020
                sd_bus_error *error) {
1021
        const char *reply;
4✔
1022

1023
        assert(json);
4✔
1024

1025
        sd_json_variant *v = sd_json_variant_by_key(json, "available");
4✔
1026
        if (!v)
4✔
1027
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "check-new", "Missing key 'available'");
×
1028

1029
        if (sd_json_variant_is_null(v))
4✔
1030
                reply = "";
1031
        else
1032
                reply = sd_json_variant_string(v);
×
1033
        if (!reply)
×
1034
                return -EINVAL;
1035

1036
        return sd_bus_reply_method_return(msg, "s", reply);
4✔
1037
}
1038

1039
static int target_method_check_new(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
4✔
1040
        Target *t = ASSERT_PTR(userdata);
4✔
1041
        _cleanup_(job_freep) Job *j = NULL;
4✔
1042
        int r;
4✔
1043

1044
        assert(msg);
4✔
1045

1046
        const char *details[] = {
8✔
1047
                "class", target_class_to_string(t->class),
4✔
1048
                "name", t->name,
4✔
1049
                "offline", "0",
1050
                NULL
1051
        };
1052

1053
        r = bus_verify_polkit_async(
8✔
1054
                        msg,
1055
                        "org.freedesktop.sysupdate1.check",
1056
                        details,
1057
                        &t->manager->polkit_registry,
4✔
1058
                        error);
1059
        if (r < 0)
4✔
1060
                return r;
1061
        if (r == 0)
4✔
1062
                return 1; /* Will call us back */
1063

1064
        r = job_new(JOB_CHECK_NEW, t, msg, target_method_check_new_finish, &j);
4✔
1065
        if (r < 0)
4✔
1066
                return r;
1067

1068
        r = job_start(j);
4✔
1069
        if (r < 0)
4✔
1070
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1071
        TAKE_PTR(j); /* Avoid job from being killed & freed */
4✔
1072

1073
        return 1;
4✔
1074
}
1075

1076
static int target_method_update_finished_early(
×
1077
                sd_bus_message *msg,
1078
                const Job *j,
1079
                sd_json_variant *json,
1080
                sd_bus_error *error) {
1081

1082
        /* Called when job finishes w/ a successful exit code, but before any work begins.
1083
         * This happens when there is no candidate (i.e. we're already up-to-date), or
1084
         * specified update is already installed. */
1085
        return sd_bus_error_setf(error, BUS_ERROR_NO_UPDATE_CANDIDATE,
×
1086
                                 "Job exited successfully with no work to do, assume already updated");
1087
}
1088

1089
static int target_method_update_detach(sd_bus_message *msg, const Job *j) {
2✔
1090
        int r;
2✔
1091

1092
        assert(msg);
2✔
1093
        assert(j);
2✔
1094

1095
        r = sd_bus_reply_method_return(msg, "sto", j->version, j->id, j->object_path);
2✔
1096
        if (r < 0)
2✔
1097
                return bus_log_parse_error(r);
×
1098

1099
        return 0;
1100
}
1101

1102
static int target_method_update(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
2✔
1103
        Target *t = ASSERT_PTR(userdata);
2✔
1104
        _cleanup_(job_freep) Job *j = NULL;
2✔
1105
        const char *version, *action;
2✔
1106
        uint64_t flags;
2✔
1107
        int r;
2✔
1108

1109
        assert(msg);
2✔
1110

1111
        r = sd_bus_message_read(msg, "st", &version, &flags);
2✔
1112
        if (r < 0)
2✔
1113
                return r;
1114

1115
        if (flags != 0)
2✔
1116
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1117

1118
        if (isempty(version))
4✔
1119
                action = "org.freedesktop.sysupdate1.update";
1120
        else
1121
                action = "org.freedesktop.sysupdate1.update-to-version";
1122

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

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

1141
        r = job_new(JOB_UPDATE, t, msg, target_method_update_finished_early, &j);
2✔
1142
        if (r < 0)
2✔
1143
                return r;
1144
        j->detach_cb = target_method_update_detach;
2✔
1145

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

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

1155
        return 1;
2✔
1156
}
1157

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

1164
        sd_json_variant *v;
×
1165
        uint64_t instances, disabled;
×
1166

1167
        assert(json);
×
1168

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

1177
        v = sd_json_variant_by_key(json, "disabledTransfers");
×
1178
        if (!v)
×
1179
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
×
1180
        if (!sd_json_variant_is_unsigned(v))
×
1181
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
×
1182
        disabled = sd_json_variant_unsigned(v);
×
1183
        assert(disabled <= UINT32_MAX);
×
1184

1185
        return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
×
1186
}
1187

1188
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1189
        Target *t = ASSERT_PTR(userdata);
×
1190
        _cleanup_(job_freep) Job *j = NULL;
×
1191
        int r;
×
1192

1193
        assert(msg);
×
1194

1195
        const char *details[] = {
×
1196
                "class", target_class_to_string(t->class),
×
1197
                "name", t->name,
×
1198
                NULL
1199
        };
1200

1201
        r = bus_verify_polkit_async(
×
1202
                msg,
1203
                "org.freedesktop.sysupdate1.vacuum",
1204
                details,
1205
                &t->manager->polkit_registry,
×
1206
                error);
1207
        if (r < 0)
×
1208
                return r;
1209
        if (r == 0)
×
1210
                return 1; /* Will call us back */
1211

1212
        r = job_new(JOB_VACUUM, t, msg, target_method_vacuum_finish, &j);
×
1213
        if (r < 0)
×
1214
                return r;
1215

1216
        r = job_start(j);
×
1217
        if (r < 0)
×
1218
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1219
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1220

1221
        return 1;
×
1222
}
1223

1224
static int target_method_get_version(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
4✔
1225
        Target *t = ASSERT_PTR(userdata);
4✔
1226
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
4✔
1227
        sd_json_variant *version_json;
4✔
1228
        int r;
4✔
1229

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

1234
        version_json = sd_json_variant_by_key(v, "current");
4✔
1235
        if (!version_json)
4✔
1236
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'current'");
×
1237

1238
        if (sd_json_variant_is_null(version_json))
4✔
1239
                return sd_bus_reply_method_return(msg, "s", "");
×
1240

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

1244
        return sd_bus_reply_method_return(msg, "s", sd_json_variant_string(version_json));
4✔
1245
}
1246

1247
static int target_get_appstream(Target *t, char ***ret) {
×
1248
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
×
1249
        sd_json_variant *appstream_url_json;
×
1250
        int r;
×
1251

1252
        r = sysupdate_run_simple(&v, t, "--offline", "list", NULL);
×
1253
        if (r < 0)
×
1254
                return log_error_errno(r, "Failed to run 'systemd-sysupdate list': %m");
×
1255

1256
        appstream_url_json = sd_json_variant_by_key(v, "appstreamUrls");
×
1257
        if (!appstream_url_json)
×
1258
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Missing key 'appstreamUrls'");
×
1259

1260
        r = sd_json_variant_strv(appstream_url_json, ret);
×
1261
        if (r < 0)
×
1262
                return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "list", "Key 'appstreamUrls' should be strv");
×
1263

1264
        return 0;
1265
}
1266

1267
static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1268
        Target *t = ASSERT_PTR(userdata);
×
1269
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1270
        _cleanup_strv_free_ char **appstream_urls = NULL;
×
1271
        int r;
×
1272

1273
        r = target_get_appstream(t, &appstream_urls);
×
1274
        if (r < 0)
×
1275
                return r;
1276

1277
        r = sd_bus_message_new_method_return(msg, &reply);
×
1278
        if (r < 0)
×
1279
                return r;
1280

1281
        r = sd_bus_message_append_strv(reply, appstream_urls);
×
1282
        if (r < 0)
×
1283
                return r;
1284

1285
        return sd_bus_message_send(reply);
×
1286
}
1287

1288
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1289
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
×
1290
        _cleanup_strv_free_ char **features = NULL;
×
1291
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1292
        Target *t = ASSERT_PTR(userdata);
×
1293
        sd_json_variant *v;
×
1294
        uint64_t flags;
×
1295
        int r;
×
1296

1297
        assert(msg);
×
1298

1299
        r = sd_bus_message_read(msg, "t", &flags);
×
1300
        if (r < 0)
×
1301
                return r;
1302
        if (flags != 0)
×
1303
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1304

1305
        r = sysupdate_run_simple(&json, t, "features", NULL);
×
1306
        if (r < 0)
×
1307
                return r;
1308

1309
        v = sd_json_variant_by_key(json, "features");
×
1310
        if (!v)
×
1311
                return -EINVAL;
1312
        r = sd_json_variant_strv(v, &features);
×
1313
        if (r < 0)
×
1314
                return r;
1315

1316
        r = sd_bus_message_new_method_return(msg, &reply);
×
1317
        if (r < 0)
×
1318
                return r;
1319

1320
        r = sd_bus_message_append_strv(reply, features);
×
1321
        if (r < 0)
×
1322
                return r;
1323

1324
        return sd_bus_message_send(reply);
×
1325
}
1326

1327
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1328
        Target *t = ASSERT_PTR(userdata);
×
1329
        _cleanup_(job_freep) Job *j = NULL;
×
1330
        const char *feature;
×
1331
        uint64_t flags;
×
1332
        int r;
×
1333

1334
        assert(msg);
×
1335

1336
        r = sd_bus_message_read(msg, "st", &feature, &flags);
×
1337
        if (r < 0)
×
1338
                return r;
1339

1340
        if (isempty(feature))
×
1341
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified");
×
1342

1343
        if (flags != 0)
×
1344
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1345

1346
        r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
×
1347
        if (r < 0)
×
1348
                return r;
1349

1350
        j->feature = strdup(feature);
×
1351
        if (!j->feature)
×
1352
                return log_oom();
×
1353

1354
        r = job_start(j);
×
1355
        if (r < 0)
×
1356
                return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
×
1357
        TAKE_PTR(j); /* Avoid job from being killed & freed */
×
1358

1359
        return 1;
×
1360
}
1361

1362
static bool feature_name_is_valid(const char *name) {
×
1363
        if (isempty(name))
×
1364
                return false;
1365

1366
        if (!ascii_is_valid(name))
×
1367
                return false;
1368

1369
        if (!filename_is_valid(strjoina(name, ".feature.d")))
×
1370
                return false;
×
1371

1372
        return true;
1373
}
1374

1375
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1376
        _cleanup_free_ char *feature_ext = NULL;
×
1377
        Target *t = ASSERT_PTR(userdata);
×
1378
        const char *feature;
×
1379
        uint64_t flags;
×
1380
        int32_t enabled;
×
1381
        int r;
×
1382

1383
        assert(msg);
×
1384

1385
        if (t->class != TARGET_HOST)
×
1386
                return sd_bus_reply_method_errorf(msg,
×
1387
                                                  SD_BUS_ERROR_NOT_SUPPORTED,
1388
                                                  "For now, features can only be managed on the host system.");
1389

1390
        r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
×
1391
        if (r < 0)
×
1392
                return r;
1393
        if (!feature_name_is_valid(feature))
×
1394
                return sd_bus_reply_method_errorf(msg,
×
1395
                                                  SD_BUS_ERROR_INVALID_ARGS,
1396
                                                  "The specified feature is invalid");
1397
        if (flags != 0)
×
1398
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
×
1399

1400
        if (!endswith(feature, ".feature")) {
×
1401
                feature_ext = strjoin(feature, ".feature");
×
1402
                if (!feature_ext)
×
1403
                        return -ENOMEM;
1404
                feature = feature_ext;
×
1405
        }
1406

1407
        const char *details[] = {
×
1408
                "class", target_class_to_string(t->class),
×
1409
                "name", t->name,
×
1410
                "feature", feature,
1411
                "enabled", enabled >= 0 ? true_false(enabled) : "unset",
×
1412
                NULL
1413
        };
1414

1415
        r = bus_verify_polkit_async(
×
1416
                msg,
1417
                "org.freedesktop.sysupdate1.manage-features",
1418
                details,
1419
                &t->manager->polkit_registry,
×
1420
                error);
1421
        if (r < 0)
×
1422
                return r;
1423
        if (r == 0)
×
1424
                return 1; /* Will call us back */
1425

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

1430
                r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
×
1431
                if (r < 0)
×
1432
                        return r;
1433

1434
                if (unlink(path) < 0)
×
1435
                        return -errno;
×
1436
        } else { /* otherwise, create the drop-in with the right settings */
1437
                r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
×
1438
                                         "# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
1439
                                         "[Feature]\n"
1440
                                         "Enabled=%s\n",
1441
                                         yes_no(enabled));
1442
                if (r < 0)
×
1443
                        return r;
1444
        }
1445

1446
        return sd_bus_reply_method_return(msg, NULL);
×
1447
}
1448

1449
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
10✔
1450
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
10✔
1451
        _cleanup_strv_free_ char **components = NULL;
10✔
1452
        sd_json_variant *v;
10✔
1453
        bool have_default;
10✔
1454
        int r;
10✔
1455

1456
        r = sysupdate_run_simple(&json, t, "components", NULL);
10✔
1457
        if (r < 0)
10✔
1458
                return log_debug_errno(r, "Failed to run 'systemd-sysupdate components': %m");
×
1459

1460
        v = sd_json_variant_by_key(json, "default");
10✔
1461
        if (!v)
10✔
1462
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'default'");
×
1463
        have_default = sd_json_variant_boolean(v);
10✔
1464

1465
        v = sd_json_variant_by_key(json, "components");
10✔
1466
        if (!v)
10✔
1467
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Missing key 'components'");
×
1468
        r = sd_json_variant_strv(v, &components);
10✔
1469
        if (r < 0)
10✔
1470
                return log_sysupdate_bad_json_debug(SYNTHETIC_ERRNO(EPROTO), "components", "Key 'components' should be a strv");
×
1471

1472
        if (ret_components)
10✔
1473
                *ret_components = TAKE_PTR(components);
10✔
1474
        if (ret_have_default)
10✔
1475
                *ret_have_default = have_default;
10✔
1476
        return 0;
1477
}
1478

1479
static int manager_ensure_targets(Manager *m);
1480

1481
static int target_object_find(
28✔
1482
                sd_bus *bus,
1483
                const char *path,
1484
                const char *iface,
1485
                void *userdata,
1486
                void **found,
1487
                sd_bus_error *error) {
1488

1489
        Manager *m = ASSERT_PTR(userdata);
28✔
1490
        Target *t;
28✔
1491
        _cleanup_free_ char *e = NULL;
28✔
1492
        const char *p;
28✔
1493
        int r;
28✔
1494

1495
        assert(bus);
28✔
1496
        assert(path);
28✔
1497
        assert(found);
28✔
1498

1499
        p = startswith(path, "/org/freedesktop/sysupdate1/target/");
28✔
1500
        if (!p)
28✔
1501
                return 0;
1502

1503
        e = bus_label_unescape(p);
28✔
1504
        if (!e)
28✔
1505
                return -ENOMEM;
1506

1507
        r = manager_ensure_targets(m);
28✔
1508
        if (r < 0)
28✔
1509
                return r;
1510

1511
        t = hashmap_get(m->targets, e);
28✔
1512
        if (!t)
28✔
1513
                return 0;
1514

1515
        *found = t;
28✔
1516
        return 1;
28✔
1517
}
1518

1519
static char *target_bus_path(Target *t) {
10✔
1520
        _cleanup_free_ char *e = NULL;
10✔
1521

1522
        assert(t);
10✔
1523

1524
        e = bus_label_escape(t->id);
10✔
1525
        if (!e)
10✔
1526
                return NULL;
1527

1528
        return strjoin("/org/freedesktop/sysupdate1/target/", e);
10✔
1529
}
1530

1531
static int target_node_enumerator(
×
1532
                sd_bus *bus,
1533
                const char *path,
1534
                void *userdata,
1535
                char ***nodes,
1536
                sd_bus_error *error) {
1537

1538
        _cleanup_strv_free_ char **l = NULL;
×
1539
        Manager *m = ASSERT_PTR(userdata);
×
1540
        Target *t;
×
1541
        unsigned k = 0;
×
1542
        int r;
×
1543

1544
        r = manager_ensure_targets(m);
×
1545
        if (r < 0)
×
1546
                return r;
1547

1548
        l = new0(char*, hashmap_size(m->targets) + 1);
×
1549
        if (!l)
×
1550
                return -ENOMEM;
1551

1552
        HASHMAP_FOREACH(t, m->targets) {
×
1553
                l[k] = target_bus_path(t);
×
1554
                if (!l[k])
×
1555
                        return -ENOMEM;
×
1556
                k++;
×
1557
        }
1558

1559
        *nodes = TAKE_PTR(l);
×
1560
        return 1;
×
1561
}
1562

1563
static const sd_bus_vtable target_vtable[] = {
1564
        SD_BUS_VTABLE_START(0),
1565

1566
        SD_BUS_PROPERTY("Class", "s", target_property_get_class,
1567
                        offsetof(Target, class), SD_BUS_VTABLE_PROPERTY_CONST),
1568
        SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Target, name),
1569
                        SD_BUS_VTABLE_PROPERTY_CONST),
1570
        SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Target, path),
1571
                        SD_BUS_VTABLE_PROPERTY_CONST),
1572

1573
        SD_BUS_METHOD_WITH_ARGS("List",
1574
                                SD_BUS_ARGS("t", flags),
1575
                                SD_BUS_RESULT("as", versions),
1576
                                target_method_list,
1577
                                SD_BUS_VTABLE_UNPRIVILEGED),
1578

1579
        SD_BUS_METHOD_WITH_ARGS("Describe",
1580
                                SD_BUS_ARGS("s", version, "t", flags),
1581
                                SD_BUS_RESULT("s", json),
1582
                                target_method_describe,
1583
                                SD_BUS_VTABLE_UNPRIVILEGED),
1584

1585
        SD_BUS_METHOD_WITH_ARGS("CheckNew",
1586
                                SD_BUS_NO_ARGS,
1587
                                SD_BUS_RESULT("s", new_version),
1588
                                target_method_check_new,
1589
                                SD_BUS_VTABLE_UNPRIVILEGED),
1590

1591
        SD_BUS_METHOD_WITH_ARGS("Update",
1592
                                SD_BUS_ARGS("s", new_version, "t", flags),
1593
                                SD_BUS_RESULT("s", new_version, "t", job_id, "o", job_path),
1594
                                target_method_update,
1595
                                SD_BUS_VTABLE_UNPRIVILEGED),
1596

1597
        SD_BUS_METHOD_WITH_ARGS("Vacuum",
1598
                                SD_BUS_NO_ARGS,
1599
                                SD_BUS_RESULT("u", instances, "u", disabled_transfers),
1600
                                target_method_vacuum,
1601
                                SD_BUS_VTABLE_UNPRIVILEGED),
1602

1603
        SD_BUS_METHOD_WITH_ARGS("GetAppStream",
1604
                                SD_BUS_NO_ARGS,
1605
                                SD_BUS_RESULT("as", appstream),
1606
                                target_method_get_appstream,
1607
                                SD_BUS_VTABLE_UNPRIVILEGED),
1608

1609
        SD_BUS_METHOD_WITH_ARGS("GetVersion",
1610
                                SD_BUS_NO_ARGS,
1611
                                SD_BUS_RESULT("s", version),
1612
                                target_method_get_version,
1613
                                SD_BUS_VTABLE_UNPRIVILEGED),
1614

1615
        SD_BUS_METHOD_WITH_ARGS("ListFeatures",
1616
                                SD_BUS_ARGS("t", flags),
1617
                                SD_BUS_RESULT("as", features),
1618
                                target_method_list_features,
1619
                                SD_BUS_VTABLE_UNPRIVILEGED),
1620

1621
        SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
1622
                                SD_BUS_ARGS("s", feature, "t", flags),
1623
                                SD_BUS_RESULT("s", json),
1624
                                target_method_describe_feature,
1625
                                SD_BUS_VTABLE_UNPRIVILEGED),
1626

1627
        SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
1628
                                SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
1629
                                SD_BUS_NO_RESULT,
1630
                                target_method_set_feature_enabled,
1631
                                SD_BUS_VTABLE_UNPRIVILEGED),
1632

1633
        SD_BUS_VTABLE_END
1634
};
1635

1636
static const BusObjectImplementation target_object = {
1637
        "/org/freedesktop/sysupdate1/target",
1638
        "org.freedesktop.sysupdate1.Target",
1639
        .fallback_vtables = BUS_FALLBACK_VTABLES({target_vtable, target_object_find}),
1640
        .node_enumerator = target_node_enumerator,
1641
};
1642

1643
static Manager *manager_free(Manager *m) {
1✔
1644
        if (!m)
1✔
1645
                return NULL;
1646

1647
        hashmap_free(m->targets);
1✔
1648
        hashmap_free(m->jobs);
1✔
1649

1650
        m->bus = sd_bus_flush_close_unref(m->bus);
1✔
1651
        free(m->notify_socket_path);
1✔
1652
        sd_event_unref(m->event);
1✔
1653

1654
        return mfree(m);
1✔
1655
}
1656

1657
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
2✔
1658

1659
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
35✔
1660
        Manager *m = ASSERT_PTR(userdata);
35✔
1661
        int r;
35✔
1662

1663
        assert(fd >= 0);
35✔
1664

1665
        _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
×
1666
        _cleanup_free_ char *buf = NULL;
35✔
1667
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
35✔
1668
        if (r == -EAGAIN)
35✔
1669
                return 0;
1670
        if (r < 0)
35✔
1671
                return r;
1672

1673
        Job *j;
35✔
1674
        HASHMAP_FOREACH(j, m->jobs) {
55✔
1675
                PidRef child_pidref = PIDREF_NULL;
55✔
1676

1677
                r = event_source_get_child_pidref(j->child, &child_pidref);
55✔
1678
                if (r < 0)
55✔
1679
                        return log_warning_errno(r, "Failed to get child pidref: %m");
×
1680

1681
                if (pidref_equal(&sender_pidref, &child_pidref))
55✔
1682
                        break;
1683
        }
1684
        if (!j) {
35✔
1685
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1686
                return 0;
×
1687
        }
1688

1689
        char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
35✔
1690
        char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
35✔
1691
        char *errno_str = find_line_startswith(buf, "ERRNO=");
35✔
1692
        const char *ready = find_line_startswith(buf, "READY=1");
35✔
1693

1694
        if (version)
35✔
1695
                job_on_version(j, truncate_nl(version));
2✔
1696

1697
        if (progress)
35✔
1698
                job_on_progress(j, truncate_nl(progress));
7✔
1699

1700
        if (errno_str)
35✔
1701
                job_on_errno(j, truncate_nl(errno_str));
×
1702

1703
        /* Should come last, since this might actually detach the job */
1704
        if (ready)
35✔
1705
                job_on_ready(j);
2✔
1706

1707
        return 0;
1708
}
1709

1710
static int manager_new(Manager **ret) {
1✔
1711
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
1712
        int r;
1✔
1713

1714
        assert(ret);
1✔
1715

1716
        m = new(Manager, 1);
1✔
1717
        if (!m)
1✔
1718
                return -ENOMEM;
1719

1720
        *m = (Manager) {
1✔
1721
                .runtime_scope = RUNTIME_SCOPE_SYSTEM,
1722
        };
1723

1724
        r = sd_event_default(&m->event);
1✔
1725
        if (r < 0)
1✔
1726
                return r;
1727

1728
        (void) sd_event_set_watchdog(m->event, true);
1✔
1729

1730
        r = sd_event_set_signal_exit(m->event, true);
1✔
1731
        if (r < 0)
1✔
1732
                return r;
1733

1734
        r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK,
1✔
1735
                                sigrtmin18_handler, NULL);
1736
        if (r < 0)
1✔
1737
                return r;
1738

1739
        r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL);
1✔
1740
        if (r < 0)
1✔
1741
                log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m");
×
1742

1743
        r = sd_bus_default_system(&m->bus);
1✔
1744
        if (r < 0)
1✔
1745
                return r;
1746

1747
        r = notify_socket_prepare(
2✔
1748
                        m->event,
1749
                        SD_EVENT_PRIORITY_NORMAL - 1, /* Make this processed before SIGCHLD. */
1750
                        manager_on_notify,
1751
                        m,
1752
                        &m->notify_socket_path);
1✔
1753
        if (r < 0)
1✔
1754
                return r;
1755

1756
        *ret = TAKE_PTR(m);
1✔
1757
        return 0;
1✔
1758
}
1759

1760
static int manager_enumerate_image_class(Manager *m, TargetClass class) {
40✔
1761
        _cleanup_hashmap_free_ Hashmap *images = NULL;
40✔
1762
        Image *image;
40✔
1763
        int r;
40✔
1764

1765
        r = image_discover(m->runtime_scope, (ImageClass) class, NULL, &images);
40✔
1766
        if (r < 0)
40✔
1767
                return r;
1768

1769
        HASHMAP_FOREACH(image, images) {
50✔
1770
                _cleanup_(target_freep) Target *t = NULL;
10✔
1771
                bool have = false;
10✔
1772

1773
                if (image_is_host(image))
10✔
1774
                        continue; /* We already enroll the host ourselves */
10✔
1775

1776
                r = target_new(m, class, image->name, image->path, &t);
×
1777
                if (r < 0)
×
1778
                        return r;
1779
                t->image_type = image->type;
×
1780

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

1789
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
×
1790
                if (r < 0)
×
1791
                        return r;
1792
                TAKE_PTR(t);
×
1793
        }
1794

1795
        return 0;
40✔
1796
}
1797

1798
static int manager_enumerate_components(Manager *m) {
10✔
1799
        _cleanup_strv_free_ char **components = NULL;
10✔
1800
        bool have_default;
10✔
1801
        int r;
10✔
1802

1803
        r = target_list_components(NULL, &components, &have_default);
10✔
1804
        if (r < 0)
10✔
1805
                return r;
1806

1807
        if (have_default) {
10✔
1808
                _cleanup_(target_freep) Target *t = NULL;
×
1809

1810
                r = target_new(m, TARGET_HOST, "host", "sysupdate.d", &t);
10✔
1811
                if (r < 0)
10✔
1812
                        return r;
1813

1814
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
10✔
1815
                if (r < 0)
10✔
1816
                        return r;
1817
                TAKE_PTR(t);
10✔
1818
        }
1819

1820
        STRV_FOREACH(component, components) {
18✔
1821
                _cleanup_free_ char *path = NULL;
8✔
1822
                _cleanup_(target_freep) Target *t = NULL;
8✔
1823

1824
                path = strjoin("sysupdate.", *component, ".d");
8✔
1825
                if (!path)
8✔
1826
                        return -ENOMEM;
1827

1828
                r = target_new(m, TARGET_COMPONENT, *component, path, &t);
8✔
1829
                if (r < 0)
8✔
1830
                        return r;
1831

1832
                r = hashmap_ensure_put(&m->targets, &target_hash_ops, t->id, t);
8✔
1833
                if (r < 0)
8✔
1834
                        return r;
1835
                TAKE_PTR(t);
8✔
1836
        }
1837

1838
        return 0;
1839
}
1840

1841
static int manager_enumerate_targets(Manager *m) {
10✔
1842
        static const TargetClass discoverable_classes[] = {
10✔
1843
                TARGET_MACHINE,
1844
                TARGET_PORTABLE,
1845
                TARGET_SYSEXT,
1846
                TARGET_CONFEXT,
1847
        };
1848
        int r;
10✔
1849

1850
        assert(m);
10✔
1851

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

1859
        r = manager_enumerate_components(m);
10✔
1860
        if (r < 0)
10✔
1861
                log_warning_errno(r, "Failed to enumerate components, ignoring: %m");
×
1862

1863
        return 0;
10✔
1864
}
1865

1866
static int manager_ensure_targets(Manager *m) {
34✔
1867
        assert(m);
34✔
1868

1869
        if (!hashmap_isempty(m->targets))
34✔
1870
                return 0;
1871

1872
        return manager_enumerate_targets(m);
10✔
1873
}
1874

1875
static int method_list_targets(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
6✔
1876
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
6✔
1877
        Manager *m = ASSERT_PTR(userdata);
6✔
1878
        Target *t;
6✔
1879
        int r;
6✔
1880

1881
        assert(msg);
6✔
1882

1883
        r = manager_ensure_targets(m);
6✔
1884
        if (r < 0)
6✔
1885
                return r;
1886

1887
        r = sd_bus_message_new_method_return(msg, &reply);
6✔
1888
        if (r < 0)
6✔
1889
                return r;
1890

1891
        r = sd_bus_message_open_container(reply, 'a', "(sso)");
6✔
1892
        if (r < 0)
6✔
1893
                return r;
1894

1895
        HASHMAP_FOREACH(t, m->targets) {
16✔
1896
                _cleanup_free_ char *bus_path = NULL;
10✔
1897

1898
                bus_path = target_bus_path(t);
10✔
1899
                if (!bus_path)
10✔
1900
                        return -ENOMEM;
1901

1902
                r = sd_bus_message_append(reply, "(sso)",
10✔
1903
                                          target_class_to_string(t->class),
1904
                                          t->name,
10✔
1905
                                          bus_path);
1906
                if (r < 0)
10✔
1907
                        return r;
1908
        }
1909

1910
        r = sd_bus_message_close_container(reply);
6✔
1911
        if (r < 0)
6✔
1912
                return r;
1913

1914
        return sd_bus_message_send(reply);
6✔
1915
}
1916

1917
static int method_list_jobs(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
×
1918
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
×
1919
        Manager *m = ASSERT_PTR(userdata);
×
1920
        Job *j;
×
1921
        int r;
×
1922

1923
        assert(msg);
×
1924

1925
        r = sd_bus_message_new_method_return(msg, &reply);
×
1926
        if (r < 0)
×
1927
                return r;
1928

1929
        r = sd_bus_message_open_container(reply, 'a', "(tsuo)");
×
1930
        if (r < 0)
×
1931
                return r;
1932

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

1943
        r = sd_bus_message_close_container(reply);
×
1944
        if (r < 0)
×
1945
                return r;
1946

1947
        return sd_bus_message_send(reply);
×
1948
}
1949

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

1957
        assert(msg);
×
1958

1959
        r = manager_ensure_targets(m);
×
1960
        if (r < 0)
×
1961
                return r;
1962

1963
        HASHMAP_FOREACH(t, m->targets) {
×
1964
                char **target_appstream;
×
1965

1966
                r = target_get_appstream(t, &target_appstream);
×
1967
                if (r < 0)
×
1968
                        return r;
×
1969

1970
                r = strv_extend_strv_consume(&urls, target_appstream, /* filter_duplicates = */ true);
×
1971
                if (r < 0)
×
1972
                        return r;
1973
        }
1974

1975
        r = sd_bus_message_new_method_return(msg, &reply);
×
1976
        if (r < 0)
×
1977
                return r;
1978

1979
        r = sd_bus_message_append_strv(reply, urls);
×
1980
        if (r < 0)
×
1981
                return r;
1982

1983
        return sd_bus_message_send(reply);
×
1984
}
1985

1986
static const sd_bus_vtable manager_vtable[] = {
1987
        SD_BUS_VTABLE_START(0),
1988

1989
        SD_BUS_METHOD_WITH_ARGS("ListTargets",
1990
                                SD_BUS_NO_ARGS,
1991
                                SD_BUS_RESULT("a(sso)", targets),
1992
                                method_list_targets,
1993
                                SD_BUS_VTABLE_UNPRIVILEGED),
1994

1995
        SD_BUS_METHOD_WITH_ARGS("ListJobs",
1996
                                SD_BUS_NO_ARGS,
1997
                                SD_BUS_RESULT("a(tsuo)", jobs),
1998
                                method_list_jobs,
1999
                                SD_BUS_VTABLE_UNPRIVILEGED),
2000

2001
        SD_BUS_METHOD_WITH_ARGS("ListAppStream",
2002
                                SD_BUS_NO_ARGS,
2003
                                SD_BUS_RESULT("as", urls),
2004
                                method_list_appstream,
2005
                                SD_BUS_VTABLE_UNPRIVILEGED),
2006

2007
        SD_BUS_SIGNAL_WITH_ARGS("JobRemoved",
2008
                                SD_BUS_ARGS("t", id, "o", path, "i", status),
2009
                                0),
2010

2011
        SD_BUS_VTABLE_END
2012
};
2013

2014
static const BusObjectImplementation manager_object = {
2015
        "/org/freedesktop/sysupdate1",
2016
        "org.freedesktop.sysupdate1.Manager",
2017
        .vtables = BUS_VTABLES(manager_vtable),
2018
        .children = BUS_IMPLEMENTATIONS(&job_object, &target_object),
2019
};
2020

2021
static int manager_add_bus_objects(Manager *m) {
1✔
2022
        int r;
1✔
2023

2024
        assert(m);
1✔
2025

2026
        r = bus_add_implementation(m->bus, &manager_object, m);
1✔
2027
        if (r < 0)
1✔
2028
                return r;
2029

2030
        r = bus_log_control_api_register(m->bus);
1✔
2031
        if (r < 0)
1✔
2032
                return r;
2033

2034
        r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.sysupdate1", 0, NULL, NULL);
1✔
2035
        if (r < 0)
1✔
2036
                return log_error_errno(r, "Failed to request name: %m");
×
2037

2038
        r = sd_bus_attach_event(m->bus, m->event, 0);
1✔
2039
        if (r < 0)
1✔
2040
                return log_error_errno(r, "Failed to attach bus to event loop: %m");
×
2041

2042
        return 0;
2043
}
2044

2045
static bool manager_is_idle(void *userdata) {
92✔
2046
        Manager *m = ASSERT_PTR(userdata);
92✔
2047

2048
        return hashmap_isempty(m->jobs);
92✔
2049
}
2050

2051
static void manager_check_idle(Manager *m) {
20✔
2052
        assert(m);
20✔
2053

2054
        if (!hashmap_isempty(m->jobs))
20✔
2055
                return;
2056

2057
        hashmap_clear(m->targets);
10✔
2058
        log_debug("Cleared target cache");
10✔
2059
}
2060

2061
static int manager_run(Manager *m) {
1✔
2062
        assert(m);
1✔
2063

2064
        return bus_event_loop_with_idle(m->event,
1✔
2065
                                        m->bus,
2066
                                        "org.freedesktop.sysupdate1",
2067
                                        DEFAULT_EXIT_USEC,
2068
                                        manager_is_idle,
2069
                                        m);
2070
}
2071

2072
static int run(int argc, char *argv[]) {
1✔
2073
        _cleanup_(manager_freep) Manager *m = NULL;
1✔
2074
        int r;
1✔
2075

2076
        log_setup();
1✔
2077

2078
        r = service_parse_argv("systemd-sysupdated.service",
2✔
2079
                               "System update management service.",
2080
                               BUS_IMPLEMENTATIONS(&manager_object,
1✔
2081
                                                   &log_control_object),
2082
                               /* runtime_scope= */ NULL,
2083
                               argc, argv);
2084
        if (r <= 0)
1✔
2085
                return r;
2086

2087
        umask(0022);
1✔
2088

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

2092
        r = manager_new(&m);
1✔
2093
        if (r < 0)
1✔
2094
                return log_error_errno(r, "Failed to allocate manager object: %m");
×
2095

2096
        r = manager_add_bus_objects(m);
1✔
2097
        if (r < 0)
1✔
2098
                return log_error_errno(r, "Failed to add bus objects: %m");
×
2099

2100
        r = manager_run(m);
1✔
2101
        if (r < 0)
1✔
2102
                return log_error_errno(r, "Failed to run event loop: %m");
×
2103

2104
        return 0;
2105
}
2106

2107
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