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

systemd / systemd / 28272947092

26 Jun 2026 08:38PM UTC coverage: 72.893% (+0.2%) from 72.703%
28272947092

push

github

poettering
sysupdate: Address review feedback on CheckNew varlink scaffolding

Follow-up to #42422:

 - Rename process_image() to context_process_image(), since it now
   operates on a Context object.
 - Use IN_SET() in image_type_can_sysupdate() instead of a switch.
 - Name the return parameters of context_list_components() ret_xyz, per
   our coding style.
 - Drop a redundant "else" after a return in vl_method_check_new().

9 of 11 new or added lines in 1 file covered. (81.82%)

12567 existing lines in 144 files now uncovered.

341026 of 467845 relevant lines covered (72.89%)

1339355.33 hits per line

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

72.24
/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-cleanup.h"
39
#include "sysupdate-feature.h"
40
#include "sysupdate-instance.h"
41
#include "sysupdate-pattern.h"
42
#include "sysupdate-resource.h"
43
#include "sysupdate-transfer.h"
44
#include "time-util.h"
45
#include "web-util.h"
46

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

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

54
        free(t->temporary_partial_path);
9,908✔
55
        free(t->temporary_pending_path);
9,908✔
56

57
        free(t->id);
9,908✔
58

59
        free(t->min_version);
9,908✔
60
        strv_free(t->protected_versions);
9,908✔
61
        free(t->current_symlink);
9,908✔
62
        free(t->final_path);
9,908✔
63

64
        strv_free(t->features);
9,908✔
65
        strv_free(t->requisite_features);
9,908✔
66

67
        strv_free(t->changelog);
9,908✔
68
        strv_free(t->appstream);
9,908✔
69

70
        partition_info_destroy(&t->partition_info);
9,908✔
71
        free(t->final_partition_label);
9,908✔
72

73
        resource_destroy(&t->source);
9,908✔
74
        resource_destroy(&t->target);
9,908✔
75

76
        return mfree(t);
9,908✔
77
}
78

79
Transfer* transfer_new(Context *ctx) {
9,908✔
80
        Transfer *t;
9,908✔
81

82
        t = new(Transfer, 1);
9,908✔
83
        if (!t)
9,908✔
84
                return NULL;
85

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

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

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

103
                .partition_info = PARTITION_INFO_NULL,
104

105
                .context = ctx,
106
        };
107

108
        return t;
9,908✔
109
}
110

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

123
        _cleanup_free_ char *resolved = NULL;
×
124
        char ***protected_versions = ASSERT_PTR(data);
×
125
        Transfer *t = ASSERT_PTR(userdata);
×
UNCOV
126
        int r;
×
127

UNCOV
128
        assert(rvalue);
×
129

130
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved);
×
131
        if (r < 0) {
×
UNCOV
132
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
133
                           "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
134
                return 0;
135
        }
136

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

143
        r = strv_extend(protected_versions, resolved);
×
144
        if (r < 0)
×
UNCOV
145
                return log_oom();
×
146

147
        return 0;
148
}
149

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

162
        _cleanup_free_ char *resolved = NULL;
×
163
        char **version = ASSERT_PTR(data);
×
UNCOV
164
        Transfer *t = ASSERT_PTR(userdata);
×
165
        int r;
×
166

167
        assert(rvalue);
×
168

169
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved);
×
UNCOV
170
        if (r < 0) {
×
UNCOV
171
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
172
                           "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
173
                return 0;
174
        }
175

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

UNCOV
182
        return free_and_replace(*version, resolved);
×
183
}
184

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

201
        assert(rvalue);
×
202

UNCOV
203
        if (isempty(rvalue)) {
×
UNCOV
204
                *s = strv_free(*s);
×
205
                return 0;
×
206
        }
207

UNCOV
208
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved);
×
UNCOV
209
        if (r < 0) {
×
UNCOV
210
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
211
                           "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
212
                return 0;
213
        }
214

UNCOV
215
        if (!http_url_is_valid(resolved)) {
×
UNCOV
216
                log_syntax(unit, LOG_WARNING, filename, line, 0,
×
217
                           "%s= URL is not valid, ignoring: %s", lvalue, rvalue);
218
                return 0;
219
        }
220

UNCOV
221
        r = strv_push(s, TAKE_PTR(resolved));
×
UNCOV
222
        if (r < 0)
×
UNCOV
223
                return log_oom();
×
224

225
        return 0;
226
}
227

228
static int config_parse_current_symlink(
1,232✔
229
                const char *unit,
230
                const char *filename,
231
                unsigned line,
232
                const char *section,
233
                unsigned section_line,
234
                const char *lvalue,
235
                int ltype,
236
                const char *rvalue,
237
                void *data,
238
                void *userdata) {
239

240
        _cleanup_free_ char *resolved = NULL;
1,232✔
241
        char **current_symlink = ASSERT_PTR(data);
1,232✔
242
        Transfer *t = ASSERT_PTR(userdata);
1,232✔
243
        int r;
1,232✔
244

245
        assert(rvalue);
1,232✔
246

247
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved);
1,232✔
248
        if (r < 0) {
1,232✔
UNCOV
249
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
250
                           "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
251
                return 0;
252
        }
253

254
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
1,232✔
255
        if (r < 0)
1,232✔
256
                return 0;
257

258
        return free_and_replace(*current_symlink, resolved);
1,232✔
259
}
260

261
static int config_parse_instances_max(
7,412✔
262
                const char *unit,
263
                const char *filename,
264
                unsigned line,
265
                const char *section,
266
                unsigned section_line,
267
                const char *lvalue,
268
                int ltype,
269
                const char *rvalue,
270
                void *data,
271
                void *userdata) {
272

273
        uint64_t *instances_max = data, i;
7,412✔
274
        int r;
7,412✔
275

276
        assert(rvalue);
7,412✔
277
        assert(data);
7,412✔
278

279
        if (isempty(rvalue)) {
7,412✔
UNCOV
280
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
UNCOV
281
                return 0;
×
282
        }
283

284
        r = safe_atou64(rvalue, &i);
7,412✔
285
        if (r < 0) {
7,412✔
UNCOV
286
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
287
                           "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
288
                return 0;
289
        }
290

291
        if (i < 2) {
7,412✔
292
                log_syntax(unit, LOG_WARNING, filename, line, 0,
8✔
293
                           "InstancesMax= value must be at least 2, bumping: %s", rvalue);
294
                *instances_max = 2;
8✔
295
        } else
296
                *instances_max = i;
7,404✔
297

298
        return 0;
299
}
300

301
static int config_parse_resource_pattern(
19,816✔
302
                const char *unit,
303
                const char *filename,
304
                unsigned line,
305
                const char *section,
306
                unsigned section_line,
307
                const char *lvalue,
308
                int ltype,
309
                const char *rvalue,
310
                void *data,
311
                void *userdata) {
312

313
        char ***patterns = ASSERT_PTR(data);
19,816✔
314
        Transfer *t = ASSERT_PTR(userdata);
19,816✔
315
        int r;
19,816✔
316

317
        assert(rvalue);
19,816✔
318

319
        if (isempty(rvalue)) {
19,816✔
UNCOV
320
                *patterns = strv_free(*patterns);
×
UNCOV
321
                return 0;
×
322
        }
323

324
        for (;;) {
64,376✔
325
                _cleanup_free_ char *word = NULL, *resolved = NULL;
22,280✔
326

327
                r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
42,096✔
328
                if (r < 0) {
42,096✔
UNCOV
329
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
330
                                   "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
331
                        return 0;
332
                }
333
                if (r == 0)
42,096✔
334
                        break;
335

336
                r = specifier_printf(word, NAME_MAX, specifier_table, t->context->root, NULL, &resolved);
22,280✔
337
                if (r < 0) {
22,280✔
UNCOV
338
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
339
                                   "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
340
                        return 0;
341
                }
342

343
                if (!pattern_valid(resolved))
22,280✔
344
                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
345
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
346

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

352
        strv_uniq(*patterns);
19,816✔
353
        return 0;
19,816✔
354
}
355

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

372
        assert(rvalue);
19,816✔
373

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

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

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

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

395
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
19,816✔
396

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

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

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

415
        assert(rvalue);
2,496✔
416

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

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

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

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

UNCOV
443
        assert(rvalue);
×
444

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

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

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

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

UNCOV
471
        assert(rvalue);
×
472

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

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

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

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

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

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

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

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

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

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

547
        assert(path);
9,908✔
548
        assert(dirs);
9,908✔
549

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

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

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

575
        t->enabled = transfer_decide_if_enabled(t, known_features);
9,908✔
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

649
        if (!t->target.path && !t->target.path_auto)
9,908✔
UNCOV
650
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
651
                                  "Target specification lacks Path= field.");
652

653
        if (t->target.path &&
9,908✔
654
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
9,908✔
655
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
656
                                  "Target path is not a normalized, absolute path: %s", t->target.path);
657

658
        if (strv_isempty(t->target.patterns)) {
9,908✔
UNCOV
659
                log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification.");
×
UNCOV
660
                strv_free(t->target.patterns);
×
661
                t->target.patterns = strv_copy(t->source.patterns);
×
UNCOV
662
                if (!t->target.patterns)
×
UNCOV
663
                        return log_oom();
×
664
        }
665

666
        if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
9,908✔
UNCOV
667
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
668
                                  "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
669

670
        /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
671
        if (t->instances_max == 0)
9,908✔
672
                t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
2,496✔
673

674
        return 0;
675
}
676

677
int transfer_resolve_paths(
9,908✔
678
                Transfer *t,
679
                const char *root,
680
                const char *node) {
681

682
        int r;
9,908✔
683

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

689
        assert(t);
9,908✔
690

691
        r = resource_resolve_path(&t->source, root, t->context->transfer_source, node);
9,908✔
692
        if (r < 0)
9,908✔
693
                return r;
694

695
        r = resource_resolve_path(&t->target, root, /* relative_to_directory= */ NULL, node);
9,908✔
696
        if (r < 0)
9,908✔
UNCOV
697
                return r;
×
698

699
        return 0;
700
}
701

702
static void transfer_remove_temporary(Transfer *t) {
1,467✔
703
        _cleanup_closedir_ DIR *d = NULL;
2,934✔
704
        int r;
1,467✔
705

706
        assert(t);
1,467✔
707

708
        if (!t->remove_temporary)
1,467✔
709
                return;
710

711
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
1,467✔
712
                return;
713

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

716
        d = opendir(t->target.path);
1,115✔
717
        if (!d) {
1,115✔
UNCOV
718
                if (errno == ENOENT)
×
719
                        return;
720

721
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
1,467✔
722
                return;
723
        }
724

725
        for (;;) {
5,169✔
726
                struct dirent *de;
5,169✔
727

728
                errno = 0;
5,169✔
729
                de = readdir_no_dot(d);
5,169✔
730
                if (!de) {
5,169✔
731
                        if (errno != 0)
1,115✔
UNCOV
732
                                log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
×
733
                        break;
1,115✔
734
                }
735

736
                if (!startswith(de->d_name, ".#"))
4,054✔
737
                        continue;
4,054✔
738

UNCOV
739
                r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
×
UNCOV
740
                if (r == -ENOENT)
×
741
                        continue;
×
UNCOV
742
                if (r < 0) {
×
UNCOV
743
                        log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
×
UNCOV
744
                        continue;
×
745
                }
746

UNCOV
747
                log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
×
748
        }
749
}
750

751
static int transfer_instance_vacuum(
640✔
752
                Transfer *t,
753
                Instance *instance) {
754
        int r;
640✔
755

756
        assert(t);
640✔
757
        assert(instance);
640✔
758

759
        switch (t->target.type) {
640✔
760

761
        case RESOURCE_REGULAR_FILE:
416✔
762
        case RESOURCE_DIRECTORY:
763
        case RESOURCE_SUBVOLUME:
764
                r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
416✔
765
                if (r < 0 && r != -ENOENT)
416✔
UNCOV
766
                        return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
×
767

768
                (void) rmdir_parents(instance->path, t->target.path);
416✔
769

770
                break;
416✔
771

772
        case RESOURCE_PARTITION: {
224✔
773
                PartitionInfo pinfo = instance->partition_info;
224✔
774
                PartitionChange change = PARTITION_LABEL;
224✔
775

776
                /* label "_empty" means "no contents" for our purposes */
777
                pinfo.label = (char*) "_empty";
224✔
778

779
                /* If the partition had a derived partial/pending type UUID, restore the original
780
                 * partition type so that the slot is properly recognized as empty in subsequent
781
                 * scans. */
782
                if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
224✔
783
                        pinfo.type = t->target.partition_type.uuid;
32✔
784
                        change |= PARTITION_TYPE;
32✔
785
                }
786

787
                log_debug("Resetting partition '%s' to empty.", pinfo.device);
224✔
788
                r = patch_partition(t->target.path, &pinfo, change);
224✔
789
                if (r < 0)
224✔
790
                        return r;
×
791

792
                t->target.n_empty++;
224✔
793
                break;
224✔
794
        }
795

UNCOV
796
        default:
×
UNCOV
797
                assert_not_reached();
×
798
        }
799

800
        return 0;
801
}
802

803
int transfer_vacuum(
1,467✔
804
                Transfer *t,
805
                uint64_t space,
806
                const char *extra_protected_version) {
807

808
        uint64_t instances_max, limit;
1,467✔
809
        int r, count = 0;
1,467✔
810

811
        assert(t);
1,467✔
812

813
        transfer_remove_temporary(t);
1,467✔
814

815
        /* First, remove any partial or pending instances (unless protected) */
816
        for (size_t i = 0; i < t->target.n_instances;) {
5,513✔
817
                Instance *instance = t->target.instances[i];
2,579✔
818

819
                assert(instance);
2,579✔
820

821
                if (!instance->is_pending && !instance->is_partial) {
2,579✔
822
                        i++;
2,547✔
823
                        continue;
2,547✔
824
                }
825

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

837
                assert(instance->resource);
32✔
838

839
                log_info("%s Removing old %s '%s' (%s).",
64✔
840
                         glyph(GLYPH_RECYCLING),
841
                         instance->is_partial ? "partial" : "pending",
842
                         instance->path,
843
                         resource_type_to_string(instance->resource->type));
844

845
                r = transfer_instance_vacuum(t, instance);
32✔
846
                if (r < 0)
32✔
847
                        return r;
848

849
                instance_free(instance);
32✔
850
                memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
32✔
851
                t->target.n_instances--;
32✔
852

853
                count++;
32✔
854
        }
855

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

858
        instances_max = t->context->instances_max != UINT64_MAX ? t->context->instances_max : t->instances_max;
1,467✔
859
        assert(instances_max >= 1);
1,467✔
860
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
1,467✔
861
                limit = UINT64_MAX;
862
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
1,115✔
863
                limit = 0;
864
        else if (space > instances_max)
923✔
UNCOV
865
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
866
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
867
        else if (space == instances_max)
923✔
UNCOV
868
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
869
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
870
        else
871
                limit = instances_max - space;
923✔
872

873
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
1,467✔
874
                _cleanup_free_ char *patterns = NULL;
352✔
875
                uint64_t rm, remain;
352✔
876

877
                patterns = strv_join(t->target.patterns, "|");
352✔
878
                if (!patterns)
352✔
879
                        (void) log_oom_debug();
×
880

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

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

906
                rm = LESS_BY(space, t->target.n_empty);
352✔
907
                remain = LESS_BY(t->target.n_instances, rm);
352✔
908
                limit = MIN(limit, remain);
352✔
909
        }
910

911
        while (t->target.n_instances > limit) {
2,075✔
912
                Instance *oldest;
608✔
913
                size_t p = t->target.n_instances - 1;
608✔
914

915
                for (;;) {
608✔
916
                        oldest = t->target.instances[p];
608✔
917
                        assert(oldest);
608✔
918

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

924
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
UNCOV
925
                        if (p == 0) {
×
926
                                oldest = NULL;
927
                                break;
928
                        }
929

UNCOV
930
                        p--;
×
931
                }
932

933
                if (!oldest) /* Nothing more to remove */
608✔
934
                        break;
935

936
                assert(oldest->resource);
608✔
937

938
                log_info("%s Removing %s '%s' (%s).",
1,216✔
939
                         glyph(GLYPH_RECYCLING),
940
                         space == UINT64_MAX ? "disabled" : "old",
941
                         oldest->path,
942
                         resource_type_to_string(oldest->resource->type));
943

944
                r = transfer_instance_vacuum(t, oldest);
608✔
945
                if (r < 0)
608✔
946
                        return r;
947

948
                instance_free(oldest);
608✔
949
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
608✔
950
                t->target.n_instances--;
608✔
951

952
                count++;
608✔
953
        }
954

955
        return count;
956
}
957

958
static void compile_pattern_fields(
1,971✔
959
                const Transfer *t,
960
                const Instance *i,
961
                InstanceMetadata *ret) {
962

963
        assert(t);
1,971✔
964
        assert(i);
1,971✔
965
        assert(ret);
1,971✔
966

967
        *ret = (InstanceMetadata) {
3,942✔
968
                .version = i->metadata.version,
1,971✔
969

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

990
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
1,971✔
991
}
1,971✔
992

993
typedef struct CalloutContext {
994
        const Transfer *transfer;
995
        const Instance *instance;
996
        TransferProgress callback;
997
        PidRef pid;
998
        const char *name;
999
        int helper_errno;
1000
        void* userdata;
1001
} CalloutContext;
1002

1003
static CalloutContext *callout_context_free(CalloutContext *ctx) {
971✔
1004
        if (!ctx)
971✔
1005
                return NULL;
1006

1007
        /* We don't own any data but need to clean up the job pid */
1008
        pidref_done(&ctx->pid);
971✔
1009

1010
        return mfree(ctx);
971✔
1011
}
1012

1013
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
1,942✔
1014

1015
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
971✔
1016
                               const char *name, void* userdata, CalloutContext **ret) {
1017
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
971✔
1018

1019
        assert(t);
971✔
1020
        assert(i);
971✔
1021
        assert(cb);
971✔
1022
        assert(ret);
971✔
1023

1024
        ctx = new(CalloutContext, 1);
971✔
1025
        if (!ctx)
971✔
1026
                return -ENOMEM;
1027

1028
        *ctx = (CalloutContext) {
971✔
1029
                .transfer = t,
1030
                .instance = i,
1031
                .callback = cb,
1032
                .pid = PIDREF_NULL,
1033
                .name = name,
1034
                .userdata = userdata,
1035
        };
1036

1037
        *ret = TAKE_PTR(ctx);
971✔
1038
        return 0;
971✔
1039
}
1040

1041
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
971✔
1042
        CalloutContext *ctx = ASSERT_PTR(userdata);
971✔
1043
        int r;
971✔
1044

1045
        assert(s);
971✔
1046
        assert(si);
971✔
1047
        assert(ctx);
971✔
1048

1049
        if (si->si_code == CLD_EXITED) {
971✔
1050
                if (si->si_status == EXIT_SUCCESS) {
971✔
1051
                        r = 0;
955✔
1052
                        log_debug("%s succeeded.", ctx->name);
955✔
1053
                } else if (ctx->helper_errno != 0) {
16✔
1054
                        r = -ctx->helper_errno;
16✔
1055
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
16✔
1056
                } else {
1057
                        r = -EPROTO;
×
UNCOV
1058
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
1059
                }
1060
        } else {
UNCOV
1061
                r = -EPROTO;
×
UNCOV
1062
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
UNCOV
1063
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
1064
                else
UNCOV
1065
                        log_error("%s failed due to unknown reason.", ctx->name);
×
1066
        }
1067

1068
        return sd_event_exit(sd_event_source_get_event(s), r);
971✔
1069
}
1070

1071
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
1,812✔
1072
        CalloutContext *ctx = ASSERT_PTR(userdata);
1,812✔
1073
        int r;
1,812✔
1074

1075
        assert(fd >= 0);
1,812✔
1076

1077
        _cleanup_free_ char *buf = NULL;
1,812✔
1078
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
1,812✔
1079
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
1,812✔
1080
        if (r == -EAGAIN)
1,812✔
1081
                return 0;
1082
        if (r < 0)
1,812✔
1083
                return r;
1084

1085
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
1,812✔
UNCOV
1086
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1087
                return 0;
1088
        }
1089

1090
        char *errno_str = find_line_startswith(buf, "ERRNO=");
1,812✔
1091
        if (errno_str) {
1,812✔
1092
                truncate_nl(errno_str);
16✔
1093
                r = parse_errno(errno_str);
16✔
1094
                if (r < 0)
16✔
UNCOV
1095
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1096
                else {
1097
                        ctx->helper_errno = r;
16✔
1098
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
16✔
1099
                }
1100
        }
1101

1102
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
1,812✔
1103
        if (progress_str) {
1,812✔
1104
                truncate_nl(progress_str);
825✔
1105

1106
                int progress = parse_percent(progress_str);
825✔
1107
                if (progress < 0)
825✔
UNCOV
1108
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1109
                else {
1110
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
825✔
1111
                        if (r < 0)
825✔
UNCOV
1112
                                return r;
×
1113
                }
1114
        }
1115

1116
        return 0;
1117
}
1118

1119
static int run_callout(
971✔
1120
                const char *name,
1121
                char *cmdline[],
1122
                const Transfer *transfer,
1123
                const Instance *instance,
1124
                TransferProgress callback,
1125
                void *userdata) {
1126

1127
        int r;
971✔
1128

1129
        assert(name);
971✔
1130
        assert(cmdline);
971✔
1131
        assert(cmdline[0]);
971✔
1132

UNCOV
1133
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1134
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
971✔
1135
        if (r < 0)
971✔
UNCOV
1136
                return log_oom();
×
1137

1138
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
971✔
1139
        r = sd_event_new(&event);
971✔
1140
        if (r < 0)
971✔
UNCOV
1141
                return log_error_errno(r, "Failed to create event: %m");
×
1142

1143
        /* Kill the helper & return an error if we get interrupted by a signal */
1144
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
971✔
1145
        if (r < 0)
971✔
UNCOV
1146
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1147
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
971✔
1148
        if (r < 0)
971✔
UNCOV
1149
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1150

1151
        _cleanup_free_ char *bind_name = NULL;
971✔
1152
        r = notify_socket_prepare(
971✔
1153
                        event,
1154
                        SD_EVENT_PRIORITY_NORMAL - 5,
1155
                        helper_on_notify,
1156
                        ctx,
1157
                        &bind_name);
1158
        if (r < 0)
971✔
UNCOV
1159
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1160

1161
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
971✔
1162
        if (r < 0)
1,942✔
UNCOV
1163
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1164
        if (r == 0) {
1,942✔
1165
                /* Child */
1166
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
971✔
UNCOV
1167
                        log_error_errno(errno, "setenv() failed: %m");
×
UNCOV
1168
                        _exit(EXIT_FAILURE);
×
1169
                }
1170
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
971✔
UNCOV
1171
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
UNCOV
1172
                _exit(EXIT_FAILURE);
×
1173
        }
1174

1175
        /* Quit the loop w/ when child process exits */
1176
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
971✔
1177
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
971✔
1178
        if (r < 0)
971✔
UNCOV
1179
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1180

1181
        r = sd_event_source_set_child_process_own(exit_source, true);
971✔
1182
        if (r < 0)
971✔
UNCOV
1183
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1184

1185
        /* Process events until the helper quits */
1186
        return sd_event_loop(event);
971✔
1187
}
1188

1189
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
1190
 * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
1191
 * skipped). */
1192
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
1,971✔
1193
        _cleanup_free_ char *formatted_pattern = NULL;
1,971✔
1194
        int r;
1,971✔
1195

1196
        assert(t);
1,971✔
1197
        assert(i);
1,971✔
1198

1199
        assert(!t->final_path);
1,971✔
1200
        assert(!t->temporary_partial_path);
1,971✔
1201
        assert(!t->temporary_pending_path);
1,971✔
1202
        assert(!t->final_partition_label);
1,971✔
1203
        assert(!strv_isempty(t->target.patterns));
1,971✔
1204

1205
        /* Format the target name using the first pattern specified */
1206
        compile_pattern_fields(t, i, f);
1,971✔
1207
        r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
1,971✔
1208
        if (r < 0)
1,971✔
1209
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1210

1211
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1,971✔
1212
                _cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
1,419✔
1213

1214
                if (!path_is_safe(formatted_pattern))
1,419✔
UNCOV
1215
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1216

1217
                t->final_path = path_join(t->target.path, formatted_pattern);
1,419✔
1218
                if (!t->final_path)
1,419✔
UNCOV
1219
                        return log_oom();
×
1220

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

1231
                if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
1,419✔
UNCOV
1232
                        return log_oom();
×
1233

1234
                if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
1,419✔
UNCOV
1235
                        return log_oom();
×
1236

1237
                t->temporary_partial_path = path_join(final_dir, partial_filename);
1,419✔
1238
                if (!t->temporary_partial_path)
1,419✔
UNCOV
1239
                        return log_oom();
×
1240

1241
                t->temporary_pending_path = path_join(final_dir, pending_filename);
1,419✔
1242
                if (!t->temporary_pending_path)
1,419✔
1243
                        return log_oom();
×
1244
        }
1245

1246
        if (t->target.type == RESOURCE_PARTITION) {
1,971✔
1247
                r = gpt_partition_label_valid(formatted_pattern);
552✔
1248
                if (r < 0)
552✔
UNCOV
1249
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1250
                if (!r)
552✔
UNCOV
1251
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1252

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

1256
                /* Derive temporary partition type UUIDs for partial/pending states from the configured
1257
                 * partition type. This avoids the need for label prefixes. */
1258
                r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
552✔
1259
                if (r < 0)
552✔
UNCOV
1260
                        return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
×
1261

1262
                r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
552✔
1263
                if (r < 0)
552✔
UNCOV
1264
                        return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
×
1265

1266
                t->final_partition_label = TAKE_PTR(formatted_pattern);
552✔
1267
        }
1268

1269
        return 0;
1270
}
1271

1272
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
971✔
1273
        _cleanup_free_ char *digest = NULL;
971✔
1274
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
971✔
1275
        const char *where = NULL;
971✔
1276
        Instance *existing;
971✔
1277
        int r;
971✔
1278

1279
        assert(t);
971✔
1280
        assert(i);
971✔
1281
        assert(f);
971✔
1282
        assert(i->resource == &t->source);
971✔
1283
        assert(cb);
971✔
1284

1285
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1286
        existing = resource_find_instance(&t->target, i->metadata.version);
971✔
1287
        if (existing && (existing->is_partial || existing->is_pending))
971✔
UNCOV
1288
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
×
1289
        if (existing) {
971✔
UNCOV
1290
                log_info("No need to acquire '%s', already installed.", i->path);
×
1291
                return 0;
1292
        }
1293

1294
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
971✔
1295
                r = mkdir_parents(t->temporary_partial_path, 0755);
683✔
1296
                if (r < 0)
683✔
UNCOV
1297
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1298

1299
                r = mkdir_parents(t->temporary_pending_path, 0755);
683✔
1300
                if (r < 0)
683✔
UNCOV
1301
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1302

1303
                r = mkdir_parents(t->final_path, 0755);
683✔
1304
                if (r < 0)
683✔
UNCOV
1305
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1306

1307
                where = t->final_path;
683✔
1308
        }
1309

1310
        if (t->target.type == RESOURCE_PARTITION) {
971✔
1311
                r = find_suitable_partition(
288✔
1312
                                t->target.path,
288✔
1313
                                i->metadata.size,
1314
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
288✔
1315
                                &t->partition_info);
1316
                if (r < 0)
288✔
1317
                        return r;
1318

1319
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
288✔
1320
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
288✔
1321

1322
                where = t->partition_info.device;
288✔
1323

1324
                /* Set the partition label and change the partition type to the derived "partial" type UUID
1325
                 * to indicate that a transfer to it is in progress. */
1326
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
288✔
1327
                if (r < 0)
288✔
1328
                        return r;
1329
                t->partition_info.type = t->partition_type_partial;
288✔
1330
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
288✔
1331

1332
                log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
288✔
1333
                          t->partition_info.device,
1334
                          t->partition_info.label,
1335
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1336
                r = patch_partition(
576✔
1337
                                t->target.path,
288✔
1338
                                &t->partition_info,
1339
                                t->partition_change);
1340
                if (r < 0)
288✔
1341
                        return r;
1342
        }
1343

1344
        assert(where);
971✔
1345

1346
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
1,942✔
1347

1348
        if (RESOURCE_IS_URL(i->resource->type)) {
971✔
1349
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1350
                 * download. */
1351

1352
                if (!i->metadata.sha256sum_set)
114✔
UNCOV
1353
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 checksum not known for download '%s', refusing.", i->path);
×
1354

1355
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
114✔
1356
                if (!digest)
114✔
UNCOV
1357
                        return log_oom();
×
1358
        }
1359

1360
        switch (i->resource->type) { /* Source */
971✔
1361

1362
        case RESOURCE_REGULAR_FILE:
774✔
1363

1364
                switch (t->target.type) { /* Target */
774✔
1365

1366
                case RESOURCE_REGULAR_FILE:
550✔
1367

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

1373
                        r = run_callout("(sd-import-raw)",
1,100✔
1374
                                        STRV_MAKE(
550✔
1375
                                               SYSTEMD_IMPORT_PATH,
1376
                                               "raw",
1377
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1378
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1379
                                               i->path,
1380
                                               t->temporary_partial_path),
1381
                                        t, i, cb, userdata);
1382
                        break;
774✔
1383

1384
                case RESOURCE_PARTITION:
224✔
1385

1386
                        /* regular file → partition */
1387

1388
                        r = run_callout("(sd-import-raw)",
448✔
1389
                                        STRV_MAKE(
224✔
1390
                                               SYSTEMD_IMPORT_PATH,
1391
                                               "raw",
1392
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1393
                                               "--offset", offset,
1394
                                               "--size-max", max_size,
1395
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1396
                                               i->path,
1397
                                               t->target.path),
1398
                                        t, i, cb, userdata);
1399
                        break;
1400

UNCOV
1401
                default:
×
UNCOV
1402
                        assert_not_reached();
×
1403
                }
1404

1405
                break;
774✔
1406

1407
        case RESOURCE_DIRECTORY:
83✔
1408
        case RESOURCE_SUBVOLUME:
1409
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1410

1411
                /* directory/subvolume → directory/subvolume */
1412

1413
                r = run_callout("(sd-import-fs)",
166✔
1414
                                STRV_MAKE(
166✔
1415
                                       SYSTEMD_IMPORT_FS_PATH,
1416
                                       "run",
1417
                                       "--direct",          /* just untar the specified file, don't do anything else */
1418
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1419
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1420
                                       i->path,
1421
                                       t->temporary_partial_path),
1422
                                t, i, cb, userdata);
1423
                break;
1424

1425
        case RESOURCE_TAR:
×
UNCOV
1426
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1427

1428
                /* tar → directory/subvolume */
1429

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

1442
        case RESOURCE_URL_FILE:
66✔
1443

1444
                switch (t->target.type) {
66✔
1445

1446
                case RESOURCE_REGULAR_FILE:
2✔
1447

1448
                        /* url file → regular file */
1449

1450
                        r = run_callout("(sd-pull-raw)",
4✔
1451
                                       STRV_MAKE(
2✔
1452
                                               SYSTEMD_PULL_PATH,
1453
                                               "raw",
1454
                                               "--direct",          /* just download the specified URL, don't download anything else */
1455
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1456
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1457
                                               i->path,
1458
                                               t->temporary_partial_path),
1459
                                        t, i, cb, userdata);
1460
                        break;
66✔
1461

1462
                case RESOURCE_PARTITION:
64✔
1463

1464
                        /* url file → partition */
1465

1466
                        r = run_callout("(sd-pull-raw)",
128✔
1467
                                        STRV_MAKE(
64✔
1468
                                               SYSTEMD_PULL_PATH,
1469
                                               "raw",
1470
                                               "--direct",              /* just download the specified URL, don't download anything else */
1471
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1472
                                               "--offset", offset,
1473
                                               "--size-max", max_size,
1474
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1475
                                               i->path,
1476
                                               t->target.path),
1477
                                        t, i, cb, userdata);
1478
                        break;
1479

UNCOV
1480
                default:
×
UNCOV
1481
                        assert_not_reached();
×
1482
                }
1483

1484
                break;
66✔
1485

1486
        case RESOURCE_URL_TAR:
48✔
1487
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
48✔
1488

1489
                r = run_callout("(sd-pull-tar)",
96✔
1490
                                STRV_MAKE(
96✔
1491
                                       SYSTEMD_PULL_PATH,
1492
                                       "tar",
1493
                                       "--direct",          /* just download the specified URL, don't download anything else */
1494
                                       "--verify", digest,  /* validate by explicit SHA256 sum */
1495
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1496
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1497
                                       i->path,
1498
                                       t->temporary_partial_path),
1499
                                t, i, cb, userdata);
1500
                break;
1501

UNCOV
1502
        default:
×
UNCOV
1503
                assert_not_reached();
×
1504
        }
1505
        if (r < 0)
971✔
1506
                return r;
1507

1508
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
955✔
1509
                bool need_sync = false;
683✔
1510
                assert(t->temporary_partial_path);
683✔
1511
                assert(t->temporary_pending_path);
683✔
1512

1513
                /* Apply file attributes if set */
1514
                if (f->mtime != USEC_INFINITY) {
683✔
1515
                        struct timespec ts;
633✔
1516

1517
                        timespec_store(&ts, f->mtime);
633✔
1518

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

1522
                        need_sync = true;
633✔
1523
                }
1524

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

1533
                        need_sync = true;
1534
                }
1535

1536
                /* Synchronize */
1537
                if (t->context->sync && need_sync) {
683✔
1538
                        if (t->target.type == RESOURCE_REGULAR_FILE)
633✔
1539
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
550✔
1540
                        else {
1541
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1542
                                r = syncfs_path(AT_FDCWD, t->temporary_partial_path);
83✔
1543
                        }
1544
                        if (r < 0)
633✔
UNCOV
1545
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
×
1546
                }
1547

1548
                t->install_read_only = f->read_only;
683✔
1549

1550
                /* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate it’s ready to install. */
1551
                log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
683✔
1552
                r = install_file(AT_FDCWD, t->temporary_partial_path,
2,049✔
1553
                                 AT_FDCWD, t->temporary_pending_path,
683✔
1554
                                 INSTALL_REPLACE|
683✔
1555
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,366✔
1556
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
683✔
1557
                if (r < 0)
683✔
UNCOV
1558
                        return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
×
1559
        }
1560

1561
        if (t->target.type == RESOURCE_PARTITION) {
955✔
1562
                /* Now change the partition type to the derived "pending" type UUID to indicate that the
1563
                 * acquire is complete and the partition is ready for install. */
1564
                t->partition_info.type = t->partition_type_pending;
272✔
1565
                t->partition_change = PARTITION_TYPE;
272✔
1566

1567
                if (f->partition_uuid_set) {
272✔
1568
                        t->partition_info.uuid = f->partition_uuid;
×
UNCOV
1569
                        t->partition_change |= PARTITION_UUID;
×
1570
                }
1571

1572
                if (f->partition_flags_set) {
272✔
1573
                        t->partition_info.flags = f->partition_flags;
×
UNCOV
1574
                        t->partition_change |= PARTITION_FLAGS;
×
1575
                }
1576

1577
                if (f->no_auto >= 0) {
272✔
1578
                        t->partition_info.no_auto = f->no_auto;
×
UNCOV
1579
                        t->partition_change |= PARTITION_NO_AUTO;
×
1580
                }
1581

1582
                if (f->read_only >= 0) {
272✔
1583
                        t->partition_info.read_only = f->read_only;
×
UNCOV
1584
                        t->partition_change |= PARTITION_READ_ONLY;
×
1585
                }
1586

1587
                if (f->growfs >= 0) {
272✔
UNCOV
1588
                        t->partition_info.growfs = f->growfs;
×
UNCOV
1589
                        t->partition_change |= PARTITION_GROWFS;
×
1590
                }
1591

1592
                log_debug("Marking partition '%s' as pending (type=%s).",
272✔
1593
                          t->partition_info.device,
1594
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1595
                r = patch_partition(
544✔
1596
                                t->target.path,
272✔
1597
                                &t->partition_info,
272✔
1598
                                t->partition_change);
1599
                if (r < 0)
272✔
1600
                        return r;
1601
        }
1602

1603
        /* For regular file cases the only step left is to install the file in place, which install_file()
1604
         * will do via rename(). For partition cases the only step left is to update the partition table,
1605
         * which is done at the same place. */
1606

1607
        log_info("Successfully acquired '%s'.", i->path);
955✔
1608
        return 0;
1609
}
1610

1611
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
796✔
1612
        InstanceMetadata f;
796✔
1613
        Instance *existing;
796✔
1614
        int r;
796✔
1615

1616
        assert(t);
796✔
1617
        assert(i);
796✔
1618

1619
        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
796✔
1620

1621
        /* Does this instance already exist in the target but isn’t pending? */
1622
        existing = resource_find_instance(&t->target, i->metadata.version);
796✔
1623
        if (existing && !existing->is_pending) {
796✔
1624
                log_info("Resource '%s' instance is already in the target but is not pending.", i->path);
84✔
1625
                return 0;
796✔
1626
        }
1627

1628
        /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
1629
         * transfer_acquire_instance(). */
1630
        r = transfer_compute_temporary_paths(t, i, &f);
712✔
1631
        if (r < 0)
712✔
1632
                return r;
1633

1634
        /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
1635
         * already happened in the acquire phase, the target should already have that information and it
1636
         * should already have been claimed with the pending partition type UUID. */
1637
        if (t->target.type == RESOURCE_PARTITION) {
712✔
1638
                assert(i->resource == &t->target);
200✔
1639
                assert(i->is_pending);
200✔
1640

1641
                r = partition_info_copy(&t->partition_info, &i->partition_info);
200✔
1642
                if (r < 0)
200✔
UNCOV
1643
                        return r;
×
1644
        }
1645

1646
        return 0;
1647
}
1648

1649
int transfer_install_instance(
939✔
1650
                Transfer *t,
1651
                Instance *i,
1652
                const char *root) {
1653

1654
        int r;
939✔
1655

1656
        assert(t);
939✔
1657
        assert(i);
939✔
1658
        assert(i->resource);
939✔
1659
        assert(i->is_pending || t == container_of(i->resource, Transfer, source));
939✔
1660

1661
        log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
939✔
1662

1663
        if (t->temporary_pending_path) {
939✔
1664
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
683✔
1665
                assert(t->final_path);
683✔
1666

1667
                r = install_file(AT_FDCWD, t->temporary_pending_path,
2,049✔
1668
                                 AT_FDCWD, t->final_path,
1669
                                 INSTALL_REPLACE|
683✔
1670
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,366✔
1671
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
683✔
1672
                if (r < 0)
683✔
UNCOV
1673
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1674

1675
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
683✔
1676
                         i->path,
1677
                         resource_type_to_string(i->resource->type),
1678
                         t->final_path,
1679
                         resource_type_to_string(t->target.type));
1680

1681
                t->temporary_pending_path = mfree(t->temporary_pending_path);
683✔
1682

1683
                (void) context_installdb_record(t->context, t->target.path, t->target.patterns);
683✔
1684
        }
1685

1686
        if (t->final_partition_label) {
939✔
1687
                assert(t->target.type == RESOURCE_PARTITION);
256✔
1688
                assert(t->target.partition_type_set);
256✔
1689

1690
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
256✔
1691
                if (r < 0)
256✔
1692
                        return r;
1693

1694
                /* Restore the original partition type UUID now that the partition is fully installed. */
1695
                t->partition_info.type = t->target.partition_type.uuid;
256✔
1696
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
256✔
1697

1698
                r = patch_partition(
512✔
1699
                                t->target.path,
256✔
1700
                                &t->partition_info,
256✔
1701
                                t->partition_change);
1702
                if (r < 0)
256✔
1703
                        return r;
1704

1705
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
256✔
1706
                         i->path,
1707
                         resource_type_to_string(i->resource->type),
1708
                         t->partition_info.device,
1709
                         resource_type_to_string(t->target.type));
1710
        }
1711

1712
        if (t->current_symlink) {
939✔
1713
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
128✔
1714
                const char *link_path, *link_target;
128✔
1715
                bool resolve_link_path = false;
128✔
1716

1717
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
128✔
1718

1719
                        assert(t->target.path);
128✔
1720

1721
                        if (path_is_absolute(t->current_symlink)) {
128✔
1722
                                link_path = t->current_symlink;
1723
                                resolve_link_path = true;
1724
                        } else {
UNCOV
1725
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
UNCOV
1726
                                if (!buf)
×
UNCOV
1727
                                        return log_oom();
×
1728

1729
                                link_path = buf;
1730
                        }
1731

1732
                        link_target = t->final_path;
128✔
1733

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

UNCOV
1736
                        assert(path_is_absolute(t->current_symlink));
×
1737

UNCOV
1738
                        link_path = t->current_symlink;
×
UNCOV
1739
                        link_target = t->partition_info.device;
×
1740

1741
                        resolve_link_path = true;
×
1742
                } else
UNCOV
1743
                        assert_not_reached();
×
1744

1745
                if (resolve_link_path && root) {
128✔
UNCOV
1746
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
UNCOV
1747
                        if (r < 0)
×
UNCOV
1748
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1749

1750
                        link_path = resolved;
×
1751
                }
1752

1753
                if (link_target) {
128✔
1754
                        r = path_extract_directory(link_path, &parent);
128✔
1755
                        if (r < 0)
128✔
UNCOV
1756
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1757

1758
                        r = path_make_relative(parent, link_target, &relative);
128✔
1759
                        if (r < 0)
128✔
UNCOV
1760
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1761

1762
                        r = mkdir_parents(link_path, 0755);
128✔
1763
                        if (r < 0)
128✔
UNCOV
1764
                                return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path);
×
1765

1766
                        r = symlink_atomic(relative, link_path);
128✔
1767
                        if (r < 0)
128✔
UNCOV
1768
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1769
                                                       link_path,
1770
                                                       glyph(GLYPH_ARROW_RIGHT),
1771
                                                       relative);
1772

1773
                        log_info("Updated symlink '%s' %s '%s'.",
128✔
1774
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1775
                }
1776
        }
1777

1778
        return 0;
1779
}
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