diff options
Diffstat (limited to 'bin/sbuild-createchroot')
-rwxr-xr-x | bin/sbuild-createchroot | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/bin/sbuild-createchroot b/bin/sbuild-createchroot new file mode 100755 index 0000000..07e5912 --- /dev/null +++ b/bin/sbuild-createchroot @@ -0,0 +1,813 @@ +#!/usr/bin/perl +# +# Run debootstrap and add a few other files needed to create a working +# sbuild chroot. +# Copyright © 2004 Francesco P. Lovergine <frankie@debian.org>. +# Copyright © 2007-2010 Roger Leigh <rleigh@debian.org>. +# +# 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, see +# <http://www.gnu.org/licenses/>. +# +####################################################################### + +use strict; +use warnings; + +umask 0022; + +use English; + +use Sbuild::AptResolver; + +package Conf; + +sub setup { + my $conf = shift; + + my $keyring = ''; + $keyring = '/etc/apt/trusted.gpg' + if -f '/etc/apt/trusted.gpg'; + + my %createchroot_keys = ( + 'CHROOT_PREFIX' => { + DEFAULT => undef + }, + 'CHROOT_SUFFIX' => { + DEFAULT => '-sbuild' + }, + 'FOREIGN' => { + DEFAULT => 0 + }, + 'INCLUDE' => { + DEFAULT => '' + }, + 'EXCLUDE' => { + DEFAULT => '' + }, + 'COMPONENTS' => { + DEFAULT => 'main' + }, + 'RESOLVE_DEPS' => { + DEFAULT => 1 + }, + 'KEEP_DEBOOTSTRAP_DIR' => { + DEFAULT => 0 + }, + 'DEBOOTSTRAP' => { + DEFAULT => 'debootstrap' + }, + 'KEYRING' => { + DEFAULT => undef + }, + 'SETUP_ONLY' => { + DEFAULT => 0 + }, + 'MAKE_SBUILD_TARBALL' => { + DEFAULT => '' + }, + 'KEEP_SBUILD_CHROOT_DIR' => { + DEFAULT => 0 + }, + 'DEB_SRC' => { + DEFAULT => 1 + }, + 'ALIASES' => { + DEFAULT => [] + }, + 'EXTRA_REPOSITORIES' => { + DEFAULT => [] + }, + 'COMMAND_PREFIX' => { + DEFAULT => '' + }, + 'CHROOT_MODE' => { + DEFAULT => 'schroot' + }, + 'MERGED_USR' => { + DEFAULT => 'auto' + }, + ); + + $conf->set_allowed_keys(\%createchroot_keys); +} + +package Options; + +use Sbuild::OptionsBase; +use Sbuild::Conf qw(); + +BEGIN { + use Exporter (); + our (@ISA, @EXPORT); + + @ISA = qw(Exporter Sbuild::OptionsBase); + + @EXPORT = qw(); +} + +sub set_options { + my $self = shift; + + $self->add_options( + "chroot-mode=s" => sub { + $self->set_conf('CHROOT_MODE', $_[1]); + }, + "chroot-prefix=s" => sub { + $self->set_conf('CHROOT_PREFIX', $_[1]); + }, + "chroot-suffix=s" => sub { + $self->set_conf('CHROOT_SUFFIX', $_[1]); + }, + "arch=s" => sub { + $self->set_conf('BUILD_ARCH', $_[1]); + }, + "foreign" => sub { + $self->set_conf('FOREIGN', 1); + }, + "resolve-deps" => sub { + $self->set_conf('RESOLVE_DEPS', 1) + }, + "no-resolve-deps" => sub { + $self->set_conf('RESOLVE_DEPS', 0) + }, + "keep-debootstrap-dir" => sub { + $self->set_conf('KEEP_DEBOOTSTRAP_DIR', 1) + }, + "debootstrap=s" => sub { + $self->set_conf('DEBOOTSTRAP', $_[1]) + }, + "exclude=s" => sub { + $self->set_conf('EXCLUDE', $_[1]); + }, + "include=s" => sub { + $self->set_conf('INCLUDE', $_[1]); + }, + "components=s" => sub { + $self->set_conf('COMPONENTS', $_[1]); + }, + "keyring=s" => sub { + $self->set_conf('KEYRING', $_[1]); + }, + "setup-only" => sub { + $self->set_conf('SETUP_ONLY', 1); + }, + "make-sbuild-tarball=s" => sub { + $self->set_conf('MAKE_SBUILD_TARBALL', $_[1]); + }, + "keep-sbuild-chroot-dir" => sub { + $self->set_conf('KEEP_SBUILD_CHROOT_DIR', 1); + }, + "no-deb-src" => sub { + $self->set_conf('DEB_SRC', 0); + }, + "alias=s" => sub { + push @{$self->get_conf('ALIASES')}, $_[1]; + }, + "extra-repository=s" => sub { + push @{$self->get_conf('EXTRA_REPOSITORIES')}, $_[1]; + }, + "command-prefix=s" => sub { + $self->set_conf('COMMAND_PREFIX', $_[1]); + }, + "merged-usr" => sub { + $self->set_conf('MERGED_USR', 1) + }, + "auto-merged-usr" => sub { + $self->set_conf('MERGED_USR', 'auto') + }, + "no-merged-usr" => sub { + $self->set_conf('MERGED_USR', 0) + }); +} + +package main; + +use POSIX; +use Getopt::Long qw(:config no_ignore_case auto_abbrev gnu_getopt); +use Sbuild qw(dump_file help_text version_text usage_error check_packages); +use Sbuild::ChrootPlain; +use Sbuild::ChrootUnshare; +use Sbuild::ChrootRoot; +use Sbuild::Sysconfig; +use Sbuild::Conf qw(); +use Sbuild::Utility; +use File::Basename qw(dirname); +use File::Path qw(mkpath rmtree); +use File::Temp qw(tempfile); +use File::Copy; +use Cwd qw(abs_path); +use IPC::Open3; +use File::Spec; + +sub add_items ($@); +sub makedir ($$); + +my %personalities = ( + 'armel:arm64' => 'linux32', + 'armhf:arm64' => 'linux32', + 'i386:amd64' => 'linux32', + 'mipsel:mips64el' => 'linux32', + 'powerpc:ppc64' => 'linux32', +); + +my $conf = Sbuild::Conf::new(); +Conf::setup($conf); +exit 1 if !defined($conf); +my $options = Options->new($conf, "sbuild-createchroot", "8"); +exit 1 if !defined($options); + + +usage_error("sbuild-createchroot", + "Incorrect number of options") if (@ARGV <2 || @ARGV >4); + +if ($conf->get('CHROOT_MODE') eq 'unshare' and !$conf->get('MAKE_SBUILD_TARBALL')) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare requires --make-sbuild-tarball to be set"); +} + +if ($conf->get('CHROOT_MODE') eq 'unshare' and $conf->get('SETUP_ONLY')) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare is incompatible with --setup-only") +} + +if ($conf->get('CHROOT_MODE') eq 'unshare' and scalar @{$conf->get('ALIASES')} > 0) { + usage_error("sbuild-createchroot", + "--chroot-mode=unshare is incompatible with --alias") +} + +if ($conf->get('CHROOT_MODE') ne 'schroot' and $conf->get('COMMAND_PREFIX')) { + usage_error("sbuild-createchroot", + "--command-prefix requires --chroot-mode=schroot") +} + +if ($conf->get('MAKE_SBUILD_TARBALL') and -e $conf->get('MAKE_SBUILD_TARBALL')) { + print STDERR "E: tarball already exists: ". $conf->get('MAKE_SBUILD_TARBALL') . "\n"; + exit 1; +} + +# Make sure fakeroot and build-essential are installed +$conf->set('INCLUDE', add_items($conf->get('INCLUDE'), + "fakeroot", + "build-essential")); + +# Deal with SUITE-VARIANT +my $suite = $ARGV[0]; + +# check if schroot name is already in use + +my $chrootname; +if (defined $conf->get('CHROOT_PREFIX') && $conf->get('CHROOT_PREFIX') ne "") { + $chrootname = $conf->get('CHROOT_PREFIX') +} else { + $chrootname = $suite +} +$chrootname .= "-" . $conf->get('BUILD_ARCH') . $conf->get('CHROOT_SUFFIX'); + +if ($conf->get('CHROOT_MODE') eq 'schroot') { + # We redirect stderr to /dev/null because otherwise schroot might print + # warnings on stderr which throws off autopkgtest + open(NULL, ">", File::Spec->devnull); + my $pid = open3(my $in = '', \*PH, \*NULL, 'schroot', '-l', '--all-source-chroots'); + while (my $line = <PH>) { + $line ne "source:$chrootname\n" or die "chroot with name $chrootname already exists"; + } + waitpid($pid, 0); + if (($? >> 8) != 0) { + die "schroot exited with non-zero exit status"; + } +} + +my $target = $ARGV[1]; +if (-e $target) { + if (!-d $target) { + die "$target exists and is not a directory"; + } + chmod 0755, $target or die "cannot chmod $target"; + # only check if the directory is empty if the --setup-only option is not + # given because that option needs an already populated directory + if (!$conf->get('SETUP_ONLY')) { + # check if the directory is empty or contains nothing more than an + # empty lost+found directory. The latter exists on freshly created + # ext3 and ext4 partitions. + # rationale for requiring an empty directory: https://bugs.debian.org/833525 + opendir(my $dh, $target) or die "Can't opendir($target): $!"; + while (my $entry = readdir $dh) { + # skip the "." and ".." entries + next if $entry eq "."; + next if $entry eq ".."; + # if the entry is a directory named "lost+found" then skip it + # if it's empty + if ($entry eq "lost+found" and -d "$target/$entry") { + opendir(my $dh2, "$target/$entry"); + # Attempt reading the directory thrice. If the third time + # succeeds, then it has more entries than just "." and ".." + # and must thus not be empty. + readdir $dh2; + readdir $dh2; + # rationale for requiring an empty directory: + # https://bugs.debian.org/833525 + if (readdir $dh2) { + die "$target contains a non-empty lost+found directory"; + } + closedir($dh2); + } else { + die "$target is not empty"; + } + } + closedir($dh); + } +} else { + # Create the target directory in advance so abs_path (which is buggy) + # won't fail. Remove if abs_path is replaced by something better. + makedir($target, 0755); +} +$target = abs_path($target); +my $script = undef; +my $mirror = "http://deb.debian.org/debian"; + +$mirror = $ARGV[2] if $#ARGV >= 2; +$script = $ARGV[3] if $#ARGV == 3; + +if ($conf->get('VERBOSE')) { + print "I: SUITE: $suite\n"; + print "I: TARGET: $target\n"; + print "I: MIRROR: $mirror\n"; + print "I: SCRIPT: $script\n" if (defined($script)); +} + +my @args = ("--arch=" . $conf->get('BUILD_ARCH'), + "--variant=buildd"); +push @args, "--verbose" if $conf->get('VERBOSE'); +push @args, "--foreign" if $conf->get('FOREIGN'); +push @args, "--keep-debootstrap-dir" if $conf->get('KEEP_DEBOOTSTRAP_DIR'); +push @args, "--include=" . $conf->get('INCLUDE') if $conf->get('INCLUDE'); +push @args, "--exclude=" . $conf->get('EXCLUDE') if $conf->get('EXCLUDE'); +push @args, "--components=" . $conf->get('COMPONENTS') + if $conf->get('COMPONENTS'); +push @args, "--keyring=" . $conf->get('KEYRING') if $conf->get('KEYRING'); +push @args, "--no-check-gpg" if defined $conf->get('KEYRING') && $conf->get('KEYRING') eq ""; +push @args, $conf->get('RESOLVE_DEPS') ? + "--resolve-deps" : "--no-resolve-deps"; +if ($conf->get('MERGED_USR') ne 'auto') { + push @args, $conf->get('MERGED_USR') ? + "--merged-usr" : "--no-merged-usr"; +} +push @args, "$suite", "$target", "$mirror"; +push @args, "$script" if $script; + +# Set the path to debootstrap +my $debootstrap = $conf->get('DEBOOTSTRAP'); + +# Get the name of the debootstrap binary +my $debootstrap_bin = $debootstrap; +$debootstrap_bin =~ s/^.*\///s; + +if ($conf->get('VERBOSE')) { + print "I: Running $debootstrap_bin " . join(' ',@args) . "\n"; +} + +my @idmap; +if ($conf->get('CHROOT_MODE') eq 'unshare') { + @idmap = read_subuid_subgid; + # sanity check + if (scalar(@idmap) != 2 || $idmap[0][0] ne 'u' || $idmap[1][0] ne 'g') { + printf STDERR "invalid idmap\n"; + return 0; + } +} + +# Run debootstrap with specified options. +if (!$conf->get('SETUP_ONLY')) { + if ($conf->get('CHROOT_MODE') eq 'unshare') { + if(!test_unshare) { + print STDERR "E: unable to to unshare\n"; + exit 1; + } + + makedir($target, 0755); + + my $outer_gid = $REAL_GROUP_ID+0; + system(get_unshare_cmd({ + IDMAP => [['u', '0', $REAL_USER_ID, '1'], + ['g', '0', $outer_gid, '1'], + ['u', '1', $idmap[0][2], '1'], + ['g', '1', $idmap[1][2], '1'], + ] + }), 'chown', '1:1', $target); + my @cmd = ('env', 'PATH=/usr/sbin:/usr/bin:/sbin:/bin', + get_unshare_cmd({UNSHARE_FLAGS => CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC, FORK => 1, IDMAP => \@idmap}), 'sh', '-c', " + rootdir=\"\$1\"; shift; + hostname sbuild; + mkdir -p \"\$rootdir/dev\"; + for f in null zero full random urandom tty; do + touch \"\$rootdir/dev/\$f\"; + chmod -rwx \"\$rootdir/dev/\$f\"; + mount -o bind \"/dev/\$f\" \"\$rootdir/dev/\$f\"; + done; + mkdir -p \"\$rootdir/sys\"; + mount -o rbind /sys \"\$rootdir/sys\"; + mkdir -p \"\$rootdir/proc\"; + mount -t proc proc \"\$rootdir/proc\"; + mkdir -p \"\$rootdir/fakebin\"; + ln -sf /bin/true \"\$rootdir/fakebin/mknod\"; + ln -sf /bin/true \"\$rootdir/fakebin/mount\"; + export PATH=\"\$rootdir/fakebin:/fakebin:\$PATH\" + \"\$@\"; + exit_status=\$?; + rm \"\$rootdir/fakebin/mknod\" \"\$rootdir/fakebin/mount\"; + rm -d \"\$rootdir/fakebin\"; + exit \$exit_status; + ", '--', $target, $debootstrap, @args + ); + !system(@cmd) or die "E: Error running @cmd"; + } else { + if ($REAL_USER_ID == 0) { + chown(0, 0, $target) or die "cannot chown $target"; + } + !system($debootstrap, @args) or die "E: Error running $debootstrap_bin"; + } +} + +if (!($conf->get('SETUP_ONLY') && $conf->get('MAKE_SBUILD_TARBALL'))) { + my $sources_list = ""; + + # Add deb-src to /etc/apt/sources.list. + if ($conf->get('DEB_SRC')) { + my $comps = join(' ',split(/,/,$conf->get('COMPONENTS'))); + $sources_list .= "deb-src $mirror $suite $comps\n"; + } + + # Add extra repositories to /etc/apt/sources.list + for my $repo (@{$conf->get('EXTRA_REPOSITORIES')}) { + $sources_list .= "$repo\n"; + } + + my $passwd_sbuild = `getent passwd sbuild`; + my $group_sbuild = `getent group sbuild`; + + my $setup_script = <<"EOF"; +open (my \$passwd_fd, ">>", "\$target/etc/passwd") or die "cannot open /etc/passwd"; +print \$passwd_fd \$passwd_sbuild; +close(\$passwd_fd); +open (my \$group_fd, ">>", "\$target/etc/group") or die "cannot open /etc/group"; +print \$group_fd \$group_sbuild; +close(\$group_fd); + + +# Set up minimal /etc/hosts if it didn't exist yet. Normally, the package +# netbase would create the file. +my \$hosts = "\${target}/etc/hosts"; +if (! -e \$hosts) { + open(HOSTS, ">\$hosts") + or die "Can't open \$hosts for writing"; + # write the default content that would be created by the netbase package + print HOSTS <<"EOF2"; +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + +EOF2 + close HOSTS or die "Can't close \$hosts"; + + # Display /etc/hosts. + print "I: Configured /etc/hosts:\n"; + dump_file("\$hosts"); +} + +# Set up minimal /usr/sbin/policy-rc.d. +my \$policy_rc_d = "\${target}/usr/sbin/policy-rc.d"; +open(POLICY_RC_D, ">\$policy_rc_d") + or die "Can't open \$policy_rc_d for writing"; +print POLICY_RC_D <<"EOF2"; +#!/bin/sh +echo "All runlevel operations denied by policy" >&2 +exit 101 +EOF2 + +close POLICY_RC_D or die "Can't close \$policy_rc_d"; + +my (undef, undef, \$uid, undef) = getpwnam('root'); +chown(\$uid, -1, \$policy_rc_d) == 1 + or die "E: Failed to set root: ownership on \$policy_rc_d"; +chmod(0775, \$policy_rc_d) == 1 + or die "E: Failed to set 0755 permissions on \$policy_rc_d"; + +# Display /usr/sbin/policy-rc.d. +print "I: Configured /usr/sbin/policy-rc.d:\n"; +dump_file("\$policy_rc_d"); +EOF + + if ($conf->get('DEB_SRC') || scalar @{$conf->get('EXTRA_REPOSITORIES')} > 0) { + $setup_script .= <<"EOF"; +my \$sources = "\${target}/etc/apt/sources.list"; +open(SOURCES, ">>\$sources") + or die "E: Can't open \$sources for writing"; + +print SOURCES \$sources_list; +close SOURCES or die "E: Can't close \$sources"; +EOF + } + + $setup_script .= <<"EOF"; +# Display /etc/apt/sources.list. +print "I: Configured APT /etc/apt/sources.list:\n"; +dump_file("\${target}/etc/apt/sources.list"); +print "I: Please add any additional APT sources to \${target}/etc/apt/sources.list\n"; +EOF + if ($conf->get('CHROOT_MODE') eq 'unshare') { + my $group_sbuild = `getent group sbuild`; + $setup_script = <<"EOF"; +use strict; +use warnings; +use Sbuild qw(dump_file); +my \$target = \$ARGV[0]; +my \$passwd_sbuild = \$ARGV[1]; +my \$group_sbuild = \$ARGV[2]; +my \$sources_list = \$ARGV[3]; +$setup_script +EOF + !system(get_unshare_cmd( + {IDMAP => \@idmap}), 'perl', '-e', $setup_script, $target, $passwd_sbuild, $group_sbuild, $sources_list + ) or die "E: failed running setup script"; + } else { + eval $setup_script; + if ($@) { + die "E: failed running setup script: $@\n"; + } + } +} + +if ($conf->get('CHROOT_MODE') eq 'schroot') { + # Write out schroot chroot configuration. + + my $arch = $conf->get('BUILD_ARCH'); + my $config_entry = <<"EOF"; +[$chrootname] +description=Debian $suite/$arch autobuilder +groups=root,sbuild +root-groups=root,sbuild +profile=sbuild +EOF + + # Determine the schroot chroot configuration to use. + if ($conf->get('MAKE_SBUILD_TARBALL')) { + my $tarball = $conf->get('MAKE_SBUILD_TARBALL'); + + # Default to using tar gzip compression if unable to determine compression + # mode via file extension. + if ($tarball !~ /\.(tgz|tbz|tlz|txz|tar(\.(gz|bz2|lz|xz))?)$/) { + print "I: Renaming sbuild tarball '$tarball' to '$tarball.tar.gz'\n"; + $tarball .= ".tar.gz"; + $conf->set('MAKE_SBUILD_TARBALL', $tarball); + } + + $config_entry .= <<"EOF"; +type=file +file=$tarball +EOF + } else { + # Determine whether system has overlayfs capability + my $uniontype = "none"; + if (lc("$^O") =~ /linux/ && -e '/sbin/modprobe') { + my $ret = system(qw(/sbin/modprobe overlay)); + if ($ret == 0 && open(FILE, "/proc/filesystems")) { + if (grep {/\soverlay$/} <FILE>) { + $uniontype = "overlay"; + } + close(FILE); + } + } + + $config_entry .= <<"EOF"; +type=directory +directory=$target +union-type=$uniontype +EOF + } + + if (scalar @{$conf->get('ALIASES')} > 0) { + my $aliases = join ',', @{$conf->get('ALIASES')}; + $config_entry .= "aliases=$aliases\n"; + } + + if ($conf->get('COMMAND_PREFIX') ne '') { + $config_entry .= "command-prefix=" . $conf->get('COMMAND_PREFIX') . "\n"; + } + + if (-d "/etc/schroot/chroot.d") { + # TODO: Don't hardcode path + my $SCHROOT_CONF = + new File::Temp( TEMPLATE => "$chrootname-XXXXXX", + DIR => "/etc/schroot/chroot.d", + UNLINK => 0) + or die "Can't open schroot configuration file: $!\n"; + + print $SCHROOT_CONF "$config_entry"; + + my ($personality, $personality_message); + # Detect whether personality might be needed. + if ($conf->get('ARCH') ne $conf->get('BUILD_ARCH')) { + # Take care of the known case(s). + my $key = $conf->get('BUILD_ARCH') . ':' . $conf->get('ARCH'); + if (exists $personalities{$key}) { + $personality = $personalities{$key}; + $personality_message = + "I: Added personality=$personality automatically " . + "(" . $conf->get('BUILD_ARCH') . " on " . $conf->get('ARCH') . ").\n"; + } else { + $personality_message = + "W: The selected architecture and the current architecture do not match\n" . + "W: (" . $conf->get('BUILD_ARCH') . " versus " . $conf->get('ARCH') . ").\n" . + "I: You probably need to add a personality option (see schroot(1)).\n" . + "I: You may want to report your use case to the sbuild developers so that\n" . + "I: the appropriate option gets automatically added in the future.\n\n"; + } + } + + # Add personality if detected. + print $SCHROOT_CONF "personality=$personality\n" if $personality; + + # Needed to display file below. + $SCHROOT_CONF->flush(); + + # Display schroot configuration. + print "I: schroot chroot configuration written to $SCHROOT_CONF.\n"; + chmod 0644, "$SCHROOT_CONF"; + dump_file("$SCHROOT_CONF"); + print "I: Please rename and modify this file as required.\n"; + print $personality_message if $personality_message; + } +} + +if ($conf->get('CHROOT_MODE') eq 'schroot' || $conf->get('CHROOT_MODE') eq 'sudo') { + if (! -d "$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot") { + makedir("$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot", 0775); + } + + # Populate /etc/sbuild/chroot with a symlink to be able to use the chroot in + # sudo mode for directory based chroots + my $chrootlink = "$Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chrootname"; + if ((defined $chrootlink) && (! $conf->get('MAKE_SBUILD_TARBALL'))) { + if (! -e $chrootlink) { + if (symlink($target, $chrootlink)) { + print "I: sudo chroot configuration linked as $Sbuild::Sysconfig::paths{'SBUILD_SYSCONF_DIR'}/chroot/$chrootname.\n"; + } else { + print STDERR "E: Failed to symlink $target to $chrootlink: $!\n"; + } + } else { + print "W: Not creating symlink $target to $chrootlink: file already exists\n"; + + } + } +} + +if (!$conf->get('SETUP_ONLY') || !$conf->get('MAKE_SBUILD_TARBALL')) { + # FIXME: also update packages with the unshare backend + if ($conf->get('ARCH') eq $conf->get('HOST_ARCH') && $conf->get('CHROOT_MODE') ne 'unshare') { + my $session = Sbuild::ChrootPlain->new($conf, $target); + my $host = Sbuild::ChrootRoot->new($conf); + if (defined($session)) { + $session->set('Log Stream', \*STDOUT); + + if (!$session->begin_session() || !$host->begin_session()) { + print STDERR "E: Error creating chroot session: skipping apt update\n"; + } else { + my $resolver = Sbuild::AptResolver->new($conf, $session, $host); + $resolver->setup(); + + print "I: Setting reference package list.\n"; + check_packages($session, "set"); + + print "I: Updating chroot.\n"; + my $status = $resolver->update(); + print "W: Failed to update APT package lists\n" + if ($status); + + $status = $resolver->distupgrade(); + print "W: Failed to upgrade chroot\n" + if ($status); + + $status = $resolver->clean(); + print "W: Failed to clean up downloaded packages\n" + if ($status); + + $resolver->cleanup(); + $session->end_session(); + $session = undef; + } + } + } elsif ($conf->get('ARCH') ne $conf->get('HOST_ARCH')) { + print "W: The selected architecture and the current architecture do not match\n"; + print "W: (" . $conf->get('BUILD_ARCH') . " versus " . $conf->get('ARCH') . ").\n"; + print "W: Not automatically updating APT package lists.\n"; + print "I: Run \"apt-get update\" and \"apt-get dist-upgrade\" prior to use.\n"; + print "I: Run \"sbuild-checkpackages --set\" to set reference package list.\n"; + } +} + +# This block makes the tarball chroot if one has been requested and delete +# the sbuild chroot directory created, unless it's been requested to keep the +# directory. +if ($conf->get('MAKE_SBUILD_TARBALL') && !$conf->get('SETUP_ONLY')) { + my ($tmpfh, $tmpfile) = tempfile("XXXXXX"); + my @program_list = ("/bin/tar", "-c", "-C", $target); + push @program_list, get_tar_compress_options($conf->get('MAKE_SBUILD_TARBALL')); + if ($conf->get('CHROOT_MODE') ne 'unshare') { + push @program_list, '-f', $tmpfile; + } + push @program_list, './'; + + print "I: Creating tarball...\n"; + if ($conf->get('CHROOT_MODE') eq 'unshare') { + open(my $in, '-|', get_unshare_cmd( + {IDMAP => \@idmap}), @program_list + ) // die "could not exec tar"; + if (copy($in, $tmpfile) != 1 ) { + die "unable to copy: $!\n"; + } + close($in) or die "Could not create chroot tarball: $?\n"; + } else { + system(@program_list) == 0 or die "Could not create chroot tarball: $?\n"; + } + + makedir(dirname($conf->get('MAKE_SBUILD_TARBALL')), 0755); + move("$tmpfile", $conf->get('MAKE_SBUILD_TARBALL')) or die "cannot mv to $conf->get('MAKE_SBUILD_TARBALL'): $!"; + chmod 0644, $conf->get('MAKE_SBUILD_TARBALL'); + + print "I: Done creating " . $conf->get('MAKE_SBUILD_TARBALL') . "\n"; + + if (! $conf->get('KEEP_SBUILD_CHROOT_DIR')) { + if ($conf->get('CHROOT_MODE') eq 'unshare') { + # this looks like a recipe for disaster, but since we execute "rm -rf" with + # lxc-usernsexec, we only have permission to delete the files that were + # created with the fake root user + system(get_unshare_cmd({IDMAP => \@idmap}), 'rm', '-rf', $target); + die "Unable to remove $target" if -e $target; + } else { + rmtree("$target"); + } + print "I: chroot $target has been removed.\n"; + } else { + print "I: chroot $target has been kept.\n"; + } +} + +print "I: Successfully set up $suite chroot.\n"; +if ($conf->get('CHROOT_MODE') eq 'schroot') { + print "I: Run \"sbuild-adduser\" to add new sbuild users.\n"; +} + +exit 0; + +# Add items to the start of a comma-separated list, and remove the +# items from later in the list if they were already in the list. +sub add_items ($@) { + my $items = shift; + my @add = @_; + + my $ret = ''; + my %values; + + foreach (@_) { + $values{$_} = ''; + $ret .= "$_," + } + + # Only add if not already used, to eliminate duplicates. + foreach (split (/,/,$items)) { + $ret .= "$_," if (!defined($values{$_})); + } + + # Remove trailing comma. + $ret =~ s/,$//; + + return $ret; +} + +sub makedir ($$) { + my $dir = shift; + my $perms = shift; + + mkpath($dir, + { mode => $perms, + verbose => 1, + error => \my $error + }); + + for my $diag (@$error) { + my ($file, $message) = each %$diag; + print "E: Can't make directory $file: $message\n"; + } +} |