#!/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 => ''
	},
	'EXTRA_SUITES'				=> {
	    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]);
	},
	"extra-suites=s" => sub {
	    $self->set_conf('EXTRA_SUITES', $_[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, "--extra-suites=" . $conf->get('EXTRA_SUITES')
    if $conf->get('EXTRA_SUITES');
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);

        0 == system(
            'unshare',
            # comment to guide perltidy line wrapping
            '--map-user',   '0',
            '--map-group',  '0',
            '--map-users',  "$idmap[0][2],1,1",
            '--map-groups', "$idmap[1][2],1,1",
            'chown',        '1:1', $target
        ) or die "E: Unable to chown $target";

        my @cmd = (
            'env', 'PATH=/usr/sbin:/usr/bin:/sbin:/bin',
            '/usr/libexec/sbuild-usernsexec',
            (map { join ":", @{$_} } @idmap),
            $target,
            'dummyuser',
            'dummydir',
            '--',
            'sh', '-c', '
                rootdir="$1"; shift;
                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
        0 == system(
            "/usr/libexec/sbuild-usernsexec",
            (map { join ":", @{$_} } @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, '-|',
            "/usr/libexec/sbuild-usernsexec",
            (map { join ":", @{$_} } @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(
                "/usr/libexec/sbuild-usernsexec",
                (map { join ":", @{$_} } @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";
    }
}
