diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c index 5ae8ef5f..74354c8e 100644 --- a/libkmod/libkmod-builtin.c +++ b/libkmod/libkmod-builtin.c @@ -1,333 +1,193 @@ // SPDX-License-Identifier: LGPL-2.1-or-later /* * Copyright (C) 2019 Alexey Gladkov + * Copyright (C) 2024 Tobias Stöckmann */ #include #include -#include +#include #include #include #include #include -#include +#include + +#include #include "libkmod.h" #include "libkmod-internal.h" #define MODULES_BUILTIN_MODINFO "modules.builtin.modinfo" -struct kmod_builtin_iter { +struct kmod_builtin_info { struct kmod_ctx *ctx; - // The file descriptor. - int file; - - // The total size in bytes. - ssize_t size; - - // The offset of current module. - off_t pos; - - // The offset at which the next module is located. - off_t next; - - // Number of strings in the current block. - ssize_t nstrings; + // The file handle. + FILE *fp; - // Internal buffer and its size. + // Internal buffer and its size. Used by getdelim. size_t bufsz; char *buf; }; -static struct kmod_builtin_iter *kmod_builtin_iter_new(struct kmod_ctx *ctx) +static bool kmod_builtin_info_init(struct kmod_builtin_info *info, + struct kmod_ctx *ctx) { char path[PATH_MAX]; - int file, sv_errno; - struct stat sb; - struct kmod_builtin_iter *iter = NULL; + FILE *fp = NULL; const char *dirname = kmod_get_dirname(ctx); size_t len = strlen(dirname); - file = -1; - if ((len + 1 + strlen(MODULES_BUILTIN_MODINFO) + 1) >= PATH_MAX) { - sv_errno = ENAMETOOLONG; - goto fail; - } - - snprintf(path, PATH_MAX, "%s/%s", dirname, MODULES_BUILTIN_MODINFO); - - file = open(path, O_RDONLY|O_CLOEXEC); - if (file < 0) { - sv_errno = errno; - goto fail; + errno = ENAMETOOLONG; + return false; } + snprintf(path, PATH_MAX, "%s/" MODULES_BUILTIN_MODINFO, dirname); - if (fstat(file, &sb) < 0) { - sv_errno = errno; - goto fail; - } - - if (sb.st_size > INTPTR_MAX) { - sv_errno = ENOMEM; - goto fail; - } + fp = fopen(path, "r"); + if (fp == NULL) + return false; - iter = malloc(sizeof(*iter)); - if (!iter) { - sv_errno = ENOMEM; - goto fail; - } + info->ctx = ctx; + info->fp = fp; + info->bufsz = 0; + info->buf = NULL; - iter->ctx = ctx; - iter->file = file; - iter->size = sb.st_size; - iter->nstrings = 0; - iter->pos = 0; - iter->next = 0; - iter->bufsz = 0; - iter->buf = NULL; - - return iter; -fail: - if (file >= 0) - close(file); - - errno = sv_errno; - - return iter; + return true; } -static void kmod_builtin_iter_free(struct kmod_builtin_iter *iter) +static void kmod_builtin_info_release(struct kmod_builtin_info *info) { - close(iter->file); - free(iter->buf); - free(iter); + free(info->buf); + fclose(info->fp); } -static off_t get_string(struct kmod_builtin_iter *iter, off_t offset, - char **line, size_t *size) +static ssize_t get_string(struct kmod_builtin_info *info) { - int sv_errno; - char buf[BUFSIZ]; - char *nullp = NULL; - size_t linesz = 0; - - while (!nullp) { - ssize_t sz; - size_t partsz; - - sz = pread(iter->file, buf, BUFSIZ, offset); - if (sz < 0) { - sv_errno = errno; - goto fail; - } else if (sz == 0) { - offset = 0; - break; - } - - nullp = memchr(buf, '\0', (size_t) sz); - partsz = (size_t)((nullp) ? (nullp - buf) + 1 : sz); - offset += (off_t) partsz; - - if (iter->bufsz < linesz + partsz) { - void *tmp; - size_t s; + ssize_t len; - s = linesz + partsz; - tmp = realloc(iter->buf, s); - - if (!tmp) { - sv_errno = errno; - goto fail; - } - iter->bufsz = s; - iter->buf = tmp; - } - - strncpy(iter->buf + linesz, buf, partsz); - linesz += partsz; + len = getdelim(&info->buf, &info->bufsz, '\0', info->fp); + if (len > 0 && info->buf[len] != '\0') { + errno = EINVAL; + len = -1; } - if (linesz) { - if (iter->buf[linesz - 1] != '\0') { - sv_errno = EINVAL; - goto fail; - } - *line = iter->buf; - *size = linesz; - } - - return offset; -fail: - errno = sv_errno; - return -1; + return len; } -static bool kmod_builtin_iter_next(struct kmod_builtin_iter *iter) +static ssize_t get_strings(struct kmod_builtin_info *info, const char *modname, + struct strbuf *buf) { - char *line, *modname; - size_t linesz; - off_t pos, offset, modlen; - - modname = NULL; + const size_t modlen = strlen(modname); + ssize_t count, n; - iter->nstrings = 0; - offset = pos = iter->next; + for (count = 0; count < INTPTR_MAX;) { + char *dot, *line; - while (offset < iter->size) { - char *dot; - off_t len; - - offset = get_string(iter, pos, &line, &linesz); - if (offset <= 0) { - if (offset) - ERR(iter->ctx, "get_string: %s\n", strerror(errno)); - pos = iter->size; + n = get_string(info); + if (n == -1) { + if (!feof(info->fp)) { + count = -errno; + ERR(info->ctx, "get_string: %s\n", strerror(errno)); + } break; } + line = info->buf; dot = strchr(line, '.'); - if (!dot) { - ERR(iter->ctx, "kmod_builtin_iter_next: unexpected string without modname prefix\n"); - pos = iter->size; - break; + if (dot == NULL) { + count = -EINVAL; + ERR(info->ctx, "get_strings: " + "unexpected string without modname prefix\n"); + return count; } - - len = dot - line; - - if (!modname) { - modname = strdup(line); - modlen = len; - } else if (modlen != len || strncmp(modname, line, len)) { + if (strncmp(line, modname, modlen) || line[modlen] != '.') { + /* + * If no string matched modname yet, keep searching. + * Otherwise this indicates that no further string will + * match again. + */ + if (count == 0) + continue; break; } - - iter->nstrings++; - pos = offset; + if (!strbuf_pushchars(buf, dot + 1) || + !strbuf_pushchar(buf, '\0')) { + count = -errno; + ERR(info->ctx, "get_strings: " + "failed to append modinfo string\n"); + return count; + } + count++; } - iter->pos = iter->next; - iter->next = pos; - - free(modname); + if (count == INTPTR_MAX) { + count = -ENOMEM; + ERR(info->ctx, "get_strings: " + "too many modinfo strings encountered\n"); + return count; + } - return (iter->pos < iter->size); + return count; } -static bool kmod_builtin_iter_get_modname(struct kmod_builtin_iter *iter, - char modname[static PATH_MAX]) +static char **strbuf_to_vector(struct strbuf *buf, size_t count) { - int sv_errno; - char *line, *dot; - size_t linesz, len; - off_t offset; - - if (iter->pos == iter->size) { - sv_errno = EINVAL; - goto fail; - } - - line = NULL; + /* size required for string vector + terminating NULL */ + const size_t vecsz = sizeof(char *) * (count + 1); + char **vector; + char *s; + size_t n; - offset = get_string(iter, iter->pos, &line, &linesz); - if (offset <= 0) { - sv_errno = errno; - if (offset) - ERR(iter->ctx, "get_string: %s\n", strerror(errno)); - goto fail; + /* make sure that vector and strings fit into memory constraints */ + if (SIZE_MAX / sizeof(char *) - 1 < count || + SIZE_MAX - buf->used < vecsz) { + errno = ENOMEM; + return NULL; } - dot = strchr(line, '.'); - if (!dot) { - sv_errno = EINVAL; - ERR(iter->ctx, "kmod_builtin_iter_get_modname: unexpected string without modname prefix\n"); - goto fail; - } - - len = dot - line; + vector = realloc(buf->bytes, vecsz + buf->used); + if (vector == NULL) + return NULL; + buf->bytes = NULL; + memmove(vector + count + 1, vector, buf->used); + s = (char *)(vector + count + 1); - if (len >= PATH_MAX) { - sv_errno = ENAMETOOLONG; - goto fail; + for (n = 0; n < count; n++) { + vector[n] = s; + s += strlen(s) + 1; } + vector[n] = NULL; - strncpy(modname, line, len); - modname[len] = '\0'; - - return true; -fail: - errno = sv_errno; - return false; + return vector; } /* array will be allocated with strings in a single malloc, just free *array */ ssize_t kmod_builtin_get_modinfo(struct kmod_ctx *ctx, const char *modname, char ***modinfo) { - ssize_t count = 0; - char *s, *line = NULL; - size_t i, n, linesz, modlen, size; - off_t pos, offset; + struct kmod_builtin_info info; + struct strbuf buf; + ssize_t count; - char *name = NULL; - char buf[PATH_MAX]; - - struct kmod_builtin_iter *iter = kmod_builtin_iter_new(ctx); - - if (!iter) + if (!kmod_builtin_info_init(&info, ctx)) return -errno; - - while (!name && kmod_builtin_iter_next(iter)) { - if (!kmod_builtin_iter_get_modname(iter, buf)) { + strbuf_init(&buf); + + count = get_strings(&info, modname, &buf); + if (count == 0) + *modinfo = NULL; + else if (count > 0) { + *modinfo = strbuf_to_vector(&buf, (size_t)count); + if (*modinfo == NULL) { count = -errno; - goto fail; + ERR(ctx, "strbuf_to_vector: %s\n", strerror(errno)); } - - if (strcmp(modname, buf)) - continue; - - name = buf; - } - - if (!name) { - count = -ENOSYS; - goto fail; - } - - modlen = strlen(modname) + 1; - count = iter->nstrings; - size = iter->next - iter->pos - (modlen * count); - - *modinfo = malloc(size + sizeof(char *) * (count + 1)); - if (!*modinfo) { - count = -errno; - goto fail; } - s = (char *)(*modinfo + count + 1); - i = 0; - - n = 0; - offset = pos = iter->pos; - - while (offset < iter->next) { - offset = get_string(iter, pos, &line, &linesz); - if (offset <= 0) { - count = (offset) ? -errno : -EINVAL; - free(*modinfo); - goto fail; - } - - strcpy(s + i, line + modlen); - (*modinfo)[n++] = s + i; - i += linesz - modlen; - - pos = offset; - } -fail: - kmod_builtin_iter_free(iter); + strbuf_release(&buf); + kmod_builtin_info_release(&info); return count; }