diff --git a/em_multistrap b/em_multistrap index 3830cf5..49cae98 100755 --- a/em_multistrap +++ b/em_multistrap @@ -15,16 +15,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +use IO::File; use Config::Auto; use File::Basename; +use Parse::Debian::Packages; use strict; use vars qw/ $progname $ourversion %scripts $dstrap $script $extra @archives $deb $cachedir $config_str %packages $retval $str $retries $dir $include $arch $foreign $suite $url $forceunpack $option %options @e $sourcesname $libdir $dpkgdir @debootstrap %suites $mirror $etcdir $repo @dirs @touch %sources $section %keys $host $key $value $type - $file $config /; + $file $config $tidy /; $progname = basename($0); +$ourversion = "0.0.3"; while( @ARGV ) { $_= shift( @ARGV ); @@ -43,6 +46,13 @@ while( @ARGV ) { elsif (/^(-a|--arch)$/) { $arch = shift(@ARGV); } + elsif (/^(-d|--dir)$/) { + $dir = shift(@ARGV); + $dir .= ($dir =~ m:/$:) ? '' : "/"; + } + elsif (/^(--tidy-up)$/) { + $tidy++; + } else { die "$progname: Unknown option $_.\n"; } @@ -68,7 +78,7 @@ foreach $section (sort keys %keys) { $arch = $keys{$section}{'arch'} if (not defined $arch); $retries = $keys{$section}{'retries'}; - $dir = $keys{$section}{'directory'}; + $dir = $keys{$section}{'directory'} if (not defined $dir); $forceunpack = lc($keys{$section}{'forceunpack'}); @debootstrap = split(' ', lc($keys{$section}{'debootstrap'})); } @@ -81,10 +91,13 @@ foreach $section (sort keys %keys) $options{$section}=$keys{$section}{'options'}; } } - +print "$progname $ourversion using $file\n"; $host = `dpkg-architecture -qDEB_BUILD_ARCH`; chomp ($host); -$foreign = ($host ne $arch) ? "--foreign" : ''; +die ("$progname is not currently able to support native operation.\n") + if ($host eq $arch); +# don't let debootstrap do second-stage +$foreign = "--foreign"; # always set $cachedir = "var/cache/apt/"; # archives $libdir = "var/lib/apt/"; # lists $etcdir = "etc/apt/"; # sources @@ -157,6 +170,10 @@ $config_str .= " -o Dir::Cache=${dir}${cachedir}"; system ("apt-get $config_str update"); $str = join (' ', values %packages); chomp($str); +$str .= " "; +my $required = &get_required_debs; +$str .= join (' ', @$required); +chomp($str); print "apt-get -y $config_str install $str\n"; $retval = system ("apt-get -y $config_str install $str"); die ("apt download failed. Exit value: ".($retval/256)."\n") @@ -177,32 +194,216 @@ foreach $dstrap (@debootstrap) $include .= join (',', @e); } &write_script ($dstrap); - $str = "debootstrap $option $include --arch $arch $foreign $suite $dir "; - $str .= "$url $script"; + $str = "/usr/sbin/debootstrap $option $include --arch $arch $foreign"; + $str .= " --keep-debootstrap-dir $suite $dir $url $script"; print "$str\n"; $retval = system ($str); while ($retval != 0 and $retries > 0) { - print "Problem - trying again, $retries left.\n"; + print "Problem - trying again, $retries left. ".($retval/250)."\n$!\n"; sleep 1; $retval = system ("$str"); $retries--; } } -if ($forceunpack eq "true") + +&force_unpack if ($forceunpack eq "true"); +system ("touch ${dir}${libdir}lists/lock"); +(not defined $tidy) ? system ("apt-get $config_str update") : &tidy_apt; +system ("rm -rf ${dir}debootstrap"); +print "\nMultistrap system installed successfully in $dir.\n\n"; +exit 0; + +sub force_unpack { + my %unpack=(); opendir (DEBS, "${dir}${cachedir}archives/") or die ("Cannot read apt archives directory.\n"); @archives=grep(/.*\.deb$/, readdir DEBS); closedir (DEBS); - chdir ("${dir}"); - foreach $deb (@archives) + print "I: Calculating obsolete packages\n"; + foreach $deb (sort @archives) { - print "Extracting $deb...\n"; + my $version = `dpkg -f ${dir}${cachedir}archives/$deb Version`; + my $package = `dpkg -f ${dir}${cachedir}archives/$deb Package`; + chomp ($version); + chomp ($package); + if (exists $unpack{$package}) + { + my $test=system("dpkg --compare-versions $unpack{$package} '<<' $version"); + $test /= 256; + # unlink version in $unpack if 0 + # unlink $deb (current one) if 1 + if ($test == 0) + { + my $old = $deb; + $old =~ s/$version/$unpack{$package}/; + print "I: Removing $old\n"; + unlink "${dir}${cachedir}archives/$old"; + next; + } + else + { + print "I: Removing $deb\n"; + unlink "${dir}${cachedir}archives/$deb"; + } + } + $unpack{$package}=$version; + } + open (LOCK, ">${dir}${libdir}lists/lock"); + close (LOCK); + opendir (DEBS, "${dir}${cachedir}archives/") + or die ("Cannot read apt archives directory.\n"); + @archives=grep(/.*\.deb$/, readdir DEBS); + closedir (DEBS); + my $old = `pwd`; + chomp ($old); + chdir ("${dir}"); + foreach $deb (sort @archives) + { + print "I: Extracting $deb...\n"; system ("ar -p \"./${cachedir}archives/$deb\" data.tar.gz | zcat | tar -xf -"); + my $ver=`dpkg -f ./${cachedir}archives/$deb Version`; + my $pkg=`dpkg -f ./${cachedir}archives/$deb Package`; + chomp ($ver); + chomp ($pkg); + mkdir ("./tmp"); + my $tmpdir = `mktemp -p ./tmp -d -t multistrap.XXXXXX`; + chomp ($tmpdir); + mkdir ("./${tmpdir}/listing"); + system ("ar -p \"./${cachedir}archives/$deb\" data.tar.gz > ./${tmpdir}/listing/data.tar.gz"); + my $datatar = `tar -tzf ./${tmpdir}/listing/data.tar.gz`; + my @lines = split("\n", $datatar); + open (LIST, ">>./${dpkgdir}info/${pkg}.list"); + foreach my $l (@lines) + { + chomp ($l); + $l =~ s:^\.::; + $l =~ s:^/$:/\.:; + $l =~ s:/$::; + print LIST "$l\n"; + } + close (LIST); + system ("rm -rf ./${tmpdir}/listing"); + system ("dpkg -e ./${cachedir}archives/$deb ${tmpdir}/"); + opendir (MAINT, "./${tmpdir}"); + my @maint=grep(!m:\.\.?:, readdir (MAINT)); + closedir (MAINT); + open (AVAIL, ">>./${dpkgdir}available"); + open (STATUS, ">>./${dpkgdir}status"); + foreach my $mscript (@maint) + { + rename "./${tmpdir}/$mscript", "./${dpkgdir}info/$pkg.$mscript"; + if ( $mscript eq "control" ) + { + open (MSCRIPT, "./${dpkgdir}info/$pkg.$mscript"); + my @scr=; + close (MSCRIPT); + my @avail = grep(!/^$/, @scr); + print AVAIL @avail; + print STATUS @avail; + print AVAIL "\n"; + print STATUS "Status: install ok unpacked\n"; + unlink ("./${dpkgdir}info/$mscript"); + } + } + close (AVAIL); + if ( -f "./${dpkgdir}info/$pkg.conffiles") + { + print STATUS "Conffiles:\n"; + print " -> Processing conffiles for $pkg\n"; + open (CONF, "./${dpkgdir}info/$pkg.conffiles"); + my @lines=; + close (CONF); + foreach my $line (@lines) + { + chomp ($line); + my $md5=`md5sum ./$line | cut -d" " -f1`; + chomp ($md5); + print STATUS " $line $md5\n"; + } + } + print STATUS "\n"; + close (STATUS); + system ("rm -rf ./${tmpdir}"); + } + chdir ("$old"); + print "I: Unpacking complete.\n"; +} + +sub tidy_apt +{ + print "I: Tidying up apt cache and list data.\n"; + opendir (DEBS, "${dir}${libdir}lists/") + or die ("Cannot read apt lists directory.\n"); + my @lists=grep(!m:\.\.?$:, readdir DEBS); + closedir (DEBS); + foreach my $file (@lists) + { + next if (-d $file); + unlink ("${dir}${libdir}lists/$file"); + } + opendir (DEBS, "${dir}${cachedir}/") + or die ("Cannot read apt cache directory/.\n"); + my @files=grep(!m:\.\.?$:, readdir DEBS); + closedir (DEBS); + foreach my $file (@files) + { + next if (-d $file); + next unless ($file =~ /\.bin$/); + unlink ("${dir}${cachedir}$file"); + } + if ($forceunpack eq "true") + { + opendir (DEBS, "${dir}${cachedir}/archives/") + or die ("Cannot read apt archives directory/.\n"); + my @files=grep(!m:\.\.?$:, readdir DEBS); + closedir (DEBS); + foreach my $file (@files) + { + next if (-d $file); + next unless ($file =~ /\.deb$/); + unlink ("${dir}${cachedir}archives/$file"); + } } } -system ("apt-get $config_str update"); + +sub get_required_debs +{ + # emulate required="$(get_debs Priority: required)" + # from debootstrap/functions + # needs to be run after the first apt-get install so that + # Packages files exist + my @required=(); + my @debs=(); + opendir (PKGS, "${dir}${libdir}lists/") + or die ("Cannot open ${dir}${libdir}lists/ directory. $!\n"); + my @lists=grep(/_Packages$/, readdir (PKGS)); + closedir (PKGS); + # only read Packages files from the debootstrap entries where + # options do not include --no-resolve-deps + foreach my $strap (@debootstrap) + { + next if ($options{$strap} =~ /no-resolve-deps/); + my $s = lc($strap); + foreach my $l (@lists) + { + next unless ($l =~ /$s/); + push (@required, $l); + } + } + foreach my $file (@required) + { + my $fh = IO::File->new("${dir}${libdir}lists/$file"); + my $parser = Parse::Debian::Packages->new( $fh ); + while (my %package = $parser->next) + { + next unless $package{'Priority'} eq "required"; + push @debs, $package{'Package'}; + } + } + return \@debs; +} sub write_script { @@ -316,6 +517,11 @@ END em_multistrap - extends debootstrap for multiple repository support +=head1 Synopsis + + em_multistrap [-a ARCH] [-d DIR] -f CONFIG_FILE + em_multistrap -?|-h|--help|--version + =head1 Description em_multistrap extends debootstrap to provide support for multiple @@ -327,8 +533,11 @@ Example configuration: [General] arch=arm - directory=/opt/emdebian/debootstrap/multistrap/ + directory=/opt/multistrap/ retries=5 + # extract all downloaded archives as well as those + # unpacked by debootstrap. + forceunpack=false # the order of sections is important. # debootstraps are unpacked in this sequence. debootstrap=Debian @@ -341,7 +550,9 @@ Example configuration: options= This will result in a completely normal debootstrap of Debian lenny from -the specified mirror using packages for ARM. +the specified mirror, for ARM in /opt/multistrap/. + +'Architecture' and 'directory' can be overridden on the command line. Section names are case-insensitive. @@ -356,7 +567,7 @@ contains packages that are also in any of the other repositories but at a lower version. This prevents debootstrap unpacking the older version of the duplicated package. -General settings: +=head1 General settings: The order of repositories specified in the debootstrap (under General), determines which repository is unpacked first - this is important if one @@ -383,4 +594,15 @@ em_multistrap does not currently implement machine:variant support but the build directory is not packed up at the end of the run so other scripts can be used to implement customisations. +=head1 Native mode disabled + +em_multistrap is not intended for native support, it was developed for +cross architeeture support. In order for multiple repositories to be +used, em_multistrap sets the --foreign option with debootstrap even if +debootstrap would be able to complete the installation of each single +repository. The reason for this is so that only the packages selected +by apt are actually unpacked. + +Currently, em_multistrap disables native support. + =cut