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

systemd / systemd / 21419361949

27 Jan 2026 02:53PM UTC coverage: 72.793% (-0.03%) from 72.821%
21419361949

push

github

keszybz
kernel-install: handle removal unsuccessful UKIs and loader entries separately

When a tries file exists, 90-uki-copy.install removes a previous UKI of the
same kernel version and all it's unbooted variants. This removal is guarded
behind a check for the existence of the already booted UKI, i.e. if uki.efi
already exists, uki.efi and uki+*.efi will be removed.

This leaves the edge case that if uki.efi does not exist, but only an unbooted,
e.g. uki+3.efi, it will not be removed. This is not a problem, if the number of
tries is constant between both builds, since a new uki+3.efi would overwrite
the existing one, but if the number of tries is changed to, e.g. uki+5.efi, we
are left with both uki+3.efi and uki+5.efi.

The same is done for loader entries.

311334 of 427698 relevant lines covered (72.79%)

1157141.18 hits per line

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

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

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

7
#include "sd-id128.h"
8

9
#include "alloc-util.h"
10
#include "build-path.h"
11
#include "chase.h"
12
#include "conf-parser.h"
13
#include "dirent-util.h"
14
#include "errno-util.h"
15
#include "event-util.h"
16
#include "extract-word.h"
17
#include "fd-util.h"
18
#include "fs-util.h"
19
#include "glyph-util.h"
20
#include "gpt.h"
21
#include "hashmap.h"
22
#include "hexdecoct.h"
23
#include "install-file.h"
24
#include "mkdir.h"
25
#include "notify-recv.h"
26
#include "parse-helpers.h"
27
#include "parse-util.h"
28
#include "percent-util.h"
29
#include "pidref.h"
30
#include "process-util.h"
31
#include "rm-rf.h"
32
#include "signal-util.h"
33
#include "specifier.h"
34
#include "stdio-util.h"
35
#include "strv.h"
36
#include "sync-util.h"
37
#include "sysupdate.h"
38
#include "sysupdate-feature.h"
39
#include "sysupdate-instance.h"
40
#include "sysupdate-pattern.h"
41
#include "sysupdate-resource.h"
42
#include "sysupdate-transfer.h"
43
#include "time-util.h"
44
#include "tmpfile-util.h"
45
#include "web-util.h"
46

47
/* Default value for InstancesMax= for fs object targets */
48
#define DEFAULT_FILE_INSTANCES_MAX 3
49

50
Transfer* transfer_free(Transfer *t) {
604✔
51
        if (!t)
604✔
52
                return NULL;
53

54
        t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
604✔
55

56
        free(t->id);
604✔
57

58
        free(t->min_version);
604✔
59
        strv_free(t->protected_versions);
604✔
60
        free(t->current_symlink);
604✔
61
        free(t->final_path);
604✔
62

63
        strv_free(t->features);
604✔
64
        strv_free(t->requisite_features);
604✔
65

66
        strv_free(t->changelog);
604✔
67
        strv_free(t->appstream);
604✔
68

69
        partition_info_destroy(&t->partition_info);
604✔
70

71
        resource_destroy(&t->source);
604✔
72
        resource_destroy(&t->target);
604✔
73

74
        return mfree(t);
604✔
75
}
76

77
Transfer* transfer_new(Context *ctx) {
604✔
78
        Transfer *t;
604✔
79

80
        t = new(Transfer, 1);
604✔
81
        if (!t)
604✔
82
                return NULL;
83

84
        *t = (Transfer) {
604✔
85
                .source.type = _RESOURCE_TYPE_INVALID,
86
                .target.type = _RESOURCE_TYPE_INVALID,
87
                .remove_temporary = true,
88
                .mode = MODE_INVALID,
89
                .tries_left = UINT64_MAX,
90
                .tries_done = UINT64_MAX,
91
                .verify = true,
92

93
                /* the three flags, as configured by the user */
94
                .no_auto = -1,
95
                .read_only = -1,
96
                .growfs = -1,
97

98
                /* the read only flag, as ultimately determined */
99
                .install_read_only = -1,
100

101
                .partition_info = PARTITION_INFO_NULL,
102

103
                .context = ctx,
104
        };
105

106
        return t;
604✔
107
}
108

109
static int config_parse_protect_version(
×
110
                const char *unit,
111
                const char *filename,
112
                unsigned line,
113
                const char *section,
114
                unsigned section_line,
115
                const char *lvalue,
116
                int ltype,
117
                const char *rvalue,
118
                void *data,
119
                void *userdata) {
120

121
        _cleanup_free_ char *resolved = NULL;
×
122
        char ***protected_versions = ASSERT_PTR(data);
×
123
        int r;
×
124

125
        assert(rvalue);
×
126

127
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
128
        if (r < 0) {
×
129
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
130
                           "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
131
                return 0;
×
132
        }
133

134
        if (!version_is_valid(resolved))  {
×
135
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
136
                           "ProtectVersion= string is not valid, ignoring: %s", resolved);
137
                return 0;
×
138
        }
139

140
        r = strv_extend(protected_versions, resolved);
×
141
        if (r < 0)
×
142
                return log_oom();
×
143

144
        return 0;
145
}
146

147
static int config_parse_min_version(
×
148
                const char *unit,
149
                const char *filename,
150
                unsigned line,
151
                const char *section,
152
                unsigned section_line,
153
                const char *lvalue,
154
                int ltype,
155
                const char *rvalue,
156
                void *data,
157
                void *userdata) {
158

159
        _cleanup_free_ char *resolved = NULL;
×
160
        char **version = ASSERT_PTR(data);
×
161
        int r;
×
162

163
        assert(rvalue);
×
164

165
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
166
        if (r < 0) {
×
167
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
168
                           "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
169
                return 0;
×
170
        }
171

172
        if (!version_is_valid(rvalue)) {
×
173
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
174
                           "MinVersion= string is not valid, ignoring: %s", resolved);
175
                return 0;
×
176
        }
177

178
        return free_and_replace(*version, resolved);
×
179
}
180

181
static int config_parse_url_specifiers(
×
182
                const char *unit,
183
                const char *filename,
184
                unsigned line,
185
                const char *section,
186
                unsigned section_line,
187
                const char *lvalue,
188
                int ltype,
189
                const char *rvalue,
190
                void *data,
191
                void *userdata) {
192
        char ***s = ASSERT_PTR(data);
×
193
        _cleanup_free_ char *resolved = NULL;
×
194
        int r;
×
195

196
        assert(rvalue);
×
197

198
        if (isempty(rvalue)) {
×
199
                *s = strv_free(*s);
×
200
                return 0;
×
201
        }
202

203
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
×
204
        if (r < 0) {
×
205
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
206
                           "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
207
                return 0;
×
208
        }
209

210
        if (!http_url_is_valid(resolved)) {
×
211
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
212
                           "%s= URL is not valid, ignoring: %s", lvalue, rvalue);
213
                return 0;
×
214
        }
215

216
        r = strv_push(s, TAKE_PTR(resolved));
×
217
        if (r < 0)
×
218
                return log_oom();
×
219

220
        return 0;
221
}
222

223
static int config_parse_current_symlink(
100✔
224
                const char *unit,
225
                const char *filename,
226
                unsigned line,
227
                const char *section,
228
                unsigned section_line,
229
                const char *lvalue,
230
                int ltype,
231
                const char *rvalue,
232
                void *data,
233
                void *userdata) {
234

235
        _cleanup_free_ char *resolved = NULL;
100✔
236
        char **current_symlink = ASSERT_PTR(data);
100✔
237
        int r;
100✔
238

239
        assert(rvalue);
100✔
240

241
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
100✔
242
        if (r < 0) {
100✔
243
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
244
                           "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
245
                return 0;
×
246
        }
247

248
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
100✔
249
        if (r < 0)
100✔
250
                return 0;
251

252
        return free_and_replace(*current_symlink, resolved);
100✔
253
}
254

255
static int config_parse_instances_max(
400✔
256
                const char *unit,
257
                const char *filename,
258
                unsigned line,
259
                const char *section,
260
                unsigned section_line,
261
                const char *lvalue,
262
                int ltype,
263
                const char *rvalue,
264
                void *data,
265
                void *userdata) {
266

267
        uint64_t *instances_max = data, i;
400✔
268
        int r;
400✔
269

270
        assert(rvalue);
400✔
271
        assert(data);
400✔
272

273
        if (isempty(rvalue)) {
400✔
274
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
275
                return 0;
×
276
        }
277

278
        r = safe_atou64(rvalue, &i);
400✔
279
        if (r < 0) {
400✔
280
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
281
                           "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
282
                return 0;
×
283
        }
284

285
        if (i < 2) {
400✔
286
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
287
                           "InstancesMax= value must be at least 2, bumping: %s", rvalue);
288
                *instances_max = 2;
×
289
        } else
290
                *instances_max = i;
400✔
291

292
        return 0;
293
}
294

295
static int config_parse_resource_pattern(
1,208✔
296
                const char *unit,
297
                const char *filename,
298
                unsigned line,
299
                const char *section,
300
                unsigned section_line,
301
                const char *lvalue,
302
                int ltype,
303
                const char *rvalue,
304
                void *data,
305
                void *userdata) {
306

307
        char ***patterns = ASSERT_PTR(data);
1,208✔
308
        int r;
1,208✔
309

310
        assert(rvalue);
1,208✔
311

312
        if (isempty(rvalue)) {
1,208✔
313
                *patterns = strv_free(*patterns);
×
314
                return 0;
×
315
        }
316

317
        for (;;) {
4,024✔
318
                _cleanup_free_ char *word = NULL, *resolved = NULL;
1,408✔
319

320
                r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
2,616✔
321
                if (r < 0) {
2,616✔
322
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
323
                                   "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
324
                        return 0;
×
325
                }
326
                if (r == 0)
2,616✔
327
                        break;
328

329
                r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
1,408✔
330
                if (r < 0) {
1,408✔
331
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
332
                                   "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
333
                        return 0;
×
334
                }
335

336
                if (!pattern_valid(resolved))
1,408✔
337
                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
338
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
339

340
                r = strv_consume(patterns, TAKE_PTR(resolved));
1,408✔
341
                if (r < 0)
1,408✔
342
                        return log_oom();
×
343
        }
344

345
        strv_uniq(*patterns);
1,208✔
346
        return 0;
1,208✔
347
}
348

349
static int config_parse_resource_path(
1,208✔
350
                const char *unit,
351
                const char *filename,
352
                unsigned line,
353
                const char *section,
354
                unsigned section_line,
355
                const char *lvalue,
356
                int ltype,
357
                const char *rvalue,
358
                void *data,
359
                void *userdata) {
360
        _cleanup_free_ char *resolved = NULL;
1,208✔
361
        Resource *rr = ASSERT_PTR(data);
1,208✔
362
        int r;
1,208✔
363

364
        assert(rvalue);
1,208✔
365

366
        if (streq(rvalue, "auto")) {
1,208✔
367
                rr->path_auto = true;
×
368
                rr->path = mfree(rr->path);
×
369
                return 0;
×
370
        }
371

372
        r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved);
1,208✔
373
        if (r < 0) {
1,208✔
374
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
375
                           "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
376
                return 0;
×
377
        }
378

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

383
        rr->path_auto = false;
1,208✔
384
        return free_and_replace(rr->path, resolved);
1,208✔
385
}
386

387
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
1,208✔
388

389
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
300✔
390
                                             PATH_RELATIVE_TO_ROOT);
391

392
static int config_parse_resource_ptype(
204✔
393
                const char *unit,
394
                const char *filename,
395
                unsigned line,
396
                const char *section,
397
                unsigned section_line,
398
                const char *lvalue,
399
                int ltype,
400
                const char *rvalue,
401
                void *data,
402
                void *userdata) {
403

404
        Resource *rr = ASSERT_PTR(data);
204✔
405
        int r;
204✔
406

407
        assert(rvalue);
204✔
408

409
        r = gpt_partition_type_from_string(rvalue, &rr->partition_type);
204✔
410
        if (r < 0) {
204✔
411
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
412
                           "Failed to parse partition type, ignoring: %s", rvalue);
413
                return 0;
×
414
        }
415

416
        rr->partition_type_set = true;
204✔
417
        return 0;
204✔
418
}
419

420
static int config_parse_partition_uuid(
×
421
                const char *unit,
422
                const char *filename,
423
                unsigned line,
424
                const char *section,
425
                unsigned section_line,
426
                const char *lvalue,
427
                int ltype,
428
                const char *rvalue,
429
                void *data,
430
                void *userdata) {
431

432
        Transfer *t = ASSERT_PTR(data);
×
433
        int r;
×
434

435
        assert(rvalue);
×
436

437
        r = sd_id128_from_string(rvalue, &t->partition_uuid);
×
438
        if (r < 0) {
×
439
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
440
                           "Failed to parse partition UUID, ignoring: %s", rvalue);
441
                return 0;
×
442
        }
443

444
        t->partition_uuid_set = true;
×
445
        return 0;
×
446
}
447

448
static int config_parse_partition_flags(
×
449
                const char *unit,
450
                const char *filename,
451
                unsigned line,
452
                const char *section,
453
                unsigned section_line,
454
                const char *lvalue,
455
                int ltype,
456
                const char *rvalue,
457
                void *data,
458
                void *userdata) {
459

460
        Transfer *t = ASSERT_PTR(data);
×
461
        int r;
×
462

463
        assert(rvalue);
×
464

465
        r = safe_atou64(rvalue, &t->partition_flags);
×
466
        if (r < 0) {
×
467
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
468
                           "Failed to parse partition flags, ignoring: %s", rvalue);
469
                return 0;
×
470
        }
471

472
        t->partition_flags_set = true;
×
473
        return 0;
×
474
}
475

476
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
604✔
477
        assert(t);
604✔
478

479
        /* Requisite feature disabled -> transfer disabled */
480
        STRV_FOREACH(id, t->requisite_features) {
604✔
481
                Feature *f = hashmap_get(known_features, *id);
×
482
                if (!f || !f->enabled) /* missing features are implicitly disabled */
×
483
                        return false;
484
        }
485

486
        /* No features defined -> transfer implicitly enabled */
487
        if (strv_isempty(t->features))
604✔
488
                return true;
489

490
        /* At least one feature enabled -> transfer enabled */
491
        STRV_FOREACH(id, t->features) {
190✔
492
                Feature *f = hashmap_get(known_features, *id);
100✔
493
                if (f && f->enabled)
100✔
494
                        return true;
495
        }
496

497
        /* All listed features disabled -> transfer disabled */
498
        return false;
499
}
500

501
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
604✔
502
        assert(t);
604✔
503

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

535
        _cleanup_free_ char *filename = NULL;
604✔
536
        char *e;
604✔
537
        int r;
604✔
538

539
        assert(path);
604✔
540
        assert(dirs);
604✔
541

542
        r = path_extract_filename(path, &filename);
604✔
543
        if (r < 0)
604✔
544
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
545

546
        r = config_parse_many_full(
1,812✔
547
                        STRV_MAKE_CONST(path),
604✔
548
                        dirs,
549
                        strjoina(filename, ".d"),
3,020✔
550
                        arg_root,
551
                        /* root_fd= */ -EBADF,
552
                        "Transfer\0"
553
                        "Source\0"
554
                        "Target\0",
555
                        config_item_table_lookup, table,
556
                        CONFIG_PARSE_WARN,
557
                        /* userdata= */ NULL,
558
                        /* stats_by_path= */ NULL,
559
                        /* drop_in_files= */ NULL);
560
        if (r < 0)
604✔
561
                return r;
562

563
        e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
604✔
564
        *e = 0; /* Remove the file extension */
604✔
565
        t->id = TAKE_PTR(filename);
604✔
566

567
        t->enabled = transfer_decide_if_enabled(t, known_features);
604✔
568

569
        if (!RESOURCE_IS_SOURCE(t->source.type))
604✔
570
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
571
                                  "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
572

573
        if (t->target.type < 0) {
604✔
574
                switch (t->source.type) {
×
575

576
                case RESOURCE_URL_FILE:
×
577
                case RESOURCE_REGULAR_FILE:
578
                        t->target.type =
×
579
                                t->target.path && path_startswith(t->target.path, "/dev/") ?
×
580
                                RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
×
581
                        break;
×
582

583
                case RESOURCE_URL_TAR:
×
584
                case RESOURCE_TAR:
585
                case RESOURCE_DIRECTORY:
586
                        t->target.type = RESOURCE_DIRECTORY;
×
587
                        break;
×
588

589
                case RESOURCE_SUBVOLUME:
×
590
                        t->target.type = RESOURCE_SUBVOLUME;
×
591
                        break;
×
592

593
                default:
×
594
                        assert_not_reached();
×
595
                }
596
        }
597

598
        if (!RESOURCE_IS_TARGET(t->target.type))
604✔
599
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
600
                                  "Target Type= must be one of partition, regular-file, directory, subvolume.");
601

602
        if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
604✔
603
             !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
504✔
604
            (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
604✔
605
             !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
100✔
606
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
607
                                  "Target type '%s' is incompatible with source type '%s', refusing.",
608
                                  resource_type_to_string(t->target.type), resource_type_to_string(t->source.type));
609

610
        if (!t->source.path && !t->source.path_auto)
604✔
611
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
612
                                  "Source specification lacks Path=.");
613

614
        if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !arg_transfer_source)
604✔
615
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
616
                                  "PathRelativeTo=explicit requires --transfer-source= to be specified.");
617

618
        if (t->target.path_relative_to == PATH_RELATIVE_TO_EXPLICIT)
604✔
619
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
620
                                  "PathRelativeTo=explicit can only be used in source specifications.");
621

622
        if (t->source.path) {
604✔
623
                if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
604✔
624
                        if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
564✔
625
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
626
                                                  "Source path is not a normalized, absolute path: %s", t->source.path);
627

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

637
        if (strv_isempty(t->source.patterns))
604✔
638
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
639
                                  "Source specification lacks MatchPattern=.");
640

641
        if (!t->target.path && !t->target.path_auto)
604✔
642
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
643
                                  "Target specification lacks Path= field.");
644

645
        if (t->target.path &&
604✔
646
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
604✔
647
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
648
                                  "Target path is not a normalized, absolute path: %s", t->target.path);
649

650
        if (strv_isempty(t->target.patterns)) {
604✔
651
                log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification.");
×
652
                strv_free(t->target.patterns);
×
653
                t->target.patterns = strv_copy(t->source.patterns);
×
654
                if (!t->target.patterns)
×
655
                        return log_oom();
×
656
        }
657

658
        if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
604✔
659
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
660
                                  "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
661

662
        /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
663
        if (t->instances_max == 0)
604✔
664
                t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
204✔
665

666
        return 0;
667
}
668

669
int transfer_resolve_paths(
604✔
670
                Transfer *t,
671
                const char *root,
672
                const char *node) {
673

674
        int r;
604✔
675

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

681
        assert(t);
604✔
682

683
        r = resource_resolve_path(&t->source, root, arg_transfer_source, node);
604✔
684
        if (r < 0)
604✔
685
                return r;
686

687
        r = resource_resolve_path(&t->target, root, /* relative_to_directory= */ NULL, node);
604✔
688
        if (r < 0)
604✔
689
                return r;
×
690

691
        return 0;
692
}
693

694
static void transfer_remove_temporary(Transfer *t) {
102✔
695
        _cleanup_closedir_ DIR *d = NULL;
102✔
696
        int r;
102✔
697

698
        assert(t);
102✔
699

700
        if (!t->remove_temporary)
102✔
701
                return;
702

703
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
102✔
704
                return;
705

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

708
        d = opendir(t->target.path);
70✔
709
        if (!d) {
70✔
710
                if (errno == ENOENT)
×
711
                        return;
712

713
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
×
714
                return;
×
715
        }
716

717
        for (;;) {
266✔
718
                struct dirent *de;
266✔
719

720
                errno = 0;
266✔
721
                de = readdir_no_dot(d);
266✔
722
                if (!de) {
266✔
723
                        if (errno != 0)
70✔
724
                                log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
×
725
                        break;
70✔
726
                }
727

728
                if (!startswith(de->d_name, ".#"))
196✔
729
                        continue;
196✔
730

731
                r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
×
732
                if (r == -ENOENT)
×
733
                        continue;
×
734
                if (r < 0) {
×
735
                        log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
×
736
                        continue;
×
737
                }
738

739
                log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
×
740
        }
741
}
742

743
int transfer_vacuum(
102✔
744
                Transfer *t,
745
                uint64_t space,
746
                const char *extra_protected_version) {
747

748
        uint64_t instances_max, limit;
102✔
749
        int r, count = 0;
102✔
750

751
        assert(t);
102✔
752

753
        transfer_remove_temporary(t);
102✔
754

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

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

772
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
102✔
773
                _cleanup_free_ char *patterns = NULL;
32✔
774
                uint64_t rm, remain;
32✔
775

776
                patterns = strv_join(t->target.patterns, "|");
32✔
777
                if (!patterns)
32✔
778
                        (void) log_oom_debug();
×
779

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

783
                if (t->target.n_empty + t->target.n_instances < 2)
32✔
784
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
785
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s, refusing.",
786
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
787
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
788
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
789
                                               strempty(patterns),
790
                                               !isempty(patterns) ? "'" : "");
791
                if (space > t->target.n_empty + t->target.n_instances)
32✔
792
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
793
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s for operation.",
794
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
795
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
796
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
797
                                               strempty(patterns),
798
                                               !isempty(patterns) ? "'" : "");
799
                if (space == t->target.n_empty + t->target.n_instances)
32✔
800
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
801
                                               "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.",
802
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
803
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
804

805
                rm = LESS_BY(space, t->target.n_empty);
32✔
806
                remain = LESS_BY(t->target.n_instances, rm);
32✔
807
                limit = MIN(limit, remain);
32✔
808
        }
809

810
        while (t->target.n_instances > limit) {
152✔
811
                Instance *oldest;
50✔
812
                size_t p = t->target.n_instances - 1;
50✔
813

814
                for (;;) {
50✔
815
                        oldest = t->target.instances[p];
50✔
816
                        assert(oldest);
50✔
817

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

823
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
824
                        if (p == 0) {
×
825
                                oldest = NULL;
826
                                break;
827
                        }
828

829
                        p--;
×
830
                }
831

832
                if (!oldest) /* Nothing more to remove */
50✔
833
                        break;
834

835
                assert(oldest->resource);
50✔
836

837
                log_info("%s Removing %s '%s' (%s).",
100✔
838
                         glyph(GLYPH_RECYCLING),
839
                         space == UINT64_MAX ? "disabled" : "old",
840
                         oldest->path,
841
                         resource_type_to_string(oldest->resource->type));
842

843
                switch (t->target.type) {
50✔
844

845
                case RESOURCE_REGULAR_FILE:
30✔
846
                case RESOURCE_DIRECTORY:
847
                case RESOURCE_SUBVOLUME:
848
                        r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
30✔
849
                        if (r < 0 && r != -ENOENT)
30✔
850
                                return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
×
851

852
                        (void) rmdir_parents(oldest->path, t->target.path);
30✔
853

854
                        break;
30✔
855

856
                case RESOURCE_PARTITION: {
20✔
857
                        PartitionInfo pinfo = oldest->partition_info;
20✔
858

859
                        /* label "_empty" means "no contents" for our purposes */
860
                        pinfo.label = (char*) "_empty";
20✔
861

862
                        r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
20✔
863
                        if (r < 0)
20✔
864
                                return r;
×
865

866
                        t->target.n_empty++;
20✔
867
                        break;
20✔
868
                }
869

870
                default:
×
871
                        assert_not_reached();
×
872
                }
873

874
                instance_free(oldest);
50✔
875
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
50✔
876
                t->target.n_instances--;
50✔
877

878
                count++;
50✔
879
        }
880

881
        return count;
882
}
883

884
static void compile_pattern_fields(
74✔
885
                const Transfer *t,
886
                const Instance *i,
887
                InstanceMetadata *ret) {
888

889
        assert(t);
74✔
890
        assert(i);
74✔
891
        assert(ret);
74✔
892

893
        *ret = (InstanceMetadata) {
148✔
894
                .version = i->metadata.version,
74✔
895

896
                /* We generally prefer explicitly configured values for the transfer over those automatically
897
                 * derived from the source instance. Also, if the source is a tar archive, then let's not
898
                 * patch mtime/mode and use the one embedded in the tar file */
899
                .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
74✔
900
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
74✔
901
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
74✔
902
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
74✔
903
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
74✔
904
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
74✔
905
                .size = i->metadata.size,
74✔
906
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
74✔
907
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
60✔
908
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
74✔
909
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
60✔
910
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
74✔
911
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
74✔
912
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
74✔
913
                .sha256sum_set = i->metadata.sha256sum_set,
74✔
914
        };
915

916
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
74✔
917
}
74✔
918

919
typedef struct CalloutContext {
920
        const Transfer *transfer;
921
        const Instance *instance;
922
        TransferProgress callback;
923
        PidRef pid;
924
        const char *name;
925
        int helper_errno;
926
        void* userdata;
927
} CalloutContext;
928

929
static CalloutContext *callout_context_free(CalloutContext *ctx) {
74✔
930
        if (!ctx)
74✔
931
                return NULL;
932

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

936
        return mfree(ctx);
74✔
937
}
938

939
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
148✔
940

941
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
74✔
942
                               const char *name, void* userdata, CalloutContext **ret) {
943
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
74✔
944

945
        assert(t);
74✔
946
        assert(i);
74✔
947
        assert(cb);
74✔
948

949
        ctx = new(CalloutContext, 1);
74✔
950
        if (!ctx)
74✔
951
                return -ENOMEM;
952

953
        *ctx = (CalloutContext) {
74✔
954
                .transfer = t,
955
                .instance = i,
956
                .callback = cb,
957
                .pid = PIDREF_NULL,
958
                .name = name,
959
                .userdata = userdata,
960
        };
961

962
        *ret = TAKE_PTR(ctx);
74✔
963
        return 0;
74✔
964
}
965

966
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
74✔
967
        CalloutContext *ctx = ASSERT_PTR(userdata);
74✔
968
        int r;
74✔
969

970
        assert(s);
74✔
971
        assert(si);
74✔
972
        assert(ctx);
74✔
973

974
        if (si->si_code == CLD_EXITED) {
74✔
975
                if (si->si_status == EXIT_SUCCESS) {
74✔
976
                        r = 0;
74✔
977
                        log_debug("%s succeeded.", ctx->name);
74✔
978
                } else if (ctx->helper_errno != 0) {
×
979
                        r = -ctx->helper_errno;
×
980
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
×
981
                } else {
982
                        r = -EPROTO;
×
983
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
984
                }
985
        } else {
986
                r = -EPROTO;
×
987
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
988
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
989
                else
990
                        log_error("%s failed due to unknown reason.", ctx->name);
×
991
        }
992

993
        return sd_event_exit(sd_event_source_get_event(s), r);
74✔
994
}
995

996
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
143✔
997
        CalloutContext *ctx = ASSERT_PTR(userdata);
143✔
998
        int r;
143✔
999

1000
        assert(fd >= 0);
143✔
1001

1002
        _cleanup_free_ char *buf = NULL;
143✔
1003
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
143✔
1004
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
143✔
1005
        if (r == -EAGAIN)
143✔
1006
                return 0;
1007
        if (r < 0)
143✔
1008
                return r;
1009

1010
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
143✔
1011
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1012
                return 0;
×
1013
        }
1014

1015
        char *errno_str = find_line_startswith(buf, "ERRNO=");
143✔
1016
        if (errno_str) {
143✔
1017
                truncate_nl(errno_str);
×
1018
                r = parse_errno(errno_str);
×
1019
                if (r < 0)
×
1020
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1021
                else {
1022
                        ctx->helper_errno = r;
×
1023
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
×
1024
                }
1025
        }
1026

1027
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
143✔
1028
        if (progress_str) {
143✔
1029
                truncate_nl(progress_str);
69✔
1030

1031
                int progress = parse_percent(progress_str);
69✔
1032
                if (progress < 0)
69✔
1033
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1034
                else {
1035
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
69✔
1036
                        if (r < 0)
69✔
1037
                                return r;
×
1038
                }
1039
        }
1040

1041
        return 0;
1042
}
1043

1044
static int run_callout(
74✔
1045
                const char *name,
1046
                char *cmdline[],
1047
                const Transfer *transfer,
1048
                const Instance *instance,
1049
                TransferProgress callback,
1050
                void *userdata) {
1051

1052
        int r;
74✔
1053

1054
        assert(name);
74✔
1055
        assert(cmdline);
74✔
1056
        assert(cmdline[0]);
74✔
1057

1058
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1059
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
74✔
1060
        if (r < 0)
74✔
1061
                return log_oom();
×
1062

1063
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
74✔
1064
        r = sd_event_new(&event);
74✔
1065
        if (r < 0)
74✔
1066
                return log_error_errno(r, "Failed to create event: %m");
×
1067

1068
        /* Kill the helper & return an error if we get interrupted by a signal */
1069
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
74✔
1070
        if (r < 0)
74✔
1071
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1072
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
74✔
1073
        if (r < 0)
74✔
1074
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1075

1076
        _cleanup_free_ char *bind_name = NULL;
74✔
1077
        r = notify_socket_prepare(
74✔
1078
                        event,
1079
                        SD_EVENT_PRIORITY_NORMAL - 5,
1080
                        helper_on_notify,
1081
                        ctx,
1082
                        &bind_name);
1083
        if (r < 0)
74✔
1084
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1085

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

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

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

1110
        /* Process events until the helper quits */
1111
        return sd_event_loop(event);
74✔
1112
}
1113

1114
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
74✔
1115
        _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
74✔
1116
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
74✔
1117
        const char *where = NULL;
74✔
1118
        InstanceMetadata f;
74✔
1119
        Instance *existing;
74✔
1120
        int r;
74✔
1121

1122
        assert(t);
74✔
1123
        assert(i);
74✔
1124
        assert(i->resource == &t->source);
74✔
1125
        assert(cb);
74✔
1126

1127
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1128
        existing = resource_find_instance(&t->target, i->metadata.version);
74✔
1129
        if (existing) {
74✔
1130
                log_info("No need to acquire '%s', already installed.", i->path);
×
1131
                return 0;
×
1132
        }
1133

1134
        assert(!t->final_path);
74✔
1135
        assert(!t->temporary_path);
74✔
1136
        assert(!strv_isempty(t->target.patterns));
74✔
1137

1138
        /* Format the target name using the first pattern specified */
1139
        compile_pattern_fields(t, i, &f);
74✔
1140
        r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
74✔
1141
        if (r < 0)
74✔
1142
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1143

1144
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1145

1146
                if (!path_is_safe(formatted_pattern))
46✔
1147
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1148

1149
                t->final_path = path_join(t->target.path, formatted_pattern);
46✔
1150
                if (!t->final_path)
46✔
1151
                        return log_oom();
×
1152

1153
                r = mkdir_parents(t->final_path, 0755);
46✔
1154
                if (r < 0)
46✔
1155
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1156

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

1161
                where = t->final_path;
46✔
1162
        }
1163

1164
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1165
                r = gpt_partition_label_valid(formatted_pattern);
28✔
1166
                if (r < 0)
28✔
1167
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1168
                if (!r)
28✔
1169
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1170

1171
                r = find_suitable_partition(
28✔
1172
                                t->target.path,
28✔
1173
                                i->metadata.size,
1174
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
28✔
1175
                                &t->partition_info);
1176
                if (r < 0)
28✔
1177
                        return r;
1178

1179
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
28✔
1180
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
28✔
1181

1182
                where = t->partition_info.device;
28✔
1183
        }
1184

1185
        assert(where);
74✔
1186

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

1189
        if (RESOURCE_IS_URL(i->resource->type)) {
74✔
1190
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1191
                 * download. */
1192

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

1196
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
8✔
1197
                if (!digest)
8✔
1198
                        return log_oom();
×
1199
        }
1200

1201
        switch (i->resource->type) { /* Source */
74✔
1202

1203
        case RESOURCE_REGULAR_FILE:
56✔
1204

1205
                switch (t->target.type) { /* Target */
56✔
1206

1207
                case RESOURCE_REGULAR_FILE:
32✔
1208

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

1214
                        r = run_callout("(sd-import-raw)",
64✔
1215
                                        STRV_MAKE(
32✔
1216
                                               SYSTEMD_IMPORT_PATH,
1217
                                               "raw",
1218
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1219
                                               arg_sync ? "--sync=yes" : "--sync=no",
1220
                                               i->path,
1221
                                               t->temporary_path),
1222
                                        t, i, cb, userdata);
1223
                        break;
56✔
1224

1225
                case RESOURCE_PARTITION:
24✔
1226

1227
                        /* regular file → partition */
1228

1229
                        r = run_callout("(sd-import-raw)",
48✔
1230
                                        STRV_MAKE(
24✔
1231
                                               SYSTEMD_IMPORT_PATH,
1232
                                               "raw",
1233
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1234
                                               "--offset", offset,
1235
                                               "--size-max", max_size,
1236
                                               arg_sync ? "--sync=yes" : "--sync=no",
1237
                                               i->path,
1238
                                               t->target.path),
1239
                                        t, i, cb, userdata);
1240
                        break;
1241

1242
                default:
×
1243
                        assert_not_reached();
×
1244
                }
1245

1246
                break;
56✔
1247

1248
        case RESOURCE_DIRECTORY:
10✔
1249
        case RESOURCE_SUBVOLUME:
1250
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1251

1252
                /* directory/subvolume → directory/subvolume */
1253

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

1266
        case RESOURCE_TAR:
×
1267
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1268

1269
                /* tar → directory/subvolume */
1270

1271
                r = run_callout("(sd-import-tar)",
×
1272
                                STRV_MAKE(
×
1273
                                       SYSTEMD_IMPORT_PATH,
1274
                                       "tar",
1275
                                       "--direct",          /* just untar the specified file, don't do anything else */
1276
                                       arg_sync ? "--sync=yes" : "--sync=no",
1277
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1278
                                       i->path,
1279
                                       t->temporary_path),
1280
                                t, i, cb, userdata);
1281
                break;
1282

1283
        case RESOURCE_URL_FILE:
4✔
1284

1285
                switch (t->target.type) {
4✔
1286

1287
                case RESOURCE_REGULAR_FILE:
×
1288

1289
                        /* url file → regular file */
1290

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

1303
                case RESOURCE_PARTITION:
4✔
1304

1305
                        /* url file → partition */
1306

1307
                        r = run_callout("(sd-pull-raw)",
8✔
1308
                                        STRV_MAKE(
4✔
1309
                                               SYSTEMD_PULL_PATH,
1310
                                               "raw",
1311
                                               "--direct",              /* just download the specified URL, don't download anything else */
1312
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1313
                                               "--offset", offset,
1314
                                               "--size-max", max_size,
1315
                                               arg_sync ? "--sync=yes" : "--sync=no",
1316
                                               i->path,
1317
                                               t->target.path),
1318
                                        t, i, cb, userdata);
1319
                        break;
1320

1321
                default:
×
1322
                        assert_not_reached();
×
1323
                }
1324

1325
                break;
4✔
1326

1327
        case RESOURCE_URL_TAR:
4✔
1328
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
4✔
1329

1330
                r = run_callout("(sd-pull-tar)",
8✔
1331
                                STRV_MAKE(
8✔
1332
                                       SYSTEMD_PULL_PATH,
1333
                                       "tar",
1334
                                       "--direct",          /* just download the specified URL, don't download anything else */
1335
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1336
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1337
                                       arg_sync ? "--sync=yes" : "--sync=no",
1338
                                       i->path,
1339
                                       t->temporary_path),
1340
                                t, i, cb, userdata);
1341
                break;
1342

1343
        default:
×
1344
                assert_not_reached();
×
1345
        }
1346
        if (r < 0)
74✔
1347
                return r;
1348

1349
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
74✔
1350
                bool need_sync = false;
46✔
1351
                assert(t->temporary_path);
46✔
1352

1353
                /* Apply file attributes if set */
1354
                if (f.mtime != USEC_INFINITY) {
46✔
1355
                        struct timespec ts;
42✔
1356

1357
                        timespec_store(&ts, f.mtime);
42✔
1358

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

1362
                        need_sync = true;
42✔
1363
                }
1364

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

1373
                        need_sync = true;
1374
                }
1375

1376
                /* Synchronize */
1377
                if (arg_sync && need_sync) {
46✔
1378
                        if (t->target.type == RESOURCE_REGULAR_FILE)
42✔
1379
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
32✔
1380
                        else {
1381
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
10✔
1382
                                r = syncfs_path(AT_FDCWD, t->temporary_path);
10✔
1383
                        }
1384
                        if (r < 0)
42✔
1385
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
×
1386
                }
1387

1388
                t->install_read_only = f.read_only;
46✔
1389
        }
1390

1391
        if (t->target.type == RESOURCE_PARTITION) {
74✔
1392
                free_and_replace(t->partition_info.label, formatted_pattern);
28✔
1393
                t->partition_change = PARTITION_LABEL;
28✔
1394

1395
                if (f.partition_uuid_set) {
28✔
1396
                        t->partition_info.uuid = f.partition_uuid;
×
1397
                        t->partition_change |= PARTITION_UUID;
×
1398
                }
1399

1400
                if (f.partition_flags_set) {
28✔
1401
                        t->partition_info.flags = f.partition_flags;
×
1402
                        t->partition_change |= PARTITION_FLAGS;
×
1403
                }
1404

1405
                if (f.no_auto >= 0) {
28✔
1406
                        t->partition_info.no_auto = f.no_auto;
×
1407
                        t->partition_change |= PARTITION_NO_AUTO;
×
1408
                }
1409

1410
                if (f.read_only >= 0) {
28✔
1411
                        t->partition_info.read_only = f.read_only;
×
1412
                        t->partition_change |= PARTITION_READ_ONLY;
×
1413
                }
1414

1415
                if (f.growfs >= 0) {
28✔
1416
                        t->partition_info.growfs = f.growfs;
×
1417
                        t->partition_change |= PARTITION_GROWFS;
×
1418
                }
1419
        }
1420

1421
        /* For regular file cases the only step left is to install the file in place, which install_file()
1422
         * will do via rename(). For partition cases the only step left is to update the partition table,
1423
         * which is done at the same place. */
1424

1425
        log_info("Successfully acquired '%s'.", i->path);
74✔
1426
        return 0;
1427
}
1428

1429
int transfer_install_instance(
74✔
1430
                Transfer *t,
1431
                Instance *i,
1432
                const char *root) {
1433

1434
        int r;
74✔
1435

1436
        assert(t);
74✔
1437
        assert(i);
74✔
1438
        assert(i->resource);
74✔
1439
        assert(t == container_of(i->resource, Transfer, source));
74✔
1440

1441
        if (t->temporary_path) {
74✔
1442
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
46✔
1443
                assert(t->final_path);
46✔
1444

1445
                r = install_file(AT_FDCWD, t->temporary_path,
138✔
1446
                                 AT_FDCWD, t->final_path,
1447
                                 INSTALL_REPLACE|
46✔
1448
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
92✔
1449
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
46✔
1450
                if (r < 0)
46✔
1451
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1452

1453
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
46✔
1454
                         i->path,
1455
                         resource_type_to_string(i->resource->type),
1456
                         t->final_path,
1457
                         resource_type_to_string(t->target.type));
1458

1459
                t->temporary_path = mfree(t->temporary_path);
46✔
1460
        }
1461

1462
        if (t->partition_change != 0) {
74✔
1463
                assert(t->target.type == RESOURCE_PARTITION);
28✔
1464

1465
                r = patch_partition(
56✔
1466
                                t->target.path,
28✔
1467
                                &t->partition_info,
28✔
1468
                                t->partition_change);
1469
                if (r < 0)
28✔
1470
                        return r;
1471

1472
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
28✔
1473
                         i->path,
1474
                         resource_type_to_string(i->resource->type),
1475
                         t->partition_info.device,
1476
                         resource_type_to_string(t->target.type));
1477
        }
1478

1479
        if (t->current_symlink) {
74✔
1480
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
14✔
1481
                const char *link_path, *link_target;
14✔
1482
                bool resolve_link_path = false;
14✔
1483

1484
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
14✔
1485

1486
                        assert(t->target.path);
14✔
1487

1488
                        if (path_is_absolute(t->current_symlink)) {
14✔
1489
                                link_path = t->current_symlink;
1490
                                resolve_link_path = true;
1491
                        } else {
1492
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1493
                                if (!buf)
×
1494
                                        return log_oom();
×
1495

1496
                                link_path = buf;
1497
                        }
1498

1499
                        link_target = t->final_path;
14✔
1500

1501
                } else if (t->target.type == RESOURCE_PARTITION) {
×
1502

1503
                        assert(path_is_absolute(t->current_symlink));
×
1504

1505
                        link_path = t->current_symlink;
×
1506
                        link_target = t->partition_info.device;
×
1507

1508
                        resolve_link_path = true;
×
1509
                } else
1510
                        assert_not_reached();
×
1511

1512
                if (resolve_link_path && root) {
14✔
1513
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
1514
                        if (r < 0)
×
1515
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1516

1517
                        link_path = resolved;
×
1518
                }
1519

1520
                if (link_target) {
14✔
1521
                        r = path_extract_directory(link_path, &parent);
14✔
1522
                        if (r < 0)
14✔
1523
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1524

1525
                        r = path_make_relative(parent, link_target, &relative);
14✔
1526
                        if (r < 0)
14✔
1527
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1528

1529
                        r = symlink_atomic(relative, link_path);
14✔
1530
                        if (r < 0)
14✔
1531
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1532
                                                       link_path,
1533
                                                       glyph(GLYPH_ARROW_RIGHT),
1534
                                                       relative);
1535

1536
                        log_info("Updated symlink '%s' %s '%s'.",
14✔
1537
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1538
                }
1539
        }
1540

1541
        return 0;
1542
}
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