summaryrefslogtreecommitdiffstats
path: root/src/basic/uid-range.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic/uid-range.c')
-rw-r--r--src/basic/uid-range.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c
new file mode 100644
index 0000000..8463599
--- /dev/null
+++ b/src/basic/uid-range.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "format-util.h"
+#include "macro.h"
+#include "path-util.h"
+#include "sort-util.h"
+#include "stat-util.h"
+#include "uid-range.h"
+#include "user-util.h"
+
+UidRange *uid_range_free(UidRange *range) {
+ if (!range)
+ return NULL;
+
+ free(range->entries);
+ return mfree(range);
+}
+
+static bool uid_range_entry_intersect(const UidRangeEntry *a, const UidRangeEntry *b) {
+ assert(a);
+ assert(b);
+
+ return a->start <= b->start + b->nr && a->start + a->nr >= b->start;
+}
+
+static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = CMP(a->start, b->start);
+ if (r != 0)
+ return r;
+
+ return CMP(a->nr, b->nr);
+}
+
+static void uid_range_coalesce(UidRange *range) {
+ assert(range);
+
+ if (range->n_entries <= 0)
+ return;
+
+ typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare);
+
+ for (size_t i = 0; i < range->n_entries; i++) {
+ UidRangeEntry *x = range->entries + i;
+
+ for (size_t j = i + 1; j < range->n_entries; j++) {
+ UidRangeEntry *y = range->entries + j;
+ uid_t begin, end;
+
+ if (!uid_range_entry_intersect(x, y))
+ break;
+
+ begin = MIN(x->start, y->start);
+ end = MAX(x->start + x->nr, y->start + y->nr);
+
+ x->start = begin;
+ x->nr = end - begin;
+
+ if (range->n_entries > j + 1)
+ memmove(y, y + 1, sizeof(UidRangeEntry) * (range->n_entries - j - 1));
+
+ range->n_entries--;
+ j--;
+ }
+ }
+}
+
+int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce) {
+ _cleanup_(uid_range_freep) UidRange *range_new = NULL;
+ UidRange *p;
+
+ assert(range);
+
+ if (nr <= 0)
+ return 0;
+
+ if (start > UINT32_MAX - nr) /* overflow check */
+ return -ERANGE;
+
+ if (*range)
+ p = *range;
+ else {
+ range_new = new0(UidRange, 1);
+ if (!range_new)
+ return -ENOMEM;
+
+ p = range_new;
+ }
+
+ if (!GREEDY_REALLOC(p->entries, p->n_entries + 1))
+ return -ENOMEM;
+
+ p->entries[p->n_entries++] = (UidRangeEntry) {
+ .start = start,
+ .nr = nr,
+ };
+
+ if (coalesce)
+ uid_range_coalesce(p);
+
+ TAKE_PTR(range_new);
+ *range = p;
+
+ return 0;
+}
+
+int uid_range_add_str(UidRange **range, const char *s) {
+ uid_t start, end;
+ int r;
+
+ assert(range);
+ assert(s);
+
+ r = parse_uid_range(s, &start, &end);
+ if (r < 0)
+ return r;
+
+ return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true);
+}
+
+int uid_range_next_lower(const UidRange *range, uid_t *uid) {
+ uid_t closest = UID_INVALID, candidate;
+
+ assert(range);
+ assert(uid);
+
+ if (*uid == 0)
+ return -EBUSY;
+
+ candidate = *uid - 1;
+
+ for (size_t i = 0; i < range->n_entries; i++) {
+ uid_t begin, end;
+
+ begin = range->entries[i].start;
+ end = range->entries[i].start + range->entries[i].nr - 1;
+
+ if (candidate >= begin && candidate <= end) {
+ *uid = candidate;
+ return 1;
+ }
+
+ if (end < candidate)
+ closest = end;
+ }
+
+ if (closest == UID_INVALID)
+ return -EBUSY;
+
+ *uid = closest;
+ return 1;
+}
+
+bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) {
+ if (nr == 0) /* empty range? always covered... */
+ return true;
+
+ if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */
+ return false;
+
+ if (!range)
+ return false;
+
+ for (size_t i = 0; i < range->n_entries; i++)
+ if (start >= range->entries[i].start &&
+ start + nr <= range->entries[i].start + range->entries[i].nr)
+ return true;
+
+ return false;
+}
+
+int uid_range_load_userns(UidRange **ret, const char *path) {
+ _cleanup_(uid_range_freep) UidRange *range = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from
+ * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID
+ * maps).
+ *
+ * To simplify things this will modify the passed array in case of later failure. */
+
+ assert(ret);
+
+ if (!path)
+ path = "/proc/self/uid_map";
+
+ f = fopen(path, "re");
+ if (!f) {
+ r = -errno;
+
+ if (r == -ENOENT && path_startswith(path, "/proc/"))
+ return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
+
+ return r;
+ }
+
+ range = new0(UidRange, 1);
+ if (!range)
+ return -ENOMEM;
+
+ for (;;) {
+ uid_t uid_base, uid_shift, uid_range;
+ int k;
+
+ errno = 0;
+ k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
+ if (k == EOF) {
+ if (ferror(f))
+ return errno_or_else(EIO);
+
+ break;
+ }
+ if (k != 3)
+ return -EBADMSG;
+
+ r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false);
+ if (r < 0)
+ return r;
+ }
+
+ uid_range_coalesce(range);
+
+ *ret = TAKE_PTR(range);
+ return 0;
+}