2018-09-18 09:20:24 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
#
|
2021-03-08 06:52:14 +00:00
|
|
|
# © 2018 - 2021 Johannes Schauer Marin Rodrigues <josch@mister-muffin.de>
|
2018-09-18 09:20:24 +00:00
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
# of this software and associated documentation files (the "Software"), to
|
|
|
|
# deal in the Software without restriction, including without limitation the
|
|
|
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
# sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
# all copies or substantial portions of the Software.
|
2019-11-13 10:53:30 +00:00
|
|
|
#
|
|
|
|
# The software is provided "as is", without warranty of any kind, express or
|
|
|
|
# implied, including but not limited to the warranties of merchantability,
|
|
|
|
# fitness for a particular purpose and noninfringement. In no event shall the
|
|
|
|
# authors or copyright holders be liable for any claim, damages or other
|
|
|
|
# liability, whether in an action of contract, tort or otherwise, arising
|
|
|
|
# from, out of or in connection with the software or the use or other dealings
|
|
|
|
# in the software.
|
2018-09-18 09:20:24 +00:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
2021-02-06 13:40:15 +00:00
|
|
|
our $VERSION = '0.7.5';
|
2019-02-23 07:55:31 +00:00
|
|
|
|
2018-09-18 09:20:24 +00:00
|
|
|
use English;
|
|
|
|
use Getopt::Long;
|
|
|
|
use Pod::Usage;
|
|
|
|
use File::Copy;
|
|
|
|
use File::Path qw(make_path remove_tree);
|
|
|
|
use File::Temp qw(tempfile tempdir);
|
2019-12-09 09:40:51 +00:00
|
|
|
use File::Basename;
|
2020-06-23 20:45:17 +00:00
|
|
|
use File::Find;
|
2020-11-13 18:02:41 +00:00
|
|
|
use Cwd qw(abs_path getcwd);
|
2020-01-09 07:39:40 +00:00
|
|
|
require "syscall.ph"; ## no critic (Modules::RequireBarewordIncludes)
|
2018-09-23 17:47:14 +00:00
|
|
|
use Fcntl qw(S_IFCHR S_IFBLK FD_CLOEXEC F_GETFD F_SETFD);
|
2018-09-23 17:36:07 +00:00
|
|
|
use List::Util qw(any none);
|
2020-08-24 14:20:04 +00:00
|
|
|
use POSIX qw(SIGINT SIGHUP SIGPIPE SIGTERM SIG_BLOCK SIG_UNBLOCK strftime);
|
2019-01-20 09:39:01 +00:00
|
|
|
use Carp;
|
|
|
|
use Term::ANSIColor;
|
2019-12-09 09:40:51 +00:00
|
|
|
use Socket;
|
2020-08-25 14:06:05 +00:00
|
|
|
use Time::HiRes;
|
2020-08-15 16:09:06 +00:00
|
|
|
use version;
|
2018-09-18 09:20:24 +00:00
|
|
|
|
2020-01-09 07:39:40 +00:00
|
|
|
## no critic (InputOutput::RequireBriefOpen)
|
|
|
|
|
2018-09-18 09:20:24 +00:00
|
|
|
# from sched.h
|
2020-01-09 07:39:40 +00:00
|
|
|
# use typeglob constants because "use constant" has several drawback as
|
|
|
|
# explained in the documentation for the Readonly CPAN module
|
2021-01-13 17:40:24 +00:00
|
|
|
*CLONE_NEWNS = \0x20000; # mount namespace
|
|
|
|
*CLONE_NEWUTS = \0x4000000; # utsname
|
|
|
|
*CLONE_NEWIPC = \0x8000000; # ipc
|
|
|
|
*CLONE_NEWUSER = \0x10000000; # user
|
|
|
|
*CLONE_NEWPID = \0x20000000; # pid
|
|
|
|
*CLONE_NEWNET = \0x40000000; # net
|
|
|
|
*_LINUX_CAPABILITY_VERSION_3 = \0x20080522;
|
|
|
|
*CAP_SYS_ADMIN = \21;
|
|
|
|
our ($CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC,
|
|
|
|
$CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET,
|
|
|
|
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN);
|
2018-09-18 09:20:24 +00:00
|
|
|
|
2021-03-08 07:04:35 +00:00
|
|
|
#<<<
|
2018-09-18 09:20:24 +00:00
|
|
|
# type codes:
|
|
|
|
# 0 -> normal file
|
|
|
|
# 1 -> hardlink
|
|
|
|
# 2 -> symlink
|
|
|
|
# 3 -> character special
|
|
|
|
# 4 -> block special
|
|
|
|
# 5 -> directory
|
|
|
|
my @devfiles = (
|
2021-03-08 07:04:35 +00:00
|
|
|
# filename mode type link target major minor
|
|
|
|
["", oct(755), 5, '', undef, undef],
|
|
|
|
["console", oct(666), 3, '', 5, 1],
|
|
|
|
["fd", oct(777), 2, '/proc/self/fd', undef, undef],
|
|
|
|
["full", oct(666), 3, '', 1, 7],
|
|
|
|
["null", oct(666), 3, '', 1, 3],
|
|
|
|
["ptmx", oct(666), 3, '', 5, 2],
|
|
|
|
["pts/", oct(755), 5, '', undef, undef],
|
|
|
|
["random", oct(666), 3, '', 1, 8],
|
|
|
|
["shm/", oct(755), 5, '', undef, undef],
|
|
|
|
["stderr", oct(777), 2, '/proc/self/fd/2', undef, undef],
|
|
|
|
["stdin", oct(777), 2, '/proc/self/fd/0', undef, undef],
|
|
|
|
["stdout", oct(777), 2, '/proc/self/fd/1', undef, undef],
|
|
|
|
["tty", oct(666), 3, '', 5, 0],
|
|
|
|
["urandom", oct(666), 3, '', 1, 9],
|
|
|
|
["zero", oct(666), 3, '', 1, 5],
|
2018-09-18 09:20:24 +00:00
|
|
|
);
|
2021-03-08 07:04:35 +00:00
|
|
|
#>>>
|
2018-09-18 09:20:24 +00:00
|
|
|
|
2019-01-20 09:39:01 +00:00
|
|
|
# verbosity levels:
|
|
|
|
# 0 -> print nothing
|
|
|
|
# 1 -> normal output and progress bars
|
|
|
|
# 2 -> verbose output
|
|
|
|
# 3 -> debug output
|
|
|
|
my $verbosity_level = 1;
|
|
|
|
|
2020-01-09 07:39:40 +00:00
|
|
|
my $is_covering = !!(eval { Devel::Cover::get_coverage() });
|
2020-01-08 14:33:49 +00:00
|
|
|
|
2020-08-19 06:16:19 +00:00
|
|
|
# the reason why Perl::Critic warns about this is, that it suspects that the
|
|
|
|
# programmer wants to implement a test whether the terminal is interactive or
|
|
|
|
# not, in which case, complex interactions with the magic *ARGV indeed make it
|
|
|
|
# advisable to use IO::Interactive. In our case, we do not want to create an
|
|
|
|
# interactivity check but just want to check whether STDERR is opened to a tty,
|
|
|
|
# so our use of -t is fine and not "fragile and complicated" as is written in
|
|
|
|
# the description of InputOutput::ProhibitInteractiveTest. Also see
|
|
|
|
# https://github.com/Perl-Critic/Perl-Critic/issues/918
|
|
|
|
sub stderr_is_tty() {
|
|
|
|
## no critic (InputOutput::ProhibitInteractiveTest)
|
|
|
|
if (-t STDERR) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-20 09:39:01 +00:00
|
|
|
sub debug {
|
|
|
|
if ($verbosity_level < 3) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
my $msg = shift;
|
2020-01-03 23:37:49 +00:00
|
|
|
my ($package, $filename, $line) = caller;
|
|
|
|
$msg = "D: $PID $line $msg";
|
2020-08-19 06:16:19 +00:00
|
|
|
if (stderr_is_tty()) {
|
2020-01-08 16:44:07 +00:00
|
|
|
$msg = colored($msg, 'clear');
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
print STDERR "$msg\n";
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub info {
|
|
|
|
if ($verbosity_level == 0) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
my $msg = shift;
|
2020-01-03 23:37:49 +00:00
|
|
|
if ($verbosity_level >= 3) {
|
2020-01-08 14:41:49 +00:00
|
|
|
my ($package, $filename, $line) = caller;
|
2020-01-08 16:44:07 +00:00
|
|
|
$msg = "$PID $line $msg";
|
2020-01-03 23:37:49 +00:00
|
|
|
}
|
2019-01-20 09:39:01 +00:00
|
|
|
$msg = "I: $msg";
|
2020-08-19 06:16:19 +00:00
|
|
|
if (stderr_is_tty()) {
|
2020-01-08 16:44:07 +00:00
|
|
|
$msg = colored($msg, 'green');
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
print STDERR "$msg\n";
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub warning {
|
|
|
|
if ($verbosity_level == 0) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
my $msg = shift;
|
|
|
|
$msg = "W: $msg";
|
2020-08-19 06:16:19 +00:00
|
|
|
if (stderr_is_tty()) {
|
2020-01-08 16:44:07 +00:00
|
|
|
$msg = colored($msg, 'bold yellow');
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
print STDERR "$msg\n";
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub error {
|
|
|
|
# if error() is called with the string from a previous error() that was
|
|
|
|
# caught inside an eval(), then the string will have a newline which we
|
|
|
|
# are stripping here
|
2020-01-08 16:44:07 +00:00
|
|
|
chomp(my $msg = shift);
|
2019-01-20 09:39:01 +00:00
|
|
|
$msg = "E: $msg";
|
2020-08-19 06:16:19 +00:00
|
|
|
if (stderr_is_tty()) {
|
2020-01-08 16:44:07 +00:00
|
|
|
$msg = colored($msg, 'bold red');
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
if ($verbosity_level == 3) {
|
2020-08-19 06:16:19 +00:00
|
|
|
croak $msg; # produces a backtrace
|
2019-01-20 09:39:01 +00:00
|
|
|
} else {
|
2020-01-08 14:41:49 +00:00
|
|
|
die "$msg\n";
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 22:54:48 +00:00
|
|
|
# check whether a directory is mounted by comparing the device number of the
|
|
|
|
# directory itself with its parent
|
2020-01-09 07:39:40 +00:00
|
|
|
sub is_mountpoint {
|
2019-12-02 22:54:48 +00:00
|
|
|
my $dir = shift;
|
2020-01-08 16:44:07 +00:00
|
|
|
if (!-e $dir) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return 0;
|
2019-12-02 22:54:48 +00:00
|
|
|
}
|
|
|
|
my @a = stat "$dir/.";
|
|
|
|
my @b = stat "$dir/..";
|
|
|
|
# if the device number is different, then the directory must be mounted
|
|
|
|
if ($a[0] != $b[0]) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return 1;
|
2019-12-02 22:54:48 +00:00
|
|
|
}
|
|
|
|
# if the inode number is the same, then the directory must be mounted
|
|
|
|
if ($a[1] == $b[1]) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return 1;
|
2019-12-02 22:54:48 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-18 09:20:24 +00:00
|
|
|
# tar cannot figure out the decompression program when receiving data on
|
|
|
|
# standard input, thus we do it ourselves. This is copied from tar's
|
|
|
|
# src/suffix.c
|
2020-01-09 07:39:40 +00:00
|
|
|
sub get_tar_compressor {
|
2018-09-18 09:20:24 +00:00
|
|
|
my $filename = shift;
|
2019-01-20 09:41:29 +00:00
|
|
|
if ($filename eq '-') {
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-20 09:41:29 +00:00
|
|
|
} elsif ($filename =~ /\.tar$/) {
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-20 09:41:29 +00:00
|
|
|
} elsif ($filename =~ /\.(gz|tgz|taz)$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['gzip'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.(Z|taZ)$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['compress'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.(bz2|tbz|tbz2|tz2)$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['bzip2'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.lz$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['lzip'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.(lzma|tlz)$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['lzma'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.lzo$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['lzop'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.lz4$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['lz4'];
|
2018-09-18 09:20:24 +00:00
|
|
|
} elsif ($filename =~ /\.(xz|txz)$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['xz', '--threads=0'];
|
2019-01-20 09:41:29 +00:00
|
|
|
} elsif ($filename =~ /\.zst$/) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return ['zstd'];
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-13 15:05:57 +00:00
|
|
|
sub test_unshare_userns {
|
2018-12-05 07:06:26 +00:00
|
|
|
my $verbose = shift;
|
2018-09-24 18:07:46 +00:00
|
|
|
if ($EFFECTIVE_USER_ID == 0) {
|
2021-01-13 15:05:57 +00:00
|
|
|
my $msg = "cannot unshare user namespace when executing as root";
|
2020-01-08 14:41:49 +00:00
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
return 0;
|
2018-09-24 18:07:46 +00:00
|
|
|
}
|
2018-09-18 09:20:24 +00:00
|
|
|
# arguments to syscalls have to be stored in their own variable or
|
|
|
|
# otherwise we will get "Modification of a read-only value attempted"
|
2020-01-09 07:39:40 +00:00
|
|
|
my $unshare_flags = $CLONE_NEWUSER;
|
2018-09-18 09:20:24 +00:00
|
|
|
# we spawn a new per process because if unshare succeeds, we would
|
2018-10-01 15:14:59 +00:00
|
|
|
# otherwise have unshared the mmdebstrap process itself which we don't want
|
2019-01-20 09:39:01 +00:00
|
|
|
my $pid = fork() // error "fork() failed: $!";
|
2018-09-18 09:20:24 +00:00
|
|
|
if ($pid == 0) {
|
2020-01-09 07:39:40 +00:00
|
|
|
my $ret = syscall(&SYS_unshare, $unshare_flags);
|
2020-01-08 14:41:49 +00:00
|
|
|
if ($ret == 0) {
|
|
|
|
exit 0;
|
|
|
|
} else {
|
|
|
|
my $msg = "unshare syscall failed: $!";
|
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
exit 1;
|
|
|
|
}
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
waitpid($pid, 0);
|
|
|
|
if (($? >> 8) != 0) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return 0;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
2018-09-24 18:09:08 +00:00
|
|
|
# if newuidmap and newgidmap exist, the exit status will be 1 when
|
|
|
|
# executed without parameters
|
|
|
|
system "newuidmap 2>/dev/null";
|
|
|
|
if (($? >> 8) != 1) {
|
2020-01-08 14:41:49 +00:00
|
|
|
if (($? >> 8) == 127) {
|
|
|
|
my $msg = "cannot find newuidmap";
|
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
my $msg = "newuidmap returned unknown exit status: $?";
|
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2018-09-24 18:09:08 +00:00
|
|
|
}
|
|
|
|
system "newgidmap 2>/dev/null";
|
|
|
|
if (($? >> 8) != 1) {
|
2020-01-08 14:41:49 +00:00
|
|
|
if (($? >> 8) == 127) {
|
|
|
|
my $msg = "cannot find newgidmap";
|
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
my $msg = "newgidmap returned unknown exit status: $?";
|
|
|
|
if ($verbose) {
|
|
|
|
warning $msg;
|
|
|
|
} else {
|
|
|
|
debug $msg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2018-09-24 18:09:08 +00:00
|
|
|
}
|
2018-09-18 09:20:24 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub read_subuid_subgid() {
|
|
|
|
my $username = getpwuid $<;
|
|
|
|
my ($subid, $num_subid, $fh, $n);
|
|
|
|
my @result = ();
|
|
|
|
|
2020-01-08 16:44:07 +00:00
|
|
|
if (!-e "/etc/subuid") {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subuid doesn't exist";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
2020-01-08 16:44:07 +00:00
|
|
|
if (!-r "/etc/subuid") {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subuid is not readable";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-08 16:44:07 +00:00
|
|
|
open $fh, "<", "/etc/subuid"
|
|
|
|
or error "cannot open /etc/subuid for reading: $!";
|
2018-09-18 09:20:24 +00:00
|
|
|
while (my $line = <$fh>) {
|
2020-01-08 14:41:49 +00:00
|
|
|
($n, $subid, $num_subid) = split(/:/, $line, 3);
|
|
|
|
last if ($n eq $username);
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
close $fh;
|
|
|
|
push @result, ["u", 0, $subid, $num_subid];
|
|
|
|
|
|
|
|
if (scalar(@result) < 1) {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subuid does not contain an entry for $username";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
if (scalar(@result) > 1) {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subuid contains multiple entries for $username";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-08 16:44:07 +00:00
|
|
|
open $fh, "<", "/etc/subgid"
|
|
|
|
or error "cannot open /etc/subgid for reading: $!";
|
2018-09-18 09:20:24 +00:00
|
|
|
while (my $line = <$fh>) {
|
2020-01-08 14:41:49 +00:00
|
|
|
($n, $subid, $num_subid) = split(/:/, $line, 3);
|
|
|
|
last if ($n eq $username);
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
close $fh;
|
|
|
|
push @result, ["g", 0, $subid, $num_subid];
|
|
|
|
|
|
|
|
if (scalar(@result) < 2) {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subgid does not contain an entry for $username";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
if (scalar(@result) > 2) {
|
2020-01-08 14:41:49 +00:00
|
|
|
warning "/etc/subgid contains multiple entries for $username";
|
|
|
|
return;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return @result;
|
|
|
|
}
|
|
|
|
|
|
|
|
# This function spawns two child processes forming the following process tree
|
|
|
|
#
|
|
|
|
# A
|
|
|
|
# |
|
|
|
|
# fork()
|
|
|
|
# | \
|
|
|
|
# B C
|
|
|
|
# | |
|
|
|
|
# | fork()
|
|
|
|
# | | \
|
|
|
|
# | D E
|
|
|
|
# | | |
|
|
|
|
# |unshare()
|
|
|
|
# | close()
|
|
|
|
# | | |
|
|
|
|
# | | read()
|
|
|
|
# | | newuidmap(D)
|
|
|
|
# | | newgidmap(D)
|
|
|
|
# | | /
|
|
|
|
# | waitpid()
|
|
|
|
# | |
|
|
|
|
# | fork()
|
|
|
|
# | | \
|
|
|
|
# | F G
|
|
|
|
# | | |
|
|
|
|
# | | exec()
|
|
|
|
# | | /
|
|
|
|
# | waitpid()
|
|
|
|
# | /
|
|
|
|
# waitpid()
|
|
|
|
#
|
|
|
|
# To better refer to each individual part, we give each process a new
|
|
|
|
# identifier after calling fork(). Process A is the main process. After
|
|
|
|
# executing fork() we call the parent and child B and C, respectively. This
|
|
|
|
# first fork() is done because we do not want to modify A. B then remains
|
|
|
|
# waiting for its child C to finish. C calls fork() again, splitting into
|
|
|
|
# the parent D and its child E. In the parent D we call unshare() and close a
|
|
|
|
# pipe shared by D and E to signal to E that D is done with calling unshare().
|
|
|
|
# E notices this by using read() and follows up with executing the tools
|
|
|
|
# new[ug]idmap on D. E finishes and D continues with doing another fork().
|
|
|
|
# This is because when unsharing the PID namespace, we need a PID 1 to be kept
|
|
|
|
# alive or otherwise any child processes cannot fork() anymore themselves. So
|
|
|
|
# we keep F as PID 1 and finally call exec() in G.
|
2020-01-09 07:39:40 +00:00
|
|
|
sub get_unshare_cmd {
|
2020-01-08 16:44:07 +00:00
|
|
|
my $cmd = shift;
|
2018-09-18 09:20:24 +00:00
|
|
|
my $idmap = shift;
|
|
|
|
|
2021-01-13 15:05:57 +00:00
|
|
|
# unsharing the mount namespace (NEWNS) requires CAP_SYS_ADMIN
|
2020-01-08 16:44:07 +00:00
|
|
|
my $unshare_flags
|
2021-01-13 15:05:57 +00:00
|
|
|
= $CLONE_NEWNS | $CLONE_NEWPID | $CLONE_NEWUTS | $CLONE_NEWIPC;
|
|
|
|
|
|
|
|
# we only need to add CLONE_NEWUSER if we are not yet root
|
|
|
|
if ($EFFECTIVE_USER_ID != 0) {
|
|
|
|
$unshare_flags |= $CLONE_NEWUSER;
|
|
|
|
}
|
2018-09-18 09:20:24 +00:00
|
|
|
|
|
|
|
if (0) {
|
2020-01-09 07:39:40 +00:00
|
|
|
$unshare_flags |= $CLONE_NEWNET;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# fork a new process and let the child get unshare()ed
|
|
|
|
# we don't want to unshare the parent process
|
2019-01-20 09:39:01 +00:00
|
|
|
my $gcpid = fork() // error "fork() failed: $!";
|
2018-09-18 09:20:24 +00:00
|
|
|
if ($gcpid == 0) {
|
2020-01-08 15:23:34 +00:00
|
|
|
# Create a pipe for the parent process to signal the child process that
|
|
|
|
# it is done with calling unshare() so that the child can go ahead
|
|
|
|
# setting up uid_map and gid_map.
|
2020-01-08 14:41:49 +00:00
|
|
|
pipe my $rfh, my $wfh;
|
2020-01-08 15:23:34 +00:00
|
|
|
# We have to do this dance with forking a process and then modifying
|
|
|
|
# the parent from the child because:
|
|
|
|
# - new[ug]idmap can only be called on a process id after that process
|
|
|
|
# has unshared the user namespace
|
|
|
|
# - a process looses its capabilities if it performs an execve() with
|
|
|
|
# nonzero user ids see the capabilities(7) man page for details.
|
|
|
|
# - a process that unshared the user namespace by default does not
|
|
|
|
# have the privileges to call new[ug]idmap on itself
|
2020-01-08 14:41:49 +00:00
|
|
|
#
|
2020-01-08 15:23:34 +00:00
|
|
|
# this also works the other way around (the child setting up a user
|
|
|
|
# namespace and being modified from the parent) but that way, the
|
|
|
|
# parent would have to stay around until the child exited (so a pid
|
|
|
|
# would be wasted). Additionally, that variant would require an
|
|
|
|
# additional pipe to let the parent signal the child that it is done
|
|
|
|
# with calling new[ug]idmap. The way it is done here, this signaling
|
|
|
|
# can instead be done by wait()-ing for the exit of the child.
|
|
|
|
|
2020-01-08 14:41:49 +00:00
|
|
|
my $ppid = $$;
|
|
|
|
my $cpid = fork() // error "fork() failed: $!";
|
|
|
|
if ($cpid == 0) {
|
|
|
|
# child
|
|
|
|
|
|
|
|
# Close the writing descriptor at our end of the pipe so that we
|
|
|
|
# see EOF when parent closes its descriptor.
|
|
|
|
close $wfh;
|
|
|
|
|
|
|
|
# Wait for the parent process to finish its unshare() call by
|
|
|
|
# waiting for an EOF.
|
|
|
|
0 == sysread $rfh, my $c, 1 or error "read() did not receive EOF";
|
|
|
|
|
2021-01-13 15:05:57 +00:00
|
|
|
# the process is already root, so no need for newuidmap/newgidmap
|
|
|
|
if ($EFFECTIVE_USER_ID == 0) {
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
2020-01-08 14:41:49 +00:00
|
|
|
# The program's new[ug]idmap have to be used because they are
|
|
|
|
# setuid root. These privileges are needed to map the ids from
|
|
|
|
# /etc/sub[ug]id to the user namespace set up by the parent.
|
|
|
|
# Without these privileges, only the id of the user itself can be
|
|
|
|
# mapped into the new namespace.
|
|
|
|
#
|
|
|
|
# Since new[ug]idmap is setuid root we also don't need to write
|
|
|
|
# "deny" to /proc/$$/setgroups beforehand (this is otherwise
|
|
|
|
# required for unprivileged processes trying to write to
|
|
|
|
# /proc/$$/gid_map since kernel version 3.19 for security reasons)
|
|
|
|
# and therefore the parent process keeps its ability to change its
|
|
|
|
# own group here.
|
|
|
|
#
|
|
|
|
# Since /proc/$ppid/[ug]id_map can only be written to once,
|
|
|
|
# respectively, instead of making multiple calls to new[ug]idmap,
|
|
|
|
# we assemble a command line that makes one call each.
|
|
|
|
my $uidmapcmd = "";
|
|
|
|
my $gidmapcmd = "";
|
|
|
|
foreach (@{$idmap}) {
|
|
|
|
my ($t, $hostid, $nsid, $range) = @{$_};
|
|
|
|
if ($t ne "u" and $t ne "g" and $t ne "b") {
|
|
|
|
error "invalid idmap type: $t";
|
|
|
|
}
|
|
|
|
if ($t eq "u" or $t eq "b") {
|
|
|
|
$uidmapcmd .= " $hostid $nsid $range";
|
|
|
|
}
|
|
|
|
if ($t eq "g" or $t eq "b") {
|
|
|
|
$gidmapcmd .= " $hostid $nsid $range";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
my $idmapcmd = '';
|
|
|
|
if ($uidmapcmd ne "") {
|
2020-01-08 16:44:07 +00:00
|
|
|
0 == system "newuidmap $ppid $uidmapcmd"
|
|
|
|
or error "newuidmap $ppid $uidmapcmd failed: $!";
|
2020-01-08 14:41:49 +00:00
|
|
|
}
|
|
|
|
if ($gidmapcmd ne "") {
|
2020-01-08 16:44:07 +00:00
|
|
|
0 == system "newgidmap $ppid $gidmapcmd"
|
|
|
|
or error "newgidmap $ppid $gidmapcmd failed: $!";
|
2020-01-08 14:41:49 +00:00
|
|
|
}
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# parent
|
|
|
|
|
|
|
|
# After fork()-ing, the parent immediately calls unshare...
|
2020-01-08 16:44:07 +00:00
|
|
|
0 == syscall &SYS_unshare, $unshare_flags
|
|
|
|
or error "unshare() failed: $!";
|
2020-01-08 14:41:49 +00:00
|
|
|
|
|
|
|
# .. and then signals the child process that we are done with the
|
|
|
|
# unshare() call by sending an EOF.
|
|
|
|
close $wfh;
|
|
|
|
|
|
|
|
# Wait for the child process to finish its setup by waiting for its
|
|
|
|
# exit.
|
|
|
|
$cpid == waitpid $cpid, 0 or error "waitpid() failed: $!";
|
|
|
|
my $exit = $? >> 8;
|
|
|
|
if ($exit != 0) {
|
|
|
|
error "child had a non-zero exit status: $exit";
|
|
|
|
}
|
|
|
|
|
|
|
|
# Currently we are nobody (uid and gid are 65534). So we become root
|
|
|
|
# user and group instead.
|
|
|
|
#
|
|
|
|
# We are using direct syscalls instead of setting $(, $), $< and $>
|
|
|
|
# because then perl would do additional stuff which we don't need or
|
|
|
|
# want here, like checking /proc/sys/kernel/ngroups_max (which might
|
|
|
|
# not exist). It would also also call setgroups() in a way that makes
|
|
|
|
# the root user be part of the group unknown.
|
2021-01-13 15:05:57 +00:00
|
|
|
if ($EFFECTIVE_USER_ID != 0) {
|
|
|
|
0 == syscall &SYS_setgid, 0 or error "setgid failed: $!";
|
|
|
|
0 == syscall &SYS_setuid, 0 or error "setuid failed: $!";
|
|
|
|
0 == syscall &SYS_setgroups, 0, 0 or error "setgroups failed: $!";
|
|
|
|
}
|
2020-01-08 14:41:49 +00:00
|
|
|
|
|
|
|
if (1) {
|
|
|
|
# When the pid namespace is also unshared, then processes expect a
|
|
|
|
# master pid to always be alive within the namespace. To achieve
|
|
|
|
# this, we fork() here instead of exec() to always have one dummy
|
|
|
|
# process running as pid 1 inside the namespace. This is also what
|
|
|
|
# the unshare tool does when used with the --fork option.
|
|
|
|
#
|
|
|
|
# Otherwise, without a pid 1, new processes cannot be forked
|
|
|
|
# anymore after pid 1 finished.
|
|
|
|
my $cpid = fork() // error "fork() failed: $!";
|
|
|
|
if ($cpid != 0) {
|
|
|
|
# The parent process will stay alive as pid 1 in this
|
|
|
|
# namespace until the child finishes executing. This is
|
|
|
|
# important because pid 1 must never die or otherwise nothing
|
|
|
|
# new can be forked.
|
|
|
|
$cpid == waitpid $cpid, 0 or error "waitpid() failed: $!";
|
2020-01-08 16:44:07 +00:00
|
|
|
exit($? >> 8);
|
2020-01-08 14:41:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&{$cmd}();
|
|
|
|
|
|
|
|
exit 0;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# parent
|
|
|
|
return $gcpid;
|
|
|
|
}
|
|
|
|
|
2020-01-09 07:39:40 +00:00
|
|
|
sub havemknod {
|
2020-01-08 16:44:07 +00:00
|
|
|
my $root = shift;
|
2018-09-18 09:20:24 +00:00
|
|
|
my $havemknod = 0;
|
|
|
|
if (-e "$root/test-dev-null") {
|
2020-01-08 14:41:49 +00:00
|
|
|
error "/test-dev-null already exists";
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
2020-01-08 16:44:07 +00:00
|
|
|
TEST: {
|
2020-01-08 14:41:49 +00:00
|
|
|
# we fork so that we can read STDERR
|
|
|
|
my $pid = open my $fh, '-|' // error "failed to fork(): $!";
|
|
|
|
if ($pid == 0) {
|
|
|
|
open(STDERR, '>&', STDOUT) or error "cannot open STDERR: $!";
|
|
|
|
# we use mknod(1) instead of the system call because creating the
|
|
|
|
# right dev_t argument requires makedev(3)
|
|
|
|
exec 'mknod', "$root/test-dev-null", 'c', '1', '3';
|
|
|
|
}
|
2020-01-08 16:44:07 +00:00
|
|
|
chomp(
|
|
|
|
my $content = do { local $/; <$fh> }
|
|
|
|
);
|
2020-01-08 14:41:49 +00:00
|
|
|
close $fh;
|
|
|
|
{
|
|
|
|
last TEST unless $? == 0 and $content eq '';
|
|
|
|
last TEST unless -c "$root/test-dev-null";
|
|
|
|
last TEST unless open my $fh, '>', "$root/test-dev-null";
|
|
|
|
last TEST unless print $fh 'test';
|
|
|
|
}
|
|
|
|
$havemknod = 1;
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
if (-e "$root/test-dev-null") {
|
2020-01-08 16:44:07 +00:00
|
|
|
unlink "$root/test-dev-null"
|
|
|
|
or error "cannot unlink /test-dev-null: $!";
|
2018-09-18 09:20:24 +00:00
|
|
|
}
|
|
|
|
return $havemknod;
|
|
|
|
}
|
|
|
|
|
2018-09-23 17:47:14 +00:00
|
|
|
sub print_progress {
|
2019-01-20 09:39:01 +00:00
|
|
|
if ($verbosity_level != 1) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return;
|
2019-01-20 09:39:01 +00:00
|
|
|
}
|
2018-09-23 17:47:14 +00:00
|
|
|
my $perc = shift;
|
2020-08-19 06:16:19 +00:00
|
|
|
if (!stderr_is_tty()) {
|
2020-01-08 14:41:49 +00:00
|
|
|
return;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
if ($perc eq "done") {
|
2020-01-08 14:41:49 +00:00
|
|
|
# \e[2K clears everything on the current line (i.e. the progress bar)
|
|
|
|
print STDERR "\e[2Kdone\n";
|
|
|
|
return;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
if ($perc >= 100) {
|
2020-01-08 14:41:49 +00:00
|
|
|
$perc = 100;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
my $width = 50;
|
2020-01-08 16:44:07 +00:00
|
|
|
my $num_x = int($perc * $width / 100);
|
|
|
|
my $bar = '=' x $num_x;
|
2018-09-23 17:47:14 +00:00
|
|
|
if ($num_x != $width) {
|
2020-01-08 14:41:49 +00:00
|
|
|
$bar .= '>';
|
|
|
|
$bar .= ' ' x ($width - $num_x - 1);
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
printf STDERR "%6.2f [%s]\r", $perc, $bar;
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
sub run_progress {
|
2019-04-25 06:49:28 +00:00
|
|
|
my ($get_exec, $line_handler, $line_has_error, $chdir) = @_;
|
2018-09-23 17:47:14 +00:00
|
|
|
pipe my $rfh, my $wfh;
|
2019-01-13 09:17:46 +00:00
|
|
|
my $got_signal = 0;
|
2020-01-08 16:44:07 +00:00
|
|
|
my $ignore = sub {
|
2020-01-08 14:41:49 +00:00
|
|
|
info "run_progress() received signal $_[0]: waiting for child...";
|
2019-01-13 09:17:46 +00:00
|
|
|
};
|
|
|
|
|
2020-04-10 10:55:02 +00:00
|
|
|
debug("run_progress: exec " . (join ' ', ($get_exec->('${FD}'))));
|
|
|
|
|
2019-01-13 09:17:46 +00:00
|
|
|
# delay signals so that we can fork and change behaviour of the signal
|
|
|
|
# handler in parent and child without getting interrupted
|
|
|
|
my $sigset = POSIX::SigSet->new(SIGINT, SIGHUP, SIGPIPE, SIGTERM);
|
2019-01-20 09:39:01 +00:00
|
|
|
POSIX::sigprocmask(SIG_BLOCK, $sigset) or error "Can't block signals: $!";
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2019-01-20 09:39:01 +00:00
|
|
|
my $pid1 = open(my $pipe, '-|') // error "failed to fork(): $!";
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
if ($pid1 == 0) {
|
2020-01-08 14:41:49 +00:00
|
|
|
# child: default signal handlers
|
2020-01-09 07:39:40 +00:00
|
|
|
local $SIG{'INT'} = 'DEFAULT';
|
|
|
|
local $SIG{'HUP'} = 'DEFAULT';
|
|
|
|
local $SIG{'PIPE'} = 'DEFAULT';
|
|
|
|
local $SIG{'TERM'} = 'DEFAULT';
|
2020-01-08 14:41:49 +00:00
|
|
|
|
|
|
|
# unblock all delayed signals (and possibly handle them)
|
2020-01-08 16:44:07 +00:00
|
|
|
POSIX::sigprocmask(SIG_UNBLOCK, $sigset)
|
|
|
|
or error "Can't unblock signals: $!";
|
2020-01-08 14:41:49 +00:00
|
|
|
|
|
|
|
close $rfh;
|
|
|
|
# Unset the close-on-exec flag, so that the file descriptor does not
|
|
|
|
# get closed when we exec
|
2020-01-08 16:44:07 +00:00
|
|
|
my $flags = fcntl($wfh, F_GETFD, 0) or error "fcntl F_GETFD: $!";
|
|
|
|
fcntl($wfh, F_SETFD, $flags & ~FD_CLOEXEC)
|
|
|
|
or error "fcntl F_SETFD: $!";
|
2020-01-08 14:41:49 +00:00
|
|
|
my $fd = fileno $wfh;
|
|
|
|
# redirect stderr to stdout so that we can capture it
|
|
|
|
open(STDERR, '>&', STDOUT) or error "cannot open STDOUT: $!";
|
|
|
|
my @execargs = $get_exec->($fd);
|
|
|
|
# before apt 1.5, "apt-get update" attempted to chdir() into the
|
|
|
|
# working directory. This will fail if the current working directory
|
|
|
|
# is not accessible by the user (for example in unshare mode). See
|
|
|
|
# Debian bug #860738
|
|
|
|
if (defined $chdir) {
|
|
|
|
chdir $chdir or error "failed chdir() to $chdir: $!";
|
|
|
|
}
|
2020-01-09 07:39:40 +00:00
|
|
|
eval { Devel::Cover::set_coverage("none") } if $is_covering;
|
2020-01-08 16:44:07 +00:00
|
|
|
exec { $execargs[0] } @execargs
|
|
|
|
or error 'cannot exec() ' . (join ' ', @execargs);
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
close $wfh;
|
|
|
|
|
|
|
|
# spawn two processes:
|
2019-01-13 21:04:25 +00:00
|
|
|
# parent will parse stdout to look for errors
|
2018-09-23 17:47:14 +00:00
|
|
|
# child will parse $rfh for the progress meter
|
2019-01-20 09:39:01 +00:00
|
|
|
my $pid2 = fork() // error "failed to fork(): $!";
|
2019-01-13 21:04:25 +00:00
|
|
|
if ($pid2 == 0) {
|
2020-01-08 14:41:49 +00:00
|
|
|
# child: default signal handlers
|
2020-01-09 07:39:40 +00:00
|
|
|
local $SIG{'INT'} = 'IGNORE';
|
|
|
|
local $SIG{'HUP'} = 'IGNORE';
|
|
|
|
local $SIG{'PIPE'} = 'IGNORE';
|
|
|
|
local $SIG{'TERM'} = 'IGNORE';
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2020-01-08 14:41:49 +00:00
|
|
|
# unblock all delayed signals (and possibly handle them)
|
2020-01-08 16:44:07 +00:00
|
|
|
POSIX::sigprocmask(SIG_UNBLOCK, $sigset)
|
|
|
|
or error "Can't unblock signals: $!";
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2020-08-18 12:31:38 +00:00
|
|
|
my $progress = 0.0;
|
|
|
|
my $status = undef;
|
|
|
|
print_progress($progress);
|
2020-01-08 14:41:49 +00:00
|
|
|
while (my $line = <$rfh>) {
|
2020-08-18 12:31:38 +00:00
|
|
|
my ($newprogress, $newstatus) = $line_handler->($line);
|
|
|
|
next unless $newprogress;
|
|
|
|
# start a new line if the new progress value is less than the
|
|
|
|
# previous one
|
|
|
|
if ($newprogress < $progress) {
|
|
|
|
print_progress("done");
|
|
|
|
}
|
|
|
|
if (defined $newstatus) {
|
|
|
|
$status = $newstatus;
|
|
|
|
}
|
2020-08-19 06:16:19 +00:00
|
|
|
if ( defined $status
|
|
|
|
and $verbosity_level == 1
|
|
|
|
and stderr_is_tty()) {
|
2020-08-18 12:31:38 +00:00
|
|
|
# \e[2K clears everything on the current line (i.e. the
|
|
|
|
# progress bar)
|
|
|
|
print STDERR "\e[2K$status: ";
|
|
|
|
}
|
|
|
|
print_progress($newprogress);
|
|
|
|
$progress = $newprogress;
|
2020-01-08 14:41:49 +00:00
|
|
|
}
|
2020-01-09 07:39:40 +00:00
|
|
|
print_progress("done");
|
2018-09-23 17:47:14 +00:00
|
|
|
|
2020-01-08 14:41:49 +00:00
|
|
|
exit 0;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 09:17:46 +00:00
|
|
|
# parent: ignore signals
|
|
|
|
# by using "local", the original is automatically restored once the
|
|
|
|
# function returns
|
2020-01-08 16:44:07 +00:00
|
|
|
local $SIG{'INT'} = $ignore;
|
|
|
|
local $SIG{'HUP'} = $ignore;
|
2019-01-13 09:17:46 +00:00
|
|
|
local $SIG{'PIPE'} = $ignore;
|
|
|
|
local $SIG{'TERM'} = $ignore;
|
|
|
|
|
|
|
|
# unblock all delayed signals (and possibly handle them)
|
2020-01-08 16:44:07 +00:00
|
|
|
POSIX::sigprocmask(SIG_UNBLOCK, $sigset)
|
|
|
|
or error "Can't unblock signals: $!";
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2020-01-08 16:44:07 +00:00
|
|
|
my $output = '';
|
2019-01-13 21:04:25 +00:00
|
|
|
my $has_error = 0;
|
|
|
|
while (my $line = <$pipe>) {
|
2020-01-08 14:41:49 +00:00
|
|
|
$has_error = $line_has_error->($line);
|
|
|
|
if ($verbosity_level >= 2) {
|
|
|
|
print STDERR $line;
|
|
|
|
} else {
|
|
|
|
# forward captured apt output
|
|
|
|
$output .= $line;
|
|
|
|
}
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
2019-01-13 21:04:25 +00:00
|
|
|
|
|
|
|
close($pipe);
|
2018-09-23 17:47:14 +00:00
|
|
|
my $fail = 0;
|
2019-01-13 21:04:25 +00:00
|
|
|
if ($? != 0 or $has_error) {
|
2020-01-08 14:41:49 +00:00
|
|
|
$fail = 1;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
waitpid $pid2, 0;
|
2019-01-20 09:39:01 +00:00
|
|
|
$? == 0 or error "progress parsing failed";
|
2018-09-23 17:47:14 +00:00
|
|
|
|
2019-01-13 09:17:46 +00:00
|
|
|
if ($got_signal) {
|
2020-01-08 14:41:49 +00:00
|
|
|
error "run_progress() received signal: $got_signal";
|
2019-01-13 09:17:46 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
# only print failure after progress output finished or otherwise it
|
|
|
|
# might interfere with the remaining output
|
2018-09-23 17:47:14 +00:00
|
|
|
if ($fail) {
|
2020-01-08 14:41:49 +00:00
|
|
|
if ($verbosity_level >= 1) {
|
|
|
|
print STDERR $output;
|
|
|
|
}
|
2020-01-08 16:44:07 +00:00
|
|
|
error((join ' ', $get_exec->('<$fd>')) . ' failed');
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2018-09-23 17:47:14 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
sub run_dpkg_progress {
|
2018-12-27 20:08:53 +00:00
|
|
|
my $options = shift;
|
2020-01-08 16:44:07 +00:00
|
|
|
my @debs = @{ $options->{PKGS} // [] };
|
|
|
|
my $get_exec
|
|
|
|
= sub { return @{ $options->{ARGV} }, "--status-fd=$_[0]", @debs; };
|
2019-01-13 21:04:25 +00:00
|
|
|
my $line_has_error = sub { return 0; };
|
2020-01-08 16:44:07 +00:00
|
|
|
my $num = 0;
|
2019-01-13 21:04:25 +00:00
|
|
|
# each package has one install and one configure step, thus the total
|
|
|
|
# number is twice the number of packages
|
2020-01-08 16:44:07 +00:00
|
|
|
my $total = (scalar @debs) * 2;
|
2019-01-13 21:04:25 +00:00
|
|
|
my $line_handler = sub {
|
2020-08-18 12:31:38 +00:00
|
|
|
my $status = undef;
|
2020-01-08 14:41:49 +00:00
|
|
|
if ($_[0] =~ /^processing: (install|configure): /) {
|
2020-08-18 12:31:38 +00:00
|
|
|
if ($1 eq 'install') {
|
|
|
|
$status = 'installing';
|
|
|
|
} elsif ($1 eq 'configure') {
|
|
|
|
$status = 'configuring';
|
|
|
|
} else {
|
|
|
|
error "unknown status: $1";
|
|
|
|
}
|
2020-01-08 14:41:49 +00:00
|
|
|
$num += 1;
|
|
|
|
}
|
2020-08-18 12:31:38 +00:00
|
|
|
return $num / $total * 100, $status;
|
2019-01-13 21:04:25 +00:00
|
|
|
};
|
2019-01-20 09:39:01 +00:00
|
|
|
run_progress $get_exec, $line_handler, $line_has_error;
|
2020-01-09 07:39:40 +00:00
|
|
|
return;
|
2019-01-13 21:04:25 +00:00
|
|
|
}
|
2019-01-13 09:17:46 +00:00
|
|
|
|
2019-01-13 21:04:25 +00:00
|
|
|
sub run_apt_progress {
|
2020-11-13 18:02:41 +00:00
|
|
|
my $options = shift;
|
|
|
|
my @debs = @{ $options->{PKGS} // [] };
|
|
|
|
my $tmpedsp;
|
|
|
|
if (exists $options->{EDSP_RES}) {
|
|
|
|
(undef, $tmpedsp) = tempfile(
|
|
|
|
"mmdebstrap.edsp.XXXXXXXXXXXX",
|
|
|
|
OPEN => 0,
|
|
|
|
TMPDIR => 1
|
|
|
|
);
|
|
|
|
}
|
2019-01-13 21:04:25 +00:00
|
|
|
my $get_exec = sub {
|
2020-11-29 01:30:03 +00:00
|
|