1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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"
11 #include "glyph-util.h"
13 #include "hexdecoct.h"
14 #include "install-file.h"
15 #include "parse-util.h"
16 #include "path-util.h"
17 #include "process-util.h"
19 #include "specifier.h"
20 #include "stat-util.h"
21 #include "stdio-util.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"
32 /* Default value for InstancesMax= for fs object targets */
33 #define DEFAULT_FILE_INSTANCES_MAX 3
35 Transfer
*transfer_free(Transfer
*t
) {
39 t
->temporary_path
= rm_rf_subvolume_and_free(t
->temporary_path
);
41 free(t
->definition_path
);
43 strv_free(t
->protected_versions
);
44 free(t
->current_symlink
);
47 partition_info_destroy(&t
->partition_info
);
49 resource_destroy(&t
->source
);
50 resource_destroy(&t
->target
);
55 Transfer
*transfer_new(void) {
63 .source
.type
= _RESOURCE_TYPE_INVALID
,
64 .target
.type
= _RESOURCE_TYPE_INVALID
,
65 .remove_temporary
= true,
67 .tries_left
= UINT64_MAX
,
68 .tries_done
= UINT64_MAX
,
71 /* the three flags, as configured by the user */
76 /* the read only flag, as ultimately determined */
77 .install_read_only
= -1,
79 .partition_info
= PARTITION_INFO_NULL
,
85 static const Specifier specifier_table
[] = {
86 COMMON_SYSTEM_SPECIFIERS
,
87 COMMON_TMP_SPECIFIERS
,
91 static int config_parse_protect_version(
96 unsigned section_line
,
103 _cleanup_free_
char *resolved
= NULL
;
104 char ***protected_versions
= data
;
110 r
= specifier_printf(rvalue
, NAME_MAX
, specifier_table
, arg_root
, NULL
, &resolved
);
112 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
113 "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue
);
117 if (!version_is_valid(resolved
)) {
118 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
119 "ProtectVersion= string is not valid, ignoring: %s", resolved
);
123 r
= strv_extend(protected_versions
, resolved
);
130 static int config_parse_min_version(
132 const char *filename
,
135 unsigned section_line
,
142 _cleanup_free_
char *resolved
= NULL
;
143 char **version
= data
;
149 r
= specifier_printf(rvalue
, NAME_MAX
, specifier_table
, arg_root
, NULL
, &resolved
);
151 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
152 "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue
);
156 if (!version_is_valid(rvalue
)) {
157 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
158 "MinVersion= string is not valid, ignoring: %s", resolved
);
162 return free_and_replace(*version
, resolved
);
165 static int config_parse_current_symlink(
167 const char *filename
,
170 unsigned section_line
,
177 _cleanup_free_
char *resolved
= NULL
;
178 char **current_symlink
= data
;
184 r
= specifier_printf(rvalue
, NAME_MAX
, specifier_table
, arg_root
, NULL
, &resolved
);
186 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
187 "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue
);
191 r
= path_simplify_and_warn(resolved
, 0, unit
, filename
, line
, lvalue
);
195 return free_and_replace(*current_symlink
, resolved
);
198 static int config_parse_instances_max(
200 const char *filename
,
203 unsigned section_line
,
210 uint64_t *instances_max
= data
, i
;
216 if (isempty(rvalue
)) {
217 *instances_max
= 0; /* Revert to default logic, see transfer_read_definition() */
221 r
= safe_atou64(rvalue
, &i
);
223 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
224 "Failed to parse InstancesMax= value, ignoring: %s", rvalue
);
229 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
230 "InstancesMax= value must be at least 2, bumping: %s", rvalue
);
238 static int config_parse_resource_pattern(
240 const char *filename
,
243 unsigned section_line
,
250 char ***patterns
= data
;
256 if (isempty(rvalue
)) {
257 *patterns
= strv_free(*patterns
);
262 _cleanup_free_
char *word
= NULL
, *resolved
= NULL
;
264 r
= extract_first_word(&rvalue
, &word
, NULL
, EXTRACT_CUNESCAPE
|EXTRACT_UNESCAPE_RELAX
);
266 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
267 "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue
);
273 r
= specifier_printf(word
, NAME_MAX
, specifier_table
, arg_root
, NULL
, &resolved
);
275 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
276 "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue
);
280 if (!pattern_valid(resolved
))
281 return log_syntax(unit
, LOG_ERR
, filename
, line
, SYNTHETIC_ERRNO(EINVAL
),
282 "MatchPattern= string is not valid, refusing: %s", resolved
);
284 r
= strv_consume(patterns
, TAKE_PTR(resolved
));
289 strv_uniq(*patterns
);
293 static int config_parse_resource_path(
295 const char *filename
,
298 unsigned section_line
,
305 _cleanup_free_
char *resolved
= NULL
;
312 if (streq(rvalue
, "auto")) {
313 rr
->path_auto
= true;
314 rr
->path
= mfree(rr
->path
);
318 r
= specifier_printf(rvalue
, PATH_MAX
-1, specifier_table
, arg_root
, NULL
, &resolved
);
320 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
321 "Failed to expand specifiers in Path=, ignoring: %s", rvalue
);
325 /* Note that we don't validate the path as being absolute or normalized. We'll do that in
326 * transfer_read_definition() as we might not know yet whether Path refers to an URL or a file system
329 rr
->path_auto
= false;
330 return free_and_replace(rr
->path
, resolved
);
333 static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type
, resource_type
, ResourceType
, "Invalid resource type");
335 static int config_parse_resource_ptype(
337 const char *filename
,
340 unsigned section_line
,
353 r
= gpt_partition_type_uuid_from_string(rvalue
, &rr
->partition_type
);
355 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
356 "Failed parse partition type, ignoring: %s", rvalue
);
360 rr
->partition_type_set
= true;
364 static int config_parse_partition_uuid(
366 const char *filename
,
369 unsigned section_line
,
382 r
= sd_id128_from_string(rvalue
, &t
->partition_uuid
);
384 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
385 "Failed parse partition UUID, ignoring: %s", rvalue
);
389 t
->partition_uuid_set
= true;
393 static int config_parse_partition_flags(
395 const char *filename
,
398 unsigned section_line
,
411 r
= safe_atou64(rvalue
, &t
->partition_flags
);
413 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
414 "Failed parse partition flags, ignoring: %s", rvalue
);
418 t
->partition_flags_set
= true;
422 int transfer_read_definition(Transfer
*t
, const char *path
) {
428 ConfigTableItem table
[] = {
429 { "Transfer", "MinVersion", config_parse_min_version
, 0, &t
->min_version
},
430 { "Transfer", "ProtectVersion", config_parse_protect_version
, 0, &t
->protected_versions
},
431 { "Transfer", "Verify", config_parse_bool
, 0, &t
->verify
},
432 { "Source", "Type", config_parse_resource_type
, 0, &t
->source
.type
},
433 { "Source", "Path", config_parse_resource_path
, 0, &t
->source
},
434 { "Source", "MatchPattern", config_parse_resource_pattern
, 0, &t
->source
.patterns
},
435 { "Target", "Type", config_parse_resource_type
, 0, &t
->target
.type
},
436 { "Target", "Path", config_parse_resource_path
, 0, &t
->target
},
437 { "Target", "MatchPattern", config_parse_resource_pattern
, 0, &t
->target
.patterns
},
438 { "Target", "MatchPartitionType", config_parse_resource_ptype
, 0, &t
->target
},
439 { "Target", "PartitionUUID", config_parse_partition_uuid
, 0, t
},
440 { "Target", "PartitionFlags", config_parse_partition_flags
, 0, t
},
441 { "Target", "PartitionNoAuto", config_parse_tristate
, 0, &t
->no_auto
},
442 { "Target", "PartitionGrowFileSystem", config_parse_tristate
, 0, &t
->growfs
},
443 { "Target", "ReadOnly", config_parse_tristate
, 0, &t
->read_only
},
444 { "Target", "Mode", config_parse_mode
, 0, &t
->mode
},
445 { "Target", "TriesLeft", config_parse_uint64
, 0, &t
->tries_left
},
446 { "Target", "TriesDone", config_parse_uint64
, 0, &t
->tries_done
},
447 { "Target", "InstancesMax", config_parse_instances_max
, 0, &t
->instances_max
},
448 { "Target", "RemoveTemporary", config_parse_bool
, 0, &t
->remove_temporary
},
449 { "Target", "CurrentSymlink", config_parse_current_symlink
, 0, &t
->current_symlink
},
453 r
= config_parse(NULL
, path
, NULL
,
457 config_item_table_lookup
, table
,
464 if (!RESOURCE_IS_SOURCE(t
->source
.type
))
465 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
466 "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
468 if (t
->target
.type
< 0) {
469 switch (t
->source
.type
) {
471 case RESOURCE_URL_FILE
:
472 case RESOURCE_REGULAR_FILE
:
474 t
->target
.path
&& path_startswith(t
->target
.path
, "/dev/") ?
475 RESOURCE_PARTITION
: RESOURCE_REGULAR_FILE
;
478 case RESOURCE_URL_TAR
:
480 case RESOURCE_DIRECTORY
:
481 t
->target
.type
= RESOURCE_DIRECTORY
;
484 case RESOURCE_SUBVOLUME
:
485 t
->target
.type
= RESOURCE_SUBVOLUME
;
489 assert_not_reached();
493 if (!RESOURCE_IS_TARGET(t
->target
.type
))
494 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
495 "Target Type= must be one of partition, regular-file, directory, subvolume.");
497 if ((IN_SET(t
->source
.type
, RESOURCE_URL_FILE
, RESOURCE_PARTITION
, RESOURCE_REGULAR_FILE
) &&
498 !IN_SET(t
->target
.type
, RESOURCE_PARTITION
, RESOURCE_REGULAR_FILE
)) ||
499 (IN_SET(t
->source
.type
, RESOURCE_URL_TAR
, RESOURCE_TAR
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
) &&
500 !IN_SET(t
->target
.type
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
)))
501 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
502 "Target type '%s' is incompatible with source type '%s', refusing.",
503 resource_type_to_string(t
->source
.type
), resource_type_to_string(t
->target
.type
));
505 if (!t
->source
.path
&& !t
->source
.path_auto
)
506 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
507 "Source specification lacks Path=.");
509 if (t
->source
.path
) {
510 if (RESOURCE_IS_FILESYSTEM(t
->source
.type
) || t
->source
.type
== RESOURCE_PARTITION
)
511 if (!path_is_absolute(t
->source
.path
) || !path_is_normalized(t
->source
.path
))
512 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
513 "Source path is not a normalized, absolute path: %s", t
->source
.path
);
515 /* We unofficially support file:// in addition to http:// and https:// for url
516 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
517 * server, and CURL abstracts this away from us thankfully. */
518 if (RESOURCE_IS_URL(t
->source
.type
))
519 if (!http_url_is_valid(t
->source
.path
) && !file_url_is_valid(t
->source
.path
))
520 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
521 "Source path is not a valid HTTP or HTTPS URL: %s", t
->source
.path
);
524 if (strv_isempty(t
->source
.patterns
))
525 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
526 "Source specification lacks MatchPattern=.");
528 if (!t
->target
.path
&& !t
->target
.path_auto
)
529 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
530 "Target specification lacks Path= field.");
532 if (t
->target
.path
&&
533 (!path_is_absolute(t
->target
.path
) || !path_is_normalized(t
->target
.path
)))
534 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
535 "Target path is not a normalized, absolute path: %s", t
->target
.path
);
537 if (strv_isempty(t
->target
.patterns
)) {
538 strv_free(t
->target
.patterns
);
539 t
->target
.patterns
= strv_copy(t
->source
.patterns
);
540 if (!t
->target
.patterns
)
544 if (t
->current_symlink
&& !RESOURCE_IS_FILESYSTEM(t
->target
.type
) && !path_is_absolute(t
->current_symlink
))
545 return log_syntax(NULL
, LOG_ERR
, path
, 1, SYNTHETIC_ERRNO(EINVAL
),
546 "Current symlink must be absolute path if target is partition: %s", t
->current_symlink
);
548 /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
549 if (t
->instances_max
== 0)
550 t
->instances_max
= t
->target
.type
== RESOURCE_PARTITION
? UINT64_MAX
: DEFAULT_FILE_INSTANCES_MAX
;
555 int transfer_resolve_paths(
562 /* If Path=auto is used in [Source] or [Target] sections, let's automatically detect the path of the
563 * block device to use. Moreover, if this path points to a directory but we need a block device,
564 * automatically determine the backing block device, so that users can reference block devices by
569 r
= resource_resolve_path(&t
->source
, root
, node
);
573 r
= resource_resolve_path(&t
->target
, root
, node
);
580 static void transfer_remove_temporary(Transfer
*t
) {
581 _cleanup_(closedirp
) DIR *d
= NULL
;
586 if (!t
->remove_temporary
)
589 if (!IN_SET(t
->target
.type
, RESOURCE_REGULAR_FILE
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
))
592 /* Removes all temporary files/dirs from previous runs in the target directory, i.e. all those starting with '.#' */
594 d
= opendir(t
->target
.path
);
599 log_debug_errno(errno
, "Failed to open target directory '%s', ignoring: %m", t
->target
.path
);
607 de
= readdir_no_dot(d
);
610 log_debug_errno(errno
, "Failed to read target directory '%s', ignoring: %m", t
->target
.path
);
614 if (!startswith(de
->d_name
, ".#"))
617 r
= rm_rf_child(dirfd(d
), de
->d_name
, REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
|REMOVE_CHMOD
);
621 log_warning_errno(r
, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t
->target
.path
, de
->d_name
);
625 log_debug("Removed temporary resource instance '%s/%s'.", t
->target
.path
, de
->d_name
);
632 const char *extra_protected_version
) {
634 uint64_t instances_max
, limit
;
639 transfer_remove_temporary(t
);
641 /* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
643 instances_max
= arg_instances_max
!= UINT64_MAX
? arg_instances_max
: t
->instances_max
;
644 assert(instances_max
>= 1);
645 if (instances_max
== UINT64_MAX
) /* Keep infinite instances? */
647 else if (space
> instances_max
)
648 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC
),
649 "Asked to delete more instances than total maximum allowed number of instances, refusing.");
650 else if (space
== instances_max
)
651 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC
),
652 "Asked to delete all possible instances, can't allow that. One instance must always remain.");
654 limit
= instances_max
- space
;
656 if (t
->target
.type
== RESOURCE_PARTITION
) {
659 /* If we are looking at a partition table, we also have to take into account how many
660 * partition slots of the right type are available */
662 if (t
->target
.n_empty
+ t
->target
.n_instances
< 2)
663 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC
),
664 "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR
" (%s), refusing.",
665 SD_ID128_FORMAT_VAL(t
->target
.partition_type
),
666 gpt_partition_type_uuid_to_string(t
->target
.partition_type
));
667 if (space
> t
->target
.n_empty
+ t
->target
.n_instances
)
668 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC
),
669 "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR
" (%s) for operation.",
670 SD_ID128_FORMAT_VAL(t
->target
.partition_type
),
671 gpt_partition_type_uuid_to_string(t
->target
.partition_type
));
672 if (space
== t
->target
.n_empty
+ t
->target
.n_instances
)
673 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC
),
674 "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.",
675 SD_ID128_FORMAT_VAL(t
->target
.partition_type
),
676 gpt_partition_type_uuid_to_string(t
->target
.partition_type
));
678 rm
= LESS_BY(space
, t
->target
.n_empty
);
679 remain
= LESS_BY(t
->target
.n_instances
, rm
);
680 limit
= MIN(limit
, remain
);
683 while (t
->target
.n_instances
> limit
) {
685 size_t p
= t
->target
.n_instances
- 1;
688 oldest
= t
->target
.instances
[p
];
691 /* If this is listed among the protected versions, then let's not remove it */
692 if (!strv_contains(t
->protected_versions
, oldest
->metadata
.version
) &&
693 (!extra_protected_version
|| !streq(extra_protected_version
, oldest
->metadata
.version
)))
696 log_debug("Version '%s' is protected, not removing.", oldest
->metadata
.version
);
705 if (!oldest
) /* Nothing more to remove */
708 assert(oldest
->resource
);
710 log_info("%s Removing old '%s' (%s).", special_glyph(SPECIAL_GLYPH_RECYCLING
), oldest
->path
, resource_type_to_string(oldest
->resource
->type
));
712 switch (t
->target
.type
) {
714 case RESOURCE_REGULAR_FILE
:
715 case RESOURCE_DIRECTORY
:
716 case RESOURCE_SUBVOLUME
:
717 r
= rm_rf(oldest
->path
, REMOVE_ROOT
|REMOVE_PHYSICAL
|REMOVE_SUBVOLUME
|REMOVE_MISSING_OK
|REMOVE_CHMOD
);
718 if (r
< 0 && r
!= -ENOENT
)
719 return log_error_errno(r
, "Failed to make room, deleting '%s' failed: %m", oldest
->path
);
723 case RESOURCE_PARTITION
: {
724 PartitionInfo pinfo
= oldest
->partition_info
;
726 /* label "_empty" means "no contents" for our purposes */
727 pinfo
.label
= (char*) "_empty";
729 r
= patch_partition(t
->target
.path
, &pinfo
, PARTITION_LABEL
);
738 assert_not_reached();
742 instance_free(oldest
);
743 memmove(t
->target
.instances
+ p
, t
->target
.instances
+ p
+ 1, (t
->target
.n_instances
- p
- 1) * sizeof(Instance
*));
744 t
->target
.n_instances
--;
752 static void compile_pattern_fields(
755 InstanceMetadata
*ret
) {
761 *ret
= (InstanceMetadata
) {
762 .version
= i
->metadata
.version
,
764 /* We generally prefer explicitly configured values for the transfer over those automatically
765 * derived from the source instance. Also, if the source is a tar archive, then let's not
766 * patch mtime/mode and use the one embedded in the tar file */
767 .partition_uuid
= t
->partition_uuid_set
? t
->partition_uuid
: i
->metadata
.partition_uuid
,
768 .partition_uuid_set
= t
->partition_uuid_set
|| i
->metadata
.partition_uuid_set
,
769 .partition_flags
= t
->partition_flags_set
? t
->partition_flags
: i
->metadata
.partition_flags
,
770 .partition_flags_set
= t
->partition_flags_set
|| i
->metadata
.partition_flags_set
,
771 .mtime
= RESOURCE_IS_TAR(i
->resource
->type
) ? USEC_INFINITY
: i
->metadata
.mtime
,
772 .mode
= t
->mode
!= MODE_INVALID
? t
->mode
: (RESOURCE_IS_TAR(i
->resource
->type
) ? MODE_INVALID
: i
->metadata
.mode
),
773 .size
= i
->metadata
.size
,
774 .tries_done
= t
->tries_done
!= UINT64_MAX
? t
->tries_done
:
775 i
->metadata
.tries_done
!= UINT64_MAX
? i
->metadata
.tries_done
: 0,
776 .tries_left
= t
->tries_left
!= UINT64_MAX
? t
->tries_left
:
777 i
->metadata
.tries_left
!= UINT64_MAX
? i
->metadata
.tries_left
: 3,
778 .no_auto
= t
->no_auto
>= 0 ? t
->no_auto
: i
->metadata
.no_auto
,
779 .read_only
= t
->read_only
>= 0 ? t
->read_only
: i
->metadata
.read_only
,
780 .growfs
= t
->growfs
>= 0 ? t
->growfs
: i
->metadata
.growfs
,
781 .sha256sum_set
= i
->metadata
.sha256sum_set
,
784 memcpy(ret
->sha256sum
, i
->metadata
.sha256sum
, sizeof(ret
->sha256sum
));
787 static int run_helper(
790 const char * const cmdline
[]) {
798 r
= safe_fork(name
, FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_LOG
|FORK_WAIT
, NULL
);
804 (void) unsetenv("NOTIFY_SOCKET");
805 execv(path
, (char *const*) cmdline
);
806 log_error_errno(errno
, "Failed to execute %s tool: %m", path
);
813 int transfer_acquire_instance(Transfer
*t
, Instance
*i
) {
814 _cleanup_free_
char *formatted_pattern
= NULL
, *digest
= NULL
;
815 char offset
[DECIMAL_STR_MAX(uint64_t)+1], max_size
[DECIMAL_STR_MAX(uint64_t)+1];
816 const char *where
= NULL
;
824 assert(t
== container_of(i
->resource
, Transfer
, source
));
826 /* Does this instance already exist in the target? Then we don't need to acquire anything */
827 existing
= resource_find_instance(&t
->target
, i
->metadata
.version
);
829 log_info("No need to acquire '%s', already installed.", i
->path
);
833 assert(!t
->final_path
);
834 assert(!t
->temporary_path
);
835 assert(!strv_isempty(t
->target
.patterns
));
837 /* Format the target name using the first pattern specified */
838 compile_pattern_fields(t
, i
, &f
);
839 r
= pattern_format(t
->target
.patterns
[0], &f
, &formatted_pattern
);
841 return log_error_errno(r
, "Failed to format target pattern: %m");
843 if (RESOURCE_IS_FILESYSTEM(t
->target
.type
)) {
845 if (!filename_is_valid(formatted_pattern
))
846 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern
);
848 t
->final_path
= path_join(t
->target
.path
, formatted_pattern
);
852 r
= tempfn_random(t
->final_path
, "sysupdate", &t
->temporary_path
);
854 return log_error_errno(r
, "Failed to generate temporary target path: %m");
856 where
= t
->final_path
;
859 if (t
->target
.type
== RESOURCE_PARTITION
) {
860 r
= gpt_partition_label_valid(formatted_pattern
);
862 return log_error_errno(r
, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern
);
864 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern
);
866 r
= find_suitable_partition(
869 t
->target
.partition_type_set
? &t
->target
.partition_type
: NULL
,
874 xsprintf(offset
, "%" PRIu64
, t
->partition_info
.start
);
875 xsprintf(max_size
, "%" PRIu64
, t
->partition_info
.size
);
877 where
= t
->partition_info
.device
;
882 log_info("%s Acquiring %s %s %s...", special_glyph(SPECIAL_GLYPH_DOWNLOAD
), i
->path
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), where
);
884 if (RESOURCE_IS_URL(i
->resource
->type
)) {
885 /* For URL sources we require the SHA256 sum to be known so that we can validate the
888 if (!i
->metadata
.sha256sum_set
)
889 return log_error_errno(r
, "SHA256 checksum not known for download '%s', refusing.", i
->path
);
891 digest
= hexmem(i
->metadata
.sha256sum
, sizeof(i
->metadata
.sha256sum
));
896 switch (i
->resource
->type
) { /* Source */
898 case RESOURCE_REGULAR_FILE
:
900 switch (t
->target
.type
) { /* Target */
902 case RESOURCE_REGULAR_FILE
:
904 /* regular file → regular file (why fork off systemd-import for such a simple file
905 * copy case? implicit decompression mostly, and thus also sandboxing. Also, the
906 * importer has some tricks up its sleeve, such as sparse file generation, which we
907 * want to take benefit of, too.) */
909 r
= run_helper("(sd-import-raw)",
910 import_binary_path(),
911 (const char* const[]) {
914 "--direct", /* just copy/unpack the specified file, don't do anything else */
915 arg_sync
? "--sync=yes" : "--sync=no",
922 case RESOURCE_PARTITION
:
924 /* regular file → partition */
926 r
= run_helper("(sd-import-raw)",
927 import_binary_path(),
928 (const char* const[]) {
931 "--direct", /* just copy/unpack the specified file, don't do anything else */
933 "--size-max", max_size
,
934 arg_sync
? "--sync=yes" : "--sync=no",
942 assert_not_reached();
947 case RESOURCE_DIRECTORY
:
948 case RESOURCE_SUBVOLUME
:
949 assert(IN_SET(t
->target
.type
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
951 /* directory/subvolume → directory/subvolume */
953 r
= run_helper("(sd-import-fs)",
954 import_fs_binary_path(),
955 (const char* const[]) {
958 "--direct", /* just untar the specified file, don't do anything else */
959 arg_sync
? "--sync=yes" : "--sync=no",
960 t
->target
.type
== RESOURCE_SUBVOLUME
? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
968 assert(IN_SET(t
->target
.type
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
970 /* tar → directory/subvolume */
972 r
= run_helper("(sd-import-tar)",
973 import_binary_path(),
974 (const char* const[]) {
977 "--direct", /* just untar the specified file, don't do anything else */
978 arg_sync
? "--sync=yes" : "--sync=no",
979 t
->target
.type
== RESOURCE_SUBVOLUME
? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
986 case RESOURCE_URL_FILE
:
988 switch (t
->target
.type
) {
990 case RESOURCE_REGULAR_FILE
:
992 /* url file → regular file */
994 r
= run_helper("(sd-pull-raw)",
996 (const char* const[]) {
999 "--direct", /* just download the specified URL, don't download anything else */
1000 "--verify", digest
, /* validate by explicit SHA256 sum */
1001 arg_sync
? "--sync=yes" : "--sync=no",
1008 case RESOURCE_PARTITION
:
1010 /* url file → partition */
1012 r
= run_helper("(sd-pull-raw)",
1014 (const char* const[]) {
1017 "--direct", /* just download the specified URL, don't download anything else */
1018 "--verify", digest
, /* validate by explicit SHA256 sum */
1020 "--size-max", max_size
,
1021 arg_sync
? "--sync=yes" : "--sync=no",
1029 assert_not_reached();
1034 case RESOURCE_URL_TAR
:
1035 assert(IN_SET(t
->target
.type
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
1037 r
= run_helper("(sd-pull-tar)",
1039 (const char*const[]) {
1042 "--direct", /* just download the specified URL, don't download anything else */
1043 "--verify", digest
, /* validate by explicit SHA256 sum */
1044 t
->target
.type
== RESOURCE_SUBVOLUME
? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1045 arg_sync
? "--sync=yes" : "--sync=no",
1053 assert_not_reached();
1058 if (RESOURCE_IS_FILESYSTEM(t
->target
.type
)) {
1059 bool need_sync
= false;
1060 assert(t
->temporary_path
);
1062 /* Apply file attributes if set */
1063 if (f
.mtime
!= USEC_INFINITY
) {
1066 timespec_store(&ts
, f
.mtime
);
1068 if (utimensat(AT_FDCWD
, t
->temporary_path
, (struct timespec
[2]) { ts
, ts
}, AT_SYMLINK_NOFOLLOW
) < 0)
1069 return log_error_errno(errno
, "Failed to adjust mtime of '%s': %m", t
->temporary_path
);
1074 if (f
.mode
!= MODE_INVALID
) {
1075 /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1076 * kernels don't support that however, in that case we fall back to chmod(). Not as
1077 * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1078 if (fchmodat(AT_FDCWD
, t
->temporary_path
, f
.mode
, AT_SYMLINK_NOFOLLOW
) < 0 &&
1079 (!ERRNO_IS_NOT_SUPPORTED(errno
) || chmod(t
->temporary_path
, f
.mode
) < 0))
1080 return log_error_errno(errno
, "Failed to adjust mode of '%s': %m", t
->temporary_path
);
1086 if (arg_sync
&& need_sync
) {
1087 if (t
->target
.type
== RESOURCE_REGULAR_FILE
)
1088 r
= fsync_path_and_parent_at(AT_FDCWD
, t
->temporary_path
);
1090 assert(IN_SET(t
->target
.type
, RESOURCE_DIRECTORY
, RESOURCE_SUBVOLUME
));
1091 r
= syncfs_path(AT_FDCWD
, t
->temporary_path
);
1094 return log_error_errno(r
, "Failed to synchronize file system backing '%s': %m", t
->temporary_path
);
1097 t
->install_read_only
= f
.read_only
;
1100 if (t
->target
.type
== RESOURCE_PARTITION
) {
1101 free_and_replace(t
->partition_info
.label
, formatted_pattern
);
1102 t
->partition_change
= PARTITION_LABEL
;
1104 if (f
.partition_uuid_set
) {
1105 t
->partition_info
.uuid
= f
.partition_uuid
;
1106 t
->partition_change
|= PARTITION_UUID
;
1109 if (f
.partition_flags_set
) {
1110 t
->partition_info
.flags
= f
.partition_flags
;
1111 t
->partition_change
|= PARTITION_FLAGS
;
1114 if (f
.no_auto
>= 0) {
1115 t
->partition_info
.no_auto
= f
.no_auto
;
1116 t
->partition_change
|= PARTITION_NO_AUTO
;
1119 if (f
.read_only
>= 0) {
1120 t
->partition_info
.read_only
= f
.read_only
;
1121 t
->partition_change
|= PARTITION_READ_ONLY
;
1124 if (f
.growfs
>= 0) {
1125 t
->partition_info
.growfs
= f
.growfs
;
1126 t
->partition_change
|= PARTITION_GROWFS
;
1130 /* For regular file cases the only step left is to install the file in place, which install_file()
1131 * will do via rename(). For partition cases the only step left is to update the partition table,
1132 * which is done at the same place. */
1134 log_info("Successfully acquired '%s'.", i
->path
);
1138 int transfer_install_instance(
1147 assert(i
->resource
);
1148 assert(t
== container_of(i
->resource
, Transfer
, source
));
1150 if (t
->temporary_path
) {
1151 assert(RESOURCE_IS_FILESYSTEM(t
->target
.type
));
1152 assert(t
->final_path
);
1154 r
= install_file(AT_FDCWD
, t
->temporary_path
,
1155 AT_FDCWD
, t
->final_path
,
1157 (t
->install_read_only
> 0 ? INSTALL_READ_ONLY
: 0)|
1158 (t
->target
.type
== RESOURCE_REGULAR_FILE
? INSTALL_FSYNC_FULL
: INSTALL_SYNCFS
));
1160 return log_error_errno(r
, "Failed to move '%s' into place: %m", t
->final_path
);
1162 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1164 resource_type_to_string(i
->resource
->type
),
1166 resource_type_to_string(t
->target
.type
));
1168 t
->temporary_path
= mfree(t
->temporary_path
);
1171 if (t
->partition_change
!= 0) {
1172 assert(t
->target
.type
== RESOURCE_PARTITION
);
1174 r
= patch_partition(
1177 t
->partition_change
);
1181 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1183 resource_type_to_string(i
->resource
->type
),
1184 t
->partition_info
.device
,
1185 resource_type_to_string(t
->target
.type
));
1188 if (t
->current_symlink
) {
1189 _cleanup_free_
char *buf
= NULL
, *parent
= NULL
, *relative
= NULL
, *resolved
= NULL
;
1190 const char *link_path
, *link_target
;
1191 bool resolve_link_path
= false;
1193 if (RESOURCE_IS_FILESYSTEM(t
->target
.type
)) {
1195 assert(t
->target
.path
);
1197 if (path_is_absolute(t
->current_symlink
)) {
1198 link_path
= t
->current_symlink
;
1199 resolve_link_path
= true;
1201 buf
= path_make_absolute(t
->current_symlink
, t
->target
.path
);
1208 link_target
= t
->final_path
;
1210 } else if (t
->target
.type
== RESOURCE_PARTITION
) {
1212 assert(path_is_absolute(t
->current_symlink
));
1214 link_path
= t
->current_symlink
;
1215 link_target
= t
->partition_info
.device
;
1217 resolve_link_path
= true;
1219 assert_not_reached();
1221 if (resolve_link_path
&& root
) {
1222 r
= chase_symlinks(link_path
, root
, CHASE_PREFIX_ROOT
|CHASE_NONEXISTENT
, &resolved
, NULL
);
1224 return log_error_errno(r
, "Failed to resolve current symlink path '%s': %m", link_path
);
1226 link_path
= resolved
;
1230 r
= path_extract_directory(link_path
, &parent
);
1232 return log_error_errno(r
, "Failed to extract directory of target path '%s': %m", link_path
);
1234 r
= path_make_relative(parent
, link_target
, &relative
);
1236 return log_error_errno(r
, "Failed to make symlink path '%s' relative to '%s': %m", link_target
, parent
);
1238 r
= symlink_atomic(relative
, link_path
);
1240 return log_error_errno(r
, "Failed to update current symlink '%s' → '%s': %m", link_path
, relative
);
1242 log_info("Updated symlink '%s' → '%s'.", link_path
, relative
);