/* * libkmod - interface to kernel built-in modules * * Copyright (C) 2019 Alexey Gladkov * * 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 . */ #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_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; }