@ -23,13 +23,13 @@
use strict;
use warnings;
our $VERSION = '0.8.0 ';
our $VERSION = '0.8.1 ';
use English;
use Getopt::Long;
use Pod::Usage;
use File::Copy;
use File::Path qw(make_path remove_tree );
use File::Path qw(make_path);
use File::Temp qw(tempfile tempdir);
use File::Basename;
use File::Find;
@ -244,9 +244,9 @@ sub get_tar_compressor {
} elsif ($filename =~ /\.lz4$/) {
return ['lz4'];
} elsif ($filename =~ /\.(xz|txz)$/) {
return ['xz', '--threads=0' ];
return ['xz'];
} elsif ($filename =~ /\.zst$/) {
return ['zstd', '--threads=0' ];
return ['zstd'];
}
return;
}
@ -1550,41 +1550,6 @@ sub setup {
sub run_setup() {
my $options = shift;
my $dpkgversion = version->new(0);
{
my $pid = open my $fh, '-|' // error "failed to fork(): $!";
if ($pid == 0) {
# redirect stderr to /dev/null to hide error messages from dpkg
# versions before 1.20.0
open(STDERR, '>', '/dev/null')
or error "cannot open /dev/null for writing: $!";
exec 'dpkg', '--robot', '--version';
}
chomp(
my $content = do { local $/; <$fh> }
);
close $fh;
# the --robot option was introduced in 1.20.0 but until 1.20.2 the
# output contained a string after the version, separated by a
# whitespace -- since then, it's only the version
if ($? == 0 and $content =~ /^([0-9.]+)( .*)?$/) {
# dpkg is new enough for the --robot option
$dpkgversion = version->new($1);
}
}
my $aptversion = version->new(0);
{
my $pid = open my $fh, '-|', 'apt-get',
'--version' // error "failed to fork(): $!";
chomp(my $firstline = <$fh>);
close $fh;
if ( $? == 0
and $firstline =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/)
{
$aptversion = version->new($1);
}
}
{
my @directories = (
'/etc/apt/apt.conf.d', '/etc/apt/sources.list.d',
@ -1595,8 +1560,7 @@ sub run_setup() {
push @directories, '/var/lib/dpkg';
# since we do not know the dpkg version inside the chroot at this
# point, we can only omit it in chrootless mode
if ( $options->{mode} ne 'chrootless'
or $dpkgversion < "1.20.0"
if ($options->{mode} ne 'chrootless'
or scalar @{ $options->{dpkgopts} } > 0) {
push @directories, '/etc/dpkg/dpkg.cfg.d/';
}
@ -1609,8 +1573,7 @@ sub run_setup() {
push @directories, '/var/log/apt';
# since we do not know the dpkg version inside the chroot at this
# point, we can only omit it in chrootless mode
if ( $options->{mode} ne 'chrootless'
or $dpkgversion < "1.20.0") {
if ($options->{mode} ne 'chrootless') {
push @directories, '/var/lib/dpkg/triggers',
'/var/lib/dpkg/info', '/var/lib/dpkg/alternatives',
'/var/lib/dpkg/updates';
@ -1688,24 +1651,6 @@ sub run_setup() {
# options.
print $conf "pkgCacheGen::ForceEssential \",\";\n";
}
if ($options->{dryrun}
and ($aptversion < "2.1.16")) {
# Without this option, apt will fail with:
# E: Could not configure 'libc6:amd64'.
# E: Could not perform immediate configuration on 'libgcc1:amd64'.
#
# See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=953260
#
# For some while there were other immediate configuration problems
# which were fixed by changing the dependencies in src:glibc:
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=972552
#
# Immediate configuration only was fixed in apt later which then also
# fixed these bugs:
# https://bugs.debian.org/973325
# https://bugs.debian.org/973305
print $conf "APT::Immediate-Configure false;\n";
}
close $conf;
# We put certain configuration items in their own configuration file
@ -1728,29 +1673,6 @@ sub run_setup() {
close $fh;
}
# /var/lib/dpkg/available is required to exist or otherwise package
# removals will fail
# since we do not know the dpkg version inside the chroot at this point, we
# can only omit it in chrootless mode
if ( $options->{mode} ne 'chrootless'
or $dpkgversion < "1.20.0") {
open my $fh, '>', "$options->{root}/var/lib/dpkg/available"
or error "failed to open(): $!";
close $fh;
}
# /var/lib/dpkg/cmethopt is used by dselect
# see #930788
# since we do not know the dpkg version inside the chroot at this point, we
# can only omit it in chrootless mode
if ( $options->{mode} ne 'chrootless'
or $dpkgversion < "1.20.0") {
open my $fh, '>', "$options->{root}/var/lib/dpkg/cmethopt"
or error "failed to open(): $!";
print $fh "apt apt\n";
close $fh;
}
# we create /var/lib/dpkg/arch inside the chroot either if there is more
# than the native architecture in the chroot or if chrootless mode is
# used to create a chroot of a different architecture than the native
@ -2039,27 +1961,10 @@ sub run_setup() {
sub run_update() {
my $options = shift;
my $aptversion = version->new(0);
{
my $pid = open my $fh, '-|', 'apt-get',
'--version' // error "failed to fork(): $!";
chomp(my $firstline = <$fh>);
close $fh;
if ( $? == 0
and $firstline =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/)
{
$aptversion = version->new($1);
}
}
my $aptopts = {
ARGV => ['apt-get', 'update'],
ARGV => ['apt-get', 'update', '--error-on=any'],
CHDIR => $options->{root},
};
if ($aptversion < "2.1.16") {
$aptopts->{FIND_APT_WARNINGS} = 1;
} else {
push @{ $aptopts->{ARGV} }, '--error-on=any';
}
info "running apt-get update...";
run_apt_progress($aptopts);
@ -3102,15 +3007,14 @@ sub run_cleanup() {
# apt since 1.6 creates the auxfiles directory. If apt inside the
# chroot is older than that, then it will not know how to clean it.
if (-e "$options->{root}/var/lib/apt/lists/auxfiles") {
remove_tree("$options->{root}/var/lib/apt/lists/auxfiles",
{ error => \my $err });
if (@$err) {
for my $diag (@$err) {
my ($file, $message) = %$diag;
if ($file eq '') { warning "general error: $message"; }
else { warning "problem unlinking $file: $message"; }
}
}
0 == system(
'rm',
'--interactive=never',
'--recursive',
'--preserve-root',
'--one-file-system',
"$options->{root}/var/lib/apt/lists/auxfiles"
) or error "rm failed: $?";
}
}
@ -3180,15 +3084,11 @@ sub run_cleanup() {
next if $entry eq ".";
next if $entry eq "..";
warning "deleting files in /tmp: $entry";
remove_tree("$options->{root}/tmp/$entry",
{ error => \my $err });
if (@$err) {
for my $diag (@$err) {
my ($file, $message) = %$diag;
if ($file eq '') { warning "general error: $message"; }
else { warning "problem unlinking $file: $message"; }
}
}
0 == system(
'rm', '--interactive=never',
'--recursive', '--preserve-root',
'--one-file-system', "$options->{root}/tmp/$entry"
) or error "rm failed: $?";
}
closedir($dh);
}
@ -4564,8 +4464,11 @@ sub main() {
$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
}
foreach my $tool ('dpkg', 'dpkg-deb', 'apt-get', 'apt-cache', 'apt-config',
'tar') {
foreach my $tool (
'dpkg', 'dpkg-deb', 'apt-get', 'apt-cache',
'apt-config', 'tar', 'rm', 'find',
'env'
) {
my $found = 0;
foreach my $path (split /:/, $ENV{PATH}) {
if (-f "$path/$tool" && -x _ ) {
@ -4578,6 +4481,49 @@ sub main() {
}
}
{
my $dpkgversion = version->new(0);
my $pid = open my $fh, '-|' // error "failed to fork(): $!";
if ($pid == 0) {
# redirect stderr to /dev/null to hide error messages from dpkg
# versions before 1.20.0
open(STDERR, '>', '/dev/null')
or error "cannot open /dev/null for writing: $!";
exec 'dpkg', '--robot', '--version';
}
chomp(
my $content = do { local $/; <$fh> }
);
close $fh;
# the --robot option was introduced in 1.20.0 but until 1.20.2 the
# output contained a string after the version, separated by a
# whitespace -- since then, it's only the version
if ($? == 0 and $content =~ /^([0-9.]+)( .*)?$/) {
# dpkg is new enough for the --robot option
$dpkgversion = version->new($1);
}
if ($dpkgversion < "1.20.0") {
error "need dpkg >= 1.20.0 but have $dpkgversion";
}
}
{
my $aptversion = version->new(0);
my $pid = open my $fh, '-|', 'apt-get',
'--version' // error "failed to fork(): $!";
chomp(
my $content = do { local $/; <$fh> }
);
close $fh;
if ( $? == 0
and $content =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/m) {
$aptversion = version->new($1);
}
if ($aptversion < "2.3.7") {
error "need apt >= 2.3.7 but have $aptversion";
}
}
my $check_fakechroot_running = sub {
# test if we are inside fakechroot already
# We fork a child process because setting FAKECHROOT_DETECT seems to
@ -5950,31 +5896,18 @@ sub main() {
# no risk of removing anything important.
$pid = get_unshare_cmd(
sub {
# File::Path will produce the error "cannot stat initial
# working directory" if the working directory cannot be
# accessed by the unprivileged unshared user. Thus, we
# first navigate to the parent of the root directory.
chdir "$options->{root}/.."
or error "unable to chdir() to parent directory of"
. " $options->{root}: $!";
remove_tree($options->{root}, { error => \my $err });
if (@$err) {
for my $diag (@$err) {
my ($file, $message) = %$diag;
if ($file eq '') {
warning "general error: $message";
} else {
# the unshared process might not have
# sufficient permissions to remove the root
# directory. We attempt its removal later, so
# don't warn if its removal fails now.
if ($file ne $options->{root}) {
warning
"problem unlinking $file: $message";
}
}
}
}
# change CWD to chroot directory because find tries to
# chdir to the current directory which might not be
# accessible by the unshared user:
# find: Failed to restore initial working directory
0 == system('env', "--chdir=$options->{root}", 'find',
$options->{root}, '-mount',
'-mindepth', '1', '-delete')
or error "rm failed: $?";
# ignore failure in case the unshared user doesn't have the
# required permissions -- we attempt again later if
# necessary
rmdir "$options->{root}";
},
\@idmap
);
@ -5982,8 +5915,7 @@ sub main() {
$? == 0 or error "remove_tree failed";
# in unshare mode, the toplevel directory might've been created in
# a directory that the unshared user cannot change and thus cannot
# delete. If the root directory still exists after remove_tree
# above, we attempt its removal again outside as the normal user.
# delete. We attempt its removal again outside as the normal user.
if (-e $options->{root}) {
rmdir "$options->{root}"
or error "cannot rmdir $options->{root}: $!";
@ -6001,7 +5933,7 @@ sub main() {
# be removed.
0 == system('rm', '--interactive=never', '--recursive',
'--preserve-root', '--one-file-system', $options->{root})
or error "rm failed: $! ";
or error "rm failed: $? ";
} else {
error "unknown mode: $options->{mode}";
}
@ -7133,9 +7065,9 @@ extension to compressor applies:
.tlz lzma
.lzo lzop
.lz4 lz4
.xz xz --threads=0
.txz xz --threads=0
.zst zstd --threads=0
.xz xz
.txz xz
.zst zstd
To change compression specific options, either use the respecitve environment
variables like B<XZ_OPT> or send B<mmdebstrap> output to your compressor of