sysupdate: Allow patterns to match path with directories

`MatchPattern` for regular-file and directory as target can now match
subdirectories This is useful to install files for examples in `.extra.d`
directories:

```
[Target]
Type=regular-file
Path=/EFI/Linux
PathRelativeTo=boot
MatchPattern=gnomeos_@v.efi.extra.d/apparmor.addon.efi
```

The if the directories in the path do not exist, they will be created.  Whereas
the part in `Path` is not created.
This commit is contained in:
Valentin David
2023-09-24 14:35:59 +02:00
parent 18c3ffbfcc
commit 8b051623cd
5 changed files with 141 additions and 35 deletions

View File

@@ -23,6 +23,7 @@ typedef enum PatternElementType {
PATTERN_READ_ONLY,
PATTERN_GROWFS,
PATTERN_SHA256SUM,
PATTERN_SLASH,
_PATTERN_ELEMENT_TYPE_MAX,
_PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
} PatternElementType;
@@ -82,7 +83,7 @@ static bool valid_char(char x) {
if ((unsigned) x < ' ' || x >= 127)
return false;
return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '/', '|');
return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '|');
}
static int pattern_split(
@@ -90,7 +91,7 @@ static int pattern_split(
PatternElement **ret) {
_cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
bool at = false, last_literal = true;
bool at = false, last_literal = true, last_slash = false;
PatternElement *last = NULL;
uint64_t mask_found = 0;
size_t l, k = 0;
@@ -123,7 +124,7 @@ static int pattern_split(
/* We insist that two pattern field markers are separated by some literal string that
* we can use to separate the fields when parsing. */
if (!last_literal)
if (!last_literal && !last_slash)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
if (ret) {
@@ -139,10 +140,28 @@ static int pattern_split(
}
mask_found |= bit;
last_literal = at = false;
last_slash = last_literal = at = false;
continue;
}
if (*e == '/') {
if (ret) {
PatternElement *z;
z = malloc(offsetof(PatternElement, literal));
if (!z)
return -ENOMEM;
z->type = PATTERN_SLASH;
LIST_INSERT_AFTER(elements, first, last, z);
last = z;
}
last_literal = false;
last_slash = true;
continue ;
}
if (!valid_char(*e))
return log_debug_errno(
SYNTHETIC_ERRNO(EBADRQC),
@@ -150,6 +169,7 @@ static int pattern_split(
(unsigned) *e);
last_literal = true;
last_slash = false;
if (!ret)
continue;
@@ -203,6 +223,16 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
_cleanup_free_ char *t = NULL;
const char *n;
if (e->type == PATTERN_SLASH) {
if (*p == '/') {
++p;
continue;
} else if (*p == '\0')
goto retry;
else
goto nope;
}
if (e->type == PATTERN_LITERAL) {
const char *k;
@@ -399,13 +429,19 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
}
return true;
return PATTERN_MATCH_YES;
nope:
if (ret)
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
return false;
return PATTERN_MATCH_NO;
retry:
if (ret)
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
return PATTERN_MATCH_RETRY;
}
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
@@ -422,14 +458,14 @@ int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
}
return true;
return r;
}
}
if (ret)
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
return false;
return PATTERN_MATCH_NO;
}
int pattern_valid(const char *pattern) {
@@ -465,6 +501,12 @@ int pattern_format(
switch (e->type) {
case PATTERN_SLASH:
if (!strextend(&j, "/"))
return -ENOMEM;
break;
case PATTERN_LITERAL:
if (!strextend(&j, e->literal))
return -ENOMEM;

View File

@@ -6,6 +6,12 @@
#include "sysupdate-instance.h"
#include "time-util.h"
enum {
PATTERN_MATCH_NO,
PATTERN_MATCH_YES,
PATTERN_MATCH_RETRY,
};
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret);
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret);
int pattern_valid(const char *pattern);

View File

@@ -69,30 +69,16 @@ static int resource_add_instance(
return 0;
}
static int resource_load_from_directory(
static int resource_load_from_directory_recursive(
Resource *rr,
DIR* d,
const char* relpath,
mode_t m) {
_cleanup_closedir_ DIR *d = NULL;
int r;
assert(rr);
assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
assert(IN_SET(m, S_IFREG, S_IFDIR));
d = opendir(rr->path);
if (!d) {
if (errno == ENOENT) {
log_debug("Directory %s does not exist, not loading any resources.", rr->path);
return 0;
}
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
}
for (;;) {
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
_cleanup_free_ char *joined = NULL;
_cleanup_free_ char *joined = NULL, *rel_joined = NULL;
Instance *instance;
struct dirent *de;
struct stat st;
@@ -111,7 +97,7 @@ static int resource_load_from_directory(
break;
case DT_DIR:
if (m != S_IFDIR)
if (!IN_SET(m, S_IFDIR, S_IFREG))
continue;
break;
@@ -132,16 +118,36 @@ static int resource_load_from_directory(
return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name);
}
if ((st.st_mode & S_IFMT) != m)
if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
continue;
r = pattern_match_many(rr->patterns, de->d_name, &extracted_fields);
if (r < 0)
rel_joined = path_join(relpath, de->d_name);
if (!rel_joined)
return log_oom();
r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
if (r == PATTERN_MATCH_RETRY) {
_cleanup_closedir_ DIR *subdir = NULL;
subdir = xopendirat(dirfd(d), rel_joined, 0);
if (!subdir)
continue;
r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
if (r < 0)
return r;
if (r == 0)
continue;
}
else if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r == 0)
else if (r == PATTERN_MATCH_NO)
continue;
joined = path_join(rr->path, de->d_name);
if (de->d_type == DT_DIR && m != S_IFDIR)
continue;
joined = path_join(rr->path, rel_joined);
if (!joined)
return log_oom();
@@ -160,6 +166,28 @@ static int resource_load_from_directory(
return 0;
}
static int resource_load_from_directory(
Resource *rr,
mode_t m) {
_cleanup_closedir_ DIR *d = NULL;
assert(rr);
assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
assert(IN_SET(m, S_IFREG, S_IFDIR));
d = opendir(rr->path);
if (!d) {
if (errno == ENOENT) {
log_debug_errno(errno, "Directory %s does not exist, not loading any resources: %m", rr->path);
return 0;
}
return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
}
return resource_load_from_directory_recursive(rr, d, NULL, m);
}
static int resource_load_from_blockdev(Resource *rr) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
@@ -204,7 +232,7 @@ static int resource_load_from_blockdev(Resource *rr) {
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r == 0)
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
continue;
r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
@@ -402,7 +430,7 @@ static int resource_load_from_web(
r = pattern_match_many(rr->patterns, fn, &extracted_fields);
if (r < 0)
return log_error_errno(r, "Failed to match pattern: %m");
if (r > 0) {
if (r == PATTERN_MATCH_YES) {
_cleanup_free_ char *path = NULL;
r = import_url_append_component(rr->path, fn, &path);

View File

@@ -12,6 +12,7 @@
#include "gpt.h"
#include "hexdecoct.h"
#include "install-file.h"
#include "mkdir.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "process-util.h"
@@ -713,6 +714,8 @@ int transfer_vacuum(
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: {
@@ -836,13 +839,17 @@ int transfer_acquire_instance(Transfer *t, Instance *i) {
if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
if (!filename_is_valid(formatted_pattern))
if (!path_is_valid_full(formatted_pattern, /* accept_dot_dot = */ false))
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();
r = mkdir_parents(t->final_path, 0755);
if (r < 0)
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");

View File

@@ -50,6 +50,9 @@ new_version() {
# Create a random "UKI" payload
echo $RANDOM >"/var/tmp/72-source/uki-$2.efi"
# Create a random extra payload
echo $RANDOM >"/var/tmp/72-source/uki-extra-$2.efi"
# Create tarball of a directory
mkdir -p "/var/tmp/72-source/dir-$2"
echo $RANDOM >"/var/tmp/72-source/dir-$2/foo.txt"
@@ -88,6 +91,9 @@ verify_version() {
cmp "/var/tmp/72-source/uki-$3.efi" "/var/tmp/72-xbootldr/EFI/Linux/uki_$3+3-0.efi"
test -z "$(ls -A /var/tmp/72-esp/EFI/Linux)"
# Check the extra efi
cmp "/var/tmp/72-source/uki-extra-$3.efi" "/var/tmp/72-xbootldr/EFI/Linux/uki_$3.efi.extra.d/extra.addon.efi"
# Check the directories
cmp "/var/tmp/72-source/dir-$3/foo.txt" /var/tmp/72-dirs/current/foo.txt
cmp "/var/tmp/72-source/dir-$3/bar.txt" /var/tmp/72-dirs/current/bar.txt
@@ -183,6 +189,21 @@ Mode=0444
TriesLeft=3
TriesDone=0
InstancesMax=2
EOF
cat >/var/tmp/72-defs/05-fifth.conf <<EOF
[Source]
Type=regular-file
Path=/var/tmp/72-source
MatchPattern=uki-extra-@v.efi
[Target]
Type=regular-file
Path=/EFI/Linux
PathRelativeTo=boot
MatchPattern=uki_@v.efi.extra.d/extra.addon.efi
Mode=0444
InstancesMax=2
EOF
rm -rf /var/tmp/72-esp /var/tmp/72-xbootldr
@@ -206,6 +227,8 @@ EOF
update_now
verify_version "$blockdev" "$sector_size" v3 1 3
test ! -f "/var/tmp/72-xbootldr/EFI/Linux/uki_v1+3-0.efi"
test ! -f "/var/tmp/72-xbootldr/EFI/Linux/uki_v1.efi.extra.d/extra.addon.efi"
test ! -d "/var/tmp/72-xbootldr/EFI/Linux/uki_v1.efi.extra.d"
# Create fourth version, and update through a file:// URL. This should be
# almost as good as testing HTTP, but is simpler for us to set up. file:// is