Merge pull request #39 from marcosps/mpdesouza_mkinitramfs
Make virtme-mkinitramfs great again
diff --git a/bin/virtme-prep-kdir-mods b/bin/virtme-prep-kdir-mods
new file mode 100755
index 0000000..be28fb6
--- /dev/null
+++ b/bin/virtme-prep-kdir-mods
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# This is still a bit of an experiment.
+
+if ! [ -d .tmp_versions ]; then
+ echo 'virtme-prep-kdir-mods must be run from a kernel build directory' >&2
+ exit 1
+fi
+
+FAKEVER=0.0.0
+MODDIR=".virtme_mods/lib/modules/$FAKEVER"
+
+if ! [ -f "modules.order" ]; then
+ echo "modules.order is missing. Your kernel may be too old or you didn't make modules." >&2
+ exit 1
+fi
+
+# Set up .virtme_mods/lib/modules/0.0.0 as a module directory for this kernel,
+# but fill it with symlinks instead of actual modules.
+
+mkdir -p "$MODDIR/kernel"
+ln -srfT . "$MODDIR/build"
+
+# Remove all preexisting symlinks and add symlinks to all modules that belong
+# to the build kenrnel.
+find "$MODDIR/kernel" -type l -print0 |xargs -0 rm -f --
+while read -r i; do
+ [ ! -e "$i" ] && i=$(echo "$i" | sed s:^kernel/::)
+ mkdir -p "$MODDIR/kernel/$(dirname "$i")"
+ ln -sr "$i" "$MODDIR/kernel/$i"
+done < modules.order
+
+
+# Link in the files that make modules_install would copy
+ln -srf modules.builtin modules.builtin.modinfo modules.order "$MODDIR/"
+
+# Now run depmod to collect dependencies
+depmod -ae -F System.map -b .virtme_mods "$FAKEVER"
diff --git a/setup.py b/setup.py
index 33b8a27..a0b804e 100755
--- a/setup.py
+++ b/setup.py
@@ -30,6 +30,9 @@
'virtme-mkinitramfs = virtme.commands.mkinitramfs:main',
]
},
+ scripts = [
+ 'bin/virtme-prep-kdir-mods',
+ ],
package_data = {
'virtme.guest': [
'virtme-init',
diff --git a/virtme/commands/configkernel.py b/virtme/commands/configkernel.py
index 0da6fab..bd3d42f 100644
--- a/virtme/commands/configkernel.py
+++ b/virtme/commands/configkernel.py
@@ -121,7 +121,14 @@
if maketarget:
subprocess.check_call(['make', 'ARCH=%s' % arch.linuxname, maketarget])
- with open('.config', 'ab') as conffile:
+ config = '.config'
+
+ # Check if KBUILD_OUTPUT is defined and if it's a directory
+ config_dir = os.environ.get('KBUILD_OUTPUT', '')
+ if config_dir and os.path.isdir(config_dir):
+ config = os.path.join(config_dir, config)
+
+ with open(config, 'ab') as conffile:
conffile.write('\n'.join(conf).encode('utf-8'))
subprocess.check_call(['make', 'ARCH=%s' % arch.linuxname, updatetarget])
diff --git a/virtme/commands/run.py b/virtme/commands/run.py
index c40e225..e0a00eb 100644
--- a/virtme/commands/run.py
+++ b/virtme/commands/run.py
@@ -15,6 +15,7 @@
import shlex
import re
import itertools
+import pkg_resources
from .. import virtmods
from .. import modfinder
from .. import mkinitramfs
@@ -41,6 +42,9 @@
help='Use a compiled kernel source directory')
g = parser.add_argument_group(title='Kernel options')
+ g.add_argument('--mods', action='store', metavar='none|use|auto', default='use',
+ help='Setup loadable kernel modules inside a compiled kernel source directory (used in conjunction with --kdir); none: ignore kernel modules, use: asks user to refresh virtme\'s kernel modules directory, auto: automatically refreshes virtme\'s kernel modules directory')
+
g.add_argument('-a', '--kopt', action='append', default=[],
help='Add a kernel option. You can specify this more than once.')
@@ -113,12 +117,18 @@
_ARGPARSER = make_parser()
-def arg_fail(message):
+def arg_fail(message, show_usage=True):
print(message)
- _ARGPARSER.print_usage()
+ if show_usage:
+ _ARGPARSER.print_usage()
sys.exit(1)
+def is_file_more_recent(a, b):
+ return os.stat(a).st_mtime > os.stat(b).st_mtime
+
def find_kernel_and_mods(arch, args):
+ use_root_mods = False
+
if args.installed_kernel is not None:
kver = args.installed_kernel
modfiles = modfinder.find_modules_from_install(
@@ -128,21 +138,47 @@
if not os.path.exists(kimg):
kimg = '/boot/vmlinuz-%s' % kver
dtb = None # For now
+ use_root_mods = True
elif args.kdir is not None:
kimg = os.path.join(args.kdir, arch.kimg_path())
- modfiles = []
- moddir = None
+ virtme_mods = os.path.join(args.kdir, '.virtme_mods')
+ mod_file = os.path.join(args.kdir, 'modules.order')
+ virtme_mod_file = os.path.join(virtme_mods, 'lib/modules/0.0.0/modules.dep')
- # Once kmod gets fixed (if ever), we can do something like:
- # modfiles = modfinder.find_modules_from_install(
- # virtmods.MODALIASES,
- # moddir=os.path.join(args.kernel_build_dir, '.tmp_moddir'))
+ # Kernel modules support
+ kver = None
+ moddir = None
+ modfiles = []
+ if args.mods == 'none':
+ pass
+ elif args.mods == 'use' or args.mods == 'auto':
+ # Check if modules.order exists, otherwise it's not possible to use
+ # this option
+ if not os.path.exists(mod_file):
+ arg_fail('%s not found: kernel modules not enabled or kernel not compiled properly' % mod_file, show_usage=False)
+ # Check if virtme's kernel modules directory needs to be updated
+ if not os.path.exists(virtme_mods) or \
+ is_file_more_recent(mod_file, virtme_mod_file):
+ if args.mods == 'use':
+ # Inform user to manually refresh virtme's kernel modules
+ # directory
+ arg_fail("please run virtme-prep-kdir-mods to update virtme's kernel modules directory or use --mods=auto", show_usage=False)
+ else:
+ # Auto-refresh virtme's kernel modules directory
+ guest_tools.run_script('virtme-prep-kdir-mods')
+ moddir = os.path.join(virtme_mods, 'lib/modules', '0.0.0')
+ modfiles = modfinder.find_modules_from_install(
+ virtmods.MODALIASES, root=virtme_mods, kver='0.0.0')
+ else:
+ arg_fail("invalid argument '%s', please use --mods=none|use|auto" % args.mods)
dtb_path = arch.dtb_path()
if dtb_path is None:
dtb = None
else:
dtb = os.path.join(args.kdir, dtb_path)
+ elif args.mods is not None:
+ arg_fail("--mods must be used together with --kdir")
elif args.kimg is not None:
kimg = args.kimg
modfiles = []
@@ -151,7 +187,7 @@
else:
arg_fail('You must specify a kernel to use.')
- return kimg,dtb,modfiles,moddir
+ return kimg,dtb,modfiles,moddir,use_root_mods
def export_virtfs(qemu, arch, qemuargs, path, mount_tag, security_model='none', readonly=True):
# NB: We can't use -virtfs for this, because it can't handle a mount_tag
@@ -189,7 +225,7 @@
config = mkinitramfs.Config()
- kimg,dtb,modfiles,moddir = find_kernel_and_mods(arch, args)
+ kimg,dtb,modfiles,moddir,use_root_mods = find_kernel_and_mods(arch, args)
config.modfiles = modfiles
if config.modfiles:
need_initramfs = True
@@ -216,9 +252,20 @@
'/bin/mount -n -t 9p -o ro,version=9p2000.L,trans=virtio,access=any virtme.guesttools /run/virtme/guesttools',
'exec /run/virtme/guesttools/virtme-init']
- # Map modules
+ # Arrange for modules to end up in the right place
if moddir is not None:
- export_virtfs(qemu, arch, qemuargs, moddir, 'virtme.moddir')
+ if use_root_mods:
+ # Tell virtme-init to use the root /lib/modules
+ kernelargs.append("virtme_root_mods=1")
+ else:
+ # We're grabbing modules from somewhere other than /lib/modules.
+ # Rather than mounting it separately, symlink it in the guest.
+ # This allows symlinks within the module directory to resolve
+ # correctly in the guest.
+ kernelargs.append("virtme_link_mods=/%s" % qemu.quote_optarg(os.path.relpath(moddir, args.root)))
+ else:
+ # No modules are available. virtme-init will hide /lib/modules/KVER
+ pass
# Set up mounts
mount_index = 0
@@ -306,7 +353,7 @@
name,fn = namefile
if '=' in fn or ',' in fn:
arg_fail("--disk filenames cannot contain '=' or ','")
- if '=' in fn or ',' in name:
+ if '=' in name or ',' in name:
arg_fail("--disk device names cannot contain '=' or ','")
driveid = 'disk%d' % i
qemuargs.extend(['-drive', 'if=none,id=%s,file=%s' % (driveid, fn),
diff --git a/virtme/guest/virtme-init b/virtme/guest/virtme-init
index cd3caa4..f60faa5 100755
--- a/virtme/guest/virtme-init
+++ b/virtme/guest/virtme-init
@@ -27,10 +27,12 @@
kver="`uname -r`"
-if [[ -n "${mount_tags[virtme.moddir]}" ]]; then
- mount -t tmpfs none /lib/modules
- mkdir /lib/modules/"$kver"
- mount -n -t 9p -o ro,version=9p2000.L,trans=virtio,access=any virtme.moddir /lib/modules/"$kver"
+if [[ -n "$virtme_root_mods" ]]; then
+ # /lib/modules is already set up
+ true
+elif [[ -n "$virtme_link_mods" ]]; then
+ mount -n -t tmpfs none /lib/modules
+ ln -s "$virtme_link_mods" "/lib/modules/$kver"
elif [[ -d "/lib/modules/$kver" ]]; then
# We may have mismatched modules. Mask them off.
mount -n -t tmpfs -o ro,mode=0000 disallow_modules "/lib/modules/$kver"
diff --git a/virtme/guest_tools.py b/virtme/guest_tools.py
index 96c1cc2..b84f761 100644
--- a/virtme/guest_tools.py
+++ b/virtme/guest_tools.py
@@ -1,14 +1,16 @@
# -*- mode: python -*-
# resources.py: Find virtme's resources
-# Copyright © 2014 Andy Lutomirski
+# Copyright © 2014-2019 Andy Lutomirski
# Licensed under the GPLv2, which is available in the virtme distribution
# as a file called LICENSE with SHA-256 hash:
# 8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643
-"""Helpers to find virtme's guest tools."""
+"""Helpers to find virtme's guest tools and host scripts."""
import os
+import shutil
import pkg_resources
+import subprocess
def find_guest_tools():
"""Return the path of the guest tools installed with the running virtme.
@@ -19,3 +21,22 @@
# No luck. This is somewhat surprising.
return None
+
+def find_script(name):
+ # If we're running out of a source checkout, we can find scripts through
+ # the 'virtme/scripts' symlink.
+ fn = pkg_resources.resource_filename(__name__, 'scripts/%s' % name)
+ if os.path.isfile(fn):
+ return fn
+
+ # Otherwise assume we're actually installed and in PATH.
+ fn = shutil.which(name)
+ if fn is not None:
+ return fn
+
+ # No luck. This is somewhat surprising.
+ raise Exception('could not find script %s' % name)
+
+def run_script(name):
+ fn = find_script(name)
+ subprocess.check_call(executable=fn, args=[fn])
diff --git a/virtme/mkinitramfs.py b/virtme/mkinitramfs.py
index baaefe9..dde504d 100644
--- a/virtme/mkinitramfs.py
+++ b/virtme/mkinitramfs.py
@@ -9,6 +9,7 @@
import io
import os.path
import shlex
+import itertools
from . import cpiowriter
from . import modfinder
from . import virtmods
@@ -40,6 +41,7 @@
cw.write_file(name=b'bin/modprobe', body=b'\n'.join([
b'#!/bin/sh',
b'echo "virtme: initramfs does not have module $3" >/dev/console',
+ b'exit 1',
]), mode=0o755)
_LOGFUNC = """log() {
@@ -165,12 +167,12 @@
cw.write_trailer()
def find_busybox(root, is_native):
- for path in ('usr/local/bin/busybox', 'usr/local/sbin/busybox',
- 'usr/bin/busybox-static',
- 'usr/bin/busybox', 'usr/sbin/busybox',
- 'bin/busybox', 'sbin/busybox'):
- if os.path.isfile(os.path.join(root, path)):
- return os.path.join(root, path)
+ for p in itertools.product(['usr/local', 'usr', ''],
+ ['bin', 'sbin'],
+ ['', '-static', '.static']):
+ path = os.path.join(root, p[0], p[1], 'busybox' + p[2])
+ if os.path.isfile(path):
+ return path
if is_native:
# Try the host's busybox, if any
diff --git a/virtme/scripts b/virtme/scripts
new file mode 120000
index 0000000..19f285a
--- /dev/null
+++ b/virtme/scripts
@@ -0,0 +1 @@
+../bin
\ No newline at end of file