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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 hits per line

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

73.09
/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 "path-util.h"
29
#include "percent-util.h"
30
#include "pidref.h"
31
#include "process-util.h"
32
#include "rm-rf.h"
33
#include "signal-util.h"
34
#include "specifier.h"
35
#include "stdio-util.h"
36
#include "strv.h"
37
#include "sync-util.h"
38
#include "sysupdate.h"
39
#include "sysupdate-cleanup.h"
40
#include "sysupdate-config.h"
41
#include "sysupdate-feature.h"
42
#include "sysupdate-instance.h"
43
#include "sysupdate-pattern.h"
44
#include "sysupdate-resource.h"
45
#include "sysupdate-transfer.h"
46
#include "time-util.h"
47
#include "web-util.h"
48

49
/* Default value for InstancesMax= for fs object targets */
50
#define DEFAULT_FILE_INSTANCES_MAX 3
51

52
enum {
53
        MATCH_PATTERN_SOURCE,
54
        MATCH_PATTERN_TARGET,
55
};
56

57
Transfer* transfer_free(Transfer *t) {
9,923✔
58
        if (!t)
9,923✔
59
                return NULL;
60

61
        free(t->temporary_partial_path);
9,923✔
62
        free(t->temporary_pending_path);
9,923✔
63

64
        free(t->id);
9,923✔
65

66
        free(t->min_version);
9,923✔
67
        strv_free(t->protected_versions);
9,923✔
68
        free(t->current_symlink);
9,923✔
69
        free(t->final_path);
9,923✔
70

71
        strv_free(t->features);
9,923✔
72
        strv_free(t->requisite_features);
9,923✔
73

74
        strv_free(t->changelog);
9,923✔
75
        strv_free(t->appstream);
9,923✔
76

77
        partition_info_destroy(&t->partition_info);
9,923✔
78
        free(t->final_partition_label);
9,923✔
79

80
        resource_destroy(&t->source);
9,923✔
81
        resource_destroy(&t->target);
9,923✔
82

83
        return mfree(t);
9,923✔
84
}
85

86
Transfer* transfer_new(Context *ctx) {
9,923✔
87
        Transfer *t;
9,923✔
88

89
        t = new(Transfer, 1);
9,923✔
90
        if (!t)
9,923✔
91
                return NULL;
92

93
        *t = (Transfer) {
9,923✔
94
                .source.type = _RESOURCE_TYPE_INVALID,
95
                .target.type = _RESOURCE_TYPE_INVALID,
96
                .remove_temporary = true,
97
                .mode = MODE_INVALID,
98
                .tries_left = UINT64_MAX,
99
                .tries_done = UINT64_MAX,
100
                .verify = true,
101

102
                /* the three flags, as configured by the user */
103
                .no_auto = -1,
104
                .read_only = -1,
105
                .growfs = -1,
106

107
                /* the read only flag, as ultimately determined */
108
                .install_read_only = -1,
109

110
                .partition_info = PARTITION_INFO_NULL,
111

112
                .context = ctx,
113
        };
114

115
        return t;
9,923✔
116
}
117

118
static int config_parse_protect_version(
×
119
                const char *unit,
120
                const char *filename,
121
                unsigned line,
122
                const char *section,
123
                unsigned section_line,
124
                const char *lvalue,
125
                int ltype,
126
                const char *rvalue,
127
                void *data,
128
                void *userdata) {
129

130
        _cleanup_free_ char *resolved = NULL;
×
131
        char ***protected_versions = ASSERT_PTR(data);
×
132
        Transfer *t = ASSERT_PTR(userdata);
×
133
        int r;
×
134

135
        assert(rvalue);
×
136

137
        r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
×
138
        if (r < 0) {
×
139
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
140
                           "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
141
                return 0;
142
        }
143

144
        if (!version_is_valid(resolved))  {
×
145
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
146
                           "ProtectVersion= string is not valid, ignoring: %s", resolved);
147
                return 0;
148
        }
149

150
        r = strv_extend(protected_versions, resolved);
×
151
        if (r < 0)
×
152
                return log_oom();
×
153

154
        return 0;
155
}
156

157
static int config_parse_min_version(
×
158
                const char *unit,
159
                const char *filename,
160
                unsigned line,
161
                const char *section,
162
                unsigned section_line,
163
                const char *lvalue,
164
                int ltype,
165
                const char *rvalue,
166
                void *data,
167
                void *userdata) {
168

169
        _cleanup_free_ char *resolved = NULL;
×
170
        char **version = ASSERT_PTR(data);
×
171
        Transfer *t = ASSERT_PTR(userdata);
×
172
        int r;
×
173

174
        assert(rvalue);
×
175

176
        r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
×
177
        if (r < 0) {
×
178
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
179
                           "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
180
                return 0;
181
        }
182

183
        if (!version_is_valid(rvalue)) {
×
184
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
185
                           "MinVersion= string is not valid, ignoring: %s", resolved);
186
                return 0;
187
        }
188

189
        return free_and_replace(*version, resolved);
×
190
}
191

192
static int config_parse_transfer_url_specifiers_many(
×
193
                const char *unit,
194
                const char *filename,
195
                unsigned line,
196
                const char *section,
197
                unsigned section_line,
198
                const char *lvalue,
199
                int ltype,
200
                const char *rvalue,
201
                void *data,
202
                void *userdata) {
203

204
        Transfer *t = ASSERT_PTR(userdata);
×
205

206
        /* Here we expect userdata to point to our Transfer object, but config_parse_url_specifiers() wants
207
         * the root dir there, hence fix this up */
208

209
        return config_parse_url_specifiers_many(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, t->context->root);
×
210
}
211

212
static int config_parse_current_symlink(
1,232✔
213
                const char *unit,
214
                const char *filename,
215
                unsigned line,
216
                const char *section,
217
                unsigned section_line,
218
                const char *lvalue,
219
                int ltype,
220
                const char *rvalue,
221
                void *data,
222
                void *userdata) {
223

224
        _cleanup_free_ char *resolved = NULL;
1,232✔
225
        char **current_symlink = ASSERT_PTR(data);
1,232✔
226
        Transfer *t = ASSERT_PTR(userdata);
1,232✔
227
        int r;
1,232✔
228

229
        assert(rvalue);
1,232✔
230

231
        r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
1,232✔
232
        if (r < 0) {
1,232✔
233
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
234
                           "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
235
                return 0;
236
        }
237

238
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
1,232✔
239
        if (r < 0)
1,232✔
240
                return 0;
241

242
        return free_and_replace(*current_symlink, resolved);
1,232✔
243
}
244

245
static int config_parse_instances_max(
7,425✔
246
                const char *unit,
247
                const char *filename,
248
                unsigned line,
249
                const char *section,
250
                unsigned section_line,
251
                const char *lvalue,
252
                int ltype,
253
                const char *rvalue,
254
                void *data,
255
                void *userdata) {
256

257
        uint64_t *instances_max = data, i;
7,425✔
258
        int r;
7,425✔
259

260
        assert(rvalue);
7,425✔
261
        assert(data);
7,425✔
262

263
        if (isempty(rvalue)) {
7,425✔
264
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
265
                return 0;
×
266
        }
267

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

275
        if (i < 2) {
7,425✔
276
                log_syntax(unit, LOG_WARNING, filename, line, 0,
17✔
277
                           "InstancesMax= value must be at least 2, bumping: %s", rvalue);
278
                *instances_max = 2;
17✔
279
        } else
280
                *instances_max = i;
7,408✔
281

282
        return 0;
283
}
284

285
static int config_parse_resource_pattern(
19,846✔
286
                const char *unit,
287
                const char *filename,
288
                unsigned line,
289
                const char *section,
290
                unsigned section_line,
291
                const char *lvalue,
292
                int ltype,
293
                const char *rvalue,
294
                void *data,
295
                void *userdata) {
296

297
        char ***patterns = ASSERT_PTR(data);
19,846✔
298
        Transfer *t = ASSERT_PTR(userdata);
19,846✔
299
        bool is_source = ltype == MATCH_PATTERN_SOURCE;
19,846✔
300
        int r;
19,846✔
301

302
        assert(rvalue);
19,846✔
303

304
        if (isempty(rvalue)) {
19,846✔
305
                *patterns = strv_free(*patterns);
×
306
                return 0;
×
307
        }
308

309
        for (;;) {
64,476✔
310
                _cleanup_free_ char *word = NULL, *resolved = NULL;
22,315✔
311
                const char *body;
42,161✔
312

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

322
                r = specifier_printf(word, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
22,315✔
323
                if (r < 0) {
22,315✔
324
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
325
                                   "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
326
                        return 0;
327
                }
328

329
                /* The glob directory prefix on a source MatchPattern= means "match the rest against the
330
                 * basename" thus the remainder must be a valid filename (no slashes).
331
                 * Target patterns can not use it. */
332
                body = resolved;
22,315✔
333
                if (pattern_skip_glob_directory_prefix(&body)) {
22,315✔
334
                        if (!is_source)
6✔
335
                                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
336
                                                  "'**/' prefix is only supported in a source MatchPattern=, refusing: %s", resolved);
337
                        if (!filename_is_valid(body))
6✔
338
                                return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
339
                                                  "The pattern after a '**/' prefix must be a valid filename, refusing: %s", resolved);
340
                }
341

342
                /* The glob directory prefix is not allowed in the remainder and pattern_valid will catch this. */
343
                r = pattern_valid(body);
22,315✔
344
                if (r <= 0)
22,315✔
345
                        return log_syntax(unit, LOG_ERR, filename, line, r < 0 ? r : SYNTHETIC_ERRNO(EINVAL),
×
346
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
347

348
                r = strv_consume(patterns, TAKE_PTR(resolved));
22,315✔
349
                if (r < 0)
22,315✔
350
                        return log_oom();
×
351
        }
352

353
        strv_uniq(*patterns);
19,846✔
354
        return 0;
19,846✔
355
}
356

357
static int config_parse_resource_path(
19,846✔
358
                const char *unit,
359
                const char *filename,
360
                unsigned line,
361
                const char *section,
362
                unsigned section_line,
363
                const char *lvalue,
364
                int ltype,
365
                const char *rvalue,
366
                void *data,
367
                void *userdata) {
368
        _cleanup_free_ char *resolved = NULL;
19,846✔
369
        Resource *rr = ASSERT_PTR(data);
19,846✔
370
        Transfer *t = ASSERT_PTR(userdata);
19,846✔
371
        int r;
19,846✔
372

373
        assert(rvalue);
19,846✔
374

375
        if (streq(rvalue, "auto")) {
19,846✔
376
                rr->path_auto = true;
×
377
                rr->path = mfree(rr->path);
×
378
                return 0;
×
379
        }
380

381
        r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
19,846✔
382
        if (r < 0) {
19,846✔
383
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
384
                           "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
385
                return 0;
386
        }
387

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

392
        rr->path_auto = false;
19,846✔
393
        return free_and_replace(rr->path, resolved);
19,846✔
394
}
395

396
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
19,846✔
397

398
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
3,696✔
399
                                             PATH_RELATIVE_TO_ROOT);
400

401
static int config_parse_resource_ptype(
2,496✔
402
                const char *unit,
403
                const char *filename,
404
                unsigned line,
405
                const char *section,
406
                unsigned section_line,
407
                const char *lvalue,
408
                int ltype,
409
                const char *rvalue,
410
                void *data,
411
                void *userdata) {
412

413
        Resource *rr = ASSERT_PTR(data);
2,496✔
414
        int r;
2,496✔
415

416
        assert(rvalue);
2,496✔
417

418
        r = gpt_partition_type_from_string(rvalue, &rr->partition_type);
2,496✔
419
        if (r < 0) {
2,496✔
420
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
421
                           "Failed to parse partition type, ignoring: %s", rvalue);
422
                return 0;
423
        }
424

425
        rr->partition_type_set = true;
2,496✔
426
        return 0;
2,496✔
427
}
428

429
static int config_parse_partition_uuid(
×
430
                const char *unit,
431
                const char *filename,
432
                unsigned line,
433
                const char *section,
434
                unsigned section_line,
435
                const char *lvalue,
436
                int ltype,
437
                const char *rvalue,
438
                void *data,
439
                void *userdata) {
440

441
        Transfer *t = ASSERT_PTR(data);
×
442
        int r;
×
443

444
        assert(rvalue);
×
445

446
        r = sd_id128_from_string(rvalue, &t->partition_uuid);
×
447
        if (r < 0) {
×
448
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
449
                           "Failed to parse partition UUID, ignoring: %s", rvalue);
450
                return 0;
451
        }
452

453
        t->partition_uuid_set = true;
×
454
        return 0;
×
455
}
456

457
static int config_parse_partition_flags(
×
458
                const char *unit,
459
                const char *filename,
460
                unsigned line,
461
                const char *section,
462
                unsigned section_line,
463
                const char *lvalue,
464
                int ltype,
465
                const char *rvalue,
466
                void *data,
467
                void *userdata) {
468

469
        Transfer *t = ASSERT_PTR(data);
×
470
        int r;
×
471

472
        assert(rvalue);
×
473

474
        r = safe_atou64(rvalue, &t->partition_flags);
×
475
        if (r < 0) {
×
476
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
477
                           "Failed to parse partition flags, ignoring: %s", rvalue);
478
                return 0;
479
        }
480

481
        t->partition_flags_set = true;
×
482
        return 0;
×
483
}
484

485
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
9,923✔
486
        assert(t);
9,923✔
487

488
        /* Requisite feature disabled -> transfer disabled */
489
        STRV_FOREACH(id, t->requisite_features) {
9,923✔
490
                Feature *f = hashmap_get(known_features, *id);
×
491
                if (!f || !f->enabled) /* missing features are implicitly disabled */
×
492
                        return false;
493
        }
494

495
        /* No features defined -> transfer implicitly enabled */
496
        if (strv_isempty(t->features))
9,923✔
497
                return true;
498

499
        /* At least one feature enabled -> transfer enabled */
500
        STRV_FOREACH(id, t->features) {
2,364✔
501
                Feature *f = hashmap_get(known_features, *id);
1,232✔
502
                if (f && f->enabled)
1,232✔
503
                        return true;
504
        }
505

506
        /* All listed features disabled -> transfer disabled */
507
        return false;
508
}
509

510
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
9,923✔
511
        assert(t);
9,923✔
512

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

544
        _cleanup_free_ char *filename = NULL;
9,923✔
545
        char *e;
9,923✔
546
        int r;
9,923✔
547

548
        assert(path);
9,923✔
549
        assert(dirs);
9,923✔
550

551
        r = path_extract_filename(path, &filename);
9,923✔
552
        if (r < 0)
9,923✔
553
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
554

555
        r = config_parse_many_full(
29,769✔
556
                        STRV_MAKE_CONST(path),
9,923✔
557
                        dirs,
558
                        strjoina(filename, ".d"),
49,615✔
559
                        t->context->root,
9,923✔
560
                        /* root_fd= */ -EBADF,
561
                        "Transfer\0"
562
                        "Source\0"
563
                        "Target\0",
564
                        config_item_table_lookup, table,
565
                        CONFIG_PARSE_WARN,
566
                        t,
567
                        /* ret_stats_by_path= */ NULL,
568
                        /* ret_drop_in_files= */ NULL);
569
        if (r < 0)
9,923✔
570
                return r;
571

572
        e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
9,923✔
573
        *e = 0; /* Remove the file extension */
9,923✔
574
        t->id = TAKE_PTR(filename);
9,923✔
575

576
        t->enabled = transfer_decide_if_enabled(t, known_features);
9,923✔
577

578
        if (!RESOURCE_IS_SOURCE(t->source.type))
9,923✔
579
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
580
                                  "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
581

582
        if (t->target.type < 0) {
9,923✔
583
                switch (t->source.type) {
×
584

585
                case RESOURCE_URL_FILE:
×
586
                case RESOURCE_REGULAR_FILE:
587
                        t->target.type =
×
588
                                t->target.path && path_startswith(t->target.path, "/dev/") ?
×
589
                                RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
×
590
                        break;
×
591

592
                case RESOURCE_URL_TAR:
×
593
                case RESOURCE_TAR:
594
                case RESOURCE_DIRECTORY:
595
                        t->target.type = RESOURCE_DIRECTORY;
×
596
                        break;
×
597

598
                case RESOURCE_SUBVOLUME:
×
599
                        t->target.type = RESOURCE_SUBVOLUME;
×
600
                        break;
×
601

602
                default:
×
603
                        assert_not_reached();
×
604
                }
605
        }
606

607
        if (!RESOURCE_IS_TARGET(t->target.type))
9,923✔
608
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
609
                                  "Target Type= must be one of partition, regular-file, directory, subvolume.");
610

611
        if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
9,923✔
612
             !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
8,687✔
613
            (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
9,923✔
614
             !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
1,236✔
615
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
616
                                  "Target type '%s' is incompatible with source type '%s', refusing.",
617
                                  resource_type_to_string(t->target.type), resource_type_to_string(t->source.type));
618

619
        if (!t->source.path && !t->source.path_auto)
9,923✔
620
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
621
                                  "Source specification lacks Path=.");
622

623
        if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !t->context->transfer_source)
9,923✔
624
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
625
                                  "PathRelativeTo=explicit requires --transfer-source= to be specified.");
626

627
        if (t->target.path_relative_to == PATH_RELATIVE_TO_EXPLICIT)
9,923✔
628
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
629
                                  "PathRelativeTo=explicit can only be used in source specifications.");
630

631
        if (t->source.path) {
9,923✔
632
                if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
9,923✔
633
                        if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
9,150✔
634
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
635
                                                  "Source path is not a normalized, absolute path: %s", t->source.path);
636

637
                /* We unofficially support file:// in addition to http:// and https:// for url
638
                 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
639
                 * server, and CURL abstracts this away from us thankfully. */
640
                if (RESOURCE_IS_URL(t->source.type))
9,923✔
641
                        if (!http_url_is_valid(t->source.path) && !file_url_is_valid(t->source.path))
773✔
642
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
643
                                                  "Source path is not a valid HTTP or HTTPS URL: %s", t->source.path);
644
        }
645

646
        if (strv_isempty(t->source.patterns))
9,923✔
647
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
648
                                  "Source specification lacks MatchPattern=.");
649

650
        if (IN_SET(t->source.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
10,779✔
651
            resource_has_glob_directory_pattern(&t->source))
856✔
652
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
653
                                  "MatchPattern= with '**/' prefix is not supported for source Type=directory and Type=subvolume, refusing.");
654

655
        if (!t->target.path && !t->target.path_auto)
9,923✔
656
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
657
                                  "Target specification lacks Path= field.");
658

659
        if (t->target.path &&
9,923✔
660
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
9,923✔
661
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
662
                                  "Target path is not a normalized, absolute path: %s", t->target.path);
663

664
        if (strv_isempty(t->target.patterns)) {
9,923✔
665
                log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification.");
×
666
                strv_free(t->target.patterns);
×
667
                t->target.patterns = strv_copy(t->source.patterns);
×
668
                if (!t->target.patterns)
×
669
                        return log_oom();
×
670

671
                /* Strip any glob directory prefix when inheriting from source because it's only used for finding and
672
                 * not to replicate the same directory layout at the target, so we don't support it there. */
673
                STRV_FOREACH(p, t->target.patterns) {
×
674
                        const char *body = *p;
×
675

676
                        if (pattern_skip_glob_directory_prefix(&body))
×
677
                                memmove(*p, body, strlen(body) + 1);
×
678
                }
679

680
                /* Stripping the glob directory prefix can turn distinct source patterns into duplicates,
681
                 * so re-uniq for parity with the source side. */
682
                strv_uniq(t->target.patterns);
×
683
        }
684

685
        if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
9,923✔
686
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
687
                                  "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
688

689
        /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
690
        if (t->instances_max == 0)
9,923✔
691
                t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
2,500✔
692

693
        return 0;
694
}
695

696
int transfer_resolve_paths(
9,923✔
697
                Transfer *t,
698
                const char *root,
699
                const char *node) {
700

701
        int r;
9,923✔
702

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

708
        assert(t);
9,923✔
709

710
        r = resource_resolve_path(&t->source, root, t->context->transfer_source, node);
9,923✔
711
        if (r < 0)
9,923✔
712
                return r;
713

714
        r = resource_resolve_path(&t->target, root, /* relative_to_directory= */ NULL, node);
9,923✔
715
        if (r < 0)
9,923✔
716
                return r;
×
717

718
        return 0;
719
}
720

721
static void transfer_remove_temporary(Transfer *t) {
1,478✔
722
        _cleanup_closedir_ DIR *d = NULL;
2,956✔
723
        int r;
1,478✔
724

725
        assert(t);
1,478✔
726

727
        if (!t->remove_temporary)
1,478✔
728
                return;
729

730
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
1,478✔
731
                return;
732

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

735
        d = opendir(t->target.path);
1,126✔
736
        if (!d) {
1,126✔
737
                if (errno == ENOENT)
×
738
                        return;
739

740
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
1,478✔
741
                return;
742
        }
743

744
        for (;;) {
5,181✔
745
                struct dirent *de;
5,181✔
746

747
                errno = 0;
5,181✔
748
                de = readdir_no_dot(d);
5,181✔
749
                if (!de) {
5,181✔
750
                        if (errno != 0)
1,126✔
751
                                log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
×
752
                        break;
1,126✔
753
                }
754

755
                if (!startswith(de->d_name, ".#"))
4,055✔
756
                        continue;
4,055✔
757

758
                r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
×
759
                if (r == -ENOENT)
×
760
                        continue;
×
761
                if (r < 0) {
×
762
                        log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
×
763
                        continue;
×
764
                }
765

766
                log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
×
767
        }
768
}
769

770
static int transfer_instance_vacuum(
640✔
771
                Transfer *t,
772
                Instance *instance) {
773
        int r;
640✔
774

775
        assert(t);
640✔
776
        assert(instance);
640✔
777

778
        switch (t->target.type) {
640✔
779

780
        case RESOURCE_REGULAR_FILE:
416✔
781
        case RESOURCE_DIRECTORY:
782
        case RESOURCE_SUBVOLUME:
783
                r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
416✔
784
                if (r < 0 && r != -ENOENT)
416✔
785
                        return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
×
786

787
                (void) rmdir_parents(instance->path, t->target.path);
416✔
788

789
                break;
416✔
790

791
        case RESOURCE_PARTITION: {
224✔
792
                PartitionInfo pinfo = instance->partition_info;
224✔
793
                PartitionChange change = PARTITION_LABEL;
224✔
794

795
                /* label "_empty" means "no contents" for our purposes */
796
                pinfo.label = (char*) "_empty";
224✔
797

798
                /* If the partition had a derived partial/pending type UUID, restore the original
799
                 * partition type so that the slot is properly recognized as empty in subsequent
800
                 * scans. */
801
                if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
224✔
802
                        pinfo.type = t->target.partition_type.uuid;
32✔
803
                        change |= PARTITION_TYPE;
32✔
804
                }
805

806
                log_debug("Resetting partition '%s' to empty.", pinfo.device);
224✔
807
                r = patch_partition(t->target.path, &pinfo, change);
224✔
808
                if (r < 0)
224✔
809
                        return r;
×
810

811
                t->target.n_empty++;
224✔
812
                break;
224✔
813
        }
814

815
        default:
×
816
                assert_not_reached();
×
817
        }
818

819
        return 0;
820
}
821

822
int transfer_vacuum(
1,478✔
823
                Transfer *t,
824
                uint64_t space,
825
                const char *extra_protected_version) {
826

827
        uint64_t instances_max, limit;
1,478✔
828
        int r, count = 0;
1,478✔
829

830
        assert(t);
1,478✔
831

832
        transfer_remove_temporary(t);
1,478✔
833

834
        /* First, remove any partial or pending instances (unless protected) */
835
        for (size_t i = 0; i < t->target.n_instances;) {
5,536✔
836
                Instance *instance = t->target.instances[i];
2,580✔
837

838
                assert(instance);
2,580✔
839

840
                if (!instance->is_pending && !instance->is_partial) {
2,580✔
841
                        i++;
2,548✔
842
                        continue;
2,548✔
843
                }
844

845
                /* If this is pending and listed among the protected versions, then let's not remove it.
846
                 * In future, we will also want to keep partial protected versions, but that’s only useful
847
                 * once we support resuming downloads. */
848
                if (instance->is_pending &&
48✔
849
                    (strv_contains(t->protected_versions, instance->metadata.version) ||
32✔
850
                     (extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) {
×
851
                        log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version);
×
852
                        i++;
×
853
                        continue;
×
854
                }
855

856
                assert(instance->resource);
32✔
857

858
                log_info("%s Removing old %s '%s' (%s).",
64✔
859
                         glyph(GLYPH_RECYCLING),
860
                         instance->is_partial ? "partial" : "pending",
861
                         instance->path,
862
                         resource_type_to_string(instance->resource->type));
863

864
                r = transfer_instance_vacuum(t, instance);
32✔
865
                if (r < 0)
32✔
866
                        return r;
867

868
                instance_free(instance);
32✔
869
                memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
32✔
870
                t->target.n_instances--;
32✔
871

872
                count++;
32✔
873
        }
874

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

877
        instances_max = t->context->instances_max != UINT64_MAX ? t->context->instances_max : t->instances_max;
1,478✔
878
        assert(instances_max >= 1);
1,478✔
879
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
1,478✔
880
                limit = UINT64_MAX;
881
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
1,126✔
882
                limit = 0;
883
        else if (space > instances_max)
934✔
884
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
885
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
886
        else if (space == instances_max)
934✔
887
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
888
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
889
        else
890
                limit = instances_max - space;
934✔
891

892
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
1,478✔
893
                _cleanup_free_ char *patterns = NULL;
352✔
894
                uint64_t rm, remain;
352✔
895

896
                patterns = strv_join(t->target.patterns, "|");
352✔
897
                if (!patterns)
352✔
898
                        (void) log_oom_debug();
×
899

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

903
                if (t->target.n_empty + t->target.n_instances < 2)
352✔
904
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
905
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s, refusing.",
906
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
907
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
908
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
909
                                               strempty(patterns),
910
                                               !isempty(patterns) ? "'" : "");
911
                if (space > t->target.n_empty + t->target.n_instances)
352✔
912
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
913
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s for operation.",
914
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
915
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
916
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
917
                                               strempty(patterns),
918
                                               !isempty(patterns) ? "'" : "");
919
                if (space == t->target.n_empty + t->target.n_instances)
352✔
920
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
921
                                               "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.",
922
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
923
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
924

925
                rm = LESS_BY(space, t->target.n_empty);
352✔
926
                remain = LESS_BY(t->target.n_instances, rm);
352✔
927
                limit = MIN(limit, remain);
352✔
928
        }
929

930
        while (t->target.n_instances > limit) {
2,086✔
931
                Instance *oldest;
608✔
932
                size_t p = t->target.n_instances - 1;
608✔
933

934
                for (;;) {
608✔
935
                        oldest = t->target.instances[p];
608✔
936
                        assert(oldest);
608✔
937

938
                        /* If this is listed among the protected versions, then let's not remove it */
939
                        if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
608✔
940
                            (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
592✔
941
                                break;
942

943
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
944
                        if (p == 0) {
×
945
                                oldest = NULL;
946
                                break;
947
                        }
948

949
                        p--;
×
950
                }
951

952
                if (!oldest) /* Nothing more to remove */
608✔
953
                        break;
954

955
                assert(oldest->resource);
608✔
956

957
                log_info("%s Removing %s '%s' (%s).",
1,216✔
958
                         glyph(GLYPH_RECYCLING),
959
                         space == UINT64_MAX ? "disabled" : "old",
960
                         oldest->path,
961
                         resource_type_to_string(oldest->resource->type));
962

963
                r = transfer_instance_vacuum(t, oldest);
608✔
964
                if (r < 0)
608✔
965
                        return r;
966

967
                instance_free(oldest);
608✔
968
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
608✔
969
                t->target.n_instances--;
608✔
970

971
                count++;
608✔
972
        }
973

974
        return count;
975
}
976

977
static void compile_pattern_fields(
1,982✔
978
                const Transfer *t,
979
                const Instance *i,
980
                InstanceMetadata *ret) {
981

982
        assert(t);
1,982✔
983
        assert(i);
1,982✔
984
        assert(ret);
1,982✔
985

986
        *ret = (InstanceMetadata) {
3,964✔
987
                .version = i->metadata.version,
1,982✔
988

989
                /* We generally prefer explicitly configured values for the transfer over those automatically
990
                 * derived from the source instance. Also, if the source is a tar archive, then let's not
991
                 * patch mtime/mode and use the one embedded in the tar file */
992
                .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
1,982✔
993
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
1,982✔
994
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
1,982✔
995
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
1,982✔
996
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
1,982✔
997
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
1,982✔
998
                .size = i->metadata.size,
1,982✔
999
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
1,982✔
1000
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
1,706✔
1001
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
1,982✔
1002
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
1,706✔
1003
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
1,982✔
1004
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
1,982✔
1005
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
1,982✔
1006
                .sha256sum_set = i->metadata.sha256sum_set,
1,982✔
1007
        };
1008

1009
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
1,982✔
1010
}
1,982✔
1011

1012
typedef struct CalloutContext {
1013
        const Transfer *transfer;
1014
        const Instance *instance;
1015
        TransferProgress callback;
1016
        PidRef pid;
1017
        const char *name;
1018
        int helper_errno;
1019
        void* userdata;
1020
} CalloutContext;
1021

1022
static CalloutContext *callout_context_free(CalloutContext *ctx) {
982✔
1023
        if (!ctx)
982✔
1024
                return NULL;
1025

1026
        /* We don't own any data but need to clean up the job pid */
1027
        pidref_done(&ctx->pid);
982✔
1028

1029
        return mfree(ctx);
982✔
1030
}
1031

1032
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
1,964✔
1033

1034
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
982✔
1035
                               const char *name, void* userdata, CalloutContext **ret) {
1036
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
982✔
1037

1038
        assert(t);
982✔
1039
        assert(i);
982✔
1040
        assert(cb);
982✔
1041
        assert(ret);
982✔
1042

1043
        ctx = new(CalloutContext, 1);
982✔
1044
        if (!ctx)
982✔
1045
                return -ENOMEM;
1046

1047
        *ctx = (CalloutContext) {
982✔
1048
                .transfer = t,
1049
                .instance = i,
1050
                .callback = cb,
1051
                .pid = PIDREF_NULL,
1052
                .name = name,
1053
                .userdata = userdata,
1054
        };
1055

1056
        *ret = TAKE_PTR(ctx);
982✔
1057
        return 0;
982✔
1058
}
1059

1060
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
982✔
1061
        CalloutContext *ctx = ASSERT_PTR(userdata);
982✔
1062
        int r;
982✔
1063

1064
        assert(s);
982✔
1065
        assert(si);
982✔
1066
        assert(ctx);
982✔
1067

1068
        if (si->si_code == CLD_EXITED) {
982✔
1069
                if (si->si_status == EXIT_SUCCESS) {
982✔
1070
                        r = 0;
966✔
1071
                        log_debug("%s succeeded.", ctx->name);
966✔
1072
                } else if (ctx->helper_errno != 0) {
16✔
1073
                        r = -ctx->helper_errno;
16✔
1074
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
16✔
1075
                } else {
1076
                        r = -EPROTO;
×
1077
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
1078
                }
1079
        } else {
1080
                r = -EPROTO;
×
1081
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
1082
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
1083
                else
1084
                        log_error("%s failed due to unknown reason.", ctx->name);
×
1085
        }
1086

1087
        return sd_event_exit(sd_event_source_get_event(s), r);
982✔
1088
}
1089

1090
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
1,828✔
1091
        CalloutContext *ctx = ASSERT_PTR(userdata);
1,828✔
1092
        int r;
1,828✔
1093

1094
        assert(fd >= 0);
1,828✔
1095

1096
        _cleanup_free_ char *buf = NULL;
1,828✔
1097
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
1,828✔
1098
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
1,828✔
1099
        if (r == -EAGAIN)
1,828✔
1100
                return 0;
1101
        if (r < 0)
1,828✔
1102
                return r;
1103

1104
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
1,828✔
1105
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1106
                return 0;
1107
        }
1108

1109
        char *errno_str = find_line_startswith(buf, "ERRNO=");
1,828✔
1110
        if (errno_str) {
1,828✔
1111
                truncate_nl(errno_str);
16✔
1112
                r = parse_errno(errno_str);
16✔
1113
                if (r < 0)
16✔
1114
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1115
                else {
1116
                        ctx->helper_errno = r;
16✔
1117
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
16✔
1118
                }
1119
        }
1120

1121
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
1,828✔
1122
        if (progress_str) {
1,828✔
1123
                truncate_nl(progress_str);
830✔
1124

1125
                int progress = parse_percent(progress_str);
830✔
1126
                if (progress < 0)
830✔
1127
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1128
                else {
1129
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
830✔
1130
                        if (r < 0)
830✔
1131
                                return r;
×
1132
                }
1133
        }
1134

1135
        return 0;
1136
}
1137

1138
static int run_callout(
982✔
1139
                const char *name,
1140
                char *cmdline[],
1141
                const Transfer *transfer,
1142
                const Instance *instance,
1143
                TransferProgress callback,
1144
                void *userdata) {
1145

1146
        int r;
982✔
1147

1148
        assert(name);
982✔
1149
        assert(cmdline);
982✔
1150
        assert(cmdline[0]);
982✔
1151

1152
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1153
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
982✔
1154
        if (r < 0)
982✔
1155
                return log_oom();
×
1156

1157
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
982✔
1158
        r = sd_event_new(&event);
982✔
1159
        if (r < 0)
982✔
1160
                return log_error_errno(r, "Failed to create event: %m");
×
1161

1162
        /* Kill the helper & return an error if we get interrupted by a signal */
1163
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
982✔
1164
        if (r < 0)
982✔
1165
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1166
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
982✔
1167
        if (r < 0)
982✔
1168
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1169

1170
        _cleanup_free_ char *bind_name = NULL;
982✔
1171
        r = notify_socket_prepare(
982✔
1172
                        event,
1173
                        SD_EVENT_PRIORITY_NORMAL - 5,
1174
                        helper_on_notify,
1175
                        ctx,
1176
                        &bind_name);
1177
        if (r < 0)
982✔
1178
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1179

1180
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
982✔
1181
        if (r < 0)
1,964✔
1182
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1183
        if (r == 0) {
1,964✔
1184
                /* Child */
1185
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
982✔
1186
                        log_error_errno(errno, "setenv() failed: %m");
×
1187
                        _exit(EXIT_FAILURE);
×
1188
                }
1189
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
982✔
1190
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
1191
                _exit(EXIT_FAILURE);
×
1192
        }
1193

1194
        /* Quit the loop w/ when child process exits */
1195
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
982✔
1196
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
982✔
1197
        if (r < 0)
982✔
1198
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1199

1200
        r = sd_event_source_set_child_process_own(exit_source, true);
982✔
1201
        if (r < 0)
982✔
1202
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1203

1204
        /* Process events until the helper quits */
1205
        return sd_event_loop(event);
982✔
1206
}
1207

1208
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
1209
 * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
1210
 * skipped). */
1211
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
1,982✔
1212
        _cleanup_free_ char *formatted_pattern = NULL;
1,982✔
1213
        int r;
1,982✔
1214

1215
        assert(t);
1,982✔
1216
        assert(i);
1,982✔
1217

1218
        assert(!t->final_path);
1,982✔
1219
        assert(!t->temporary_partial_path);
1,982✔
1220
        assert(!t->temporary_pending_path);
1,982✔
1221
        assert(!t->final_partition_label);
1,982✔
1222
        assert(!strv_isempty(t->target.patterns));
1,982✔
1223

1224
        /* Format the target name using the first pattern specified */
1225
        compile_pattern_fields(t, i, f);
1,982✔
1226
        r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
1,982✔
1227
        if (r < 0)
1,982✔
1228
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1229

1230
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1,982✔
1231
                _cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
1,430✔
1232

1233
                if (!path_is_safe(formatted_pattern))
1,430✔
1234
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1235

1236
                t->final_path = path_join(t->target.path, formatted_pattern);
1,430✔
1237
                if (!t->final_path)
1,430✔
1238
                        return log_oom();
×
1239

1240
                /* Build the paths for the partial and pending files, which hold the resource while it’s
1241
                 * being acquired and after it’s been acquired (but before it’s moved to the final_path
1242
                 * when it’s installed).
1243
                 *
1244
                 * Split the filename off the `final_path`, then add a prefix to it for each of partial and
1245
                 * pending, then join them back on to the same directory. */
1246
                r = path_split_prefix_filename(t->final_path, &final_dir, &final_filename);
1,430✔
1247
                if (r < 0)
1,430✔
1248
                        return log_error_errno(r, "Failed to parse path: %m");
×
1249

1250
                if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
1,430✔
1251
                        return log_oom();
×
1252

1253
                if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
1,430✔
1254
                        return log_oom();
×
1255

1256
                t->temporary_partial_path = path_join(final_dir, partial_filename);
1,430✔
1257
                if (!t->temporary_partial_path)
1,430✔
1258
                        return log_oom();
×
1259

1260
                t->temporary_pending_path = path_join(final_dir, pending_filename);
1,430✔
1261
                if (!t->temporary_pending_path)
1,430✔
1262
                        return log_oom();
×
1263
        }
1264

1265
        if (t->target.type == RESOURCE_PARTITION) {
1,982✔
1266
                r = gpt_partition_label_valid(formatted_pattern);
552✔
1267
                if (r < 0)
552✔
1268
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1269
                if (!r)
552✔
1270
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1271

1272
                if (!t->target.partition_type_set)
552✔
1273
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition type must be set for partition targets.");
×
1274

1275
                /* Derive temporary partition type UUIDs for partial/pending states from the configured
1276
                 * partition type. This avoids the need for label prefixes. */
1277
                r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
552✔
1278
                if (r < 0)
552✔
1279
                        return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
×
1280

1281
                r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
552✔
1282
                if (r < 0)
552✔
1283
                        return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
×
1284

1285
                t->final_partition_label = TAKE_PTR(formatted_pattern);
552✔
1286
        }
1287

1288
        return 0;
1289
}
1290

1291
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
982✔
1292
        _cleanup_free_ char *digest = NULL;
982✔
1293
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
982✔
1294
        const char *where = NULL;
982✔
1295
        Instance *existing;
982✔
1296
        int r;
982✔
1297

1298
        assert(t);
982✔
1299
        assert(i);
982✔
1300
        assert(f);
982✔
1301
        assert(i->resource == &t->source);
982✔
1302
        assert(cb);
982✔
1303

1304
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1305
        existing = resource_find_instance(&t->target, i->metadata.version);
982✔
1306
        if (existing && (existing->is_partial || existing->is_pending))
982✔
1307
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
×
1308
        if (existing) {
982✔
1309
                log_info("No need to acquire '%s', already installed.", i->path);
×
1310
                return 0;
1311
        }
1312

1313
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
982✔
1314
                r = mkdir_parents(t->temporary_partial_path, 0755);
694✔
1315
                if (r < 0)
694✔
1316
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1317

1318
                r = mkdir_parents(t->temporary_pending_path, 0755);
694✔
1319
                if (r < 0)
694✔
1320
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1321

1322
                r = mkdir_parents(t->final_path, 0755);
694✔
1323
                if (r < 0)
694✔
1324
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1325

1326
                where = t->final_path;
694✔
1327
        }
1328

1329
        if (t->target.type == RESOURCE_PARTITION) {
982✔
1330
                r = find_suitable_partition(
288✔
1331
                                t->target.path,
288✔
1332
                                i->metadata.size,
1333
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
288✔
1334
                                &t->partition_info);
1335
                if (r < 0)
288✔
1336
                        return r;
1337

1338
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
288✔
1339
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
288✔
1340

1341
                where = t->partition_info.device;
288✔
1342

1343
                /* Set the partition label and change the partition type to the derived "partial" type UUID
1344
                 * to indicate that a transfer to it is in progress. */
1345
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
288✔
1346
                if (r < 0)
288✔
1347
                        return r;
1348
                t->partition_info.type = t->partition_type_partial;
288✔
1349
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
288✔
1350

1351
                log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
288✔
1352
                          t->partition_info.device,
1353
                          t->partition_info.label,
1354
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1355
                r = patch_partition(
576✔
1356
                                t->target.path,
288✔
1357
                                &t->partition_info,
1358
                                t->partition_change);
1359
                if (r < 0)
288✔
1360
                        return r;
1361
        }
1362

1363
        assert(where);
982✔
1364

1365
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
1,964✔
1366

1367
        if (RESOURCE_IS_URL(i->resource->type)) {
982✔
1368
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1369
                 * download. */
1370

1371
                if (!i->metadata.sha256sum_set)
119✔
1372
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 checksum not known for download '%s', refusing.", i->path);
×
1373

1374
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
119✔
1375
                if (!digest)
119✔
1376
                        return log_oom();
×
1377
        }
1378

1379
        switch (i->resource->type) { /* Source */
982✔
1380

1381
        case RESOURCE_REGULAR_FILE:
780✔
1382

1383
                switch (t->target.type) { /* Target */
780✔
1384

1385
                case RESOURCE_REGULAR_FILE:
556✔
1386

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

1392
                        r = run_callout("(sd-import-raw)",
1,112✔
1393
                                        STRV_MAKE(
556✔
1394
                                               SYSTEMD_IMPORT_PATH,
1395
                                               "raw",
1396
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1397
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1398
                                               i->path,
1399
                                               t->temporary_partial_path),
1400
                                        t, i, cb, userdata);
1401
                        break;
780✔
1402

1403
                case RESOURCE_PARTITION:
224✔
1404

1405
                        /* regular file → partition */
1406

1407
                        r = run_callout("(sd-import-raw)",
448✔
1408
                                        STRV_MAKE(
224✔
1409
                                               SYSTEMD_IMPORT_PATH,
1410
                                               "raw",
1411
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1412
                                               "--offset", offset,
1413
                                               "--size-max", max_size,
1414
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1415
                                               i->path,
1416
                                               t->target.path),
1417
                                        t, i, cb, userdata);
1418
                        break;
1419

1420
                default:
×
1421
                        assert_not_reached();
×
1422
                }
1423

1424
                break;
780✔
1425

1426
        case RESOURCE_DIRECTORY:
83✔
1427
        case RESOURCE_SUBVOLUME:
1428
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1429

1430
                /* directory/subvolume → directory/subvolume */
1431

1432
                r = run_callout("(sd-import-fs)",
166✔
1433
                                STRV_MAKE(
166✔
1434
                                       SYSTEMD_IMPORT_FS_PATH,
1435
                                       "run",
1436
                                       "--direct",          /* just untar the specified file, don't do anything else */
1437
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1438
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1439
                                       i->path,
1440
                                       t->temporary_partial_path),
1441
                                t, i, cb, userdata);
1442
                break;
1443

1444
        case RESOURCE_TAR:
×
1445
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1446

1447
                /* tar → directory/subvolume */
1448

1449
                r = run_callout("(sd-import-tar)",
×
1450
                                STRV_MAKE(
×
1451
                                       SYSTEMD_IMPORT_PATH,
1452
                                       "tar",
1453
                                       "--direct",          /* just untar the specified file, don't do anything else */
1454
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1455
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1456
                                       i->path,
1457
                                       t->temporary_partial_path),
1458
                                t, i, cb, userdata);
1459
                break;
1460

1461
        case RESOURCE_URL_FILE:
71✔
1462

1463
                switch (t->target.type) {
71✔
1464

1465
                case RESOURCE_REGULAR_FILE:
7✔
1466

1467
                        /* url file → regular file */
1468

1469
                        r = run_callout("(sd-pull-raw)",
14✔
1470
                                       STRV_MAKE(
7✔
1471
                                               SYSTEMD_PULL_PATH,
1472
                                               "raw",
1473
                                               "--direct",          /* just download the specified URL, don't download anything else */
1474
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1475
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1476
                                               i->path,
1477
                                               t->temporary_partial_path),
1478
                                        t, i, cb, userdata);
1479
                        break;
71✔
1480

1481
                case RESOURCE_PARTITION:
64✔
1482

1483
                        /* url file → partition */
1484

1485
                        r = run_callout("(sd-pull-raw)",
128✔
1486
                                        STRV_MAKE(
64✔
1487
                                               SYSTEMD_PULL_PATH,
1488
                                               "raw",
1489
                                               "--direct",              /* just download the specified URL, don't download anything else */
1490
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1491
                                               "--offset", offset,
1492
                                               "--size-max", max_size,
1493
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1494
                                               i->path,
1495
                                               t->target.path),
1496
                                        t, i, cb, userdata);
1497
                        break;
1498

1499
                default:
×
1500
                        assert_not_reached();
×
1501
                }
1502

1503
                break;
71✔
1504

1505
        case RESOURCE_URL_TAR:
48✔
1506
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
48✔
1507

1508
                r = run_callout("(sd-pull-tar)",
96✔
1509
                                STRV_MAKE(
96✔
1510
                                       SYSTEMD_PULL_PATH,
1511
                                       "tar",
1512
                                       "--direct",          /* just download the specified URL, don't download anything else */
1513
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1514
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1515
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1516
                                       i->path,
1517
                                       t->temporary_partial_path),
1518
                                t, i, cb, userdata);
1519
                break;
1520

1521
        default:
×
1522
                assert_not_reached();
×
1523
        }
1524
        if (r < 0)
982✔
1525
                return r;
1526

1527
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
966✔
1528
                bool need_sync = false;
694✔
1529
                assert(t->temporary_partial_path);
694✔
1530
                assert(t->temporary_pending_path);
694✔
1531

1532
                /* Apply file attributes if set */
1533
                if (f->mtime != USEC_INFINITY) {
694✔
1534
                        struct timespec ts;
639✔
1535

1536
                        timespec_store(&ts, f->mtime);
639✔
1537

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

1541
                        need_sync = true;
639✔
1542
                }
1543

1544
                if (f->mode != MODE_INVALID) {
694✔
1545
                        /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1546
                         * kernels don't support that however, in that case we fall back to chmod(). Not as
1547
                         * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1548
                        if (fchmodat(AT_FDCWD, t->temporary_partial_path, f->mode, AT_SYMLINK_NOFOLLOW) < 0 &&
639✔
1549
                            (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f->mode) < 0))
×
1550
                                return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path);
×
1551

1552
                        need_sync = true;
1553
                }
1554

1555
                /* Synchronize */
1556
                if (t->context->sync && need_sync) {
694✔
1557
                        if (t->target.type == RESOURCE_REGULAR_FILE)
639✔
1558
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
556✔
1559
                        else {
1560
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1561
                                r = syncfs_path(AT_FDCWD, t->temporary_partial_path);
83✔
1562
                        }
1563
                        if (r < 0)
639✔
1564
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
×
1565
                }
1566

1567
                t->install_read_only = f->read_only;
694✔
1568

1569
                /* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate it’s ready to install. */
1570
                log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
694✔
1571
                r = install_file(AT_FDCWD, t->temporary_partial_path,
2,082✔
1572
                                 AT_FDCWD, t->temporary_pending_path,
694✔
1573
                                 INSTALL_REPLACE|
694✔
1574
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,388✔
1575
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
694✔
1576
                if (r < 0)
694✔
1577
                        return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
×
1578
        }
1579

1580
        if (t->target.type == RESOURCE_PARTITION) {
966✔
1581
                /* Now change the partition type to the derived "pending" type UUID to indicate that the
1582
                 * acquire is complete and the partition is ready for install. */
1583
                t->partition_info.type = t->partition_type_pending;
272✔
1584
                t->partition_change = PARTITION_TYPE;
272✔
1585

1586
                if (f->partition_uuid_set) {
272✔
1587
                        t->partition_info.uuid = f->partition_uuid;
×
1588
                        t->partition_change |= PARTITION_UUID;
×
1589
                }
1590

1591
                if (f->partition_flags_set) {
272✔
1592
                        t->partition_info.flags = f->partition_flags;
×
1593
                        t->partition_change |= PARTITION_FLAGS;
×
1594
                }
1595

1596
                if (f->no_auto >= 0) {
272✔
1597
                        t->partition_info.no_auto = f->no_auto;
×
1598
                        t->partition_change |= PARTITION_NO_AUTO;
×
1599
                }
1600

1601
                if (f->read_only >= 0) {
272✔
1602
                        t->partition_info.read_only = f->read_only;
×
1603
                        t->partition_change |= PARTITION_READ_ONLY;
×
1604
                }
1605

1606
                if (f->growfs >= 0) {
272✔
1607
                        t->partition_info.growfs = f->growfs;
×
1608
                        t->partition_change |= PARTITION_GROWFS;
×
1609
                }
1610

1611
                log_debug("Marking partition '%s' as pending (type=%s).",
272✔
1612
                          t->partition_info.device,
1613
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1614
                r = patch_partition(
544✔
1615
                                t->target.path,
272✔
1616
                                &t->partition_info,
272✔
1617
                                t->partition_change);
1618
                if (r < 0)
272✔
1619
                        return r;
1620
        }
1621

1622
        /* For regular file cases the only step left is to install the file in place, which install_file()
1623
         * will do via rename(). For partition cases the only step left is to update the partition table,
1624
         * which is done at the same place. */
1625

1626
        log_info("Successfully acquired '%s'.", i->path);
966✔
1627
        return 0;
1628
}
1629

1630
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
796✔
1631
        InstanceMetadata f;
796✔
1632
        Instance *existing;
796✔
1633
        int r;
796✔
1634

1635
        assert(t);
796✔
1636
        assert(i);
796✔
1637

1638
        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
796✔
1639

1640
        /* Does this instance already exist in the target but isn’t pending? */
1641
        existing = resource_find_instance(&t->target, i->metadata.version);
796✔
1642
        if (existing && !existing->is_pending) {
796✔
1643
                log_info("Resource '%s' instance is already in the target but is not pending.", i->path);
84✔
1644
                return 0;
796✔
1645
        }
1646

1647
        /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
1648
         * transfer_acquire_instance(). */
1649
        r = transfer_compute_temporary_paths(t, i, &f);
712✔
1650
        if (r < 0)
712✔
1651
                return r;
1652

1653
        /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
1654
         * already happened in the acquire phase, the target should already have that information and it
1655
         * should already have been claimed with the pending partition type UUID. */
1656
        if (t->target.type == RESOURCE_PARTITION) {
712✔
1657
                assert(i->resource == &t->target);
200✔
1658
                assert(i->is_pending);
200✔
1659

1660
                r = partition_info_copy(&t->partition_info, &i->partition_info);
200✔
1661
                if (r < 0)
200✔
1662
                        return r;
×
1663
        }
1664

1665
        return 0;
1666
}
1667

1668
int transfer_install_instance(
950✔
1669
                Transfer *t,
1670
                Instance *i,
1671
                const char *root) {
1672

1673
        int r;
950✔
1674

1675
        assert(t);
950✔
1676
        assert(i);
950✔
1677
        assert(i->resource);
950✔
1678
        assert(i->is_pending || t == container_of(i->resource, Transfer, source));
950✔
1679

1680
        log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
950✔
1681

1682
        if (t->temporary_pending_path) {
950✔
1683
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
694✔
1684
                assert(t->final_path);
694✔
1685

1686
                r = install_file(AT_FDCWD, t->temporary_pending_path,
2,082✔
1687
                                 AT_FDCWD, t->final_path,
1688
                                 INSTALL_REPLACE|
694✔
1689
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,388✔
1690
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
694✔
1691
                if (r < 0)
694✔
1692
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1693

1694
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
694✔
1695
                         i->path,
1696
                         resource_type_to_string(i->resource->type),
1697
                         t->final_path,
1698
                         resource_type_to_string(t->target.type));
1699

1700
                t->temporary_pending_path = mfree(t->temporary_pending_path);
694✔
1701

1702
                (void) context_installdb_record(t->context, t->target.path, t->target.patterns);
694✔
1703
        }
1704

1705
        if (t->final_partition_label) {
950✔
1706
                assert(t->target.type == RESOURCE_PARTITION);
256✔
1707
                assert(t->target.partition_type_set);
256✔
1708

1709
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
256✔
1710
                if (r < 0)
256✔
1711
                        return r;
1712

1713
                /* Restore the original partition type UUID now that the partition is fully installed. */
1714
                t->partition_info.type = t->target.partition_type.uuid;
256✔
1715
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
256✔
1716

1717
                r = patch_partition(
512✔
1718
                                t->target.path,
256✔
1719
                                &t->partition_info,
256✔
1720
                                t->partition_change);
1721
                if (r < 0)
256✔
1722
                        return r;
1723

1724
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
256✔
1725
                         i->path,
1726
                         resource_type_to_string(i->resource->type),
1727
                         t->partition_info.device,
1728
                         resource_type_to_string(t->target.type));
1729
        }
1730

1731
        if (t->current_symlink) {
950✔
1732
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
128✔
1733
                const char *link_path, *link_target;
128✔
1734
                bool resolve_link_path = false;
128✔
1735

1736
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
128✔
1737

1738
                        assert(t->target.path);
128✔
1739

1740
                        if (path_is_absolute(t->current_symlink)) {
128✔
1741
                                link_path = t->current_symlink;
1742
                                resolve_link_path = true;
1743
                        } else {
1744
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1745
                                if (!buf)
×
1746
                                        return log_oom();
×
1747

1748
                                link_path = buf;
1749
                        }
1750

1751
                        link_target = t->final_path;
128✔
1752

1753
                } else if (t->target.type == RESOURCE_PARTITION) {
×
1754

1755
                        assert(path_is_absolute(t->current_symlink));
×
1756

1757
                        link_path = t->current_symlink;
×
1758
                        link_target = t->partition_info.device;
×
1759

1760
                        resolve_link_path = true;
×
1761
                } else
1762
                        assert_not_reached();
×
1763

1764
                if (resolve_link_path && root) {
128✔
1765
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
1766
                        if (r < 0)
×
1767
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1768

1769
                        link_path = resolved;
×
1770
                }
1771

1772
                if (link_target) {
128✔
1773
                        r = path_extract_directory(link_path, &parent);
128✔
1774
                        if (r < 0)
128✔
1775
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1776

1777
                        r = path_make_relative(parent, link_target, &relative);
128✔
1778
                        if (r < 0)
128✔
1779
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1780

1781
                        r = mkdir_parents(link_path, 0755);
128✔
1782
                        if (r < 0)
128✔
1783
                                return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path);
×
1784

1785
                        r = symlink_atomic(relative, link_path);
128✔
1786
                        if (r < 0)
128✔
1787
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1788
                                                       link_path,
1789
                                                       glyph(GLYPH_ARROW_RIGHT),
1790
                                                       relative);
1791

1792
                        log_info("Updated symlink '%s' %s '%s'.",
128✔
1793
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1794
                }
1795
        }
1796

1797
        return 0;
1798
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc