diff --git a/README.md b/README.md index f491d4a..f04245a 100644 --- a/README.md +++ b/README.md @@ -54,42 +54,38 @@ Features (as documented in the kernel documentation). The network configuration needs to be specified via the `ip=` kernel command line parameter. + * Very trivial module loading support (**no** automatic dependency + resolution). -On a x86_64 system with the default `-Og` compiler flags, statically -linked with the binary stripped, and the resulting initramfs compressed -with `gzip -9`, the images produced have the following size, depending -on the compiled-in options: - -| UUID | NFS4 | DEBUG | `initrd.img` size (bytes) | -| ---- | ---- | ----- | ------------------------- | -| N | N | N | 8996 | -| N | N | Y | 9432 | -| Y | N | N | 10347 | -| Y | N | Y | 10820 | -| N | Y | N | 12267 | -| N | Y | Y | 12767 | -| Y | Y | N | 13542 | -| Y | Y | Y | 14033 | - -In all cases here, dietlibc 0.33~cvs20120325-6 was used. - -The size of an initramfs using `tiny-initramfs` is thus about 16 KiB if -one doesn't use glibc. +When compiled on a x86_64 system with the default `-Og` compiler flags, +statically linked against dietlibc 0.33~cvs20120325-6, the binary +stripped and the resulting initramfs (without any modules added) +compressed with `gzip -9`, the images produced are between 9 kiB and +14 kiB, depending on the feature set selected. Using musl instead of dietlibc adds between 1.8 and 2.4 kiB to the resulting `initrd.img` size (depending on the feature set). Using glibc instead of dietlibc adds around 310 kiB to the resulting -initrd image. +initrd image and is not recommended (although it will work). + +Adding modules to the initramfs will increase the size, and many block +device and file system drivers are 100s of kiB in size. On the other +hand, the kernel would be larger if they were compiled in, so the +actual amount of space lost due to using modules is quite a bit +smaller. Requirements ------------ - * The kernel must have the necessary block device drivers built-in - that are required to access the root and `/usr` file systems. - **Warning:** this is not true for most default kernels of mainstream - distributions, as they require a full initramfs to load the modules - required to mount the root file system. + * The kernel should have the necessary block device and file system + drivers built-in that are required to access the root and `/usr` + file systems. **Warning:** this is not true for most default kernels + of mainstream distributions, as they require a full initramfs to + load the modules required to mount the root file system. + * If the necessary drivers are not built into the kernel, there is + limited support for loading modules from within the initramfs, see + below for details. * The kernel must have `CONFIG_DEVTMPFS` built-in, because this implementation assumes the kernel will just create the devices by itself. (This is true for most distribution kernels.) @@ -107,8 +103,6 @@ When not to use udev (such as `/dev/disk/by-label/...`). Only the kernel names themselves, such as `/dev/sda1`, as well as `UUID=` and hexadecimal device numbers (`0xMAJMIN`, e.g. `0x801`) are supported. - * No modules can be loaded in the initramfs, everything that's - required needs to be compiled in. * NFSv2/NFSv3 are currently not supported. * When booting from USB storage you should always use `UUID=`, because device names are not necessarily stable. @@ -239,6 +233,67 @@ be used to boot the system. Note that as of now there is no integration with distributions, so configuring the boot loader etc. has to be done manually. +Support for loading modules +--------------------------- + +There is limited support for loading modules if `--enable-modules` is +specified during the `configure` invocation. To use this feature, one +needs to create a file `/modules` in the initramfs image that is of the +following format: + + /file.ko options + +The modules should not be in a sub-directory, because the directory +containing them will not be cleaned-up by tiny-initramfs after mounting +the root file system. (Loading the modules will work though.) + +For example, the virtio block device driver `virtio_blk` requires some +additional modules to work. Using `modprobe` one may find out which: + + $ /sbin/modprobe --all --ignore-install --quiet --show-depends virtio_blk + insmod /lib/modules/[...]/kernel/drivers/virtio/virtio.ko + insmod /lib/modules/[...]/kernel/drivers/virtio/virtio_ring.ko + insmod /lib/modules/[...]/kernel/drivers/block/virtio_blk.ko + +It turns out that this is not quite sufficient, because the +`virtio_pci` driver is also required for `virtio_blk` to work (the +driver loads without `virtio_pci`, but doesn't work), so one may use: + + $ /sbin/modprobe --all --ignore-install --quiet --show-depends virtio_blk virtio_pci + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio.ko + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_ring.ko + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/block/virtio_blk.ko + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio.ko + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_ring.ko + insmod /lib/modules/3.16.0-4-amd64/kernel/drivers/virtio/virtio_pci.ko + +(Note that in case soft dependencies are treated via `install` lines, +these have to be resolved manually. This is typically not the case for +drivers needed within initramfs, because other implementations also +suffer from the same issue. `install` is deprecated anyway according to +the manual page of `modprobe.d`.) + +One may then copy these drivers to the initramfs image, and then add +a module file with the following contents: + + /virtio.ko + /virtio_ring.ko + /virtio_blk.ko + /virtio.ko + /virtio_ring.ko + /virtio_pci.ko + +The order is important, because dependency resolution is **not** +performed by tiny-initramfs, it has to be done while creating the +initramfs image. Duplicate entries are not a problem, because they will +silently be ignored (but you may remove duplicate entries if you don't +change the order otherwise). + +Options may be specified when followed by a space (tab characters not +supported), for example: + + /libata.ko noacpi + Debugging --------- diff --git a/configure.ac b/configure.ac index 14ce6cc..014f8e7 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,27 @@ AS_IF([test x"$enable_nfs4" = x"yes"], [AC_DEFINE([ENABLE_NFS4], [1], [Define if AM_CONDITIONAL([ENABLE_NFS4], [test x"$enable_nfs4" = x"yes"]) AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Enable support for printing debug messages])]) AS_IF([test x"$enable_debug" = x"yes"], [AC_DEFINE([ENABLE_DEBUG], [1], [Define if debug messages are enabled])]) +AC_ARG_ENABLE([modules], [AS_HELP_STRING([--enable-modules], [Enable support for loading modules (default is no)])]) +AS_IF([test x"$enable_modules" = x"yes"], [ + AC_CHECK_FUNC([finit_module], [AC_DEFINE([HAVE_FINIT_MODULE], [1], [Defined if finit_module is available.])], []) + AC_MSG_CHECKING([for SYS_finit_module]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include + #include + int a = + #ifdef SYS_finit_module + 42 + #endif + ;]])],[ + ac_cv_have_sys_finit_module=yes + AC_DEFINE([HAVE_SYS_FINIT_MODULE], [1], [Defined if SYS_finit_module is available.]) + AC_MSG_RESULT([yes]) + ], [AC_MSG_RESULT([no])]) + AC_CHECK_FUNC([init_module]) + AS_IF([test x"$ac_cv_func_init_module" != x"yes" && test x"$ac_cv_have_sys_finit_module" != x"yes" && test x"$ac_cv_func_finit_module" != x"yes"], + [AC_MSG_ERROR([Neither finit_module nor SYS_finit_module nor init_module syscalls available.]) + ]) + AC_DEFINE([ENABLE_MODULES], [1], [Define if support for loading modules is enabled]) +]) AC_ARG_WITH([variant-name], [AS_HELP_STRING([--with-variant-name=NAME], [The variant name of the installed binary (default: none)])]) AS_IF([test x"$with_variant_name" != x""], [ AC_DEFINE([VARIANT], ["$with_variant_name"], [The installed binary variant name]) diff --git a/tiny_initramfs.c b/tiny_initramfs.c index 0235f0b..b5668bf 100644 --- a/tiny_initramfs.c +++ b/tiny_initramfs.c @@ -25,6 +25,14 @@ #include #include #include +#if defined(ENABLE_MODULES) +#if !defined(HAVE_FINIT_MODULE) && !defined(HAVE_SYS_FINIT_MODULE) +#include +#include +#elif defined(HAVE_SYS_FINIT_MODULE) +#include +#endif +#endif static void parse_cmdline(); static int parse_cmdline_helper(void *data, const char *line, int line_is_incomplete); @@ -41,6 +49,13 @@ static void debug_dump_file(const char *fn); static int debug_dump_file_helper(void *data, const char *line, int line_is_incomplete); #endif +#ifdef ENABLE_MODULES +static void load_modules(); +static int load_module_helper(void *data, const char *line, int line_is_incomplete); +static int cleanup_module_helper(void *data, const char *line, int line_is_incomplete); +extern int init_module(void *module_image, unsigned long len, const char *param_values); +#endif + static char root_device[MAX_PATH_LEN]; static char root_options[MAX_LINE_LEN]; #ifdef ENABLE_NFS4 @@ -68,6 +83,13 @@ int main(int argc, char **argv) warn("Begun execution", NULL); #endif +#ifdef ENABLE_MODULES + load_modules(); +#ifdef ENABLE_DEBUG + warn("Loaded all kernel moduels", NULL); +#endif +#endif /* defined(ENABLE_MODULES) */ + r = mount("proc", "/proc", "proc", MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL); if (r < 0) panic(errno, "Could not mount /proc", NULL); @@ -446,4 +468,107 @@ void cleanup_initramfs() (void) rmdir("/dev"); (void) rmdir("/proc"); (void) unlink("/init"); +#ifdef ENABLE_MODULES + (void) traverse_file_by_line(MODULES_FILE, (traverse_line_t) cleanup_module_helper, NULL); + (void) unlink(MODULES_FILE); +#endif +} + +#ifdef ENABLE_MODULES +void load_modules() +{ + (void) traverse_file_by_line(MODULES_FILE, (traverse_line_t) load_module_helper, NULL); +} + +int load_module_helper(void *data, const char *line, int line_is_incomplete) +{ + (void)data; + int r, fd; + char *ptr; + const char *opts; + + if (line_is_incomplete) + return 0; + + if (!*line) + return 0; + + ptr = strchr(line, ' '); + if (ptr) { + *ptr = '\0'; + opts = ptr + 1; + } else { + opts = ""; + } + +#ifdef ENABLE_DEBUG + if (*opts) + warn("Loading kernel module ", line, " (with options: ", opts, ")", NULL); + else + warn("Loading kernel module ", line, NULL); +#endif + + fd = open(line, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + warn("Couldn't load ", line, ": ", strerror(errno), NULL); + return 0; + } +#if defined(HAVE_FINIT_MODULE) + r = finit_module(fd, opts, 0); + if (r < 0) + r = -errno; +#elif defined(HAVE_SYS_FINIT_MODULE) + r = syscall(SYS_finit_module, fd, opts, 0); + if (r < 0) + r = -errno; +#else + { + void *contents; + struct stat st; + + r = fstat(fd, &st); + if (r < 0) { + warn("Couldn't stat ", line, ": ", strerror(errno), NULL); + close(fd); + return 0; + } + + contents = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (!contents) { + warn("Couldn't mmap ", line, ": ", strerror(errno), NULL); + close(fd); + return 0; + } + r = init_module(contents, (unsigned long) st.st_size, opts); + if (r < 0) + r = -errno; + munmap(contents, (size_t) st.st_size); + } +#endif + + /* Ignore duplicate modules, this simplifies initramfs creation logic + * a bit. */ + if (r < 0 && r != -EEXIST) + warn("Couldn't load ", line, ": ", strerror(-r), NULL); + + close(fd); + + return 0; } + +int cleanup_module_helper(void *data, const char *line, int line_is_incomplete) +{ + (void)data; + char *ptr; + + if (line_is_incomplete) + return 0; + + ptr = strchr(line, ' '); + if (ptr) + *ptr = '\0'; + + (void) unlink(line); + return 0; +} +#endif diff --git a/tiny_initramfs.h b/tiny_initramfs.h index 28300a2..12bc87e 100644 --- a/tiny_initramfs.h +++ b/tiny_initramfs.h @@ -34,6 +34,10 @@ #define MAX_PATH_LEN 1024 #endif +#ifndef MODULES_FILE +#define MODULES_FILE "/modules" +#endif + #ifndef TARGET_DIRECTORY #define TARGET_DIRECTORY "/target" #endif