$HOME - /src - /var/log

Creating a tiny init in rc

DISCLAIMER: I take no responsability if you attempt to use this, and end up with a broken system. Always remember to do your own research. Also: Have fun.

Today, we will do a writeup on creating a small init, and init scripts, but opposed to writing them in bash or POSIX sh, we will write it in rc.
Despite rc not being specified by POSIX, we will still try to avoid any non-POSIX utilities, so that our scripts remains as portable as possible.

What is rc you say?
rc is the shell (or command interpreter to be correct) used by plan9.
rc has since been ported to various OS's, like Linux, and BSD's. In comparisson to more mainstream shells like bash or ksh, rc is a lot smaller.
This might imply that rc lacks features, but this is a feature, not a bug.

Lets also give a crude explenation of the boot process of a Linux system:

  1. Bootloader starts, which loads up the kernel (On UEFI systems, this is optional with the use of efistub).
  2. kernel starts, and calls the init (by call, we mean run, or start).
  3. init starts and calls your initscript
  4. login prompt should now be present and the OS has finished its boot process.

Anyway, lets get on with it, and create a simple init for linux.
Lets start with the actual init, which might located as /bin/init, depending on your system.
The init is the OS's first process to start. (Also refered to as PID 1 (Process ID 1)).
If your init is not PID 1, something is wrong, and the init should exit with an error.

So, the first thing your init probably should do, is check wether its PID 1 or not.
In rc it can look something like this:

#!/bin/rc  
test $pid -eq 1

In rc, $pid is a special variable, which contains the PID of the running process.
In bash and other shells, the equivalant would be $$.

Next on the list would be to exit, should test not give us a exit code of 0.
We then append; || exit 1 at the end of our script, and it should now exit, if test returns a non-zero value. (0 here is true, and >0 is false)
The numerical value after exit, is called an exit code, and everything >0, is considered an error. Now we should have somthing like this:

#!/bin/rc  
test $pid -eq 1 || exit 1

Now that our init has checked if its PID is 1, we can now call our initscript.
Initscripts is not to be confused with the init, as they're different componets of the init system.
The initscripts job for the most part be to mount your drives, and start your service manager. Initscripts can in a lot of cases do a lot more than this, but in this scenario, everything else is superflous.

Moving forward, we can append now /etc/rc.init, so that our init can start the initscript.
Your initscript might be located elsewhere, or named something else.
(I.e. Kiss Linux has them located in /lib/init/rc.boot)

Our init should now look like this:

#!/bin/rc  
test $pid -eq 1 || exit 1  
/etc/rc.init

Now our init has done more or less everything its supposed to. The boot process will now continue with our initscript (/etc/rc.init) But before we move on to write our own initscript, we would need to make sure that our init does not exit.

To avoid our init to exit, we will use a while loop, acompanied by the wait command and our previously mentioned $pid variable. We will use while() which is the equivalant of while : on regular shell (in this case both () and : is interpreted as true).
This means that aslong as the value is true (which it is, and always is), the loop will continue.
An infinite loop if you will. We will then run wait $pid, this will wait for $pid to exit. Which doesnt occour.

Our inits final form can now look something like this:

#!/bin/rc
test $pid -eq 1 || exit 1
/etc/rc.init
wait(){wait $pid)}

We now have a init in measly 3 lines of code. Which is pretty small, even in comparisson to other small inits like sinit, which is just shy of 100 LOC. Although, sinit is written in C, and not rc.

Now for the initscripts; We will start with /etc/rc.init, which is the part which is ran during the boot process. Our /etc/rc.init might look something like this:

#!/bin/rc
umask 022

mkdir -p /dev/pts
mkdir -p /dev/shm
mount -t proc none /proc
mount -o remount,rw /
mount -a

Lets run through them and give a brief explenation what they do:

At this stage, we should now have a successfully booted system. However, for the keen eyed among you, who might be asking yourself:

Where is my login prompt?

This is correct. We have yet to start any getty's. We could append /bin/getty /dev/tty1 linux to our initscript, and have login prompt ready for us. However, if you log out, or type in the wrong password, login would not give you a new login prompt, and youd be stuck with a blinking prompt.

This is because we have to run the command in a while loop, or using a service manager giving us the same functionality. Using a service manager for this gives you more granual control over how many getty's you wish to run. Be it 2, or 12.
Chances are youd likely to be wanting to run atleast 2 tty's anyway, to have a way out, should your GUI freeze up on you.

Now that we have a working init, and initscript for booting up, we can now turn to how to properly shutdown our machine. Obviously we could just rip out the powercable, or force the computer of, but we dont wanna mess up our partitions now do we?
Our /etc.rc.shutdown can look something like this:

#!/bin/rc

kill -s TERM -1
sleep 1
kill -s KILL -1

swapoff -a

mount -o remount,ro /
sync

umount -a

wait

switch($1) {
case -p
	halt -p
case -r
	halt -r
}

Again, lets run through what they mean, and why theyre there.

switch($1) {
case -p
	halt -p
case -r
	halt -r
}

Unfortunatly our rc.shutdown script is in a less ideal state due to non-POSIX tootls.
Another alternative, would be to write a portable script in C, like Oasis Linux does.

At this point, we now have a way to boot our machine, aswell as shut it down properly. I Hoped this peaked your interest, maybe you learned something new, or made you look into some of these topics.