]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysupdate/sysupdate-transfer.c
tree-wide: use ASSERT_PTR more
[thirdparty/systemd.git] / src / sysupdate / sysupdate-transfer.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "sd-id128.h"
4
5 #include "alloc-util.h"
6 #include "blockdev-util.h"
7 #include "chase-symlinks.h"
8 #include "conf-parser.h"
9 #include "dirent-util.h"
10 #include "fd-util.h"
11 #include "glyph-util.h"
12 #include "gpt.h"
13 #include "hexdecoct.h"
14 #include "install-file.h"
15 #include "parse-helpers.h"
16 #include "parse-util.h"
17 #include "process-util.h"
18 #include "rm-rf.h"
19 #include "specifier.h"
20 #include "stat-util.h"
21 #include "stdio-util.h"
22 #include "strv.h"
23 #include "sync-util.h"
24 #include "sysupdate-pattern.h"
25 #include "sysupdate-resource.h"
26 #include "sysupdate-transfer.h"
27 #include "sysupdate-util.h"
28 #include "sysupdate.h"
29 #include "tmpfile-util.h"
30 #include "web-util.h"
31
32 /* Default value for InstancesMax= for fs object targets */
33 #define DEFAULT_FILE_INSTANCES_MAX 3
34
35 Transfer *transfer_free(Transfer *t) {
36 if (!t)
37 return NULL;
38
39 t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
40
41 free(t->definition_path);
42 free(t->min_version);
43 strv_free(t->protected_versions);
44 free(t->current_symlink);
45 free(t->final_path);
46
47 partition_info_destroy(&t->partition_info);
48
49 resource_destroy(&t->source);
50 resource_destroy(&t->target);
51
52 return mfree(t);
53 }
54
55 Transfer *transfer_new(void) {
56 Transfer *t;
57
58 t = new(Transfer, 1);
59 if (!t)
60 return NULL;
61
62 *t = (Transfer) {
63 .source.type = _RESOURCE_TYPE_INVALID,
64 .target.type = _RESOURCE_TYPE_INVALID,
65 .remove_temporary = true,
66 .mode = MODE_INVALID,
67 .tries_left = UINT64_MAX,
68 .tries_done = UINT64_MAX,
69 .verify = true,
70
71 /* the three flags, as configured by the user */
72 .no_auto = -1,
73 .read_only = -1,
74 .growfs = -1,
75
76 /* the read only flag, as ultimately determined */
77 .install_read_only = -1,
78
79 .partition_info = PARTITION_INFO_NULL,
80 };
81
82 return t;
83 }
84
85 static const Specifier specifier_table[] = {
86 COMMON_SYSTEM_SPECIFIERS,
87 COMMON_TMP_SPECIFIERS,
88 {}
89 };
90
91 static int config_parse_protect_version(
92 const char *unit,
93 const char *filename,
94 unsigned line,
95 const char *section,
96 unsigned section_line,
97 const char *lvalue,
98 int ltype,
99 const char *rvalue,
100 void *data,
101 void *userdata) {
102
103 _cleanup_free_ char *resolved = NULL;
104 char ***protected_versions = ASSERT_PTR(data);
105 int r;
106
107 assert(rvalue);
108
109 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
110 if (r < 0) {
111 log_syntax(unit, LOG_WARNING, filename, line, r,
112 "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
113 return 0;
114 }
115
116 if (!version_is_valid(resolved)) {
117 log_syntax(unit, LOG_WARNING, filename, line, 0,
118 "ProtectVersion= string is not valid, ignoring: %s", resolved);
119 return 0;
120 }
121
122 r = strv_extend(protected_versions, resolved);
123 if (r < 0)
124 return log_oom();
125
126 return 0;
127 }
128
129 static int config_parse_min_version(
130 const char *unit,
131 const char *filename,
132 unsigned line,
133 const char *section,
134 unsigned section_line,
135 const char *lvalue,
136 int ltype,
137 const char *rvalue,
138 void *data,
139 void *userdata) {
140
141 _cleanup_free_ char *resolved = NULL;
142 char **version = ASSERT_PTR(data);
143 int r;
144
145 assert(rvalue);
146
147 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
148 if (r < 0) {
149 log_syntax(unit, LOG_WARNING, filename, line, r,
150 "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
151 return 0;
152 }
153
154 if (!version_is_valid(rvalue)) {
155 log_syntax(unit, LOG_WARNING, filename, line, 0,
156 "MinVersion= string is not valid, ignoring: %s", resolved);
157 return 0;
158 }
159
160 return free_and_replace(*version, resolved);
161 }
162
163 static int config_parse_current_symlink(
164 const char *unit,
165 const char *filename,
166 unsigned line,
167 const char *section,
168 unsigned section_line,
169 const char *lvalue,
170 int ltype,
171 const char *rvalue,
172 void *data,
173 void *userdata) {
174
175 _cleanup_free_ char *resolved = NULL;
176 char **current_symlink = ASSERT_PTR(data);
177 int r;
178
179 assert(rvalue);
180
181 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
182 if (r < 0) {
183 log_syntax(unit, LOG_WARNING, filename, line, r,
184 "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
185 return 0;
186 }
187
188 r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
189 if (r < 0)
190 return 0;
191
192 return free_and_replace(*current_symlink, resolved);
193 }
194
195 static int config_parse_instances_max(
196 const char *unit,
197 const char *filename,
198 unsigned line,
199 const char *section,
200 unsigned section_line,
201 const char *lvalue,
202 int ltype,
203 const char *rvalue,
204 void *data,
205 void *userdata) {
206
207 uint64_t *instances_max = data, i;
208 int r;
209
210 assert(rvalue);
211 assert(data);
212
213 if (isempty(rvalue)) {
214 *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
215 return 0;
216 }
217
218 r = safe_atou64(rvalue, &i);
219 if (r < 0) {
220 log_syntax(unit, LOG_WARNING, filename, line, r,
221 "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
222 return 0;
223 }
224
225 if (i < 2) {
226 log_syntax(unit, LOG_WARNING, filename, line, 0,
227 "InstancesMax= value must be at least 2, bumping: %s", rvalue);
228 *instances_max = 2;
229 } else
230 *instances_max = i;
231
232 return 0;
233 }
234
235 static int config_parse_resource_pattern(
236 const char *unit,
237 const char *filename,
238 unsigned line,
239 const char *section,
240 unsigned section_line,
241 const char *lvalue,
242 int ltype,
243 const char *rvalue,
244 void *data,
245 void *userdata) {
246
247 char ***patterns = ASSERT_PTR(data);
248 int r;
249
250 assert(rvalue);
251
252 if (isempty(rvalue)) {
253 *patterns = strv_free(*patterns);
254 return 0;
255 }
256
257 for (;;) {
258 _cleanup_free_ char *word = NULL, *resolved = NULL;
259
260 r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
261 if (r < 0) {
262 log_syntax(unit, LOG_WARNING, filename, line, r,
263 "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
264 return 0;
265 }
266 if (r == 0)
267 break;
268
269 r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
270 if (r < 0) {
271 log_syntax(unit, LOG_WARNING, filename, line, r,
272 "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
273 return 0;
274 }
275
276 if (!pattern_valid(resolved))
277 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
278 "MatchPattern= string is not valid, refusing: %s", resolved);
279
280 r = strv_consume(patterns, TAKE_PTR(resolved));
281 if (r < 0)
282 return log_oom();
283 }
284
285 strv_uniq(*patterns);
286 return 0;
287 }
288
289 static int config_parse_resource_path(
290 const char *unit,
291 const char *filename,
292 unsigned line,
293 const char *section,
294 unsigned section_line,
295 const char *lvalue,
296 int ltype,
297 const char *rvalue,
298 void *data,
299 void *userdata) {
300
301 _cleanup_free_ char *resolved = NULL;
302 Resource *rr = ASSERT_PTR(data);
303 int r;
304
305 assert(rvalue);
306
307 if (streq(rvalue, "auto")) {
308 rr->path_auto = true;
309 rr->path = mfree(rr->path);
310 return 0;
311 }
312
313 r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved);
314 if (r < 0) {
315 log_syntax(unit, LOG_WARNING, filename, line, r,
316 "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
317 return 0;
318 }
319
320 /* Note that we don't validate the path as being absolute or normalized. We'll do that in
321 * transfer_read_definition() as we might not know yet whether Path refers to an URL or a file system
322 * path. */
323
324 rr->path_auto = false;
325 return free_and_replace(rr->path, resolved);
326 }
327
328 static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType, "Invalid resource type");
329
330 static int config_parse_resource_ptype(
331 const char *unit,
332 const char *filename,
333 unsigned line,
334 const char *section,
335 unsigned section_line,
336 const char *lvalue,
337 int ltype,
338 const char *rvalue,
339 void *data,
340 void *userdata) {
341
342 Resource *rr = ASSERT_PTR(data);
343 int r;
344
345 assert(rvalue);
346
347 r = gpt_partition_type_uuid_from_string(rvalue, &rr->partition_type);
348 if (r < 0) {
349 log_syntax(unit, LOG_WARNING, filename, line, r,
350 "Failed parse partition type, ignoring: %s", rvalue);
351 return 0;
352 }
353
354 rr->partition_type_set = true;
355 return 0;
356 }
357
358 static int config_parse_partition_uuid(
359 const char *unit,
360 const char *filename,
361 unsigned line,
362 const char *section,
363 unsigned section_line,
364 const char *lvalue,
365 int ltype,
366 const char *rvalue,
367 void *data,
368 void *userdata) {
369
370 Transfer *t = ASSERT_PTR(data);
371 int r;
372
373 assert(rvalue);
374
375 r = sd_id128_from_string(rvalue, &t->partition_uuid);
376 if (r < 0) {
377 log_syntax(unit, LOG_WARNING, filename, line, r,
378 "Failed parse partition UUID, ignoring: %s", rvalue);
379 return 0;
380 }
381
382 t->partition_uuid_set = true;
383 return 0;
384 }
385
386 static int config_parse_partition_flags(
387 const char *unit,
388 const char *filename,
389 unsigned line,
390 const char *section,
391 unsigned section_line,
392 const char *lvalue,
393 int ltype,
394 const char *rvalue,
395 void *data,
396 void *userdata) {
397
398 Transfer *t = ASSERT_PTR(data);
399 int r;
400
401 assert(rvalue);
402
403 r = safe_atou64(rvalue, &t->partition_flags);
404 if (r < 0) {
405 log_syntax(unit, LOG_WARNING, filename, line, r,
406 "Failed parse partition flags, ignoring: %s", rvalue);
407 return 0;
408 }
409
410 t->partition_flags_set = true;
411 return 0;
412 }
413
414 int transfer_read_definition(Transfer *t, const char *path) {
415 int r;
416
417 assert(t);
418 assert(path);
419
420 ConfigTableItem table[] = {
421 { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version },
422 { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions },
423 { "Transfer", "Verify", config_parse_bool, 0, &t->verify },
424 { "Source", "Type", config_parse_resource_type, 0, &t->source.type },
425 { "Source", "Path", config_parse_resource_path, 0, &t->source },
426 { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns },
427 { "Target", "Type", config_parse_resource_type, 0, &t->target.type },
428 { "Target", "Path", config_parse_resource_path, 0, &t->target },
429 { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns },
430 { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target },
431 { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t },
432 { "Target", "PartitionFlags", config_parse_partition_flags, 0, t },
433 { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto },
434 { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs },
435 { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only },
436 { "Target", "Mode", config_parse_mode, 0, &t->mode },
437 { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left },
438 { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done },
439 { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max },
440 { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary },
441 { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink },
442 {}
443 };
444
445 r = config_parse(NULL, path, NULL,
446 "Transfer\0"
447 "Source\0"
448 "Target\0",
449 config_item_table_lookup, table,
450 CONFIG_PARSE_WARN,
451 t,
452 NULL);
453 if (r < 0)
454 return r;
455
456 if (!RESOURCE_IS_SOURCE(t->source.type))
457 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
458 "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
459
460 if (t->target.type < 0) {
461 switch (t->source.type) {
462
463 case RESOURCE_URL_FILE:
464 case RESOURCE_REGULAR_FILE:
465 t->target.type =
466 t->target.path && path_startswith(t->target.path, "/dev/") ?
467 RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
468 break;
469
470 case RESOURCE_URL_TAR:
471 case RESOURCE_TAR:
472 case RESOURCE_DIRECTORY:
473 t->target.type = RESOURCE_DIRECTORY;
474 break;
475
476 case RESOURCE_SUBVOLUME:
477 t->target.type = RESOURCE_SUBVOLUME;
478 break;
479
480 default:
481 assert_not_reached();
482 }
483 }
484
485 if (!RESOURCE_IS_TARGET(t->target.type))
486 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
487 "Target Type= must be one of partition, regular-file, directory, subvolume.");
488
489 if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
490 !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
491 (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
492 !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
493 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
494 "Target type '%s' is incompatible with source type '%s', refusing.",
495 resource_type_to_string(t->source.type), resource_type_to_string(t->target.type));
496
497 if (!t->source.path && !t->source.path_auto)
498 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
499 "Source specification lacks Path=.");
500
501 if (t->source.path) {
502 if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
503 if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
504 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
505 "Source path is not a normalized, absolute path: %s", t->source.path);
506
507 /* We unofficially support file:// in addition to http:// and https:// for url
508 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
509 * server, and CURL abstracts this away from us thankfully. */
510 if (RESOURCE_IS_URL(t->source.type))
511 if (!http_url_is_valid(t->source.path) && !file_url_is_valid(t->source.path))
512 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
513 "Source path is not a valid HTTP or HTTPS URL: %s", t->source.path);
514 }
515
516 if (strv_isempty(t->source.patterns))
517 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
518 "Source specification lacks MatchPattern=.");
519
520 if (!t->target.path && !t->target.path_auto)
521 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
522 "Target specification lacks Path= field.");
523
524 if (t->target.path &&
525 (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
526 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
527 "Target path is not a normalized, absolute path: %s", t->target.path);
528
529 if (strv_isempty(t->target.patterns)) {
530 strv_free(t->target.patterns);
531 t->target.patterns = strv_copy(t->source.patterns);
532 if (!t->target.patterns)
533 return log_oom();
534 }
535
536 if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
537 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
538 "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
539
540 /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
541 if (t->instances_max == 0)
542 t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
543
544 return 0;
545 }
546
547 int transfer_resolve_paths(
548 Transfer *t,
549 const char *root,
550 const char *node) {
551
552 int r;
553
554 /* If Path=auto is used in [Source] or [Target] sections, let's automatically detect the path of the
555 * block device to use. Moreover, if this path points to a directory but we need a block device,
556 * automatically determine the backing block device, so that users can reference block devices by
557 * mount point. */
558
559 assert(t);
560
561 r = resource_resolve_path(&t->source, root, node);
562 if (r < 0)
563 return r;
564
565 r = resource_resolve_path(&t->target, root, node);
566 if (r < 0)
567 return r;
568
569 return 0;
570 }
571
572 static void transfer_remove_temporary(Transfer *t) {
573 _cleanup_(closedirp) DIR *d = NULL;
574 int r;
575
576 assert(t);
577
578 if (!t->remove_temporary)
579 return;
580
581 if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
582 return;
583
584 /* Removes all temporary files/dirs from previous runs in the target directory, i.e. all those starting with '.#' */
585
586 d = opendir(t->target.path);
587 if (!d) {
588 if (errno == ENOENT)
589 return;
590
591 log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
592 return;
593 }
594
595 for (;;) {
596 struct dirent *de;
597
598 errno = 0;
599 de = readdir_no_dot(d);
600 if (!de) {
601 if (errno != 0)
602 log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
603 break;
604 }
605
606 if (!startswith(de->d_name, ".#"))
607 continue;
608
609 r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
610 if (r == -ENOENT)
611 continue;
612 if (r < 0) {
613 log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
614 continue;
615 }
616
617 log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
618 }
619 }
620
621 int transfer_vacuum(
622 Transfer *t,
623 uint64_t space,
624 const char *extra_protected_version) {
625
626 uint64_t instances_max, limit;
627 int r, count = 0;
628
629 assert(t);
630
631 transfer_remove_temporary(t);
632
633 /* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
634
635 instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
636 assert(instances_max >= 1);
637 if (instances_max == UINT64_MAX) /* Keep infinite instances? */
638 limit = UINT64_MAX;
639 else if (space > instances_max)
640 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
641 "Asked to delete more instances than total maximum allowed number of instances, refusing.");
642 else if (space == instances_max)
643 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
644 "Asked to delete all possible instances, can't allow that. One instance must always remain.");
645 else
646 limit = instances_max - space;
647
648 if (t->target.type == RESOURCE_PARTITION) {
649 uint64_t rm, remain;
650
651 /* If we are looking at a partition table, we also have to take into account how many
652 * partition slots of the right type are available */
653
654 if (t->target.n_empty + t->target.n_instances < 2)
655 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
656 "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), refusing.",
657 SD_ID128_FORMAT_VAL(t->target.partition_type),
658 gpt_partition_type_uuid_to_string(t->target.partition_type));
659 if (space > t->target.n_empty + t->target.n_instances)
660 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
661 "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s) for operation.",
662 SD_ID128_FORMAT_VAL(t->target.partition_type),
663 gpt_partition_type_uuid_to_string(t->target.partition_type));
664 if (space == t->target.n_empty + t->target.n_instances)
665 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
666 "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.",
667 SD_ID128_FORMAT_VAL(t->target.partition_type),
668 gpt_partition_type_uuid_to_string(t->target.partition_type));
669
670 rm = LESS_BY(space, t->target.n_empty);
671 remain = LESS_BY(t->target.n_instances, rm);
672 limit = MIN(limit, remain);
673 }
674
675 while (t->target.n_instances > limit) {
676 Instance *oldest;
677 size_t p = t->target.n_instances - 1;
678
679 for (;;) {
680 oldest = t->target.instances[p];
681 assert(oldest);
682
683 /* If this is listed among the protected versions, then let's not remove it */
684 if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
685 (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
686 break;
687
688 log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
689 if (p == 0) {
690 oldest = NULL;
691 break;
692 }
693
694 p--;
695 }
696
697 if (!oldest) /* Nothing more to remove */
698 break;
699
700 assert(oldest->resource);
701
702 log_info("%s Removing old '%s' (%s).", special_glyph(SPECIAL_GLYPH_RECYCLING), oldest->path, resource_type_to_string(oldest->resource->type));
703
704 switch (t->target.type) {
705
706 case RESOURCE_REGULAR_FILE:
707 case RESOURCE_DIRECTORY:
708 case RESOURCE_SUBVOLUME:
709 r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
710 if (r < 0 && r != -ENOENT)
711 return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
712
713 break;
714
715 case RESOURCE_PARTITION: {
716 PartitionInfo pinfo = oldest->partition_info;
717
718 /* label "_empty" means "no contents" for our purposes */
719 pinfo.label = (char*) "_empty";
720
721 r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
722 if (r < 0)
723 return r;
724
725 t->target.n_empty++;
726 break;
727 }
728
729 default:
730 assert_not_reached();
731 break;
732 }
733
734 instance_free(oldest);
735 memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
736 t->target.n_instances--;
737
738 count++;
739 }
740
741 return count;
742 }
743
744 static void compile_pattern_fields(
745 const Transfer *t,
746 const Instance *i,
747 InstanceMetadata *ret) {
748
749 assert(t);
750 assert(i);
751 assert(ret);
752
753 *ret = (InstanceMetadata) {
754 .version = i->metadata.version,
755
756 /* We generally prefer explicitly configured values for the transfer over those automatically
757 * derived from the source instance. Also, if the source is a tar archive, then let's not
758 * patch mtime/mode and use the one embedded in the tar file */
759 .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
760 .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
761 .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
762 .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
763 .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
764 .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
765 .size = i->metadata.size,
766 .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
767 i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
768 .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
769 i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
770 .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
771 .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
772 .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
773 .sha256sum_set = i->metadata.sha256sum_set,
774 };
775
776 memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
777 }
778
779 static int run_helper(
780 const char *name,
781 const char *path,
782 const char * const cmdline[]) {
783
784 int r;
785
786 assert(name);
787 assert(path);
788 assert(cmdline);
789
790 r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
791 if (r < 0)
792 return r;
793 if (r == 0) {
794 /* Child */
795
796 (void) unsetenv("NOTIFY_SOCKET");
797 execv(path, (char *const*) cmdline);
798 log_error_errno(errno, "Failed to execute %s tool: %m", path);
799 _exit(EXIT_FAILURE);
800 }
801
802 return 0;
803 }
804
805 int transfer_acquire_instance(Transfer *t, Instance *i) {
806 _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
807 char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
808 const char *where = NULL;
809 InstanceMetadata f;
810 Instance *existing;
811 int r;
812
813 assert(t);
814 assert(i);
815 assert(i->resource);
816 assert(t == container_of(i->resource, Transfer, source));
817
818 /* Does this instance already exist in the target? Then we don't need to acquire anything */
819 existing = resource_find_instance(&t->target, i->metadata.version);
820 if (existing) {
821 log_info("No need to acquire '%s', already installed.", i->path);
822 return 0;
823 }
824
825 assert(!t->final_path);
826 assert(!t->temporary_path);
827 assert(!strv_isempty(t->target.patterns));
828
829 /* Format the target name using the first pattern specified */
830 compile_pattern_fields(t, i, &f);
831 r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
832 if (r < 0)
833 return log_error_errno(r, "Failed to format target pattern: %m");
834
835 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
836
837 if (!filename_is_valid(formatted_pattern))
838 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
839
840 t->final_path = path_join(t->target.path, formatted_pattern);
841 if (!t->final_path)
842 return log_oom();
843
844 r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
845 if (r < 0)
846 return log_error_errno(r, "Failed to generate temporary target path: %m");
847
848 where = t->final_path;
849 }
850
851 if (t->target.type == RESOURCE_PARTITION) {
852 r = gpt_partition_label_valid(formatted_pattern);
853 if (r < 0)
854 return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
855 if (!r)
856 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
857
858 r = find_suitable_partition(
859 t->target.path,
860 i->metadata.size,
861 t->target.partition_type_set ? &t->target.partition_type : NULL,
862 &t->partition_info);
863 if (r < 0)
864 return r;
865
866 xsprintf(offset, "%" PRIu64, t->partition_info.start);
867 xsprintf(max_size, "%" PRIu64, t->partition_info.size);
868
869 where = t->partition_info.device;
870 }
871
872 assert(where);
873
874 log_info("%s Acquiring %s %s %s...", special_glyph(SPECIAL_GLYPH_DOWNLOAD), i->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), where);
875
876 if (RESOURCE_IS_URL(i->resource->type)) {
877 /* For URL sources we require the SHA256 sum to be known so that we can validate the
878 * download. */
879
880 if (!i->metadata.sha256sum_set)
881 return log_error_errno(r, "SHA256 checksum not known for download '%s', refusing.", i->path);
882
883 digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
884 if (!digest)
885 return log_oom();
886 }
887
888 switch (i->resource->type) { /* Source */
889
890 case RESOURCE_REGULAR_FILE:
891
892 switch (t->target.type) { /* Target */
893
894 case RESOURCE_REGULAR_FILE:
895
896 /* regular file → regular file (why fork off systemd-import for such a simple file
897 * copy case? implicit decompression mostly, and thus also sandboxing. Also, the
898 * importer has some tricks up its sleeve, such as sparse file generation, which we
899 * want to take benefit of, too.) */
900
901 r = run_helper("(sd-import-raw)",
902 import_binary_path(),
903 (const char* const[]) {
904 "systemd-import",
905 "raw",
906 "--direct", /* just copy/unpack the specified file, don't do anything else */
907 arg_sync ? "--sync=yes" : "--sync=no",
908 i->path,
909 t->temporary_path,
910 NULL
911 });
912 break;
913
914 case RESOURCE_PARTITION:
915
916 /* regular file → partition */
917
918 r = run_helper("(sd-import-raw)",
919 import_binary_path(),
920 (const char* const[]) {
921 "systemd-import",
922 "raw",
923 "--direct", /* just copy/unpack the specified file, don't do anything else */
924 "--offset", offset,
925 "--size-max", max_size,
926 arg_sync ? "--sync=yes" : "--sync=no",
927 i->path,
928 t->target.path,
929 NULL
930 });
931 break;
932
933 default:
934 assert_not_reached();
935 }
936
937 break;
938
939 case RESOURCE_DIRECTORY:
940 case RESOURCE_SUBVOLUME:
941 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
942
943 /* directory/subvolume → directory/subvolume */
944
945 r = run_helper("(sd-import-fs)",
946 import_fs_binary_path(),
947 (const char* const[]) {
948 "systemd-import-fs",
949 "run",
950 "--direct", /* just untar the specified file, don't do anything else */
951 arg_sync ? "--sync=yes" : "--sync=no",
952 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
953 i->path,
954 t->temporary_path,
955 NULL
956 });
957 break;
958
959 case RESOURCE_TAR:
960 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
961
962 /* tar → directory/subvolume */
963
964 r = run_helper("(sd-import-tar)",
965 import_binary_path(),
966 (const char* const[]) {
967 "systemd-import",
968 "tar",
969 "--direct", /* just untar the specified file, don't do anything else */
970 arg_sync ? "--sync=yes" : "--sync=no",
971 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
972 i->path,
973 t->temporary_path,
974 NULL
975 });
976 break;
977
978 case RESOURCE_URL_FILE:
979
980 switch (t->target.type) {
981
982 case RESOURCE_REGULAR_FILE:
983
984 /* url file → regular file */
985
986 r = run_helper("(sd-pull-raw)",
987 pull_binary_path(),
988 (const char* const[]) {
989 "systemd-pull",
990 "raw",
991 "--direct", /* just download the specified URL, don't download anything else */
992 "--verify", digest, /* validate by explicit SHA256 sum */
993 arg_sync ? "--sync=yes" : "--sync=no",
994 i->path,
995 t->temporary_path,
996 NULL
997 });
998 break;
999
1000 case RESOURCE_PARTITION:
1001
1002 /* url file → partition */
1003
1004 r = run_helper("(sd-pull-raw)",
1005 pull_binary_path(),
1006 (const char* const[]) {
1007 "systemd-pull",
1008 "raw",
1009 "--direct", /* just download the specified URL, don't download anything else */
1010 "--verify", digest, /* validate by explicit SHA256 sum */
1011 "--offset", offset,
1012 "--size-max", max_size,
1013 arg_sync ? "--sync=yes" : "--sync=no",
1014 i->path,
1015 t->target.path,
1016 NULL
1017 });
1018 break;
1019
1020 default:
1021 assert_not_reached();
1022 }
1023
1024 break;
1025
1026 case RESOURCE_URL_TAR:
1027 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
1028
1029 r = run_helper("(sd-pull-tar)",
1030 pull_binary_path(),
1031 (const char*const[]) {
1032 "systemd-pull",
1033 "tar",
1034 "--direct", /* just download the specified URL, don't download anything else */
1035 "--verify", digest, /* validate by explicit SHA256 sum */
1036 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1037 arg_sync ? "--sync=yes" : "--sync=no",
1038 i->path,
1039 t->temporary_path,
1040 NULL
1041 });
1042 break;
1043
1044 default:
1045 assert_not_reached();
1046 }
1047 if (r < 0)
1048 return r;
1049
1050 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1051 bool need_sync = false;
1052 assert(t->temporary_path);
1053
1054 /* Apply file attributes if set */
1055 if (f.mtime != USEC_INFINITY) {
1056 struct timespec ts;
1057
1058 timespec_store(&ts, f.mtime);
1059
1060 if (utimensat(AT_FDCWD, t->temporary_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
1061 return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_path);
1062
1063 need_sync = true;
1064 }
1065
1066 if (f.mode != MODE_INVALID) {
1067 /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1068 * kernels don't support that however, in that case we fall back to chmod(). Not as
1069 * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1070 if (fchmodat(AT_FDCWD, t->temporary_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
1071 (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_path, f.mode) < 0))
1072 return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_path);
1073
1074 need_sync = true;
1075 }
1076
1077 /* Synchronize */
1078 if (arg_sync && need_sync) {
1079 if (t->target.type == RESOURCE_REGULAR_FILE)
1080 r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
1081 else {
1082 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
1083 r = syncfs_path(AT_FDCWD, t->temporary_path);
1084 }
1085 if (r < 0)
1086 return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
1087 }
1088
1089 t->install_read_only = f.read_only;
1090 }
1091
1092 if (t->target.type == RESOURCE_PARTITION) {
1093 free_and_replace(t->partition_info.label, formatted_pattern);
1094 t->partition_change = PARTITION_LABEL;
1095
1096 if (f.partition_uuid_set) {
1097 t->partition_info.uuid = f.partition_uuid;
1098 t->partition_change |= PARTITION_UUID;
1099 }
1100
1101 if (f.partition_flags_set) {
1102 t->partition_info.flags = f.partition_flags;
1103 t->partition_change |= PARTITION_FLAGS;
1104 }
1105
1106 if (f.no_auto >= 0) {
1107 t->partition_info.no_auto = f.no_auto;
1108 t->partition_change |= PARTITION_NO_AUTO;
1109 }
1110
1111 if (f.read_only >= 0) {
1112 t->partition_info.read_only = f.read_only;
1113 t->partition_change |= PARTITION_READ_ONLY;
1114 }
1115
1116 if (f.growfs >= 0) {
1117 t->partition_info.growfs = f.growfs;
1118 t->partition_change |= PARTITION_GROWFS;
1119 }
1120 }
1121
1122 /* For regular file cases the only step left is to install the file in place, which install_file()
1123 * will do via rename(). For partition cases the only step left is to update the partition table,
1124 * which is done at the same place. */
1125
1126 log_info("Successfully acquired '%s'.", i->path);
1127 return 0;
1128 }
1129
1130 int transfer_install_instance(
1131 Transfer *t,
1132 Instance *i,
1133 const char *root) {
1134
1135 int r;
1136
1137 assert(t);
1138 assert(i);
1139 assert(i->resource);
1140 assert(t == container_of(i->resource, Transfer, source));
1141
1142 if (t->temporary_path) {
1143 assert(RESOURCE_IS_FILESYSTEM(t->target.type));
1144 assert(t->final_path);
1145
1146 r = install_file(AT_FDCWD, t->temporary_path,
1147 AT_FDCWD, t->final_path,
1148 INSTALL_REPLACE|
1149 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1150 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
1151 if (r < 0)
1152 return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
1153
1154 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1155 i->path,
1156 resource_type_to_string(i->resource->type),
1157 t->final_path,
1158 resource_type_to_string(t->target.type));
1159
1160 t->temporary_path = mfree(t->temporary_path);
1161 }
1162
1163 if (t->partition_change != 0) {
1164 assert(t->target.type == RESOURCE_PARTITION);
1165
1166 r = patch_partition(
1167 t->target.path,
1168 &t->partition_info,
1169 t->partition_change);
1170 if (r < 0)
1171 return r;
1172
1173 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1174 i->path,
1175 resource_type_to_string(i->resource->type),
1176 t->partition_info.device,
1177 resource_type_to_string(t->target.type));
1178 }
1179
1180 if (t->current_symlink) {
1181 _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
1182 const char *link_path, *link_target;
1183 bool resolve_link_path = false;
1184
1185 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1186
1187 assert(t->target.path);
1188
1189 if (path_is_absolute(t->current_symlink)) {
1190 link_path = t->current_symlink;
1191 resolve_link_path = true;
1192 } else {
1193 buf = path_make_absolute(t->current_symlink, t->target.path);
1194 if (!buf)
1195 return log_oom();
1196
1197 link_path = buf;
1198 }
1199
1200 link_target = t->final_path;
1201
1202 } else if (t->target.type == RESOURCE_PARTITION) {
1203
1204 assert(path_is_absolute(t->current_symlink));
1205
1206 link_path = t->current_symlink;
1207 link_target = t->partition_info.device;
1208
1209 resolve_link_path = true;
1210 } else
1211 assert_not_reached();
1212
1213 if (resolve_link_path && root) {
1214 r = chase_symlinks(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
1215 if (r < 0)
1216 return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
1217
1218 link_path = resolved;
1219 }
1220
1221 if (link_target) {
1222 r = path_extract_directory(link_path, &parent);
1223 if (r < 0)
1224 return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
1225
1226 r = path_make_relative(parent, link_target, &relative);
1227 if (r < 0)
1228 return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
1229
1230 r = symlink_atomic(relative, link_path);
1231 if (r < 0)
1232 return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m",
1233 link_path,
1234 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
1235 relative);
1236
1237 log_info("Updated symlink '%s' %s '%s'.",
1238 link_path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), relative);
1239 }
1240 }
1241
1242 return 0;
1243 }