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

systemd / systemd / 28689257516

03 Jul 2026 11:32PM UTC coverage: 72.9% (+0.3%) from 72.618%
28689257516

push

github

bluca
hwdb: map mic-mute key on Logitech K950 (Bluetooth)

The mic-mute key on the Logitech K950 keyboard (046D:B388, Bluetooth)
emits BTN_0 (scancode 0x100e1) instead of a usable key, so it does
nothing under GNOME/KDE. Map it to KEY_MICMUTE, its intended function.

Scancode determined with evtest on the device.

343301 of 470919 relevant lines covered (72.9%)

1344559.44 hits per line

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

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

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

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

55
        free(t->temporary_partial_path);
9,912✔
56
        free(t->temporary_pending_path);
9,912✔
57

58
        free(t->id);
9,912✔
59

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

65
        strv_free(t->features);
9,912✔
66
        strv_free(t->requisite_features);
9,912✔
67

68
        strv_free(t->changelog);
9,912✔
69
        strv_free(t->appstream);
9,912✔
70

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

74
        resource_destroy(&t->source);
9,912✔
75
        resource_destroy(&t->target);
9,912✔
76

77
        return mfree(t);
9,912✔
78
}
79

80
Transfer* transfer_new(Context *ctx) {
9,912✔
81
        Transfer *t;
9,912✔
82

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

87
        *t = (Transfer) {
9,912✔
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;
9,912✔
110
}
111

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

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

129
        assert(rvalue);
×
130

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

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

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

148
        return 0;
149
}
150

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

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

168
        assert(rvalue);
×
169

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

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

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

186
static int config_parse_transfer_url_specifiers_many(
×
187
                const char *unit,
188
                const char *filename,
189
                unsigned line,
190
                const char *section,
191
                unsigned section_line,
192
                const char *lvalue,
193
                int ltype,
194
                const char *rvalue,
195
                void *data,
196
                void *userdata) {
197

198
        Transfer *t = ASSERT_PTR(userdata);
×
199

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

203
        return config_parse_url_specifiers_many(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, t->context->root);
×
204
}
205

206
static int config_parse_current_symlink(
1,232✔
207
                const char *unit,
208
                const char *filename,
209
                unsigned line,
210
                const char *section,
211
                unsigned section_line,
212
                const char *lvalue,
213
                int ltype,
214
                const char *rvalue,
215
                void *data,
216
                void *userdata) {
217

218
        _cleanup_free_ char *resolved = NULL;
1,232✔
219
        char **current_symlink = ASSERT_PTR(data);
1,232✔
220
        Transfer *t = ASSERT_PTR(userdata);
1,232✔
221
        int r;
1,232✔
222

223
        assert(rvalue);
1,232✔
224

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

232
        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
1,232✔
233
        if (r < 0)
1,232✔
234
                return 0;
235

236
        return free_and_replace(*current_symlink, resolved);
1,232✔
237
}
238

239
static int config_parse_instances_max(
7,416✔
240
                const char *unit,
241
                const char *filename,
242
                unsigned line,
243
                const char *section,
244
                unsigned section_line,
245
                const char *lvalue,
246
                int ltype,
247
                const char *rvalue,
248
                void *data,
249
                void *userdata) {
250

251
        uint64_t *instances_max = data, i;
7,416✔
252
        int r;
7,416✔
253

254
        assert(rvalue);
7,416✔
255
        assert(data);
7,416✔
256

257
        if (isempty(rvalue)) {
7,416✔
258
                *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
×
259
                return 0;
×
260
        }
261

262
        r = safe_atou64(rvalue, &i);
7,416✔
263
        if (r < 0) {
7,416✔
264
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
265
                           "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
266
                return 0;
267
        }
268

269
        if (i < 2) {
7,416✔
270
                log_syntax(unit, LOG_WARNING, filename, line, 0,
8✔
271
                           "InstancesMax= value must be at least 2, bumping: %s", rvalue);
272
                *instances_max = 2;
8✔
273
        } else
274
                *instances_max = i;
7,408✔
275

276
        return 0;
277
}
278

279
static int config_parse_resource_pattern(
19,824✔
280
                const char *unit,
281
                const char *filename,
282
                unsigned line,
283
                const char *section,
284
                unsigned section_line,
285
                const char *lvalue,
286
                int ltype,
287
                const char *rvalue,
288
                void *data,
289
                void *userdata) {
290

291
        char ***patterns = ASSERT_PTR(data);
19,824✔
292
        Transfer *t = ASSERT_PTR(userdata);
19,824✔
293
        int r;
19,824✔
294

295
        assert(rvalue);
19,824✔
296

297
        if (isempty(rvalue)) {
19,824✔
298
                *patterns = strv_free(*patterns);
×
299
                return 0;
×
300
        }
301

302
        for (;;) {
64,400✔
303
                _cleanup_free_ char *word = NULL, *resolved = NULL;
22,288✔
304

305
                r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
42,112✔
306
                if (r < 0) {
42,112✔
307
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
308
                                   "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
309
                        return 0;
310
                }
311
                if (r == 0)
42,112✔
312
                        break;
313

314
                r = specifier_printf(word, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
22,288✔
315
                if (r < 0) {
22,288✔
316
                        log_syntax(unit, LOG_WARNING, filename, line, r,
×
317
                                   "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
318
                        return 0;
319
                }
320

321
                if (!pattern_valid(resolved))
22,288✔
322
                        return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
×
323
                                          "MatchPattern= string is not valid, refusing: %s", resolved);
324

325
                r = strv_consume(patterns, TAKE_PTR(resolved));
22,288✔
326
                if (r < 0)
22,288✔
327
                        return log_oom();
×
328
        }
329

330
        strv_uniq(*patterns);
19,824✔
331
        return 0;
19,824✔
332
}
333

334
static int config_parse_resource_path(
19,824✔
335
                const char *unit,
336
                const char *filename,
337
                unsigned line,
338
                const char *section,
339
                unsigned section_line,
340
                const char *lvalue,
341
                int ltype,
342
                const char *rvalue,
343
                void *data,
344
                void *userdata) {
345
        _cleanup_free_ char *resolved = NULL;
19,824✔
346
        Resource *rr = ASSERT_PTR(data);
19,824✔
347
        Transfer *t = ASSERT_PTR(userdata);
19,824✔
348
        int r;
19,824✔
349

350
        assert(rvalue);
19,824✔
351

352
        if (streq(rvalue, "auto")) {
19,824✔
353
                rr->path_auto = true;
×
354
                rr->path = mfree(rr->path);
×
355
                return 0;
×
356
        }
357

358
        r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, t->context->root, NULL, &resolved);
19,824✔
359
        if (r < 0) {
19,824✔
360
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
361
                           "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
362
                return 0;
363
        }
364

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

369
        rr->path_auto = false;
19,824✔
370
        return free_and_replace(rr->path, resolved);
19,824✔
371
}
372

373
static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType);
19,824✔
374

375
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_resource_path_relto, path_relative_to, PathRelativeTo,
3,696✔
376
                                             PATH_RELATIVE_TO_ROOT);
377

378
static int config_parse_resource_ptype(
2,496✔
379
                const char *unit,
380
                const char *filename,
381
                unsigned line,
382
                const char *section,
383
                unsigned section_line,
384
                const char *lvalue,
385
                int ltype,
386
                const char *rvalue,
387
                void *data,
388
                void *userdata) {
389

390
        Resource *rr = ASSERT_PTR(data);
2,496✔
391
        int r;
2,496✔
392

393
        assert(rvalue);
2,496✔
394

395
        r = gpt_partition_type_from_string(rvalue, &rr->partition_type);
2,496✔
396
        if (r < 0) {
2,496✔
397
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
398
                           "Failed to parse partition type, ignoring: %s", rvalue);
399
                return 0;
400
        }
401

402
        rr->partition_type_set = true;
2,496✔
403
        return 0;
2,496✔
404
}
405

406
static int config_parse_partition_uuid(
×
407
                const char *unit,
408
                const char *filename,
409
                unsigned line,
410
                const char *section,
411
                unsigned section_line,
412
                const char *lvalue,
413
                int ltype,
414
                const char *rvalue,
415
                void *data,
416
                void *userdata) {
417

418
        Transfer *t = ASSERT_PTR(data);
×
419
        int r;
×
420

421
        assert(rvalue);
×
422

423
        r = sd_id128_from_string(rvalue, &t->partition_uuid);
×
424
        if (r < 0) {
×
425
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
426
                           "Failed to parse partition UUID, ignoring: %s", rvalue);
427
                return 0;
428
        }
429

430
        t->partition_uuid_set = true;
×
431
        return 0;
×
432
}
433

434
static int config_parse_partition_flags(
×
435
                const char *unit,
436
                const char *filename,
437
                unsigned line,
438
                const char *section,
439
                unsigned section_line,
440
                const char *lvalue,
441
                int ltype,
442
                const char *rvalue,
443
                void *data,
444
                void *userdata) {
445

446
        Transfer *t = ASSERT_PTR(data);
×
447
        int r;
×
448

449
        assert(rvalue);
×
450

451
        r = safe_atou64(rvalue, &t->partition_flags);
×
452
        if (r < 0) {
×
453
                log_syntax(unit, LOG_WARNING, filename, line, r,
×
454
                           "Failed to parse partition flags, ignoring: %s", rvalue);
455
                return 0;
456
        }
457

458
        t->partition_flags_set = true;
×
459
        return 0;
×
460
}
461

462
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
9,912✔
463
        assert(t);
9,912✔
464

465
        /* Requisite feature disabled -> transfer disabled */
466
        STRV_FOREACH(id, t->requisite_features) {
9,912✔
467
                Feature *f = hashmap_get(known_features, *id);
×
468
                if (!f || !f->enabled) /* missing features are implicitly disabled */
×
469
                        return false;
470
        }
471

472
        /* No features defined -> transfer implicitly enabled */
473
        if (strv_isempty(t->features))
9,912✔
474
                return true;
475

476
        /* At least one feature enabled -> transfer enabled */
477
        STRV_FOREACH(id, t->features) {
2,364✔
478
                Feature *f = hashmap_get(known_features, *id);
1,232✔
479
                if (f && f->enabled)
1,232✔
480
                        return true;
481
        }
482

483
        /* All listed features disabled -> transfer disabled */
484
        return false;
485
}
486

487
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
9,912✔
488
        assert(t);
9,912✔
489

490
        ConfigTableItem table[] = {
9,912✔
491
                { "Transfer",    "MinVersion",              config_parse_min_version,                  0, &t->min_version             },
9,912✔
492
                { "Transfer",    "ProtectVersion",          config_parse_protect_version,              0, &t->protected_versions      },
9,912✔
493
                { "Transfer",    "Verify",                  config_parse_bool,                         0, &t->verify                  },
9,912✔
494
                { "Transfer",    "ChangeLog",               config_parse_transfer_url_specifiers_many, 0, &t->changelog               },
9,912✔
495
                { "Transfer",    "AppStream",               config_parse_transfer_url_specifiers_many, 0, &t->appstream               },
9,912✔
496
                { "Transfer",    "Features",                config_parse_strv,                         0, &t->features                },
9,912✔
497
                { "Transfer",    "RequisiteFeatures",       config_parse_strv,                         0, &t->requisite_features      },
9,912✔
498
                { "Source",      "Type",                    config_parse_resource_type,                0, &t->source.type             },
9,912✔
499
                { "Source",      "Path",                    config_parse_resource_path,                0, &t->source                  },
9,912✔
500
                { "Source",      "PathRelativeTo",          config_parse_resource_path_relto,          0, &t->source.path_relative_to },
9,912✔
501
                { "Source",      "MatchPattern",            config_parse_resource_pattern,             0, &t->source.patterns         },
9,912✔
502
                { "Target",      "Type",                    config_parse_resource_type,                0, &t->target.type             },
9,912✔
503
                { "Target",      "Path",                    config_parse_resource_path,                0, &t->target                  },
9,912✔
504
                { "Target",      "PathRelativeTo",          config_parse_resource_path_relto,          0, &t->target.path_relative_to },
9,912✔
505
                { "Target",      "MatchPattern",            config_parse_resource_pattern,             0, &t->target.patterns         },
9,912✔
506
                { "Target",      "MatchPartitionType",      config_parse_resource_ptype,               0, &t->target                  },
507
                { "Target",      "PartitionUUID",           config_parse_partition_uuid,               0, t                           },
508
                { "Target",      "PartitionFlags",          config_parse_partition_flags,              0, t                           },
509
                { "Target",      "PartitionNoAuto",         config_parse_tristate,                     0, &t->no_auto                 },
9,912✔
510
                { "Target",      "PartitionGrowFileSystem", config_parse_tristate,                     0, &t->growfs                  },
9,912✔
511
                { "Target",      "ReadOnly",                config_parse_tristate,                     0, &t->read_only               },
9,912✔
512
                { "Target",      "Mode",                    config_parse_mode,                         0, &t->mode                    },
9,912✔
513
                { "Target",      "TriesLeft",               config_parse_uint64,                       0, &t->tries_left              },
9,912✔
514
                { "Target",      "TriesDone",               config_parse_uint64,                       0, &t->tries_done              },
9,912✔
515
                { "Target",      "InstancesMax",            config_parse_instances_max,                0, &t->instances_max           },
9,912✔
516
                { "Target",      "RemoveTemporary",         config_parse_bool,                         0, &t->remove_temporary        },
9,912✔
517
                { "Target",      "CurrentSymlink",          config_parse_current_symlink,              0, &t->current_symlink         },
9,912✔
518
                {}
519
        };
520

521
        _cleanup_free_ char *filename = NULL;
9,912✔
522
        char *e;
9,912✔
523
        int r;
9,912✔
524

525
        assert(path);
9,912✔
526
        assert(dirs);
9,912✔
527

528
        r = path_extract_filename(path, &filename);
9,912✔
529
        if (r < 0)
9,912✔
530
                return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
×
531

532
        r = config_parse_many_full(
29,736✔
533
                        STRV_MAKE_CONST(path),
9,912✔
534
                        dirs,
535
                        strjoina(filename, ".d"),
49,560✔
536
                        t->context->root,
9,912✔
537
                        /* root_fd= */ -EBADF,
538
                        "Transfer\0"
539
                        "Source\0"
540
                        "Target\0",
541
                        config_item_table_lookup, table,
542
                        CONFIG_PARSE_WARN,
543
                        t,
544
                        /* ret_stats_by_path= */ NULL,
545
                        /* ret_drop_in_files= */ NULL);
546
        if (r < 0)
9,912✔
547
                return r;
548

549
        e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
9,912✔
550
        *e = 0; /* Remove the file extension */
9,912✔
551
        t->id = TAKE_PTR(filename);
9,912✔
552

553
        t->enabled = transfer_decide_if_enabled(t, known_features);
9,912✔
554

555
        if (!RESOURCE_IS_SOURCE(t->source.type))
9,912✔
556
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
557
                                  "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
558

559
        if (t->target.type < 0) {
9,912✔
560
                switch (t->source.type) {
×
561

562
                case RESOURCE_URL_FILE:
×
563
                case RESOURCE_REGULAR_FILE:
564
                        t->target.type =
×
565
                                t->target.path && path_startswith(t->target.path, "/dev/") ?
×
566
                                RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
×
567
                        break;
×
568

569
                case RESOURCE_URL_TAR:
×
570
                case RESOURCE_TAR:
571
                case RESOURCE_DIRECTORY:
572
                        t->target.type = RESOURCE_DIRECTORY;
×
573
                        break;
×
574

575
                case RESOURCE_SUBVOLUME:
×
576
                        t->target.type = RESOURCE_SUBVOLUME;
×
577
                        break;
×
578

579
                default:
×
580
                        assert_not_reached();
×
581
                }
582
        }
583

584
        if (!RESOURCE_IS_TARGET(t->target.type))
9,912✔
585
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
586
                                  "Target Type= must be one of partition, regular-file, directory, subvolume.");
587

588
        if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
9,912✔
589
             !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
8,676✔
590
            (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
9,912✔
591
             !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
1,236✔
592
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
593
                                  "Target type '%s' is incompatible with source type '%s', refusing.",
594
                                  resource_type_to_string(t->target.type), resource_type_to_string(t->source.type));
595

596
        if (!t->source.path && !t->source.path_auto)
9,912✔
597
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
598
                                  "Source specification lacks Path=.");
599

600
        if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !t->context->transfer_source)
9,912✔
601
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
602
                                  "PathRelativeTo=explicit requires --transfer-source= to be specified.");
603

604
        if (t->target.path_relative_to == PATH_RELATIVE_TO_EXPLICIT)
9,912✔
605
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
606
                                  "PathRelativeTo=explicit can only be used in source specifications.");
607

608
        if (t->source.path) {
9,912✔
609
                if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
9,912✔
610
                        if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
9,144✔
611
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
612
                                                  "Source path is not a normalized, absolute path: %s", t->source.path);
613

614
                /* We unofficially support file:// in addition to http:// and https:// for url
615
                 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
616
                 * server, and CURL abstracts this away from us thankfully. */
617
                if (RESOURCE_IS_URL(t->source.type))
9,912✔
618
                        if (!http_url_is_valid(t->source.path) && !file_url_is_valid(t->source.path))
768✔
619
                                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
620
                                                  "Source path is not a valid HTTP or HTTPS URL: %s", t->source.path);
621
        }
622

623
        if (strv_isempty(t->source.patterns))
9,912✔
624
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
625
                                  "Source specification lacks MatchPattern=.");
626

627
        if (!t->target.path && !t->target.path_auto)
9,912✔
628
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
629
                                  "Target specification lacks Path= field.");
630

631
        if (t->target.path &&
9,912✔
632
            (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
9,912✔
633
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
634
                                  "Target path is not a normalized, absolute path: %s", t->target.path);
635

636
        if (strv_isempty(t->target.patterns)) {
9,912✔
637
                log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification.");
×
638
                strv_free(t->target.patterns);
×
639
                t->target.patterns = strv_copy(t->source.patterns);
×
640
                if (!t->target.patterns)
×
641
                        return log_oom();
×
642
        }
643

644
        if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
9,912✔
645
                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
×
646
                                  "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
647

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

652
        return 0;
653
}
654

655
int transfer_resolve_paths(
9,912✔
656
                Transfer *t,
657
                const char *root,
658
                const char *node) {
659

660
        int r;
9,912✔
661

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

667
        assert(t);
9,912✔
668

669
        r = resource_resolve_path(&t->source, root, t->context->transfer_source, node);
9,912✔
670
        if (r < 0)
9,912✔
671
                return r;
672

673
        r = resource_resolve_path(&t->target, root, /* relative_to_directory= */ NULL, node);
9,912✔
674
        if (r < 0)
9,912✔
675
                return r;
×
676

677
        return 0;
678
}
679

680
static void transfer_remove_temporary(Transfer *t) {
1,469✔
681
        _cleanup_closedir_ DIR *d = NULL;
2,938✔
682
        int r;
1,469✔
683

684
        assert(t);
1,469✔
685

686
        if (!t->remove_temporary)
1,469✔
687
                return;
688

689
        if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
1,469✔
690
                return;
691

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

694
        d = opendir(t->target.path);
1,117✔
695
        if (!d) {
1,117✔
696
                if (errno == ENOENT)
×
697
                        return;
698

699
                log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
1,469✔
700
                return;
701
        }
702

703
        for (;;) {
5,172✔
704
                struct dirent *de;
5,172✔
705

706
                errno = 0;
5,172✔
707
                de = readdir_no_dot(d);
5,172✔
708
                if (!de) {
5,172✔
709
                        if (errno != 0)
1,117✔
710
                                log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
×
711
                        break;
1,117✔
712
                }
713

714
                if (!startswith(de->d_name, ".#"))
4,055✔
715
                        continue;
4,055✔
716

717
                r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
×
718
                if (r == -ENOENT)
×
719
                        continue;
×
720
                if (r < 0) {
×
721
                        log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
×
722
                        continue;
×
723
                }
724

725
                log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
×
726
        }
727
}
728

729
static int transfer_instance_vacuum(
640✔
730
                Transfer *t,
731
                Instance *instance) {
732
        int r;
640✔
733

734
        assert(t);
640✔
735
        assert(instance);
640✔
736

737
        switch (t->target.type) {
640✔
738

739
        case RESOURCE_REGULAR_FILE:
416✔
740
        case RESOURCE_DIRECTORY:
741
        case RESOURCE_SUBVOLUME:
742
                r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
416✔
743
                if (r < 0 && r != -ENOENT)
416✔
744
                        return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
×
745

746
                (void) rmdir_parents(instance->path, t->target.path);
416✔
747

748
                break;
416✔
749

750
        case RESOURCE_PARTITION: {
224✔
751
                PartitionInfo pinfo = instance->partition_info;
224✔
752
                PartitionChange change = PARTITION_LABEL;
224✔
753

754
                /* label "_empty" means "no contents" for our purposes */
755
                pinfo.label = (char*) "_empty";
224✔
756

757
                /* If the partition had a derived partial/pending type UUID, restore the original
758
                 * partition type so that the slot is properly recognized as empty in subsequent
759
                 * scans. */
760
                if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
224✔
761
                        pinfo.type = t->target.partition_type.uuid;
32✔
762
                        change |= PARTITION_TYPE;
32✔
763
                }
764

765
                log_debug("Resetting partition '%s' to empty.", pinfo.device);
224✔
766
                r = patch_partition(t->target.path, &pinfo, change);
224✔
767
                if (r < 0)
224✔
768
                        return r;
×
769

770
                t->target.n_empty++;
224✔
771
                break;
224✔
772
        }
773

774
        default:
×
775
                assert_not_reached();
×
776
        }
777

778
        return 0;
779
}
780

781
int transfer_vacuum(
1,469✔
782
                Transfer *t,
783
                uint64_t space,
784
                const char *extra_protected_version) {
785

786
        uint64_t instances_max, limit;
1,469✔
787
        int r, count = 0;
1,469✔
788

789
        assert(t);
1,469✔
790

791
        transfer_remove_temporary(t);
1,469✔
792

793
        /* First, remove any partial or pending instances (unless protected) */
794
        for (size_t i = 0; i < t->target.n_instances;) {
5,518✔
795
                Instance *instance = t->target.instances[i];
2,580✔
796

797
                assert(instance);
2,580✔
798

799
                if (!instance->is_pending && !instance->is_partial) {
2,580✔
800
                        i++;
2,548✔
801
                        continue;
2,548✔
802
                }
803

804
                /* If this is pending and listed among the protected versions, then let's not remove it.
805
                 * In future, we will also want to keep partial protected versions, but that’s only useful
806
                 * once we support resuming downloads. */
807
                if (instance->is_pending &&
48✔
808
                    (strv_contains(t->protected_versions, instance->metadata.version) ||
32✔
809
                     (extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) {
×
810
                        log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version);
×
811
                        i++;
×
812
                        continue;
×
813
                }
814

815
                assert(instance->resource);
32✔
816

817
                log_info("%s Removing old %s '%s' (%s).",
64✔
818
                         glyph(GLYPH_RECYCLING),
819
                         instance->is_partial ? "partial" : "pending",
820
                         instance->path,
821
                         resource_type_to_string(instance->resource->type));
822

823
                r = transfer_instance_vacuum(t, instance);
32✔
824
                if (r < 0)
32✔
825
                        return r;
826

827
                instance_free(instance);
32✔
828
                memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
32✔
829
                t->target.n_instances--;
32✔
830

831
                count++;
32✔
832
        }
833

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

836
        instances_max = t->context->instances_max != UINT64_MAX ? t->context->instances_max : t->instances_max;
1,469✔
837
        assert(instances_max >= 1);
1,469✔
838
        if (instances_max == UINT64_MAX) /* Keep infinite instances? */
1,469✔
839
                limit = UINT64_MAX;
840
        else if (space == UINT64_MAX) /* forcibly delete all instances? */
1,117✔
841
                limit = 0;
842
        else if (space > instances_max)
925✔
843
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
844
                                       "Asked to delete more instances than total maximum allowed number of instances, refusing.");
845
        else if (space == instances_max)
925✔
846
                return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
×
847
                                       "Asked to delete all possible instances, can't allow that. One instance must always remain.");
848
        else
849
                limit = instances_max - space;
925✔
850

851
        if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
1,469✔
852
                _cleanup_free_ char *patterns = NULL;
352✔
853
                uint64_t rm, remain;
352✔
854

855
                patterns = strv_join(t->target.patterns, "|");
352✔
856
                if (!patterns)
352✔
857
                        (void) log_oom_debug();
×
858

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

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

884
                rm = LESS_BY(space, t->target.n_empty);
352✔
885
                remain = LESS_BY(t->target.n_instances, rm);
352✔
886
                limit = MIN(limit, remain);
352✔
887
        }
888

889
        while (t->target.n_instances > limit) {
2,077✔
890
                Instance *oldest;
608✔
891
                size_t p = t->target.n_instances - 1;
608✔
892

893
                for (;;) {
608✔
894
                        oldest = t->target.instances[p];
608✔
895
                        assert(oldest);
608✔
896

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

902
                        log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
×
903
                        if (p == 0) {
×
904
                                oldest = NULL;
905
                                break;
906
                        }
907

908
                        p--;
×
909
                }
910

911
                if (!oldest) /* Nothing more to remove */
608✔
912
                        break;
913

914
                assert(oldest->resource);
608✔
915

916
                log_info("%s Removing %s '%s' (%s).",
1,216✔
917
                         glyph(GLYPH_RECYCLING),
918
                         space == UINT64_MAX ? "disabled" : "old",
919
                         oldest->path,
920
                         resource_type_to_string(oldest->resource->type));
921

922
                r = transfer_instance_vacuum(t, oldest);
608✔
923
                if (r < 0)
608✔
924
                        return r;
925

926
                instance_free(oldest);
608✔
927
                memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
608✔
928
                t->target.n_instances--;
608✔
929

930
                count++;
608✔
931
        }
932

933
        return count;
934
}
935

936
static void compile_pattern_fields(
1,973✔
937
                const Transfer *t,
938
                const Instance *i,
939
                InstanceMetadata *ret) {
940

941
        assert(t);
1,973✔
942
        assert(i);
1,973✔
943
        assert(ret);
1,973✔
944

945
        *ret = (InstanceMetadata) {
3,946✔
946
                .version = i->metadata.version,
1,973✔
947

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

968
        memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
1,973✔
969
}
1,973✔
970

971
typedef struct CalloutContext {
972
        const Transfer *transfer;
973
        const Instance *instance;
974
        TransferProgress callback;
975
        PidRef pid;
976
        const char *name;
977
        int helper_errno;
978
        void* userdata;
979
} CalloutContext;
980

981
static CalloutContext *callout_context_free(CalloutContext *ctx) {
973✔
982
        if (!ctx)
973✔
983
                return NULL;
984

985
        /* We don't own any data but need to clean up the job pid */
986
        pidref_done(&ctx->pid);
973✔
987

988
        return mfree(ctx);
973✔
989
}
990

991
DEFINE_TRIVIAL_CLEANUP_FUNC(CalloutContext*, callout_context_free);
1,946✔
992

993
static int callout_context_new(const Transfer *t, const Instance *i, TransferProgress cb,
973✔
994
                               const char *name, void* userdata, CalloutContext **ret) {
995
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
973✔
996

997
        assert(t);
973✔
998
        assert(i);
973✔
999
        assert(cb);
973✔
1000
        assert(ret);
973✔
1001

1002
        ctx = new(CalloutContext, 1);
973✔
1003
        if (!ctx)
973✔
1004
                return -ENOMEM;
1005

1006
        *ctx = (CalloutContext) {
973✔
1007
                .transfer = t,
1008
                .instance = i,
1009
                .callback = cb,
1010
                .pid = PIDREF_NULL,
1011
                .name = name,
1012
                .userdata = userdata,
1013
        };
1014

1015
        *ret = TAKE_PTR(ctx);
973✔
1016
        return 0;
973✔
1017
}
1018

1019
static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
973✔
1020
        CalloutContext *ctx = ASSERT_PTR(userdata);
973✔
1021
        int r;
973✔
1022

1023
        assert(s);
973✔
1024
        assert(si);
973✔
1025
        assert(ctx);
973✔
1026

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

1046
        return sd_event_exit(sd_event_source_get_event(s), r);
973✔
1047
}
1048

1049
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
1,817✔
1050
        CalloutContext *ctx = ASSERT_PTR(userdata);
1,817✔
1051
        int r;
1,817✔
1052

1053
        assert(fd >= 0);
1,817✔
1054

1055
        _cleanup_free_ char *buf = NULL;
1,817✔
1056
        _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
1,817✔
1057
        r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
1,817✔
1058
        if (r == -EAGAIN)
1,817✔
1059
                return 0;
1060
        if (r < 0)
1,817✔
1061
                return r;
1062

1063
        if (!pidref_equal(&ctx->pid, &sender_pid)) {
1,817✔
1064
                log_warning("Got notification datagram from unexpected peer, ignoring.");
×
1065
                return 0;
1066
        }
1067

1068
        char *errno_str = find_line_startswith(buf, "ERRNO=");
1,817✔
1069
        if (errno_str) {
1,817✔
1070
                truncate_nl(errno_str);
16✔
1071
                r = parse_errno(errno_str);
16✔
1072
                if (r < 0)
16✔
1073
                        log_warning_errno(r, "Got invalid errno value '%s', ignoring: %m", errno_str);
×
1074
                else {
1075
                        ctx->helper_errno = r;
16✔
1076
                        log_debug_errno(r, "Got errno from callout: %i (%m)", r);
16✔
1077
                }
1078
        }
1079

1080
        char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
1,817✔
1081
        if (progress_str) {
1,817✔
1082
                truncate_nl(progress_str);
828✔
1083

1084
                int progress = parse_percent(progress_str);
828✔
1085
                if (progress < 0)
828✔
1086
                        log_warning("Got invalid percent value '%s', ignoring.", progress_str);
×
1087
                else {
1088
                        r = ctx->callback(ctx->transfer, ctx->instance, progress);
828✔
1089
                        if (r < 0)
828✔
1090
                                return r;
×
1091
                }
1092
        }
1093

1094
        return 0;
1095
}
1096

1097
static int run_callout(
973✔
1098
                const char *name,
1099
                char *cmdline[],
1100
                const Transfer *transfer,
1101
                const Instance *instance,
1102
                TransferProgress callback,
1103
                void *userdata) {
1104

1105
        int r;
973✔
1106

1107
        assert(name);
973✔
1108
        assert(cmdline);
973✔
1109
        assert(cmdline[0]);
973✔
1110

1111
        _cleanup_(callout_context_freep) CalloutContext *ctx = NULL;
×
1112
        r = callout_context_new(transfer, instance, callback, name, userdata, &ctx);
973✔
1113
        if (r < 0)
973✔
1114
                return log_oom();
×
1115

1116
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
973✔
1117
        r = sd_event_new(&event);
973✔
1118
        if (r < 0)
973✔
1119
                return log_error_errno(r, "Failed to create event: %m");
×
1120

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

1129
        _cleanup_free_ char *bind_name = NULL;
973✔
1130
        r = notify_socket_prepare(
973✔
1131
                        event,
1132
                        SD_EVENT_PRIORITY_NORMAL - 5,
1133
                        helper_on_notify,
1134
                        ctx,
1135
                        &bind_name);
1136
        if (r < 0)
973✔
1137
                return log_error_errno(r, "Failed to prepare notify socket: %m");
×
1138

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

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

1159
        r = sd_event_source_set_child_process_own(exit_source, true);
973✔
1160
        if (r < 0)
973✔
1161
                return log_error_errno(r, "Failed to take ownership of child process: %m");
×
1162

1163
        /* Process events until the helper quits */
1164
        return sd_event_loop(event);
973✔
1165
}
1166

1167
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
1168
 * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
1169
 * skipped). */
1170
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
1,973✔
1171
        _cleanup_free_ char *formatted_pattern = NULL;
1,973✔
1172
        int r;
1,973✔
1173

1174
        assert(t);
1,973✔
1175
        assert(i);
1,973✔
1176

1177
        assert(!t->final_path);
1,973✔
1178
        assert(!t->temporary_partial_path);
1,973✔
1179
        assert(!t->temporary_pending_path);
1,973✔
1180
        assert(!t->final_partition_label);
1,973✔
1181
        assert(!strv_isempty(t->target.patterns));
1,973✔
1182

1183
        /* Format the target name using the first pattern specified */
1184
        compile_pattern_fields(t, i, f);
1,973✔
1185
        r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
1,973✔
1186
        if (r < 0)
1,973✔
1187
                return log_error_errno(r, "Failed to format target pattern: %m");
×
1188

1189
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1,973✔
1190
                _cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
1,421✔
1191

1192
                if (!path_is_safe(formatted_pattern))
1,421✔
1193
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
×
1194

1195
                t->final_path = path_join(t->target.path, formatted_pattern);
1,421✔
1196
                if (!t->final_path)
1,421✔
1197
                        return log_oom();
×
1198

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

1209
                if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
1,421✔
1210
                        return log_oom();
×
1211

1212
                if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
1,421✔
1213
                        return log_oom();
×
1214

1215
                t->temporary_partial_path = path_join(final_dir, partial_filename);
1,421✔
1216
                if (!t->temporary_partial_path)
1,421✔
1217
                        return log_oom();
×
1218

1219
                t->temporary_pending_path = path_join(final_dir, pending_filename);
1,421✔
1220
                if (!t->temporary_pending_path)
1,421✔
1221
                        return log_oom();
×
1222
        }
1223

1224
        if (t->target.type == RESOURCE_PARTITION) {
1,973✔
1225
                r = gpt_partition_label_valid(formatted_pattern);
552✔
1226
                if (r < 0)
552✔
1227
                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
×
1228
                if (!r)
552✔
1229
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
×
1230

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

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

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

1244
                t->final_partition_label = TAKE_PTR(formatted_pattern);
552✔
1245
        }
1246

1247
        return 0;
1248
}
1249

1250
int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, TransferProgress cb, void *userdata) {
973✔
1251
        _cleanup_free_ char *digest = NULL;
973✔
1252
        char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
973✔
1253
        const char *where = NULL;
973✔
1254
        Instance *existing;
973✔
1255
        int r;
973✔
1256

1257
        assert(t);
973✔
1258
        assert(i);
973✔
1259
        assert(f);
973✔
1260
        assert(i->resource == &t->source);
973✔
1261
        assert(cb);
973✔
1262

1263
        /* Does this instance already exist in the target? Then we don't need to acquire anything */
1264
        existing = resource_find_instance(&t->target, i->metadata.version);
973✔
1265
        if (existing && (existing->is_partial || existing->is_pending))
973✔
1266
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
×
1267
        if (existing) {
973✔
1268
                log_info("No need to acquire '%s', already installed.", i->path);
×
1269
                return 0;
1270
        }
1271

1272
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
973✔
1273
                r = mkdir_parents(t->temporary_partial_path, 0755);
685✔
1274
                if (r < 0)
685✔
1275
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1276

1277
                r = mkdir_parents(t->temporary_pending_path, 0755);
685✔
1278
                if (r < 0)
685✔
1279
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1280

1281
                r = mkdir_parents(t->final_path, 0755);
685✔
1282
                if (r < 0)
685✔
1283
                        return log_error_errno(r, "Cannot create target directory: %m");
×
1284

1285
                where = t->final_path;
685✔
1286
        }
1287

1288
        if (t->target.type == RESOURCE_PARTITION) {
973✔
1289
                r = find_suitable_partition(
288✔
1290
                                t->target.path,
288✔
1291
                                i->metadata.size,
1292
                                t->target.partition_type_set ? &t->target.partition_type.uuid : NULL,
288✔
1293
                                &t->partition_info);
1294
                if (r < 0)
288✔
1295
                        return r;
1296

1297
                xsprintf(offset, "%" PRIu64, t->partition_info.start);
288✔
1298
                xsprintf(max_size, "%" PRIu64, t->partition_info.size);
288✔
1299

1300
                where = t->partition_info.device;
288✔
1301

1302
                /* Set the partition label and change the partition type to the derived "partial" type UUID
1303
                 * to indicate that a transfer to it is in progress. */
1304
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
288✔
1305
                if (r < 0)
288✔
1306
                        return r;
1307
                t->partition_info.type = t->partition_type_partial;
288✔
1308
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
288✔
1309

1310
                log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
288✔
1311
                          t->partition_info.device,
1312
                          t->partition_info.label,
1313
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1314
                r = patch_partition(
576✔
1315
                                t->target.path,
288✔
1316
                                &t->partition_info,
1317
                                t->partition_change);
1318
                if (r < 0)
288✔
1319
                        return r;
1320
        }
1321

1322
        assert(where);
973✔
1323

1324
        log_info("%s Acquiring %s %s %s...", glyph(GLYPH_DOWNLOAD), i->path, glyph(GLYPH_ARROW_RIGHT), where);
1,946✔
1325

1326
        if (RESOURCE_IS_URL(i->resource->type)) {
973✔
1327
                /* For URL sources we require the SHA256 sum to be known so that we can validate the
1328
                 * download. */
1329

1330
                if (!i->metadata.sha256sum_set)
116✔
1331
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 checksum not known for download '%s', refusing.", i->path);
×
1332

1333
                digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
116✔
1334
                if (!digest)
116✔
1335
                        return log_oom();
×
1336
        }
1337

1338
        switch (i->resource->type) { /* Source */
973✔
1339

1340
        case RESOURCE_REGULAR_FILE:
774✔
1341

1342
                switch (t->target.type) { /* Target */
774✔
1343

1344
                case RESOURCE_REGULAR_FILE:
550✔
1345

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

1351
                        r = run_callout("(sd-import-raw)",
1,100✔
1352
                                        STRV_MAKE(
550✔
1353
                                               SYSTEMD_IMPORT_PATH,
1354
                                               "raw",
1355
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1356
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1357
                                               i->path,
1358
                                               t->temporary_partial_path),
1359
                                        t, i, cb, userdata);
1360
                        break;
774✔
1361

1362
                case RESOURCE_PARTITION:
224✔
1363

1364
                        /* regular file → partition */
1365

1366
                        r = run_callout("(sd-import-raw)",
448✔
1367
                                        STRV_MAKE(
224✔
1368
                                               SYSTEMD_IMPORT_PATH,
1369
                                               "raw",
1370
                                               "--direct",          /* just copy/unpack the specified file, don't do anything else */
1371
                                               "--offset", offset,
1372
                                               "--size-max", max_size,
1373
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1374
                                               i->path,
1375
                                               t->target.path),
1376
                                        t, i, cb, userdata);
1377
                        break;
1378

1379
                default:
×
1380
                        assert_not_reached();
×
1381
                }
1382

1383
                break;
774✔
1384

1385
        case RESOURCE_DIRECTORY:
83✔
1386
        case RESOURCE_SUBVOLUME:
1387
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1388

1389
                /* directory/subvolume → directory/subvolume */
1390

1391
                r = run_callout("(sd-import-fs)",
166✔
1392
                                STRV_MAKE(
166✔
1393
                                       SYSTEMD_IMPORT_FS_PATH,
1394
                                       "run",
1395
                                       "--direct",          /* just untar the specified file, don't do anything else */
1396
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1397
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1398
                                       i->path,
1399
                                       t->temporary_partial_path),
1400
                                t, i, cb, userdata);
1401
                break;
1402

1403
        case RESOURCE_TAR:
×
1404
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
×
1405

1406
                /* tar → directory/subvolume */
1407

1408
                r = run_callout("(sd-import-tar)",
×
1409
                                STRV_MAKE(
×
1410
                                       SYSTEMD_IMPORT_PATH,
1411
                                       "tar",
1412
                                       "--direct",          /* just untar the specified file, don't do anything else */
1413
                                       t->context->sync ? "--sync=yes" : "--sync=no",
1414
                                       t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1415
                                       i->path,
1416
                                       t->temporary_partial_path),
1417
                                t, i, cb, userdata);
1418
                break;
1419

1420
        case RESOURCE_URL_FILE:
68✔
1421

1422
                switch (t->target.type) {
68✔
1423

1424
                case RESOURCE_REGULAR_FILE:
4✔
1425

1426
                        /* url file → regular file */
1427

1428
                        r = run_callout("(sd-pull-raw)",
8✔
1429
                                       STRV_MAKE(
4✔
1430
                                               SYSTEMD_PULL_PATH,
1431
                                               "raw",
1432
                                               "--direct",          /* just download the specified URL, don't download anything else */
1433
                                               "--verify", digest,  /* validate by explicit SHA256 sum */
1434
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1435
                                               i->path,
1436
                                               t->temporary_partial_path),
1437
                                        t, i, cb, userdata);
1438
                        break;
68✔
1439

1440
                case RESOURCE_PARTITION:
64✔
1441

1442
                        /* url file → partition */
1443

1444
                        r = run_callout("(sd-pull-raw)",
128✔
1445
                                        STRV_MAKE(
64✔
1446
                                               SYSTEMD_PULL_PATH,
1447
                                               "raw",
1448
                                               "--direct",              /* just download the specified URL, don't download anything else */
1449
                                               "--verify", digest,      /* validate by explicit SHA256 sum */
1450
                                               "--offset", offset,
1451
                                               "--size-max", max_size,
1452
                                               t->context->sync ? "--sync=yes" : "--sync=no",
1453
                                               i->path,
1454
                                               t->target.path),
1455
                                        t, i, cb, userdata);
1456
                        break;
1457

1458
                default:
×
1459
                        assert_not_reached();
×
1460
                }
1461

1462
                break;
68✔
1463

1464
        case RESOURCE_URL_TAR:
48✔
1465
                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
48✔
1466

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

1480
        default:
×
1481
                assert_not_reached();
×
1482
        }
1483
        if (r < 0)
973✔
1484
                return r;
1485

1486
        if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
957✔
1487
                bool need_sync = false;
685✔
1488
                assert(t->temporary_partial_path);
685✔
1489
                assert(t->temporary_pending_path);
685✔
1490

1491
                /* Apply file attributes if set */
1492
                if (f->mtime != USEC_INFINITY) {
685✔
1493
                        struct timespec ts;
633✔
1494

1495
                        timespec_store(&ts, f->mtime);
633✔
1496

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

1500
                        need_sync = true;
633✔
1501
                }
1502

1503
                if (f->mode != MODE_INVALID) {
685✔
1504
                        /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1505
                         * kernels don't support that however, in that case we fall back to chmod(). Not as
1506
                         * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1507
                        if (fchmodat(AT_FDCWD, t->temporary_partial_path, f->mode, AT_SYMLINK_NOFOLLOW) < 0 &&
633✔
1508
                            (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f->mode) < 0))
×
1509
                                return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path);
×
1510

1511
                        need_sync = true;
1512
                }
1513

1514
                /* Synchronize */
1515
                if (t->context->sync && need_sync) {
685✔
1516
                        if (t->target.type == RESOURCE_REGULAR_FILE)
633✔
1517
                                r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
550✔
1518
                        else {
1519
                                assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
83✔
1520
                                r = syncfs_path(AT_FDCWD, t->temporary_partial_path);
83✔
1521
                        }
1522
                        if (r < 0)
633✔
1523
                                return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
×
1524
                }
1525

1526
                t->install_read_only = f->read_only;
685✔
1527

1528
                /* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate it’s ready to install. */
1529
                log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
685✔
1530
                r = install_file(AT_FDCWD, t->temporary_partial_path,
2,055✔
1531
                                 AT_FDCWD, t->temporary_pending_path,
685✔
1532
                                 INSTALL_REPLACE|
685✔
1533
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,370✔
1534
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
685✔
1535
                if (r < 0)
685✔
1536
                        return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
×
1537
        }
1538

1539
        if (t->target.type == RESOURCE_PARTITION) {
957✔
1540
                /* Now change the partition type to the derived "pending" type UUID to indicate that the
1541
                 * acquire is complete and the partition is ready for install. */
1542
                t->partition_info.type = t->partition_type_pending;
272✔
1543
                t->partition_change = PARTITION_TYPE;
272✔
1544

1545
                if (f->partition_uuid_set) {
272✔
1546
                        t->partition_info.uuid = f->partition_uuid;
×
1547
                        t->partition_change |= PARTITION_UUID;
×
1548
                }
1549

1550
                if (f->partition_flags_set) {
272✔
1551
                        t->partition_info.flags = f->partition_flags;
×
1552
                        t->partition_change |= PARTITION_FLAGS;
×
1553
                }
1554

1555
                if (f->no_auto >= 0) {
272✔
1556
                        t->partition_info.no_auto = f->no_auto;
×
1557
                        t->partition_change |= PARTITION_NO_AUTO;
×
1558
                }
1559

1560
                if (f->read_only >= 0) {
272✔
1561
                        t->partition_info.read_only = f->read_only;
×
1562
                        t->partition_change |= PARTITION_READ_ONLY;
×
1563
                }
1564

1565
                if (f->growfs >= 0) {
272✔
1566
                        t->partition_info.growfs = f->growfs;
×
1567
                        t->partition_change |= PARTITION_GROWFS;
×
1568
                }
1569

1570
                log_debug("Marking partition '%s' as pending (type=%s).",
272✔
1571
                          t->partition_info.device,
1572
                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
1573
                r = patch_partition(
544✔
1574
                                t->target.path,
272✔
1575
                                &t->partition_info,
272✔
1576
                                t->partition_change);
1577
                if (r < 0)
272✔
1578
                        return r;
1579
        }
1580

1581
        /* For regular file cases the only step left is to install the file in place, which install_file()
1582
         * will do via rename(). For partition cases the only step left is to update the partition table,
1583
         * which is done at the same place. */
1584

1585
        log_info("Successfully acquired '%s'.", i->path);
957✔
1586
        return 0;
1587
}
1588

1589
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
796✔
1590
        InstanceMetadata f;
796✔
1591
        Instance *existing;
796✔
1592
        int r;
796✔
1593

1594
        assert(t);
796✔
1595
        assert(i);
796✔
1596

1597
        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
796✔
1598

1599
        /* Does this instance already exist in the target but isn’t pending? */
1600
        existing = resource_find_instance(&t->target, i->metadata.version);
796✔
1601
        if (existing && !existing->is_pending) {
796✔
1602
                log_info("Resource '%s' instance is already in the target but is not pending.", i->path);
84✔
1603
                return 0;
796✔
1604
        }
1605

1606
        /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
1607
         * transfer_acquire_instance(). */
1608
        r = transfer_compute_temporary_paths(t, i, &f);
712✔
1609
        if (r < 0)
712✔
1610
                return r;
1611

1612
        /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
1613
         * already happened in the acquire phase, the target should already have that information and it
1614
         * should already have been claimed with the pending partition type UUID. */
1615
        if (t->target.type == RESOURCE_PARTITION) {
712✔
1616
                assert(i->resource == &t->target);
200✔
1617
                assert(i->is_pending);
200✔
1618

1619
                r = partition_info_copy(&t->partition_info, &i->partition_info);
200✔
1620
                if (r < 0)
200✔
1621
                        return r;
×
1622
        }
1623

1624
        return 0;
1625
}
1626

1627
int transfer_install_instance(
941✔
1628
                Transfer *t,
1629
                Instance *i,
1630
                const char *root) {
1631

1632
        int r;
941✔
1633

1634
        assert(t);
941✔
1635
        assert(i);
941✔
1636
        assert(i->resource);
941✔
1637
        assert(i->is_pending || t == container_of(i->resource, Transfer, source));
941✔
1638

1639
        log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
941✔
1640

1641
        if (t->temporary_pending_path) {
941✔
1642
                assert(RESOURCE_IS_FILESYSTEM(t->target.type));
685✔
1643
                assert(t->final_path);
685✔
1644

1645
                r = install_file(AT_FDCWD, t->temporary_pending_path,
2,055✔
1646
                                 AT_FDCWD, t->final_path,
1647
                                 INSTALL_REPLACE|
685✔
1648
                                 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1,370✔
1649
                                 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
685✔
1650
                if (r < 0)
685✔
1651
                        return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
×
1652

1653
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
685✔
1654
                         i->path,
1655
                         resource_type_to_string(i->resource->type),
1656
                         t->final_path,
1657
                         resource_type_to_string(t->target.type));
1658

1659
                t->temporary_pending_path = mfree(t->temporary_pending_path);
685✔
1660

1661
                (void) context_installdb_record(t->context, t->target.path, t->target.patterns);
685✔
1662
        }
1663

1664
        if (t->final_partition_label) {
941✔
1665
                assert(t->target.type == RESOURCE_PARTITION);
256✔
1666
                assert(t->target.partition_type_set);
256✔
1667

1668
                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
256✔
1669
                if (r < 0)
256✔
1670
                        return r;
1671

1672
                /* Restore the original partition type UUID now that the partition is fully installed. */
1673
                t->partition_info.type = t->target.partition_type.uuid;
256✔
1674
                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
256✔
1675

1676
                r = patch_partition(
512✔
1677
                                t->target.path,
256✔
1678
                                &t->partition_info,
256✔
1679
                                t->partition_change);
1680
                if (r < 0)
256✔
1681
                        return r;
1682

1683
                log_info("Successfully installed '%s' (%s) as '%s' (%s).",
256✔
1684
                         i->path,
1685
                         resource_type_to_string(i->resource->type),
1686
                         t->partition_info.device,
1687
                         resource_type_to_string(t->target.type));
1688
        }
1689

1690
        if (t->current_symlink) {
941✔
1691
                _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
128✔
1692
                const char *link_path, *link_target;
128✔
1693
                bool resolve_link_path = false;
128✔
1694

1695
                if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
128✔
1696

1697
                        assert(t->target.path);
128✔
1698

1699
                        if (path_is_absolute(t->current_symlink)) {
128✔
1700
                                link_path = t->current_symlink;
1701
                                resolve_link_path = true;
1702
                        } else {
1703
                                buf = path_make_absolute(t->current_symlink, t->target.path);
×
1704
                                if (!buf)
×
1705
                                        return log_oom();
×
1706

1707
                                link_path = buf;
1708
                        }
1709

1710
                        link_target = t->final_path;
128✔
1711

1712
                } else if (t->target.type == RESOURCE_PARTITION) {
×
1713

1714
                        assert(path_is_absolute(t->current_symlink));
×
1715

1716
                        link_path = t->current_symlink;
×
1717
                        link_target = t->partition_info.device;
×
1718

1719
                        resolve_link_path = true;
×
1720
                } else
1721
                        assert_not_reached();
×
1722

1723
                if (resolve_link_path && root) {
128✔
1724
                        r = chase(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT|CHASE_TRIGGER_AUTOFS, &resolved, NULL);
×
1725
                        if (r < 0)
×
1726
                                return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
×
1727

1728
                        link_path = resolved;
×
1729
                }
1730

1731
                if (link_target) {
128✔
1732
                        r = path_extract_directory(link_path, &parent);
128✔
1733
                        if (r < 0)
128✔
1734
                                return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
×
1735

1736
                        r = path_make_relative(parent, link_target, &relative);
128✔
1737
                        if (r < 0)
128✔
1738
                                return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
×
1739

1740
                        r = mkdir_parents(link_path, 0755);
128✔
1741
                        if (r < 0)
128✔
1742
                                return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path);
×
1743

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

1751
                        log_info("Updated symlink '%s' %s '%s'.",
128✔
1752
                                 link_path, glyph(GLYPH_ARROW_RIGHT), relative);
1753
                }
1754
        }
1755

1756
        return 0;
1757
}
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