Skip to content

Commit

Permalink
Add initial code to repository
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-se committed Jan 5, 2016
0 parents commit 4998500
Show file tree
Hide file tree
Showing 10 changed files with 1,867 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.o
initrd.img
initramfs/
tiny_initrd
674 changes: 674 additions & 0 deletions COPYING

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions Makefile
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
224 changes: 224 additions & 0 deletions README.md
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.
106 changes: 106 additions & 0 deletions fstab.c
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;
}
Loading

0 comments on commit 4998500

Please sign in to comment.