mirror of
https://github.com/systemd/systemd
synced 2026-03-13 00:24:48 +01:00
Compare commits
19 Commits
e4dcf0cbce
...
8ae4aa26c1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ae4aa26c1 | ||
|
|
6cf5581af8 | ||
|
|
b6defb8f84 | ||
|
|
eefb46c83b | ||
|
|
7081618cd9 | ||
|
|
51480b56c8 | ||
|
|
10268261a8 | ||
|
|
7f204857e1 | ||
|
|
6015c6cd6f | ||
|
|
e6e8efcffb | ||
|
|
3bc9208435 | ||
|
|
cf7cccf2f7 | ||
|
|
594d0345fa | ||
|
|
ba9687adef | ||
|
|
adcc03a0c0 | ||
|
|
48fff6a27a | ||
|
|
edf7679faf | ||
|
|
fdc661116d | ||
|
|
ec10efa1a1 |
6
TODO
6
TODO
@ -128,6 +128,12 @@ Features:
|
|||||||
- metrics from pid1: suppress metrics form units that are inactive and have nothing to report
|
- metrics from pid1: suppress metrics form units that are inactive and have nothing to report
|
||||||
- how to plug facts into this? i.e. hostname, ssh keys, and so on
|
- how to plug facts into this? i.e. hostname, ssh keys, and so on
|
||||||
- switch to daan's suggested hierarchy?
|
- switch to daan's suggested hierarchy?
|
||||||
|
- enforce naming rules: a backend can only report metrics with the prefix of its service name
|
||||||
|
- use that to optimize sorted report generation
|
||||||
|
- figure out report vs. metrics
|
||||||
|
- systemd-report: add prefix matching of metrics
|
||||||
|
- add "hint-suppress-zero" flag (which suppresses all metrics which are zero)
|
||||||
|
- add "hint-object" parameter (which only queries info about certain object)
|
||||||
|
|
||||||
* implement a varlink registry service, similar to the one of the reference
|
* implement a varlink registry service, similar to the one of the reference
|
||||||
implementation, backed by /run/varlink/registry/. Then, also implement
|
implementation, backed by /run/varlink/registry/. Then, also implement
|
||||||
|
|||||||
@ -292,7 +292,7 @@ node /org/freedesktop/resolve1 {
|
|||||||
(no IDNA conversion is applied), followed by the 16-bit class and type fields (which may be
|
(no IDNA conversion is applied), followed by the 16-bit class and type fields (which may be
|
||||||
ANY). Finally, a <varname>flags</varname> field may be passed in to alter behaviour of the look-up (see
|
ANY). Finally, a <varname>flags</varname> field may be passed in to alter behaviour of the look-up (see
|
||||||
below). On completion, an array of RR items is returned. Each array entry consists of the network interface
|
below). On completion, an array of RR items is returned. Each array entry consists of the network interface
|
||||||
index the RR was discovered on, the type and class field of the RR found, and a byte array of the raw
|
index the RR was discovered on, the class and type field of the RR found, and a byte array of the raw
|
||||||
RR discovered. The raw RR data starts with the RR's domain name, in the original casing, followed
|
RR discovered. The raw RR data starts with the RR's domain name, in the original casing, followed
|
||||||
by the RR type, class, TTL and RDATA, in the binary format documented in
|
by the RR type, class, TTL and RDATA, in the binary format documented in
|
||||||
<ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>. For RRs that support name
|
<ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>. For RRs that support name
|
||||||
|
|||||||
@ -128,19 +128,33 @@
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>update</option> <optional><replaceable>VERSION</replaceable></optional></term>
|
<term><option>update</option> <optional>--offline</optional> <optional><replaceable>VERSION</replaceable></optional></term>
|
||||||
|
|
||||||
<listitem><para>Installs (updates to) the specified version, or if none is specified to the newest
|
<listitem><para>Installs (updates to) the specified version, or if none is specified to the newest
|
||||||
version available. If the version is already installed or no newer version available, no operation is
|
version available. If the version is already installed or no newer version available, no operation is
|
||||||
executed.</para>
|
executed.</para>
|
||||||
|
|
||||||
|
<para>If <option>--offline</option> is specified, the update must already have been acquired using
|
||||||
|
<command>acquire</command> and, if so, this pre-acquired version is the one which will be updated
|
||||||
|
to.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>acquire</option> <optional><replaceable>VERSION</replaceable></optional></term>
|
||||||
|
|
||||||
|
<listitem><para>Acquires (downloads) the specified version, ready to install it. If no version is
|
||||||
|
specified, the newest version available is acquired. If the version is already installed or no newer
|
||||||
|
version is available, no operation is executed.</para>
|
||||||
|
|
||||||
<para>If a new version to install/update to is found, old installed versions are deleted until at
|
<para>If a new version to install/update to is found, old installed versions are deleted until at
|
||||||
least one new version can be installed, as configured via <varname>InstanceMax=</varname> in
|
least one new version can be installed, as configured via <varname>InstanceMax=</varname> in
|
||||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, or
|
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, or
|
||||||
via the available partition slots of the right type. This implicit operation can also be invoked
|
via the available partition slots of the right type. This implicit operation can also be invoked
|
||||||
explicitly via the <command>vacuum</command> command described below.</para>
|
explicitly via the <command>vacuum</command> command described below.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
@ -299,7 +313,7 @@
|
|||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--reboot</option></term>
|
<term><option>--reboot</option></term>
|
||||||
|
|
||||||
<listitem><para>When used in combination with the <command>update</command> command and a new version is
|
<listitem><para>When used in combination with the <command>update</command> commands and a new version is
|
||||||
installed, automatically reboots the system immediately afterwards.</para>
|
installed, automatically reboots the system immediately afterwards.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v251"/></listitem>
|
||||||
@ -312,6 +326,9 @@
|
|||||||
This is most useful when used in combination with the <command>list</command> command, to query
|
This is most useful when used in combination with the <command>list</command> command, to query
|
||||||
locally installed versions.</para>
|
locally installed versions.</para>
|
||||||
|
|
||||||
|
<para>If used in combination with the <command>update</command> command, it allows updates to be
|
||||||
|
downloaded in advance (using <command>acquire</command>) and installed later.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
|||||||
@ -2035,6 +2035,10 @@ int vl_method_install(
|
|||||||
if (p.context.root_fd < 0)
|
if (p.context.root_fd < 0)
|
||||||
return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
|
return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
|
||||||
|
|
||||||
|
r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY);
|
||||||
|
if (r < 0)
|
||||||
|
return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
|
||||||
|
|
||||||
r = fd_verify_directory(p.context.root_fd);
|
r = fd_verify_directory(p.context.root_fd);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
|
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
|
||||||
@ -2047,15 +2051,11 @@ int vl_method_install(
|
|||||||
if (empty_or_root(p.context.root))
|
if (empty_or_root(p.context.root))
|
||||||
p.context.root = mfree(p.context.root);
|
p.context.root = mfree(p.context.root);
|
||||||
}
|
}
|
||||||
}
|
} else if (p.context.root) {
|
||||||
|
|
||||||
if (p.context.root_fd < 0 && p.context.root) {
|
|
||||||
p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||||
if (p.context.root_fd < 0)
|
if (p.context.root_fd < 0)
|
||||||
return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
|
return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
|
||||||
}
|
} else
|
||||||
|
|
||||||
if (p.context.root_fd < 0)
|
|
||||||
p.context.root_fd = XAT_FDROOT;
|
p.context.root_fd = XAT_FDROOT;
|
||||||
|
|
||||||
if (p.context.entry_token_type < 0)
|
if (p.context.entry_token_type < 0)
|
||||||
|
|||||||
@ -422,6 +422,7 @@ typedef struct Partition {
|
|||||||
|
|
||||||
bool dropped;
|
bool dropped;
|
||||||
bool factory_reset;
|
bool factory_reset;
|
||||||
|
bool discarded;
|
||||||
int32_t priority;
|
int32_t priority;
|
||||||
|
|
||||||
uint32_t weight, padding_weight;
|
uint32_t weight, padding_weight;
|
||||||
@ -2748,7 +2749,7 @@ static bool partition_needs_populate(const Partition *p) {
|
|||||||
static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) {
|
static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) {
|
||||||
MakeFileSystemFlags flags = 0;
|
MakeFileSystemFlags flags = 0;
|
||||||
|
|
||||||
if (arg_discard)
|
if (arg_discard && !p->discarded)
|
||||||
flags |= MKFS_DISCARD;
|
flags |= MKFS_DISCARD;
|
||||||
|
|
||||||
if (streq(p->format, "erofs") && !DEBUG_LOGGING && !isatty_safe(STDERR_FILENO))
|
if (streq(p->format, "erofs") && !DEBUG_LOGGING && !isatty_safe(STDERR_FILENO))
|
||||||
@ -4668,6 +4669,7 @@ static int context_discard_partition(Context *context, Partition *p) {
|
|||||||
return log_error_errno(r, "Failed to discard data for future partition %" PRIu64 ".", p->partno);
|
return log_error_errno(r, "Failed to discard data for future partition %" PRIu64 ".", p->partno);
|
||||||
|
|
||||||
log_info("Successfully discarded data from future partition %" PRIu64 ".", p->partno);
|
log_info("Successfully discarded data from future partition %" PRIu64 ".", p->partno);
|
||||||
|
p->discarded = true;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -128,7 +128,7 @@ static SD_VARLINK_DEFINE_METHOD(
|
|||||||
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"),
|
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"),
|
||||||
SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||||
SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."),
|
SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."),
|
||||||
SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||||
SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"),
|
SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"),
|
||||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE),
|
SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE),
|
||||||
SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"),
|
SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"),
|
||||||
|
|||||||
@ -45,8 +45,11 @@ struct Instance {
|
|||||||
InstanceMetadata metadata;
|
InstanceMetadata metadata;
|
||||||
|
|
||||||
/* Where we found the instance */
|
/* Where we found the instance */
|
||||||
char *path;
|
char *path; /* includes the `.sysupdate.partial.` (etc.) prefix, if applicable */
|
||||||
PartitionInfo partition_info;
|
PartitionInfo partition_info;
|
||||||
|
|
||||||
|
bool is_partial;
|
||||||
|
bool is_pending;
|
||||||
};
|
};
|
||||||
|
|
||||||
void instance_metadata_destroy(InstanceMetadata *m);
|
void instance_metadata_destroy(InstanceMetadata *m);
|
||||||
|
|||||||
@ -16,6 +16,33 @@ void partition_info_destroy(PartitionInfo *p) {
|
|||||||
p->device = mfree(p->device);
|
p->device = mfree(p->device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(dest);
|
||||||
|
assert(src);
|
||||||
|
|
||||||
|
r = free_and_strdup_warn(&dest->label, src->label);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = free_and_strdup_warn(&dest->device, src->device);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
dest->partno = src->partno;
|
||||||
|
dest->start = src->start;
|
||||||
|
dest->size = src->size;
|
||||||
|
dest->flags = src->flags;
|
||||||
|
dest->type = src->type;
|
||||||
|
dest->uuid = src->uuid;
|
||||||
|
dest->no_auto = src->no_auto;
|
||||||
|
dest->read_only = src->read_only;
|
||||||
|
dest->growfs = src->growfs;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int read_partition_info(
|
int read_partition_info(
|
||||||
struct fdisk_context *c,
|
struct fdisk_context *c,
|
||||||
struct fdisk_table *t,
|
struct fdisk_table *t,
|
||||||
|
|||||||
@ -36,6 +36,7 @@ typedef struct PartitionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void partition_info_destroy(PartitionInfo *p);
|
void partition_info_destroy(PartitionInfo *p);
|
||||||
|
int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src);
|
||||||
|
|
||||||
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
|
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
|
||||||
|
|
||||||
|
|||||||
@ -80,15 +80,22 @@ static int resource_load_from_directory_recursive(
|
|||||||
Resource *rr,
|
Resource *rr,
|
||||||
DIR* d,
|
DIR* d,
|
||||||
const char* relpath,
|
const char* relpath,
|
||||||
mode_t m) {
|
const char* relpath_for_matching,
|
||||||
|
mode_t m,
|
||||||
|
bool ancestor_is_partial,
|
||||||
|
bool ancestor_is_pending) {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||||
_cleanup_free_ char *joined = NULL, *rel_joined = NULL;
|
_cleanup_free_ char *joined = NULL, *rel_joined = NULL;
|
||||||
|
_cleanup_free_ char *rel_joined_for_matching = NULL;
|
||||||
Instance *instance;
|
Instance *instance;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
|
const char *de_d_name_stripped;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
bool is_partial = ancestor_is_partial, is_pending = ancestor_is_pending;
|
||||||
|
const char *stripped;
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
de = readdir_no_dot(d);
|
de = readdir_no_dot(d);
|
||||||
@ -128,11 +135,26 @@ static int resource_load_from_directory_recursive(
|
|||||||
if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
|
if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) {
|
||||||
|
de_d_name_stripped = stripped;
|
||||||
|
is_partial = true;
|
||||||
|
} else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) {
|
||||||
|
de_d_name_stripped = stripped;
|
||||||
|
is_pending = true;
|
||||||
|
} else
|
||||||
|
de_d_name_stripped = de->d_name;
|
||||||
|
|
||||||
rel_joined = path_join(relpath, de->d_name);
|
rel_joined = path_join(relpath, de->d_name);
|
||||||
if (!rel_joined)
|
if (!rel_joined)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
|
/* Match against the filename with any `.sysupdate.partial.` (etc.) prefix stripped, so the
|
||||||
|
* user’s patterns still apply. But don’t use the stripped version in any paths or recursion. */
|
||||||
|
rel_joined_for_matching = path_join(relpath_for_matching, de_d_name_stripped);
|
||||||
|
if (!rel_joined_for_matching)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = pattern_match_many(rr->patterns, rel_joined_for_matching, &extracted_fields);
|
||||||
if (r == PATTERN_MATCH_RETRY) {
|
if (r == PATTERN_MATCH_RETRY) {
|
||||||
_cleanup_closedir_ DIR *subdir = NULL;
|
_cleanup_closedir_ DIR *subdir = NULL;
|
||||||
|
|
||||||
@ -140,7 +162,7 @@ static int resource_load_from_directory_recursive(
|
|||||||
if (!subdir)
|
if (!subdir)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
|
r = resource_load_from_directory_recursive(rr, subdir, rel_joined, rel_joined_for_matching, m, is_partial, is_pending);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
if (r == 0)
|
if (r == 0)
|
||||||
@ -168,6 +190,9 @@ static int resource_load_from_directory_recursive(
|
|||||||
|
|
||||||
if (instance->metadata.mode == MODE_INVALID)
|
if (instance->metadata.mode == MODE_INVALID)
|
||||||
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
|
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
|
||||||
|
|
||||||
|
instance->is_partial = is_partial;
|
||||||
|
instance->is_pending = is_pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -192,7 +217,7 @@ static int resource_load_from_directory(
|
|||||||
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
|
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource_load_from_directory_recursive(rr, d, NULL, m);
|
return resource_load_from_directory_recursive(rr, d, NULL, NULL, m, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int resource_load_from_blockdev(Resource *rr) {
|
static int resource_load_from_blockdev(Resource *rr) {
|
||||||
@ -219,6 +244,9 @@ static int resource_load_from_blockdev(Resource *rr) {
|
|||||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||||
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
|
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
|
||||||
Instance *instance;
|
Instance *instance;
|
||||||
|
const char *pinfo_label_stripped;
|
||||||
|
bool is_partial = false, is_pending = false;
|
||||||
|
const char *stripped;
|
||||||
|
|
||||||
r = read_partition_info(c, t, i, &pinfo);
|
r = read_partition_info(c, t, i, &pinfo);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@ -236,7 +264,18 @@ static int resource_load_from_blockdev(Resource *rr) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
|
/* Match the label with any partial/pending prefix removed so the user’s existing patterns
|
||||||
|
* match regardless of the instance’s state. */
|
||||||
|
if ((stripped = startswith(pinfo.label, "PRT#"))) {
|
||||||
|
pinfo_label_stripped = stripped;
|
||||||
|
is_partial = true;
|
||||||
|
} else if ((stripped = startswith(pinfo.label, "PND#"))) {
|
||||||
|
pinfo_label_stripped = stripped;
|
||||||
|
is_pending = true;
|
||||||
|
} else
|
||||||
|
pinfo_label_stripped = pinfo.label;
|
||||||
|
|
||||||
|
r = pattern_match_many(rr->patterns, pinfo_label_stripped, &extracted_fields);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to match pattern: %m");
|
return log_error_errno(r, "Failed to match pattern: %m");
|
||||||
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
|
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
|
||||||
@ -262,6 +301,9 @@ static int resource_load_from_blockdev(Resource *rr) {
|
|||||||
|
|
||||||
if (instance->metadata.read_only < 0)
|
if (instance->metadata.read_only < 0)
|
||||||
instance->metadata.read_only = instance->partition_info.read_only;
|
instance->metadata.read_only = instance->partition_info.read_only;
|
||||||
|
|
||||||
|
instance->is_partial = is_partial;
|
||||||
|
instance->is_pending = is_pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -539,6 +581,11 @@ static int resource_load_from_web(
|
|||||||
memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
|
memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
|
||||||
instance->metadata.sha256sum_set = true;
|
instance->metadata.sha256sum_set = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Web resources can only be a source, not a target, so
|
||||||
|
* can never be partial or pending. */
|
||||||
|
instance->is_partial = false;
|
||||||
|
instance->is_pending = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,6 @@
|
|||||||
#include "sysupdate-resource.h"
|
#include "sysupdate-resource.h"
|
||||||
#include "sysupdate-transfer.h"
|
#include "sysupdate-transfer.h"
|
||||||
#include "time-util.h"
|
#include "time-util.h"
|
||||||
#include "tmpfile-util.h"
|
|
||||||
#include "web-util.h"
|
#include "web-util.h"
|
||||||
|
|
||||||
/* Default value for InstancesMax= for fs object targets */
|
/* Default value for InstancesMax= for fs object targets */
|
||||||
@ -51,7 +50,8 @@ Transfer* transfer_free(Transfer *t) {
|
|||||||
if (!t)
|
if (!t)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
|
free(t->temporary_partial_path);
|
||||||
|
free(t->temporary_pending_path);
|
||||||
|
|
||||||
free(t->id);
|
free(t->id);
|
||||||
|
|
||||||
@ -67,6 +67,9 @@ Transfer* transfer_free(Transfer *t) {
|
|||||||
strv_free(t->appstream);
|
strv_free(t->appstream);
|
||||||
|
|
||||||
partition_info_destroy(&t->partition_info);
|
partition_info_destroy(&t->partition_info);
|
||||||
|
free(t->temporary_partial_partition_label);
|
||||||
|
free(t->temporary_pending_partition_label);
|
||||||
|
free(t->final_partition_label);
|
||||||
|
|
||||||
resource_destroy(&t->source);
|
resource_destroy(&t->source);
|
||||||
resource_destroy(&t->target);
|
resource_destroy(&t->target);
|
||||||
@ -740,6 +743,49 @@ static void transfer_remove_temporary(Transfer *t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int transfer_instance_vacuum(
|
||||||
|
Transfer *t,
|
||||||
|
Instance *instance) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(t);
|
||||||
|
assert(instance);
|
||||||
|
|
||||||
|
switch (t->target.type) {
|
||||||
|
|
||||||
|
case RESOURCE_REGULAR_FILE:
|
||||||
|
case RESOURCE_DIRECTORY:
|
||||||
|
case RESOURCE_SUBVOLUME:
|
||||||
|
r = rm_rf(instance->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
|
||||||
|
if (r < 0 && r != -ENOENT)
|
||||||
|
return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", instance->path);
|
||||||
|
|
||||||
|
(void) rmdir_parents(instance->path, t->target.path);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESOURCE_PARTITION: {
|
||||||
|
PartitionInfo pinfo = instance->partition_info;
|
||||||
|
|
||||||
|
/* label "_empty" means "no contents" for our purposes */
|
||||||
|
pinfo.label = (char*) "_empty";
|
||||||
|
|
||||||
|
log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
|
||||||
|
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
t->target.n_empty++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int transfer_vacuum(
|
int transfer_vacuum(
|
||||||
Transfer *t,
|
Transfer *t,
|
||||||
uint64_t space,
|
uint64_t space,
|
||||||
@ -752,7 +798,45 @@ int transfer_vacuum(
|
|||||||
|
|
||||||
transfer_remove_temporary(t);
|
transfer_remove_temporary(t);
|
||||||
|
|
||||||
/* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
|
/* First, remove any partial or pending instances (unless protected) */
|
||||||
|
for (size_t i = 0; i < t->target.n_instances;) {
|
||||||
|
Instance *instance = t->target.instances[i];
|
||||||
|
|
||||||
|
assert(instance);
|
||||||
|
|
||||||
|
if (!instance->is_pending && !instance->is_partial) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this is listed among the protected versions, then let's not remove it */
|
||||||
|
if (strv_contains(t->protected_versions, instance->metadata.version) ||
|
||||||
|
(extra_protected_version && streq(extra_protected_version, instance->metadata.version))) {
|
||||||
|
log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(instance->resource);
|
||||||
|
|
||||||
|
log_info("%s Removing old %s '%s' (%s).",
|
||||||
|
glyph(GLYPH_RECYCLING),
|
||||||
|
instance->is_partial ? "partial" : "pending",
|
||||||
|
instance->path,
|
||||||
|
resource_type_to_string(instance->resource->type));
|
||||||
|
|
||||||
|
r = transfer_instance_vacuum(t, instance);
|
||||||
|
if (r < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
instance_free(instance);
|
||||||
|
memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*));
|
||||||
|
t->target.n_instances--;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Second, calculate how many instances to keep, based on the instance limit — but keep at least one */
|
||||||
|
|
||||||
instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
|
instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
|
||||||
assert(instances_max >= 1);
|
assert(instances_max >= 1);
|
||||||
@ -840,36 +924,9 @@ int transfer_vacuum(
|
|||||||
oldest->path,
|
oldest->path,
|
||||||
resource_type_to_string(oldest->resource->type));
|
resource_type_to_string(oldest->resource->type));
|
||||||
|
|
||||||
switch (t->target.type) {
|
r = transfer_instance_vacuum(t, oldest);
|
||||||
|
if (r < 0)
|
||||||
case RESOURCE_REGULAR_FILE:
|
return 0;
|
||||||
case RESOURCE_DIRECTORY:
|
|
||||||
case RESOURCE_SUBVOLUME:
|
|
||||||
r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
|
|
||||||
if (r < 0 && r != -ENOENT)
|
|
||||||
return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
|
|
||||||
|
|
||||||
(void) rmdir_parents(oldest->path, t->target.path);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESOURCE_PARTITION: {
|
|
||||||
PartitionInfo pinfo = oldest->partition_info;
|
|
||||||
|
|
||||||
/* label "_empty" means "no contents" for our purposes */
|
|
||||||
pinfo.label = (char*) "_empty";
|
|
||||||
|
|
||||||
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
t->target.n_empty++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert_not_reached();
|
|
||||||
}
|
|
||||||
|
|
||||||
instance_free(oldest);
|
instance_free(oldest);
|
||||||
memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
|
memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
|
||||||
@ -1111,8 +1168,100 @@ static int run_callout(
|
|||||||
return sd_event_loop(event);
|
return sd_event_loop(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Build the filenames and paths which is normally done by transfer_acquire_instance(), but for partial
|
||||||
|
* and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
|
||||||
|
* skipped). */
|
||||||
|
static int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
|
||||||
|
_cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(t);
|
||||||
|
assert(i);
|
||||||
|
|
||||||
|
assert(!t->final_path);
|
||||||
|
assert(!t->temporary_partial_path);
|
||||||
|
assert(!t->temporary_pending_path);
|
||||||
|
assert(!t->final_partition_label);
|
||||||
|
assert(!t->temporary_partial_partition_label);
|
||||||
|
assert(!t->temporary_pending_partition_label);
|
||||||
|
assert(!strv_isempty(t->target.patterns));
|
||||||
|
|
||||||
|
/* Format the target name using the first pattern specified */
|
||||||
|
compile_pattern_fields(t, i, f);
|
||||||
|
r = pattern_format(t->target.patterns[0], f, &formatted_pattern);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to format target pattern: %m");
|
||||||
|
|
||||||
|
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
|
||||||
|
_cleanup_free_ char *final_dir = NULL, *final_filename = NULL, *partial_filename = NULL, *pending_filename = NULL;
|
||||||
|
|
||||||
|
if (!path_is_safe(formatted_pattern))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
|
||||||
|
|
||||||
|
t->final_path = path_join(t->target.path, formatted_pattern);
|
||||||
|
if (!t->final_path)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
/* Build the paths for the partial and pending files, which hold the resource while it’s
|
||||||
|
* being acquired and after it’s been acquired (but before it’s moved to the final_path
|
||||||
|
* when it’s installed).
|
||||||
|
*
|
||||||
|
* Split the filename off the `final_path`, then add a prefix to it for each of partial and
|
||||||
|
* pending, then join them back on to the same directory. */
|
||||||
|
r = path_split_prefix_filename(t->final_path, &final_dir, &final_filename);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse path: %m");
|
||||||
|
|
||||||
|
if (!strprepend(&partial_filename, ".sysupdate.partial.", final_filename))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (!strprepend(&pending_filename, ".sysupdate.pending.", final_filename))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
t->temporary_partial_path = path_join(final_dir, partial_filename);
|
||||||
|
if (!t->temporary_partial_path)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
t->temporary_pending_path = path_join(final_dir, pending_filename);
|
||||||
|
if (!t->temporary_pending_path)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t->target.type == RESOURCE_PARTITION) {
|
||||||
|
r = gpt_partition_label_valid(formatted_pattern);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
|
||||||
|
if (!r)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
|
||||||
|
|
||||||
|
if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
|
||||||
|
return log_oom();
|
||||||
|
r = gpt_partition_label_valid(formatted_partial_pattern);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
|
||||||
|
if (!r)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
|
||||||
|
|
||||||
|
free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
|
||||||
|
|
||||||
|
if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
|
||||||
|
return log_oom();
|
||||||
|
r = gpt_partition_label_valid(formatted_pending_pattern);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
|
||||||
|
if (!r)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
|
||||||
|
|
||||||
|
free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
|
||||||
|
|
||||||
|
t->final_partition_label = TAKE_PTR(formatted_pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
|
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata) {
|
||||||
_cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
|
_cleanup_free_ char *digest = NULL;
|
||||||
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
|
char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
|
||||||
const char *where = NULL;
|
const char *where = NULL;
|
||||||
InstanceMetadata f;
|
InstanceMetadata f;
|
||||||
@ -1126,48 +1275,35 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
|
|
||||||
/* Does this instance already exist in the target? Then we don't need to acquire anything */
|
/* Does this instance already exist in the target? Then we don't need to acquire anything */
|
||||||
existing = resource_find_instance(&t->target, i->metadata.version);
|
existing = resource_find_instance(&t->target, i->metadata.version);
|
||||||
|
if (existing && (existing->is_partial || existing->is_pending))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already partial or pending in the target.", i->path);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
log_info("No need to acquire '%s', already installed.", i->path);
|
log_info("No need to acquire '%s', already installed.", i->path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!t->final_path);
|
/* Compute up the temporary paths */
|
||||||
assert(!t->temporary_path);
|
r = transfer_compute_temporary_paths(t, i, &f);
|
||||||
assert(!strv_isempty(t->target.patterns));
|
|
||||||
|
|
||||||
/* Format the target name using the first pattern specified */
|
|
||||||
compile_pattern_fields(t, i, &f);
|
|
||||||
r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to format target pattern: %m");
|
return r;
|
||||||
|
|
||||||
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
|
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
|
||||||
|
r = mkdir_parents(t->temporary_partial_path, 0755);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Cannot create target directory: %m");
|
||||||
|
|
||||||
if (!path_is_safe(formatted_pattern))
|
r = mkdir_parents(t->temporary_pending_path, 0755);
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Cannot create target directory: %m");
|
||||||
t->final_path = path_join(t->target.path, formatted_pattern);
|
|
||||||
if (!t->final_path)
|
|
||||||
return log_oom();
|
|
||||||
|
|
||||||
r = mkdir_parents(t->final_path, 0755);
|
r = mkdir_parents(t->final_path, 0755);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Cannot create target directory: %m");
|
return log_error_errno(r, "Cannot create target directory: %m");
|
||||||
|
|
||||||
r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
|
|
||||||
if (r < 0)
|
|
||||||
return log_error_errno(r, "Failed to generate temporary target path: %m");
|
|
||||||
|
|
||||||
where = t->final_path;
|
where = t->final_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->target.type == RESOURCE_PARTITION) {
|
if (t->target.type == RESOURCE_PARTITION) {
|
||||||
r = gpt_partition_label_valid(formatted_pattern);
|
|
||||||
if (r < 0)
|
|
||||||
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
|
|
||||||
if (!r)
|
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
|
|
||||||
|
|
||||||
r = find_suitable_partition(
|
r = find_suitable_partition(
|
||||||
t->target.path,
|
t->target.path,
|
||||||
i->metadata.size,
|
i->metadata.size,
|
||||||
@ -1180,6 +1316,20 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
xsprintf(max_size, "%" PRIu64, t->partition_info.size);
|
xsprintf(max_size, "%" PRIu64, t->partition_info.size);
|
||||||
|
|
||||||
where = t->partition_info.device;
|
where = t->partition_info.device;
|
||||||
|
|
||||||
|
/* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
|
||||||
|
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
t->partition_change = PARTITION_LABEL;
|
||||||
|
|
||||||
|
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
|
||||||
|
r = patch_partition(
|
||||||
|
t->target.path,
|
||||||
|
&t->partition_info,
|
||||||
|
t->partition_change);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(where);
|
assert(where);
|
||||||
@ -1218,7 +1368,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
"--direct", /* just copy/unpack the specified file, don't do anything else */
|
"--direct", /* just copy/unpack the specified file, don't do anything else */
|
||||||
arg_sync ? "--sync=yes" : "--sync=no",
|
arg_sync ? "--sync=yes" : "--sync=no",
|
||||||
i->path,
|
i->path,
|
||||||
t->temporary_path),
|
t->temporary_partial_path),
|
||||||
t, i, cb, userdata);
|
t, i, cb, userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1259,7 +1409,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
arg_sync ? "--sync=yes" : "--sync=no",
|
arg_sync ? "--sync=yes" : "--sync=no",
|
||||||
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
||||||
i->path,
|
i->path,
|
||||||
t->temporary_path),
|
t->temporary_partial_path),
|
||||||
t, i, cb, userdata);
|
t, i, cb, userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1276,7 +1426,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
arg_sync ? "--sync=yes" : "--sync=no",
|
arg_sync ? "--sync=yes" : "--sync=no",
|
||||||
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
||||||
i->path,
|
i->path,
|
||||||
t->temporary_path),
|
t->temporary_partial_path),
|
||||||
t, i, cb, userdata);
|
t, i, cb, userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1296,7 +1446,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
"--verify", digest, /* validate by explicit SHA256 sum */
|
"--verify", digest, /* validate by explicit SHA256 sum */
|
||||||
arg_sync ? "--sync=yes" : "--sync=no",
|
arg_sync ? "--sync=yes" : "--sync=no",
|
||||||
i->path,
|
i->path,
|
||||||
t->temporary_path),
|
t->temporary_partial_path),
|
||||||
t, i, cb, userdata);
|
t, i, cb, userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1336,7 +1486,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
|
||||||
arg_sync ? "--sync=yes" : "--sync=no",
|
arg_sync ? "--sync=yes" : "--sync=no",
|
||||||
i->path,
|
i->path,
|
||||||
t->temporary_path),
|
t->temporary_partial_path),
|
||||||
t, i, cb, userdata);
|
t, i, cb, userdata);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1348,7 +1498,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
|
|
||||||
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
|
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
|
||||||
bool need_sync = false;
|
bool need_sync = false;
|
||||||
assert(t->temporary_path);
|
assert(t->temporary_partial_path);
|
||||||
|
assert(t->temporary_pending_path);
|
||||||
|
|
||||||
/* Apply file attributes if set */
|
/* Apply file attributes if set */
|
||||||
if (f.mtime != USEC_INFINITY) {
|
if (f.mtime != USEC_INFINITY) {
|
||||||
@ -1356,8 +1507,8 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
|
|
||||||
timespec_store(&ts, f.mtime);
|
timespec_store(&ts, f.mtime);
|
||||||
|
|
||||||
if (utimensat(AT_FDCWD, t->temporary_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
|
if (utimensat(AT_FDCWD, t->temporary_partial_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
|
||||||
return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_path);
|
return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_partial_path);
|
||||||
|
|
||||||
need_sync = true;
|
need_sync = true;
|
||||||
}
|
}
|
||||||
@ -1366,9 +1517,9 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
/* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
|
/* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
|
||||||
* kernels don't support that however, in that case we fall back to chmod(). Not as
|
* kernels don't support that however, in that case we fall back to chmod(). Not as
|
||||||
* safe, but shouldn't be a problem, given that we don't create symlinks here. */
|
* safe, but shouldn't be a problem, given that we don't create symlinks here. */
|
||||||
if (fchmodat(AT_FDCWD, t->temporary_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
|
if (fchmodat(AT_FDCWD, t->temporary_partial_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
|
||||||
(!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_path, f.mode) < 0))
|
(!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_partial_path, f.mode) < 0))
|
||||||
return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_path);
|
return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_partial_path);
|
||||||
|
|
||||||
need_sync = true;
|
need_sync = true;
|
||||||
}
|
}
|
||||||
@ -1376,20 +1527,34 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
/* Synchronize */
|
/* Synchronize */
|
||||||
if (arg_sync && need_sync) {
|
if (arg_sync && need_sync) {
|
||||||
if (t->target.type == RESOURCE_REGULAR_FILE)
|
if (t->target.type == RESOURCE_REGULAR_FILE)
|
||||||
r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
|
r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path);
|
||||||
else {
|
else {
|
||||||
assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
|
assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
|
||||||
r = syncfs_path(AT_FDCWD, t->temporary_path);
|
r = syncfs_path(AT_FDCWD, t->temporary_partial_path);
|
||||||
}
|
}
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
|
return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_partial_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
t->install_read_only = f.read_only;
|
t->install_read_only = f.read_only;
|
||||||
|
|
||||||
|
/* Rename the file from `.sysupdate.partial.<VERSION>` to `.sysupdate.pending.<VERSION>` to indicate it’s ready to install. */
|
||||||
|
log_debug("Renaming resource instance '%s' to '%s'.", t->temporary_partial_path, t->temporary_pending_path);
|
||||||
|
r = install_file(AT_FDCWD, t->temporary_partial_path,
|
||||||
|
AT_FDCWD, t->temporary_pending_path,
|
||||||
|
INSTALL_REPLACE|
|
||||||
|
(t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
|
||||||
|
(t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to move '%s' into pending place: %m", t->temporary_pending_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->target.type == RESOURCE_PARTITION) {
|
if (t->target.type == RESOURCE_PARTITION) {
|
||||||
free_and_replace(t->partition_info.label, formatted_pattern);
|
/* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete
|
||||||
|
* and the partition is ready for install. */
|
||||||
|
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
t->partition_change = PARTITION_LABEL;
|
t->partition_change = PARTITION_LABEL;
|
||||||
|
|
||||||
if (f.partition_uuid_set) {
|
if (f.partition_uuid_set) {
|
||||||
@ -1416,6 +1581,14 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
t->partition_info.growfs = f.growfs;
|
t->partition_info.growfs = f.growfs;
|
||||||
t->partition_change |= PARTITION_GROWFS;
|
t->partition_change |= PARTITION_GROWFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
|
||||||
|
r = patch_partition(
|
||||||
|
t->target.path,
|
||||||
|
&t->partition_info,
|
||||||
|
t->partition_change);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For regular file cases the only step left is to install the file in place, which install_file()
|
/* For regular file cases the only step left is to install the file in place, which install_file()
|
||||||
@ -1426,6 +1599,42 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
|
||||||
|
InstanceMetadata f;
|
||||||
|
Instance *existing;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(t);
|
||||||
|
assert(i);
|
||||||
|
|
||||||
|
log_debug("transfer_process_partial_and_pending_instance %s", i->path);
|
||||||
|
|
||||||
|
/* Does this instance already exist in the target but isn’t pending? */
|
||||||
|
existing = resource_find_instance(&t->target, i->metadata.version);
|
||||||
|
if (existing && !existing->is_pending)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already in the target but is not pending.", i->path);
|
||||||
|
|
||||||
|
/* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
|
||||||
|
* transfer_acquire_instance(). */
|
||||||
|
r = transfer_compute_temporary_paths(t, i, &f);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
|
||||||
|
* already happened in the acquire phase, the target should already have that information and it
|
||||||
|
* should already have been claimed as `PND#`. */
|
||||||
|
if (t->target.type == RESOURCE_PARTITION) {
|
||||||
|
assert(i->resource == &t->target);
|
||||||
|
assert(i->is_pending);
|
||||||
|
|
||||||
|
r = partition_info_copy(&t->partition_info, &i->partition_info);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int transfer_install_instance(
|
int transfer_install_instance(
|
||||||
Transfer *t,
|
Transfer *t,
|
||||||
Instance *i,
|
Instance *i,
|
||||||
@ -1436,13 +1645,15 @@ int transfer_install_instance(
|
|||||||
assert(t);
|
assert(t);
|
||||||
assert(i);
|
assert(i);
|
||||||
assert(i->resource);
|
assert(i->resource);
|
||||||
assert(t == container_of(i->resource, Transfer, source));
|
assert(i->is_pending || t == container_of(i->resource, Transfer, source));
|
||||||
|
|
||||||
if (t->temporary_path) {
|
log_debug("transfer_install_instance %s %s %s %d", i->path, t->temporary_pending_path, t->final_partition_label, t->partition_change);
|
||||||
|
|
||||||
|
if (t->temporary_pending_path) {
|
||||||
assert(RESOURCE_IS_FILESYSTEM(t->target.type));
|
assert(RESOURCE_IS_FILESYSTEM(t->target.type));
|
||||||
assert(t->final_path);
|
assert(t->final_path);
|
||||||
|
|
||||||
r = install_file(AT_FDCWD, t->temporary_path,
|
r = install_file(AT_FDCWD, t->temporary_pending_path,
|
||||||
AT_FDCWD, t->final_path,
|
AT_FDCWD, t->final_path,
|
||||||
INSTALL_REPLACE|
|
INSTALL_REPLACE|
|
||||||
(t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
|
(t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
|
||||||
@ -1456,11 +1667,17 @@ int transfer_install_instance(
|
|||||||
t->final_path,
|
t->final_path,
|
||||||
resource_type_to_string(t->target.type));
|
resource_type_to_string(t->target.type));
|
||||||
|
|
||||||
t->temporary_path = mfree(t->temporary_path);
|
t->temporary_pending_path = mfree(t->temporary_pending_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->partition_change != 0) {
|
if (t->temporary_pending_partition_label) {
|
||||||
assert(t->target.type == RESOURCE_PARTITION);
|
assert(t->target.type == RESOURCE_PARTITION);
|
||||||
|
assert(t->final_partition_label);
|
||||||
|
|
||||||
|
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
t->partition_change = PARTITION_LABEL;
|
||||||
|
|
||||||
r = patch_partition(
|
r = patch_partition(
|
||||||
t->target.path,
|
t->target.path,
|
||||||
|
|||||||
@ -39,13 +39,17 @@ typedef struct Transfer {
|
|||||||
int growfs;
|
int growfs;
|
||||||
|
|
||||||
/* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */
|
/* If we create a new file/dir/subvol in the fs, the temporary and final path we create it under, as well as the read-only flag for it */
|
||||||
char *temporary_path;
|
char *temporary_partial_path;
|
||||||
|
char *temporary_pending_path;
|
||||||
char *final_path;
|
char *final_path;
|
||||||
int install_read_only;
|
int install_read_only;
|
||||||
|
|
||||||
/* If we write to a partition in a partition table, the metrics of it */
|
/* If we write to a partition in a partition table, the metrics of it */
|
||||||
PartitionInfo partition_info;
|
PartitionInfo partition_info;
|
||||||
PartitionChange partition_change;
|
PartitionChange partition_change;
|
||||||
|
char *final_partition_label;
|
||||||
|
char *temporary_partial_partition_label;
|
||||||
|
char *temporary_pending_partition_label;
|
||||||
|
|
||||||
Context *context;
|
Context *context;
|
||||||
} Transfer;
|
} Transfer;
|
||||||
@ -63,5 +67,6 @@ int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
|
|||||||
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
|
int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
|
||||||
|
|
||||||
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
|
int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
|
||||||
|
int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i);
|
||||||
|
|
||||||
int transfer_install_instance(Transfer *t, Instance *i, const char *root);
|
int transfer_install_instance(Transfer *t, Instance *i, const char *root);
|
||||||
|
|||||||
@ -9,6 +9,9 @@ const char* update_set_flags_to_color(UpdateSetFlags flags) {
|
|||||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||||
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
|
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
|
||||||
|
|
||||||
|
if (flags & (UPDATE_PARTIAL|UPDATE_PENDING))
|
||||||
|
return ansi_highlight_cyan();
|
||||||
|
|
||||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_INCOMPLETE))
|
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_INCOMPLETE))
|
||||||
return ansi_highlight_yellow();
|
return ansi_highlight_yellow();
|
||||||
|
|
||||||
@ -29,6 +32,9 @@ const char* update_set_flags_to_glyph(UpdateSetFlags flags) {
|
|||||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||||
return glyph(GLYPH_MULTIPLICATION_SIGN);
|
return glyph(GLYPH_MULTIPLICATION_SIGN);
|
||||||
|
|
||||||
|
if (flags & (UPDATE_PARTIAL|UPDATE_PENDING))
|
||||||
|
return glyph(GLYPH_DOWNLOAD);
|
||||||
|
|
||||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||||
return glyph(GLYPH_BLACK_CIRCLE);
|
return glyph(GLYPH_BLACK_CIRCLE);
|
||||||
|
|
||||||
@ -54,6 +60,18 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) {
|
|||||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
return "current";
|
return "current";
|
||||||
|
|
||||||
|
case UPDATE_INSTALLED|UPDATE_PENDING|UPDATE_NEWEST:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PENDING|UPDATE_NEWEST:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
|
return "current+pending";
|
||||||
|
|
||||||
|
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST:
|
||||||
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
|
return "current+partial";
|
||||||
|
|
||||||
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||||
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||||
return "candidate";
|
return "candidate";
|
||||||
|
|||||||
@ -10,6 +10,8 @@ typedef enum UpdateSetFlags {
|
|||||||
UPDATE_OBSOLETE = 1 << 3,
|
UPDATE_OBSOLETE = 1 << 3,
|
||||||
UPDATE_PROTECTED = 1 << 4,
|
UPDATE_PROTECTED = 1 << 4,
|
||||||
UPDATE_INCOMPLETE = 1 << 5,
|
UPDATE_INCOMPLETE = 1 << 5,
|
||||||
|
UPDATE_PARTIAL = 1 << 6,
|
||||||
|
UPDATE_PENDING = 1 << 7,
|
||||||
} UpdateSetFlags;
|
} UpdateSetFlags;
|
||||||
|
|
||||||
const char* update_set_flags_to_color(UpdateSetFlags flags);
|
const char* update_set_flags_to_color(UpdateSetFlags flags);
|
||||||
|
|||||||
@ -397,7 +397,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
assert(flags == UPDATE_INSTALLED);
|
assert(flags == UPDATE_INSTALLED);
|
||||||
|
|
||||||
match = resource_find_instance(&t->target, cursor);
|
match = resource_find_instance(&t->target, cursor);
|
||||||
if (!match) {
|
if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) {
|
||||||
/* When we're looking for installed versions, let's be robust and treat
|
/* When we're looking for installed versions, let's be robust and treat
|
||||||
* an incomplete installation as an installation. Otherwise, there are
|
* an incomplete installation as an installation. Otherwise, there are
|
||||||
* situations that can lead to sysupdate wiping the currently booted OS.
|
* situations that can lead to sysupdate wiping the currently booted OS.
|
||||||
@ -413,6 +413,14 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
|
|
||||||
if (strv_contains(t->protected_versions, cursor))
|
if (strv_contains(t->protected_versions, cursor))
|
||||||
extra_flags |= UPDATE_PROTECTED;
|
extra_flags |= UPDATE_PROTECTED;
|
||||||
|
|
||||||
|
/* Partial or pending updates by definition are not incomplete, they’re
|
||||||
|
* partial/pending instead */
|
||||||
|
if (match && match->is_partial)
|
||||||
|
extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
|
||||||
|
|
||||||
|
if (match && match->is_pending)
|
||||||
|
extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = free_and_strdup_warn(&boundary, cursor);
|
r = free_and_strdup_warn(&boundary, cursor);
|
||||||
@ -431,7 +439,9 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
|
|
||||||
/* Merge in what we've learned and continue onto the next version */
|
/* Merge in what we've learned and continue onto the next version */
|
||||||
|
|
||||||
if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
|
if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
|
||||||
|
FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
|
||||||
|
FLAGS_SET(u->flags, UPDATE_PENDING)) {
|
||||||
assert(u->n_instances == c->n_transfers);
|
assert(u->n_instances == c->n_transfers);
|
||||||
|
|
||||||
/* Incomplete updates will have picked NULL instances for the transfers that
|
/* Incomplete updates will have picked NULL instances for the transfers that
|
||||||
@ -450,7 +460,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
|
|
||||||
/* If this is the newest installed version, that is incomplete and just became marked
|
/* If this is the newest installed version, that is incomplete and just became marked
|
||||||
* as available, and if there is no other candidate available, we promote this to be
|
* as available, and if there is no other candidate available, we promote this to be
|
||||||
* the candidate. */
|
* the candidate. Ignore partial or pending status on the update set. */
|
||||||
if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
|
if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
|
||||||
!c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
|
!c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
|
||||||
c->candidate = u;
|
c->candidate = u;
|
||||||
@ -486,7 +496,8 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
|
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
|
||||||
c->newest_installed = us;
|
c->newest_installed = us;
|
||||||
|
|
||||||
/* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
|
/* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
|
||||||
|
* It may be partial or pending. */
|
||||||
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
|
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
|
||||||
c->candidate = us;
|
c->candidate = us;
|
||||||
}
|
}
|
||||||
@ -496,6 +507,11 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
|||||||
c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
|
c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
|
||||||
c->candidate = NULL;
|
c->candidate = NULL;
|
||||||
|
|
||||||
|
/* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
|
||||||
|
if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
|
||||||
|
!c->candidate)
|
||||||
|
c->candidate = c->newest_installed;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,13 +611,14 @@ static int context_show_version(Context *c, const char *version) {
|
|||||||
if (!sd_json_format_enabled(arg_json_format_flags))
|
if (!sd_json_format_enabled(arg_json_format_flags))
|
||||||
printf("%s%s%s Version: %s\n"
|
printf("%s%s%s Version: %s\n"
|
||||||
" State: %s%s%s\n"
|
" State: %s%s%s\n"
|
||||||
"Installed: %s%s\n"
|
"Installed: %s%s%s%s\n"
|
||||||
"Available: %s%s\n"
|
"Available: %s%s\n"
|
||||||
"Protected: %s%s%s\n"
|
"Protected: %s%s%s\n"
|
||||||
" Obsolete: %s%s%s\n\n",
|
" Obsolete: %s%s%s\n\n",
|
||||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
|
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
|
||||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
||||||
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
||||||
|
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
|
||||||
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
||||||
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
||||||
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
||||||
@ -794,7 +811,7 @@ static int context_show_version(Context *c, const char *version) {
|
|||||||
if (!sd_json_format_enabled(arg_json_format_flags)) {
|
if (!sd_json_format_enabled(arg_json_format_flags)) {
|
||||||
printf("%s%s%s Version: %s\n"
|
printf("%s%s%s Version: %s\n"
|
||||||
" State: %s%s%s\n"
|
" State: %s%s%s\n"
|
||||||
"Installed: %s%s%s%s%s\n"
|
"Installed: %s%s%s%s%s%s%s\n"
|
||||||
"Available: %s%s\n"
|
"Available: %s%s\n"
|
||||||
"Protected: %s%s%s\n"
|
"Protected: %s%s%s\n"
|
||||||
" Obsolete: %s%s%s\n",
|
" Obsolete: %s%s%s\n",
|
||||||
@ -802,6 +819,7 @@ static int context_show_version(Context *c, const char *version) {
|
|||||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
||||||
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
||||||
FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
|
FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
|
||||||
|
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PENDING) ? " (pending)" : "", FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PARTIAL) ? " (partial)" : "",
|
||||||
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
||||||
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
||||||
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
||||||
@ -827,6 +845,8 @@ static int context_show_version(Context *c, const char *version) {
|
|||||||
SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("newest", FLAGS_SET(us->flags, UPDATE_NEWEST)),
|
||||||
SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("available", FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
|
||||||
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
||||||
|
SD_JSON_BUILD_PAIR_BOOLEAN("partial", FLAGS_SET(us->flags, UPDATE_PARTIAL)),
|
||||||
|
SD_JSON_BUILD_PAIR_BOOLEAN("pending", FLAGS_SET(us->flags, UPDATE_PENDING)),
|
||||||
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
|
||||||
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
|
||||||
SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
|
SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
|
||||||
@ -982,10 +1002,11 @@ static int context_on_acquire_progress(const Transfer *t, const Instance *inst,
|
|||||||
overall, n - i, i, inst->metadata.version, overall);
|
overall, n - i, i, inst->metadata.version, overall);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int context_apply(
|
static int context_process_partial_and_pending(Context *c, const char *version);
|
||||||
|
|
||||||
|
static int context_acquire(
|
||||||
Context *c,
|
Context *c,
|
||||||
const char *version,
|
const char *version) {
|
||||||
UpdateSet **ret_applied) {
|
|
||||||
|
|
||||||
UpdateSet *us = NULL;
|
UpdateSet *us = NULL;
|
||||||
int r;
|
int r;
|
||||||
@ -1000,9 +1021,6 @@ static int context_apply(
|
|||||||
if (!c->candidate) {
|
if (!c->candidate) {
|
||||||
log_info("No update needed.");
|
log_info("No update needed.");
|
||||||
|
|
||||||
if (ret_applied)
|
|
||||||
*ret_applied = NULL;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,11 +1029,16 @@ static int context_apply(
|
|||||||
|
|
||||||
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
|
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
|
||||||
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
|
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
|
||||||
else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
|
else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
|
||||||
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
|
log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
|
||||||
|
|
||||||
if (ret_applied)
|
return 0;
|
||||||
*ret_applied = NULL;
|
} else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
|
||||||
|
log_info("Selected update '%s' is already acquired and pending installation.", us->version);
|
||||||
|
|
||||||
|
return context_process_partial_and_pending(c, version);
|
||||||
|
} else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
|
||||||
|
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1075,14 +1098,106 @@ static int context_apply(
|
|||||||
if (arg_sync)
|
if (arg_sync)
|
||||||
sync();
|
sync();
|
||||||
|
|
||||||
(void) sd_notifyf(/* unset_environment= */ false,
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check to see if we have an update set acquired and pending installation. */
|
||||||
|
static int context_process_partial_and_pending(
|
||||||
|
Context *c,
|
||||||
|
const char *version) {
|
||||||
|
|
||||||
|
UpdateSet *us = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
if (version) {
|
||||||
|
us = context_update_set_by_version(c, version);
|
||||||
|
if (!us)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
|
||||||
|
} else {
|
||||||
|
if (!c->candidate) {
|
||||||
|
log_info("No update needed.");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
us = c->candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
|
||||||
|
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
|
||||||
|
else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
|
||||||
|
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
|
||||||
|
if (!FLAGS_SET(us->flags, UPDATE_PENDING))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
|
||||||
|
|
||||||
|
if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
|
||||||
|
|
||||||
|
if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
|
||||||
|
log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
|
||||||
|
if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
|
||||||
|
log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
|
||||||
|
|
||||||
|
log_info("Selected update '%s' for install.", us->version);
|
||||||
|
|
||||||
|
/* There should now be one instance picked for each transfer, and the order is the same */
|
||||||
|
assert(us->n_instances == c->n_transfers);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||||
|
Instance *inst = us->instances[i];
|
||||||
|
Transfer *t = c->transfers[i];
|
||||||
|
|
||||||
|
assert(inst);
|
||||||
|
|
||||||
|
r = transfer_process_partial_and_pending_instance(t, inst);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int context_install(
|
||||||
|
Context *c,
|
||||||
|
const char *version,
|
||||||
|
UpdateSet **ret_applied) {
|
||||||
|
|
||||||
|
UpdateSet *us = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
if (version) {
|
||||||
|
us = context_update_set_by_version(c, version);
|
||||||
|
if (!us)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
|
||||||
|
} else {
|
||||||
|
if (!c->candidate) {
|
||||||
|
log_info("No update needed.");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
us = c->candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) sd_notifyf(/* unset_environment=*/ false,
|
||||||
"STATUS=Installing '%s'.", us->version);
|
"STATUS=Installing '%s'.", us->version);
|
||||||
|
|
||||||
for (size_t i = 0; i < c->n_transfers; i++) {
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
||||||
Instance *inst = us->instances[i];
|
Instance *inst = us->instances[i];
|
||||||
Transfer *t = c->transfers[i];
|
Transfer *t = c->transfers[i];
|
||||||
|
|
||||||
if (inst->resource == &t->target)
|
if (inst->resource == &t->target &&
|
||||||
|
!inst->is_pending)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
r = transfer_install_instance(t, inst, arg_root);
|
r = transfer_install_instance(t, inst, arg_root);
|
||||||
@ -1173,13 +1288,16 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
|||||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||||
_cleanup_strv_free_ char **versions = NULL;
|
_cleanup_strv_free_ char **versions = NULL;
|
||||||
const char *current = NULL;
|
const char *current = NULL;
|
||||||
|
bool current_is_pending = false;
|
||||||
|
|
||||||
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
|
FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) {
|
||||||
UpdateSet *us = *update_set;
|
UpdateSet *us = *update_set;
|
||||||
|
|
||||||
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
|
if (FLAGS_SET(us->flags, UPDATE_INSTALLED) &&
|
||||||
FLAGS_SET(us->flags, UPDATE_NEWEST))
|
FLAGS_SET(us->flags, UPDATE_NEWEST)) {
|
||||||
current = us->version;
|
current = us->version;
|
||||||
|
current_is_pending = FLAGS_SET(us->flags, UPDATE_PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
r = strv_extend(&versions, us->version);
|
r = strv_extend(&versions, us->version);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@ -1197,7 +1315,7 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
|||||||
return log_oom();
|
return log_oom();
|
||||||
}
|
}
|
||||||
|
|
||||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("current", current),
|
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING(current_is_pending ? "current+pending" : "current", current),
|
||||||
SD_JSON_BUILD_PAIR_STRV("all", versions),
|
SD_JSON_BUILD_PAIR_STRV("all", versions),
|
||||||
SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
|
SD_JSON_BUILD_PAIR_STRV("appstreamUrls", appstream_urls));
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@ -1409,7 +1527,12 @@ static int verb_vacuum(int argc, char **argv, void *userdata) {
|
|||||||
return context_vacuum(context, 0, NULL);
|
return context_vacuum(context, 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int verb_update(int argc, char **argv, void *userdata) {
|
typedef enum {
|
||||||
|
UPDATE_ACTION_ACQUIRE = 1 << 0,
|
||||||
|
UPDATE_ACTION_INSTALL = 1 << 1,
|
||||||
|
} UpdateActionFlags;
|
||||||
|
|
||||||
|
static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
|
||||||
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
||||||
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
||||||
_cleanup_(context_freep) Context* context = NULL;
|
_cleanup_(context_freep) Context* context = NULL;
|
||||||
@ -1443,7 +1566,15 @@ static int verb_update(int argc, char **argv, void *userdata) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
r = context_apply(context, version, &applied);
|
if (action_flags & UPDATE_ACTION_ACQUIRE)
|
||||||
|
r = context_acquire(context, version);
|
||||||
|
else
|
||||||
|
r = context_process_partial_and_pending(context, version);
|
||||||
|
if (r < 0)
|
||||||
|
return r; /* error */
|
||||||
|
|
||||||
|
if (action_flags & UPDATE_ACTION_INSTALL && r > 0) /* update needed */
|
||||||
|
r = context_install(context, version, &applied);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
@ -1468,6 +1599,19 @@ static int verb_update(int argc, char **argv, void *userdata) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int verb_update(int argc, char **argv, void *userdata) {
|
||||||
|
UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
|
||||||
|
|
||||||
|
if (!arg_offline)
|
||||||
|
flags |= UPDATE_ACTION_ACQUIRE;
|
||||||
|
|
||||||
|
return verb_update_impl(argc, argv, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int verb_acquire(int argc, char **argv, void *userdata) {
|
||||||
|
return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
|
||||||
|
}
|
||||||
|
|
||||||
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
|
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
|
||||||
_cleanup_(context_freep) Context* context = NULL;
|
_cleanup_(context_freep) Context* context = NULL;
|
||||||
_cleanup_free_ char *booted_version = NULL;
|
_cleanup_free_ char *booted_version = NULL;
|
||||||
@ -1647,6 +1791,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
|
|||||||
" features [FEATURE] Show optional features\n"
|
" features [FEATURE] Show optional features\n"
|
||||||
" check-new Check if there's a new version available\n"
|
" check-new Check if there's a new version available\n"
|
||||||
" update [VERSION] Install new version now\n"
|
" update [VERSION] Install new version now\n"
|
||||||
|
" acquire [VERSION] Acquire (download) new version now\n"
|
||||||
" vacuum Make room, by deleting old versions\n"
|
" vacuum Make room, by deleting old versions\n"
|
||||||
" pending Report whether a newer version is installed than\n"
|
" pending Report whether a newer version is installed than\n"
|
||||||
" currently booted\n"
|
" currently booted\n"
|
||||||
@ -1860,6 +2005,7 @@ static int sysupdate_main(int argc, char *argv[]) {
|
|||||||
{ "features", VERB_ANY, 2, 0, verb_features },
|
{ "features", VERB_ANY, 2, 0, verb_features },
|
||||||
{ "check-new", VERB_ANY, 1, 0, verb_check_new },
|
{ "check-new", VERB_ANY, 1, 0, verb_check_new },
|
||||||
{ "update", VERB_ANY, 2, 0, verb_update },
|
{ "update", VERB_ANY, 2, 0, verb_update },
|
||||||
|
{ "acquire", VERB_ANY, 2, 0, verb_acquire },
|
||||||
{ "vacuum", VERB_ANY, 1, 0, verb_vacuum },
|
{ "vacuum", VERB_ANY, 1, 0, verb_vacuum },
|
||||||
{ "reboot", 1, 1, 0, verb_pending_or_reboot },
|
{ "reboot", 1, 1, 0, verb_pending_or_reboot },
|
||||||
{ "pending", 1, 1, 0, verb_pending_or_reboot },
|
{ "pending", 1, 1, 0, verb_pending_or_reboot },
|
||||||
|
|||||||
@ -92,11 +92,27 @@ new_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_now() {
|
update_now() {
|
||||||
|
local update_type="${1:?}"
|
||||||
|
|
||||||
# Update to newest version. First there should be an update ready, then we
|
# Update to newest version. First there should be an update ready, then we
|
||||||
# do the update, and then there should not be any ready anymore
|
# do the update, and then there should not be any ready anymore
|
||||||
|
#
|
||||||
|
# The update can either be done monolithically (by calling the `update`
|
||||||
|
# verb) or split (`acquire` then `update`). Both options are allowed for
|
||||||
|
# most updates in the test suite, so the test suite can be run to test both
|
||||||
|
# modes. Some updates in the test suite need to be monolithic (e.g. when
|
||||||
|
# repairing an installation), so that can be overridden via the local.
|
||||||
|
|
||||||
"$SYSUPDATE" --verify=no check-new
|
"$SYSUPDATE" --verify=no check-new
|
||||||
"$SYSUPDATE" --verify=no update
|
if [[ "$update_type" == "monolithic" ]]; then
|
||||||
|
"$SYSUPDATE" --verify=no update
|
||||||
|
elif [[ "$update_type" == "split-offline" ]]; then
|
||||||
|
"$SYSUPDATE" --verify=no acquire
|
||||||
|
"$SYSUPDATE" --verify=no update --offline
|
||||||
|
elif [[ "$update_type" == "split" ]]; then
|
||||||
|
"$SYSUPDATE" --verify=no acquire
|
||||||
|
"$SYSUPDATE" --verify=no update
|
||||||
|
fi
|
||||||
(! "$SYSUPDATE" --verify=no check-new)
|
(! "$SYSUPDATE" --verify=no check-new)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +151,7 @@ verify_version_current() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for sector_size in "${SECTOR_SIZES[@]}"; do
|
for sector_size in "${SECTOR_SIZES[@]}"; do
|
||||||
|
for update_type in monolithic split-offline split; do
|
||||||
# Disk size of:
|
# Disk size of:
|
||||||
# - 1MB for GPT
|
# - 1MB for GPT
|
||||||
# - 4 partitions of 2048 sectors each
|
# - 4 partitions of 2048 sectors each
|
||||||
@ -267,18 +284,18 @@ EOF
|
|||||||
|
|
||||||
# Install initial version and verify
|
# Install initial version and verify
|
||||||
new_version "$sector_size" v1
|
new_version "$sector_size" v1
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version_current "$blockdev" "$sector_size" v1 1
|
verify_version_current "$blockdev" "$sector_size" v1 1
|
||||||
|
|
||||||
# Create second version, update and verify that it is added
|
# Create second version, update and verify that it is added
|
||||||
new_version "$sector_size" v2
|
new_version "$sector_size" v2
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version "$blockdev" "$sector_size" v1 1
|
verify_version "$blockdev" "$sector_size" v1 1
|
||||||
verify_version_current "$blockdev" "$sector_size" v2 2
|
verify_version_current "$blockdev" "$sector_size" v2 2
|
||||||
|
|
||||||
# Create third version, update and verify it replaced the first version
|
# Create third version, update and verify it replaced the first version
|
||||||
new_version "$sector_size" v3
|
new_version "$sector_size" v3
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version_current "$blockdev" "$sector_size" v3 1
|
verify_version_current "$blockdev" "$sector_size" v3 1
|
||||||
verify_version "$blockdev" "$sector_size" v2 2
|
verify_version "$blockdev" "$sector_size" v2 2
|
||||||
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi"
|
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi"
|
||||||
@ -295,16 +312,17 @@ EOF
|
|||||||
# Create a fifth version, that's complete on the server side. We should
|
# Create a fifth version, that's complete on the server side. We should
|
||||||
# completely skip the incomplete v4 and install v5 instead.
|
# completely skip the incomplete v4 and install v5 instead.
|
||||||
new_version "$sector_size" v5
|
new_version "$sector_size" v5
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version "$blockdev" "$sector_size" v3 1
|
verify_version "$blockdev" "$sector_size" v3 1
|
||||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||||
|
|
||||||
# Make the local installation of v5 incomplete by deleting a file, then make
|
# Make the local installation of v5 incomplete by deleting a file, then make
|
||||||
# sure that sysupdate still recognizes the installation and can complete it
|
# sure that sysupdate still recognizes the installation and can complete it
|
||||||
# in place
|
# in place
|
||||||
|
# Always do this as a monolithic update for the repair to work.
|
||||||
rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d"
|
rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d"
|
||||||
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
|
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
|
||||||
update_now
|
update_now "monolithic"
|
||||||
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
|
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
|
||||||
verify_version "$blockdev" "$sector_size" v3 1
|
verify_version "$blockdev" "$sector_size" v3 1
|
||||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||||
@ -316,7 +334,7 @@ EOF
|
|||||||
mkdir "$CONFIGDIR/optional.feature.d"
|
mkdir "$CONFIGDIR/optional.feature.d"
|
||||||
echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf"
|
echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf"
|
||||||
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
|
"$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null
|
||||||
update_now
|
update_now "monolithic"
|
||||||
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
|
"$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null
|
||||||
verify_version "$blockdev" "$sector_size" v3 1
|
verify_version "$blockdev" "$sector_size" v3 1
|
||||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||||
@ -340,7 +358,7 @@ EOF
|
|||||||
updatectl update
|
updatectl update
|
||||||
else
|
else
|
||||||
# If no updatectl, gracefully fall back to systemd-sysupdate
|
# If no updatectl, gracefully fall back to systemd-sysupdate
|
||||||
update_now
|
update_now "$update_type"
|
||||||
fi
|
fi
|
||||||
# User-facing updatectl returns 0 if there's no updates, so use the low-level
|
# User-facing updatectl returns 0 if there's no updates, so use the low-level
|
||||||
# utility to make sure we did upgrade
|
# utility to make sure we did upgrade
|
||||||
@ -397,7 +415,7 @@ MatchPattern=dir-@v
|
|||||||
InstancesMax=3
|
InstancesMax=3
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version "$blockdev" "$sector_size" v6 1
|
verify_version "$blockdev" "$sector_size" v6 1
|
||||||
verify_version_current "$blockdev" "$sector_size" v7 2
|
verify_version_current "$blockdev" "$sector_size" v7 2
|
||||||
|
|
||||||
@ -420,7 +438,7 @@ EOF
|
|||||||
# (what .transfer files were called before v257)
|
# (what .transfer files were called before v257)
|
||||||
for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done
|
for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done
|
||||||
new_version "$sector_size" v8
|
new_version "$sector_size" v8
|
||||||
update_now
|
update_now "$update_type"
|
||||||
verify_version_current "$blockdev" "$sector_size" v8 1
|
verify_version_current "$blockdev" "$sector_size" v8 1
|
||||||
verify_version "$blockdev" "$sector_size" v7 2
|
verify_version "$blockdev" "$sector_size" v7 2
|
||||||
|
|
||||||
@ -428,5 +446,6 @@ EOF
|
|||||||
[[ -b "$blockdev" ]] && losetup --detach "$blockdev"
|
[[ -b "$blockdev" ]] && losetup --detach "$blockdev"
|
||||||
rm "$BACKING_FILE"
|
rm "$BACKING_FILE"
|
||||||
done
|
done
|
||||||
|
done
|
||||||
|
|
||||||
touch /testok
|
touch /testok
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user