diff options
Diffstat (limited to 'src/basic/uid-range.c')
-rw-r--r-- | src/basic/uid-range.c | 237 |
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; +} |