summaryrefslogtreecommitdiffstats
path: root/src/sysupdate/sysupdate-pattern.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sysupdate/sysupdate-pattern.c')
-rw-r--r--src/sysupdate/sysupdate-pattern.c605
1 files changed, 605 insertions, 0 deletions
diff --git a/src/sysupdate/sysupdate-pattern.c b/src/sysupdate/sysupdate-pattern.c
new file mode 100644
index 0000000..6d9c8d8
--- /dev/null
+++ b/src/sysupdate/sysupdate-pattern.c
@@ -0,0 +1,605 @@
+/* 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;
+}