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

systemd / systemd / 25893429527

14 May 2026 09:08PM UTC coverage: 72.364% (-0.2%) from 72.584%
25893429527

push

github

bluca
ci: switch SUSE mkosi mirror to cdn.o.o

The cdn mirror is preferred by SUSE for clouds/CIs. There have been issues with some
mirrors, which fail to download from GHA quite often lately, so hopefully this will
make it reliable again.

328159 of 453485 relevant lines covered (72.36%)

1405869.02 hits per line

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

71.71
/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) {
4,536✔
50
        if (!t)
4,536✔
51
                return NULL;
52

53
        free(t->temporary_partial_path);
4,536✔
54
        free(t->temporary_pending_path);
4,536✔
55

56
        free(t->id);
4,536✔
57

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

63
        strv_free(t->features);
4,536✔
64
        strv_free(t->requisite_features);
4,536✔
65

66
        strv_free(t->changelog);
4,536✔
67
        strv_free(t->appstream);
4,536✔
68

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

72
        resource_destroy(&t->source);
4,536✔
73
        resource_destroy(&t->target);
4,536✔
74

75
        return mfree(t);
4,536✔
76
}
77

78
Transfer* transfer_new(Context *ctx) {
4,536✔
79
        Transfer *t;
4,536✔
80

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

85
        *t = (Transfer) {
4,536✔
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;
4,536✔
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(
3,396✔
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;
3,396✔
269
        int r;
3,396✔
270

271
        assert(rvalue);
3,396✔
272
        assert(data);
3,396✔
273

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

279
        r = safe_atou64(rvalue, &i);
3,396✔
280
        if (r < 0) {
3,396✔
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) {
3,396✔
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;
3,396✔
292

293
        return 0;
294
}
295

296
static int config_parse_resource_pattern(
9,072✔
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);
9,072✔
309
        int r;
9,072✔
310

311
        assert(rvalue);
9,072✔
312

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

318
        for (;;) {
29,480✔
319
                _cleanup_free_ char *word = NULL, *resolved = NULL;
10,204✔
320

321
                r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
19,276✔
322
                if (r < 0) {
19,276✔
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)
19,276✔
328
                        break;
329

330
                r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
10,204✔
331
                if (r < 0) {
10,204✔
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))
10,204✔
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));
10,204✔
342
                if (r < 0)
10,204✔
343
                        return log_oom();
×
344
        }
345

346
        strv_uniq(*patterns);
9,072✔
347
        return 0;
9,072✔
348
}
349

350
static int config_parse_resource_path(
9,072✔
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;
9,072✔
362
        Resource *rr = ASSERT_PTR(data);
9,072✔
363
        int r;
9,072✔
364

365
        assert(rvalue);
9,072✔
366

367
        if (streq(rvalue, "auto")) {
9,072✔
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);
9,072✔
374
        if (r < 0) {
9,072✔
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;
9,072✔
385
        return free_and_replace(rr->path, resolved);
9,072✔
386
}
387

388
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
9,072✔
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) {
4,536✔
478
        assert(t);
4,536✔
479

480
        /* Requisite feature disabled -> transfer disabled */
481
        STRV_FOREACH(id, t->requisite_features) {
4,536✔
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))
4,536✔
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) {
4,536✔
503
        assert(t);
4,536✔
504

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

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

540
        assert(path);
4,536✔
541
        assert(dirs);
4,536✔
542

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

547
        r = config_parse_many_full(
13,608✔
548
                        STRV_MAKE_CONST(path),
4,536✔
549
                        dirs,
550
                        strjoina(filename, ".d"),
22,680✔
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)
4,536✔
562
                return r;
563

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

568
        t->enabled = transfer_decide_if_enabled(t, known_features);
4,536✔
569

570
        if (!RESOURCE_IS_SOURCE(t->source.type))
4,536✔
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) {
4,536✔
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))
4,536✔
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) &&
4,536✔
604
             !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
3,970✔
605
            (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
4,536✔
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)
4,536✔
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)
4,536✔
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)
4,536✔
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) {
4,536✔
624
                if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
4,536✔
625
                        if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
4,172✔
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))
4,536✔
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))
4,536✔
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)
4,536✔
643
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
644
                                  "Target specification lacks Path= field.");
645

646
        if (t->target.path &&
4,536✔
647
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
4,536✔
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)) {
4,536✔
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))
4,536✔
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)
4,536✔
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(
4,536✔
671
                Transfer *t,
672
                const char *root,
673
                const char *node) {
674

675
        int r;
4,536✔
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);
4,536✔
683

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

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

692
        return 0;
693
}
694

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

699
        assert(t);
728✔
700

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

704
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
728✔
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);
552✔
710
        if (!d) {
552✔
711
                if (errno == ENOENT)
×
712
                        return;
713

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

718
        for (;;) {
2,576✔
719
                struct dirent *de;
2,576✔
720

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

729
                if (!startswith(de->d_name, ".#"))
2,024✔
730
                        continue;
2,024✔
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(
320✔
745
                Transfer *t,
746
                Instance *instance) {
747
        int r;
320✔
748

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

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

754
        case RESOURCE_REGULAR_FILE:
208✔
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);
208✔
758
                if (r < 0 && r != -ENOENT)
208✔
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);
208✔
762

763
                break;
208✔
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(
728✔
797
                Transfer *t,
798
                uint64_t space,
799
                const char *extra_protected_version) {
800

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

804
        assert(t);
728✔
805

806
        transfer_remove_temporary(t);
728✔
807

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

812
                assert(instance);
1,288✔
813

814
                if (!instance->is_pending && !instance->is_partial) {
1,288✔
815
                        i++;
1,272✔
816
                        continue;
1,272✔
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)))) {
×
825
                        log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version);
×
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;
728✔
852
        assert(instances_max >= 1);
728✔
853
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
728✔
854
                limit = UINT64_MAX;
855
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
552✔
856
                limit = 0;
857
        else if (space > instances_max)
456✔
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)
456✔
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;
456✔
865

866
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
728✔
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✔
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✔
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✔
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✔
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) {
1,032✔
905
                Instance *oldest;
304✔
906
                size_t p = t->target.n_instances - 1;
304✔
907

908
                for (;;) {
304✔
909
                        oldest = t->target.instances[p];
304✔
910
                        assert(oldest);
304✔
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) &&
304✔
914
                            (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
296✔
915
                                break;
916

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

923
                        p--;
×
924
                }
925

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

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

931
                log_info("%s Removing %s '%s' (%s).",
608✔
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);
304✔
938
                if (r < 0)
304✔
939
                        return 0;
940

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

945
                count++;
304✔
946
        }
947

948
        return count;
949
}
950

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

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

960
        *ret = (InstanceMetadata) {
1,960✔
961
                .version = i->metadata.version,
980✔
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,
980✔
967
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
980✔
968
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
980✔
969
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
980✔
970
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
980✔
971
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
980✔
972
                .size = i->metadata.size,
980✔
973
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
980✔
974
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
842✔
975
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
980✔
976
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
842✔
977
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
980✔
978
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
980✔
979
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
980✔
980
                .sha256sum_set = i->metadata.sha256sum_set,
980✔
981
        };
982

983
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
980✔
984
}
980✔
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) {
480✔
997
        if (!ctx)
480✔
998
                return NULL;
999

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

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

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

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

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

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

1021
        *ctx = (CalloutContext) {
480✔
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);
480✔
1031
        return 0;
480✔
1032
}
1033

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

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

1042
        if (si->si_code == CLD_EXITED) {
480✔
1043
                if (si->si_status == EXIT_SUCCESS) {
480✔
1044
                        r = 0;
472✔
1045
                        log_debug("%s succeeded.", ctx->name);
472✔
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 {
1050
                        r = -EPROTO;
×
1051
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
1052
                }
1053
        } else {
1054
                r = -EPROTO;
×
1055
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
1056
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
1057
                else
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);
480✔
1062
}
1063

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

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

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

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

1083
        char *errno_str = find_line_startswith(buf, "ERRNO=");
899✔
1084
        if (errno_str) {
899✔
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=");
899✔
1096
        if (progress_str) {
899✔
1097
                truncate_nl(progress_str);
411✔
1098

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

1109
        return 0;
1110
}
1111

1112
static int run_callout(
480✔
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;
480✔
1121

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

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

1131
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
480✔
1132
        r = sd_event_new(&event);
480✔
1133
        if (r < 0)
480✔
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));
480✔
1138
        if (r < 0)
480✔
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));
480✔
1141
        if (r < 0)
480✔
1142
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1143

1144
        _cleanup_free_ char *bind_name = NULL;
480✔
1145
        r = notify_socket_prepare(
480✔
1146
                        event,
1147
                        SD_EVENT_PRIORITY_NORMAL - 5,
1148
                        helper_on_notify,
1149
                        ctx,
1150
                        &bind_name);
1151
        if (r < 0)
480✔
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);
480✔
1155
        if (r < 0)
960✔
1156
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1157
        if (r == 0) {
960✔
1158
                /* Child */
1159
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
480✔
1160
                        log_error_errno(errno, "setenv() failed: %m");
×
1161
                        _exit(EXIT_FAILURE);
×
1162
                }
1163
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
480✔
1164
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
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;
480✔
1170
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
480✔
1171
        if (r < 0)
480✔
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);
480✔
1175
        if (r < 0)
480✔
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);
480✔
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) {
980✔
1186
        _cleanup_free_ char *formatted_pattern = NULL;
980✔
1187
        int r;
980✔
1188

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

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

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

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

1207
                if (!path_is_safe(formatted_pattern))
704✔
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);
704✔
1211
                if (!t->final_path)
704✔
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);
704✔
1221
                if (r < 0)
704✔
1222
                        return log_error_errno(r, "Failed to parse path: %m");
×
1223

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

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

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

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

1239
        if (t->target.type == RESOURCE_PARTITION) {
980✔
1240
                r = gpt_partition_label_valid(formatted_pattern);
276✔
1241
                if (r < 0)
276✔
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✔
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✔
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✔
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) {
480✔
1266
        _cleanup_free_ char *digest = NULL;
480✔
1267
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
480✔
1268
        const char *where = NULL;
480✔
1269
        Instance *existing;
480✔
1270
        int r;
480✔
1271

1272
        assert(t);
480✔
1273
        assert(i);
480✔
1274
        assert(f);
480✔
1275
        assert(i->resource == &t->source);
480✔
1276
        assert(cb);
480✔
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);
480✔
1280
        if (existing && (existing->is_partial || existing->is_pending))
480✔
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) {
480✔
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)) {
480✔
1288
                r = mkdir_parents(t->temporary_partial_path, 0755);
336✔
1289
                if (r < 0)
336✔
1290
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1291

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

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

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

1303
        if (t->target.type == RESOURCE_PARTITION) {
480✔
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);
480✔
1338

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

1341
        if (RESOURCE_IS_URL(i->resource->type)) {
480✔
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✔
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✔
1350
                        return log_oom();
×
1351
        }
1352

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

1355
        case RESOURCE_REGULAR_FILE:
384✔
1356

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

1359
                case RESOURCE_REGULAR_FILE:
272✔
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)",
544✔
1367
                                        STRV_MAKE(
272✔
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;
384✔
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

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

1398
                break;
384✔
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

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

1421
                /* tar → directory/subvolume */
1422

1423
                r = run_callout("(sd-import-tar)",
×
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

1439
                case RESOURCE_REGULAR_FILE:
×
1440

1441
                        /* url file → regular file */
1442

1443
                        r = run_callout("(sd-pull-raw)",
×
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

1473
                default:
×
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

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

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

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

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

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

1515
                        need_sync = true;
312✔
1516
                }
1517

1518
                if (f->mode != MODE_INVALID) {
336✔
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 &&
312✔
1523
                            (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f->mode) < 0))
×
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) {
336✔
1531
                        if (t->target.type == RESOURCE_REGULAR_FILE)
312✔
1532
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
272✔
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)
312✔
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;
336✔
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);
336✔
1545
                r = install_file(AT_FDCWD, t->temporary_partial_path,
1,008✔
1546
                                 AT_FDCWD, t->temporary_pending_path,
336✔
1547
                                 INSTALL_REPLACE|
336✔
1548
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
672✔
1549
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
336✔
1550
                if (r < 0)
336✔
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) {
472✔
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✔
1561
                        t->partition_info.uuid = f->partition_uuid;
×
1562
                        t->partition_change |= PARTITION_UUID;
×
1563
                }
1564

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

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

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

1580
                if (f->growfs >= 0) {
136✔
1581
                        t->partition_info.growfs = f->growfs;
×
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);
472✔
1601
        return 0;
1602
}
1603

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

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

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

1614
        /* Does this instance already exist in the target but isn’t pending? */
1615
        existing = resource_find_instance(&t->target, i->metadata.version);
398✔
1616
        if (existing && !existing->is_pending) {
398✔
1617
                log_info("Resource '%s' instance is already in the target but is not pending.", i->path);
42✔
1618
                return 0;
398✔
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);
356✔
1624
        if (r < 0)
356✔
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) {
356✔
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✔
1636
                        return r;
×
1637
        }
1638

1639
        return 0;
1640
}
1641

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

1647
        int r;
464✔
1648

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

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

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

1660
                r = install_file(AT_FDCWD, t->temporary_pending_path,
1,008✔
1661
                                 AT_FDCWD, t->final_path,
1662
                                 INSTALL_REPLACE|
336✔
1663
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
672✔
1664
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
336✔
1665
                if (r < 0)
336✔
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).",
336✔
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);
336✔
1675
        }
1676

1677
        if (t->final_partition_label) {
464✔
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) {
464✔
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 {
1716
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1717
                                if (!buf)
×
1718
                                        return log_oom();
×
1719

1720
                                link_path = buf;
1721
                        }
1722

1723
                        link_target = t->final_path;
64✔
1724

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

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

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

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

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

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✔
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✔
1751
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1752

1753
                        r = mkdir_parents(link_path, 0755);
64✔
1754
                        if (r < 0)
64✔
1755
                                return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path);
×
1756

1757
                        r = symlink_atomic(relative, link_path);
64✔
1758
                        if (r < 0)
64✔
1759
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1760
                                                       link_path,
1761
                                                       glyph(GLYPH_ARROW_RIGHT),
1762
                                                       relative);
1763

1764
                        log_info("Updated symlink '%s' %s '%s'.",
64✔
1765
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1766
                }
1767
        }
1768

1769
        return 0;
1770
}
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