0

Background

I need to assemble my own initramfs and before writing its init script I want to do all the required operations by hand, i.e. from an interactive shell. My choice is statically linked busybox for all operations.

What I do

I have busybox executable in initramfs /bin, symlink for shell in /bin: 'sh' -> /bin/busybox, and in initramfs root, init symlink to /bin/sh

What I get

The busybox thinks now that its name is init and gives me a prompt Please press Enter to activate this console. After I press Enter, I get into the shell, but its PID is not 1. A process that has PID=1 is init. Because of this I can't manually execute switch_root and continue with booting main system.

I tried to run bash as shell in initramfs and succeeded: it runs with PID=1 and allows execution of switch_root.

Question

From initramfs, how can I run interactive busybox shell with PID=1?

1 Answer 1

2

TLDR Answer

You can do this by simply symlinking init program to your shell of choice, in the initramfs, as you already discovered.

"Busybox" problem

The problem you are facing is a busybox "feature". Busybox packs all the utilities it has selected to contain at compilation into a single universal executable. It then starts "sub-utility" on execution based on it's symlink name, i.e. argv[0] vector.

This technique allows it to have all the utilities statically linked-in (no dylibs aka DLLs are needed - making it very robust - broken libc (like glibc) will never make your busybox not work - yet it allows it to share compiled code common among ebbeded utilities as if they were split into exes and dylib and referenced by those, as all utilities live in a single executable and thus all can call same shared functions directly (like strlen, printf etc.) - i.e. code of functions within executable is shared among all the sub-utilities as if it was living in a DLL) and save storage space (you just need a single busybox binary, which contains all the utilities/commands in a single file, thus all the utilities share same ELF header and other ELF format structures and are simply a functions of that executable - making the overall result smaller as dozens of separate executables: symlinks take less space on the device than multiple ELF structures of multiple utility executables would take).

Now, busybox determines sub-utility to run, let's say ls, by either seeing ["busybox", "ls"...] or by seeing ["ls",..] at zeroth position of it's argv vector when execd.

Ie ln -s busybox ls ; ./ls is equivalent to running busybox ls which is equivalent to running ls, if previous symlink is placed correctly (i.e. in $PATH).

As your symlink is named init, that gets passed by kernel into the busysbox's argv[0] vector and that is what busybox "sees" as name it was invoked with and thus starts as actual init program sub-utility.

Solution

To fix this, this could maybe work:

  • create text file /bin/init
  • mark it executable
  • make it's code the following:
#!/bin/sh
exec /bin/sh # this should replace current init process which is the sh interpreter from shebang above but with arg[0] of `init` with sh interpreter with argv[0] of `sh` - and this should be enough, to make busybox believe it should run `sh` sub-utility, which will end as PID1 because of `exec` in front.

In a nutshell you must persuade busybox it's running as a process named sh for it to act like shell.

In my experience, when developing ad-hoc iniramfses and init schemes it's much more productive to have /bin/init point to your actual init implementation and define 2 bootloader menus instead:

  • menuitem 1: boot with my init
  • menuitem 2: boot with shell, by adding init=/bin/sh as LAST parameter to the kernel command line

This way you can simply pick the mode in which to boot (testrun, manual) by selecting wished menuitem from booloader/qemu etc..

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .