diff options
Diffstat (limited to 'libkmod/libkmod-builtin.c')
-rw-r--r-- | libkmod/libkmod-builtin.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c new file mode 100644 index 0000000..65334a8 --- /dev/null +++ b/libkmod/libkmod-builtin.c @@ -0,0 +1,330 @@ +/* + * libkmod - interface to kernel built-in modules + * + * Copyright (C) 2019 Alexey Gladkov <gladkov.alexey@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "libkmod.h" +#include "libkmod-internal.h" + +#define MODULES_BUILTIN_MODINFO "modules.builtin.modinfo" + +struct kmod_builtin_iter { + 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; + + // Internal buffer and its size. + size_t bufsz; + char *buf; +}; + +static struct kmod_builtin_iter *kmod_builtin_iter_new(struct kmod_ctx *ctx) +{ + char path[PATH_MAX]; + int file, sv_errno; + struct stat sb; + struct kmod_builtin_iter *iter = 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; + } + + if (fstat(file, &sb) < 0) { + sv_errno = errno; + goto fail; + } + + iter = malloc(sizeof(*iter)); + if (!iter) { + sv_errno = ENOMEM; + goto fail; + } + + 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; +} + +static void kmod_builtin_iter_free(struct kmod_builtin_iter *iter) +{ + close(iter->file); + free(iter->buf); + free(iter); +} + +static off_t get_string(struct kmod_builtin_iter *iter, off_t offset, + char **line, size_t *size) +{ + int sv_errno; + char *nullp = NULL; + size_t linesz = 0; + + while (!nullp) { + char buf[BUFSIZ]; + 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) { + iter->bufsz = linesz + partsz; + iter->buf = realloc(iter->buf, iter->bufsz); + + if (!iter->buf) { + sv_errno = errno; + goto fail; + } + } + + strncpy(iter->buf + linesz, buf, partsz); + linesz += partsz; + } + + if (linesz) { + *line = iter->buf; + *size = linesz; + } + + return offset; +fail: + errno = sv_errno; + return -1; +} + +static bool kmod_builtin_iter_next(struct kmod_builtin_iter *iter) +{ + char *line, *modname; + size_t linesz; + off_t pos, offset, modlen; + + modname = NULL; + + iter->nstrings = 0; + offset = pos = iter->next; + + 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; + break; + } + + dot = strchr(line, '.'); + if (!dot) { + ERR(iter->ctx, "kmod_builtin_iter_next: unexpected string without modname prefix\n"); + pos = iter->size; + break; + } + + len = dot - line; + + if (!modname) { + modname = strdup(line); + modlen = len; + } else if (modlen != len || strncmp(modname, line, len)) { + break; + } + + iter->nstrings++; + pos = offset; + } + + iter->pos = iter->next; + iter->next = pos; + + free(modname); + + return (iter->pos < iter->size); +} + +static bool kmod_builtin_iter_get_modname(struct kmod_builtin_iter *iter, + char modname[static PATH_MAX]) +{ + int sv_errno; + char *line, *dot; + size_t linesz, len; + off_t offset; + + if (iter->pos == iter->size) + return false; + + line = NULL; + + 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; + } + + dot = strchr(line, '.'); + if (!dot) { + sv_errno = errno; + ERR(iter->ctx, "kmod_builtin_iter_get_modname: unexpected string without modname prefix\n"); + goto fail; + } + + len = dot - line; + + if (len >= PATH_MAX) { + sv_errno = ENAMETOOLONG; + goto fail; + } + + strncpy(modname, line, len); + modname[len] = '\0'; + + return true; +fail: + errno = sv_errno; + return false; +} + +/* 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; + + char *name = NULL; + char buf[PATH_MAX]; + + struct kmod_builtin_iter *iter = kmod_builtin_iter_new(ctx); + + if (!iter) + return -errno; + + while (!name && kmod_builtin_iter_next(iter)) { + if (!kmod_builtin_iter_get_modname(iter, buf)) { + count = -errno; + goto fail; + } + + 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); + return count; +} |