[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Multiple console support in d-i

On 2019-01-25 03:45 +0000, Wookey wrote:
> So, unless anyone can see a problem with this approach, I'll finish
> this off. Trying to do it with separate /var/run/consoles and
> /var/run/extra-consoles files was getting very messy. 

OK. This took way longer than I hoped as it was not entirely trivial
to get everything working.

Attached is a patch which provides working multiconsole support for
linux (not hurd or bsd, sorry).

After getting the proc/console choosing code working nicely the
installer was still mysteriously not working (nothing on consoles
except debug) unless I let it respawn in which case it sort of worked
(things appeared but input oddness and continuous respawning isn't much
use to anyone).

I was confused for a while as to what was going on but eventually worked it out.

Just to recap:
The objective here is to run D-I on all the enabled consoles, (and
ideally not fiddle with the code any more than is needed).

First step was upgrading the console parsing code to use
/proc/consoles to get the list, noting the preferred console if there
is one marked as such.

The main complication is that reopen-consoles is
run twice by init, once with debian-installer-startup and once with debian-installer:
# main rc script
::sysinit:/sbin/reopen-console /sbin/debian-installer-startup
# main setup program
::respawn:/sbin/reopen-console /sbin/debian-installer

The first run of reopen-console works out which consoles to use, and
writes it in a file, (/var/run/console-devices), the second run just
uses that file.
debian-installer-startup is just a shell script that runs through all the
debian-installer-startup.d rc scripts, like S01mount, S04countcpus-linux-hppa, S10syslog.

debian-installer actually runs the installer, on the second invocation
of reopen-consoles.

So the original plan was just to run $@ (debian-installer) on the
found consoles. But doing that for the rc scripts is not helpful. Most
of it is idempotent, but you end up with two syslogs and two klogds
and running it all twice on different consoles really isn't right.

So I added an --all-consoles option to declare that we want something
(/sbin/debian-installer in this case) run on all the consoles.

# main rc script
::sysinit:/sbin/reopen-console /sbin/debian-installer-startup
# main setup program
::respawn:/sbin/reopen-console --all-consoles /sbin/debian-installer

It's also worth noting that 'steal-ctty' cannot set the process as
'controlling tty' when two are run, because only one process can be
controlling tty in a session. So I did add error checking to that (it
had none before), but had to take it out again for the 'set ctty'
IOCTL as it's correct for it to fail in the muti-console case.

So what happens now is that the rc-scripts are run on the 'preferred'
console (or the first console listed if none are marked preferred),
then D-I is run on all of them. It does not return to init in this
case (as previously discussed).

This is an important improvement so despite it having ended up rather
late in the day, I hope we can include this for buster. I'm happy to
do more work on tidying up any breakage if we find any.

Future work:

All this faffage has made me realise that a better approach to all
this would probably be to get rid of all this 'steal-ctty' bodgery,
and use init to do it's job: it's designed to run multiple things on
multiple consoles, and deal with file handles and them failing etc.

The catch is that to make this work we'd have to use sysinit: to run
the console-choosing code, then write a new inittab containing one
respawn:/sbin/debian-installer for each console instance, then HUP
init. This should do exactly the right thing (assuming that busybox
init isn't too thick to get HUPing right).

That is way cleaner and I'm happy to have a go at that, but it feels
more intrusive and unless I'm very lucky it may take a while so I
sugest we go with the above for now, as it works already.

One bug I just noticed in the bit we did today is that the 'default'
preferred console (for when one is not indicated by the kernel) avoids
the existence-check for the /dev file, so that should be
improved. It's not hard, but I'll resist doing it now and sending an
untested patch :-)

Principal hats:  Linaro, Debian, Wookware, ARM
diff --git a/src/etc/inittab-linux b/src/etc/inittab-linux
index a7b8a23..437e208 100644
--- a/src/etc/inittab-linux
+++ b/src/etc/inittab-linux
@@ -5,7 +5,7 @@
 ::sysinit:/sbin/reopen-console /sbin/debian-installer-startup
 # main setup program
-::respawn:/sbin/reopen-console /sbin/debian-installer
+::respawn:/sbin/reopen-console --all-consoles /sbin/debian-installer
 # convenience shells
diff --git a/src/sbin/reopen-console-linux b/src/sbin/reopen-console-linux
index 3287dd0..32dfd24 100755
--- a/src/sbin/reopen-console-linux
+++ b/src/sbin/reopen-console-linux
@@ -1,74 +1,67 @@
 # In order to give proper access to the tty, we need to locate the device
-# corresponding to the console we are actually using.
+# corresponding to each console we are actually using.
+# This script is run twice, once at sysinit to run the debian-installer-startup
+# rc scripts, and once to start the installer itself.
+# The first time it parses the consoles used, the second time they are read from files
+# The startup scripts need to be run just once (on one console) (not idempotent)
+# The installer is run on all the enabled consoles (using the --all-consoles option)
-if ! [ -f /var/run/console-device ]; then
-	# If the kernel emitted a "handover" message, then it's the one
-	case $(uname -r) in
-	    2.6.2*|2.6.3[01]*)
-		console="$(dmesg -s 262143 |
-			sed -n -r -e 's/(.*\])? *console handover: boot \[.*\] -> real \[(.*)\]$/\2/p')"
-		;;
-	    2.6.3[234567]*)
-		console="$(dmesg -s 262143 |
-			sed -n -r -e 's/(.*\])? *console \[(.*)\] enabled, bootconsole disabled$/\2/p')"
-		;;
-	    *) # >= 2.6.38
-		console_major_minor="$(get-real-console-linux)"
-		console_raw="$(readlink "/sys/dev/char/${console_major_minor}")"
-		console="${console_raw##*/}"
-		;;
-	esac
-	# Except if it is the wrong type...
-	if [ "$console" ] && [ "$(console-type)" = serial ] && \
-	   expr "$console" : "tty[0-9]" >/dev/null; then
-		console=
-	fi
+if ! [ -f /var/run/console-devices ]; then
-	if [ -z "$console" ]; then
-		# Retrieve all enabled consoles from boot log; ignore those
-		# for which no device file exists
-		for cons in $(dmesg -s 262143 |
-			sed -n -r -e 's/(.*\])? *console \[(.*)\] enabled/\2/p')
-		do
-			if [ -e "/dev/$cons" ]; then
-				consoles="${consoles:+$consoles$NL}$cons"
-			fi
-		done
-		# Only one console? Then we are good.
-		if [ $(echo "$consoles" | wc -l) -eq 1 ]; then
-			console="$consoles"
+	preferred=
+	# Retrieve all enabled consoles from kernel; ignore those
+	# for which no device file exists
+	kernelconsoles="$(cat /proc/consoles)"
+	for cons in $(echo "$kernelconsoles" | sed -n -r -e 's/(^.*)  .*\((.*)\).*$/\1/p' )
+	do
+		# prefer first console listed if none marked preferred
+		if [ "$preferred" = "" ]; then
+			preferred="$cons"
-	fi
+		status=$(echo "$kernelconsoles" | grep $cons | sed -n -r -e 's/(^.*) *.*\((.*)\).*$/\2/p' )
+		if [ -e "/dev/$cons" ] && [ $(echo "$status" | grep -o 'E') ]; then
+			consoles="${consoles:+$consoles$NL}$cons"
+		fi
+		# 'C' console is 'most prefered'.
+		if [ $(echo "$status" | grep -o 'C') ]; then
+			preferred="$cons"
+		fi
+	done
-	if [ -z "$console" ]; then
-		# Locate the last enabled console present on the command line
-		for arg in $(cat /proc/cmdline); do
-			case $arg in
-			    console=*)
-				arg=${arg#console=}
-				cons=${arg%%,*}
-				if echo "$consoles" | grep -q "^$cons$"; then
-					console=$cons
-				fi
-				;;
-			esac
-		done
+	if [ -z "$consoles" ]; then
+		# Nothing found? Default to /dev/console.
+		consoles=console
+	for cons in $consoles
+	do
+		echo "/dev/$cons " >> /var/run/console-devices
+	done
+	echo "/dev/$preferred " > /var/run/console-preferred
-	if [ -z "$console" ]; then
-		# Still nothing? Default to /dev/console.
-		console=console
-	fi
-	echo /dev/$console > /var/run/console-device
+# run startup scripts on one console, D-I itself on all consoles
+if [ "$1"x = "--all-consoles"x ]; then
+	shift
+	# Start d-i on each console.
+	for cons in $(cat /var/run/console-devices)
+	do
+		/sbin/steal-ctty $cons "$@" &
+	done
+	#Don't respawn in init if running installer on multiple consoles
+	sleep 2147483647  #'infinity' not supported in D-I busybox; 68 years will have to do
+	cons=$(cat /var/run/console-preferred)
+	# Some other session may have console as ctty. Steal it from them
+	exec /sbin/steal-ctty $cons "$@"
-# Some other session may have it as ctty. Steal it from them
-exec /sbin/steal-ctty $(cat /var/run/console-device) "$@"
diff --git a/src/sbin/steal-ctty.c b/src/sbin/steal-ctty.c
index 0f3b14f..b99c1bb 100644
--- a/src/sbin/steal-ctty.c
+++ b/src/sbin/steal-ctty.c
@@ -28,8 +28,14 @@ int main(int argc, char ** argv)
     while (fd > 2) {
-    ioctl(0, TIOCSCTTY, (char *) 1);
-    execvp(argv[2], &argv[2]);
+    /* make controlling tty if possible - can't be done if D-I is 
+       run on multiple consoles so just quietly move on */
+    if (-1 == ioctl(0, TIOCSCTTY, (char *) 1)) {
+      }
+    if (-1 == execvp(argv[2], &argv[2])) {
+        perror("execvp");
+        return 1;
+    }
     /* never reached. */
     return 0;

Attachment: signature.asc
Description: PGP signature

Reply to: