make-rc: A parallel (as in make(1)) alternative to sysv-rc

Alejandro Colomar (man-pages) alx.manpages at
Wed Jan 5 02:03:53 GMT 2022

Hi all,

Most of you I added you to this email because I found you on the 
maintainers list for the Debian sysv-rc package (now dead for a long time).
I also CCd Devuan, since I hope you'll be interested in this little 
project of mine.
I also CCd linux-man@, since there's not many people listening there, 
and not much traffic these days so I hope you won't be angry for this 
little bit of spam; and I hope some good guys reading that list may have 
some comments on this.  Sorry again for the bit of spam.
I also CCd help-make at .  I (ab)used your software in a way that it was 
never designed to; not sorry for that; I'll say it was the original Unix 
authors' fault for designing such (ab)usable tools :).  Maybe you're 
interested in this thread, maybe you don't care, but I'll CC you in case 
some of you may be interested in this.
And finally, Randy, as you asked, I'll CC you for news on this.
Anyone not interested in follow-ups, please email me, and I'll try to 
remove from CC.

So, last friday (yes, that's New Year's Eve), I was reading something, 
and got this idea... the main valid claim for systemd is that it blows 
away competition in terms of performance?  Full parallelization?  Knows 
about dependencies?  I don't know too much of systems; I know some basic 
stuff, but I'm mostly a C programmer, so I don't know init(1)... okay. 
But since I program, I sure know the good ol' make(1).  It's old, and 
it's good at fully parallelizing _everything_.  Dependencies? sure; 
fast? sure; parallel? sure; simple? sure; a bit bloated? GNU make maybe, 
especially for init(1), but far from systemd(1):

$ ls -lh $(realpath $(which systemd make bash sh 2>/dev/null))
-rwxr-xr-x 1 root root 1.2M Dec 15 00:43 /usr/bin/bash
-rwxr-xr-x 1 root root 123K Nov  3 11:51 /usr/bin/dash
-rwxr-xr-x 1 root root 235K Apr 10  2021 /usr/bin/make
-rwxr-xr-x 1 root root 1.8M Nov 19 21:11 /usr/lib/systemd/systemd

So, if the problem is that the rc scripts don't run parallel and don't 
know about exact dependencies from each-other, let's rewrite that part 
and only that part of the system with make(1); that was the idea.  Don't 
touch init, don't touch the scripts themselves... only the part that 
decides on which order to run them.  That's the idea.

I decided to start with a clean install of Devuan (since I'm a Debian 
user, and I'm used to it; it might have been easier to start with a 
smaller system, but I might have found other issues in the middle;  I 
know that the only difference in Devuan is sysvinit, which is what I 
want).  The install has XFCE and sysvinit as options.  I wanted the 
system to have GUI, so that if I can boot XFCE with it, I can boot 
anything (except maybe for the bad boy, GNOME, but I don't even like it).

I wrote a couple of scripts to port the existing rc system to my 
make-based rc system.  The script didn't touch the old one, in case I 
needed it to boot if I screwed something, of course.  One script creates 
the makefiles without declaring the dependencies between them, and then 
another makefile creates the dependencies to match the current boot 
order (allowing for the same level of parallelization that the current 
semi-parallel rc script uses).  From there, one could analyze the 
dependencies to remove some that are incorrect, and make it faster, but 
since I don't know the real dependencies, I didn't want to break stuff.

So, here go the scripts:

====================== ========================

find /etc/rc[0123456S].d/[KS]* \
|sed s,/etc/rc..d/...,, \
|sort \
|uniq \
|while read x; do\

	test -e $mk \
		&& continue;

	>$mk cat <<EOF
.PHONY: K$x S$x

: K$x

: S$x

	/etc/init.d/$x stop

	/etc/init.d/$x start

	find /etc/rc[0123456S].d/K*$x 2>/dev/null \
	|sed s,/etc/rc,, \
	|sed s,.d/K.*,, \
	|sort \
	|tac \
	|while read n; do \
		sed -i "/: K$x$/s/^/$n /" $mk;

	find /etc/rc[0123456S].d/S*$x 2>/dev/null \
	|sed s,/etc/rc,, \
	|sed s,.d/S.*,, \
	|sort \
	|tac \
	|while read n; do \
		sed -i "/: S$x$/s/^/$n /" $mk;

	sed -i "s/ *:/:/" $mk;
	sed -i "/^:/d"    $mk;

===================== ===================

find /etc/*.mk \
|sed s,/etc/,, \
|sed s,.mk$,, \
|sort \
|while read x; do\

	for n in 0 1 2 3 4 5 6 S; do \
		K=$(find /etc/rc$n.d/K??$x 2>/dev/null \
		    |grep -o /K.. \
		    |sed s,/K0*,,);
		test -z "$K" || test $K -eq 1 \
			&& continue;
		k=$(printf %02d $(($K - 1)));

		find /etc/rc$n.d/K$k* \
		|sed s,/etc/rc$n.d/K$k,,;
	done \
	|sort \
	|uniq \
	|while read dep; do \
		echo K$dep;
	done \
	|xargs echo \
	|while read Kdeps; do
		test -z "$Kdeps" \
			&& break;

		>>$mk cat <<-EOF

		K$x: $Kdeps

	for n in 0 1 2 3 4 5 6 S; do \
		S=$(find /etc/rc$n.d/S??$x 2>/dev/null \
		    |grep -o /S.. \
		    |sed s,/S0*,,);
		test -z "$S" || test $S -eq 1 \
			&& continue;
		s=$(printf %02d $(($S - 1)));

		find /etc/rc$n.d/S$s* \
		|sed s,/etc/rc$n.d/S$s,,;
	done \
	|sort \
	|uniq \
	|while read dep; do \
		echo S$dep;
	done \
	|xargs echo \
	|while read Sdeps; do
		test -z "$Sdeps" \
			&& break;

		>>$mk cat <<-EOF

		S$x: $Sdeps

	for n in 0 1 2 3 4 5 6 S; do \
		test -e /etc/rc$n.d/S01$x \
			|| continue;
		K=$(find /etc/rc$n.d/K* 2>/dev/null \
		    |grep -o /K.. \
		    |sed s,/K,, \
		    |sort \
		    |uniq \
		    |tac \
		    |head -n1)
		test -z "$K" \
			&& continue;
		k=$(printf %02d $K);

		find /etc/rc$n.d/K$k* \
		|sed s,/etc/rc$n.d/K$k,,;
	done \
	|sort \
	|uniq \
	|while read dep; do \
		echo K$dep;
	done \
	|xargs echo \
	|while read Kdeps; do
		test -z "$Kdeps" \
			&& break;

		>>$mk cat <<-EOF

		S$x: $Kdeps

I know some stuff could go into functions to make them shorter, and 
probably some comments could also help, but I wrote them fast, just to 
have some proof of concept, and later I could makes them nicer.  Special 
thanks to Doug for creating pipes, I love them :)

The One Makefile To Rule Them All is the following:

================ /etc/ ================
MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-builtin-variables

.PHONY: all 0 1 2 3 4 5 6 S

all: 5

0 1 2 3 4 5 6 S:

include /etc/*.mk

And then a sample file (created by the above scripts) is:

================ /etc/ ===============
.PHONY: Kvboxadd Svboxadd

0 1 6: Kvboxadd

2 3 4 5: Svboxadd

	/etc/init.d/vboxadd stop
	sleep 1

	/etc/init.d/vboxadd start
	sleep 1

Kvboxadd: Kalsa-utils Kbrightness Kelogind Knetwork-manager 
Kpulseaudio-enable-autospawn Ksaned Kslim Kspeech-dispatcher Kurandom 


I added the sleep 1 for debug; the rest is the output of the scripts.

As you can see I kept the K... and S... syntax of rc scripts.  However, 
instead of a symlink, now there's a makefile declaring dependencies and 
calls directly the script with no symlink.  Now all makefiles are in a 
single directory (I found it simpler this way), but we could think of a 
different structure if it's better.  These makefiles (or more 
technically, parts of a makefile), resemble the systemd service files, 
where each one declares what should have already run before start and 
before kill, and also contains the info about which runlevels want this 
(both S and K).

Because of the script, and because some scripts used .sh extension and 
some others didn't, there are some traces of that in the names of the 
targets; nothing really problematic, only ugly.  I could have removed 
it, but it was more work, and I noticed late.

I only had to manipulate a single line from only one of these makefiles 
after running the scripts, but that I'll keep if for later;  it was 
about dependencies, because there was a missing number in one of the 
runlevel dirs (no jump from S02 to S04 in one of the dirs, no S03)

And finally, to run all this madness, I had to tweak /lib/init/rc a 
little bit:

diff --git a/rc b/rc
index 5e7f3a4..e1ffa31 100755
--- a/rc
+++ b/rc
@@ -60,7 +60,7 @@ export VERBOSE
  # insserv package to be enabled. Boot concurrency also requires
  # startpar to be installed.
  test -s /etc/init.d/.depend.boot  || CONCURRENCY="none"
  test -s /etc/init.d/.depend.start || CONCURRENCY="none"
  test -s /etc/init.d/.depend.stop  || CONCURRENCY="none"
@@ -168,7 +168,10 @@ then

-       if [ makefile = "$CONCURRENCY" ]
+       if [ makefile2 = "$CONCURRENCY" ]
+       then
+               make -C "/etc/" "$runlevel"
+       elif [ makefile = "$CONCURRENCY" ]
                 if [ S = "$runlevel" ]

I'd like to know your thoughts about this.  If it seems like it could be 
a good replacement for sysv-rc (for Devuan; I guess for Debian it's too 
late) I'd like to know.  Or if you think it can be a good thing but 
needs some polish, I'd like to hear it too.  It's just a proof of 
concept after 2 days of having had fun with it :)

I'd like it to be able to compete in terms of performance with 
systemd(1), so that we could possibly take over it, but hey, that's only 
a hope.

And now I'll tell you a bit about the outcome I had:  I booted with this 
configuration, and I first got into single-user mode, which I don't yet 
understand why it happened (there's probably something I didn't get 
right, but no real blocking problem, I guess).  But then, after the 
typical type password or ^D to continue, I pressed ^D, and I got a shiny 
XFCE GUI completely wirking.  Not bad for this prototype, I'd say.  I'd 
like some help to polish this, if you may.

Happy new year!


Alejandro Colomar
Linux man-pages maintainer;

More information about the Debian-init-diversity mailing list