mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Unfortunately, hex output can only be produced with unsigned types. Some cases can be fixed by producing the correct type, but a few simply have to be cast. At least casting makes it explicit.
606 lines
19 KiB
C
606 lines
19 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "alloc-util.h"
|
|
#include "hexdecoct.h"
|
|
#include "list.h"
|
|
#include "parse-util.h"
|
|
#include "path-util.h"
|
|
#include "stdio-util.h"
|
|
#include "string-util.h"
|
|
#include "sysupdate-pattern.h"
|
|
#include "sysupdate-util.h"
|
|
|
|
typedef enum PatternElementType {
|
|
PATTERN_LITERAL,
|
|
PATTERN_VERSION,
|
|
PATTERN_PARTITION_UUID,
|
|
PATTERN_PARTITION_FLAGS,
|
|
PATTERN_MTIME,
|
|
PATTERN_MODE,
|
|
PATTERN_SIZE,
|
|
PATTERN_TRIES_DONE,
|
|
PATTERN_TRIES_LEFT,
|
|
PATTERN_NO_AUTO,
|
|
PATTERN_READ_ONLY,
|
|
PATTERN_GROWFS,
|
|
PATTERN_SHA256SUM,
|
|
_PATTERN_ELEMENT_TYPE_MAX,
|
|
_PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
|
|
} PatternElementType;
|
|
|
|
typedef struct PatternElement PatternElement;
|
|
|
|
struct PatternElement {
|
|
PatternElementType type;
|
|
LIST_FIELDS(PatternElement, elements);
|
|
char literal[];
|
|
};
|
|
|
|
static PatternElement *pattern_element_free_all(PatternElement *e) {
|
|
PatternElement *p;
|
|
|
|
while ((p = LIST_POP(elements, e)))
|
|
free(p);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
|
|
|
|
static PatternElementType pattern_element_type_from_char(char c) {
|
|
switch (c) {
|
|
case 'v':
|
|
return PATTERN_VERSION;
|
|
case 'u':
|
|
return PATTERN_PARTITION_UUID;
|
|
case 'f':
|
|
return PATTERN_PARTITION_FLAGS;
|
|
case 't':
|
|
return PATTERN_MTIME;
|
|
case 'm':
|
|
return PATTERN_MODE;
|
|
case 's':
|
|
return PATTERN_SIZE;
|
|
case 'd':
|
|
return PATTERN_TRIES_DONE;
|
|
case 'l':
|
|
return PATTERN_TRIES_LEFT;
|
|
case 'a':
|
|
return PATTERN_NO_AUTO;
|
|
case 'r':
|
|
return PATTERN_READ_ONLY;
|
|
case 'g':
|
|
return PATTERN_GROWFS;
|
|
case 'h':
|
|
return PATTERN_SHA256SUM;
|
|
default:
|
|
return _PATTERN_ELEMENT_TYPE_INVALID;
|
|
}
|
|
}
|
|
|
|
static bool valid_char(char x) {
|
|
|
|
/* Let's refuse control characters here, and let's reserve some characters typically used in pattern
|
|
* languages so that we can use them later, possibly. */
|
|
|
|
if ((unsigned) x < ' ' || x >= 127)
|
|
return false;
|
|
|
|
return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '/', '|');
|
|
}
|
|
|
|
static int pattern_split(
|
|
const char *pattern,
|
|
PatternElement **ret) {
|
|
|
|
_cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
|
|
bool at = false, last_literal = true;
|
|
PatternElement *last = NULL;
|
|
uint64_t mask_found = 0;
|
|
size_t l, k = 0;
|
|
|
|
assert(pattern);
|
|
|
|
l = strlen(pattern);
|
|
|
|
for (const char *e = pattern; *e != 0; e++) {
|
|
if (*e == '@') {
|
|
if (!at) {
|
|
at = true;
|
|
continue;
|
|
}
|
|
|
|
/* Two at signs in a sequence, write out one */
|
|
at = false;
|
|
|
|
} else if (at) {
|
|
PatternElementType t;
|
|
uint64_t bit;
|
|
|
|
t = pattern_element_type_from_char(*e);
|
|
if (t < 0)
|
|
return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
|
|
|
|
bit = UINT64_C(1) << t;
|
|
if (mask_found & bit)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
|
|
|
|
/* 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)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
|
|
|
|
if (ret) {
|
|
PatternElement *z;
|
|
|
|
z = malloc(offsetof(PatternElement, literal));
|
|
if (!z)
|
|
return -ENOMEM;
|
|
|
|
z->type = t;
|
|
LIST_INSERT_AFTER(elements, first, last, z);
|
|
last = z;
|
|
}
|
|
|
|
mask_found |= bit;
|
|
last_literal = at = false;
|
|
continue;
|
|
}
|
|
|
|
if (!valid_char(*e))
|
|
return log_debug_errno(
|
|
SYNTHETIC_ERRNO(EBADRQC),
|
|
"Invalid character 0x%0x in pattern, refusing.",
|
|
(unsigned) *e);
|
|
|
|
last_literal = true;
|
|
|
|
if (!ret)
|
|
continue;
|
|
|
|
if (!last || last->type != PATTERN_LITERAL) {
|
|
PatternElement *z;
|
|
|
|
z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
|
|
if (!z)
|
|
return -ENOMEM;
|
|
|
|
z->type = PATTERN_LITERAL;
|
|
k = 0;
|
|
|
|
LIST_INSERT_AFTER(elements, first, last, z);
|
|
last = z;
|
|
}
|
|
|
|
assert(last);
|
|
assert(last->type == PATTERN_LITERAL);
|
|
|
|
last->literal[k++] = *e;
|
|
}
|
|
|
|
if (at)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
|
|
if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
|
|
|
|
if (ret)
|
|
*ret = TAKE_PTR(first);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
|
|
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
|
|
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
|
|
const char *p;
|
|
int r;
|
|
|
|
assert(pattern);
|
|
assert(s);
|
|
|
|
r = pattern_split(pattern, &elements);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
p = s;
|
|
LIST_FOREACH(elements, e, elements) {
|
|
_cleanup_free_ char *t = NULL;
|
|
const char *n;
|
|
|
|
if (e->type == PATTERN_LITERAL) {
|
|
const char *k;
|
|
|
|
/* Skip literal fields */
|
|
k = startswith(p, e->literal);
|
|
if (!k)
|
|
goto nope;
|
|
|
|
p = k;
|
|
continue;
|
|
}
|
|
|
|
if (e->elements_next) {
|
|
/* The next element must be literal, as we use it to determine where to split */
|
|
assert(e->elements_next->type == PATTERN_LITERAL);
|
|
|
|
n = strstr(p, e->elements_next->literal);
|
|
if (!n)
|
|
goto nope;
|
|
|
|
} else
|
|
/* End of the string */
|
|
assert_se(n = strchr(p, 0));
|
|
t = strndup(p, n - p);
|
|
if (!t)
|
|
return -ENOMEM;
|
|
|
|
switch (e->type) {
|
|
|
|
case PATTERN_VERSION:
|
|
if (!version_is_valid(t)) {
|
|
log_debug("Version string is not valid, refusing: %s", t);
|
|
goto nope;
|
|
}
|
|
|
|
assert(!found.version);
|
|
found.version = TAKE_PTR(t);
|
|
break;
|
|
|
|
case PATTERN_PARTITION_UUID: {
|
|
sd_id128_t id;
|
|
|
|
if (sd_id128_from_string(t, &id) < 0)
|
|
goto nope;
|
|
|
|
assert(!found.partition_uuid_set);
|
|
found.partition_uuid = id;
|
|
found.partition_uuid_set = true;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_PARTITION_FLAGS: {
|
|
uint64_t f;
|
|
|
|
if (safe_atoux64(t, &f) < 0)
|
|
goto nope;
|
|
|
|
if (found.partition_flags_set && found.partition_flags != f)
|
|
goto nope;
|
|
|
|
assert(!found.partition_flags_set);
|
|
found.partition_flags = f;
|
|
found.partition_flags_set = true;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_MTIME: {
|
|
uint64_t v;
|
|
|
|
if (safe_atou64(t, &v) < 0)
|
|
goto nope;
|
|
if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
|
|
goto nope;
|
|
if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
|
|
goto nope;
|
|
|
|
assert(found.mtime == USEC_INFINITY);
|
|
found.mtime = v;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_MODE: {
|
|
mode_t m;
|
|
|
|
r = parse_mode(t, &m);
|
|
if (r < 0)
|
|
goto nope;
|
|
if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
|
|
goto nope;
|
|
|
|
assert(found.mode == MODE_INVALID);
|
|
found.mode = m;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_SIZE: {
|
|
uint64_t u;
|
|
|
|
r = safe_atou64(t, &u);
|
|
if (r < 0)
|
|
goto nope;
|
|
if (u == UINT64_MAX)
|
|
goto nope;
|
|
|
|
assert(found.size == UINT64_MAX);
|
|
found.size = u;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_TRIES_DONE: {
|
|
uint64_t u;
|
|
|
|
r = safe_atou64(t, &u);
|
|
if (r < 0)
|
|
goto nope;
|
|
if (u == UINT64_MAX)
|
|
goto nope;
|
|
|
|
assert(found.tries_done == UINT64_MAX);
|
|
found.tries_done = u;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_TRIES_LEFT: {
|
|
uint64_t u;
|
|
|
|
r = safe_atou64(t, &u);
|
|
if (r < 0)
|
|
goto nope;
|
|
if (u == UINT64_MAX)
|
|
goto nope;
|
|
|
|
assert(found.tries_left == UINT64_MAX);
|
|
found.tries_left = u;
|
|
break;
|
|
}
|
|
|
|
case PATTERN_NO_AUTO:
|
|
r = parse_boolean(t);
|
|
if (r < 0)
|
|
goto nope;
|
|
|
|
assert(found.no_auto < 0);
|
|
found.no_auto = r;
|
|
break;
|
|
|
|
case PATTERN_READ_ONLY:
|
|
r = parse_boolean(t);
|
|
if (r < 0)
|
|
goto nope;
|
|
|
|
assert(found.read_only < 0);
|
|
found.read_only = r;
|
|
break;
|
|
|
|
case PATTERN_GROWFS:
|
|
r = parse_boolean(t);
|
|
if (r < 0)
|
|
goto nope;
|
|
|
|
assert(found.growfs < 0);
|
|
found.growfs = r;
|
|
break;
|
|
|
|
case PATTERN_SHA256SUM: {
|
|
_cleanup_free_ void *d = NULL;
|
|
size_t l;
|
|
|
|
if (strlen(t) != sizeof(found.sha256sum) * 2)
|
|
goto nope;
|
|
|
|
r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
|
|
if (r == -ENOMEM)
|
|
return r;
|
|
if (r < 0)
|
|
goto nope;
|
|
|
|
assert(!found.sha256sum_set);
|
|
assert(l == sizeof(found.sha256sum));
|
|
memcpy(found.sha256sum, d, l);
|
|
found.sha256sum_set = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert_se("unexpected pattern element");
|
|
}
|
|
|
|
p = n;
|
|
}
|
|
|
|
if (ret) {
|
|
*ret = found;
|
|
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
|
}
|
|
|
|
return true;
|
|
|
|
nope:
|
|
if (ret)
|
|
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
|
|
_cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
|
|
int r;
|
|
|
|
STRV_FOREACH(p, patterns) {
|
|
r = pattern_match(*p, s, &found);
|
|
if (r < 0)
|
|
return r;
|
|
if (r > 0) {
|
|
if (ret) {
|
|
*ret = found;
|
|
found = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
*ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
int pattern_valid(const char *pattern) {
|
|
int r;
|
|
|
|
r = pattern_split(pattern, NULL);
|
|
if (r == -EINVAL)
|
|
return false;
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return true;
|
|
}
|
|
|
|
int pattern_format(
|
|
const char *pattern,
|
|
const InstanceMetadata *fields,
|
|
char **ret) {
|
|
|
|
_cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
|
|
_cleanup_free_ char *j = NULL;
|
|
int r;
|
|
|
|
assert(pattern);
|
|
assert(fields);
|
|
assert(ret);
|
|
|
|
r = pattern_split(pattern, &elements);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
LIST_FOREACH(elements, e, elements) {
|
|
|
|
switch (e->type) {
|
|
|
|
case PATTERN_LITERAL:
|
|
if (!strextend(&j, e->literal))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
|
|
case PATTERN_VERSION:
|
|
if (!fields->version)
|
|
return -ENXIO;
|
|
|
|
if (!strextend(&j, fields->version))
|
|
return -ENOMEM;
|
|
break;
|
|
|
|
case PATTERN_PARTITION_UUID: {
|
|
char formatted[SD_ID128_STRING_MAX];
|
|
|
|
if (!fields->partition_uuid_set)
|
|
return -ENXIO;
|
|
|
|
if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
}
|
|
|
|
case PATTERN_PARTITION_FLAGS:
|
|
if (!fields->partition_flags_set)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%" PRIx64, fields->partition_flags);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
break;
|
|
|
|
case PATTERN_MTIME:
|
|
if (fields->mtime == USEC_INFINITY)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%" PRIu64, fields->mtime);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
break;
|
|
|
|
case PATTERN_MODE:
|
|
if (fields->mode == MODE_INVALID)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%03o", fields->mode);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
break;
|
|
|
|
case PATTERN_SIZE:
|
|
if (fields->size == UINT64_MAX)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%" PRIu64, fields->size);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case PATTERN_TRIES_DONE:
|
|
if (fields->tries_done == UINT64_MAX)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%" PRIu64, fields->tries_done);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case PATTERN_TRIES_LEFT:
|
|
if (fields->tries_left == UINT64_MAX)
|
|
return -ENXIO;
|
|
|
|
r = strextendf(&j, "%" PRIu64, fields->tries_left);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case PATTERN_NO_AUTO:
|
|
if (fields->no_auto < 0)
|
|
return -ENXIO;
|
|
|
|
if (!strextend(&j, one_zero(fields->no_auto)))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
|
|
case PATTERN_READ_ONLY:
|
|
if (fields->read_only < 0)
|
|
return -ENXIO;
|
|
|
|
if (!strextend(&j, one_zero(fields->read_only)))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
|
|
case PATTERN_GROWFS:
|
|
if (fields->growfs < 0)
|
|
return -ENXIO;
|
|
|
|
if (!strextend(&j, one_zero(fields->growfs)))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
|
|
case PATTERN_SHA256SUM: {
|
|
_cleanup_free_ char *h = NULL;
|
|
|
|
if (!fields->sha256sum_set)
|
|
return -ENXIO;
|
|
|
|
h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
|
|
if (!h)
|
|
return -ENOMEM;
|
|
|
|
if (!strextend(&j, h))
|
|
return -ENOMEM;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
}
|
|
|
|
*ret = TAKE_PTR(j);
|
|
return 0;
|
|
}
|