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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

66.28
/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-instance.h"
35
#include "sysupdate-pattern.h"
36
#include "sysupdate-resource.h"
37
#include "sysupdate-transfer.h"
38
#include "tmpfile-util.h"
39
#include "web-util.h"
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

95
                .partition_info = PARTITION_INFO_NULL,
96

97
                .context = ctx,
98
        };
99

100
        return t;
556✔
101
}
102

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

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

119
        assert(rvalue);
×
120

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

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

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

138
        return 0;
139
}
140

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

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

157
        assert(rvalue);
×
158

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

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

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

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

190
        assert(rvalue);
×
191

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

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

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

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

214
        return 0;
215
}
216

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

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

233
        assert(rvalue);
92✔
234

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

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

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

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

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

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

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

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

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

286
        return 0;
287
}
288

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

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

304
        assert(rvalue);
1,112✔
305

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

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

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

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

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

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

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

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

358
        assert(rvalue);
1,112✔
359

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

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

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

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

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

383
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
276✔
384
                                             PATH_RELATIVE_TO_ROOT);
385

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

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

401
        assert(rvalue);
188✔
402

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

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

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

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

429
        assert(rvalue);
×
430

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

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

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

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

457
        assert(rvalue);
×
458

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

659
        return 0;
660
}
661

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

667
        int r;
556✔
668

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

674
        assert(t);
556✔
675

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

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

684
        return 0;
685
}
686

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

691
        assert(t);
102✔
692

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

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

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

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

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

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

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

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

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

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

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

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

744
        assert(t);
102✔
745

746
        transfer_remove_temporary(t);
102✔
747

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

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

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

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

771
                if (t->target.n_empty + t->target.n_instances < 2)
32✔
772
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
773
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), refusing.",
774
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
775
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
776
                if (space > t->target.n_empty + t->target.n_instances)
32✔
777
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
778
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s) for operation.",
779
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
780
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
781
                if (space == t->target.n_empty + t->target.n_instances)
32✔
782
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
783
                                               "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.",
784
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
785
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
786

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

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

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

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

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

811
                        p--;
×
812
                }
813

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

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

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

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

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

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

836
                        break;
30✔
837

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

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

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

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

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

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

860
                count++;
50✔
861
        }
862

863
        return count;
864
}
865

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

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

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

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

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

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

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

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

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

921
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
148✔
922

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

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

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

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

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

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

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

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

975
        return sd_event_exit(sd_event_source_get_event(s), r);
74✔
976
}
977

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

982
        assert(fd >= 0);
145✔
983

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

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

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

1009
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
145✔
1010
        if (progress_str) {
145✔
1011
                truncate_nl(progress_str);
71✔
1012

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

1023
        return 0;
1024
}
1025

1026
static int run_callout(
74✔
1027
                const char *name,
1028
                char *cmdline[],
1029
                const Transfer *transfer,
1030
                const Instance *instance,
1031
                TransferProgress callback,
1032
                void *userdata) {
1033

1034
        int r;
74✔
1035

1036
        assert(name);
74✔
1037
        assert(cmdline);
74✔
1038
        assert(cmdline[0]);
74✔
1039

1040
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1041
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
74✔
1042
        if (r < 0)
74✔
1043
                return log_oom();
×
1044

1045
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
74✔
1046
        r = sd_event_new(&event);
74✔
1047
        if (r < 0)
74✔
1048
                return log_error_errno(r, "Failed to create event: %m");
×
1049

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

1058
        _cleanup_free_ char *bind_name = NULL;
74✔
1059
        r = notify_socket_prepare(
74✔
1060
                        event,
1061
                        SD_EVENT_PRIORITY_NORMAL - 5,
1062
                        helper_on_notify,
1063
                        ctx,
1064
                        &bind_name);
1065
        if (r < 0)
74✔
1066
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1067

1068
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
74✔
1069
        if (r < 0)
148✔
1070
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1071
        if (r == 0) {
148✔
1072
                /* Child */
1073
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
74✔
1074
                        log_error_errno(errno, "setenv() failed: %m");
×
1075
                        _exit(EXIT_FAILURE);
×
1076
                }
1077
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
74✔
1078
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
1079
                _exit(EXIT_FAILURE);
×
1080
        }
1081

1082
        /* Quit the loop w/ when child process exits */
1083
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
74✔
1084
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
74✔
1085
        if (r < 0)
74✔
1086
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1087

1088
        r = sd_event_source_set_child_process_own(exit_source, true);
74✔
1089
        if (r < 0)
74✔
1090
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1091

1092
        /* Process events until the helper quits */
1093
        return sd_event_loop(event);
74✔
1094
}
1095

1096
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
74✔
1097
        _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
74✔
1098
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
74✔
1099
        const char *where = NULL;
74✔
1100
        InstanceMetadata f;
74✔
1101
        Instance *existing;
74✔
1102
        int r;
74✔
1103

1104
        assert(t);
74✔
1105
        assert(i);
74✔
1106
        assert(i->resource == &t->source);
74✔
1107
        assert(cb);
74✔
1108

1109
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1110
        existing = resource_find_instance(&t->target, i->metadata.version);
74✔
1111
        if (existing) {
74✔
1112
                log_info("No need to acquire '%s', already installed.", i->path);
×
1113
                return 0;
×
1114
        }
1115

1116
        assert(!t->final_path);
74✔
1117
        assert(!t->temporary_path);
74✔
1118
        assert(!strv_isempty(t->target.patterns));
74✔
1119

1120
        /* Format the target name using the first pattern specified */
1121
        compile_pattern_fields(t, i, &f);
74✔
1122
        r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
74✔
1123
        if (r < 0)
74✔
1124
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1125

1126
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1127

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

1131
                t->final_path = path_join(t->target.path, formatted_pattern);
46✔
1132
                if (!t->final_path)
46✔
1133
                        return log_oom();
×
1134

1135
                r = mkdir_parents(t->final_path, 0755);
46✔
1136
                if (r < 0)
46✔
1137
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1138

1139
                r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
46✔
1140
                if (r < 0)
46✔
1141
                        return log_error_errno(r, "Failed to generate temporary target path: %m");
×
1142

1143
                where = t->final_path;
46✔
1144
        }
1145

1146
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1147
                r = gpt_partition_label_valid(formatted_pattern);
28✔
1148
                if (r < 0)
28✔
1149
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1150
                if (!r)
28✔
1151
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1152

1153
                r = find_suitable_partition(
28✔
1154
                                t->target.path,
28✔
1155
                                i->metadata.size,
1156
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
28✔
1157
                                &t->partition_info);
1158
                if (r < 0)
28✔
1159
                        return r;
1160

1161
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
28✔
1162
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
28✔
1163

1164
                where = t->partition_info.device;
28✔
1165
        }
1166

1167
        assert(where);
74✔
1168

1169
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
148✔
1170

1171
        if (RESOURCE_IS_URL(i->resource->type)) {
74✔
1172
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1173
                 * download. */
1174

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

1178
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
8✔
1179
                if (!digest)
8✔
1180
                        return log_oom();
×
1181
        }
1182

1183
        switch (i->resource->type) { /* Source */
74✔
1184

1185
        case RESOURCE_REGULAR_FILE:
56✔
1186

1187
                switch (t->target.type) { /* Target */
56✔
1188

1189
                case RESOURCE_REGULAR_FILE:
32✔
1190

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

1196
                        r = run_callout("(sd-import-raw)",
64✔
1197
                                        STRV_MAKE(
32✔
1198
                                               SYSTEMD_IMPORT_PATH,
1199
                                               "raw",
1200
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1201
                                               arg_sync ? "--sync=yes" : "--sync=no",
1202
                                               i->path,
1203
                                               t->temporary_path),
1204
                                        t, i, cb, userdata);
1205
                        break;
56✔
1206

1207
                case RESOURCE_PARTITION:
24✔
1208

1209
                        /* regular file → partition */
1210

1211
                        r = run_callout("(sd-import-raw)",
48✔
1212
                                        STRV_MAKE(
24✔
1213
                                               SYSTEMD_IMPORT_PATH,
1214
                                               "raw",
1215
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1216
                                               "--offset", offset,
1217
                                               "--size-max", max_size,
1218
                                               arg_sync ? "--sync=yes" : "--sync=no",
1219
                                               i->path,
1220
                                               t->target.path),
1221
                                        t, i, cb, userdata);
1222
                        break;
1223

1224
                default:
×
1225
                        assert_not_reached();
×
1226
                }
1227

1228
                break;
56✔
1229

1230
        case RESOURCE_DIRECTORY:
10✔
1231
        case RESOURCE_SUBVOLUME:
1232
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1233

1234
                /* directory/subvolume → directory/subvolume */
1235

1236
                r = run_callout("(sd-import-fs)",
20✔
1237
                                STRV_MAKE(
20✔
1238
                                       SYSTEMD_IMPORT_FS_PATH,
1239
                                       "run",
1240
                                       "--direct",          /* just untar the specified file, don't do anything else */
1241
                                       arg_sync ? "--sync=yes" : "--sync=no",
1242
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1243
                                       i->path,
1244
                                       t->temporary_path),
1245
                                t, i, cb, userdata);
1246
                break;
1247

1248
        case RESOURCE_TAR:
×
1249
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1250

1251
                /* tar → directory/subvolume */
1252

1253
                r = run_callout("(sd-import-tar)",
×
1254
                                STRV_MAKE(
×
1255
                                       SYSTEMD_IMPORT_PATH,
1256
                                       "tar",
1257
                                       "--direct",          /* just untar the specified file, don't do anything else */
1258
                                       arg_sync ? "--sync=yes" : "--sync=no",
1259
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1260
                                       i->path,
1261
                                       t->temporary_path),
1262
                                t, i, cb, userdata);
1263
                break;
1264

1265
        case RESOURCE_URL_FILE:
4✔
1266

1267
                switch (t->target.type) {
4✔
1268

1269
                case RESOURCE_REGULAR_FILE:
×
1270

1271
                        /* url file → regular file */
1272

1273
                        r = run_callout("(sd-pull-raw)",
×
1274
                                       STRV_MAKE(
×
1275
                                               SYSTEMD_PULL_PATH,
1276
                                               "raw",
1277
                                               "--direct",          /* just download the specified URL, don't download anything else */
1278
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1279
                                               arg_sync ? "--sync=yes" : "--sync=no",
1280
                                               i->path,
1281
                                               t->temporary_path),
1282
                                        t, i, cb, userdata);
1283
                        break;
4✔
1284

1285
                case RESOURCE_PARTITION:
4✔
1286

1287
                        /* url file → partition */
1288

1289
                        r = run_callout("(sd-pull-raw)",
8✔
1290
                                        STRV_MAKE(
4✔
1291
                                               SYSTEMD_PULL_PATH,
1292
                                               "raw",
1293
                                               "--direct",              /* just download the specified URL, don't download anything else */
1294
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1295
                                               "--offset", offset,
1296
                                               "--size-max", max_size,
1297
                                               arg_sync ? "--sync=yes" : "--sync=no",
1298
                                               i->path,
1299
                                               t->target.path),
1300
                                        t, i, cb, userdata);
1301
                        break;
1302

1303
                default:
×
1304
                        assert_not_reached();
×
1305
                }
1306

1307
                break;
4✔
1308

1309
        case RESOURCE_URL_TAR:
4✔
1310
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
4✔
1311

1312
                r = run_callout("(sd-pull-tar)",
8✔
1313
                                STRV_MAKE(
8✔
1314
                                       SYSTEMD_PULL_PATH,
1315
                                       "tar",
1316
                                       "--direct",          /* just download the specified URL, don't download anything else */
1317
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1318
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1319
                                       arg_sync ? "--sync=yes" : "--sync=no",
1320
                                       i->path,
1321
                                       t->temporary_path),
1322
                                t, i, cb, userdata);
1323
                break;
1324

1325
        default:
×
1326
                assert_not_reached();
×
1327
        }
1328
        if (r < 0)
74✔
1329
                return r;
1330

1331
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1332
                bool need_sync = false;
46✔
1333
                assert(t->temporary_path);
46✔
1334

1335
                /* Apply file attributes if set */
1336
                if (f.mtime != USEC_INFINITY) {
46✔
1337
                        struct timespec ts;
42✔
1338

1339
                        timespec_store(&ts, f.mtime);
42✔
1340

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

1344
                        need_sync = true;
42✔
1345
                }
1346

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

1355
                        need_sync = true;
1356
                }
1357

1358
                /* Synchronize */
1359
                if (arg_sync && need_sync) {
46✔
1360
                        if (t->target.type == RESOURCE_REGULAR_FILE)
42✔
1361
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
32✔
1362
                        else {
1363
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1364
                                r = syncfs_path(AT_FDCWD, t->temporary_path);
10✔
1365
                        }
1366
                        if (r < 0)
42✔
1367
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
×
1368
                }
1369

1370
                t->install_read_only = f.read_only;
46✔
1371
        }
1372

1373
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1374
                free_and_replace(t->partition_info.label, formatted_pattern);
28✔
1375
                t->partition_change = PARTITION_LABEL;
28✔
1376

1377
                if (f.partition_uuid_set) {
28✔
1378
                        t->partition_info.uuid = f.partition_uuid;
×
1379
                        t->partition_change |= PARTITION_UUID;
×
1380
                }
1381

1382
                if (f.partition_flags_set) {
28✔
1383
                        t->partition_info.flags = f.partition_flags;
×
1384
                        t->partition_change |= PARTITION_FLAGS;
×
1385
                }
1386

1387
                if (f.no_auto >= 0) {
28✔
1388
                        t->partition_info.no_auto = f.no_auto;
×
1389
                        t->partition_change |= PARTITION_NO_AUTO;
×
1390
                }
1391

1392
                if (f.read_only >= 0) {
28✔
1393
                        t->partition_info.read_only = f.read_only;
×
1394
                        t->partition_change |= PARTITION_READ_ONLY;
×
1395
                }
1396

1397
                if (f.growfs >= 0) {
28✔
1398
                        t->partition_info.growfs = f.growfs;
×
1399
                        t->partition_change |= PARTITION_GROWFS;
×
1400
                }
1401
        }
1402

1403
        /* For regular file cases the only step left is to install the file in place, which install_file()
1404
         * will do via rename(). For partition cases the only step left is to update the partition table,
1405
         * which is done at the same place. */
1406

1407
        log_info("Successfully acquired '%s'.", i->path);
74✔
1408
        return 0;
1409
}
1410

1411
int transfer_install_instance(
74✔
1412
                Transfer *t,
1413
                Instance *i,
1414
                const char *root) {
1415

1416
        int r;
74✔
1417

1418
        assert(t);
74✔
1419
        assert(i);
74✔
1420
        assert(i->resource);
74✔
1421
        assert(t == container_of(i->resource, Transfer, source));
74✔
1422

1423
        if (t->temporary_path) {
74✔
1424
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
46✔
1425
                assert(t->final_path);
46✔
1426

1427
                r = install_file(AT_FDCWD, t->temporary_path,
138✔
1428
                                 AT_FDCWD, t->final_path,
1429
                                 INSTALL_REPLACE|
46✔
1430
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
92✔
1431
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
46✔
1432
                if (r < 0)
46✔
1433
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1434

1435
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
46✔
1436
                         i->path,
1437
                         resource_type_to_string(i->resource->type),
1438
                         t->final_path,
1439
                         resource_type_to_string(t->target.type));
1440

1441
                t->temporary_path = mfree(t->temporary_path);
46✔
1442
        }
1443

1444
        if (t->partition_change != 0) {
74✔
1445
                assert(t->target.type == RESOURCE_PARTITION);
28✔
1446

1447
                r = patch_partition(
56✔
1448
                                t->target.path,
28✔
1449
                                &t->partition_info,
28✔
1450
                                t->partition_change);
1451
                if (r < 0)
28✔
1452
                        return r;
1453

1454
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
28✔
1455
                         i->path,
1456
                         resource_type_to_string(i->resource->type),
1457
                         t->partition_info.device,
1458
                         resource_type_to_string(t->target.type));
1459
        }
1460

1461
        if (t->current_symlink) {
74✔
1462
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
14✔
1463
                const char *link_path, *link_target;
14✔
1464
                bool resolve_link_path = false;
14✔
1465

1466
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
14✔
1467

1468
                        assert(t->target.path);
14✔
1469

1470
                        if (path_is_absolute(t->current_symlink)) {
14✔
1471
                                link_path = t->current_symlink;
1472
                                resolve_link_path = true;
1473
                        } else {
1474
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1475
                                if (!buf)
×
1476
                                        return log_oom();
×
1477

1478
                                link_path = buf;
1479
                        }
1480

1481
                        link_target = t->final_path;
14✔
1482

1483
                } else if (t->target.type == RESOURCE_PARTITION) {
×
1484

1485
                        assert(path_is_absolute(t->current_symlink));
×
1486

1487
                        link_path = t->current_symlink;
×
1488
                        link_target = t->partition_info.device;
×
1489

1490
                        resolve_link_path = true;
×
1491
                } else
1492
                        assert_not_reached();
×
1493

1494
                if (resolve_link_path && root) {
14✔
1495
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
×
1496
                        if (r < 0)
×
1497
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1498

1499
                        link_path = resolved;
×
1500
                }
1501

1502
                if (link_target) {
14✔
1503
                        r = path_extract_directory(link_path, &parent);
14✔
1504
                        if (r < 0)
14✔
1505
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1506

1507
                        r = path_make_relative(parent, link_target, &relative);
14✔
1508
                        if (r < 0)
14✔
1509
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1510

1511
                        r = symlink_atomic(relative, link_path);
14✔
1512
                        if (r < 0)
14✔
1513
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1514
                                                       link_path,
1515
                                                       glyph(GLYPH_ARROW_RIGHT),
1516
                                                       relative);
1517

1518
                        log_info("Updated symlink '%s' %s '%s'.",
14✔
1519
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1520
                }
1521
        }
1522

1523
        return 0;
1524
}
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