Sunday, 20 April 2014

MAKEDEV and double virtualization

MAKEDEV can run in a couple of seconds - even on a virtual machine.

But if your virtual machine is hosting a qemu guest, then in that guest a fork/exec can take 0.2 of a second, and MAKEDEV generic-i386 can take a couple of hours. (This is because the kqemu kernel module is not available, http://www.linuxquestions.org/questions/linux-virtualization-and-cloud-90/qemu-running-on-ubuntu-vmware-guest-cannot-find-dev-kvm-936253/).

The first hacky-hack to cut down on the number of fork/exec is to stop calling sed quite so often, cue this patch (requires MAKEDEV to run under bash).

--- /sbin/MAKEDEV 2009-07-30 08:39:09.000000000 -0700
+++ /MAKEDEV 2014-02-21 06:31:43.000000000 -0800
@@ -1,4 +1,4 @@
-#! /bin/sh -
+#! /bin/bash -
 # $Id$
 
 #---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#
@@ -116,7 +116,9 @@
 
 devicename () { # translate device names to something safe
  # A-Z is not full alphabet in all locales (e.g. in et_EE)
- echo "$*" | LC_ALL=C sed -e 's/[^A-Za-z0-9_]/_/g' 
+ #echo "$*" | LC_ALL=C sed -e 's/[^A-Za-z0-9_]/_/g' 
+ echo "${*//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]/_}"
+  
 }
 
 makedev () { # usage: makedev name [bcu] major minor owner group mode
@@ -231,12 +233,14 @@
  exec 3<$procfs/devices
  while read major device extra <&3
  do
-  device=`echo $device | sed 's#/.*##'`
+  #device=`echo $device | sed 's#/.*##'`
+  device="${device%%/*}"
   case "$major" in
    Character|Block|'')
     ;;
    *)
-    safedevname=`devicename $device`
+    safedevname="${device//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234566
+789]/_}"
     eval "major_$safedevname=$major"
     devices="$devices $device"
     ;;
@@ -247,7 +251,8 @@
 
 Major () {
  device=$2
- devname=`devicename $1`
+ devname="${1//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234566
+789]/_}"
  if [ "$opt_d" ]
  then
   echo -1 # don't care
@@ -2149,7 +2154,8 @@
    exec 3<$procfs/devices
    while read major device extra <&3
    do
-    device=`echo $device | sed 's#/.*##'`
+    # device=`echo $device | sed 's#/.*##'`
+    device="${device%%/*}"
     case "$major" in
      Character|Block|'')
       ;;


The next hack is to stop the 5 or 6 fork/exec when MAKEDEV deletes a device node, creates a (temporary device node), chmods it, chowns it, and renames it.

How do we do that? We run MAKEDEV -n so that it does none of these and then feed the output to a perl script which will do them.

Sadly perl has no native mknod call and there are no libraries in this environment, and so I use perl's blessed syscall function with a hard-wired syscall 133 for mknod (as it is, on my kernel):

time /MAKEDEV -n generic-i386 | perl -ne '
print; 
umask(0);
($c, $f, $node, $major, $minor, $own, $perm) = split; 
if ($node eq "->") { 
  symlink($major, $f) || die "sym: $!";
} else {
  # system("mknod",$f,$node,$major,$minor) && die "mknod($f,$node,$major,$minor): $? $!"; 
  # chmod(oct($perm), $f) || die "chmod: $!"; 
  $n=0;
  $n=0010000 if ($node eq "f");
  $n=0020000 if ($node eq "c");
  $n=0060000 if ($node eq "b");
  $n=0140000 if ($node eq "s");
  if (syscall(133, $f, oct($perm) | $n, (($minor & 0xff) | (($major & 0xfff) << 8)
          | (( ($minor & ~0xff)) << 12)
          | (( ($major & ~0xfff)) << 32))) == -1) { die "syscall: $!"; }
  ($user,$group)=split(/:/,$own);
  $user=getpwnam($user);
  $group=getgrnam($group);
  chown($user,$group,$f) || die "chown: $own $!"; 
}
'

Leaving in the system("mknod,...) took around 24 minutes, but moving straight to syscall(mknod,...) takes1 minute 8 seconds.

I did toy with having bash use printf or something to pack a binary tar archive (or even getting perl to pack a tar archive) to pipe to tar -x, but... this will have to do for now.

I suspect this depends on a bug in MAKEDEV which seems to still create sub-directories needed even in -n mode.

It would have been better to pipe the output to a c program which would parse it, I will do that, another day...