| /* |
| * libkmod - interface to kernel built-in modules |
| * |
| * Copyright (C) 2019 Alexey Gladkov <[email protected]> |
| * |
| * 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; |
| }; |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |