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..