forked from chris-se/tiny-initramfs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 4998500
Showing
10 changed files
with
1,867 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.o | ||
initrd.img | ||
initramfs/ | ||
tiny_initrd |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
|
||
CC=musl-gcc | ||
CFLAGS=-Wall -Wextra -O2 -fstack-protector-strong | ||
LDFLAGS=-static | ||
|
||
# These are only needed for generating initrd.img | ||
STRIP=strip | ||
CPIO=cpio | ||
CPIO_ARGS=--quiet -R 0:0 -H newc | ||
FIND=find | ||
MKDIR=mkdir | ||
CP=cp | ||
RM_R=rm -r | ||
GZIP=gzip | ||
|
||
.PHONY: clean all | ||
|
||
all: tiny_initrd | ||
|
||
tiny_initrd: tiny_initrd.o io.o fstab.o mount.o log.o | ||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) | ||
|
||
initrd.img: tiny_initrd | ||
if [ -d initramfs ] ; then $(RM_R) initramfs ; fi | ||
$(MKDIR) initramfs initramfs/dev initramfs/proc initramfs/target | ||
$(CP) tiny_initrd initramfs/init | ||
$(STRIP) initramfs/init | ||
cd initramfs ; $(FIND) . | $(CPIO) -o $(CPIO_ARGS) | $(GZIP) > ../initrd.img ; cd .. | ||
$(RM_R) initramfs | ||
|
||
clean: | ||
rm -f *~ *.o tiny_initrd initrd.img |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
tiny-initrd - A minimalistic initrd implementation | ||
================================================== | ||
|
||
This is a very minimalistic [initrd](https://en.wikipedia.org/wiki/Initrd) | ||
implementation for booting Linux systems. It has nearly no features, but is | ||
very small and very fast. It is written purely in C, but uses only parts of | ||
the standard library. | ||
|
||
* It is designed for systems where an initrd is typically not necessary | ||
(block device drivers + root file system compiled into the kernel, no | ||
separate /usr file system), but where an initrd is required for | ||
microcode upgrades. Instead of having to use a full initrd, which is | ||
larger (more time spent in the boot loader loading it) and slower | ||
(because it does more), `tiny-initrd` will add next to no overhead. | ||
* In systems with a split `/usr` file system, it is necessary to mount | ||
that in the initrd already, else subtle problems may occur. If `/usr` | ||
resides on a simple block device already known to the kernel (without | ||
user space helpers such as udev), `tiny-initrd` provides a mechanism | ||
with very little overhead to mount it before the system is started. | ||
|
||
Features | ||
-------- | ||
|
||
* Simplicity: the implementation is really simple and very linear. It's | ||
most likely easier to understand than other initrds. The entire program | ||
is less than 1000 LoC, and that includes the License headers in the | ||
files. | ||
* Size: the implementation is really small (see below). | ||
* Speed: there is no noticeable performance penalty, because very little | ||
is done before execution is handed over to the operating system proper. | ||
* Supports mounting the `/` file system for kernel-named devices, for | ||
example `root=/dev/sda1`. | ||
* Supports parsing `/etc/fstab` to determine if a separate `/usr` | ||
partition exists and mounting that - as long as the entry there also | ||
specifies a kernel-named device as the source. | ||
* Supports the `root=`, `rootflags=`, `rootfstype=`, `rootdelay=`, | ||
`rootwait`, `ro`, `rw` and `init=` parameters. | ||
* Default timeout of 180 seconds to wait for the root device to appear | ||
(starts after the `rootdelay=` delay is over), after which a kernel | ||
panic is caused; if `rootwait` is specified it will wait indefinitely. | ||
(Recompilation is necessary for a larger timeout.) | ||
|
||
On an x86_64 system with the default `-O2 -fstack-protector=strong` | ||
compiler flags, statically linked with the binary stripped, and the | ||
resulting initrd compressed with default `gzip`, the images produced have | ||
the following size for different libcs tested: | ||
|
||
| libc implementation | `initrd.img` size (bytes) | | ||
| ------------------- | -------------------------:| | ||
| musl 1.1.5 | 11272 | | ||
| dietlibc 0.33 | 9397 | | ||
| glibc 2.19 | 323688 | | ||
|
||
The size of an initrd using `tiny-initrd` is thus about 10 KiB if one | ||
doesn't use glibc. | ||
|
||
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 full initrds to load the modules required to mount the | ||
root file system. | ||
* 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.) | ||
|
||
When not to use | ||
--------------- | ||
|
||
* `tiny-initrd` does not support `UUID=` nor `PARTUUID=` for mounting the | ||
root or `/usr` file systems. It also doesn't support symlinks created by | ||
udev (such as `/dev/disk/by-label/...`). Only the kernel names | ||
themselves, such as `/dev/sda1` are supported. | ||
* No modules can be loaded in the initrd, everything that's required | ||
needs to be compiled in. | ||
* `/` or `/usr` on network file systems are currently not supported. | ||
* Booting from USB storage is not recommended, because the device | ||
names aren't stable. (It could work regardless if there is only one USB | ||
storage device attached at all times.) | ||
* `/usr` on a FUSE file system, as they require user space helpers running | ||
to be able to mount. Generally speaking, any file system that can't be | ||
mounted with just a trivial `mount` syscall, but requires a userspace | ||
helper, will not work. | ||
|
||
If your setup falls into one of these cases, please use a full initrd | ||
instead of `tiny-initrd`. It is not meant to replace those, but provide a | ||
light-weight solution in cases where the complexities of a full initrd are | ||
unnecessary. | ||
|
||
Caveats | ||
------- | ||
|
||
* Since the initrd is supposed to be small, `fsck` will not be executed by | ||
`tiny-initrd`. For the `/` file system this is perfectly fine, as | ||
most distributions support checking the root file system at boot outside | ||
of the initrd. But this doesn't work for `/usr`, because e.g. `e2fsck` | ||
will not check a mounted file system other than the root file system; | ||
and e.g. systemd passes `-M` to `fsck` by default for non-root file | ||
systems, so mounted file systems are excluded anyway. It shouldn't be | ||
too difficult to special-case `/usr` here as well, but that work needs | ||
to be done if a file system check at boot is to be performed for `/usr` | ||
with `tiny-initrd`. (Note that `e2fsck` plus the required libraries are | ||
about 2.5 MiB in size, so having `fsck` present in the initrd image is | ||
not in the scope of `tiny-initrd`, because it would kill all the | ||
advantages.) | ||
* If you use anything other than systemd as the init system, you need to | ||
make sure that a split-`/usr` file system is remounted read-write if the | ||
`ro` option is passed on the kernel command line (because `tiny-initrd` | ||
will also mount `/usr` read-only then) - otherwise `/usr` will remain | ||
read-only after boot. `tiny_initrd` itself doesn't care about which | ||
init system is used, but the init system must be able to cope with the | ||
state that `tiny_initrd` leaves the `/usr` file system in. This may | ||
require changes to some scripts. | ||
* If `/usr` is a bind mount in `/etc/fstab`, this will currently fail, | ||
even though it should be supportable. (It's on the TODO list, as long as | ||
that doesn't require yet another file system.) | ||
* Overlay-type file systems for `/usr` are untested, but they should work | ||
if they are compiled into the kernel. What is *not* supported are | ||
overlay-type file systems for `/` and/or if something has to be done | ||
prior to mounting these file systems (such as creating a directory, or | ||
mounting an additional tmpfs or similar). | ||
* Old-style `root=MAJ:MIN` is currently not supported, but on the TODO | ||
list. | ||
|
||
HOWTO | ||
----- | ||
|
||
Install an alternative libc implementation that's designed for embedded use | ||
cases, such as [musl](http://www.musl-libc.org/) or | ||
[dietlibc](http://www.fefe.de/dietlibc/). This is strictly speaking not | ||
required, as the default glibc will also work, but then the binary size | ||
of the resulting binary will be far larger. If `tiny-initrd` doesn't work | ||
with your favorite libc implementation, please report this, so that it may | ||
be fixed. | ||
|
||
Find out the compile command required to use your C library. For example, | ||
with musl it's `musl-gcc`, with dietlibc it's `diet gcc`. | ||
|
||
Use | ||
|
||
CC=musl-gcc make | ||
|
||
to compile the `tiny_initrd` binary and | ||
|
||
CC=musl-gcc make initrd.img | ||
|
||
to auto-create the initrd image. Replace `musl-gcc` with the appropriate | ||
command for your libc implementation. | ||
|
||
The initrd creation is really simple, you may also do so manually: | ||
|
||
1. create an empty directory, let's call it `initramfs/` | ||
2. copy `tiny_initrd` to the directory, call it `init` (you can call it | ||
something else, but then you also need to pass the `rdinit=/newname` | ||
option to the kernel command line) | ||
3. strip the binary to reduce it's size (optional) | ||
4. create the `dev`, `proc` and `target` subdirectories | ||
5. cpio the directory and compress it | ||
|
||
The following commands do just that: | ||
|
||
mkdir initramfs | ||
cp tiny_initrd initramfs/init | ||
strip initramfs/init | ||
mkdir initramfs/dev initramfs/proc initramfs/target | ||
cd initramfs ; find . | cpio -o --quiet -R 0:0 -H newc | gzip > ../initrd.img | ||
|
||
With this there's now a (kernel-independent) initrd image that may 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. | ||
|
||
Design considerations | ||
--------------------- | ||
|
||
The design of `tiny-initrd` is as minimalistic as possible. The buffered | ||
I/O functions from the `stdio.h` standard library are completely avoided, | ||
because they can increase the code size quite a bit, depending on the libc | ||
implementation. At one point an own minimalistic buffered I/O routine is | ||
implemented (much smaller than the full standard library linked in). | ||
|
||
Dynamic allocations are avoided and buffers on the stack are used. Code | ||
that properly handles dynamic allocations tends to be longer, so this | ||
reduces code size. The buffers are sized generously (there are not that | ||
many buffers that the amount of RAM used is a concern just yet, even for | ||
small systems), so that no real flexibility is sacrificed. | ||
|
||
None of this is extremely performance-critical (it is going to be quite | ||
fast regardless, because very little is done compared to a even just | ||
running a shell), so no algorithm is optimized for speed directly. For | ||
example, the mount option parser table is somewhat compressed to reduce | ||
the code size (negation and recursive variants of mount options are not | ||
repeated), to the point where further reduction would likely sacrifice the | ||
readability of the code. Execution speed is achieved by doing very little, | ||
not by micro-optimizing algorithms. | ||
|
||
Future features | ||
--------------- | ||
|
||
There is no goal of adding too many additional features here, because any | ||
additional feature is going to increase the binary size, and this is | ||
supposed to be minimalistic and **not** a replacement for a full initrd. If | ||
you need advanced features, please use an already-existing solution. That | ||
said, there are two things that might be interesting regardless: | ||
|
||
* Some minimalistic network file system support for very simple cases, if | ||
it is possible to let the kernel do all the network configuration even | ||
if an initrd is used. For example, mounting an NFSv3 (or non-idmapped | ||
NFSv4 `sec=sys`) file system only consists of calling the `mount` | ||
syscall with a special data structure as the `data` parameter - which | ||
does not appear to be much more complex than what is currently | ||
implemented. | ||
* Support `UUID=` for the most common file systems: it shouldn't be too | ||
hard to go through all partitions and extract the UUIDs for at least the | ||
major file system types, basically just ext4, xfs and btrfs. (Open the | ||
block device, seek to position, verify that we know the file system | ||
type, seek to other position, read 16 bytes, close device.) | ||
|
||
If any of these features should be implemented, depending on by how much | ||
they increase the initrd image size, they might be made compile-time | ||
optional. The cutoff will be around 15 KiB on x86_64 (so that it should be | ||
less than 16 KiB on all architectures), anything that keeps the image size | ||
smaller than that will still be considered acceptable. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* tiny_initrd - Minimalistic initrd implementation | ||
* Copyright (C) 2016 Christian Seiler <christian@iwakd.de> | ||
* | ||
* io.c: I/O helper functions | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program 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 General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#include <string.h> | ||
#include <errno.h> | ||
#include <stdlib.h> | ||
|
||
#include "tiny_initrd.h" | ||
|
||
typedef struct fstab_find_fs_data { | ||
const char *dest; | ||
fstab_info *info; | ||
int found; | ||
} fstab_find_fs_data; | ||
|
||
static int process_fstab_entry(fstab_find_fs_data *data, const char *line, int line_is_incomplete); | ||
|
||
int fstab_find_fs(const char *dest, fstab_info *info) | ||
{ | ||
int r; | ||
fstab_find_fs_data data = { dest, info, 0 }; | ||
r = traverse_file_by_line(TARGET_DIRECTORY FSTAB_FILENAME, (traverse_line_t)process_fstab_entry, &data); | ||
if (r < 0) | ||
return r; | ||
if (!data.found) | ||
return -ENOENT; | ||
return 0; | ||
} | ||
|
||
int process_fstab_entry(fstab_find_fs_data *data, const char *orig_line, int line_is_incomplete) | ||
{ | ||
char *saveptr; | ||
char *token; | ||
int had_nws, i; | ||
char line[MAX_LINE_LEN] = { 0 }; | ||
char *fields[6] = { 0 }; | ||
|
||
/* more than MAX_LINE_LEN in fstab? just ignore it */ | ||
if (line_is_incomplete) | ||
return 0; | ||
|
||
/* ignore comments and empty lines */ | ||
if (!orig_line || !*orig_line || *orig_line == '#') | ||
return 0; | ||
|
||
/* ignore whitespace-only lines */ | ||
had_nws = 0; | ||
for (token = (char *)orig_line; *token; token++) { | ||
if (*token != ' ' && *token != '\t') { | ||
had_nws = 1; | ||
break; | ||
} | ||
} | ||
if (!had_nws) | ||
return 0; | ||
|
||
strncpy(line, orig_line, MAX_LINE_LEN - 1); | ||
|
||
i = 0; | ||
for (token = strtok_r(line, " \t", &saveptr); token != NULL; token = strtok_r(NULL, " \t", &saveptr)) { | ||
if (i < 6) { | ||
fields[i] = token; | ||
} | ||
i++; | ||
} | ||
|
||
if (i != 6) | ||
return 0; | ||
|
||
/* we are only interested in /usr */ | ||
if (strcmp(fields[1], data->dest) != 0) | ||
return 0; | ||
|
||
if (strncmp(fields[0], "/dev/", 5) != 0) | ||
return -ENODEV; | ||
|
||
/* NOTE: for the /usr use casee this is sufficient, but in general | ||
* one needs to de-escape the source and dest fields, see e.g. | ||
* the fstab-decode utility. | ||
*/ | ||
strncpy(data->info->source, fields[0], MAX_PATH_LEN - 1); | ||
strncpy(data->info->dest, fields[1], MAX_PATH_LEN - 1); | ||
strncpy(data->info->type, fields[2], MAX_PATH_LEN - 1); | ||
strncpy(data->info->options, fields[3], MAX_LINE_LEN - 1); | ||
data->info->dump = strtoul(fields[4], NULL, 10); | ||
data->info->pass = strtoul(fields[5], NULL, 10); | ||
data->found = 1; | ||
return 1; | ||
} |
Oops, something went wrong.