summaryrefslogtreecommitdiff
path: root/multistrap
diff options
context:
space:
mode:
Diffstat (limited to 'multistrap')
-rwxr-xr-xmultistrap1544
1 files changed, 1544 insertions, 0 deletions
diff --git a/multistrap b/multistrap
new file mode 100755
index 0000000..f611839
--- /dev/null
+++ b/multistrap
@@ -0,0 +1,1544 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2009-2011 Neil Williams <codehelp@debian.org>
+#
+# This package 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 3 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, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use IO::File;
+use Config::Auto;
+use Cwd qw (realpath getcwd);
+use File::Basename;
+use Parse::Debian::Packages;
+use POSIX qw(locale_h);
+use Locale::gettext;
+
+use vars qw/ $progname $ourversion $dstrap $extra @aptsources
+ $deb $cachedir $config_str %packages $retval $str $retries
+ $dir $include $arch $foreign $url $unpack $sourcedir $msg $etcdir
+ @e $sourcesname $libdir $dpkgdir @debootstrap %suites %components $chk
+ $repo @dirs @touch %sources $section %keys $host $key $value $preffile
+ $type $file $config $tidy $noauth $keyring %keyrings $deflist $cfgdir
+ @extrapkgs @includes %source $setupsh $configsh $omitrequired $dryrun
+ $omitpreinst @reinstall $tgzname %required $check @check
+ $explicit_suite $allow_recommends %omitdebsrc @dsclist @sectoutput
+ %flatfile %important $addimportant @debconf $hookdir %hooks
+ $warn_count $use_shortcut @foreignarches $olddpkg $ignorenative
+ %foreignpkgs $markauto $default_release $pre_config_str/;
+
+setlocale(LC_MESSAGES, "");
+textdomain("multistrap");
+$progname = basename($0);
+$ourversion = &our_version();
+$default_release = "*";
+$unpack = "true";
+%omitdebsrc=();
+%foreignpkgs=();
+while( @ARGV ) {
+ $_= shift( @ARGV );
+ last if m/^--$/;
+ if (!/^-/) {
+ unshift(@ARGV,$_);
+ last;
+ } elsif (/^(-\?|-h|--help|--version)$/) {
+ &usageversion();
+ exit( 0 );
+ } elsif (/^(-s|--shortcut)$/) {
+ $use_shortcut = shift(@ARGV);
+ } elsif (/^(-f|--file)$/) {
+ $file = shift(@ARGV);
+ } elsif (/^(-a|--arch)$/) {
+ $arch = shift(@ARGV);
+ } elsif (/^(-d|--dir)$/) {
+ $dir = shift(@ARGV);
+ $dir .= ($dir =~ m:/$:) ? '' : "/";
+ } elsif (/^(--tidy-up)$/) {
+ $tidy++;
+ } elsif (/^(--source-dir)$/) {
+ $sourcedir = shift (@ARGV);
+ $sourcedir .= ($sourcedir =~ m:/$:) ? '' : "/";
+ $sourcedir = (-d $sourcedir) ? $sourcedir : undef;
+ } elsif (/^(--no-auth)$/) {
+ $noauth++;
+ } elsif (/^(--dry-run|--simulate)$/) {
+ $dryrun++;
+ } else {
+ die "$progname: "._g("Unknown option")." $_.\n";
+ }
+}
+if (defined $use_shortcut) {
+ my $short = "/usr/share/multistrap/".$use_shortcut.".conf";
+ $file = $short if (-f $short);
+ $short = "/etc/multistrap.d/".$use_shortcut.".conf";
+ $file = $short if (-f $short);
+}
+$msg = sprintf (_g("Need a configuration file - use %s -f\n"), $progname);
+die ($msg)
+ if (not defined $file);
+undef ($msg);
+
+$cachedir = "var/cache/apt/"; # archives
+$libdir = "var/lib/apt/"; # lists
+$etcdir = "etc/apt/"; # sources
+$dpkgdir = "var/lib/dpkg/"; # state
+
+$cfgdir=dirname($file);
+cascade($file);
+# Translators: fields are programname, version string, include file.
+printf (_g("%s %s using %s\n"), $progname, $ourversion, $file);
+$host = `dpkg --print-architecture`;
+chomp($host);
+foreach my $inc (@includes) {
+ cascade($inc);
+}
+if (defined $omitrequired and defined $addimportant) {
+ warn("\n"._g("Error: Cannot set 'add Priority: important' when packages ".
+ "of 'Priority: required' are being omitted.\n"));
+ if (scalar @includes > 0) {
+ my $plural = ngettext("Please also check the included configuration file:",
+ "Please also check the included configuration files:", scalar @includes);
+ warn (sprintf("%s '%s'\n", $plural, join ("', '", sort @includes)));
+ }
+ if (defined $dryrun) {
+ warn("\n");
+ &dump_config;
+ exit 0;
+ }
+ exit (7);
+}
+uniq_sort (\@debootstrap);
+uniq_sort (\@aptsources);
+if (defined $dryrun) {
+ &dump_config;
+ exit 0;
+}
+# Translators: fields are: programname, versionstring, configfile.
+printf (_g("%s %s using %s\n"), $progname, $ourversion, $file);
+if ((not defined $arch) or ($arch eq "")) {
+ $arch = $host;
+ printf (_g("Defaulting architecture to native: %s\n"),$arch);
+} elsif ($arch eq $host) {
+ printf (_g("Defaulting architecture to native: %s\n"),$arch);
+} else {
+ printf (_g("Using foreign architecture: %s\n"), $arch);
+}
+$foreign++ if (($host ne $arch) or (defined $ignorenative));
+if (not defined $dir or not defined $arch) {
+ &dump_config;
+ exit 3;
+}
+unless (keys %sources) {
+ my $msg = sprintf(_g("No sources defined for a foreign multistrap.
+ Using your existing apt sources. To use different sources,
+ list them with aptsources= in '%s'."), $file);
+ warn ("$progname: $msg\n");
+ $warn_count++;
+ my $l = prepare_sources_list();
+ $deflist = join ("", @$l);
+}
+
+# Translators: fields are: programname, architecture, host architecture.
+printf (_g("%s building %s multistrap on '%s'\n"), $progname, $arch, $host);
+if ($dir =~ /^$/) {
+ my $msg = _g("No directory specified!");
+ die "$progname: $msg\n";
+}
+&mkdir_fatal ($dir);
+$dir = realpath ($dir);
+$dir .= ($dir =~ m:/$:) ? '' : "/";
+system_fatal ("mkdir -p ${dir}${cachedir}") if (not -d "${dir}${cachedir}");
+system_fatal ("mkdir -p ${dir}${libdir}") if (not -d "${dir}${libdir}");
+system_fatal ("mkdir -p ${dir}${dpkgdir}") if (not -d "${dir}${dpkgdir}");
+system_fatal ("mkdir -p ${dir}etc/apt/sources.list.d/")
+ if (not -d "${dir}etc/apt/sources.list.d/");
+system_fatal ("mkdir -p ${dir}etc/apt/trusted.gpg.d/")
+ if (not -d "${dir}etc/apt/trusted.gpg.d/");
+system_fatal ("mkdir -p ${dir}etc/apt/preferences.d/")
+ if (not -d "${dir}etc/apt/preferences.d/");
+system_fatal ("mkdir -p ${dir}usr/share/info/")
+ if (not -d "${dir}usr/share/info/");
+system_fatal ("touch ${dir}usr/share/info/dir");
+if (defined $preffile) {
+ open (PREF, "$preffile") or die ("$progname: $preffile $!");
+ my @prefs=<PREF>;
+ close (PREF);
+ my $name = basename($preffile);
+ open (MPREF, ">${dir}etc/apt/preferences.d/$name") or die ("$progname: $name $!");
+ print MPREF @prefs;
+ close (MPREF);
+}
+@dirs = qw/ alternatives info parts updates /;
+@touch = qw/ arch diversions statoverride status lock/;
+foreach my $dpkgd (@dirs) {
+ if (not -d "${dir}${dpkgdir}$dpkgd") {
+ mkdir_fatal ("${dir}${dpkgdir}$dpkgd");
+ }
+}
+foreach my $file (@touch) {
+ utime(time, time, "${dir}${dpkgdir}/$file") or (
+ open(F, ">${dir}${dpkgdir}/$file") && close F );
+}
+utime(time, time, "${dir}etc/shells") or
+ (open(F, ">${dir}etc/shells") && close F );
+
+if (not -d "${dir}etc/network") {
+ mkdir_fatal ("${dir}etc/network");
+}
+
+if (not -d "${dir}dev") {
+ mkdir_fatal ("${dir}dev");
+}
+if (($olddpkg == 0) and (scalar (@foreignarches) > 0)) {
+ if (not -d "${dir}etc/dpkg/dpkg.cfg.d/") {
+ system_fatal ("mkdir -p ${dir}etc/dpkg/dpkg.cfg.d/");
+ }
+ if (not -f "${dir}etc/dpkg/dpkg.cfg.d/multiarch") {
+ open (MA, ">${dir}etc/dpkg/dpkg.cfg.d/multiarch");
+ foreach my $farch (@foreignarches) {
+ print MA "foreign-architecture $farch\n";
+ }
+ close (MA);
+ open (VMA, ">${dir}${dpkgdir}arch");
+ print VMA "$host\n";
+ foreach my $farch (@foreignarches) {
+ print VMA "$farch\n";
+ }
+ close (VMA);
+ }
+}
+
+&guard_lib64($dir);
+
+system_fatal ("rm -rf ${dir}etc/apt/sources.list.d/*");
+unlink ("${dir}etc/apt/sources.list")
+ if (-f "${dir}etc/apt/sources.list");
+
+foreach $repo (sort keys %suites) {
+ if (not -e "${dir}${cachedir}") {
+ mkdir_fatal ("${dir}${cachedir}");
+ }
+ if (not -e "$dir/${libdir}lists") {
+ mkdir_fatal ("$dir/${libdir}lists");
+ }
+ if (not -e "$dir/${libdir}lists/partial") {
+ mkdir_fatal ("$dir/${libdir}lists/partial");
+ }
+ if (not -e "$dir/${cachedir}archives") {
+ mkdir_fatal ("$dir/${cachedir}archives");
+ }
+ if (not -e "$dir/${cachedir}archives/partial") {
+ mkdir_fatal ("$dir/${cachedir}archives/partial");
+ }
+ if (not -e "${dir}${etcdir}apt.conf.d") {
+ mkdir_fatal ("${dir}${etcdir}apt.conf.d");
+ }
+}
+foreach my $aptsrc (@debootstrap) {
+ if (defined $deflist) {
+ open (SOURCES, ">>${dir}etc/apt/sources.list.d/multistrap.sources.list")
+ or die _g("Cannot open sources list"). $!;
+ print SOURCES $deflist;
+ close SOURCES;
+ } elsif (-d "${dir}etc/apt/") {
+ open (SOURCES, ">>${dir}etc/apt/sources.list.d/multistrap-${aptsrc}.list")
+ or die _g("Cannot open sources list"). $!;
+ my $mirror = $sources{$aptsrc};
+ my $suite = (exists $flatfile{$aptsrc}) ? "" : $suites{$aptsrc};
+ my $component = (exists $flatfile{$aptsrc}) ? ""
+ : (defined $components{$aptsrc}) ? $components{$aptsrc} : "main";
+ if (defined $mirror and defined $suite) {
+ if ($olddpkg != 0) {
+ print SOURCES "deb $mirror $suite $component\n";
+ } else {
+ if (scalar (@foreignarches) == 0) {
+ print SOURCES "deb [arch=$arch] $mirror $suite $component\n";
+ } else {
+ foreach my $farch (@foreignarches) {
+ print SOURCES "deb [arch=$farch] $mirror $suite $component\n";
+ }
+ }
+ }
+ print SOURCES "deb-src $mirror $suite $component\n" if (not defined $omitdebsrc{$aptsrc});
+ close SOURCES;
+ }
+ }
+}
+my $k;
+foreach my $pkg (values %keyrings) {
+ next if (not defined $pkg);
+ next if ("" eq "$pkg");
+ $k .= "$pkg ";
+}
+if ((defined $k) and (not defined $noauth)) {
+ # the keyring package must be available to the external apt
+ # and apt refuses to allow fakeroot to do this.
+ $str = "";
+ if (not exists $ENV{FAKEROOTKEY}) {
+ if ((exists $ENV{USER}) and ($ENV{USER} ne "root")) {
+ $str = "sudo" if (-f "/usr/bin/sudo");
+ }
+ } else {
+ print "Turning off SecureApt due to use of fakeroot\n";
+ $noauth++;
+ }
+}
+if ((defined $k) and (not defined $noauth)) {
+ printf (_g("I: Installing %s\n"), $k);
+ system ("$str apt-get -y -d --reinstall install $k");
+ foreach my $keyring_pkg (values %keyrings) {
+ next if (not defined $keyring_pkg);
+ my @files=();
+ my $file = `find /var/cache/apt/archives/ -name "$keyring_pkg*"|grep -m1 $keyring_pkg`;
+ chomp ($file);
+ if ($file eq "") {
+ my $msg = sprintf (_g("Unable to download keyring package: '%s'"),$dir);
+ die "$progname: $msg\n";
+ }
+ my $xdir = `mktemp -d -t keyring.XXXXXX`;
+ chomp ($xdir);
+ system ("dpkg -X $file $xdir >/dev/null");
+ if (-d "${xdir}/usr/share/keyrings") {
+ opendir (DIR, "${xdir}/usr/share/keyrings");
+ @files=grep(!m:\.\.?$:,readdir DIR);
+ closedir (DIR);
+ }
+ foreach my $gpg (@files) {
+ next if ($gpg =~ /removed/);
+ $retval = system ("gpg --no-default-keyring ".
+ "--homedir=${dir}/etc/apt/trusted.gpg.d/ ".
+ "--keyring=multistrap.gpg ".
+ " --import ${xdir}/usr/share/keyrings/${gpg} 2>/dev/null");
+ $retval >>= 8;
+ die (_g("Secure Apt handling failed - try without authentication."))
+ if ($retval != 0);
+ }
+ system ("rm -rf ${xdir}");
+ }
+ if (-f "${dir}/etc/apt/trusted.gpg.d/multistrap.gpg") {
+ system_fatal ("cp ${dir}/etc/apt/trusted.gpg.d/multistrap.gpg ${dir}/etc/apt/trusted.gpg.d/trustdb.gpg");
+ } else {
+ die (_g("Secure Apt handling failed - try without authentication.")."\n");
+ }
+}
+
+$pre_config_str = '';
+$pre_config_str .= "Dir::Etc \"${dir}${etcdir}\";\n";
+$pre_config_str .= "Dir::Etc::Parts \"${dir}${etcdir}apt.conf.d/\";\n";
+$pre_config_str .= "Dir::Etc::PreferencesParts \"${dir}${etcdir}preferences.d/\";\n";
+
+my $tmp_apt_conf = `mktemp -t multistrap.XXXXXX`;
+chomp ($tmp_apt_conf);
+
+open CONFIG, ">$tmp_apt_conf";
+print CONFIG $pre_config_str;
+close CONFIG;
+
+$config_str = '';
+$config_str .= " -o Apt::Architecture=$arch";
+$config_str .= " -o Dir::Etc::TrustedParts=${dir}${etcdir}trusted.gpg.d";
+$config_str .= " -o Dir::Etc::Trusted=${dir}${etcdir}trusted.gpg.d/trusted.gpg";
+$config_str .= " -o Apt::Get::AllowUnauthenticated=true"
+ if (defined $noauth);
+$config_str .= " -o Apt::Get::Download-Only=true";
+$config_str .= " -o Apt::Install-Recommends=false"
+ if (not defined $allow_recommends);
+$config_str .= " -o Dir=$dir";
+$config_str .= " -o Dir::Etc=${dir}${etcdir}";
+$config_str .= " -o Dir::Etc::Parts=${dir}${etcdir}apt.conf.d/";
+$config_str .= " -o Dir::Etc::PreferencesParts=${dir}${etcdir}preferences.d/";
+$config_str .= " -o APT::Default-Release=$default_release";
+# if (not defined $preffile);
+if (defined $deflist) {
+ $sourcesname = "sources.list.d/multistrap.sources.list";
+ $config_str .= " -o Dir::Etc::SourceList=${dir}${etcdir}$sourcesname";
+}
+$config_str .= " -o Dir::State=${dir}${libdir}";
+$config_str .= " -o Dir::State::Status=${dir}${dpkgdir}status";
+$config_str .= " -o Dir::Cache=${dir}${cachedir}";
+
+my $apt_get = "APT_CONFIG=\"$tmp_apt_conf\" apt-get $config_str";
+my $apt_mark = "APT_CONFIG=\"$tmp_apt_conf\" apt-mark $config_str";
+printf (_g("Getting package lists: %s update\n"), $apt_get);
+
+$retval = system ("$apt_get update");
+$retval >>= 8;
+die (sprintf (_g("apt update failed. Exit value: %d\n"), $retval))
+ if ($retval != 0);
+my @s = ();
+$str = "";
+if (not defined $omitrequired) {
+ print _g("I: Calculating required packages.\n");
+ &get_required_debs;
+ $str .= join (' ', keys %required);
+ if (defined $addimportant) {
+ my $imps = join (' ', sort keys %important);
+ printf(_g("I: Adding 'Priority: important': %s\n"), $imps);
+ $str .= " ".$imps;
+ }
+ chomp($str);
+}
+$str .= " ";
+foreach my $sect (sort keys %packages) {
+ my @list = split (' ', $sect);
+ foreach my $pkg (@list) {
+ next if ($packages{$pkg} =~ /^\s*$/);
+ next if (!(grep(/^$sect$/i, @debootstrap)));
+ my @long=split (/ /, $packages{$sect});
+ foreach my $l (@long) {
+ chomp ($l);
+ if (defined $explicit_suite and $suites{$sect}) {
+ # instruct apt to get packages from the specified
+ # suites (when the package exists in more than one).
+ $str .= " $l/$suites{$sect}" if ((defined $l) and ($l !~ /^\s*$/));
+ } else {
+ $str .= " $l" if ((defined $l) and ($l !~ /^\s*$/));
+ }
+ }
+ }
+}
+chomp($str);
+foreach my $keystr (values %keyrings) {
+ next if (not defined $keystr);
+ $str .= " " . $keystr . " ";
+}
+chomp($str);
+@s = split (/ /, $str);
+uniq_sort (\@s);
+$str = join (' ', @s);
+print "$apt_get -y install $str\n";
+$retval = 0;
+$retval = system ("$apt_get -y install $str");
+$retval >>= 8;
+die (sprintf (_g("apt download failed. Exit value: %d\n"),$retval))
+ if ($retval != 0);
+&force_unpack if ($unpack eq "true");
+&mark_manual_install ($str) if (defined $markauto);
+system ("touch ${dir}${libdir}lists/lock");
+if ((defined $setupsh) and (-x $setupsh)) {
+ $retval = 0;
+ $retval = system ("$setupsh $dir $arch");
+ $retval >>= 8;
+ if ($retval != 0) {
+ warn sprintf(_g("setupscript '%s' returned %d.\n"), $setupsh, $retval);
+ $warn_count++;
+ }
+}
+# run first set of hooks - probably unnecessary re setupscript.
+&run_download_hooks(sort @{$hooks{'D'}}) if (defined $hooks{'D'});
+my $err = &native if (not defined ($foreign) and $unpack eq "true");
+if (defined $err and $err != 0) {
+ warn (_g("Native mode configuration reported an error!\n"));
+ $warn_count++;
+}
+&add_extra_packages;
+system ("cp $configsh $dir/") if ((defined $configsh) and (-f $configsh));
+&handle_source_packages;
+(not defined $tidy) ? system ("$apt_get update") : &tidy_apt;
+&guard_lib64($dir);
+
+# cleanly separate the bootstrap sources from the final apt sources.
+unlink ("${dir}etc/apt/sources.list.d/multistrap.sources.list")
+ if (-f "${dir}etc/apt/sources.list.d/multistrap.sources.list");
+opendir (LISTS, "${dir}etc/apt/sources.list.d/")
+ or die (_g("Cannot read apt sources list directory.\n"));
+my @sources=grep(m:^multistrap-.*\.list$:, readdir LISTS);
+closedir (LISTS);
+foreach my $filelist (@sources) {
+ next if (-d $filelist);
+ unlink ("${dir}etc/apt/sources.list.d/$filelist");
+}
+foreach my $aptsrc (@aptsources) {
+ if (defined $deflist) {
+ open (SOURCES, ">>${dir}etc/apt/sources.list.d/multistrap.sources.list")
+ or die _g("Cannot open sources list"). $!;
+ print SOURCES $deflist;
+ close SOURCES;
+ } elsif (-d "${dir}etc/apt/") {
+ open (SOURCES, ">>${dir}etc/apt/sources.list.d/multistrap-${aptsrc}.list")
+ or die _g("Cannot open sources list"). $!;
+ my $mirror = $sources{$aptsrc};
+ my $suite = (exists $flatfile{$aptsrc}) ? "" : $suites{$aptsrc};
+ my $component = (exists $flatfile{$aptsrc}) ? ""
+ : (defined $components{$aptsrc}) ? $components{$aptsrc} : "main";
+ if (defined $mirror and defined $suite) {
+ if ($olddpkg != 0) {
+ print SOURCES "deb $mirror $suite $component\n";
+ } else {
+ if (scalar (@foreignarches) == 0) {
+ print SOURCES "deb [arch=$arch] $mirror $suite $component\n";
+ } else {
+ foreach my $farch (@foreignarches) {
+ print SOURCES "deb [arch=$farch] $mirror $suite $component\n";
+ }
+ }
+ }
+ print SOURCES "deb-src $mirror $suite $component\n" if (not defined $omitdebsrc{$aptsrc});
+ close SOURCES;
+ }
+ }
+}
+# altered the sources, so get apt to update.
+(not defined $tidy) ? system ("$apt_get update") : &tidy_apt;
+# run second set of hooks
+&run_completion_hooks(sort @{$hooks{'A'}}) if (defined ($hooks{'A'}));
+unlink $tmp_apt_conf;
+if (not defined $warn_count) {
+ printf (_g("\nMultistrap system installed successfully in %s.\n"), $dir);
+} else {
+ my $plural = sprintf(ngettext ("\nMultistrap system reported %d error in %s.\n",
+ "\nMultistrap system reported %d errors in %s.\n", $warn_count), $warn_count, $dir);
+ warn ($plural);
+ $warn_count++;
+}
+if (defined $tgzname) {
+ printf (_g("\nCompressing multistrap system in '%s' to a tarball called: '%s'.\n"), $dir, $tgzname);
+ chdir ("$dir");
+ unlink $tgzname if (-f $tgzname);
+ my $retval = system ("tar -czf ../$tgzname .");
+ $retval >>= 8;
+ if ($retval == 0) {
+ printf (_g("\nRemoving build directory: '%s'\n"), $dir);
+ system ("rm -rf $dir/*");
+ }
+ my $final_path=realpath ("$dir/../$tgzname");
+ if (not defined $warn_count) {
+ printf (_g("\nMultistrap system packaged successfully as '%s'.\n"), $final_path);
+ } else {
+ warn sprintf(_g("\nMultistrap system packaged as '%s' with warnings.\n"), $final_path);
+ }
+}
+print "\n";
+if (not defined $warn_count) {
+ exit 0;
+} else {
+ exit $warn_count;
+}
+
+######### sub routine start ##########
+
+sub our_version {
+ my $query = `dpkg-query -W -f='\${Version}' multistrap 2>/dev/null`;
+ ($query ne "") ? return $query : return "2.1.15";
+}
+
+sub add_extra_packages {
+ if (scalar @extrapkgs > 0) {
+ $str = join (' ', @extrapkgs);
+ print "$apt_get -y install $str\n";
+ system ("$apt_get -y install $str");
+ &force_unpack (@extrapkgs) if ($unpack eq "true");
+ system ("touch ${dir}${libdir}lists/lock");
+ &native if (not defined ($foreign));
+ }
+}
+
+sub mark_manual_install {
+ my @manual = split(/ +/, $_[0]);
+ printf (_g("Marking automatically installed packages... please wait\n"));
+ opendir (DEBS, "${dir}${cachedir}archives/")
+ or die (_g("Cannot read apt archives directory.\n"));
+ my @archives=grep(/.*\.deb$/, readdir DEBS);
+ closedir (DEBS);
+ my @all = map {
+ `LC_ALL=C dpkg -f ${dir}${cachedir}archives/$_ Package`;
+ } @archives;
+ chomp (@all);
+ my @auto = grep {my $pkg = $_; ! grep /$pkg/, @manual} @all;
+ printf(ngettext ("Found %d package to mark.\n",
+ "Found %d packages to mark.\n", scalar @auto), scalar @auto);
+ system ("$apt_mark auto " . join (" ", sort @auto)) if (scalar @auto > 0);
+ printf (_g("Marking automatically installed packages completed.\n"));
+}
+
+sub force_unpack {
+ my (@limits) = @_;
+ my %unpack=();
+ my %filter = ();
+ opendir (DEBS, "${dir}${cachedir}archives/")
+ or die (_g("Cannot read apt archives directory.\n"));
+ my @archives=grep(/.*\.deb$/, readdir DEBS);
+ closedir (DEBS);
+ if (@limits) {
+ foreach my $l (@limits) {
+ foreach my $file (@archives) {
+ if ($file =~ m:$l:) {
+ $filter{$l} = "$file";
+ }
+ }
+ }
+ @archives = sort values %filter;
+ }
+ print _g("I: Calculating obsolete packages\n");
+ foreach $deb (sort @archives) {
+ my $version = `LC_ALL=C dpkg -f ${dir}${cachedir}archives/$deb Version`;
+ my $package = `LC_ALL=C 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 >>= 8;
+ # unlink version in $unpack if 0
+ # unlink $deb (current one) if 1
+ if ($test == 0) {
+ my $old = $deb;
+ $old =~ s/$version/$unpack{$package}/;
+ printf (_g("I: Removing %s\n"), $old);
+ unlink "${dir}${cachedir}archives/$old";
+ next;
+ } else {
+ printf (_g("I: Removing %s\n"), $deb);
+ unlink "${dir}${cachedir}archives/$deb";
+ }
+ }
+ $unpack{$package}=$version;
+ }
+ if (not @limits) {
+ open (LOCK, ">${dir}${libdir}lists/lock");
+ close (LOCK);
+ opendir (DEBS, "${dir}${cachedir}archives/")
+ or die (_g("Cannot read apt archives directory.\n"));
+ @archives=grep(/.*\.deb$/, readdir DEBS);
+ closedir (DEBS);
+ }
+ my $old = `pwd`;
+ chomp ($old);
+ chdir ("${dir}");
+ printf (_g("Using directory %s for unpacking operations\n"), $dir);
+ foreach $deb (sort @archives) {
+ printf (_g("I: Extracting %s...\n"), $deb);
+ my $ver=`LC_ALL=C dpkg -f ./${cachedir}archives/$deb Version`;
+ my $pkg=`LC_ALL=C dpkg -f ./${cachedir}archives/$deb Package`;
+ my $src=`LC_ALL=C dpkg -f ./${cachedir}archives/$deb Source`;
+ my $multi=`LC_ALL=C dpkg -f ./${cachedir}archives/$deb Multi-Arch`;
+ chomp ($ver);
+ chomp ($pkg);
+ chomp ($src);
+ chomp ($multi);
+ if (($multi eq "foreign") or ($multi eq "allowed")) {
+ $multi = '';
+ } elsif ($multi eq "same") {
+ # actually need dpkg multi-arch support implemented before this can be active.
+ #$multi=":".`LC_ALL=C dpkg -f ./${cachedir}archives/$deb Architecture`;
+ #chomp ($multi);
+ $multi = '';
+ if ($multi eq ":all") {
+ # Translators: imagine "Architecture: all" in quotes.
+ my $msg = sprintf(_g("Warning: invalid value '%s' for Multi-Arch field in Architecture: all package: %s. "), $multi, $deb);
+ warn ("$msg\n");
+ $multi = '';
+ }
+ } elsif ($multi ne '') {
+ # Translators: Please do not translate 'same', 'foreign' or 'allowed'
+ my $msg = sprintf(_g("Warning: unrecognised value '%s' for Multi-Arch field in %s. ".
+ "(Expecting 'same', 'foreign' or 'allowed'.)"), $multi, $deb);
+ warn ("$msg\n");
+ $multi = '';
+ }
+ $src =~ s/ \(.*\)//;
+ $src = $pkg if ($src eq "");
+ push @dsclist, $src;
+ mkdir_fatal ("./tmp");
+ my $tmpdir = `mktemp -p ./tmp -d -t multistrap.XXXXXX`;
+ chomp ($tmpdir);
+ my $datatar = `LC_ALL=C dpkg -X ./${cachedir}archives/$deb ${dir}`;
+ my $exit = `echo $?`;
+ chomp ($exit);
+ if ($exit ne "0") {
+ printf(_g("dpkg -X failed with error code %s\nSkipping...\n"), $exit);
+ next;
+ }
+ my @lines = split("\n", $datatar);
+ open (LIST, ">>./${dpkgdir}info/${pkg}${multi}.list");
+ foreach my $l (@lines) {
+ chomp ($l);
+ $l =~ s:^\.::;
+ $l =~ s:^/$:/\.:;
+ $l =~ s:/$::;
+ print LIST "$l\n";
+ }
+ close (LIST);
+ 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${multi}.$mscript";
+ if ( $mscript eq "control" ) {
+ open (MSCRIPT, "./${dpkgdir}info/$pkg${multi}.$mscript");
+ my @scr=<MSCRIPT>;
+ 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/$pkg${multi}.$mscript");
+ }
+ }
+ close (AVAIL);
+ if ( -f "./${dpkgdir}info/$pkg${multi}.conffiles") {
+ print STATUS "Conffiles:\n";
+ printf (_g(" -> Processing conffiles for %s\n"), $pkg);
+ open (CONF, "./${dpkgdir}info/$pkg${multi}.conffiles");
+ my @lines=<CONF>;
+ close (CONF);
+ foreach my $line (@lines) {
+ chomp ($line);
+ my $md5=`LC_ALL=C md5sum ./$line | cut -d" " -f1`;
+ chomp ($md5);
+ print STATUS " $line $md5\n";
+ }
+ }
+ print STATUS "\n";
+ close (STATUS);
+ system ("rm -rf ./${tmpdir}");
+ &guard_lib64 ($dir);
+ }
+ chdir ("$old");
+ # update-alternatives helper / preinst helper
+ if (not -d "${dir}usr/share/man/man1") {
+ &mkdir_fatal ("${dir}usr/share/man/man1");
+ }
+ print _g("I: Unpacking complete.\n");
+ foreach my $seed (@debconf) {
+ next if (not -f $seed);
+ open (SEED, "$seed") or next;
+ my @s=<SEED>;
+ close (SEED);
+ my $sfile = basename($seed);
+ printf (_g("I: Copying debconf preseed data to %s.\n"), $sfile);
+ mkdir_fatal ("${dir}/tmp/preseeds");
+ open (SEED, ">${dir}tmp/preseeds/$sfile");
+ print SEED @s;
+ close (SEED);
+ }
+}
+
+sub run_download_hooks {
+ my (@hooks) = @_;
+ return if (scalar @hooks == 0);
+ # Translators: the plural is followed by a single repeat for each
+ printf(ngettext("I: Running %d post-download hook\n",
+ "I: Running %d post-download hooks\n", scalar @hooks), scalar @hooks);
+ foreach my $hookscript (@hooks) {
+ # Translators: this is a single instance, naming the hook
+ printf (_g("I: Running post-download hook: '%s'\n"), basename($hookscript));
+ my $hookret = system ("$hookscript $dir");
+ $hookret >>= 8;
+ if ($hookret != 0) {
+ printf (_g("I: post-download hook '%s' reported an error: %d\n"), basename($hookscript), $hookret);
+ $warn_count += abs($hookret);
+ }
+ }
+}
+
+sub run_native_hooks_start {
+ my (@hooks) = @_;
+ return if (scalar @hooks == 0);
+ # Translators: the plural is followed by a single repeat for each
+ printf(ngettext("I: Starting %d native hook\n",
+ "I: Starting %d native hooks\n", scalar @hooks), scalar @hooks);
+ foreach my $hookscript (@hooks) {
+ # Translators: this is a single instance, naming the hook
+ printf (_g("I: Starting native hook: '%s'\n"), basename($hookscript));
+ my $hookret = system ("$hookscript $dir start");
+ $hookret >>= 8;
+ if ($hookret != 0) {
+ printf (_g("I: run-native hook start '%s' reported an error: %d\n"), basename($hookscript), $hookret);
+ $warn_count += abs($hookret);
+ }
+ }
+}
+
+sub run_native_hooks_end {
+ my (@hooks) = @_;
+ return if (scalar @hooks == 0);
+ # Translators: the plural is followed by a single repeat for each
+ printf(ngettext("I: Stopping %d native hook\n",
+ "I: Stopping %d native hooks\n", scalar @hooks), scalar @hooks);
+ foreach my $hookscript (@hooks) {
+ # Translators: this is a single instance, naming the hook
+ printf (_g("I: Stopping native hook: '%s'\n"), basename($hookscript));
+ my $hookret = system ("$hookscript $dir end");
+ $hookret >>= 8;
+ if ($hookret != 0) {
+ printf (_g("I: run-native hook end '%s' reported an error: %d\n"), basename($hookscript), $hookret);
+ $warn_count += abs($hookret);
+ }
+ }
+}
+
+sub run_completion_hooks {
+ my (@hooks) = @_;
+ return if (scalar @hooks == 0);
+ # Translators: the plural is followed by a single repeat for each
+ printf(ngettext("I: Running %d post-configuration hook\n",
+ "I: Running %d post-configuration hooks\n", scalar @hooks), scalar @hooks);
+ foreach my $hookscript (@hooks) {
+ # Translators: this is a single instance, naming the hook
+ printf (_g("I: Running post-configuration hook: '%s'\n"), basename($hookscript));
+ my $hookret = system ("$hookscript $dir");
+ $hookret >>= 8;
+ if ($hookret != 0) {
+ printf (_g("I: run-completion hook '%s' reported an error: %d\n"), basename($hookscript), $hookret);
+ $warn_count += abs($hookret);
+ }
+ }
+}
+
+# prevent the absolute symlink in libc6 from allowing
+# writes outside the multistrap root dir. See: #553599
+sub guard_lib64 {
+ my $dir = shift;
+ my $old = `pwd`;
+ chomp ($old);
+ unlink "${dir}lib64" if (-f "${dir}lib64");
+ if (-l "${dir}lib64" ) {
+ my $r = readlink "${dir}lib64";
+ chomp ($r);
+ if ($r =~ m:^/lib$:) {
+ printf (_g("I: Unlinking unsafe %slib64 -> /lib symbolic link.\n"), $dir);
+ unlink "${dir}lib64";
+ }
+ } elsif (not -d "${dir}lib64") {
+ chdir ("$dir");
+ if ($host eq 'i386' and $arch eq 'amd64') {
+ printf (_g("I: Replaced ./lib64 -> /lib symbolic link with new %slib64 directory.\n"), $dir);
+ mkdir_fatal ("${dir}lib64");
+ } else {
+ printf (_g("I: Setting %slib64 -> %slib symbolic link.\n"), $dir, $dir);
+ symlink "./lib", "lib64";
+ }
+ }
+ chdir ("${old}");
+}
+
+sub check_bin_sh {
+ $dir = shift;
+ my $old = `pwd`;
+ chomp ($old);
+ # dash refuses to configure if no existing shell is found.
+ # (always expects a diversion to already exist).
+ # (works OK in subsequent upgrades.) #546528
+ unlink ("$dir/var/lib/dpkg/info/dash.postinst");
+ unlink ("$dir/var/lib/dpkg/info/dash:${host}.postinst");
+ # now ensure that a usable shell is available as /bin/sh
+ if (not -l "$dir/bin/sh") {
+ print (_g("I: ./bin/sh symbolic link does not exist.\n"));
+ if (-f "$dir/bin/dash") {
+ print (_g("I: Setting ./bin/sh -> ./bin/dash\n"));
+ chdir ("$dir/bin");
+ symlink ("dash", "sh");
+ chdir ("$old");
+ } elsif (-f "$dir/bin/bash") {
+ print (_g("I: ./bin/dash not found. Setting ./bin/sh -> ./bin/bash\n"));
+ chdir ("$dir/bin");
+ symlink ("bash", "sh");
+ chdir ("$old");
+ }
+ }
+ if (-l "$dir/bin/sh") {
+ printf (_g("I: Shell found OK in %s:\n"), "${dir}bin/sh");
+ system ("(cd $dir ; ls -lh bin/sh)");
+ } else {
+ die ("No shell in $dir.");
+ }
+}
+
+sub handle_source_packages {
+ return if (not defined $sourcedir);
+ if ($unpack eq "true") {
+ opendir (DEBS, "${dir}${cachedir}/archives/")
+ or die (_g("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$/);
+ if (defined $sourcedir) {
+ my $srcname = `LC_ALL=C dpkg -f ${dir}${cachedir}archives/$file Source`;
+ chomp ($srcname);
+ $srcname =~ s/ \(.*\)//;
+ if ($srcname eq "") {
+ my $srcname = `LC_ALL=C dpkg -f ${dir}${cachedir}archives/$file Package`;
+ chomp ($srcname);
+ }
+ push @dsclist, $srcname;
+ }
+ }
+ }
+ print "Checking ${dir}${dpkgdir}status\n";
+ if (-f "${dir}${dpkgdir}status") {
+ open (STATUS, "${dir}${dpkgdir}status");
+ my @lines=<STATUS>;
+ close (STATUS);
+ my $pkg;
+ my $src;
+ foreach my $line (@lines) {
+ if ($line =~ /^Package: (.*)$/) {
+ $pkg = $1;
+ }
+ if ($line =~ /^Source: (.*)$/) {
+ my $c = $1;
+ $c =~ s/\(.*\)$//;
+ $c =~ s/ //g;
+ push @dsclist, $c;
+ $src = $c;
+ }
+ if ($line =~ /^$/) {
+ push @dsclist, $pkg if (not defined $src and defined $pkg);
+ undef $pkg;
+ undef $src;
+ }
+ }
+ }
+ uniq_sort (\@dsclist);
+ my $olddir = getcwd();
+ chdir ($sourcedir);
+ if (scalar @dsclist > 0) {
+ print "$apt_get -d source " . join (" ", @dsclist) . "\n";
+ foreach my $srcpkg (@dsclist) {
+ system ("$apt_get -d source $srcpkg");
+ }
+ }
+ chdir ($olddir);
+}
+
+sub tidy_apt {
+ print _g("I: Tidying up apt cache and list data.\n");
+ if ($unpack eq "true") {
+ opendir (DEBS, "${dir}${cachedir}/archives/")
+ or die (_g("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$/);
+ if (defined $sourcedir) {
+ system ("mv ${dir}${cachedir}archives/$file $sourcedir/$file");
+ } else {
+ unlink ("${dir}${cachedir}archives/$file");
+ }
+ }
+ $sourcedir=undef;
+ }
+ unlink ("${dir}etc/apt/sources.list")
+ if (-f "${dir}etc/apt/sources.list");
+ opendir (DEBS, "${dir}${libdir}lists/")
+ or die (_g("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 (_g("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 native arch, do a few tasks just because we can and probably should.
+sub native {
+ my $env = "DEBIAN_FRONTEND=noninteractive ".
+ "DEBCONF_NONINTERACTIVE_SEEN=true ".
+ "LC_ALL=C LANGUAGE=C LANG=C";
+ printf (_g("I: dpkg configuration settings:\n\t%s\n"), $env);
+ if (exists $ENV{FAKEROOTKEY}) {
+ warn (_g("W: Cannot use 'chroot' when fakeroot is in use. Skipping package configuration.\n"));
+ return;
+ }
+ print _g("I: Native mode - configuring unpacked packages . . .\n");
+ my $str = "";
+ if ($ENV{USER} eq 'root') {
+ $str = "sudo" if (-f "/usr/bin/sudo");
+ }
+ # check that we have a workable shell inside the chroot
+ &check_bin_sh("$dir");
+ # pre-populate debconf
+ if (-d "${dir}/tmp/preseeds/") {
+ opendir (SEEDS, "${dir}/tmp/preseeds/") or return;
+ my @seeds=grep(!m:\.\.?$:, readdir SEEDS);
+ closedir (SEEDS);
+ foreach my $s (@seeds) {
+ printf (_g("I: Running debconf for seed file: %s\n"), $s);
+ system ("$str $env chroot $dir debconf-set-selections /tmp/preseeds/$s");
+ }
+ }
+ &run_native_hooks_start(sort @{$hooks{'N'}}) if (defined ($hooks{'N'}));
+ if (not defined $omitpreinst) {
+ opendir (PRI, "${dir}/var/lib/dpkg/info") or return;
+ my @preinsts=grep(/\.preinst$/, readdir PRI);
+ closedir (PRI);
+ printf (_g("I: Running preinst scripts with 'install' argument.\n"));
+ my $f = join (" ", @reinstall);
+ foreach my $script (sort @preinsts) {
+ my $t = $script;
+ $t =~ s/\.preinst//;
+ next if ($t =~ /$f/);
+ next if ($script =~ /bash/);
+ system ("$str $env chroot $dir /var/lib/dpkg/info/$script install");
+ }
+ }
+ my $retval = 0;
+ $retval = system ("$str $env chroot $dir dpkg --configure -a");
+ $retval >>=8;
+ if ($retval != 0) {
+ warn (_g("ERR: dpkg configure reported an error.\n"));
+ }
+ # reinstall set
+ foreach my $reinst (sort @reinstall) {
+ system ("$str $env chroot $dir apt-get --reinstall -y install $reinst");
+ }
+ &run_native_hooks_end(sort @{$hooks{'N'}}) if (defined $hooks{'N'});
+ return $retval;
+}
+
+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
+ %required=();
+ my %listfiles=();
+ opendir (PKGS, "${dir}${libdir}lists/")
+ or die sprintf(_g("Cannot open %s directory. %s\n"),
+ "${dir}${libdir}lists/", $!);
+ my @lists=grep(/_Packages$/, readdir (PKGS));
+ closedir (PKGS);
+ foreach my $strap (@debootstrap) {
+ my $s = lc($strap);
+ foreach my $l (@lists) {
+ $listfiles{$l}++;
+ }
+ }
+ foreach my $file (keys %listfiles) {
+ my $fh = IO::File->new("${dir}${libdir}lists/$file");
+ my $parser = Parse::Debian::Packages->new( $fh );
+ while (my %package = $parser->next) {
+ if (not defined $package{'Priority'} and (defined $package{'Essential'})) {
+ $required{$package{'Package'}}++;
+ next;
+ }
+ next if (not defined $package{'Priority'});
+ if ($package{'Priority'} eq "required") {
+ $required{$package{'Package'}}++;
+ } elsif ($package{'Priority'} eq "important") {
+ $important{$package{'Package'}}++;
+ }
+ }
+ }
+}
+
+# inherited from apt-cross
+sub prepare_sources_list {
+ my @source_list=();
+ # collate all available/configured sources into one list
+ if (-e "/etc/apt/sources.list") {
+ open (SOURCES, "/etc/apt/sources.list")
+ or die _g("cannot open apt sources list. %s",$!);
+ @source_list = <SOURCES>;
+ close (SOURCES);
+ }
+ if (-d "/etc/apt/sources.list.d/") {
+ opendir (FILES, "/etc/apt/sources.list.d/")
+ or die _g("cannot open apt sources.list directory %s\n",$!);
+ my @files = grep(!/^\.\.?$/, readdir FILES);
+ foreach my $f (@files) {
+ next if ($f =~ /\.ucf-old$/);
+ open (SOURCES, "/etc/apt/sources.list.d/$f") or
+ die _g("cannot open /etc/apt/sources.list.d/%s %s",$f, $!);
+ while(<SOURCES>) {
+ push @source_list, $_;
+ }
+ close (SOURCES);
+ }
+ closedir (FILES);
+ }
+ return \@source_list;
+}
+
+sub usageversion {
+ printf STDERR (_g("
+%s version %s
+
+Usage:
+ %s [-a ARCH] [-d DIR] -f CONFIG_FILE
+ %s -?|-h|--help|--version
+
+Command:
+ -f|--file CONFIG_FILE: path to the multistrap configuration file.
+
+Options:
+ -a|--arch ARCHITECTURE: override the configuration file architecture.
+ -d|--dir PATH: override the configuration file directory.
+ --no-auth: do not use Secure Apt for any repositories
+ --tidy-up: remove apt cache data and downloaded archives.
+ --dry-run: output the configuration and exit
+ --simulate: output the configuration and exit
+ -?|-h|--help: print this usage message and exit
+ --version: print this usage message and exit
+
+%s replaces debootstrap to provide support for multiple
+repositories, using a configuration file to specify the relevant suites,
+architecture, extra packages and the mirror to use for each repository.
+
+Example configuration:
+[General]
+arch=armel
+directory=/opt/multistrap/
+# same as --tidy-up option if set to true
+cleanup=true
+# same as --no-auth option if set to true
+# keyring packages listed in each bootstrap will
+# still be installed.
+noauth=false
+# extract all downloaded archives (default is true)
+unpack=true
+# enable MultiArch for the specified architectures
+# default is empty
+multiarch=
+# aptsources is a list of sections to be used for downloading packages
+# and lists and placed in the /etc/apt/sources.list.d/multistrap.sources.list
+# of the target. Order is not important
+aptsources=Debian
+# the order of sections is not important.
+# the bootstrap option determines which repository
+# is used to calculate the list of Priority: required packages.
+bootstrap=Debian
+
+[Debian]
+packages=
+source=http://http.debian.net/debian
+keyring=debian-archive-keyring
+suite=stable
+
+This will result in a completely normal bootstrap of Debian stable from
+the specified mirror, for armel in /opt/multistrap/.
+
+'Architecture' and 'directory' can be overridden on the command line.
+
+Specify a package to extend the bootstap to include that package and
+all dependencies. Dependencies will be calculated by apt so as to use
+only the most recent suitable version from all configured repositories.
+
+General settings:
+
+'directory' specifies the top level directory where the bootstrap
+will be created - it is not packed into a .tgz once complete.
+
+"), $progname, $ourversion, $progname, $progname, $progname)
+ or die ("$progname: ". _g("failed to write usage:") . "$!\n");
+}
+
+sub cascade {
+ $olddpkg = &check_multiarch_dpkg;
+ $file = shift;
+ my @req_arches=();
+ $config = Config::Auto::parse($file, format => 'ini');
+ if (not defined $config or (scalar keys %$config) == 0) {
+ die ("$progname: ". sprintf(_g("Failed to parse '%s'!\n"), $file));
+ }
+ foreach $key (%$config) {
+ $type = lc($key) if (ref $key ne "HASH");
+ $value = $key if (ref $key eq "HASH");
+ $keys{$type} = $value;
+ }
+ foreach $section (sort keys %keys) {
+ if ($section eq "general") {
+ $arch = $keys{$section}{'arch'}
+ if (defined $keys{$section}{'arch'} and (not defined $arch));
+ $dir = $keys{$section}{'directory'}
+ if (defined $keys{$section}{'directory'} and (not defined $dir));
+ # support the original value but replace by new value.
+ $unpack = "false" if (defined $keys{$section}{'forceunpack'} and (lc($keys{$section}{'forceunpack'}) ne "true"));
+ $unpack = "false" if (defined $keys{$section}{'unpack'} and (lc($keys{$section}{'unpack'} ne "true")));
+ $markauto++ if ((defined $keys{$section}{'markauto'}) and (lc($keys{$section}{'markauto'}) eq "true"));
+ $configsh = lc($keys{$section}{'configscript'})
+ if (defined $keys{$section}{'configscript'} and (not defined $configsh));
+ $tgzname = lc($keys{$section}{'tarballname'})
+ if (defined $keys{$section}{'tarballname'} and (not defined $tgzname));
+ chomp($tgzname) if (defined $tgzname);
+ undef $tgzname if (defined $tgzname and $tgzname eq '');
+ if ((defined $configsh) and ($configsh eq '')) {
+ undef $configsh
+ }
+ if ((defined $configsh) and (not -x $configsh)) {
+ my $configmsg = sprintf (_g("INF: '%s' exists but is not executable - ignoring.\n"), $configsh);
+ undef $configsh;
+ warn $configmsg;
+ $warn_count++;
+ }
+ $setupsh = lc($keys{$section}{'setupscript'})
+ if (defined $keys{$section}{'setupscript'} and (not defined $setupsh));
+ undef $setupsh if ((defined $setupsh) and (not -x $setupsh));
+ $omitrequired++ if (defined $keys{$section}{'omitrequired'} and (lc($keys{$section}{'omitrequired'}) eq "true"));
+ $addimportant++ if (defined $keys{$section}{'addimportant'} and (lc($keys{$section}{'addimportant'}) eq "true"));
+ $omitpreinst++ if (defined $keys{$section}{'omitpreinst'} and ($keys{$section}{'omitpreinst'} eq "true"));
+ $tidy++ if ((defined $keys{$section}{'cleanup'}) and ($keys{$section}{'cleanup'} eq "true"));
+ $noauth++ if ((defined $keys{$section}{'noauth'}) and ($keys{$section}{'noauth'} eq "true"));
+ $ignorenative++ if ((defined $keys{$section}{'ignorenativearch'}) and
+ (lc($keys{$section}{'ignorenativearch'}) eq 'true'));
+ $preffile = lc($keys{$section}{'aptpreferences'})
+ if (defined $keys{$section}{'aptpreferences'} and (not defined $preffile));
+ undef $preffile if ((defined $preffile) and (not -f $preffile));
+ $sourcedir = $keys{$section}{'retainsources'}
+ if ((defined $keys{$section}{'retainsources'}) and (-d $keys{$section}{'retainsources'}));
+ $explicit_suite++ if ((defined $keys{$section}{'explicitsuite'}) and
+ ($keys{$section}{'explicitsuite'} eq "true"));
+ $allow_recommends++ if ((defined $keys{$section}{'allowrecommends'}) and
+ ($keys{$section}{'allowrecommends'} eq "true"));
+ $default_release = lc($keys{$section}{'aptdefaultrelease'})
+ if (defined $keys{$section}{'aptdefaultrelease'});
+ my @p = split(' ', lc($keys{$section}{'debconfseed'}))
+ if (defined $keys{$section}{'debconfseed'});
+ foreach my $f (@p) {
+ my $fl = realpath ($f);
+ next if ($fl eq "");
+ next if (not -f $fl);
+ chomp ($fl);
+ push @debconf, $fl;
+ }
+ my @h = split(' ', lc($keys{$section}{'hookdir'}))
+ if (defined $keys{$section}{'hookdir'});
+ foreach my $f (@h) {
+ opendir (HOOKS, "$f") or next;
+ my @hookfiles=grep(!m:\.\.?$:, readdir HOOKS);
+ closedir(HOOKS);
+ foreach my $hf (@hookfiles) {
+ my $fl = realpath ("$f/$hf");
+ next if (($fl eq "") or (not -f $fl) or (not -x $fl));
+ push (@{$hooks{'A'}}, $fl) if ($hf =~ /^completion/);
+ push (@{$hooks{'D'}}, $fl) if ($hf =~ /^download/);
+ push (@{$hooks{'N'}}, $fl) if ($hf =~ /^native/);
+ }
+ }
+ my @ma=();
+ if ($olddpkg == 0) {
+ @ma = split(' ',lc($keys{$section}{'multiarch'}))
+ if (defined $keys{$section}{'multiarch'});
+ }
+ push @foreignarches, @ma;
+ my @d=();
+ @d = split(' ', lc($keys{$section}{'debootstrap'}))
+ if (defined $keys{$section}{'debootstrap'});
+ push @debootstrap, @d;
+ my @b = split(' ', lc($keys{$section}{'bootstrap'}))
+ if (defined $keys{$section}{'bootstrap'});
+ push @debootstrap, @b;
+ my @a=();
+ if (exists $keys{$section}{'aptsources'}) {
+ @a = split (' ', lc($keys{$section}{'aptsources'}));
+ }
+ push @aptsources, @a;
+ my @i = split (' ', $keys{$section}{'include'})
+ if (defined $keys{$section}{'include'});
+ foreach my $inc (@i) {
+ # look for the full filepath or try same directory as current conf.
+ if (not -f $inc) {
+ $chk = realpath ("$cfgdir/$inc");
+ chomp ($chk) if (defined $chk);
+ $inc = $chk if (-f $chk);
+ }
+ if (not -f $inc) {
+ my $dirmsg = sprintf (_g("ERR: Cannot find include file: '%s' for '%s'"), $inc, $file);
+ die ("$dirmsg\n");
+ }
+ }
+ push @includes, @i;
+ } else {
+ $sources{$section}=$keys{$section}{'source'} if (not exists $source{$section});
+ # don't set suite or component if URL is of apt-ftparchive trailing-slash form
+ # regexp is: optional string in '[]', string without '[' or ']', string ending in '/'
+ $flatfile{$section}++ if (($sources{$section} =~ /^(\[.*\] )*[^\[\]]+ .+\/$/));
+ if ((exists $keys{$section}{'architecture'}) and
+ ($keys{$section}{'architecture'} ne "")) {
+ my $frgn_arch = $keys{$section}{'architecture'};
+ my @tmp=();
+ if (ref ($keys{$section}{'packages'}) eq 'ARRAY') {
+ foreach my $p (@{$keys{$section}{'packages'}}) {
+ push @tmp, "$p:$frgn_arch";
+ push @req_arches, $frgn_arch;
+ }
+ } else {
+ foreach my $p (split(' ', $keys{$section}{'packages'})) {
+ push @tmp, "$p:$frgn_arch";
+ push @req_arches, $frgn_arch;
+ }
+ }
+ if ($olddpkg == 0) {
+ $packages{$section} = join(' ', @tmp);
+ } else {
+ my $dpkgmsg = sprintf (_g("ERR: Unsupportable option: 'architecture'. ".
+ "Current dpkg version does not support MultiArch. ".
+ "Packages for '%s' have been ignored.\n"), $section);
+ warn $dpkgmsg;
+ $warn_count++;
+ }
+ } else {
+ if (ref ($keys{$section}{'packages'}) eq 'ARRAY') {
+ $packages{$section}=join(' ', @{$keys{$section}{'packages'}});
+ } else {
+ $packages{$section}=join(' ', $keys{$section}{'packages'});
+ }
+ }
+ $suites{$section}=$keys{$section}{'suite'}
+ if (not exists $suites{$section} and not exists $flatfile{$section});
+ $components{$section}=$keys{$section}{'components'}
+ if (not exists $components{$section} and not exists $flatfile{$section});
+ $omitdebsrc{$section}=$section if ((defined $keys{$section}{'omitdebsrc'})
+ and ($keys{$section}{'omitdebsrc'} eq "true"));
+ push @reinstall, split (/ /, lc($keys{$section}{'reinstall'}))
+ if (defined $keys{$section}{'reinstall'});
+ $components{$section}='main' if (not defined $components{$section});
+ $keyrings{$section}=$keys{$section}{'keyring'} if (not exists $keyrings{$section});
+ push @extrapkgs, split (' ', lc($keys{$section}{'additional'}))
+ if (defined $keys{$section}{'additional'});
+ }
+ }
+ my %archchk=();
+ foreach my $farch (@foreignarches) {
+ $archchk{$farch}++;
+ }
+ foreach my $req (@req_arches) {
+ if (not exists $archchk{$req}) {
+ # Translators: %1 and %2 are the same value here - the erroneous architecture name
+ my $reqmsg = sprintf (_g("ERR: Misconfiguration in: 'architecture' option. ".
+ "Packages of architecture=%s requested but '%s' is not included in the multiarch=".
+ join (" ", @foreignarches) . " option.\n"), $req, $req);
+ warn $reqmsg;
+ die ("\n");
+ }
+ }
+ uniq_sort (\@reinstall);
+ uniq_sort (\@extrapkgs);
+}
+
+# returns zero on success, non-zero on fail
+sub check_multiarch_dpkg {
+ my $retval = system ("dpkg --print-foreign-architectures > /dev/null 2>&1");
+ $retval >>=8;
+ return $retval;
+}
+
+sub system_fatal {
+ my $cmd = shift;
+ my $retval = system ("$cmd");
+ my $err = $!;
+ $retval >>= 8;
+ return if ($retval == 0);
+ my $msg = sprintf(_g("ERR: system call failed: '%s' %s"), $cmd, $err);
+ die ("$msg\n");
+}
+
+sub mkdir_fatal {
+ my $d = shift;
+ if (not -d "$d") {
+ my $ret = system ("mkdir -p $d");
+ $ret >>= 8 if (defined $ret);
+ my $msg = sprintf (_g("Unable to create directory '%s'"),$d);
+ die "$progname: $msg\n" if ($ret != 0);
+ }
+}
+
+sub _g {
+ return gettext(shift);
+}
+
+sub uniq_sort {
+ my $aryref = shift;
+ my %uniq = ();
+ foreach my $i (@$aryref) {
+ $uniq{$i}++;
+ }
+ @$aryref = sort keys %uniq;
+}
+
+sub dump_config {
+ if (not defined $dir or not defined $arch) {
+ my $msg = sprintf(_g("The supplied configuration file '%s'".
+ " cannot be parsed correctly."), $file);
+ warn ("\n$msg\n\n");
+ }
+ my $plural;
+ @check=();
+ push @check, @debootstrap;
+ push @check, @aptsources;
+ uniq_sort (\@check);
+ foreach my $sect (@check) {
+ if (not exists $keys{$sect}) {
+ $msg .= sprintf (_g("ERR: The '%s' section is not defined.\n"), $sect);
+ }
+ }
+ if (scalar @includes > 0) {
+ $plural = ngettext("Including configuration file from:",
+ "Including configuration files from:", scalar @includes);
+ printf ("include:\t%s '%s'\n", $plural, join ("', '", sort @includes));
+ } else {
+ printf ("include:\t\t"._g("No included configuration files.\n"));
+ }
+ undef $plural;
+ print "\n";
+ # explain the bootstrap section details explicitly and just refer to
+ # those for the apt sources.
+ foreach my $sect_name (@check) {
+ next unless (defined $packages{$sect_name});
+ printf ("Section name:\t$sect_name\n");
+ print "\tsource:\t\t$sources{$sect_name}\n";
+ my @sorted = split(/ /, $packages{$sect_name});
+ uniq_sort (\@sorted);
+ print "\tsuite:\t\t$suites{$sect_name}\n" if (not exists $flatfile{$sect_name});
+ print "\tcomponents:\t$components{$sect_name}\n" if (not exists $flatfile{$sect_name});
+ # only list packages in a bootstrapping section
+ if (not grep(/^$sect_name$/i, @debootstrap)) {
+ printf ("\t%s\n",_g("Not listed as a 'Bootstrap' section."));
+ print "\n";
+ next;
+ }
+ print "\tpackages:\t".join(" ", @sorted)."\n";
+ print "\n";
+ }
+ $plural = ngettext("Section to install", "Sections to install", scalar @debootstrap);
+ printf ("%s:\t%s\n", $plural, join(" ", sort @debootstrap));
+ $plural = ngettext("Section for updates", "Sections for updates", scalar @aptsources);
+ printf ("%s:\t%s\n", $plural, join(" ", sort @aptsources));
+ my @srcdump=();
+ foreach my $src (sort keys %sources) {
+ next if ((!grep(/^$src$/i, @aptsources)) or (!grep(/^$src$/i, @debootstrap)));
+ push @srcdump, $sources{$src};
+ }
+ my $srcmsg="omitdebsrc\t\t"._g("Omit deb-src from sources.list for sections:");
+ if (scalar keys %omitdebsrc == 0) {
+ $srcmsg .= sprintf(" %s",_g("None."));
+ } else {
+ foreach my $omit (sort keys %omitdebsrc) {
+ $srcmsg .= " " . $omitdebsrc{$omit} if (defined $omitdebsrc{$omit});
+ }
+ }
+ print "$srcmsg\n";
+ if (defined $explicit_suite) {
+ printf("explicitsuite:\t\t"._g("Explicit suite selection: Yes\n"));
+ } else {
+ printf("explicitsuite:\t\t"._g("Explicit suite selection: No - let apt use latest.\n"));
+ }
+ if (defined $allow_recommends) {
+ printf("allowrecommends:\t"._g("Recommended packages are added to the selection.\n"));
+ } else {
+ printf("allowrecommends:\t"._g("Recommended packages are ignored.\n"));
+ }
+ if ($default_release ne "*") {
+ printf("aptdefaultrelease:\t"."APT::Default-Release: ".$default_release."\n");
+ }
+ if (defined $markauto) {
+ printf("markauto:\t\t"._g("Marking dependency packages as auto-installed.\n"));
+ }
+ $plural = ngettext("Debconf preseed file", "Debconf preseed files", scalar @debconf);
+ printf("%s:\t%s\n", $plural, join(" ", sort @debconf)) if (scalar @debconf > 0);
+ if (defined ($hooks{'D'} and scalar @{$hooks{'D'}} > 0)) {
+ # Translators: leaving the plural blank to keep the lines shorter.
+ $plural = ngettext ("Download hook: ", "", scalar @{$hooks{'D'}});
+ print "download hooks:\t\t$plural".join (", ", sort @{$hooks{'D'}})."\n";
+ }
+ if (defined ($hooks{'N'} and scalar @{$hooks{'N'}} > 0)) {
+ # Translators: leaving the plural blank to keep the lines shorter.
+ $plural = ngettext ("Native hook: ", "", scalar @{$hooks{'N'}});
+ print "native hooks:\t\t$plural".join (", ", sort @{$hooks{'N'}})."\n";
+ }
+ if (defined ($hooks{'A'} and scalar @{$hooks{'A'}} > 0)) {
+ # Translators: leaving the plural blank to keep the lines shorter.
+ $plural = ngettext ("Completion hook: ", "", scalar @{$hooks{'A'}});
+ print "completion hooks:\t$plural".join (", ", sort @{$hooks{'A'}})."\n";
+ }
+ $plural = ngettext ("Extra Package: ", "Extra Packages: ", scalar @extrapkgs);
+ print "additional:\t\t$plural".join (", ", sort @extrapkgs)."\n" if (scalar @extrapkgs > 0);
+ print "reinstall:\t\t".join (", ", sort (@reinstall))."\n" if (scalar @reinstall > 0);
+ if (defined $arch and $arch ne "") {
+ printf ("Architecture:\t\t"._g("Architecture to download: %s\n"), $arch);
+ } else {
+ $msg .= sprintf(_g("Cannot determine architecture from '%s'. Using %s.\n"), $file, $host);
+ }
+ if ($olddpkg != 0) {
+ printf "MultiArch:\t\t%s\n",_g("Currently installed dpkg does not support MultiArch.");
+ } elsif (scalar (@foreignarches) > 0) {
+ $plural = ngettext("Foreign architecture", "Foreign architectures", scalar @foreignarches);
+ printf ("MultiArch:\t\t%s: %s\n", $plural, join(" ", sort @foreignarches));
+ }
+ if (defined $dir and $dir ne "") {
+ printf ("dir:\t\t\t"._g("Output directory: '%s'\n"), $dir);
+ } else {
+ $msg .= sprintf(_g("Cannot determine directory from '%s'.\n"), $file);
+ }
+ if ($unpack eq "true") {
+ printf ("unpack:\t\t\t"._g("extract all downloaded archives: %s\n"), $unpack);
+ } else {
+ printf ("unpack:\t\t\t"._g("extract all downloaded archives: %s\n"), "false");
+ }
+ print "configscript:\t\t$configsh\n" if (defined $configsh);
+ printf ("setupscript:\t\t%s: %s",_g("Script to be run after unpacking"),"$setupsh\n") if (defined $setupsh);
+ if (defined $omitrequired) {
+ printf ("omitrequired:\t\t%s\n",_g("'Priority required' packages are not included."));
+ } else {
+ printf ("omitrequired:\t\t%s\n",_g("'Priority: required' packages are included."));
+ }
+ if (defined $addimportant) {
+ printf("addimportant:\t\t"._g("'Priority: important' packages are included.\n"));
+ } else {
+ printf("addimportant:\t\t"._g("'Priority: important' packages are ignored.\n"));
+ }
+ if (defined $tidy) {
+ printf ("cleanup:\t\t"._g("remove apt cache data: true\n"));
+ } else {
+ printf ("cleanup:\t\t"._g("remove apt cache data: false\n"));
+ }
+ if (defined $noauth) {
+ printf ("noauth:\t\t\t"._g("allow the use of unauthenticated repositories: true\n"));
+ } else {
+ printf ("noauth:\t\t\t"._g("allow the use of unauthenticated repositories: false\n"));
+ }
+ if (defined $sourcedir) {
+ printf ("retainsources:\t"._g("Sources will be retained in: %s\n"), $sourcedir);
+ }
+ if (defined $tgzname) {
+ printf ("tarballname:\t\t"._g("Tarball name: '%s'\n"), $tgzname);
+ }
+ if (not defined $foreign or not defined $ignorenative) {
+ if (defined $omitpreinst) {
+ printf ("omitpreinst:\t\t"._g("Preinst scripts are not executed.\n"));
+ } else {
+ printf ("omitpreinst:\t\t"._g("Preinst scripts are executed with the install argument.\n"));
+ }
+ printf ("ignorenativearch:\t"._g("Packages will be configured.\n"));
+ } else {
+ printf ("omitpreinst:\t\t"._g("Preinst scripts are not executed.\n"));
+ printf ("ignorenativearch:\t"._g("Packages will not be configured.\n"));
+ }
+ if (defined $preffile) {
+ printf ("aptpreferences:\t\t"._g("Apt preferences file to use: '%s'\n"), $preffile);
+ } else {
+ printf ("aptpreferences:\t\t"._g("No apt preferences file. Default release: *\n"));
+ }
+ print "\n";
+ if (defined $msg) {
+ warn ("\n$msg\n");
+ exit 1;
+ }
+}