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

systemd / systemd / 25705508282

11 May 2026 11:07PM UTC coverage: 72.65% (+0.1%) from 72.511%
25705508282

push

github

bluca
firstboot,sysinstall,hostnamed: always show FANCY_NAME=

This makes sure that whenever we want to show the OS name we can show
the fancy name. Thus this moves the escaping/validation of the fancy
name out of hostnamed into generic code, and then makes use of it in
sysinstall,firstboot,prompt-util.

17 of 36 new or added lines in 6 files covered. (47.22%)

2673 existing lines in 72 files now uncovered.

327104 of 450245 relevant lines covered (72.65%)

1200575.51 hits per line

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

71.73
/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 "web-util.h"
45

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

49
Transfer* transfer_free(Transfer *t) {
3,404✔
50
        if (!t)
3,404✔
51
                return NULL;
52

53
        free(t->temporary_partial_path);
3,404✔
54
        free(t->temporary_pending_path);
3,404✔
55

56
        free(t->id);
3,404✔
57

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

63
        strv_free(t->features);
3,404✔
64
        strv_free(t->requisite_features);
3,404✔
65

66
        strv_free(t->changelog);
3,404✔
67
        strv_free(t->appstream);
3,404✔
68

69
        partition_info_destroy(&t->partition_info);
3,404✔
70
        free(t->final_partition_label);
3,404✔
71

72
        resource_destroy(&t->source);
3,404✔
73
        resource_destroy(&t->target);
3,404✔
74

75
        return mfree(t);
3,404✔
76
}
77

78
Transfer* transfer_new(Context *ctx) {
3,404✔
79
        Transfer *t;
3,404✔
80

81
        t = new(Transfer, 1);
3,404✔
82
        if (!t)
3,404✔
83
                return NULL;
84

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

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

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

102
                .partition_info = PARTITION_INFO_NULL,
103

104
                .context = ctx,
105
        };
106

107
        return t;
3,404✔
108
}
109

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

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

126
        assert(rvalue);
×
127

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

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

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

145
        return 0;
146
}
147

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

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

164
        assert(rvalue);
×
165

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

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

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

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

197
        assert(rvalue);
×
198

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

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

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

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

221
        return 0;
222
}
223

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

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

240
        assert(rvalue);
566✔
241

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

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

253
        return free_and_replace(*current_symlink, resolved);
566✔
254
}
255

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

268
        uint64_t *instances_max = data, i;
2,264✔
269
        int r;
2,264✔
270

271
        assert(rvalue);
2,264✔
272
        assert(data);
2,264✔
273

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

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

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

293
        return 0;
294
}
295

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

308
        char ***patterns = ASSERT_PTR(data);
6,808✔
309
        int r;
6,808✔
310

311
        assert(rvalue);
6,808✔
312

313
        if (isempty(rvalue)) {
6,808✔
314
                *patterns = strv_free(*patterns);
×
315
                return 0;
×
316
        }
317

318
        for (;;) {
22,688✔
319
                _cleanup_free_ char *word = NULL, *resolved = NULL;
7,940✔
320

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

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

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

341
                r = strv_consume(patterns, TAKE_PTR(resolved));
7,940✔
342
                if (r < 0)
7,940✔
343
                        return log_oom();
×
344
        }
345

346
        strv_uniq(*patterns);
6,808✔
347
        return 0;
6,808✔
348
}
349

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

365
        assert(rvalue);
6,808✔
366

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

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

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

384
        rr->path_auto = false;
6,808✔
385
        return free_and_replace(rr->path, resolved);
6,808✔
386
}
387

388
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
6,808✔
389

390
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
1,698✔
391
                                             PATH_RELATIVE_TO_ROOT);
392

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

405
        Resource *rr = ASSERT_PTR(data);
1,140✔
406
        int r;
1,140✔
407

408
        assert(rvalue);
1,140✔
409

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

417
        rr->partition_type_set = true;
1,140✔
418
        return 0;
1,140✔
419
}
420

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

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

436
        assert(rvalue);
×
437

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

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

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

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

464
        assert(rvalue);
×
465

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

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

477
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
3,404✔
478
        assert(t);
3,404✔
479

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

487
        /* No features defined -> transfer implicitly enabled */
488
        if (strv_isempty(t->features))
3,404✔
489
                return true;
490

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

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

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

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

536
        _cleanup_free_ char *filename = NULL;
3,404✔
537
        char *e;
3,404✔
538
        int r;
3,404✔
539

540
        assert(path);
3,404✔
541
        assert(dirs);
3,404✔
542

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

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

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

568
        t->enabled = transfer_decide_if_enabled(t, known_features);
3,404✔
569

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

574
        if (t->target.type < 0) {
3,404✔
575
                switch (t->source.type) {
×
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

667
        return 0;
668
}
669

670
int transfer_resolve_paths(
3,404✔
671
                Transfer *t,
672
                const char *root,
673
                const char *node) {
674

675
        int r;
3,404✔
676

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

682
        assert(t);
3,404✔
683

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

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

692
        return 0;
693
}
694

695
static void transfer_remove_temporary(Transfer *t) {
552✔
696
        _cleanup_closedir_ DIR *d = NULL;
1,104✔
697
        int r;
552✔
698

699
        assert(t);
552✔
700

701
        if (!t->remove_temporary)
552✔
702
                return;
703

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

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

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

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

718
        for (;;) {
1,408✔
719
                struct dirent *de;
1,408✔
720

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

729
                if (!startswith(de->d_name, ".#"))
1,032✔
730
                        continue;
1,032✔
731

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

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

744
static int transfer_instance_vacuum(
256✔
745
                Transfer *t,
746
                Instance *instance) {
747
        int r;
256✔
748

749
        assert(t);
256✔
750
        assert(instance);
256✔
751

752
        switch (t->target.type) {
256✔
753

754
        case RESOURCE_REGULAR_FILE:
144✔
755
        case RESOURCE_DIRECTORY:
756
        case RESOURCE_SUBVOLUME:
757
                r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
144✔
758
                if (r < 0 && r != -ENOENT)
144✔
759
                        return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
×
760

761
                (void) rmdir_parents(instance->path, t->target.path);
144✔
762

763
                break;
144✔
764

765
        case RESOURCE_PARTITION: {
112✔
766
                PartitionInfo pinfo = instance->partition_info;
112✔
767
                PartitionChange change = PARTITION_LABEL;
112✔
768

769
                /* label "_empty" means "no contents" for our purposes */
770
                pinfo.label = (char*) "_empty";
112✔
771

772
                /* If the partition had a derived partial/pending type UUID, restore the original
773
                 * partition type so that the slot is properly recognized as empty in subsequent
774
                 * scans. */
775
                if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
112✔
776
                        pinfo.type = t->target.partition_type.uuid;
16✔
777
                        change |= PARTITION_TYPE;
16✔
778
                }
779

780
                log_debug("Resetting partition '%s' to empty.", pinfo.device);
112✔
781
                r = patch_partition(t->target.path, &pinfo, change);
112✔
782
                if (r < 0)
112✔
783
                        return r;
×
784

785
                t->target.n_empty++;
112✔
786
                break;
112✔
787
        }
788

789
        default:
×
790
                assert_not_reached();
×
791
        }
792

793
        return 0;
794
}
795

796
int transfer_vacuum(
552✔
797
                Transfer *t,
798
                uint64_t space,
799
                const char *extra_protected_version) {
800

801
        uint64_t instances_max, limit;
552✔
802
        int r, count = 0;
552✔
803

804
        assert(t);
552✔
805

806
        transfer_remove_temporary(t);
552✔
807

808
        /* First, remove any partial or pending instances (unless protected) */
809
        for (size_t i = 0; i < t->target.n_instances;) {
1,880✔
810
                Instance *instance = t->target.instances[i];
776✔
811

812
                assert(instance);
776✔
813

814
                if (!instance->is_pending && !instance->is_partial) {
776✔
815
                        i++;
760✔
816
                        continue;
760✔
817
                }
818

819
                /* If this is pending and listed among the protected versions, then let's not remove it.
820
                 * In future, we will also want to keep partial protected versions, but that’s only useful
821
                 * once we support resuming downloads. */
822
                if (instance->is_pending &&
24✔
823
                    (strv_contains(t->protected_versions, instance->metadata.version) ||
16✔
824
                     (extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) {
×
UNCOV
825
                        log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version);
×
UNCOV
826
                        i++;
×
827
                        continue;
×
828
                }
829

830
                assert(instance->resource);
16✔
831

832
                log_info("%s Removing old %s '%s' (%s).",
32✔
833
                         glyph(GLYPH_RECYCLING),
834
                         instance->is_partial ? "partial" : "pending",
835
                         instance->path,
836
                         resource_type_to_string(instance->resource->type));
837

838
                r = transfer_instance_vacuum(t, instance);
16✔
839
                if (r < 0)
16✔
840
                        return 0;
841

842
                instance_free(instance);
16✔
843
                memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
16✔
844
                t->target.n_instances--;
16✔
845

846
                count++;
16✔
847
        }
848

849
        /* Second, calculate how many instances to keep, based on the instance limit — but keep at least one */
850

851
        instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
552✔
852
        assert(instances_max >= 1);
552✔
853
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
552✔
854
                limit = UINT64_MAX;
855
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
376✔
856
                limit = 0;
857
        else if (space > instances_max)
280✔
858
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
859
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
860
        else if (space == instances_max)
280✔
UNCOV
861
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
862
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
863
        else
864
                limit = instances_max - space;
280✔
865

866
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
552✔
867
                _cleanup_free_ char *patterns = NULL;
176✔
868
                uint64_t rm, remain;
176✔
869

870
                patterns = strv_join(t->target.patterns, "|");
176✔
871
                if (!patterns)
176✔
UNCOV
872
                        (void) log_oom_debug();
×
873

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

877
                if (t->target.n_empty + t->target.n_instances < 2)
176✔
UNCOV
878
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
879
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s, refusing.",
880
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
881
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
882
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
883
                                               strempty(patterns),
884
                                               !isempty(patterns) ? "'" : "");
885
                if (space > t->target.n_empty + t->target.n_instances)
176✔
UNCOV
886
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
887
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s for operation.",
888
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
889
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
890
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
891
                                               strempty(patterns),
892
                                               !isempty(patterns) ? "'" : "");
893
                if (space == t->target.n_empty + t->target.n_instances)
176✔
UNCOV
894
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
895
                                               "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.",
896
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
897
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
898

899
                rm = LESS_BY(space, t->target.n_empty);
176✔
900
                remain = LESS_BY(t->target.n_instances, rm);
176✔
901
                limit = MIN(limit, remain);
176✔
902
        }
903

904
        while (t->target.n_instances > limit) {
792✔
905
                Instance *oldest;
240✔
906
                size_t p = t->target.n_instances - 1;
240✔
907

908
                for (;;) {
240✔
909
                        oldest = t->target.instances[p];
240✔
910
                        assert(oldest);
240✔
911

912
                        /* If this is listed among the protected versions, then let's not remove it */
913
                        if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
240✔
914
                            (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
232✔
915
                                break;
916

UNCOV
917
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
UNCOV
918
                        if (p == 0) {
×
919
                                oldest = NULL;
920
                                break;
921
                        }
922

UNCOV
923
                        p--;
×
924
                }
925

926
                if (!oldest) /* Nothing more to remove */
240✔
927
                        break;
928

929
                assert(oldest->resource);
240✔
930

931
                log_info("%s Removing %s '%s' (%s).",
480✔
932
                         glyph(GLYPH_RECYCLING),
933
                         space == UINT64_MAX ? "disabled" : "old",
934
                         oldest->path,
935
                         resource_type_to_string(oldest->resource->type));
936

937
                r = transfer_instance_vacuum(t, oldest);
240✔
938
                if (r < 0)
240✔
939
                        return 0;
940

941
                instance_free(oldest);
240✔
942
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
240✔
943
                t->target.n_instances--;
240✔
944

945
                count++;
240✔
946
        }
947

948
        return count;
949
}
950

951
static void compile_pattern_fields(
704✔
952
                const Transfer *t,
953
                const Instance *i,
954
                InstanceMetadata *ret) {
955

956
        assert(t);
704✔
957
        assert(i);
704✔
958
        assert(ret);
704✔
959

960
        *ret = (InstanceMetadata) {
1,408✔
961
                .version = i->metadata.version,
704✔
962

963
                /* We generally prefer explicitly configured values for the transfer over those automatically
964
                 * derived from the source instance. Also, if the source is a tar archive, then let's not
965
                 * patch mtime/mode and use the one embedded in the tar file */
966
                .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
704✔
967
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
704✔
968
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
704✔
969
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
704✔
970
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
704✔
971
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
704✔
972
                .size = i->metadata.size,
704✔
973
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
704✔
974
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
566✔
975
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
704✔
976
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
566✔
977
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
704✔
978
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
704✔
979
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
704✔
980
                .sha256sum_set = i->metadata.sha256sum_set,
704✔
981
        };
982

983
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
704✔
984
}
704✔
985

986
typedef struct CalloutContext {
987
        const Transfer *transfer;
988
        const Instance *instance;
989
        TransferProgress callback;
990
        PidRef pid;
991
        const char *name;
992
        int helper_errno;
993
        void* userdata;
994
} CalloutContext;
995

996
static CalloutContext *callout_context_free(CalloutContext *ctx) {
352✔
997
        if (!ctx)
352✔
998
                return NULL;
999

1000
        /* We don't own any data but need to clean up the job pid */
1001
        pidref_done(&ctx->pid);
352✔
1002

1003
        return mfree(ctx);
352✔
1004
}
1005

1006
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
704✔
1007

1008
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
352✔
1009
                               const char *name, void* userdata, CalloutContext **ret) {
1010
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
352✔
1011

1012
        assert(t);
352✔
1013
        assert(i);
352✔
1014
        assert(cb);
352✔
1015
        assert(ret);
352✔
1016

1017
        ctx = new(CalloutContext, 1);
352✔
1018
        if (!ctx)
352✔
1019
                return -ENOMEM;
1020

1021
        *ctx = (CalloutContext) {
352✔
1022
                .transfer = t,
1023
                .instance = i,
1024
                .callback = cb,
1025
                .pid = PIDREF_NULL,
1026
                .name = name,
1027
                .userdata = userdata,
1028
        };
1029

1030
        *ret = TAKE_PTR(ctx);
352✔
1031
        return 0;
352✔
1032
}
1033

1034
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
352✔
1035
        CalloutContext *ctx = ASSERT_PTR(userdata);
352✔
1036
        int r;
352✔
1037

1038
        assert(s);
352✔
1039
        assert(si);
352✔
1040
        assert(ctx);
352✔
1041

1042
        if (si->si_code == CLD_EXITED) {
352✔
1043
                if (si->si_status == EXIT_SUCCESS) {
352✔
1044
                        r = 0;
344✔
1045
                        log_debug("%s succeeded.", ctx->name);
344✔
1046
                } else if (ctx->helper_errno != 0) {
8✔
1047
                        r = -ctx->helper_errno;
8✔
1048
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
8✔
1049
                } else {
UNCOV
1050
                        r = -EPROTO;
×
1051
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
1052
                }
1053
        } else {
UNCOV
1054
                r = -EPROTO;
×
1055
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
UNCOV
1056
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
1057
                else
UNCOV
1058
                        log_error("%s failed due to unknown reason.", ctx->name);
×
1059
        }
1060

1061
        return sd_event_exit(sd_event_source_get_event(s), r);
352✔
1062
}
1063

1064
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
675✔
1065
        CalloutContext *ctx = ASSERT_PTR(userdata);
675✔
1066
        int r;
675✔
1067

1068
        assert(fd >= 0);
675✔
1069

1070
        _cleanup_free_ char *buf = NULL;
675✔
1071
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
675✔
1072
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
675✔
1073
        if (r == -EAGAIN)
675✔
1074
                return 0;
1075
        if (r < 0)
675✔
1076
                return r;
1077

1078
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
675✔
UNCOV
1079
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1080
                return 0;
1081
        }
1082

1083
        char *errno_str = find_line_startswith(buf, "ERRNO=");
675✔
1084
        if (errno_str) {
675✔
1085
                truncate_nl(errno_str);
8✔
1086
                r = parse_errno(errno_str);
8✔
1087
                if (r < 0)
8✔
1088
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1089
                else {
1090
                        ctx->helper_errno = r;
8✔
1091
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
8✔
1092
                }
1093
        }
1094

1095
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
675✔
1096
        if (progress_str) {
675✔
1097
                truncate_nl(progress_str);
315✔
1098

1099
                int progress = parse_percent(progress_str);
315✔
1100
                if (progress < 0)
315✔
UNCOV
1101
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1102
                else {
1103
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
315✔
1104
                        if (r < 0)
315✔
UNCOV
1105
                                return r;
×
1106
                }
1107
        }
1108

1109
        return 0;
1110
}
1111

1112
static int run_callout(
352✔
1113
                const char *name,
1114
                char *cmdline[],
1115
                const Transfer *transfer,
1116
                const Instance *instance,
1117
                TransferProgress callback,
1118
                void *userdata) {
1119

1120
        int r;
352✔
1121

1122
        assert(name);
352✔
1123
        assert(cmdline);
352✔
1124
        assert(cmdline[0]);
352✔
1125

1126
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1127
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
352✔
1128
        if (r < 0)
352✔
UNCOV
1129
                return log_oom();
×
1130

1131
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
352✔
1132
        r = sd_event_new(&event);
352✔
1133
        if (r < 0)
352✔
UNCOV
1134
                return log_error_errno(r, "Failed to create event: %m");
×
1135

1136
        /* Kill the helper & return an error if we get interrupted by a signal */
1137
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
352✔
1138
        if (r < 0)
352✔
1139
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1140
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
352✔
1141
        if (r < 0)
352✔
UNCOV
1142
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1143

1144
        _cleanup_free_ char *bind_name = NULL;
352✔
1145
        r = notify_socket_prepare(
352✔
1146
                        event,
1147
                        SD_EVENT_PRIORITY_NORMAL - 5,
1148
                        helper_on_notify,
1149
                        ctx,
1150
                        &bind_name);
1151
        if (r < 0)
352✔
UNCOV
1152
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1153

1154
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
352✔
1155
        if (r < 0)
704✔
UNCOV
1156
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1157
        if (r == 0) {
704✔
1158
                /* Child */
1159
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
352✔
UNCOV
1160
                        log_error_errno(errno, "setenv() failed: %m");
×
1161
                        _exit(EXIT_FAILURE);
×
1162
                }
1163
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
352✔
UNCOV
1164
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
UNCOV
1165
                _exit(EXIT_FAILURE);
×
1166
        }
1167

1168
        /* Quit the loop w/ when child process exits */
1169
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
352✔
1170
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
352✔
1171
        if (r < 0)
352✔
UNCOV
1172
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1173

1174
        r = sd_event_source_set_child_process_own(exit_source, true);
352✔
1175
        if (r < 0)
352✔
UNCOV
1176
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1177

1178
        /* Process events until the helper quits */
1179
        return sd_event_loop(event);
352✔
1180
}
1181

1182
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
1183
 * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
1184
 * skipped). */
1185
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
704✔
1186
        _cleanup_free_ char *formatted_pattern = NULL;
704✔
1187
        int r;
704✔
1188

1189
        assert(t);
704✔
1190
        assert(i);
704✔
1191

1192
        assert(!t->final_path);
704✔
1193
        assert(!t->temporary_partial_path);
704✔
1194
        assert(!t->temporary_pending_path);
704✔
1195
        assert(!t->final_partition_label);
704✔
1196
        assert(!strv_isempty(t->target.patterns));
704✔
1197

1198
        /* Format the target name using the first pattern specified */
1199
        compile_pattern_fields(t, i, f);
704✔
1200
        r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
704✔
1201
        if (r < 0)
704✔
UNCOV
1202
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1203

1204
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
704✔
1205
                _cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
428✔
1206

1207
                if (!path_is_safe(formatted_pattern))
428✔
UNCOV
1208
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1209

1210
                t->final_path = path_join(t->target.path, formatted_pattern);
428✔
1211
                if (!t->final_path)
428✔
UNCOV
1212
                        return log_oom();
×
1213

1214
                /* Build the paths for the partial and pending files, which hold the resource while it’s
1215
                 * being acquired and after it’s been acquired (but before it’s moved to the final_path
1216
                 * when it’s installed).
1217
                 *
1218
                 * Split the filename off the `final_path`, then add a prefix to it for each of partial and
1219
                 * pending, then join them back on to the same directory. */
1220
                r = path_split_prefix_filename(t->final_path, &final_dir, &final_filename);
428✔
1221
                if (r < 0)
428✔
1222
                        return log_error_errno(r, "Failed to parse path: %m");
×
1223

1224
                if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
428✔
1225
                        return log_oom();
×
1226

1227
                if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
428✔
UNCOV
1228
                        return log_oom();
×
1229

1230
                t->temporary_partial_path = path_join(final_dir, partial_filename);
428✔
1231
                if (!t->temporary_partial_path)
428✔
UNCOV
1232
                        return log_oom();
×
1233

1234
                t->temporary_pending_path = path_join(final_dir, pending_filename);
428✔
1235
                if (!t->temporary_pending_path)
428✔
UNCOV
1236
                        return log_oom();
×
1237
        }
1238

1239
        if (t->target.type == RESOURCE_PARTITION) {
704✔
1240
                r = gpt_partition_label_valid(formatted_pattern);
276✔
1241
                if (r < 0)
276✔
UNCOV
1242
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1243
                if (!r)
276✔
1244
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1245

1246
                if (!t->target.partition_type_set)
276✔
UNCOV
1247
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition type must be set for partition targets.");
×
1248

1249
                /* Derive temporary partition type UUIDs for partial/pending states from the configured
1250
                 * partition type. This avoids the need for label prefixes. */
1251
                r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
276✔
1252
                if (r < 0)
276✔
UNCOV
1253
                        return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
×
1254

1255
                r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
276✔
1256
                if (r < 0)
276✔
UNCOV
1257
                        return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
×
1258

1259
                t->final_partition_label = TAKE_PTR(formatted_pattern);
276✔
1260
        }
1261

1262
        return 0;
1263
}
1264

1265
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
352✔
1266
        _cleanup_free_ char *digest = NULL;
352✔
1267
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
352✔
1268
        const char *where = NULL;
352✔
1269
        Instance *existing;
352✔
1270
        int r;
352✔
1271

1272
        assert(t);
352✔
1273
        assert(i);
352✔
1274
        assert(f);
352✔
1275
        assert(i->resource == &t->source);
352✔
1276
        assert(cb);
352✔
1277

1278
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1279
        existing = resource_find_instance(&t->target, i->metadata.version);
352✔
1280
        if (existing && (existing->is_partial || existing->is_pending))
352✔
UNCOV
1281
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
×
1282
        if (existing) {
352✔
UNCOV
1283
                log_info("No need to acquire '%s', already installed.", i->path);
×
1284
                return 0;
1285
        }
1286

1287
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
352✔
1288
                r = mkdir_parents(t->temporary_partial_path, 0755);
208✔
1289
                if (r < 0)
208✔
UNCOV
1290
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1291

1292
                r = mkdir_parents(t->temporary_pending_path, 0755);
208✔
1293
                if (r < 0)
208✔
UNCOV
1294
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1295

1296
                r = mkdir_parents(t->final_path, 0755);
208✔
1297
                if (r < 0)
208✔
UNCOV
1298
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1299

1300
                where = t->final_path;
208✔
1301
        }
1302

1303
        if (t->target.type == RESOURCE_PARTITION) {
352✔
1304
                r = find_suitable_partition(
144✔
1305
                                t->target.path,
144✔
1306
                                i->metadata.size,
1307
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
144✔
1308
                                &t->partition_info);
1309
                if (r < 0)
144✔
1310
                        return r;
1311

1312
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
144✔
1313
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
144✔
1314

1315
                where = t->partition_info.device;
144✔
1316

1317
                /* Set the partition label and change the partition type to the derived "partial" type UUID
1318
                 * to indicate that a transfer to it is in progress. */
1319
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
144✔
1320
                if (r < 0)
144✔
1321
                        return r;
1322
                t->partition_info.type = t->partition_type_partial;
144✔
1323
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
144✔
1324

1325
                log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
144✔
1326
                          t->partition_info.device,
1327
                          t->partition_info.label,
1328
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1329
                r = patch_partition(
288✔
1330
                                t->target.path,
144✔
1331
                                &t->partition_info,
1332
                                t->partition_change);
1333
                if (r < 0)
144✔
1334
                        return r;
1335
        }
1336

1337
        assert(where);
352✔
1338

1339
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
704✔
1340

1341
        if (RESOURCE_IS_URL(i->resource->type)) {
352✔
1342
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1343
                 * download. */
1344

1345
                if (!i->metadata.sha256sum_set)
56✔
UNCOV
1346
                        return log_error_errno(r, "SHA256 checksum not known for download '%s', refusing.", i->path);
×
1347

1348
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
56✔
1349
                if (!digest)
56✔
UNCOV
1350
                        return log_oom();
×
1351
        }
1352

1353
        switch (i->resource->type) { /* Source */
352✔
1354

1355
        case RESOURCE_REGULAR_FILE:
256✔
1356

1357
                switch (t->target.type) { /* Target */
256✔
1358

1359
                case RESOURCE_REGULAR_FILE:
144✔
1360

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

1366
                        r = run_callout("(sd-import-raw)",
288✔
1367
                                        STRV_MAKE(
144✔
1368
                                               SYSTEMD_IMPORT_PATH,
1369
                                               "raw",
1370
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1371
                                               arg_sync ? "--sync=yes" : "--sync=no",
1372
                                               i->path,
1373
                                               t->temporary_partial_path),
1374
                                        t, i, cb, userdata);
1375
                        break;
256✔
1376

1377
                case RESOURCE_PARTITION:
112✔
1378

1379
                        /* regular file → partition */
1380

1381
                        r = run_callout("(sd-import-raw)",
224✔
1382
                                        STRV_MAKE(
112✔
1383
                                               SYSTEMD_IMPORT_PATH,
1384
                                               "raw",
1385
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1386
                                               "--offset", offset,
1387
                                               "--size-max", max_size,
1388
                                               arg_sync ? "--sync=yes" : "--sync=no",
1389
                                               i->path,
1390
                                               t->target.path),
1391
                                        t, i, cb, userdata);
1392
                        break;
1393

UNCOV
1394
                default:
×
UNCOV
1395
                        assert_not_reached();
×
1396
                }
1397

1398
                break;
256✔
1399

1400
        case RESOURCE_DIRECTORY:
40✔
1401
        case RESOURCE_SUBVOLUME:
1402
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
40✔
1403

1404
                /* directory/subvolume → directory/subvolume */
1405

1406
                r = run_callout("(sd-import-fs)",
80✔
1407
                                STRV_MAKE(
80✔
1408
                                       SYSTEMD_IMPORT_FS_PATH,
1409
                                       "run",
1410
                                       "--direct",          /* just untar the specified file, don't do anything else */
1411
                                       arg_sync ? "--sync=yes" : "--sync=no",
1412
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1413
                                       i->path,
1414
                                       t->temporary_partial_path),
1415
                                t, i, cb, userdata);
1416
                break;
1417

UNCOV
1418
        case RESOURCE_TAR:
×
UNCOV
1419
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1420

1421
                /* tar → directory/subvolume */
1422

UNCOV
1423
                r = run_callout("(sd-import-tar)",
×
UNCOV
1424
                                STRV_MAKE(
×
1425
                                       SYSTEMD_IMPORT_PATH,
1426
                                       "tar",
1427
                                       "--direct",          /* just untar the specified file, don't do anything else */
1428
                                       arg_sync ? "--sync=yes" : "--sync=no",
1429
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1430
                                       i->path,
1431
                                       t->temporary_partial_path),
1432
                                t, i, cb, userdata);
1433
                break;
1434

1435
        case RESOURCE_URL_FILE:
32✔
1436

1437
                switch (t->target.type) {
32✔
1438

UNCOV
1439
                case RESOURCE_REGULAR_FILE:
×
1440

1441
                        /* url file → regular file */
1442

UNCOV
1443
                        r = run_callout("(sd-pull-raw)",
×
UNCOV
1444
                                       STRV_MAKE(
×
1445
                                               SYSTEMD_PULL_PATH,
1446
                                               "raw",
1447
                                               "--direct",          /* just download the specified URL, don't download anything else */
1448
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1449
                                               arg_sync ? "--sync=yes" : "--sync=no",
1450
                                               i->path,
1451
                                               t->temporary_partial_path),
1452
                                        t, i, cb, userdata);
1453
                        break;
32✔
1454

1455
                case RESOURCE_PARTITION:
32✔
1456

1457
                        /* url file → partition */
1458

1459
                        r = run_callout("(sd-pull-raw)",
64✔
1460
                                        STRV_MAKE(
32✔
1461
                                               SYSTEMD_PULL_PATH,
1462
                                               "raw",
1463
                                               "--direct",              /* just download the specified URL, don't download anything else */
1464
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1465
                                               "--offset", offset,
1466
                                               "--size-max", max_size,
1467
                                               arg_sync ? "--sync=yes" : "--sync=no",
1468
                                               i->path,
1469
                                               t->target.path),
1470
                                        t, i, cb, userdata);
1471
                        break;
1472

UNCOV
1473
                default:
×
UNCOV
1474
                        assert_not_reached();
×
1475
                }
1476

1477
                break;
32✔
1478

1479
        case RESOURCE_URL_TAR:
24✔
1480
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
24✔
1481

1482
                r = run_callout("(sd-pull-tar)",
48✔
1483
                                STRV_MAKE(
48✔
1484
                                       SYSTEMD_PULL_PATH,
1485
                                       "tar",
1486
                                       "--direct",          /* just download the specified URL, don't download anything else */
1487
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1488
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1489
                                       arg_sync ? "--sync=yes" : "--sync=no",
1490
                                       i->path,
1491
                                       t->temporary_partial_path),
1492
                                t, i, cb, userdata);
1493
                break;
1494

UNCOV
1495
        default:
×
UNCOV
1496
                assert_not_reached();
×
1497
        }
1498
        if (r < 0)
352✔
1499
                return r;
1500

1501
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
344✔
1502
                bool need_sync = false;
208✔
1503
                assert(t->temporary_partial_path);
208✔
1504
                assert(t->temporary_pending_path);
208✔
1505

1506
                /* Apply file attributes if set */
1507
                if (f->mtime != USEC_INFINITY) {
208✔
1508
                        struct timespec ts;
184✔
1509

1510
                        timespec_store(&ts, f->mtime);
184✔
1511

1512
                        if (utimensat(AT_FDCWD, t->temporary_partial_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
184✔
UNCOV
1513
                                return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_partial_path);
×
1514

1515
                        need_sync = true;
184✔
1516
                }
1517

1518
                if (f->mode != MODE_INVALID) {
208✔
1519
                        /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1520
                         * kernels don't support that however, in that case we fall back to chmod(). Not as
1521
                         * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1522
                        if (fchmodat(AT_FDCWD, t->temporary_partial_path, f->mode, AT_SYMLINK_NOFOLLOW) < 0 &&
184✔
UNCOV
1523
                            (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f->mode) < 0))
×
UNCOV
1524
                                return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path);
×
1525

1526
                        need_sync = true;
1527
                }
1528

1529
                /* Synchronize */
1530
                if (arg_sync && need_sync) {
208✔
1531
                        if (t->target.type == RESOURCE_REGULAR_FILE)
184✔
1532
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
144✔
1533
                        else {
1534
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
40✔
1535
                                r = syncfs_path(AT_FDCWD, t->temporary_partial_path);
40✔
1536
                        }
1537
                        if (r < 0)
184✔
UNCOV
1538
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
×
1539
                }
1540

1541
                t->install_read_only = f->read_only;
208✔
1542

1543
                /* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate it’s ready to install. */
1544
                log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
208✔
1545
                r = install_file(AT_FDCWD, t->temporary_partial_path,
624✔
1546
                                 AT_FDCWD, t->temporary_pending_path,
208✔
1547
                                 INSTALL_REPLACE|
208✔
1548
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
416✔
1549
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
208✔
1550
                if (r < 0)
208✔
UNCOV
1551
                        return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
×
1552
        }
1553

1554
        if (t->target.type == RESOURCE_PARTITION) {
344✔
1555
                /* Now change the partition type to the derived "pending" type UUID to indicate that the
1556
                 * acquire is complete and the partition is ready for install. */
1557
                t->partition_info.type = t->partition_type_pending;
136✔
1558
                t->partition_change = PARTITION_TYPE;
136✔
1559

1560
                if (f->partition_uuid_set) {
136✔
UNCOV
1561
                        t->partition_info.uuid = f->partition_uuid;
×
UNCOV
1562
                        t->partition_change |= PARTITION_UUID;
×
1563
                }
1564

1565
                if (f->partition_flags_set) {
136✔
UNCOV
1566
                        t->partition_info.flags = f->partition_flags;
×
UNCOV
1567
                        t->partition_change |= PARTITION_FLAGS;
×
1568
                }
1569

1570
                if (f->no_auto >= 0) {
136✔
UNCOV
1571
                        t->partition_info.no_auto = f->no_auto;
×
UNCOV
1572
                        t->partition_change |= PARTITION_NO_AUTO;
×
1573
                }
1574

1575
                if (f->read_only >= 0) {
136✔
UNCOV
1576
                        t->partition_info.read_only = f->read_only;
×
UNCOV
1577
                        t->partition_change |= PARTITION_READ_ONLY;
×
1578
                }
1579

1580
                if (f->growfs >= 0) {
136✔
UNCOV
1581
                        t->partition_info.growfs = f->growfs;
×
UNCOV
1582
                        t->partition_change |= PARTITION_GROWFS;
×
1583
                }
1584

1585
                log_debug("Marking partition '%s' as pending (type=%s).",
136✔
1586
                          t->partition_info.device,
1587
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1588
                r = patch_partition(
272✔
1589
                                t->target.path,
136✔
1590
                                &t->partition_info,
136✔
1591
                                t->partition_change);
1592
                if (r < 0)
136✔
1593
                        return r;
1594
        }
1595

1596
        /* For regular file cases the only step left is to install the file in place, which install_file()
1597
         * will do via rename(). For partition cases the only step left is to update the partition table,
1598
         * which is done at the same place. */
1599

1600
        log_info("Successfully acquired '%s'.", i->path);
344✔
1601
        return 0;
1602
}
1603

1604
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
286✔
1605
        InstanceMetadata f;
286✔
1606
        Instance *existing;
286✔
1607
        int r;
286✔
1608

1609
        assert(t);
286✔
1610
        assert(i);
286✔
1611

1612
        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
286✔
1613

1614
        /* Does this instance already exist in the target but isn’t pending? */
1615
        existing = resource_find_instance(&t->target, i->metadata.version);
286✔
1616
        if (existing && !existing->is_pending) {
286✔
1617
                log_info("Resource '%s' instance is already in the target but is not pending.", i->path);
30✔
1618
                return 0;
286✔
1619
        }
1620

1621
        /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
1622
         * transfer_acquire_instance(). */
1623
        r = transfer_compute_temporary_paths(t, i, &f);
256✔
1624
        if (r < 0)
256✔
1625
                return r;
1626

1627
        /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
1628
         * already happened in the acquire phase, the target should already have that information and it
1629
         * should already have been claimed with the pending partition type UUID. */
1630
        if (t->target.type == RESOURCE_PARTITION) {
256✔
1631
                assert(i->resource == &t->target);
100✔
1632
                assert(i->is_pending);
100✔
1633

1634
                r = partition_info_copy(&t->partition_info, &i->partition_info);
100✔
1635
                if (r < 0)
100✔
UNCOV
1636
                        return r;
×
1637
        }
1638

1639
        return 0;
1640
}
1641

1642
int transfer_install_instance(
336✔
1643
                Transfer *t,
1644
                Instance *i,
1645
                const char *root) {
1646

1647
        int r;
336✔
1648

1649
        assert(t);
336✔
1650
        assert(i);
336✔
1651
        assert(i->resource);
336✔
1652
        assert(i->is_pending || t == container_of(i->resource, Transfer, source));
336✔
1653

1654
        log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
336✔
1655

1656
        if (t->temporary_pending_path) {
336✔
1657
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
208✔
1658
                assert(t->final_path);
208✔
1659

1660
                r = install_file(AT_FDCWD, t->temporary_pending_path,
624✔
1661
                                 AT_FDCWD, t->final_path,
1662
                                 INSTALL_REPLACE|
208✔
1663
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
416✔
1664
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
208✔
1665
                if (r < 0)
208✔
UNCOV
1666
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1667

1668
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
208✔
1669
                         i->path,
1670
                         resource_type_to_string(i->resource->type),
1671
                         t->final_path,
1672
                         resource_type_to_string(t->target.type));
1673

1674
                t->temporary_pending_path = mfree(t->temporary_pending_path);
208✔
1675
        }
1676

1677
        if (t->final_partition_label) {
336✔
1678
                assert(t->target.type == RESOURCE_PARTITION);
128✔
1679
                assert(t->target.partition_type_set);
128✔
1680

1681
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
128✔
1682
                if (r < 0)
128✔
1683
                        return r;
1684

1685
                /* Restore the original partition type UUID now that the partition is fully installed. */
1686
                t->partition_info.type = t->target.partition_type.uuid;
128✔
1687
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
128✔
1688

1689
                r = patch_partition(
256✔
1690
                                t->target.path,
128✔
1691
                                &t->partition_info,
128✔
1692
                                t->partition_change);
1693
                if (r < 0)
128✔
1694
                        return r;
1695

1696
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
128✔
1697
                         i->path,
1698
                         resource_type_to_string(i->resource->type),
1699
                         t->partition_info.device,
1700
                         resource_type_to_string(t->target.type));
1701
        }
1702

1703
        if (t->current_symlink) {
336✔
1704
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
64✔
1705
                const char *link_path, *link_target;
64✔
1706
                bool resolve_link_path = false;
64✔
1707

1708
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
64✔
1709

1710
                        assert(t->target.path);
64✔
1711

1712
                        if (path_is_absolute(t->current_symlink)) {
64✔
1713
                                link_path = t->current_symlink;
1714
                                resolve_link_path = true;
1715
                        } else {
UNCOV
1716
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
UNCOV
1717
                                if (!buf)
×
UNCOV
1718
                                        return log_oom();
×
1719

1720
                                link_path = buf;
1721
                        }
1722

1723
                        link_target = t->final_path;
64✔
1724

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

1727
                        assert(path_is_absolute(t->current_symlink));
×
1728

1729
                        link_path = t->current_symlink;
×
UNCOV
1730
                        link_target = t->partition_info.device;
×
1731

UNCOV
1732
                        resolve_link_path = true;
×
1733
                } else
1734
                        assert_not_reached();
×
1735

1736
                if (resolve_link_path && root) {
64✔
UNCOV
1737
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
1738
                        if (r < 0)
×
UNCOV
1739
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1740

UNCOV
1741
                        link_path = resolved;
×
1742
                }
1743

1744
                if (link_target) {
64✔
1745
                        r = path_extract_directory(link_path, &parent);
64✔
1746
                        if (r < 0)
64✔
UNCOV
1747
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1748

1749
                        r = path_make_relative(parent, link_target, &relative);
64✔
1750
                        if (r < 0)
64✔
UNCOV
1751
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1752

1753
                        r = symlink_atomic(relative, link_path);
64✔
1754
                        if (r < 0)
64✔
UNCOV
1755
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1756
                                                       link_path,
1757
                                                       glyph(GLYPH_ARROW_RIGHT),
1758
                                                       relative);
1759

1760
                        log_info("Updated symlink '%s' %s '%s'.",
64✔
1761
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1762
                }
1763
        }
1764

1765
        return 0;
1766
}
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