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

systemd / systemd / 13710725352

06 Mar 2025 10:36PM UTC coverage: 71.765% (-0.02%) from 71.788%
13710725352

push

github

web-flow
hostnamectl: show image info in hostnamectl (#36638)

On image-based systems these properties are quite fundamental, hence
show them in the hostnamed output.

2 of 9 new or added lines in 1 file covered. (22.22%)

5360 existing lines in 93 files now uncovered.

294785 of 410763 relevant lines covered (71.77%)

717261.41 hits per line

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

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

3
#include "sd-id128.h"
4

5
#include "alloc-util.h"
6
#include "blockdev-util.h"
7
#include "build-path.h"
8
#include "chase.h"
9
#include "conf-parser.h"
10
#include "dirent-util.h"
11
#include "event-util.h"
12
#include "fd-util.h"
13
#include "glyph-util.h"
14
#include "gpt.h"
15
#include "hexdecoct.h"
16
#include "install-file.h"
17
#include "mkdir.h"
18
#include "notify-recv.h"
19
#include "parse-helpers.h"
20
#include "parse-util.h"
21
#include "percent-util.h"
22
#include "process-util.h"
23
#include "random-util.h"
24
#include "rm-rf.h"
25
#include "signal-util.h"
26
#include "socket-util.h"
27
#include "specifier.h"
28
#include "stat-util.h"
29
#include "stdio-util.h"
30
#include "strv.h"
31
#include "sync-util.h"
32
#include "sysupdate.h"
33
#include "sysupdate-feature.h"
34
#include "sysupdate-pattern.h"
35
#include "sysupdate-resource.h"
36
#include "sysupdate-transfer.h"
37
#include "tmpfile-util.h"
38
#include "web-util.h"
39

40
/* Default value for InstancesMax= for fs object targets */
41
#define DEFAULT_FILE_INSTANCES_MAX 3
42

43
Transfer* transfer_free(Transfer *t) {
556✔
44
        if (!t)
556✔
45
                return NULL;
46

47
        t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
556✔
48

49
        free(t->id);
556✔
50

51
        free(t->min_version);
556✔
52
        strv_free(t->protected_versions);
556✔
53
        free(t->current_symlink);
556✔
54
        free(t->final_path);
556✔
55

56
        strv_free(t->features);
556✔
57
        strv_free(t->requisite_features);
556✔
58

59
        strv_free(t->changelog);
556✔
60
        strv_free(t->appstream);
556✔
61

62
        partition_info_destroy(&t->partition_info);
556✔
63

64
        resource_destroy(&t->source);
556✔
65
        resource_destroy(&t->target);
556✔
66

67
        return mfree(t);
556✔
68
}
69

70
Transfer* transfer_new(Context *ctx) {
556✔
71
        Transfer *t;
556✔
72

73
        t = new(Transfer, 1);
556✔
74
        if (!t)
556✔
75
                return NULL;
76

77
        *t = (Transfer) {
556✔
78
                .source.type = _RESOURCE_TYPE_INVALID,
79
                .target.type = _RESOURCE_TYPE_INVALID,
80
                .remove_temporary = true,
81
                .mode = MODE_INVALID,
82
                .tries_left = UINT64_MAX,
83
                .tries_done = UINT64_MAX,
84
                .verify = true,
85

86
                /* the three flags, as configured by the user */
87
                .no_auto = -1,
88
                .read_only = -1,
89
                .growfs = -1,
90

91
                /* the read only flag, as ultimately determined */
92
                .install_read_only = -1,
93

94
                .partition_info = PARTITION_INFO_NULL,
95

96
                .context = ctx,
97
        };
98

99
        return t;
556✔
100
}
101

102
static int config_parse_protect_version(
×
103
                const char *unit,
104
                const char *filename,
105
                unsigned line,
106
                const char *section,
107
                unsigned section_line,
108
                const char *lvalue,
109
                int ltype,
110
                const char *rvalue,
111
                void *data,
112
                void *userdata) {
113

114
        _cleanup_free_ char *resolved = NULL;
×
115
        char ***protected_versions = ASSERT_PTR(data);
×
116
        int r;
×
117

118
        assert(rvalue);
×
119

120
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
121
        if (r < 0) {
×
122
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
123
                           "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
124
                return 0;
×
125
        }
126

127
        if (!version_is_valid(resolved))  {
×
128
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
129
                           "ProtectVersion= string is not valid, ignoring: %s", resolved);
130
                return 0;
×
131
        }
132

133
        r = strv_extend(protected_versions, resolved);
×
134
        if (r < 0)
×
135
                return log_oom();
×
136

137
        return 0;
138
}
139

140
static int config_parse_min_version(
×
141
                const char *unit,
142
                const char *filename,
143
                unsigned line,
144
                const char *section,
145
                unsigned section_line,
146
                const char *lvalue,
147
                int ltype,
148
                const char *rvalue,
149
                void *data,
150
                void *userdata) {
151

152
        _cleanup_free_ char *resolved = NULL;
×
153
        char **version = ASSERT_PTR(data);
×
154
        int r;
×
155

156
        assert(rvalue);
×
157

158
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
159
        if (r < 0) {
×
160
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
161
                           "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
162
                return 0;
×
163
        }
164

165
        if (!version_is_valid(rvalue)) {
×
166
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
167
                           "MinVersion= string is not valid, ignoring: %s", resolved);
168
                return 0;
×
169
        }
170

171
        return free_and_replace(*version, resolved);
×
172
}
173

174
static int config_parse_url_specifiers(
×
175
                const char *unit,
176
                const char *filename,
177
                unsigned line,
178
                const char *section,
179
                unsigned section_line,
180
                const char *lvalue,
181
                int ltype,
182
                const char *rvalue,
183
                void *data,
184
                void *userdata) {
185
        char ***s = ASSERT_PTR(data);
×
186
        _cleanup_free_ char *resolved = NULL;
×
187
        int r;
×
188

189
        assert(rvalue);
×
190

191
        if (isempty(rvalue)) {
×
192
                *s = strv_free(*s);
×
193
                return 0;
×
194
        }
195

196
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
197
        if (r < 0) {
×
198
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
199
                           "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
200
                return 0;
×
201
        }
202

203
        if (!http_url_is_valid(resolved)) {
×
204
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
205
                           "%s= URL is not valid, ignoring: %s", lvalue, rvalue);
206
                return 0;
×
207
        }
208

209
        r = strv_push(s, TAKE_PTR(resolved));
×
210
        if (r < 0)
×
211
                return log_oom();
×
212

213
        return 0;
214
}
215

216
static int config_parse_current_symlink(
92✔
217
                const char *unit,
218
                const char *filename,
219
                unsigned line,
220
                const char *section,
221
                unsigned section_line,
222
                const char *lvalue,
223
                int ltype,
224
                const char *rvalue,
225
                void *data,
226
                void *userdata) {
227

228
        _cleanup_free_ char *resolved = NULL;
92✔
229
        char **current_symlink = ASSERT_PTR(data);
92✔
230
        int r;
92✔
231

232
        assert(rvalue);
92✔
233

234
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
92✔
235
        if (r < 0) {
92✔
236
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
237
                           "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
238
                return 0;
×
239
        }
240

241
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
92✔
242
        if (r < 0)
92✔
243
                return 0;
244

245
        return free_and_replace(*current_symlink, resolved);
92✔
246
}
247

248
static int config_parse_instances_max(
368✔
249
                const char *unit,
250
                const char *filename,
251
                unsigned line,
252
                const char *section,
253
                unsigned section_line,
254
                const char *lvalue,
255
                int ltype,
256
                const char *rvalue,
257
                void *data,
258
                void *userdata) {
259

260
        uint64_t *instances_max = data, i;
368✔
261
        int r;
368✔
262

263
        assert(rvalue);
368✔
264
        assert(data);
368✔
265

266
        if (isempty(rvalue)) {
368✔
267
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
268
                return 0;
×
269
        }
270

271
        r = safe_atou64(rvalue, &i);
368✔
272
        if (r < 0) {
368✔
273
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
274
                           "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
275
                return 0;
×
276
        }
277

278
        if (i < 2) {
368✔
279
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
280
                           "InstancesMax= value must be at least 2, bumping: %s", rvalue);
281
                *instances_max = 2;
×
282
        } else
283
                *instances_max = i;
368✔
284

285
        return 0;
286
}
287

288
static int config_parse_resource_pattern(
1,112✔
289
                const char *unit,
290
                const char *filename,
291
                unsigned line,
292
                const char *section,
293
                unsigned section_line,
294
                const char *lvalue,
295
                int ltype,
296
                const char *rvalue,
297
                void *data,
298
                void *userdata) {
299

300
        char ***patterns = ASSERT_PTR(data);
1,112✔
301
        int r;
1,112✔
302

303
        assert(rvalue);
1,112✔
304

305
        if (isempty(rvalue)) {
1,112✔
306
                *patterns = strv_free(*patterns);
×
307
                return 0;
×
308
        }
309

310
        for (;;) {
3,704✔
311
                _cleanup_free_ char *word = NULL, *resolved = NULL;
1,296✔
312

313
                r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
2,408✔
314
                if (r < 0) {
2,408✔
315
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
316
                                   "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
317
                        return 0;
×
318
                }
319
                if (r == 0)
2,408✔
320
                        break;
321

322
                r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
1,296✔
323
                if (r < 0) {
1,296✔
324
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
325
                                   "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
326
                        return 0;
×
327
                }
328

329
                if (!pattern_valid(resolved))
1,296✔
330
                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
331
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
332

333
                r = strv_consume(patterns, TAKE_PTR(resolved));
1,296✔
334
                if (r < 0)
1,296✔
335
                        return log_oom();
×
336
        }
337

338
        strv_uniq(*patterns);
1,112✔
339
        return 0;
1,112✔
340
}
341

342
static int config_parse_resource_path(
1,112✔
343
                const char *unit,
344
                const char *filename,
345
                unsigned line,
346
                const char *section,
347
                unsigned section_line,
348
                const char *lvalue,
349
                int ltype,
350
                const char *rvalue,
351
                void *data,
352
                void *userdata) {
353
        _cleanup_free_ char *resolved = NULL;
1,112✔
354
        Resource *rr = ASSERT_PTR(data);
1,112✔
355
        int r;
1,112✔
356

357
        assert(rvalue);
1,112✔
358

359
        if (streq(rvalue, "auto")) {
1,112✔
360
                rr->path_auto = true;
×
361
                rr->path = mfree(rr->path);
×
362
                return 0;
×
363
        }
364

365
        r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved);
1,112✔
366
        if (r < 0) {
1,112✔
367
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
368
                           "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
369
                return 0;
×
370
        }
371

372
        /* Note that we don't validate the path as being absolute or normalized. We'll do that in
373
         * transfer_read_definition() as we might not know yet whether Path refers to a URL or a file system
374
         * path. */
375

376
        rr->path_auto = false;
1,112✔
377
        return free_and_replace(rr->path, resolved);
1,112✔
378
}
379

380
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
1,112✔
381

382
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
552✔
383
                                             PATH_RELATIVE_TO_ROOT);
384

385
static int config_parse_resource_ptype(
188✔
386
                const char *unit,
387
                const char *filename,
388
                unsigned line,
389
                const char *section,
390
                unsigned section_line,
391
                const char *lvalue,
392
                int ltype,
393
                const char *rvalue,
394
                void *data,
395
                void *userdata) {
396

397
        Resource *rr = ASSERT_PTR(data);
188✔
398
        int r;
188✔
399

400
        assert(rvalue);
188✔
401

402
        r = gpt_partition_type_from_string(rvalue, &rr->partition_type);
188✔
403
        if (r < 0) {
188✔
404
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
405
                           "Failed parse partition type, ignoring: %s", rvalue);
406
                return 0;
×
407
        }
408

409
        rr->partition_type_set = true;
188✔
410
        return 0;
188✔
411
}
412

413
static int config_parse_partition_uuid(
×
414
                const char *unit,
415
                const char *filename,
416
                unsigned line,
417
                const char *section,
418
                unsigned section_line,
419
                const char *lvalue,
420
                int ltype,
421
                const char *rvalue,
422
                void *data,
423
                void *userdata) {
424

425
        Transfer *t = ASSERT_PTR(data);
×
426
        int r;
×
427

428
        assert(rvalue);
×
429

430
        r = sd_id128_from_string(rvalue, &t->partition_uuid);
×
431
        if (r < 0) {
×
432
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
433
                           "Failed parse partition UUID, ignoring: %s", rvalue);
434
                return 0;
×
435
        }
436

437
        t->partition_uuid_set = true;
×
438
        return 0;
×
439
}
440

441
static int config_parse_partition_flags(
×
442
                const char *unit,
443
                const char *filename,
444
                unsigned line,
445
                const char *section,
446
                unsigned section_line,
447
                const char *lvalue,
448
                int ltype,
449
                const char *rvalue,
450
                void *data,
451
                void *userdata) {
452

453
        Transfer *t = ASSERT_PTR(data);
×
454
        int r;
×
455

456
        assert(rvalue);
×
457

458
        r = safe_atou64(rvalue, &t->partition_flags);
×
459
        if (r < 0) {
×
460
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
461
                           "Failed parse partition flags, ignoring: %s", rvalue);
462
                return 0;
×
463
        }
464

465
        t->partition_flags_set = true;
×
466
        return 0;
×
467
}
468

469
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
556✔
470
        assert(t);
556✔
471

472
        /* Requisite feature disabled -> transfer disabled */
473
        STRV_FOREACH(id, t->requisite_features) {
556✔
474
                Feature *f = hashmap_get(known_features, *id);
×
475
                if (!f || !f->enabled) /* missing features are implicitly disabled */
×
476
                        return false;
477
        }
478

479
        /* No features defined -> transfer implicitly enabled */
480
        if (strv_isempty(t->features))
556✔
481
                return true;
482

483
        /* At least one feature enabled -> transfer enabled */
484
        STRV_FOREACH(id, t->features) {
174✔
485
                Feature *f = hashmap_get(known_features, *id);
92✔
486
                if (f && f->enabled)
92✔
487
                        return true;
488
        }
489

490
        /* All listed features disabled -> transfer disabled */
491
        return false;
492
}
493

494
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
556✔
495
        assert(t);
556✔
496

497
        ConfigTableItem table[] = {
556✔
498
                { "Transfer",    "MinVersion",              config_parse_min_version,          0, &t->min_version             },
556✔
499
                { "Transfer",    "ProtectVersion",          config_parse_protect_version,      0, &t->protected_versions      },
556✔
500
                { "Transfer",    "Verify",                  config_parse_bool,                 0, &t->verify                  },
556✔
501
                { "Transfer",    "ChangeLog",               config_parse_url_specifiers,       0, &t->changelog               },
556✔
502
                { "Transfer",    "AppStream",               config_parse_url_specifiers,       0, &t->appstream               },
556✔
503
                { "Transfer",    "Features",                config_parse_strv,                 0, &t->features                },
556✔
504
                { "Transfer",    "RequisiteFeatures",       config_parse_strv,                 0, &t->requisite_features      },
556✔
505
                { "Source",      "Type",                    config_parse_resource_type,        0, &t->source.type             },
556✔
506
                { "Source",      "Path",                    config_parse_resource_path,        0, &t->source                  },
556✔
507
                { "Source",      "PathRelativeTo",          config_parse_resource_path_relto,  0, &t->source.path_relative_to },
556✔
508
                { "Source",      "MatchPattern",            config_parse_resource_pattern,     0, &t->source.patterns         },
556✔
509
                { "Target",      "Type",                    config_parse_resource_type,        0, &t->target.type             },
556✔
510
                { "Target",      "Path",                    config_parse_resource_path,        0, &t->target                  },
556✔
511
                { "Target",      "PathRelativeTo",          config_parse_resource_path_relto,  0, &t->target.path_relative_to },
556✔
512
                { "Target",      "MatchPattern",            config_parse_resource_pattern,     0, &t->target.patterns         },
556✔
513
                { "Target",      "MatchPartitionType",      config_parse_resource_ptype,       0, &t->target                  },
514
                { "Target",      "PartitionUUID",           config_parse_partition_uuid,       0, t                           },
515
                { "Target",      "PartitionFlags",          config_parse_partition_flags,      0, t                           },
516
                { "Target",      "PartitionNoAuto",         config_parse_tristate,             0, &t->no_auto                 },
556✔
517
                { "Target",      "PartitionGrowFileSystem", config_parse_tristate,             0, &t->growfs                  },
556✔
518
                { "Target",      "ReadOnly",                config_parse_tristate,             0, &t->read_only               },
556✔
519
                { "Target",      "Mode",                    config_parse_mode,                 0, &t->mode                    },
556✔
520
                { "Target",      "TriesLeft",               config_parse_uint64,               0, &t->tries_left              },
556✔
521
                { "Target",      "TriesDone",               config_parse_uint64,               0, &t->tries_done              },
556✔
522
                { "Target",      "InstancesMax",            config_parse_instances_max,        0, &t->instances_max           },
556✔
523
                { "Target",      "RemoveTemporary",         config_parse_bool,                 0, &t->remove_temporary        },
556✔
524
                { "Target",      "CurrentSymlink",          config_parse_current_symlink,      0, &t->current_symlink         },
556✔
525
                {}
526
        };
527

528
        _cleanup_free_ char *filename = NULL;
556✔
529
        char *e;
556✔
530
        int r;
556✔
531

532
        assert(path);
556✔
533
        assert(dirs);
556✔
534

535
        r = path_extract_filename(path, &filename);
556✔
536
        if (r < 0)
556✔
537
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
538

539
        r = config_parse_many(
1,668✔
540
                        STRV_MAKE_CONST(path),
556✔
541
                        dirs,
542
                        strjoina(filename, ".d"),
2,780✔
543
                        arg_root,
544
                        "Transfer\0"
545
                        "Source\0"
546
                        "Target\0",
547
                        config_item_table_lookup, table,
548
                        CONFIG_PARSE_WARN,
549
                        /* userdata= */ NULL,
550
                        /* stats_by_path= */ NULL,
551
                        /* drop_in_files= */ NULL);
552
        if (r < 0)
556✔
553
                return r;
554

555
        e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
556✔
556
        *e = 0; /* Remove the file extension */
556✔
557
        t->id = TAKE_PTR(filename);
556✔
558

559
        t->enabled = transfer_decide_if_enabled(t, known_features);
556✔
560

561
        if (!RESOURCE_IS_SOURCE(t->source.type))
556✔
562
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
563
                                  "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
564

565
        if (t->target.type < 0) {
556✔
566
                switch (t->source.type) {
×
567

568
                case RESOURCE_URL_FILE:
×
569
                case RESOURCE_REGULAR_FILE:
570
                        t->target.type =
×
571
                                t->target.path && path_startswith(t->target.path, "/dev/") ?
×
572
                                RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
×
573
                        break;
×
574

575
                case RESOURCE_URL_TAR:
×
576
                case RESOURCE_TAR:
577
                case RESOURCE_DIRECTORY:
578
                        t->target.type = RESOURCE_DIRECTORY;
×
579
                        break;
×
580

581
                case RESOURCE_SUBVOLUME:
×
582
                        t->target.type = RESOURCE_SUBVOLUME;
×
583
                        break;
×
584

585
                default:
×
586
                        assert_not_reached();
×
587
                }
588
        }
589

590
        if (!RESOURCE_IS_TARGET(t->target.type))
556✔
591
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
592
                                  "Target Type= must be one of partition, regular-file, directory, subvolume.");
593

594
        if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
556✔
595
             !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
464✔
596
            (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
556✔
597
             !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
92✔
598
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
599
                                  "Target type '%s' is incompatible with source type '%s', refusing.",
600
                                  resource_type_to_string(t->target.type), resource_type_to_string(t->source.type));
601

602
        if (!t->source.path && !t->source.path_auto)
556✔
603
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
604
                                  "Source specification lacks Path=.");
605

606
        if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !arg_transfer_source)
556✔
607
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
608
                                  "PathRelativeTo=explicit requires --transfer-source= to be specified.");
609

610
        if (t->target.path_relative_to == PATH_RELATIVE_TO_EXPLICIT)
556✔
611
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
612
                                  "PathRelativeTo=explicit can only be used in source specifications.");
613

614
        if (t->source.path) {
556✔
615
                if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
556✔
616
                        if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
532✔
617
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
618
                                                  "Source path is not a normalized, absolute path: %s", t->source.path);
619

620
                /* We unofficially support file:// in addition to http:// and https:// for url
621
                 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
622
                 * server, and CURL abstracts this away from us thankfully. */
623
                if (RESOURCE_IS_URL(t->source.type))
556✔
624
                        if (!http_url_is_valid(t->source.path) && !file_url_is_valid(t->source.path))
24✔
625
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
626
                                                  "Source path is not a valid HTTP or HTTPS URL: %s", t->source.path);
627
        }
628

629
        if (strv_isempty(t->source.patterns))
556✔
630
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
631
                                  "Source specification lacks MatchPattern=.");
632

633
        if (!t->target.path && !t->target.path_auto)
556✔
634
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
635
                                  "Target specification lacks Path= field.");
636

637
        if (t->target.path &&
556✔
638
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
556✔
639
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
640
                                  "Target path is not a normalized, absolute path: %s", t->target.path);
641

642
        if (strv_isempty(t->target.patterns)) {
556✔
643
                log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification.");
×
644
                strv_free(t->target.patterns);
×
645
                t->target.patterns = strv_copy(t->source.patterns);
×
646
                if (!t->target.patterns)
×
647
                        return log_oom();
×
648
        }
649

650
        if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
556✔
651
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
652
                                  "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
653

654
        /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
655
        if (t->instances_max == 0)
556✔
656
                t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
188✔
657

658
        return 0;
659
}
660

661
int transfer_resolve_paths(
556✔
662
                Transfer *t,
663
                const char *root,
664
                const char *node) {
665

666
        int r;
556✔
667

668
        /* If Path=auto is used in [Source] or [Target] sections, let's automatically detect the path of the
669
         * block device to use. Moreover, if this path points to a directory but we need a block device,
670
         * automatically determine the backing block device, so that users can reference block devices by
671
         * mount point. */
672

673
        assert(t);
556✔
674

675
        r = resource_resolve_path(&t->source, root, arg_transfer_source, node);
556✔
676
        if (r < 0)
556✔
677
                return r;
678

679
        r = resource_resolve_path(&t->target, root, /*relative_to_directory=*/ NULL, node);
556✔
680
        if (r < 0)
556✔
681
                return r;
×
682

683
        return 0;
684
}
685

686
static void transfer_remove_temporary(Transfer *t) {
102✔
687
        _cleanup_closedir_ DIR *d = NULL;
102✔
688
        int r;
102✔
689

690
        assert(t);
102✔
691

692
        if (!t->remove_temporary)
102✔
693
                return;
694

695
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
102✔
696
                return;
697

698
        /* Removes all temporary files/dirs from previous runs in the target directory, i.e. all those starting with '.#' */
699

700
        d = opendir(t->target.path);
70✔
701
        if (!d) {
70✔
702
                if (errno == ENOENT)
×
703
                        return;
704

705
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
×
706
                return;
×
707
        }
708

709
        for (;;) {
266✔
710
                struct dirent *de;
266✔
711

712
                errno = 0;
266✔
713
                de = readdir_no_dot(d);
532✔
714
                if (!de) {
266✔
715
                        if (errno != 0)
70✔
716
                                log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
×
717
                        break;
70✔
718
                }
719

720
                if (!startswith(de->d_name, ".#"))
196✔
721
                        continue;
196✔
722

723
                r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
×
724
                if (r == -ENOENT)
×
725
                        continue;
×
726
                if (r < 0) {
×
727
                        log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
×
728
                        continue;
×
729
                }
730

731
                log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
266✔
732
        }
733
}
734

735
int transfer_vacuum(
102✔
736
                Transfer *t,
737
                uint64_t space,
738
                const char *extra_protected_version) {
739

740
        uint64_t instances_max, limit;
102✔
741
        int r, count = 0;
102✔
742

743
        assert(t);
102✔
744

745
        transfer_remove_temporary(t);
102✔
746

747
        /* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
748

749
        instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
102✔
750
        assert(instances_max >= 1);
102✔
751
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
102✔
752
                limit = UINT64_MAX;
753
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
70✔
754
                limit = 0;
755
        else if (space > instances_max)
52✔
756
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
757
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
758
        else if (space == instances_max)
52✔
759
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
760
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
761
        else
762
                limit = instances_max - space;
52✔
763

764
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
102✔
765
                uint64_t rm, remain;
32✔
766

767
                /* If we are looking at a partition table, we also have to take into account how many
768
                 * partition slots of the right type are available */
769

770
                if (t->target.n_empty + t->target.n_instances < 2)
32✔
771
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
772
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), refusing.",
773
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
774
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
775
                if (space > t->target.n_empty + t->target.n_instances)
32✔
776
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
777
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s) for operation.",
778
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
779
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
780
                if (space == t->target.n_empty + t->target.n_instances)
32✔
781
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
782
                                               "Asked to empty all partition table slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), can't allow that. One instance must always remain.",
783
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
784
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
785

786
                rm = LESS_BY(space, t->target.n_empty);
32✔
787
                remain = LESS_BY(t->target.n_instances, rm);
32✔
788
                limit = MIN(limit, remain);
32✔
789
        }
790

791
        while (t->target.n_instances > limit) {
152✔
792
                Instance *oldest;
50✔
793
                size_t p = t->target.n_instances - 1;
50✔
794

795
                for (;;) {
50✔
796
                        oldest = t->target.instances[p];
50✔
797
                        assert(oldest);
50✔
798

799
                        /* If this is listed among the protected versions, then let's not remove it */
800
                        if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
50✔
801
                            (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
48✔
802
                                break;
803

804
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
805
                        if (p == 0) {
×
806
                                oldest = NULL;
807
                                break;
808
                        }
809

810
                        p--;
×
811
                }
812

813
                if (!oldest) /* Nothing more to remove */
50✔
814
                        break;
815

816
                assert(oldest->resource);
50✔
817

818
                log_info("%s Removing %s '%s' (%s).",
100✔
819
                         special_glyph(SPECIAL_GLYPH_RECYCLING),
820
                         space == UINT64_MAX ? "disabled" : "old",
821
                         oldest->path,
822
                         resource_type_to_string(oldest->resource->type));
823

824
                switch (t->target.type) {
50✔
825

826
                case RESOURCE_REGULAR_FILE:
30✔
827
                case RESOURCE_DIRECTORY:
828
                case RESOURCE_SUBVOLUME:
829
                        r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
30✔
830
                        if (r < 0 && r != -ENOENT)
30✔
831
                                return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
×
832

833
                        (void) rmdir_parents(oldest->path, t->target.path);
30✔
834

835
                        break;
30✔
836

837
                case RESOURCE_PARTITION: {
20✔
838
                        PartitionInfo pinfo = oldest->partition_info;
20✔
839

840
                        /* label "_empty" means "no contents" for our purposes */
841
                        pinfo.label = (char*) "_empty";
20✔
842

843
                        r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
20✔
844
                        if (r < 0)
20✔
845
                                return r;
×
846

847
                        t->target.n_empty++;
20✔
848
                        break;
20✔
849
                }
850

851
                default:
×
852
                        assert_not_reached();
×
853
                }
854

855
                instance_free(oldest);
50✔
856
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
50✔
857
                t->target.n_instances--;
50✔
858

859
                count++;
50✔
860
        }
861

862
        return count;
863
}
864

865
static void compile_pattern_fields(
74✔
866
                const Transfer *t,
867
                const Instance *i,
868
                InstanceMetadata *ret) {
869

870
        assert(t);
74✔
871
        assert(i);
74✔
872
        assert(ret);
74✔
873

874
        *ret = (InstanceMetadata) {
148✔
875
                .version = i->metadata.version,
74✔
876

877
                /* We generally prefer explicitly configured values for the transfer over those automatically
878
                 * derived from the source instance. Also, if the source is a tar archive, then let's not
879
                 * patch mtime/mode and use the one embedded in the tar file */
880
                .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
74✔
881
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
74✔
882
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
74✔
883
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
74✔
884
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
74✔
885
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
74✔
886
                .size = i->metadata.size,
74✔
887
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
74✔
888
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
60✔
889
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
74✔
890
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
60✔
891
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
74✔
892
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
74✔
893
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
74✔
894
                .sha256sum_set = i->metadata.sha256sum_set,
74✔
895
        };
896

897
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
74✔
898
}
74✔
899

900
typedef struct CalloutContext {
901
        const Transfer *transfer;
902
        const Instance *instance;
903
        TransferProgress callback;
904
        PidRef pid;
905
        const char *name;
906
        int helper_errno;
907
        void* userdata;
908
} CalloutContext;
909

910
static CalloutContext *callout_context_free(CalloutContext *ctx) {
74✔
911
        if (!ctx)
74✔
912
                return NULL;
913

914
        /* We don't own any data but need to clean up the job pid */
915
        pidref_done(&ctx->pid);
74✔
916

917
        return mfree(ctx);
74✔
918
}
919

920
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
222✔
921

922
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
74✔
923
                               const char *name, void* userdata, CalloutContext **ret) {
924
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
74✔
925

926
        assert(t);
74✔
927
        assert(i);
74✔
928
        assert(cb);
74✔
929

930
        ctx = new(CalloutContext, 1);
74✔
931
        if (!ctx)
74✔
932
                return -ENOMEM;
933

934
        *ctx = (CalloutContext) {
74✔
935
                .transfer = t,
936
                .instance = i,
937
                .callback = cb,
938
                .pid = PIDREF_NULL,
939
                .name = name,
940
                .userdata = userdata,
941
        };
942

943
        *ret = TAKE_PTR(ctx);
74✔
944
        return 0;
74✔
945
}
946

947
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
74✔
948
        _cleanup_(callout_context_freep) CalloutContext *ctx = ASSERT_PTR(userdata);
148✔
949
        int r;
74✔
950

951
        assert(s);
74✔
952
        assert(si);
74✔
953
        assert(ctx);
74✔
954

955
        pidref_done(&ctx->pid);
74✔
956

957
        if (si->si_code == CLD_EXITED) {
74✔
958
                if (si->si_status == EXIT_SUCCESS) {
74✔
959
                        r = 0;
74✔
960
                        log_debug("%s succeeded.", ctx->name);
74✔
UNCOV
961
                } else if (ctx->helper_errno != 0) {
×
962
                        r = -ctx->helper_errno;
×
963
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
×
964
                } else {
UNCOV
965
                        r = -EPROTO;
×
966
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
967
                }
968
        } else {
UNCOV
969
                r = -EPROTO;
×
970
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
971
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
972
                else
UNCOV
973
                        log_error("%s failed due to unknown reason.", ctx->name);
×
974
        }
975

976
        return sd_event_exit(sd_event_source_get_event(s), r);
148✔
977
}
978

979
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
149✔
980
        CalloutContext *ctx = ASSERT_PTR(userdata);
149✔
981
        int r;
149✔
982

983
        assert(fd >= 0);
149✔
984

985
        _cleanup_free_ char *buf = NULL;
149✔
UNCOV
986
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
×
987
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
149✔
988
        if (r == -EAGAIN)
149✔
989
                return 0;
990
        if (r < 0)
149✔
991
                return r;
992

993
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
149✔
UNCOV
994
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
995
                return 0;
×
996
        }
997

998
        char *errno_str = find_line_startswith(buf, "ERRNO=");
149✔
999
        if (errno_str) {
149✔
UNCOV
1000
                truncate_nl(errno_str);
×
1001
                r = parse_errno(errno_str);
×
1002
                if (r < 0)
×
1003
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1004
                else {
UNCOV
1005
                        ctx->helper_errno = r;
×
1006
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
×
1007
                }
1008
        }
1009

1010
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
149✔
1011
        if (progress_str) {
149✔
1012
                truncate_nl(progress_str);
75✔
1013

1014
                int progress = parse_percent(progress_str);
75✔
1015
                if (progress < 0)
75✔
1016
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
149✔
1017
                else {
1018
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
75✔
1019
                        if (r < 0)
75✔
UNCOV
1020
                                return r;
×
1021
                }
1022
        }
1023

1024
        return 0;
1025
}
1026

1027
static int run_callout(
74✔
1028
                const char *name,
1029
                char *cmdline[],
1030
                const Transfer *transfer,
1031
                const Instance *instance,
1032
                TransferProgress callback,
1033
                void *userdata) {
1034
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
74✔
1035
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL, *notify_source = NULL;
148✔
1036
        _cleanup_close_ int fd = -EBADF;
74✔
1037
        _cleanup_free_ char *bind_name = NULL;
74✔
1038
        union sockaddr_union bsa;
74✔
1039
        int r;
74✔
1040

1041
        assert(name);
74✔
1042
        assert(cmdline);
74✔
1043
        assert(cmdline[0]);
74✔
1044

1045
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
74✔
1046

1047
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
74✔
1048
        if (r < 0)
74✔
UNCOV
1049
                return log_oom();
×
1050

1051
        r = sd_event_new(&event);
74✔
1052
        if (r < 0)
74✔
UNCOV
1053
                return log_error_errno(r, "Failed to create event: %m");
×
1054

1055
        /* Kill the helper & return an error if we get interrupted by a signal */
1056
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
74✔
1057
        if (r < 0)
74✔
UNCOV
1058
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1059
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
74✔
1060
        if (r < 0)
74✔
UNCOV
1061
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1062

1063
        fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
74✔
1064
        if (fd < 0)
74✔
UNCOV
1065
                return log_error_errno(errno, "Failed to create UNIX socket for notification: %m");
×
1066

1067
        if (asprintf(&bind_name, "@%" PRIx64 "/sysupdate/" PID_FMT "/notify", random_u64(), getpid_cached()) < 0)
148✔
UNCOV
1068
                return log_oom();
×
1069

1070
        r = sockaddr_un_set_path(&bsa.un, bind_name);
74✔
1071
        if (r < 0)
74✔
UNCOV
1072
                return log_error_errno(r, "Failed to set socket path: %m");
×
1073

1074
        if (bind(fd, &bsa.sa, r) < 0)
74✔
UNCOV
1075
                return log_error_errno(errno, "Failed to bind to notification socket: %m");
×
1076

1077
        r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
74✔
1078
        if (r < 0)
74✔
UNCOV
1079
                return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
×
1080

1081
        r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
74✔
1082
        if (r < 0)
74✔
UNCOV
1083
                log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
×
1084

1085
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
74✔
1086
        if (r < 0)
148✔
UNCOV
1087
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1088
        if (r == 0) {
148✔
1089
                /* Child */
1090
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
74✔
UNCOV
1091
                        log_error_errno(errno, "setenv() failed: %m");
×
1092
                        _exit(EXIT_FAILURE);
×
1093
                }
1094
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
74✔
UNCOV
1095
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
1096
                _exit(EXIT_FAILURE);
×
1097
        }
1098

1099
        /* Quit the loop w/ when child process exits */
1100
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, (void*) ctx);
74✔
1101
        if (r < 0)
74✔
UNCOV
1102
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1103

1104
        r = sd_event_source_set_child_process_own(exit_source, true);
74✔
1105
        if (r < 0)
74✔
UNCOV
1106
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1107

1108
        /* Propagate sd_notify calls */
1109
        r = sd_event_add_io(event, &notify_source, fd, EPOLLIN, helper_on_notify, TAKE_PTR(ctx));
74✔
1110
        if (r < 0)
74✔
UNCOV
1111
                return log_error_errno(r, "Failed to add notification propagation to event loop: %m");
×
1112

1113
        (void) sd_event_source_set_description(notify_source, "notify-socket");
74✔
1114

1115
        (void) sd_event_source_set_priority(notify_source, SD_EVENT_PRIORITY_NORMAL - 5);
74✔
1116

1117
        r = sd_event_source_set_io_fd_own(notify_source, true);
74✔
1118
        if (r < 0)
74✔
UNCOV
1119
                return log_error_errno(r, "Event loop failed to take ownership of notification source: %m");
×
1120
        TAKE_FD(fd);
74✔
1121

1122
        /* Process events until the helper quits */
1123
        return sd_event_loop(event);
74✔
1124
}
1125

1126
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
74✔
1127
        _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
74✔
1128
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
74✔
1129
        const char *where = NULL;
74✔
1130
        InstanceMetadata f;
74✔
1131
        Instance *existing;
74✔
1132
        int r;
74✔
1133

1134
        assert(t);
74✔
1135
        assert(i);
74✔
1136
        assert(i->resource == &t->source);
74✔
1137
        assert(cb);
74✔
1138

1139
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1140
        existing = resource_find_instance(&t->target, i->metadata.version);
74✔
1141
        if (existing) {
74✔
UNCOV
1142
                log_info("No need to acquire '%s', already installed.", i->path);
×
1143
                return 0;
×
1144
        }
1145

1146
        assert(!t->final_path);
74✔
1147
        assert(!t->temporary_path);
74✔
1148
        assert(!strv_isempty(t->target.patterns));
74✔
1149

1150
        /* Format the target name using the first pattern specified */
1151
        compile_pattern_fields(t, i, &f);
74✔
1152
        r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
74✔
1153
        if (r < 0)
74✔
UNCOV
1154
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1155

1156
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1157

1158
                if (!path_is_valid_full(formatted_pattern, /* accept_dot_dot = */ false))
46✔
UNCOV
1159
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1160

1161
                t->final_path = path_join(t->target.path, formatted_pattern);
46✔
1162
                if (!t->final_path)
46✔
UNCOV
1163
                        return log_oom();
×
1164

1165
                r = mkdir_parents(t->final_path, 0755);
46✔
1166
                if (r < 0)
46✔
UNCOV
1167
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1168

1169
                r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
46✔
1170
                if (r < 0)
46✔
UNCOV
1171
                        return log_error_errno(r, "Failed to generate temporary target path: %m");
×
1172

1173
                where = t->final_path;
46✔
1174
        }
1175

1176
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1177
                r = gpt_partition_label_valid(formatted_pattern);
28✔
1178
                if (r < 0)
28✔
UNCOV
1179
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1180
                if (!r)
28✔
UNCOV
1181
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1182

1183
                r = find_suitable_partition(
28✔
1184
                                t->target.path,
28✔
1185
                                i->metadata.size,
1186
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
28✔
1187
                                &t->partition_info);
1188
                if (r < 0)
28✔
1189
                        return r;
1190

1191
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
28✔
1192
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
28✔
1193

1194
                where = t->partition_info.device;
28✔
1195
        }
1196

1197
        assert(where);
74✔
1198

1199
        log_info("%s Acquiring %s %s %s...", special_glyph(SPECIAL_GLYPH_DOWNLOAD), i->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), where);
148✔
1200

1201
        if (RESOURCE_IS_URL(i->resource->type)) {
74✔
1202
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1203
                 * download. */
1204

1205
                if (!i->metadata.sha256sum_set)
8✔
UNCOV
1206
                        return log_error_errno(r, "SHA256 checksum not known for download '%s', refusing.", i->path);
×
1207

1208
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
8✔
1209
                if (!digest)
8✔
UNCOV
1210
                        return log_oom();
×
1211
        }
1212

1213
        switch (i->resource->type) { /* Source */
74✔
1214

1215
        case RESOURCE_REGULAR_FILE:
56✔
1216

1217
                switch (t->target.type) { /* Target */
56✔
1218

1219
                case RESOURCE_REGULAR_FILE:
32✔
1220

1221
                        /* regular file → regular file (why fork off systemd-import for such a simple file
1222
                         * copy case? implicit decompression mostly, and thus also sandboxing. Also, the
1223
                         * importer has some tricks up its sleeve, such as sparse file generation, which we
1224
                         * want to take benefit of, too.) */
1225

1226
                        r = run_callout("(sd-import-raw)",
64✔
1227
                                        STRV_MAKE(
32✔
1228
                                               SYSTEMD_IMPORT_PATH,
1229
                                               "raw",
1230
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1231
                                               arg_sync ? "--sync=yes" : "--sync=no",
1232
                                               i->path,
1233
                                               t->temporary_path),
1234
                                        t, i, cb, userdata);
1235
                        break;
56✔
1236

1237
                case RESOURCE_PARTITION:
24✔
1238

1239
                        /* regular file → partition */
1240

1241
                        r = run_callout("(sd-import-raw)",
48✔
1242
                                        STRV_MAKE(
24✔
1243
                                               SYSTEMD_IMPORT_PATH,
1244
                                               "raw",
1245
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1246
                                               "--offset", offset,
1247
                                               "--size-max", max_size,
1248
                                               arg_sync ? "--sync=yes" : "--sync=no",
1249
                                               i->path,
1250
                                               t->target.path),
1251
                                        t, i, cb, userdata);
1252
                        break;
1253

UNCOV
1254
                default:
×
1255
                        assert_not_reached();
×
1256
                }
1257

1258
                break;
56✔
1259

1260
        case RESOURCE_DIRECTORY:
10✔
1261
        case RESOURCE_SUBVOLUME:
1262
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1263

1264
                /* directory/subvolume → directory/subvolume */
1265

1266
                r = run_callout("(sd-import-fs)",
20✔
1267
                                STRV_MAKE(
20✔
1268
                                       SYSTEMD_IMPORT_FS_PATH,
1269
                                       "run",
1270
                                       "--direct",          /* just untar the specified file, don't do anything else */
1271
                                       arg_sync ? "--sync=yes" : "--sync=no",
1272
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1273
                                       i->path,
1274
                                       t->temporary_path),
1275
                                t, i, cb, userdata);
1276
                break;
1277

UNCOV
1278
        case RESOURCE_TAR:
×
1279
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1280

1281
                /* tar → directory/subvolume */
1282

UNCOV
1283
                r = run_callout("(sd-import-tar)",
×
1284
                                STRV_MAKE(
×
1285
                                       SYSTEMD_IMPORT_PATH,
1286
                                       "tar",
1287
                                       "--direct",          /* just untar the specified file, don't do anything else */
1288
                                       arg_sync ? "--sync=yes" : "--sync=no",
1289
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1290
                                       i->path,
1291
                                       t->temporary_path),
1292
                                t, i, cb, userdata);
1293
                break;
1294

1295
        case RESOURCE_URL_FILE:
4✔
1296

1297
                switch (t->target.type) {
4✔
1298

UNCOV
1299
                case RESOURCE_REGULAR_FILE:
×
1300

1301
                        /* url file → regular file */
1302

UNCOV
1303
                        r = run_callout("(sd-pull-raw)",
×
1304
                                       STRV_MAKE(
×
1305
                                               SYSTEMD_PULL_PATH,
1306
                                               "raw",
1307
                                               "--direct",          /* just download the specified URL, don't download anything else */
1308
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1309
                                               arg_sync ? "--sync=yes" : "--sync=no",
1310
                                               i->path,
1311
                                               t->temporary_path),
1312
                                        t, i, cb, userdata);
1313
                        break;
4✔
1314

1315
                case RESOURCE_PARTITION:
4✔
1316

1317
                        /* url file → partition */
1318

1319
                        r = run_callout("(sd-pull-raw)",
8✔
1320
                                        STRV_MAKE(
4✔
1321
                                               SYSTEMD_PULL_PATH,
1322
                                               "raw",
1323
                                               "--direct",              /* just download the specified URL, don't download anything else */
1324
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1325
                                               "--offset", offset,
1326
                                               "--size-max", max_size,
1327
                                               arg_sync ? "--sync=yes" : "--sync=no",
1328
                                               i->path,
1329
                                               t->target.path),
1330
                                        t, i, cb, userdata);
1331
                        break;
1332

UNCOV
1333
                default:
×
1334
                        assert_not_reached();
×
1335
                }
1336

1337
                break;
4✔
1338

1339
        case RESOURCE_URL_TAR:
4✔
1340
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
4✔
1341

1342
                r = run_callout("(sd-pull-tar)",
8✔
1343
                                STRV_MAKE(
8✔
1344
                                       SYSTEMD_PULL_PATH,
1345
                                       "tar",
1346
                                       "--direct",          /* just download the specified URL, don't download anything else */
1347
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1348
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1349
                                       arg_sync ? "--sync=yes" : "--sync=no",
1350
                                       i->path,
1351
                                       t->temporary_path),
1352
                                t, i, cb, userdata);
1353
                break;
1354

UNCOV
1355
        default:
×
1356
                assert_not_reached();
×
1357
        }
1358
        if (r < 0)
74✔
1359
                return r;
1360

1361
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1362
                bool need_sync = false;
46✔
1363
                assert(t->temporary_path);
46✔
1364

1365
                /* Apply file attributes if set */
1366
                if (f.mtime != USEC_INFINITY) {
46✔
1367
                        struct timespec ts;
42✔
1368

1369
                        timespec_store(&ts, f.mtime);
42✔
1370

1371
                        if (utimensat(AT_FDCWD, t->temporary_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
42✔
UNCOV
1372
                                return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_path);
×
1373

1374
                        need_sync = true;
42✔
1375
                }
1376

1377
                if (f.mode != MODE_INVALID) {
46✔
1378
                        /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1379
                         * kernels don't support that however, in that case we fall back to chmod(). Not as
1380
                         * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1381
                        if (fchmodat(AT_FDCWD, t->temporary_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
42✔
UNCOV
1382
                            (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_path, f.mode) < 0))
×
1383
                                return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_path);
×
1384

1385
                        need_sync = true;
1386
                }
1387

1388
                /* Synchronize */
1389
                if (arg_sync && need_sync) {
46✔
1390
                        if (t->target.type == RESOURCE_REGULAR_FILE)
42✔
1391
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
32✔
1392
                        else {
1393
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1394
                                r = syncfs_path(AT_FDCWD, t->temporary_path);
10✔
1395
                        }
1396
                        if (r < 0)
42✔
UNCOV
1397
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
×
1398
                }
1399

1400
                t->install_read_only = f.read_only;
46✔
1401
        }
1402

1403
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1404
                free_and_replace(t->partition_info.label, formatted_pattern);
28✔
1405
                t->partition_change = PARTITION_LABEL;
28✔
1406

1407
                if (f.partition_uuid_set) {
28✔
UNCOV
1408
                        t->partition_info.uuid = f.partition_uuid;
×
1409
                        t->partition_change |= PARTITION_UUID;
×
1410
                }
1411

1412
                if (f.partition_flags_set) {
28✔
UNCOV
1413
                        t->partition_info.flags = f.partition_flags;
×
1414
                        t->partition_change |= PARTITION_FLAGS;
×
1415
                }
1416

1417
                if (f.no_auto >= 0) {
28✔
UNCOV
1418
                        t->partition_info.no_auto = f.no_auto;
×
1419
                        t->partition_change |= PARTITION_NO_AUTO;
×
1420
                }
1421

1422
                if (f.read_only >= 0) {
28✔
UNCOV
1423
                        t->partition_info.read_only = f.read_only;
×
1424
                        t->partition_change |= PARTITION_READ_ONLY;
×
1425
                }
1426

1427
                if (f.growfs >= 0) {
28✔
UNCOV
1428
                        t->partition_info.growfs = f.growfs;
×
1429
                        t->partition_change |= PARTITION_GROWFS;
×
1430
                }
1431
        }
1432

1433
        /* For regular file cases the only step left is to install the file in place, which install_file()
1434
         * will do via rename(). For partition cases the only step left is to update the partition table,
1435
         * which is done at the same place. */
1436

1437
        log_info("Successfully acquired '%s'.", i->path);
74✔
1438
        return 0;
1439
}
1440

1441
int transfer_install_instance(
74✔
1442
                Transfer *t,
1443
                Instance *i,
1444
                const char *root) {
1445

1446
        int r;
74✔
1447

1448
        assert(t);
74✔
1449
        assert(i);
74✔
1450
        assert(i->resource);
74✔
1451
        assert(t == container_of(i->resource, Transfer, source));
74✔
1452

1453
        if (t->temporary_path) {
74✔
1454
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
46✔
1455
                assert(t->final_path);
46✔
1456

1457
                r = install_file(AT_FDCWD, t->temporary_path,
138✔
1458
                                 AT_FDCWD, t->final_path,
1459
                                 INSTALL_REPLACE|
46✔
1460
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
92✔
1461
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
46✔
1462
                if (r < 0)
46✔
UNCOV
1463
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1464

1465
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
46✔
1466
                         i->path,
1467
                         resource_type_to_string(i->resource->type),
1468
                         t->final_path,
1469
                         resource_type_to_string(t->target.type));
1470

1471
                t->temporary_path = mfree(t->temporary_path);
46✔
1472
        }
1473

1474
        if (t->partition_change != 0) {
74✔
1475
                assert(t->target.type == RESOURCE_PARTITION);
28✔
1476

1477
                r = patch_partition(
56✔
1478
                                t->target.path,
28✔
1479
                                &t->partition_info,
28✔
1480
                                t->partition_change);
1481
                if (r < 0)
28✔
1482
                        return r;
1483

1484
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
28✔
1485
                         i->path,
1486
                         resource_type_to_string(i->resource->type),
1487
                         t->partition_info.device,
1488
                         resource_type_to_string(t->target.type));
1489
        }
1490

1491
        if (t->current_symlink) {
74✔
1492
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
14✔
1493
                const char *link_path, *link_target;
14✔
1494
                bool resolve_link_path = false;
14✔
1495

1496
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
14✔
1497

1498
                        assert(t->target.path);
14✔
1499

1500
                        if (path_is_absolute(t->current_symlink)) {
14✔
1501
                                link_path = t->current_symlink;
1502
                                resolve_link_path = true;
1503
                        } else {
UNCOV
1504
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1505
                                if (!buf)
×
1506
                                        return log_oom();
×
1507

1508
                                link_path = buf;
1509
                        }
1510

1511
                        link_target = t->final_path;
14✔
1512

UNCOV
1513
                } else if (t->target.type == RESOURCE_PARTITION) {
×
1514

UNCOV
1515
                        assert(path_is_absolute(t->current_symlink));
×
1516

UNCOV
1517
                        link_path = t->current_symlink;
×
1518
                        link_target = t->partition_info.device;
×
1519

UNCOV
1520
                        resolve_link_path = true;
×
1521
                } else
UNCOV
1522
                        assert_not_reached();
×
1523

1524
                if (resolve_link_path && root) {
14✔
UNCOV
1525
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
×
1526
                        if (r < 0)
×
1527
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1528

UNCOV
1529
                        link_path = resolved;
×
1530
                }
1531

1532
                if (link_target) {
14✔
1533
                        r = path_extract_directory(link_path, &parent);
14✔
1534
                        if (r < 0)
14✔
UNCOV
1535
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1536

1537
                        r = path_make_relative(parent, link_target, &relative);
14✔
1538
                        if (r < 0)
14✔
UNCOV
1539
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1540

1541
                        r = symlink_atomic(relative, link_path);
14✔
1542
                        if (r < 0)
14✔
UNCOV
1543
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1544
                                                       link_path,
1545
                                                       special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
1546
                                                       relative);
1547

1548
                        log_info("Updated symlink '%s' %s '%s'.",
14✔
1549
                                 link_path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), relative);
1550
                }
1551
        }
1552

1553
        return 0;
1554
}
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