You were very close to the solution. Anaconda (the installer) is written in Python, so I went digging into the code.
Ultimately, it executes the script like this:
rc = iutil.execWithRedirect(self.interp, ["/tmp/%s" % os.path.basename(path)],
stdin = messages, stdout = messages, stderr = messages,
root = scriptRoot)
A bit more digging and you can find iutil.execWithRedirect defined in 'iutil.py'. This function ultimately uses subprocess.Popen (a Python built in) to execute the command. It also tries very hard to get the contents of STDOUT and STDERR from the %pre script.
The code looks like this:
#prepare tee proceses
proc_std = tee(pstdout, stdout, program_log.info, command)
proc_err = tee(perrout, stderr, program_log.error, command)
#start monitoring the outputs
proc_std.start()
proc_err.start()
proc = subprocess.Popen([command] + argv, stdin=stdin,
stdout=pstdin,
stderr=perrin,
preexec_fn=chroot, cwd=root,
env=env)
proc.wait()
ret = proc.returncode
#close the input ends of pipes so we get EOF in the tee processes
os.close(pstdin)
os.close(perrin)
#wait for the output to be written and destroy them
proc_std.join()
del proc_std
proc_err.join()
del proc_err
So, with what you have, you get past the proc.wait() and os.close calls by forking into the background.
proc_std and proc_err are threads that repeatedly call readline on STDOUT and STDERR. They keep reading until EOF is encountered. Since your script inherits the STDOUT and STDERR socket from the %pre script, they will never encounter EOF. Ananconda then hangs waiting for the thread reading STDOUT to exit (on the 'proc_std.join()') line, which never happens.
It's a very confusing issue, but ultimately a very simple fix. Instead of:
/bin/sh /tmp/rpm_watcher.sh &
use
/bin/sh /tmp/rpm_watcher.sh > /dev/null 2>&1 < /dev/null &
This ensures your script doesn't inherit STDOUT and STDERR, so Anaconda doesn't hang and the install can continue.
Yes, this is how you traditionally tell a regular init system what state to boot into. If you're running sysv-init (or pretty much any widely used init system other than systemd), you can put a number between 1 and 5 at the end of the kernel arguments and it will boot into that run level (1 is always single-user mode, the others are system defined, 3 or 4 is what most Linux distros have conventionally used as the default). If you're using systemd, you can pass single
or emergency
at the end of the kernel arguments to boot into those modes respectively.
Using this mechanism for passing arbitrary arguments however is somewhat difficult because the kernel does the absolute minimum of parsing, which means in particular that:
- Arguments with whitespace in them can't be passed at all, because the kernel doesn't parse quoted strings (that is,
'some string'
gets parsed as two arguments 'some
and string'
).
- You can't reference any environment variables at all, because the kernel doesn't do variable substitution (which is normally done by the shell you're running the command from before it even starts the command).
- In general, the arguments have to be correctly interpretable under the POSIX C locale (essentially US ASCII), which throws internationalization out the window unless you want to use something like base64 or punycode.
- There is an upper limit on how much data can be passed in kernel arguments, but I forget what it is.
These limitations combined are why you can't find anything on Google about doing this type of thing, no systems integration engineer in their right mind does it, because it's far more effort to work around the above limitations than it is to just write a script containing all the required arguments and call that instead.
Best Answer
init is not "spawned" (as a child process), but rather
exec
'd like this:exec
replaces the entire process in place. The final init is still the first process (pid 1), even though it was preceded with those in the Initramfs.The Initramfs
/init
, which is a Busybox shell script with pid 1,exec
s to Busyboxswitch_root
(so nowswitch_root
is pid 1); this program changes your mount points so/mnt/root
will be the new/
.switch_root
then againexec
s to/sbin/init
of your real root filesystem; thereby it makes your real init system the first process with pid 1, which in turn may spawn any number of child processes.Certainly it could just as well be done with a Python script, if you somehow managed to bake Python into your Initramfs. Although if you don't plan to include busybox anyway, you would have to painstakingly reimplement some of its functionality (like
switch_root
, and everything else you would usually do with a simple command).However, it does not work on kernels that do not allow script binaries (
CONFIG_BINFMT_SCRIPT=y
), or rather in such a case you'd have to start the interpreter directly and make it load your script somehow.