[Yaird-devel] Bug#394389: yaird: Loopback and loop-AES device support

Peter Colberg peterco at gmx.net
Fri Oct 20 22:55:30 UTC 2006


Package: yaird
Version: 0.0.12-18
Severity: wishlist
Tags: patch

Hello,

although I fear that the number of people on this planet who still
strongly prefer loop-AES over dm-crypt, and would wish for full
loop-AES integration into yaird, might effectively be reduced to
one, I decided to finally publish this patch...

Loopback and loop-AES device support for yaird in every possible way.

Some highlights:

- Both native and loop-AES patched losetup/loop module support.

- As there is no need for a custom initrd script any longer, it is
  possible to boot off a loop-AES encrypted root filesystem with a
  standard distribution kernel, supplemented only with a
  loop-aes-modules-* package (and loop-aes-utils).

- Loop-AES devices need not necessarily be mounted directly.

  Using a single loop-AES encrypted device as an LVM physical volume
  allows easy use of multiple filesystems (i.e. LVM logical volumes)
  with a single GPG key loaded at boot time.

- GPG encryption keys are either copied directly onto the initramfs
  image, or may be loaded off an external (unencrypted) device which
  is mounted automatically upon boot time, for example an USB stick.

  Public-key encrypted GPG keys (with a GPG home directory on the
  image or on the external key device) are also supported.

- Suspend-to-disk with (non-random-key) loop-AES encrypted swap.


Instead of enumerating all supported options, here's a list of things
not supported with this implementation:

- File-backed loopback and loop-AES devices,

- Random-key encrypted loop-AES devices (either via losetup option
  phash=random/* or a swap fstab entry with loop= and encryption=
  attributes).

- (Not tested on other distributions besides Debian, yet.)



I would like to shortly mention the only big concern I had while
implementing this, and my proposed solution.

Deriving a complete configuration from losetup output only is not
possible for a loop-AES encrypted device, as losetup only shows the
attributes offset=, sizelimit=  encryption= and loinit=.

For GPG key encryption (with options gpgkey= and gpghome=), it is
necessary to have an additional configuration, e.g. an fstab entry.
However, such an entry is not feasible for a loop device which is
not mounted directly, such as a loop-AES physical LVM volume.

Therefore, I introduced a new configuration file ('/etc/loopaestab')
which specifically contains all options necessary for setting up
loop-AES devices. Its use is only mandatory in case a loop-AES device
does not appear within a loop= attribute in an fstab entry.

My proposal and a possible config format have already been described
in wishlist bug #393363 filed against the loop-aes-utils package.

The supported options would include all losetup options (see man page
description for option -F), and additionally the keyword option
'gpgmount', which instructs an initramfs creator to mount the device
containing the GPG key (and optionally GPG home directory) upon boot.

An example entry might look like this:

# <target name>	<source device>	<options>
/dev/loop0	/dev/md0	encryption=AES256,gpgkey=/boot/gnupg/maia.gpg,gpgmount



Concerning the patch itself, I split it into multiple parts as
included below, applied in ascending order to the patched yaird
0.0.12-18 package source.
The first five patches fix minor bugs I stumbled upon in code
related to loop-AES support, or add some small subroutines for
general purpose use.


=> 1101_base_canon_root_path_fix.patch

   The Base::canon subroutine does not work when given the root path
   '/' as an argument. This is a result of perl's split function:
   split (/\/+/, '/') returns an empty array.


=> 1102_base_filedev_stat_device_of_file.patch

   New Base::filedev subroutine to determine the device (and
   subsequently the filesystem via an fstab entry) a file or
   directory lies on. This is used with the gpgmount support.


=> 1103_fstab_findbydevno.patch

   - Break FsTab::findByDevName into two separate subroutines to
     allow directly looking for an entry by a device number.

   - Do not issue fatal errors within findByMountPoint or
     findByDevName subroutines! This should be handled on a higher
     level, as in Plan::addBlockDevMount or Plan::addFsTabMount.

     As a matter of fact, it *is* already handled there, but
     ironically with erroneous calls to a non-existent subroutine
     'fatal' (should be 'Base::fatal'). One may now take an educated
     guess as to why these bugs were never discovered...


=> 1104_templates_quiet_mksymdev_delay.patch

   When waiting for new /sysfs entries to appear (like with an USB
   stick used as a GPG key device upon booting), do not output
   error messages.


=> 1105_mount_readonly_and_unmount.patch

   This adds an additional argument $isRoot to the
   Plan::addBlockDevMount subroutine to explicitly specify whether
   a device to be mounted is a "root" or a "non-root" device.

   "root" devices are given the $ro /proc/cmdline option, while
   "non-root" devices (e.g. GPG key devices) are always mounted
   read-only.

   Finally, an unmount subroutine and template is added.


=> 1106_add_loopback_and_loopaes_with_loopaestab_support.patch

   This is it.


In conclusion, I would be glad hearing of real-world tests with
yaird and loop-AES support, or any suggestions for improvement.


With the hope of possible discussion and future inclusion in Debian,
and maybe later upstream...

Regards,
Peter


-- System Information:
Debian Release: testing/unstable
  APT prefers unstable
  APT policy: (500, 'unstable'), (400, 'testing'), (1, 'experimental')
Architecture: amd64 (x86_64)
Shell:  /bin/sh linked to /bin/bash
Kernel: Linux 2.6.18-maia
Locale: LANG=C, LC_CTYPE=de_DE at euro (charmap=ISO-8859-15)

Versions of packages yaird depends on:
ii  cpio                         2.6-17      GNU cpio -- a program to manage ar
ii  dash                         0.5.3-3     The Debian Almquist Shell
ii  libc6                        2.3.6.ds1-6 GNU C Library: Shared libraries
ii  libhtml-template-perl        2.8-1       HTML::Template : A module for usin
ii  libparse-recdescent-perl     1.94.free-3 Generates recursive-descent parser
ii  perl                         5.8.8-6.1   Larry Wall's Practical Extraction 

yaird recommends no packages.

-- no debconf information
-------------- next part --------------
diff -urN yaird-0.0.12.orig/perl/Base.pm yaird-0.0.12/perl/Base.pm
--- yaird-0.0.12.orig/perl/Base.pm	2006-10-15 19:42:31.000000000 +0200
+++ yaird-0.0.12/perl/Base.pm	2006-10-15 19:43:03.000000000 +0200
@@ -339,6 +339,9 @@
 	# (2) ../.. is not dropped.
 	# (3) ./.. => ..
 	my @result = ();
+	if ($path eq '/') {
+		push @result, '';
+	}
 	for my $component (split (/\/+/, $path)) {
 		if ($component eq '.') {
 			next;
-------------- next part --------------
diff -urN yaird-0.0.12.orig/perl/Base.pm yaird-0.0.12/perl/Base.pm
--- yaird-0.0.12.orig/perl/Base.pm	2006-10-15 19:43:03.000000000 +0200
+++ yaird-0.0.12/perl/Base.pm	2006-10-15 19:44:17.000000000 +0200
@@ -289,6 +289,26 @@
 	return "$major:$minor";
 }
 
+#
+# filedev -- given pathname to a file or directory, return "maj:min"
+# of the underlying (not necessarily block) device or undef.
+#
+sub filedev ($) {
+	my ($path) = @_;
+	if ( ! (-e $path)) {
+		return undef;
+	}
+
+	my @fields = stat _;
+	if ($#fields != 12) {
+		Base::fatal ("stat failed on file $path");
+	}
+	# from 2.6.10-rc2, kdev.h, backward compatible.
+	my $devno = $fields[0];
+	my $major = ($devno & 0xfff00) >> 8;
+	my $minor = ($devno & 0xff) | (($devno >> 12) & 0xfff00);
+	return "$major:$minor";
+}
 
 #
 # expandLink -- given a path to a symlink file,
-------------- next part --------------
diff -urN yaird-0.0.12.orig/perl/FsTab.pm yaird-0.0.12/perl/FsTab.pm
--- yaird-0.0.12.orig/perl/FsTab.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/FsTab.pm	2006-10-15 21:21:54.000000000 +0200
@@ -94,7 +94,7 @@
 
 sub findByMountPoint ($) {
 	my ($mnt) = @_;
-	my $result;
+	my $result = undef;
 
 	$mnt = Base::canon ($mnt);
 	for my $entry (@{FsTab::all()}) {
@@ -105,9 +105,6 @@
 			$result = $entry;
 		}
 	}
-	if (! defined ($result)) {
-		Base::fatal ("mount point not in fstab: $mnt");
-	}
 	return $result;
 }
 
@@ -120,11 +117,16 @@
 #
 sub findByDevName ($) {
 	my ($dev) = @_;
-	my $result;
 	my $devno = Base::devno ($dev);
 	if (! defined ($devno)) {
 		Base::fatal ("cannot find device number for: $dev");
 	}
+	return findByDevno ($devno);
+}
+
+sub findByDevno ($) {
+	my ($devno) = @_;
+	my $result = undef;
 
 	for my $entry (@{FsTab::all()}) {
 		my ($b2, $msg) = $entry->blockDevPath();
@@ -138,14 +140,11 @@
 
 		if ($n2 eq $devno) {
 			if (defined ($result)) {
-				Base::fatal ("duplicate device name in fstab: $dev");
+				Base::fatal ("duplicate device name in fstab: $b2");
 			}
 			$result = $entry;
 		}
 	}
-	if (! defined ($result)) {
-		Base::fatal ("device name not in fstab: $dev");
-	}
 	return $result;
 }
 
diff -urN yaird-0.0.12.orig/perl/Plan.pm yaird-0.0.12/perl/Plan.pm
--- yaird-0.0.12.orig/perl/Plan.pm	2006-10-15 21:21:19.000000000 +0200
+++ yaird-0.0.12/perl/Plan.pm	2006-10-15 21:22:14.000000000 +0200
@@ -631,7 +631,7 @@
 	#
 	my $root = FsTab::findByDevName($rootDevName);
 	if (! defined ($root)) {
-		fatal ("requested root device ($rootDevName) not in fstab");
+		Base::fatal ("requested root device ($rootDevName) not in fstab");
 	}
 
 	#
@@ -706,7 +706,7 @@
 	my ($actions, $fsTabEntry, $mountPoint) = @_;
 	my $root = FsTab::findByMountPoint($fsTabEntry);
 	if (! defined ($root)) {
-		fatal ("can't find $fsTabEntry in fstab");
+		Base::fatal ("can't find $fsTabEntry in fstab");
 	}
 
 	my ($blockDevName, $msg) = $root->blockDevPath();
-------------- next part --------------
diff -urN yaird-0.0.12.orig/templates/Debian-initrd.cfg yaird-0.0.12/templates/Debian-initrd.cfg
--- yaird-0.0.12.orig/templates/Debian-initrd.cfg	2006-10-15 19:42:31.000000000 +0200
+++ yaird-0.0.12/templates/Debian-initrd.cfg	2006-10-15 19:46:03.000000000 +0200
@@ -120,7 +120,7 @@
 			!	devfile="$1"
 			!	sysfile="$2"
 			!	cb="$3"
-			!	devpair=$(/bin/cat "$sysfile")
+			!	devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	for delay in 1 2 4 8 16
 			!	do
 			!		if [ "$devpair" = "" ]
@@ -128,7 +128,7 @@
 			!			echo "Waiting $delay seconds for $sysfile to show up"
 			!			sleep $delay
 			!		fi
-			!		devpair=$(/bin/cat "$sysfile")
+			!		devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	done
 			!
 			!	if [ "$devpair" = "" ]
diff -urN yaird-0.0.12.orig/templates/Debian.cfg yaird-0.0.12/templates/Debian.cfg
--- yaird-0.0.12.orig/templates/Debian.cfg	2006-10-15 19:42:32.000000000 +0200
+++ yaird-0.0.12/templates/Debian.cfg	2006-10-15 19:46:03.000000000 +0200
@@ -103,7 +103,7 @@
 			!	devfile="$1"
 			!	sysfile="$2"
 			!	cb="$3"
-			!	devpair=$(/bin/cat "$sysfile")
+			!	devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	for delay in 1 2 4 8 16
 			!	do
 			!		if [ "$devpair" = "" ]
@@ -111,7 +111,7 @@
 			!			echo "Waiting $delay seconds for $sysfile to show up"
 			!			sleep $delay
 			!		fi
-			!		devpair=$(/bin/cat "$sysfile")
+			!		devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	done
 			!
 			!	if [ "$devpair" = "" ]
diff -urN yaird-0.0.12.orig/templates/Fedora.cfg yaird-0.0.12/templates/Fedora.cfg
--- yaird-0.0.12.orig/templates/Fedora.cfg	2006-10-15 19:42:32.000000000 +0200
+++ yaird-0.0.12/templates/Fedora.cfg	2006-10-15 19:46:03.000000000 +0200
@@ -103,7 +103,7 @@
 			!	devfile="$1"
 			!	sysfile="$2"
 			!	cb="$3"
-			!	devpair=$(/bin/cat "$sysfile")
+			!	devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	for delay in 1 2 4 8 16
 			!	do
 			!		if [ "$devpair" = "" ]
@@ -111,7 +111,7 @@
 			!			echo "Waiting $delay seconds for $sysfile to show up"
 			!			sleep $delay
 			!		fi
-			!		devpair=$(/bin/cat "$sysfile")
+			!		devpair=$(/bin/cat "$sysfile" 2>/dev/null)
 			!	done
 			!
 			!	if [ "$devpair" = "" ]
-------------- next part --------------
diff -urN yaird-0.0.12.orig/perl/Plan.pm yaird-0.0.12/perl/Plan.pm
--- yaird-0.0.12.orig/perl/Plan.pm	2006-10-15 21:22:14.000000000 +0200
+++ yaird-0.0.12/perl/Plan.pm	2006-10-15 21:25:09.000000000 +0200
@@ -623,8 +623,8 @@
 # addBlockDevMount -- add list of actions to mount named device
 # at mountPoint: activate device, activate fstype, do mount.
 #
-sub addBlockDevMount ($$$) {
-	my ($actions, $rootDevName, $mountPoint) = @_;
+sub addBlockDevMount ($$$$) {
+	my ($actions, $rootDevName, $mountPoint, $isRoot) = @_;
 
 	#
 	# Device must be in fstab, to determine options
@@ -687,11 +687,12 @@
 	my $yspecial = $abd->yspecial();
 	my $opts = $root->opts->cmdLineVersion();
 
-	# XXX - isRoot should be readOnly, and configurable.
+	# mount devices explicitly specified as non-root read-only
 	$actions->add ("mount", $mountPoint,
 		options => $opts,
 		fsType => $fsType,
-		isRoot => 1,
+		isRoot => $isRoot,
+		readOnly => (! $isRoot),
 		device => $yspecial);
 }
 
@@ -713,7 +714,15 @@
 	if (! defined ($blockDevName)) {
 		Base::fatal ($msg);
 	}
-	addBlockDevMount ($actions, $blockDevName, $mountPoint);
+	addBlockDevMount ($actions, $blockDevName, $mountPoint, 1);
+}
+
+#
+# addUnmount -- detach device mounted at mountPoint
+#
+sub addUnmount ($$) {
+	my ($actions, $mountPoint) = @_;
+	$actions->add ("umount", $mountPoint);
 }
 
 #
@@ -792,7 +801,7 @@
 		elsif ($type eq 'mountdev') {
 			my $mountPoint = $goal->{mountPoint};
 			Base::assert (defined ($mountPoint));
-			addBlockDevMount ($actions, $value, $mountPoint);
+			addBlockDevMount ($actions, $value, $mountPoint, 1);
 		}
 		else {
 			Base::fatal ("Unknown goal");
diff -urN yaird-0.0.12.orig/templates/Debian-initrd.cfg yaird-0.0.12/templates/Debian-initrd.cfg
--- yaird-0.0.12.orig/templates/Debian-initrd.cfg	2006-10-15 21:21:19.000000000 +0200
+++ yaird-0.0.12/templates/Debian-initrd.cfg	2006-10-15 21:25:09.000000000 +0200
@@ -392,6 +392,7 @@
 		BEGIN
 			!/bin/mount -n \
 			!	<TMPL_IF NAME=isRoot>$ro</TMPL_IF> \
+			!	<TMPL_IF NAME=readOnly>-r</TMPL_IF> \
 			!	-t <TMPL_VAR NAME=fsType> \
 			!	<TMPL_VAR NAME=options> \
 			!	'<TMPL_VAR NAME=device>' \
@@ -399,6 +400,14 @@
 		END SCRIPT
 	END TEMPLATE
 
+	TEMPLATE umount
+	BEGIN
+		SCRIPT "/sbin/init"
+		BEGIN
+			!/bin/umount -n <TMPL_VAR NAME=target>
+		END SCRIPT
+	END TEMPLATE
+
 	TEMPLATE nfsstart
 	BEGIN
 		FILE "<TMPL_VAR NAME=auxDir>/trynfs"
diff -urN yaird-0.0.12.orig/templates/Debian.cfg yaird-0.0.12/templates/Debian.cfg
--- yaird-0.0.12.orig/templates/Debian.cfg	2006-10-15 21:21:19.000000000 +0200
+++ yaird-0.0.12/templates/Debian.cfg	2006-10-15 21:25:09.000000000 +0200
@@ -457,6 +457,7 @@
 		BEGIN
 			!/bin/mount -n \
 			!	<TMPL_IF NAME=isRoot>$ro</TMPL_IF> \
+			!	<TMPL_IF NAME=readOnly>-r</TMPL_IF> \
 			!	-t <TMPL_VAR NAME=fsType> \
 			!	<TMPL_VAR NAME=options> \
 			!	'<TMPL_VAR NAME=device>' \
@@ -464,6 +465,14 @@
 		END SCRIPT
 	END TEMPLATE
 
+	TEMPLATE umount
+	BEGIN
+		SCRIPT "/init"
+		BEGIN
+			!/bin/umount -n <TMPL_VAR NAME=target>
+		END SCRIPT
+	END TEMPLATE
+
 	TEMPLATE nfsstart
 	BEGIN
 		FILE "<TMPL_VAR NAME=auxDir>/trynfs"
diff -urN yaird-0.0.12.orig/templates/Fedora.cfg yaird-0.0.12/templates/Fedora.cfg
--- yaird-0.0.12.orig/templates/Fedora.cfg	2006-10-15 21:21:19.000000000 +0200
+++ yaird-0.0.12/templates/Fedora.cfg	2006-10-15 21:25:09.000000000 +0200
@@ -468,6 +468,7 @@
 		BEGIN
 			!/bin/mount -n \
 			!	<TMPL_IF NAME=isRoot>$ro</TMPL_IF> \
+			!	<TMPL_IF NAME=readOnly>-r</TMPL_IF> \
 			!	-t <TMPL_VAR NAME=fsType> \
 			!	<TMPL_VAR NAME=options> \
 			!	'<TMPL_VAR NAME=device>' \
@@ -476,6 +477,15 @@
 	END TEMPLATE
 
 
+	TEMPLATE umount
+	BEGIN
+		SCRIPT "/init"
+		BEGIN
+			!/bin/umount -n <TMPL_VAR NAME=target>
+		END SCRIPT
+	END TEMPLATE
+
+
 	TEMPLATE nfsstart
 	BEGIN
 		FILE "<TMPL_VAR NAME=auxDir>/trynfs"
-------------- next part --------------
diff -x .pc -x patches -x debian -urN yaird-0.0.12.orig/perl/ActiveBlockDev.pm yaird-0.0.12/perl/ActiveBlockDev.pm
--- yaird-0.0.12.orig/perl/ActiveBlockDev.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/ActiveBlockDev.pm	2006-10-20 16:53:15.000000000 +0200
@@ -173,6 +173,10 @@
 		Base::assert ($name =~ /^md\d+$/);
 		$self->{yspecial} = "/dev/$name";
 	}
+	elsif ($creator eq "losetup") {
+		Base::assert ($name =~ /^loop\d+$/);
+		$self->{yspecial} = "/dev/$name";
+	}
 	elsif ($creator eq "devmapper") {
 		Base::assert ($name =~ /^dm-\d+$/);
 		my $paths = BlockSpecialFileTab::pathsByDevno ($devno);
--- yaird-0.0.12.orig/perl/Conf.pm.in	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/Conf.pm.in	2006-10-20 16:53:15.000000000 +0200
@@ -61,6 +61,7 @@
 	procFs		=> sub { "/proc"; },
 	dev		=> sub { "/dev"; },
 	fstab		=> sub { "/etc/fstab"; },
+	loopaestab	=> sub { "/etc/loopaestab"; },
 	crypttab	=> sub { "/etc/crypttab"; },
 	hotplug		=> sub { "/etc/hotplug"; },
 	appVersion	=> sub { "@VERSION@"; },
--- yaird-0.0.12.orig/perl/FsEntry.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/FsEntry.pm	2006-10-20 16:53:15.000000000 +0200
@@ -83,8 +83,8 @@
 	my $msg = undef;
 
 	if ($dev =~  /^\//) {
-		if ($self->opts->exists('loop')) {
-			$msg = "loopback mount for '$dev' not supported ($origin)";
+		if ($self->opts->exists('loop') && $self->type eq 'swap') {
+			$msg = "loop option for swap device '$dev' not supported ($origin)";
 			return (undef, $msg);
 		}
 		if (-f $dev && $self->type eq "swap") {
--- yaird-0.0.12.orig/perl/FsOpts.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/FsOpts.pm	2006-10-20 16:53:15.000000000 +0200
@@ -43,6 +43,19 @@
 		next if $key eq 'user';
 		next if $key eq 'users';
 		next if $key eq 'defaults';
+
+		# loop and loop-AES attributes
+		next if $key eq 'loop';
+		next if $key eq 'offset';
+		next if $key eq 'sizelimit';
+		next if $key eq 'encryption';
+		next if $key eq 'pseed';
+		next if $key eq 'phash';
+		next if $key eq 'loinit';
+		next if $key eq 'gpgkey';
+		next if $key eq 'gpghome';
+		next if $key eq 'itercountk';
+
 		my $val = $opts->{$key};
 		if (defined ($val)) {
 			push @cmdLine, "$key=$val";
--- yaird-0.0.12.orig/perl/KConfig.pm	2006-10-20 16:54:51.000000000 +0200
+++ yaird-0.0.12/perl/KConfig.pm	2006-10-20 16:53:15.000000000 +0200
@@ -208,6 +208,9 @@
 	# Compaq Smart Array controllers
 	'cpqarray' => [ 'BLK_CPQ_DA' ],
 	'cciss' => [ 'BLK_CPQ_CISS_DA' ],
+	
+	# loopback
+	'loop' => ['BLK_DEV_LOOP' ],
 };
 
 
--- yaird-0.0.12.orig/perl/LoopAesEntry.pm	1970-01-01 01:00:00.000000000 +0100
+++ yaird-0.0.12/perl/LoopAesEntry.pm	2006-10-20 16:53:15.000000000 +0200
@@ -0,0 +1,47 @@
+#!perl -w
+#
+# LoopAesEntry -- encapsulate a single entry in /etc/loopaestab
+#   Copyright (C) 2005  Erik van Konijnenburg
+#   Copyright (C) 2006  Peter Colberg
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+use strict;
+use warnings;
+use LabeledPartitionTab;
+package LoopAesEntry;
+use base 'Obj';
+
+sub fill {
+	my $self = shift;
+	$self->SUPER::fill();
+	$self->takeArgs ('target', 'source', 'opts', 'origin');
+}
+
+sub target		{ return $_[0]->{target}; }
+sub source		{ return $_[0]->{source}; }
+sub opts	{ return $_[0]->{opts}; }
+sub origin	{ return $_[0]->{origin}; }
+
+sub string {
+	my $self = shift;
+	my $target = $self->target();
+	my $source = $self->source();
+	my $opts = $self->opts()->string();
+	return "$target in $source with $opts";
+}
+
+
+1;
--- yaird-0.0.12.orig/perl/LoopAesTab.pm	1970-01-01 01:00:00.000000000 +0100
+++ yaird-0.0.12/perl/LoopAesTab.pm	2006-10-20 16:53:15.000000000 +0200
@@ -0,0 +1,117 @@
+#!perl -w
+#
+# LoopAesTab -- encapsulate /etc/loopaestab.
+#   Copyright (C) 2005  Erik van Konijnenburg
+#   Copyright (C) 2006  Peter Colberg
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+use strict;
+use warnings;
+use Base;
+use Conf;
+use LoopAesEntry;
+package LoopAesTab;
+
+
+my $loopAesTab = undef;
+
+
+sub init () {
+	if (defined ($loopAesTab)) {
+		return;
+	}
+	$loopAesTab = [];
+	my $name = Conf::get('loopaestab');
+	if (! -e $name) {
+		#
+		# It's OK if there's no /etc/loopaestab, but if it
+		# exists, it had better be readable.
+		#
+		return;
+	}
+	if (! open (IN, "<", "$name")) {
+		Base::fatal ("can't read $name");
+	}
+	my $lineNo = 0;
+	while (defined (my $line = <IN>)) {
+		$lineNo++;
+		chomp $line;
+
+		$line =~ s/^\s*//;
+		next if $line =~ /^#/;	# comment line
+		next if $line eq "";
+
+		my @fields = split (/\s+/, $line);
+		if (@fields < 2) {
+			Base::fatal ("no source device in $name:$lineNo");
+		}
+		my $target = shift @fields;
+		my $source = shift @fields;
+		my $optString = (shift @fields or '');
+		my $opts = Opts->new (string => $optString);
+
+		my $descr = LoopAesEntry->new(
+			target => $target,
+			source => $source,
+			opts => $opts,
+			origin => "$name:$lineNo",
+			);
+		push @{$loopAesTab}, $descr;
+	}
+	if (! close (IN)) {
+		Base::fatal ("could not read $name");
+	}
+}
+
+sub all () {
+	init;
+	return $loopAesTab;
+}
+
+sub findByTarget ($) {
+	my ($target) = @_;
+	my $result;
+	my $devno = Base::devno ($target);
+	if (! defined ($devno)) {
+		Base::fatal ("cannot find device number for: $target");
+	}
+	return findByDevno ($devno);
+}
+
+sub findByDevno ($) {
+	my ($devno) = @_;
+	my $result = undef; 
+
+	for my $entry (@{LoopAesTab::all()}) {
+		my $b2 = $entry->target;
+		my $n2 = Base::devno ($b2);
+		if (! defined ($n2)) {
+			next;
+		}
+
+		if ($n2 eq $devno) {
+			if (defined ($result)) {
+				my $o1 = $entry->origin;
+				my $o2 = $result->origin;
+				Base::fatal ("duplicate device '$b2' in $o1, $o2");
+			}
+			$result = $entry;
+		}
+	}
+	return $result;
+}
+
+1;
--- yaird-0.0.12.orig/perl/LoopAesVersion.pm	1970-01-01 01:00:00.000000000 +0100
+++ yaird-0.0.12/perl/LoopAesVersion.pm	2006-10-20 16:53:15.000000000 +0200
@@ -0,0 +1,78 @@
+#!perl -w
+#
+# LoopAesVersion -- utility functions to probe for loop-AES support
+#   Copyright (C) 2006  Peter Colberg
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+use strict;
+use warnings;
+use Base;
+package LoopAesVersion;
+
+my $hasLoopAesModule = undef;
+my $hasLoopAesSetup = undef;
+
+#
+# Probe loop-AES patched loop module
+#
+sub probeLoopAesModule ($) {
+        my ($path) = @_;
+
+        for my $s ('loop_compute_sector_iv', 'loop_compute_md5_iv', 'loop_compute_md5_iv_v3') {
+                my ($rc, $lines) = Base::runCmd (failOk => 1, cmd => ['/bin/grep', $s, $path]);
+                if (! $rc) {
+                        $hasLoopAesModule = 0;
+                        return;
+                }
+        }
+        $hasLoopAesModule = 1;
+}
+
+#
+# Probe loop-AES patched losetup program
+#
+sub probeLoopAesSetup ($) {
+	my ($path) = @_;
+
+	for my $s ('multi-key-v2', 'multi-key-v3') {
+		my ($rc, $lines) = Base::runCmd (failOk => 1, cmd => ['/bin/grep', $s, $path]);
+		if (! $rc) {
+			$hasLoopAesSetup = 0;
+			return;
+		}
+	}
+	$hasLoopAesSetup = 1;
+}
+
+#
+# Check if userspace and kernel loopback versions match
+#
+sub matchingVersions () {
+	if (KConfig::isBuiltIn ('loop')) {
+		# You are on your own here...
+		return 1;
+	}
+	if (! defined ($hasLoopAesModule)) {
+		Base::fatal ("unknown loop module version");
+	}
+	if (! defined ($hasLoopAesSetup)) {
+		Base::fatal ("unknown losetup version");
+	}
+	return ($hasLoopAesModule == $hasLoopAesSetup);
+}
+
+
+1;
--- yaird-0.0.12.orig/perl/LoopDev.pm	1970-01-01 01:00:00.000000000 +0100
+++ yaird-0.0.12/perl/LoopDev.pm	2006-10-20 16:53:15.000000000 +0200
@@ -0,0 +1,47 @@
+#!perl -w
+#
+# LoopDev -- the probed values for a loopback device, as found by losetup.
+#   Copyright (C) 2005  Erik van Konijnenburg
+#   Copyright (C) 2006  Peter Colberg
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+use strict;
+use warnings;
+package LoopDev;
+use base 'Obj';
+
+sub fill {
+	my $self = shift;
+	$self->SUPER::fill();
+	$self->takeArgs ('path', 'devno', 'opts', 'device');
+}
+
+sub path	{ return $_[0]->{path}; }
+sub devno	{ return $_[0]->{devno}; }
+sub opts	{ return $_[0]->{opts}; }
+sub device	{ return $_[0]->{device}; }
+
+sub string {
+	my $self = shift;
+	my $path = $self->path;
+	my $devno = $self->devno;
+	my $opts = $self->opts->string;
+	my $device = $self->device;
+	return "$path($devno) on $device" . ($opts ? " with $opts" : "");
+}
+
+
+1;
--- yaird-0.0.12.orig/perl/LoopTab.pm	1970-01-01 01:00:00.000000000 +0100
+++ yaird-0.0.12/perl/LoopTab.pm	2006-10-20 16:53:15.000000000 +0200
@@ -0,0 +1,178 @@
+#!perl -w
+#
+# LoopTab -- encapsulate losetup output
+#   Copyright (C) 2005  Erik van Konijnenburg
+#   Copyright (C) 2006  Peter Colberg
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+#
+use strict;
+use warnings;
+use ActiveBlockDevTab;
+use Base;
+use BlockSpecialFileTab;
+use LoopAesVersion;
+use LoopDev;
+package LoopTab;
+
+
+my $loopTab = undef;
+
+
+#
+# init -- initialise table of all known loopback devices.
+#
+sub init () {
+	if (defined ($loopTab)) {
+		return;
+	}
+
+	$loopTab = [];
+
+	for my $abd (@{ActiveBlockDevTab::all ()}) {
+		next if (! ($abd->name =~ /^loop\d+$/));
+		my $paths = BlockSpecialFileTab::pathsByDevno ($abd->devno);
+		next if (! defined ($paths));
+		my ($rc, $lines) = Base::runCmd (missingOk => 1, failOk => 1, cmd => ['/sbin/losetup', ${$paths}[0]]);
+		if ($rc && defined ($lines) && @{$lines} == 1) {
+			processLine (${$lines}[0]);
+		}
+	}
+	LoopAesVersion::probeLoopAesSetup ('/sbin/losetup');
+}
+
+#
+# processLine -- parse losetup output for a single loopback device
+#
+# Both native and loop-AES patched losetup syntax is supported.
+#
+# Example native syntax:
+# /dev/loop0: [0900]:57004 (/dev/md0), offset 1000000
+#
+# Example loop-AES syntax:
+# /dev/loop1: [000e]:3762 (/dev/vg/swap) offset=1024 sizelimit=20971520 encryption=AES256 multi-key-v3 loinit=123
+#
+# Parsing expressions were derived from the function 'show_loop'
+# in file 'mount/lomount.c' of the unpatched and loop-AES (v3.1d)
+# patched util-linux package (v2.12r).
+#
+sub processLine ($) {
+	my ($line) = @_;
+
+	my ($path, $device, @attributes, @opts);
+
+	# common device description
+	if ($line =~ /^(\S+): \[[[:xdigit:]]+\]:\d+ \((\S+)\)(?:(,? )(.*))?$/) {
+		$path = $1;
+		$device = $2;
+		if (defined ($4)) {
+			@attributes = split (/$3/, $4);
+		}
+	}
+	else {
+		Base::fatal ("could not parse losetup output: '$line'");
+	}
+
+	for my $attr (@attributes) {
+		# common attribute
+		if ($attr =~ /^offset[ =](@?\d+)$/) {
+			push @opts, "offset=$1";
+		}
+		# loop-AES attribute
+		elsif ($attr =~ /^sizelimit=(\d+)$/) {
+			push @opts, "sizelimit=$1";
+		}
+		# non-loop-AES attribute
+		elsif ($attr =~ /^sizelimit (\d+)$/) {
+			# info-only attribute
+		}
+		# loop-AES attribute
+		elsif ($attr =~ /^encryption=(\S+)$/) {
+			push @opts, "encryption=$1";
+		}
+		# non-loop-AES attribute
+		elsif ($attr =~ /^encryption /) {
+			Base::fatal ("cryptoloop ('$path') not supported");
+		}
+		# loop-AES attribute
+		elsif ($attr =~ /^loinit=(\d+)$/) {
+			push @opts, "loinit=$1";
+		}
+		# loop-AES attribute
+		elsif ($attr =~ /^read-only$/) {
+			Base::fatal ("read-only loopback device ('$path') not supported");
+		}
+		# loop-AES attribute
+		elsif ($attr =~ /^multi-key-(v[23])$/) {
+			#
+			# Note: If a loopback device is in multi-key mode,
+			# a GPG encryption key is definitely required.
+			#
+			push @opts, "multikey=$1";
+		}
+		else {
+			Base::fatal ("unknown attribute '$attr' in losetup output");
+		}
+	}
+	my $optString = join (",", @opts);
+	my $opts = Opts->new (string => $optString);
+
+	my $devno = Base::devno ($path);
+	if (! defined($devno)) {
+		Base::fatal ("Device '$path' in losetup output: can't find device major/minor number");
+	}
+
+	my $n = Base::devno ($device);
+	if (! defined ($n)) {
+		Base::fatal ("Device '$device' in losetup output: can't find device major/minor number");
+	}
+
+	my $descr = LoopDev->new (
+		path => $path,
+		devno => $devno,
+		opts => $opts,
+		device => $device,
+	);
+	push @{$loopTab}, $descr;
+}
+
+sub all	() {
+	init;
+	return $loopTab;
+}
+
+sub findByPath ($) {
+	my ($path) = @_;
+	for my $ld (@{all()}) {
+		if ($ld->path() eq $path) {
+			return $ld;
+		}
+	}
+	return undef;
+}
+
+sub findByDevno ($) {
+	my ($devno) = @_;
+	for my $ld (@{all()}) {
+		if ($ld->devno() eq $devno) {
+			return $ld;
+		}
+	}
+	return undef;
+}
+
+
+1;
--- yaird-0.0.12.orig/perl/Makefile.am	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/Makefile.am	2006-10-20 16:53:15.000000000 +0200
@@ -71,6 +71,11 @@
 	LabeledPartition.pm \
 	LabeledPartitionTab.pm \
 	LogicalVolume.pm \
+	LoopAesEntry.pm \
+	LoopAesTab.pm \
+	LoopAesVersion.pm \
+	LoopDev.pm \
+	LoopTab.pm \
 	LvmTab.pm \
 	ModProbe.pm \
 	NetDev.pm \
--- yaird-0.0.12.orig/perl/Makefile.in	2005-12-08 23:42:36.000000000 +0100
+++ yaird-0.0.12/perl/Makefile.in	2006-10-20 16:53:15.000000000 +0200
@@ -229,6 +229,11 @@
 	LabeledPartition.pm \
 	LabeledPartitionTab.pm \
 	LogicalVolume.pm \
+	LoopAesEntry.pm \
+	LoopAesTab.pm \
+	LoopAesVersion.pm \
+	LoopDev.pm \
+	LoopTab.pm \
 	LvmTab.pm \
 	ModProbe.pm \
 	NetDev.pm \
--- yaird-0.0.12.orig/perl/ModProbe.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/ModProbe.pm	2006-10-20 16:53:15.000000000 +0200
@@ -90,6 +90,7 @@
 use ActionList;
 use Blacklist;
 use KConfig;
+use LoopAesVersion;
 package ModProbe;
 
 
@@ -147,6 +148,15 @@
 			Base::fatal ("modprobe shows that module $m needs an external program; this is not supported.  The offending line is: install $1");
 		}
 		elsif ($line =~ /^insmod (\S+)$/) {
+			if ($m eq 'loop' ) {
+				#
+				# There exist two different loop modules both
+				# named 'loop': a native version and a loop-AES
+				# patched version.
+				# 
+				LoopAesVersion::probeLoopAesModule ($1);
+			}
+
 			$actionList->add ("insmod", $1,
 				optionList => '');
 		}
--- yaird-0.0.12.orig/perl/Plan.pm	2006-10-20 16:54:51.000000000 +0200
+++ yaird-0.0.12/perl/Plan.pm	2006-10-20 16:54:13.000000000 +0200
@@ -33,6 +33,9 @@
 use ActionList;
 use CryptTab;
 use NetDevTab;
+use LoopAesTab;
+use LoopTab;
+use LoopAesVersion;
 
 package Plan;
 
@@ -89,6 +92,7 @@
 	$ok || ($ok = tryDmCrypt ($actions,$device,[$device,@{$working}]));
 	$ok || ($ok = tryLvm ($actions,$device,[$device,@{$working}]));
 	$ok || ($ok = tryRaid ($actions,$device,[$device,@{$working}]));
+	$ok || ($ok = tryLoop ($actions,$device,[$device,@{$working}]));
 	$ok || ($ok = tryHardware ($actions,$device,[$device,@{$working}]));
 	if (! $ok) {
 		Base::fatal ("unsupported device required: $name");
@@ -427,6 +431,414 @@
 	return 1;
 }
 
+
+#
+# tryLoop -- To start a loopback device, start the underlying hardware,
+# optionally make available a GPG encryption key by mounting (and
+# later on unmounting) the respective filesystem at boot time or
+# copying it to the image at build time, load the loop module and
+# optional encryption modules, and setup the loopback device.
+#
+sub tryLoop ($$$) {
+	my ($actions, $device, $working) = @_;
+
+	my $name = $device->name;
+	if ($name !~ /^loop\d+$/) {
+		return 0;
+	}
+	my $devno = $device->devno;
+	my ($major, $minor) = ($devno =~ /(\d+):(\d+)/);
+
+	my $loopdev = LoopTab::findByDevno ($devno);
+	if (! defined ($loopdev)) {
+		Base::fatal ("can't find Loop info for $name");
+	}
+
+	# start the underlying block device
+	my $subdev = ActiveBlockDevTab::findByPath ($loopdev->device);
+	addDevicePlan ($actions, $subdev, $working);
+
+
+	#
+	# In most cases, the losetup status output only provides
+	# a partial set of the attributes required to setup a
+	# loop-AES device. For a complete set of attributes, we
+	# therefore need to parse fstab and loopaestab in addition,
+	# and all given attribute sets have to be compared and
+	# merged in a nit-picky fashion.
+	#
+
+	# complete loopback device config
+	my %loopOpts;
+
+	#
+	# Full set of possible attributes for each loop device
+	# config source, i.e. losetup info, fstab and loopaestab.
+	#
+	# For the purpose of comparison, an attribute is either
+	# marked as case sensitive (e.g. 'gpgkey' => 1) or
+	# case insensitive (e.g. 'encryption' => 0).
+	# 
+	my $commonSupportedOpts = {
+		'offset' => 0,
+		'sizelimit' => 0,
+		'encryption' => 0,
+		'loinit' => 0,
+	};
+	my $losetupSupportedOpts = {
+		%{$commonSupportedOpts},
+		'multikey' => 0,
+	};
+	my $fsTabSupportedOpts = {
+		%{$commonSupportedOpts},
+		'gpgkey' => 1,
+		'gpghome' => 1,
+		'pseed' => 0,
+		'phash' => 0,
+		'itercountk' => 0,
+	};
+	my $loopAesTabSupportedOpts = {
+		%{$fsTabSupportedOpts},
+		'gpgmount' => 0,
+	};
+
+	# available loop device config entries
+	my @conf;
+
+	#
+	# All attributes shown by losetup describe the actual
+	# loopback device, so this set of attributes is made
+	# the highest-priority config source.
+	# 
+	push @conf, {
+		supportedOpts => $losetupSupportedOpts,
+		opts => $loopdev->opts,
+		origin => 'losetup status',
+	};
+
+	# query fstab entry for the underlying block device
+	my $fsTabEntry = FsTab::findByDevno ($subdev->devno);
+	if (defined ($fsTabEntry)) {
+		#
+		# The forbidden case of a loopback swap device with a
+		# random encryption key is handled within FsTab,
+		# so this does not have to be reconsidered here.
+		#
+
+		# check if loopback device is set correctly
+		if (! $fsTabEntry->opts->exists ("loop")) {
+			my $origin = $fsTabEntry->origin;
+			Base::fatal ("missing loop option in $origin");
+		}
+		my $n = Base::devno ($fsTabEntry->opts->get ("loop"));
+		if ((! defined ($n)) || (! ($n eq $devno))) {
+			my $origin = $fsTabEntry->origin;
+			Base::fatal ("invalid loop option in $origin");
+		}
+		push @conf, {
+			supportedOpts => $fsTabSupportedOpts,
+			opts => $fsTabEntry->opts,
+			origin => $fsTabEntry->origin,
+		};
+	}
+
+	# query loopaestab entry for the loopback device
+	my $loopAesTabEntry = LoopAesTab::findByDevno ($loopdev->devno);
+	if (defined ($loopAesTabEntry)) {
+		# check if underlying block device is set correctly
+		my $n = Base::devno ($loopAesTabEntry->source);
+		if ((! defined ($n)) || (! ($n eq $subdev->devno))) {
+			my $origin = $loopAesTabEntry->origin;
+			Base::fatal ("invalid source device in $origin");
+		}
+		push @conf, {
+			supportedOpts => $loopAesTabSupportedOpts,
+			opts => $loopAesTabEntry->opts,
+			origin => $loopAesTabEntry->origin,
+		};
+	}
+
+	# require fstab or loopaestab entry for loop-AES devices
+	if (@conf < 2 && defined ($loopOpts{encryption})) {
+		Base::fatal ("can't find fstab or loopaestab entry for loop-AES device: $name");
+	}
+
+	#
+	# For every distinct pair of config entries, all attributes
+	# supported by both config sources strictly have to match.
+	#
+	for my $i (0 .. $#conf) {
+		for my $j (($i + 1) .. $#conf) {
+			my (%count, @intersect, @conflicts);
+			my @suppOpts1 = keys (%{${$conf[$i]}{supportedOpts}});
+			my @suppOpts2 = keys (%{${$conf[$j]}{supportedOpts}});
+
+			# determine common set of supported attributes
+			for my $opt (@suppOpts1, @suppOpts2) {
+				if (++$count{$opt} == 2) {
+					push @intersect, $opt;
+				}
+			}
+
+			# basically do a case (in)sensitive hash comparison
+			for my $opt (@intersect) {
+				my $exists1 = ${$conf[$i]}{opts}->exists ($opt);
+				my $exists2 = ${$conf[$j]}{opts}->exists ($opt);
+				my $val1 = ${$conf[$i]}{opts}->get ($opt);
+				my $val2 = ${$conf[$j]}{opts}->get ($opt);
+
+				# case (in)sensitivity of an attribute value
+				my $case = ${${$conf[$i]}{supportedOpts}}{$opt};
+
+				if ($exists1 xor $exists2) {
+					push @conflicts, $opt;
+				}
+				elsif (defined ($val1) xor defined ($val2)) {
+					push @conflicts, $opt;
+				}
+				elsif (defined ($val1) && defined ($val2)) {
+					if ($case) {
+						if ($val1 ne $val2) {
+							push @conflicts, $opt;
+						}
+					}
+					else {
+						if (lc ($val1) ne lc ($val2)) {
+							push @conflicts, $opt;
+						}
+					}
+				}
+			}
+			if (@conflicts) {
+				my $opt = join (', ', @conflicts);
+				my $origin1 = ${$conf[$i]}{origin};
+				my $origin2 = ${$conf[$j]}{origin};
+				Base::fatal ("conflicting option(s) '$opt' in $origin1 and $origin2");
+			}
+		}
+	}
+	#
+	# At this point, all config entries strictly match pertaining
+	# to their respective supported attributes, and can therefore
+	# be merged to provide a complete loopback device config.
+	#
+	for my $c (@conf) {
+		my @suppOpts = keys (%{${$c}{supportedOpts}});
+
+		for my $opt (@suppOpts) {
+			my $exists = ${$c}{opts}->exists ($opt);
+			my $val = ${$c}{opts}->get ($opt);
+
+			if ($exists && (! exists ($loopOpts{$opt}))) {
+				$loopOpts{$opt} = $val;
+			}
+		}
+	}
+
+	#
+	# Loopback device attributes specified manually in fstab
+	# or loopaestab must be checked for mutual compatibility
+	# and plausible attribute values.
+	#
+	if (! defined ($loopOpts{encryption})) {
+		my @opts = ('gpgkey', 'gpghome', 'gpgmount', 'pseed', 'phash', 'itercountk');
+		if (@opts = grep { exists ($loopOpts{$_}) } @opts) {
+			my $opt = join (', ', @opts);
+			Base::fatal ("can't use option(s) '$opt' with unencrypted loopback device: $name");
+		}
+	}
+	if (! defined ($loopOpts{gpgkey})) {
+		my @opts = ('gpghome', 'gpgmount');
+		if (@opts = grep { exists ($loopOpts{$_}) } @opts) {
+			my $opt = join (', ', @opts);
+			Base::fatal ("can't use option(s) '$opt' with password encrypted loop-AES device: $name");
+		}
+		if (exists ($loopOpts{multikey})) {
+			Base::fatal ("multi-key mode loop-AES device requires GPG key: $name");
+		}
+	}
+	if (defined ($loopOpts{phash})) {
+		if (! ($loopOpts{phash} =~ /^(sha256|sha384|sha512|rmd160)$/i)) {
+			my $value = $loopOpts{phash};
+			Base::fatal ("unsupported value 'phash=$value' for loop-AES device: $name");
+		}
+	}
+	if (defined ($loopOpts{itercountk})) {
+		if (! ($loopOpts{itercountk} =~ /^\d+$/)) {
+			my $value = $loopOpts{itercountk};
+			Base::fatal ("unsupported value 'itercountk=$value' for loop-AES device: $name");
+		}
+	}
+
+	# check existence of GPG key (and optionally GPG home directory)
+	my $gpgkey = $loopOpts{gpgkey};
+	if (defined ($gpgkey)) {
+		if (! Base::isAbsolute ($gpgkey)) {
+			Base::fatal ("GPG key file ($gpgkey) not absolute: $name");
+		}
+		if (! (-f $gpgkey)) {
+			Base::fatal ("GPG key file ($gpgkey) not a regular file: $name");
+		}
+		$gpgkey = Base::canon ($gpgkey);
+	}
+	my $gpghome = $loopOpts{gpghome};
+	if (defined ($gpghome)) {
+		if (! Base::isAbsolute ($gpghome)) {
+			Base::fatal ("GPG home directory ($gpghome) not absolute: $name");
+		}
+		if (! (-d $gpghome)) {
+			Base::fatal ("GPG home directory ($gpghome) not a directory: $name");
+		}
+		$gpghome = Base::canon ($gpghome);
+	}
+
+	#
+	# In case of encryption with a GPG key, there exist
+	# two different modes of operation:
+	#
+	# * If the option 'gpgmount' has been specified, the
+	#   filesystem containing the GPG key file (and optionally
+	#   the GPG home directory) will be mounted at boot time.
+	#
+	# * Otherwise, the GPG key file (and optionally the GPG
+	#   home directory) will be copied to the image.
+	#
+
+	# choose a unique mount point for each loop device
+	my $gpgkey_mnt = (exists ($loopOpts{gpgmount}) ? "/.$name" : undef);
+
+	if (defined ($gpgkey) && defined ($gpgkey_mnt)) {
+		# create GPG key mount point on the image
+		$actions->add ("gpgmount", $gpgkey_mnt);
+
+		#
+		# Determine block device of GPG key file, and ensure
+		# that an optional GPG home directory lies on the same
+		# filesystem.
+		#
+		my $gpgdevno = Base::filedev ($gpgkey);
+		if (! defined ($gpgdevno)) {
+			Base::fatal ("can't determine device of GPG key file ($gpgkey): $name");
+		}
+		if (defined ($gpghome)) {
+			my $n = Base::filedev ($gpghome);
+			if (! defined ($n)) {
+				Base::fatal ("can't determine device of GPG home directory ($gpghome): $name");
+			}
+			if (! ($n eq $gpgdevno)) {
+				Base::fatal ("GPG key file ($gpgkey) and GPG home directory ($gpghome) not on the same filesystem: $name");
+			}
+		}
+
+		#
+		# Determine the fstab mount point of the block device
+		# and ensure that the GPG key file (and optionally the
+		# GPG home directory) really lies on this filesystem.
+		#
+		my $gpgfs = FsTab::findByDevno ($gpgdevno);
+		if (! defined ($gpgfs)) {
+			Base::fatal ("GPG key ($gpgkey) device not in fstab: $name");
+		}
+		my $gpgkey_dev = $gpgfs->dev;
+		#
+		# If it's mounted via a loop device, use that as GPG key device
+		#
+		if ($gpgfs->opts->exists ('loop')) {
+			$gpgkey_dev = $gpgfs->opts->get ('loop');
+		}
+		my $gk = $gpgkey;
+		my $gh = $gpghome;
+		my $fsmnt = $gpgfs->mnt;
+		$gk =~ s/^$fsmnt//;
+		if (defined ($gh)) {
+			$gh =~ s/^$fsmnt//;
+		}
+		if (! ($gpgkey eq Base::canon ($fsmnt . "/" . $gk))) {
+			Base::fatal ("GPG key file ($gpgkey) filesystem not found: $name");
+		}
+		if (defined ($gpghome) && (! ($gpghome eq Base::canon ($fsmnt . "/" . $gh)))) {
+			Base::fatal ("GPG home directory ($gpghome) filesystem not found: $name");
+		}
+
+		# construct absolute paths as found at boot time.
+		$gpgkey = Base::canon ($gpgkey_mnt . "/" . $gk);
+		if (defined ($gpghome)) {
+			$gpghome = Base::canon ($gpgkey_mnt . "/" . $gh);
+		}
+
+		#
+		# Start and (read-only) mount GPG key device
+		# 
+		# (Manually adding the device plan before mounting ensures
+		# that potential loops are detected. A loop might occur
+		# if the GPG key file erroneously lies on the encrypted
+		# loopback device itself.)
+		#
+		my $gpgdev = ActiveBlockDevTab::findByPath ($gpgkey_dev);
+		if (! defined ($gpgdev)) {
+			Base::fatal ("can't find block device '$gpgkey_dev' in fstab");
+		}
+		addDevicePlan ($actions, $gpgdev, $working);
+		addBlockDevMount ($actions, $gpgkey_dev, $gpgkey_mnt, 0);
+	}
+	if (defined ($gpgkey) && (! defined ($gpgkey_mnt))) {
+		# copy GPG key file (and optionally GPG home directory) to image
+		if (defined ($gpghome)) {
+			$actions->add ("gpgpublic", $gpgkey, gpghome => $gpghome);
+		}
+		else {
+			$actions->add ("gpgkey", $gpgkey);
+		}
+	}
+
+	#
+	# Load required modules, ensuring matching kernel and
+	# userspace loopback support (i.e. native or loop-AES).
+	#
+	ModProbe::addModules ($actions, [ "loop" ]);
+	if (! LoopAesVersion::matchingVersions) {
+		Base::fatal ("loop kernel module and losetup program versions mismatch");
+	}
+	my $encryption = $loopOpts{encryption};
+	if (defined ($encryption)) {
+		if ($encryption =~ /^(twofish|blowfish|serpent)\d+$/i) {
+			ModProbe::addModules ($actions, [ "loop_" . lc ($1) ]);
+		}
+		elsif (! ($encryption =~ /^(aes\d*|xor)$/i)) {
+			Base::fatal ("unsupported encryption type ($encryption): '$name'");
+		}
+	}
+
+	# load keymap to allow proper password entry
+	if (defined ($encryption)) {
+		$actions->add ("loadkeys", "loadkeys");
+	}
+
+	# setup loopback device
+	$device->setCreator ("losetup");
+	$actions->add ("losetup", $device->yspecial,
+		major => $major,
+		minor => $minor,
+		offset => $loopOpts{offset},
+		sizelimit => $loopOpts{sizelimit},
+		encryption => $loopOpts{encryption},
+		loinit => $loopOpts{loinit},
+		gpgkey => $gpgkey,
+		gpghome => $gpghome,
+		pseed => $loopOpts{pseed},
+		phash => $loopOpts{phash},
+		itercountk => $loopOpts{itercountk},
+		device => $subdev->yspecial,
+	);
+
+	# unmount GPG key device if appropriate
+	if (defined ($gpgkey_mnt)) {
+		addUnmount ($actions, $gpgkey_mnt);
+	}
+
+	return 1;
+}
+
 #
 # tryEvms Look if the device could be an evms one
 #
@@ -635,6 +1047,13 @@
 	}
 
 	#
+	# If it's mounted via a loop device, use that as root device
+	#
+	if ($root->opts->exists ('loop')) {
+		$rootDevName = $root->opts->get ('loop');
+	}
+
+	#
 	# and device must be in /dev, to determine whether
 	# it's raid, lvm, scsi or whatever.
 	#
--- yaird-0.0.12.orig/perl/TestSet.pm	2005-12-08 23:42:33.000000000 +0100
+++ yaird-0.0.12/perl/TestSet.pm	2006-10-20 16:53:15.000000000 +0200
@@ -29,6 +29,7 @@
 use LvmTab;
 use Hardware;
 use RaidTab;
+use LoopTab;
 use EvmsTab;
 use InputTab;
 use Image;
@@ -132,6 +133,14 @@
 	}
 }
 
+sub testLoopDevices () {
+	print "Loopback devices:\n";
+	for my $ld (@{LoopTab::all()}) {
+		my $str = $ld->string;
+		print "\t$str\n";
+	}
+}
+
 sub testEvms () {
 	print "Evms devices:\n";
 	for my $ev (@{EvmsTab::all()}) {
@@ -231,6 +240,7 @@
 	testLvm ();
 	testHardware ();
 	testRaidDevices();
+	testLoopDevices();
 	testInterpretation ();
 	testInput ();
 	testKconfig ();
--- yaird-0.0.12.orig/templates/Debian-initrd.cfg	2006-10-20 16:54:51.000000000 +0200
+++ yaird-0.0.12/templates/Debian-initrd.cfg	2006-10-20 16:53:15.000000000 +0200
@@ -300,6 +300,124 @@
 		END SCRIPT
 	END TEMPLATE
 
+
+	#
+	# Load keymap upon boot, allowing proper password entry
+	# for encrypted devices.
+	#
+	TEMPLATE loadkeys
+	BEGIN
+		FILE "/bin/loadkeys"
+		FILE "/bin/gunzip"
+		FILE "/etc/console/boottime.kmap.gz"
+		SCRIPT "/sbin/init"
+		BEGIN
+			!# loadkeys from the kbd package has problems
+			!# uncompressing the keymap on-the-fly when
+			!# run from initramfs, don't know why
+			!gunzip /etc/console/boottime.kmap.gz
+			!loadkeys /etc/console/boottime.kmap
+		END SCRIPT
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and GPG key file.
+	#
+	TEMPLATE gpgkey
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program, GPG key file and GPG home directory.
+	#
+	TEMPLATE gpgpublic
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+		TREE "<TMPL_VAR NAME=gpghome>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and create GPG key device mount point.
+	#
+	TEMPLATE gpgmount
+	BEGIN
+		FILE "/usr/bin/gpg"
+		DIRECTORY "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Setup plain loopback or loop-AES encrypted device.
+	#
+	# common losetup arguments:
+	# - offset
+	# - device (underlying block device)
+	#
+	# loop-AES specific losetup arguments:
+	# - encryption
+	# - loinit
+	# - sizelimit
+	# - gpgkey
+	# - gpghome
+	# - pseed
+	# - phash
+	# - itercountk
+	#
+	TEMPLATE losetup
+	BEGIN
+		FILE "/sbin/losetup"
+		SCRIPT "/sbin/init"
+		BEGIN
+			!mknod <TMPL_VAR NAME=target> b <TMPL_VAR NAME=major> <TMPL_VAR NAME=minor>
+			!DOCRYPT=1
+			!while [ "$DOCRYPT" != "0" ]; do
+			!	<TMPL_IF NAME=encryption>
+			!		echo "Encrypted device ('<TMPL_VAR NAME=device>'), please supply passphrase"
+			!	</TMPL_IF>
+			!	losetup \
+			!		<TMPL_IF NAME=encryption> \
+			!			-e <TMPL_VAR NAME=encryption> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=loinit> \
+			!			-I <TMPL_VAR NAME=loinit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=offset> \
+			!			-o <TMPL_VAR NAME=offset> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=sizelimit> \
+			!			-s <TMPL_VAR NAME=sizelimit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=gpgkey> \
+			!			-K <TMPL_VAR NAME=gpgkey> \
+			!			<TMPL_IF NAME=gpghome> \
+			!				-G <TMPL_VAR NAME=gpghome> \
+			!			<TMPL_ELSE> \
+			!				-G /nonexistent \
+			!			</TMPL_IF> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=pseed> \
+			!			-S <TMPL_VAR NAME=pseed> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=phash> \
+			!			-H <TMPL_VAR NAME=phash> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=itercountk> \
+			!			-C <TMPL_VAR NAME=itercountk> \
+			!		</TMPL_IF> \
+			!		<TMPL_VAR NAME=target> \
+			!		<TMPL_VAR NAME=device>
+			!	DOCRYPT=$?
+			!done
+		END SCRIPT
+	END TEMPLATE
+
+
 	#
 	# cryptsetup arguments:
 	# - target
--- yaird-0.0.12.orig/templates/Debian.cfg	2006-10-20 16:54:51.000000000 +0200
+++ yaird-0.0.12/templates/Debian.cfg	2006-10-20 16:54:13.000000000 +0200
@@ -329,6 +329,123 @@
 
 
 	#
+	# Load keymap upon boot, allowing proper password entry
+	# for encrypted devices.
+	#
+	TEMPLATE loadkeys
+	BEGIN
+		FILE "/bin/loadkeys"
+		FILE "/bin/gunzip"
+		FILE "/etc/console/boottime.kmap.gz"
+		SCRIPT "/init"
+		BEGIN
+			!# loadkeys from the kbd package has problems
+			!# uncompressing the keymap on-the-fly when
+			!# run from initramfs, don't know why
+			!gunzip /etc/console/boottime.kmap.gz
+			!loadkeys /etc/console/boottime.kmap
+		END SCRIPT
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and GPG key file.
+	#
+	TEMPLATE gpgkey
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program, GPG key file and GPG home directory.
+	#
+	TEMPLATE gpgpublic
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+		TREE "<TMPL_VAR NAME=gpghome>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and create GPG key device mount point.
+	#
+	TEMPLATE gpgmount
+	BEGIN
+		FILE "/usr/bin/gpg"
+		DIRECTORY "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Setup plain loopback or loop-AES encrypted device.
+	#
+	# common losetup arguments:
+	# - offset
+	# - device (underlying block device)
+	#
+	# loop-AES specific losetup arguments:
+	# - encryption
+	# - loinit
+	# - sizelimit
+	# - gpgkey
+	# - gpghome
+	# - pseed
+	# - phash
+	# - itercountk
+	#
+	TEMPLATE losetup
+	BEGIN
+		FILE "/sbin/losetup"
+		SCRIPT "/init"
+		BEGIN
+			!mknod <TMPL_VAR NAME=target> b <TMPL_VAR NAME=major> <TMPL_VAR NAME=minor>
+			!DOCRYPT=1
+			!while [ "$DOCRYPT" != "0" ]; do
+			!	<TMPL_IF NAME=encryption>
+			!		echo "Encrypted device ('<TMPL_VAR NAME=device>'), please supply passphrase"
+			!	</TMPL_IF>
+			!	losetup \
+			!		<TMPL_IF NAME=encryption> \
+			!			-e <TMPL_VAR NAME=encryption> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=loinit> \
+			!			-I <TMPL_VAR NAME=loinit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=offset> \
+			!			-o <TMPL_VAR NAME=offset> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=sizelimit> \
+			!			-s <TMPL_VAR NAME=sizelimit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=gpgkey> \
+			!			-K <TMPL_VAR NAME=gpgkey> \
+			!			<TMPL_IF NAME=gpghome> \
+			!				-G <TMPL_VAR NAME=gpghome> \
+			!			<TMPL_ELSE> \
+			!				-G /nonexistent \
+			!			</TMPL_IF> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=pseed> \
+			!			-S <TMPL_VAR NAME=pseed> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=phash> \
+			!			-H <TMPL_VAR NAME=phash> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=itercountk> \
+			!			-C <TMPL_VAR NAME=itercountk> \
+			!		</TMPL_IF> \
+			!		<TMPL_VAR NAME=target> \
+			!		<TMPL_VAR NAME=device>
+			!	DOCRYPT=$?
+			!done
+		END SCRIPT
+	END TEMPLATE
+
+
+	#
 	# cryptsetup arguments:
 	# - target
 	# - cipher
--- yaird-0.0.12.orig/templates/Fedora.cfg	2006-10-20 16:54:51.000000000 +0200
+++ yaird-0.0.12/templates/Fedora.cfg	2006-10-20 16:54:13.000000000 +0200
@@ -340,6 +340,123 @@
 
 
 	#
+	# Load keymap upon boot, allowing proper password entry
+	# for encrypted devices.
+	#
+	TEMPLATE loadkeys
+	BEGIN
+		FILE "/bin/loadkeys"
+		FILE "/bin/gunzip"
+		FILE "/etc/console/boottime.kmap.gz"
+		SCRIPT "/init"
+		BEGIN
+			!# loadkeys from the kbd package has problems
+			!# uncompressing the keymap on-the-fly when
+			!# run from initramfs, don't know why
+			!gunzip /etc/console/boottime.kmap.gz
+			!loadkeys /etc/console/boottime.kmap
+		END SCRIPT
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and GPG key file.
+	#
+	TEMPLATE gpgkey
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program, GPG key file and GPG home directory.
+	#
+	TEMPLATE gpgpublic
+	BEGIN
+		FILE "/usr/bin/gpg"
+		FILE "<TMPL_VAR NAME=target>"
+		TREE "<TMPL_VAR NAME=gpghome>"
+	END TEMPLATE
+
+
+	#
+	# Include GPG program and create GPG key device mount point.
+	#
+	TEMPLATE gpgmount
+	BEGIN
+		FILE "/usr/bin/gpg"
+		DIRECTORY "<TMPL_VAR NAME=target>"
+	END TEMPLATE
+
+
+	#
+	# Setup plain loopback or loop-AES encrypted device.
+	#
+	# common losetup arguments:
+	# - offset
+	# - device (underlying block device)
+	#
+	# loop-AES specific losetup arguments:
+	# - encryption
+	# - loinit
+	# - sizelimit
+	# - gpgkey
+	# - gpghome
+	# - pseed
+	# - phash
+	# - itercountk
+	#
+	TEMPLATE losetup
+	BEGIN
+		FILE "/sbin/losetup"
+		SCRIPT "/init"
+		BEGIN
+			!mknod <TMPL_VAR NAME=target> b <TMPL_VAR NAME=major> <TMPL_VAR NAME=minor>
+			!DOCRYPT=1
+			!while [ "$DOCRYPT" != "0" ]; do
+			!	<TMPL_IF NAME=encryption>
+			!		echo "Encrypted device ('<TMPL_VAR NAME=device>'), please supply passphrase"
+			!	</TMPL_IF>
+			!	losetup \
+			!		<TMPL_IF NAME=encryption> \
+			!			-e <TMPL_VAR NAME=encryption> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=loinit> \
+			!			-I <TMPL_VAR NAME=loinit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=offset> \
+			!			-o <TMPL_VAR NAME=offset> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=sizelimit> \
+			!			-s <TMPL_VAR NAME=sizelimit> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=gpgkey> \
+			!			-K <TMPL_VAR NAME=gpgkey> \
+			!			<TMPL_IF NAME=gpghome> \
+			!				-G <TMPL_VAR NAME=gpghome> \
+			!			<TMPL_ELSE> \
+			!				-G /nonexistent \
+			!			</TMPL_IF> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=pseed> \
+			!			-S <TMPL_VAR NAME=pseed> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=phash> \
+			!			-H <TMPL_VAR NAME=phash> \
+			!		</TMPL_IF> \
+			!		<TMPL_IF NAME=itercountk> \
+			!			-C <TMPL_VAR NAME=itercountk> \
+			!		</TMPL_IF> \
+			!		<TMPL_VAR NAME=target> \
+			!		<TMPL_VAR NAME=device>
+			!	DOCRYPT=$?
+			!done
+		END SCRIPT
+	END TEMPLATE
+
+
+	#
 	# cryptsetup arguments:
 	# - target
 	# - cipher


More information about the Yaird-devel mailing list