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

systemd / systemd / 21887594812

10 Feb 2026 10:59PM UTC coverage: 72.432% (-0.3%) from 72.697%
21887594812

push

github

web-flow
terminal-util: handle the case where no system console is active (#40630)

/dev/console might have no backing driver, in which case
/sys/class/tty/console/active is empty. Unlike get_kernel_consoles()
resolve_dev_console() currently proceeds with empty devnode, resulting
in setup_input() -> acquire_terminal() emitting -EISDIR as we're trying
to open /dev/. Let's catch this and report -ENXIO.

2 of 6 new or added lines in 1 file covered. (33.33%)

3086 existing lines in 47 files now uncovered.

311026 of 429403 relevant lines covered (72.43%)

1239369.43 hits per line

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

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

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

7
#include "sd-id128.h"
8

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

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

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

53
        free(t->temporary_partial_path);
1,956✔
54
        free(t->temporary_pending_path);
1,956✔
55

56
        free(t->id);
1,956✔
57

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

63
        strv_free(t->features);
1,956✔
64
        strv_free(t->requisite_features);
1,956✔
65

66
        strv_free(t->changelog);
1,956✔
67
        strv_free(t->appstream);
1,956✔
68

69
        partition_info_destroy(&t->partition_info);
1,956✔
70
        free(t->temporary_partial_partition_label);
1,956✔
71
        free(t->temporary_pending_partition_label);
1,956✔
72
        free(t->final_partition_label);
1,956✔
73

74
        resource_destroy(&t->source);
1,956✔
75
        resource_destroy(&t->target);
1,956✔
76

77
        return mfree(t);
1,956✔
78
}
79

80
Transfer* transfer_new(Context *ctx) {
1,956✔
81
        Transfer *t;
1,956✔
82

83
        t = new(Transfer, 1);
1,956✔
84
        if (!t)
1,956✔
85
                return NULL;
86

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

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

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

104
                .partition_info = PARTITION_INFO_NULL,
105

106
                .context = ctx,
107
        };
108

109
        return t;
1,956✔
110
}
111

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

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

128
        assert(rvalue);
×
129

UNCOV
130
        r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_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

UNCOV
143
        r = strv_extend(protected_versions, resolved);
×
UNCOV
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

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

166
        assert(rvalue);
×
167

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

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

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

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

199
        assert(rvalue);
×
200

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

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

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

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

223
        return 0;
224
}
225

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

238
        _cleanup_free_ char *resolved = NULL;
324✔
239
        char **current_symlink = ASSERT_PTR(data);
324✔
240
        int r;
324✔
241

242
        assert(rvalue);
324✔
243

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

251
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
324✔
252
        if (r < 0)
324✔
253
                return 0;
254

255
        return free_and_replace(*current_symlink, resolved);
324✔
256
}
257

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

270
        uint64_t *instances_max = data, i;
1,296✔
271
        int r;
1,296✔
272

273
        assert(rvalue);
1,296✔
274
        assert(data);
1,296✔
275

276
        if (isempty(rvalue)) {
1,296✔
UNCOV
277
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
UNCOV
278
                return 0;
×
279
        }
280

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

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

295
        return 0;
296
}
297

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

310
        char ***patterns = ASSERT_PTR(data);
3,912✔
311
        int r;
3,912✔
312

313
        assert(rvalue);
3,912✔
314

315
        if (isempty(rvalue)) {
3,912✔
UNCOV
316
                *patterns = strv_free(*patterns);
×
UNCOV
317
                return 0;
×
318
        }
319

320
        for (;;) {
13,032✔
321
                _cleanup_free_ char *word = NULL, *resolved = NULL;
4,560✔
322

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

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

339
                if (!pattern_valid(resolved))
4,560✔
UNCOV
340
                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
341
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
342

343
                r = strv_consume(patterns, TAKE_PTR(resolved));
4,560✔
344
                if (r < 0)
4,560✔
UNCOV
345
                        return log_oom();
×
346
        }
347

348
        strv_uniq(*patterns);
3,912✔
349
        return 0;
3,912✔
350
}
351

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

367
        assert(rvalue);
3,912✔
368

369
        if (streq(rvalue, "auto")) {
3,912✔
UNCOV
370
                rr->path_auto = true;
×
UNCOV
371
                rr->path = mfree(rr->path);
×
UNCOV
372
                return 0;
×
373
        }
374

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

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

386
        rr->path_auto = false;
3,912✔
387
        return free_and_replace(rr->path, resolved);
3,912✔
388
}
389

390
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
3,912✔
391

392
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
972✔
393
                                             PATH_RELATIVE_TO_ROOT);
394

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

407
        Resource *rr = ASSERT_PTR(data);
660✔
408
        int r;
660✔
409

410
        assert(rvalue);
660✔
411

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

419
        rr->partition_type_set = true;
660✔
420
        return 0;
660✔
421
}
422

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

435
        Transfer *t = ASSERT_PTR(data);
×
UNCOV
436
        int r;
×
437

438
        assert(rvalue);
×
439

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

UNCOV
447
        t->partition_uuid_set = true;
×
448
        return 0;
×
449
}
450

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

463
        Transfer *t = ASSERT_PTR(data);
×
UNCOV
464
        int r;
×
465

466
        assert(rvalue);
×
467

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

UNCOV
475
        t->partition_flags_set = true;
×
UNCOV
476
        return 0;
×
477
}
478

479
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
1,956✔
480
        assert(t);
1,956✔
481

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

489
        /* No features defined -> transfer implicitly enabled */
490
        if (strv_isempty(t->features))
1,956✔
491
                return true;
492

493
        /* At least one feature enabled -> transfer enabled */
494
        STRV_FOREACH(id, t->features) {
618✔
495
                Feature *f = hashmap_get(known_features, *id);
324✔
496
                if (f && f->enabled)
324✔
497
                        return true;
498
        }
499

500
        /* All listed features disabled -> transfer disabled */
501
        return false;
502
}
503

504
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
1,956✔
505
        assert(t);
1,956✔
506

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

538
        _cleanup_free_ char *filename = NULL;
1,956✔
539
        char *e;
1,956✔
540
        int r;
1,956✔
541

542
        assert(path);
1,956✔
543
        assert(dirs);
1,956✔
544

545
        r = path_extract_filename(path, &filename);
1,956✔
546
        if (r < 0)
1,956✔
UNCOV
547
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
548

549
        r = config_parse_many_full(
5,868✔
550
                        STRV_MAKE_CONST(path),
1,956✔
551
                        dirs,
552
                        strjoina(filename, ".d"),
9,780✔
553
                        arg_root,
554
                        /* root_fd= */ -EBADF,
555
                        "Transfer\0"
556
                        "Source\0"
557
                        "Target\0",
558
                        config_item_table_lookup, table,
559
                        CONFIG_PARSE_WARN,
560
                        /* userdata= */ NULL,
561
                        /* stats_by_path= */ NULL,
562
                        /* drop_in_files= */ NULL);
563
        if (r < 0)
1,956✔
564
                return r;
565

566
        e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
1,956✔
567
        *e = 0; /* Remove the file extension */
1,956✔
568
        t->id = TAKE_PTR(filename);
1,956✔
569

570
        t->enabled = transfer_decide_if_enabled(t, known_features);
1,956✔
571

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

576
        if (t->target.type < 0) {
1,956✔
UNCOV
577
                switch (t->source.type) {
×
578

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

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

UNCOV
592
                case RESOURCE_SUBVOLUME:
×
593
                        t->target.type = RESOURCE_SUBVOLUME;
×
594
                        break;
×
595

UNCOV
596
                default:
×
UNCOV
597
                        assert_not_reached();
×
598
                }
599
        }
600

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

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

613
        if (!t->source.path && !t->source.path_auto)
1,956✔
UNCOV
614
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
615
                                  "Source specification lacks Path=.");
616

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

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

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

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

640
        if (strv_isempty(t->source.patterns))
1,956✔
UNCOV
641
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
642
                                  "Source specification lacks MatchPattern=.");
643

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

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

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

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

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

669
        return 0;
670
}
671

672
int transfer_resolve_paths(
1,956✔
673
                Transfer *t,
674
                const char *root,
675
                const char *node) {
676

677
        int r;
1,956✔
678

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

684
        assert(t);
1,956✔
685

686
        r = resource_resolve_path(&t->source, root, arg_transfer_source, node);
1,956✔
687
        if (r < 0)
1,956✔
688
                return r;
689

690
        r = resource_resolve_path(&t->target, root, /* relative_to_directory= */ NULL, node);
1,956✔
691
        if (r < 0)
1,956✔
UNCOV
692
                return r;
×
693

694
        return 0;
695
}
696

697
static void transfer_remove_temporary(Transfer *t) {
306✔
698
        _cleanup_closedir_ DIR *d = NULL;
306✔
699
        int r;
306✔
700

701
        assert(t);
306✔
702

703
        if (!t->remove_temporary)
306✔
704
                return;
705

706
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
306✔
707
                return;
708

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

711
        d = opendir(t->target.path);
210✔
712
        if (!d) {
210✔
713
                if (errno == ENOENT)
×
714
                        return;
715

UNCOV
716
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
×
UNCOV
717
                return;
×
718
        }
719

720
        for (;;) {
798✔
721
                struct dirent *de;
798✔
722

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

731
                if (!startswith(de->d_name, ".#"))
588✔
732
                        continue;
588✔
733

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

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

746
static int transfer_instance_vacuum(
150✔
747
                Transfer *t,
748
                Instance *instance) {
749
        int r;
150✔
750

751
        assert(t);
150✔
752
        assert(instance);
150✔
753

754
        switch (t->target.type) {
150✔
755

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

763
                (void) rmdir_parents(instance->path, t->target.path);
90✔
764

765
                break;
90✔
766

767
        case RESOURCE_PARTITION: {
60✔
768
                PartitionInfo pinfo = instance->partition_info;
60✔
769

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

773
                log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
60✔
774
                r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
60✔
775
                if (r < 0)
60✔
UNCOV
776
                        return r;
×
777

778
                t->target.n_empty++;
60✔
779
                break;
60✔
780
        }
781

UNCOV
782
        default:
×
UNCOV
783
                assert_not_reached();
×
784
        }
785

786
        return 0;
787
}
788

789
int transfer_vacuum(
306✔
790
                Transfer *t,
791
                uint64_t space,
792
                const char *extra_protected_version) {
793

794
        uint64_t instances_max, limit;
306✔
795
        int r, count = 0;
306✔
796

797
        assert(t);
306✔
798

799
        transfer_remove_temporary(t);
306✔
800

801
        /* First, remove any partial or pending instances (unless protected) */
802
        for (size_t i = 0; i < t->target.n_instances;) {
738✔
803
                Instance *instance = t->target.instances[i];
432✔
804

805
                assert(instance);
432✔
806

807
                if (!instance->is_pending && !instance->is_partial) {
432✔
808
                        i++;
432✔
809
                        continue;
432✔
810
                }
811

812
                /* If this is listed among the protected versions, then let's not remove it */
UNCOV
813
                if (strv_contains(t->protected_versions, instance->metadata.version) ||
×
UNCOV
814
                    (extra_protected_version && streq(extra_protected_version, instance->metadata.version))) {
×
UNCOV
815
                        log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version);
×
UNCOV
816
                        i++;
×
UNCOV
817
                        continue;
×
818
                }
819

UNCOV
820
                assert(instance->resource);
×
821

UNCOV
822
                log_info("%s Removing old %s '%s' (%s).",
×
823
                         glyph(GLYPH_RECYCLING),
824
                         instance->is_partial ? "partial" : "pending",
825
                         instance->path,
826
                         resource_type_to_string(instance->resource->type));
827

UNCOV
828
                r = transfer_instance_vacuum(t, instance);
×
829
                if (r < 0)
×
830
                        return 0;
831

UNCOV
832
                instance_free(instance);
×
UNCOV
833
                memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
×
UNCOV
834
                t->target.n_instances--;
×
835

UNCOV
836
                count++;
×
837
        }
838

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

841
        instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
306✔
842
        assert(instances_max >= 1);
306✔
843
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
306✔
844
                limit = UINT64_MAX;
845
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
210✔
846
                limit = 0;
847
        else if (space > instances_max)
156✔
UNCOV
848
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
849
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
850
        else if (space == instances_max)
156✔
UNCOV
851
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
852
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
853
        else
854
                limit = instances_max - space;
156✔
855

856
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
306✔
857
                _cleanup_free_ char *patterns = NULL;
96✔
858
                uint64_t rm, remain;
96✔
859

860
                patterns = strv_join(t->target.patterns, "|");
96✔
861
                if (!patterns)
96✔
UNCOV
862
                        (void) log_oom_debug();
×
863

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

867
                if (t->target.n_empty + t->target.n_instances < 2)
96✔
UNCOV
868
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
869
                                               "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s, refusing.",
870
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
871
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
872
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
873
                                               strempty(patterns),
874
                                               !isempty(patterns) ? "'" : "");
875
                if (space > t->target.n_empty + t->target.n_instances)
96✔
UNCOV
876
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
877
                                               "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s)%s%s%s for operation.",
878
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
879
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid),
880
                                               !isempty(patterns) ? " and matching the expected pattern '" : "",
881
                                               strempty(patterns),
882
                                               !isempty(patterns) ? "'" : "");
883
                if (space == t->target.n_empty + t->target.n_instances)
96✔
UNCOV
884
                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
885
                                               "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.",
886
                                               SD_ID128_FORMAT_VAL(t->target.partition_type.uuid),
887
                                               gpt_partition_type_uuid_to_string(t->target.partition_type.uuid));
888

889
                rm = LESS_BY(space, t->target.n_empty);
96✔
890
                remain = LESS_BY(t->target.n_instances, rm);
96✔
891
                limit = MIN(limit, remain);
96✔
892
        }
893

894
        while (t->target.n_instances > limit) {
456✔
895
                Instance *oldest;
150✔
896
                size_t p = t->target.n_instances - 1;
150✔
897

898
                for (;;) {
150✔
899
                        oldest = t->target.instances[p];
150✔
900
                        assert(oldest);
150✔
901

902
                        /* If this is listed among the protected versions, then let's not remove it */
903
                        if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
150✔
904
                            (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
144✔
905
                                break;
906

UNCOV
907
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
UNCOV
908
                        if (p == 0) {
×
909
                                oldest = NULL;
910
                                break;
911
                        }
912

UNCOV
913
                        p--;
×
914
                }
915

916
                if (!oldest) /* Nothing more to remove */
150✔
917
                        break;
918

919
                assert(oldest->resource);
150✔
920

921
                log_info("%s Removing %s '%s' (%s).",
300✔
922
                         glyph(GLYPH_RECYCLING),
923
                         space == UINT64_MAX ? "disabled" : "old",
924
                         oldest->path,
925
                         resource_type_to_string(oldest->resource->type));
926

927
                r = transfer_instance_vacuum(t, oldest);
150✔
928
                if (r < 0)
150✔
929
                        return 0;
930

931
                instance_free(oldest);
150✔
932
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
150✔
933
                t->target.n_instances--;
150✔
934

935
                count++;
150✔
936
        }
937

938
        return count;
939
}
940

941
static void compile_pattern_fields(
342✔
942
                const Transfer *t,
943
                const Instance *i,
944
                InstanceMetadata *ret) {
945

946
        assert(t);
342✔
947
        assert(i);
342✔
948
        assert(ret);
342✔
949

950
        *ret = (InstanceMetadata) {
684✔
951
                .version = i->metadata.version,
342✔
952

953
                /* We generally prefer explicitly configured values for the transfer over those automatically
954
                 * derived from the source instance. Also, if the source is a tar archive, then let's not
955
                 * patch mtime/mode and use the one embedded in the tar file */
956
                .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
342✔
957
                .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
342✔
958
                .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
342✔
959
                .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
342✔
960
                .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
342✔
961
                .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
342✔
962
                .size = i->metadata.size,
342✔
963
                .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
342✔
964
                              i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
276✔
965
                .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
342✔
966
                              i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
276✔
967
                .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
342✔
968
                .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
342✔
969
                .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
342✔
970
                .sha256sum_set = i->metadata.sha256sum_set,
342✔
971
        };
972

973
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
342✔
974
}
342✔
975

976
typedef struct CalloutContext {
977
        const Transfer *transfer;
978
        const Instance *instance;
979
        TransferProgress callback;
980
        PidRef pid;
981
        const char *name;
982
        int helper_errno;
983
        void* userdata;
984
} CalloutContext;
985

986
static CalloutContext *callout_context_free(CalloutContext *ctx) {
222✔
987
        if (!ctx)
222✔
988
                return NULL;
989

990
        /* We don't own any data but need to clean up the job pid */
991
        pidref_done(&ctx->pid);
222✔
992

993
        return mfree(ctx);
222✔
994
}
995

996
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
444✔
997

998
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
222✔
999
                               const char *name, void* userdata, CalloutContext **ret) {
1000
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
222✔
1001

1002
        assert(t);
222✔
1003
        assert(i);
222✔
1004
        assert(cb);
222✔
1005

1006
        ctx = new(CalloutContext, 1);
222✔
1007
        if (!ctx)
222✔
1008
                return -ENOMEM;
1009

1010
        *ctx = (CalloutContext) {
222✔
1011
                .transfer = t,
1012
                .instance = i,
1013
                .callback = cb,
1014
                .pid = PIDREF_NULL,
1015
                .name = name,
1016
                .userdata = userdata,
1017
        };
1018

1019
        *ret = TAKE_PTR(ctx);
222✔
1020
        return 0;
222✔
1021
}
1022

1023
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
222✔
1024
        CalloutContext *ctx = ASSERT_PTR(userdata);
222✔
1025
        int r;
222✔
1026

1027
        assert(s);
222✔
1028
        assert(si);
222✔
1029
        assert(ctx);
222✔
1030

1031
        if (si->si_code == CLD_EXITED) {
222✔
1032
                if (si->si_status == EXIT_SUCCESS) {
222✔
1033
                        r = 0;
222✔
1034
                        log_debug("%s succeeded.", ctx->name);
222✔
UNCOV
1035
                } else if (ctx->helper_errno != 0) {
×
UNCOV
1036
                        r = -ctx->helper_errno;
×
1037
                        log_error_errno(r, "%s failed with exit status %i: %m", ctx->name, si->si_status);
×
1038
                } else {
UNCOV
1039
                        r = -EPROTO;
×
UNCOV
1040
                        log_error("%s failed with exit status %i.", ctx->name, si->si_status);
×
1041
                }
1042
        } else {
UNCOV
1043
                r = -EPROTO;
×
UNCOV
1044
                if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
×
UNCOV
1045
                        log_error("%s terminated by signal %s.", ctx->name, signal_to_string(si->si_status));
×
1046
                else
UNCOV
1047
                        log_error("%s failed due to unknown reason.", ctx->name);
×
1048
        }
1049

1050
        return sd_event_exit(sd_event_source_get_event(s), r);
222✔
1051
}
1052

1053
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
408✔
1054
        CalloutContext *ctx = ASSERT_PTR(userdata);
408✔
1055
        int r;
408✔
1056

1057
        assert(fd >= 0);
408✔
1058

1059
        _cleanup_free_ char *buf = NULL;
408✔
1060
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
408✔
1061
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
408✔
1062
        if (r == -EAGAIN)
408✔
1063
                return 0;
1064
        if (r < 0)
408✔
1065
                return r;
1066

1067
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
408✔
UNCOV
1068
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
UNCOV
1069
                return 0;
×
1070
        }
1071

1072
        char *errno_str = find_line_startswith(buf, "ERRNO=");
408✔
1073
        if (errno_str) {
408✔
1074
                truncate_nl(errno_str);
×
UNCOV
1075
                r = parse_errno(errno_str);
×
UNCOV
1076
                if (r < 0)
×
UNCOV
1077
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1078
                else {
UNCOV
1079
                        ctx->helper_errno = r;
×
UNCOV
1080
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
×
1081
                }
1082
        }
1083

1084
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
408✔
1085
        if (progress_str) {
408✔
1086
                truncate_nl(progress_str);
186✔
1087

1088
                int progress = parse_percent(progress_str);
186✔
1089
                if (progress < 0)
186✔
UNCOV
1090
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1091
                else {
1092
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
186✔
1093
                        if (r < 0)
186✔
UNCOV
1094
                                return r;
×
1095
                }
1096
        }
1097

1098
        return 0;
1099
}
1100

1101
static int run_callout(
222✔
1102
                const char *name,
1103
                char *cmdline[],
1104
                const Transfer *transfer,
1105
                const Instance *instance,
1106
                TransferProgress callback,
1107
                void *userdata) {
1108

1109
        int r;
222✔
1110

1111
        assert(name);
222✔
1112
        assert(cmdline);
222✔
1113
        assert(cmdline[0]);
222✔
1114

UNCOV
1115
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1116
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
222✔
1117
        if (r < 0)
222✔
UNCOV
1118
                return log_oom();
×
1119

1120
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
222✔
1121
        r = sd_event_new(&event);
222✔
1122
        if (r < 0)
222✔
UNCOV
1123
                return log_error_errno(r, "Failed to create event: %m");
×
1124

1125
        /* Kill the helper & return an error if we get interrupted by a signal */
1126
        r = sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
222✔
1127
        if (r < 0)
222✔
UNCOV
1128
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1129
        r = sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, INT_TO_PTR(-ECANCELED));
222✔
1130
        if (r < 0)
222✔
1131
                return log_error_errno(r, "Failed to register signal to event: %m");
×
1132

1133
        _cleanup_free_ char *bind_name = NULL;
222✔
1134
        r = notify_socket_prepare(
222✔
1135
                        event,
1136
                        SD_EVENT_PRIORITY_NORMAL - 5,
1137
                        helper_on_notify,
1138
                        ctx,
1139
                        &bind_name);
1140
        if (r < 0)
222✔
UNCOV
1141
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1142

1143
        r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
222✔
1144
        if (r < 0)
444✔
UNCOV
1145
                return log_error_errno(r, "Failed to fork process %s: %m", ctx->name);
×
1146
        if (r == 0) {
444✔
1147
                /* Child */
1148
                if (setenv("NOTIFY_SOCKET", bind_name, 1) < 0) {
222✔
UNCOV
1149
                        log_error_errno(errno, "setenv() failed: %m");
×
UNCOV
1150
                        _exit(EXIT_FAILURE);
×
1151
                }
1152
                r = invoke_callout_binary(cmdline[0], (char *const*) cmdline);
222✔
UNCOV
1153
                log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]);
×
UNCOV
1154
                _exit(EXIT_FAILURE);
×
1155
        }
1156

1157
        /* Quit the loop w/ when child process exits */
1158
        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
222✔
1159
        r = event_add_child_pidref(event, &exit_source, &ctx->pid, WEXITED, helper_on_exit, ctx);
222✔
1160
        if (r < 0)
222✔
UNCOV
1161
                return log_error_errno(r, "Failed to add child process to event loop: %m");
×
1162

1163
        r = sd_event_source_set_child_process_own(exit_source, true);
222✔
1164
        if (r < 0)
222✔
UNCOV
1165
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1166

1167
        /* Process events until the helper quits */
1168
        return sd_event_loop(event);
222✔
1169
}
1170

1171
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
1172
 * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
1173
 * skipped). */
1174
static int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
342✔
1175
        _cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
342✔
1176
        int r;
342✔
1177

1178
        assert(t);
342✔
1179
        assert(i);
342✔
1180

1181
        assert(!t->final_path);
342✔
1182
        assert(!t->temporary_partial_path);
342✔
1183
        assert(!t->temporary_pending_path);
342✔
1184
        assert(!t->final_partition_label);
342✔
1185
        assert(!t->temporary_partial_partition_label);
342✔
1186
        assert(!t->temporary_pending_partition_label);
342✔
1187
        assert(!strv_isempty(t->target.patterns));
342✔
1188

1189
        /* Format the target name using the first pattern specified */
1190
        compile_pattern_fields(t, i, f);
342✔
1191
        r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
342✔
1192
        if (r < 0)
342✔
UNCOV
1193
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1194

1195
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
342✔
1196
                _cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
210✔
1197

1198
                if (!path_is_safe(formatted_pattern))
210✔
UNCOV
1199
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1200

1201
                t->final_path = path_join(t->target.path, formatted_pattern);
210✔
1202
                if (!t->final_path)
210✔
UNCOV
1203
                        return log_oom();
×
1204

1205
                /* Build the paths for the partial and pending files, which hold the resource while it’s
1206
                 * being acquired and after it’s been acquired (but before it’s moved to the final_path
1207
                 * when it’s installed).
1208
                 *
1209
                 * Split the filename off the `final_path`, then add a prefix to it for each of partial and
1210
                 * pending, then join them back on to the same directory. */
1211
                r = path_split_prefix_filename(t->final_path, &final_dir, &final_filename);
210✔
1212
                if (r < 0)
210✔
UNCOV
1213
                        return log_error_errno(r, "Failed to parse path: %m");
×
1214

1215
                if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
210✔
UNCOV
1216
                        return log_oom();
×
1217

1218
                if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
210✔
UNCOV
1219
                        return log_oom();
×
1220

1221
                t->temporary_partial_path = path_join(final_dir, partial_filename);
210✔
1222
                if (!t->temporary_partial_path)
210✔
UNCOV
1223
                        return log_oom();
×
1224

1225
                t->temporary_pending_path = path_join(final_dir, pending_filename);
210✔
1226
                if (!t->temporary_pending_path)
210✔
UNCOV
1227
                        return log_oom();
×
1228
        }
1229

1230
        if (t->target.type == RESOURCE_PARTITION) {
342✔
1231
                r = gpt_partition_label_valid(formatted_pattern);
132✔
1232
                if (r < 0)
132✔
UNCOV
1233
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1234
                if (!r)
132✔
UNCOV
1235
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1236

1237
                if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
132✔
UNCOV
1238
                        return log_oom();
×
1239
                r = gpt_partition_label_valid(formatted_partial_pattern);
132✔
1240
                if (r < 0)
132✔
UNCOV
1241
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
×
1242
                if (!r)
132✔
1243
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
×
1244

1245
                free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
132✔
1246

1247
                if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
132✔
UNCOV
1248
                        return log_oom();
×
1249
                r = gpt_partition_label_valid(formatted_pending_pattern);
132✔
1250
                if (r < 0)
132✔
UNCOV
1251
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
×
1252
                if (!r)
132✔
UNCOV
1253
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
×
1254

1255
                free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
132✔
1256

1257
                t->final_partition_label = TAKE_PTR(formatted_pattern);
132✔
1258
        }
1259

1260
        return 0;
1261
}
1262

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

1271
        assert(t);
222✔
1272
        assert(i);
222✔
1273
        assert(i->resource == &t->source);
222✔
1274
        assert(cb);
222✔
1275

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

1285
        /* Compute up the temporary paths */
1286
        r = transfer_compute_temporary_paths(t, i, &f);
222✔
1287
        if (r < 0)
222✔
1288
                return r;
1289

1290
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
222✔
1291
                r = mkdir_parents(t->temporary_partial_path, 0755);
138✔
1292
                if (r < 0)
138✔
UNCOV
1293
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1294

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

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

1303
                where = t->final_path;
138✔
1304
        }
1305

1306
        if (t->target.type == RESOURCE_PARTITION) {
222✔
1307
                r = find_suitable_partition(
84✔
1308
                                t->target.path,
84✔
1309
                                i->metadata.size,
1310
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
84✔
1311
                                &t->partition_info);
1312
                if (r < 0)
84✔
1313
                        return r;
1314

1315
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
84✔
1316
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
84✔
1317

1318
                where = t->partition_info.device;
84✔
1319

1320
                /* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
1321
                r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
84✔
1322
                if (r < 0)
84✔
1323
                        return r;
1324
                t->partition_change = PARTITION_LABEL;
84✔
1325

1326
                log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
84✔
1327
                r = patch_partition(
168✔
1328
                                t->target.path,
84✔
1329
                                &t->partition_info,
1330
                                t->partition_change);
1331
                if (r < 0)
84✔
1332
                        return r;
1333
        }
1334

1335
        assert(where);
222✔
1336

1337
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
444✔
1338

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

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

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

1351
        switch (i->resource->type) { /* Source */
222✔
1352

1353
        case RESOURCE_REGULAR_FILE:
168✔
1354

1355
                switch (t->target.type) { /* Target */
168✔
1356

1357
                case RESOURCE_REGULAR_FILE:
96✔
1358

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

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

1375
                case RESOURCE_PARTITION:
72✔
1376

1377
                        /* regular file → partition */
1378

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

UNCOV
1392
                default:
×
UNCOV
1393
                        assert_not_reached();
×
1394
                }
1395

1396
                break;
168✔
1397

1398
        case RESOURCE_DIRECTORY:
30✔
1399
        case RESOURCE_SUBVOLUME:
1400
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
30✔
1401

1402
                /* directory/subvolume → directory/subvolume */
1403

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

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

1419
                /* tar → directory/subvolume */
1420

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

1433
        case RESOURCE_URL_FILE:
12✔
1434

1435
                switch (t->target.type) {
12✔
1436

UNCOV
1437
                case RESOURCE_REGULAR_FILE:
×
1438

1439
                        /* url file → regular file */
1440

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

1453
                case RESOURCE_PARTITION:
12✔
1454

1455
                        /* url file → partition */
1456

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

UNCOV
1471
                default:
×
UNCOV
1472
                        assert_not_reached();
×
1473
                }
1474

1475
                break;
12✔
1476

1477
        case RESOURCE_URL_TAR:
12✔
1478
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
12✔
1479

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

1493
        default:
×
1494
                assert_not_reached();
×
1495
        }
1496
        if (r < 0)
222✔
1497
                return r;
1498

1499
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
222✔
1500
                bool need_sync = false;
138✔
1501
                assert(t->temporary_partial_path);
138✔
1502
                assert(t->temporary_pending_path);
138✔
1503

1504
                /* Apply file attributes if set */
1505
                if (f.mtime != USEC_INFINITY) {
138✔
1506
                        struct timespec ts;
126✔
1507

1508
                        timespec_store(&ts, f.mtime);
126✔
1509

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

1513
                        need_sync = true;
126✔
1514
                }
1515

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

1524
                        need_sync = true;
1525
                }
1526

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

1539
                t->install_read_only = f.read_only;
138✔
1540

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

1552
        if (t->target.type == RESOURCE_PARTITION) {
222✔
1553
                /* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete
1554
                 * and the partition is ready for install. */
1555
                r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
84✔
1556
                if (r < 0)
84✔
1557
                        return r;
1558
                t->partition_change = PARTITION_LABEL;
84✔
1559

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

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

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

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

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

1585
                log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
84✔
1586
                r = patch_partition(
168✔
1587
                                t->target.path,
84✔
1588
                                &t->partition_info,
84✔
1589
                                t->partition_change);
1590
                if (r < 0)
84✔
1591
                        return r;
1592
        }
1593

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

1598
        log_info("Successfully acquired '%s'.", i->path);
222✔
1599
        return 0;
1600
}
1601

1602
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
120✔
1603
        InstanceMetadata f;
120✔
1604
        Instance *existing;
120✔
1605
        int r;
120✔
1606

1607
        assert(t);
120✔
1608
        assert(i);
120✔
1609

1610
        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
120✔
1611

1612
        /* Does this instance already exist in the target but isn’t pending? */
1613
        existing = resource_find_instance(&t->target, i->metadata.version);
120✔
1614
        if (existing && !existing->is_pending)
120✔
UNCOV
1615
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already in the target but is not pending.", i->path);
×
1616

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

1623
        /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
1624
         * already happened in the acquire phase, the target should already have that information and it
1625
         * should already have been claimed as `PND#`. */
1626
        if (t->target.type == RESOURCE_PARTITION) {
120✔
1627
                assert(i->resource == &t->target);
48✔
1628
                assert(i->is_pending);
48✔
1629

1630
                r = partition_info_copy(&t->partition_info, &i->partition_info);
48✔
1631
                if (r < 0)
48✔
UNCOV
1632
                        return r;
×
1633
        }
1634

1635
        return 0;
1636
}
1637

1638
int transfer_install_instance(
222✔
1639
                Transfer *t,
1640
                Instance *i,
1641
                const char *root) {
1642

1643
        int r;
222✔
1644

1645
        assert(t);
222✔
1646
        assert(i);
222✔
1647
        assert(i->resource);
222✔
1648
        assert(i->is_pending || t == container_of(i->resource, Transfer, source));
222✔
1649

1650
        log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
222✔
1651

1652
        if (t->temporary_pending_path) {
222✔
1653
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
138✔
1654
                assert(t->final_path);
138✔
1655

1656
                r = install_file(AT_FDCWD, t->temporary_pending_path,
414✔
1657
                                 AT_FDCWD, t->final_path,
1658
                                 INSTALL_REPLACE|
138✔
1659
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
276✔
1660
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
138✔
1661
                if (r < 0)
138✔
UNCOV
1662
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1663

1664
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
138✔
1665
                         i->path,
1666
                         resource_type_to_string(i->resource->type),
1667
                         t->final_path,
1668
                         resource_type_to_string(t->target.type));
1669

1670
                t->temporary_pending_path = mfree(t->temporary_pending_path);
138✔
1671
        }
1672

1673
        if (t->temporary_pending_partition_label) {
222✔
1674
                assert(t->target.type == RESOURCE_PARTITION);
84✔
1675
                assert(t->final_partition_label);
84✔
1676

1677
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
84✔
1678
                if (r < 0)
84✔
1679
                        return r;
1680
                t->partition_change = PARTITION_LABEL;
84✔
1681

1682
                r = patch_partition(
168✔
1683
                                t->target.path,
84✔
1684
                                &t->partition_info,
84✔
1685
                                t->partition_change);
1686
                if (r < 0)
84✔
1687
                        return r;
1688

1689
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
84✔
1690
                         i->path,
1691
                         resource_type_to_string(i->resource->type),
1692
                         t->partition_info.device,
1693
                         resource_type_to_string(t->target.type));
1694
        }
1695

1696
        if (t->current_symlink) {
222✔
1697
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
42✔
1698
                const char *link_path, *link_target;
42✔
1699
                bool resolve_link_path = false;
42✔
1700

1701
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
42✔
1702

1703
                        assert(t->target.path);
42✔
1704

1705
                        if (path_is_absolute(t->current_symlink)) {
42✔
1706
                                link_path = t->current_symlink;
1707
                                resolve_link_path = true;
1708
                        } else {
UNCOV
1709
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
UNCOV
1710
                                if (!buf)
×
UNCOV
1711
                                        return log_oom();
×
1712

1713
                                link_path = buf;
1714
                        }
1715

1716
                        link_target = t->final_path;
42✔
1717

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

UNCOV
1720
                        assert(path_is_absolute(t->current_symlink));
×
1721

UNCOV
1722
                        link_path = t->current_symlink;
×
UNCOV
1723
                        link_target = t->partition_info.device;
×
1724

UNCOV
1725
                        resolve_link_path = true;
×
1726
                } else
UNCOV
1727
                        assert_not_reached();
×
1728

1729
                if (resolve_link_path && root) {
42✔
UNCOV
1730
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
UNCOV
1731
                        if (r < 0)
×
UNCOV
1732
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1733

UNCOV
1734
                        link_path = resolved;
×
1735
                }
1736

1737
                if (link_target) {
42✔
1738
                        r = path_extract_directory(link_path, &parent);
42✔
1739
                        if (r < 0)
42✔
UNCOV
1740
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1741

1742
                        r = path_make_relative(parent, link_target, &relative);
42✔
1743
                        if (r < 0)
42✔
UNCOV
1744
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1745

1746
                        r = symlink_atomic(relative, link_path);
42✔
1747
                        if (r < 0)
42✔
UNCOV
1748
                                return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
×
1749
                                                       link_path,
1750
                                                       glyph(GLYPH_ARROW_RIGHT),
1751
                                                       relative);
1752

1753
                        log_info("Updated symlink '%s' %s '%s'.",
42✔
1754
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1755
                }
1756
        }
1757

1758
        return 0;
1759
}
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