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

systemd / systemd / 15720680019

17 Jun 2025 07:58PM UTC coverage: 72.087% (-0.02%) from 72.109%
15720680019

push

github

web-flow
sd-lldp: several improvements (#37845)

This makes
- sd-lldp-tx not send machine ID as chassis ID, but use application
specific machine ID,
- sd-lldp-tx emit vlan ID if it is running on a vlan interface,
- Describe() DBus method also reply LLDP configurations,
- io.systemd.Network.GetLLDPNeighbors varlink method provides vlan ID,
if received.

Closes #37613.

59 of 76 new or added lines in 3 files covered. (77.63%)

4663 existing lines in 75 files now uncovered.

300494 of 416851 relevant lines covered (72.09%)

704683.58 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

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

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

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

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

52
        Hashmap *targets;
53

54
        uint64_t last_job_id;
55
        Hashmap *jobs;
56

57
        Hashmap *polkit_registry;
58

59
        char *notify_socket_path;
60

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

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

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

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

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

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

86
typedef struct Target {
87
        Manager *manager;
88

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

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

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

109
typedef struct Job Job;
110

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

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

118
        uint64_t id;
119
        char *object_path;
120

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

126
        unsigned progress_percent;
127

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

133
        sd_json_variant *json;
134

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

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

149
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
38✔
150

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

160
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
×
161

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

230
        assert(ret);
20✔
231

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

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

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

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

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

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

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

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

261
        assert(j);
2✔
262

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

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

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

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

276
        j->complete_cb = NULL;
2✔
277

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

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

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

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

295
        j->status_errno = r;
×
296

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

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

304
        assert(j);
6✔
305
        assert(buf);
6✔
306

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

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

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

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

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

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

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

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

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

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

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

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

398
        job_free(j);
20✔
399

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

403
        return 0;
40✔
404
}
405

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

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

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

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

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

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

438
        assert(j);
20✔
439

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

560
        return 0;
561
}
562

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

566
        assert(j);
×
567

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

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

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

582
        assert(msg);
×
583

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

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

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

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

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

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

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

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

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

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

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

646
        assert(bus);
6✔
647
        assert(path);
6✔
648
        assert(ret);
6✔
649

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

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

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

662
        *ret = j;
6✔
663
        return 1;
6✔
664
}
665

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

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

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

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

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

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

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

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

703
        SD_BUS_VTABLE_END
704
};
705

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

872
        assert(json);
2✔
873

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

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

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

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

890
        return sd_bus_send(NULL, reply, NULL);
2✔
891
}
892

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

899
        assert(msg);
2✔
900

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

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

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

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

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

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

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

937
        return 1;
2✔
938
}
939

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

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

950
        assert(json);
12✔
951

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

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

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

966
        assert(msg);
12✔
967

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

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

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

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

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

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

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

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

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

1012
        return 1;
12✔
1013
}
1014

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

1022
        assert(json);
4✔
1023

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

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

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

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

1043
        assert(msg);
4✔
1044

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

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

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

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

1072
        return 1;
4✔
1073
}
1074

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

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

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

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

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

1098
        return 0;
1099
}
1100

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

1108
        assert(msg);
2✔
1109

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

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

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

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

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

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

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

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

1154
        return 1;
2✔
1155
}
1156

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

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

1166
        assert(json);
×
1167

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

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

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

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

1192
        assert(msg);
×
1193

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

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

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

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

1220
        return 1;
×
1221
}
1222

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

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

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

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

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

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

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

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

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

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

1263
        return 0;
1264
}
1265

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

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

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

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

1284
        return sd_bus_send(NULL, reply, NULL);
×
1285
}
1286

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

1296
        assert(msg);
×
1297

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

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

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

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

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

1323
        return sd_bus_send(NULL, reply, NULL);
×
1324
}
1325

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

1333
        assert(msg);
×
1334

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

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

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

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

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

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

1358
        return 1;
×
1359
}
1360

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

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

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

1371
        return true;
1372
}
1373

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

1382
        assert(msg);
×
1383

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1478
static int manager_ensure_targets(Manager *m);
1479

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

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

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

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

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

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

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

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

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

1521
        assert(t);
10✔
1522

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1632
        SD_BUS_VTABLE_END
1633
};
1634

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

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

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

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

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

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

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

1662
        assert(fd >= 0);
34✔
1663

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

1672
        Job *j;
34✔
1673
        HASHMAP_FOREACH(j, m->jobs) {
56✔
1674
                PidRef child_pidref = PIDREF_NULL;
56✔
1675

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

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

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

1693
        if (version)
34✔
1694
                job_on_version(j, truncate_nl(version));
2✔
1695

1696
        if (progress)
34✔
1697
                job_on_progress(j, truncate_nl(progress));
6✔
1698

1699
        if (errno_str)
34✔
1700
                job_on_errno(j, truncate_nl(errno_str));
×
1701

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

1706
        return 0;
1707
}
1708

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

1713
        assert(ret);
1✔
1714

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

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

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

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

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

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

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

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

1746
        r = notify_socket_prepare(
2✔
1747
                        m->event,
1748
                        SD_EVENT_PRIORITY_NORMAL,
1749
                        manager_on_notify,
1750
                        m,
1751
                        &m->notify_socket_path);
1✔
1752
        if (r < 0)
1✔
1753
                return r;
1754

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

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

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

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

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

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

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

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

1794
        return 0;
40✔
1795
}
1796

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

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

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

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

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

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

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

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

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

1837
        return 0;
1838
}
1839

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

1849
        assert(m);
10✔
1850

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

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

1862
        return 0;
10✔
1863
}
1864

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

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

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

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

1880
        assert(msg);
6✔
1881

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

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

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

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

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

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

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

1913
        return sd_bus_send(NULL, reply, NULL);
6✔
1914
}
1915

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

UNCOV
1922
        assert(msg);
×
1923

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

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

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

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

UNCOV
1946
        return sd_bus_send(NULL, reply, NULL);
×
1947
}
1948

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

UNCOV
1956
        assert(msg);
×
1957

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

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

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

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

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

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

UNCOV
1982
        return sd_bus_send(NULL, reply, NULL);
×
1983
}
1984

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

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

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

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

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

2010
        SD_BUS_VTABLE_END
2011
};
2012

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

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

2023
        assert(m);
1✔
2024

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

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

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

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

2041
        return 0;
2042
}
2043

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

2047
        return hashmap_isempty(m->jobs);
91✔
2048
}
2049

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

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

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

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

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

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

2075
        log_setup();
1✔
2076

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

2085
        umask(0022);
1✔
2086

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

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

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

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

2102
        return 0;
2103
}
2104

2105
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