handle INT, HUB, PIPE and TERM signals, wait for child processes and clean up mounts

This commit is contained in:
Johannes 'josch' Schauer 2019-01-13 10:17:46 +01:00
parent 1c7e0c86f0
commit 7f58c4596a
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1

View file

@ -25,6 +25,7 @@ use Cwd qw(abs_path);
require "syscall.ph"; require "syscall.ph";
use Fcntl qw(S_IFCHR S_IFBLK FD_CLOEXEC F_GETFD F_SETFD); use Fcntl qw(S_IFCHR S_IFBLK FD_CLOEXEC F_GETFD F_SETFD);
use List::Util qw(any none); use List::Util qw(any none);
use POSIX qw(SIGINT SIGHUP SIGPIPE SIGTERM SIG_BLOCK SIG_UNBLOCK);
# from sched.h # from sched.h
use constant { use constant {
@ -442,8 +443,29 @@ sub run_dpkg_progress {
my @debs = @{$options->{PKGS}}; my @debs = @{$options->{PKGS}};
my $verbose = $options->{VERBOSE} // 0; my $verbose = $options->{VERBOSE} // 0;
pipe my $rfh, my $wfh; pipe my $rfh, my $wfh;
my $got_signal = 0;
my $ignore = sub {
$got_signal = shift;
print STDERR "I: run_dpkg_progress() received signal $got_signal: waiting for dpkg...\n";
};
# 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);
POSIX::sigprocmask(SIG_BLOCK, $sigset) or die "Can't block signals: $!\n";
my $dpkgpid = open (my $pipe_dpkg, '-|') // die "failed to fork(): $!"; my $dpkgpid = open (my $pipe_dpkg, '-|') // die "failed to fork(): $!";
if ($dpkgpid == 0) { if ($dpkgpid == 0) {
# child: default signal handlers
$SIG{'INT'} = 'DEFAULT';
$SIG{'HUP'} = 'DEFAULT';
$SIG{'PIPE'} = 'DEFAULT';
$SIG{'TERM'} = 'DEFAULT';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
close $rfh; close $rfh;
# Unset the close-on-exec flag, so that the file descriptor does not # Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec dpkg # get closed when we exec dpkg
@ -463,7 +485,15 @@ sub run_dpkg_progress {
# child will parse $rfh for the progress meter # child will parse $rfh for the progress meter
my $pid = fork() // die "failed to fork(): $!"; my $pid = fork() // die "failed to fork(): $!";
if ($pid == 0) { if ($pid == 0) {
# child # child: default signal handlers
$SIG{'INT'} = 'IGNORE';
$SIG{'HUP'} = 'IGNORE';
$SIG{'PIPE'} = 'IGNORE';
$SIG{'TERM'} = 'IGNORE';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
print_progress 0.0 if not $verbose; print_progress 0.0 if not $verbose;
my $num = 0; my $num = 0;
# each package has one install and one configure step, thus the total # each package has one install and one configure step, thus the total
@ -481,7 +511,17 @@ sub run_dpkg_progress {
exit 0; exit 0;
} }
# parent # parent: ignore signals
# by using "local", the original is automatically restored once the
# function returns
local $SIG{'INT'} = $ignore;
local $SIG{'HUP'} = $ignore;
local $SIG{'PIPE'} = $ignore;
local $SIG{'TERM'} = $ignore;
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
my $dpkg_output = ''; my $dpkg_output = '';
while (my $line = <$pipe_dpkg>) { while (my $line = <$pipe_dpkg>) {
if ($verbose) { if ($verbose) {
@ -500,6 +540,10 @@ sub run_dpkg_progress {
waitpid $pid, 0; waitpid $pid, 0;
$? == 0 or die "progress parsing failed"; $? == 0 or die "progress parsing failed";
if ($got_signal) {
die "run_dpkg_progress() received signal: $got_signal";
}
# only print apt failure after progress output finished or otherwise it # only print apt failure after progress output finished or otherwise it
# might interfere # might interfere
if ($fail) { if ($fail) {
@ -514,8 +558,29 @@ sub run_apt_progress {
my @debs = @{$options->{PKGS} // []}; my @debs = @{$options->{PKGS} // []};
my $verbose = $options->{VERBOSE} // 0; my $verbose = $options->{VERBOSE} // 0;
pipe my $rfh, my $wfh; pipe my $rfh, my $wfh;
my $got_signal = 0;
my $ignore = sub {
$got_signal = shift;
print STDERR "I: run_apt_progress() received signal $got_signal: waiting for apt...\n";
};
# 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);
POSIX::sigprocmask(SIG_BLOCK, $sigset) or die "Can't block signals: $!\n";
my $aptpid = open(my $pipe_apt, '-|') // die "failed to fork(): $!"; my $aptpid = open(my $pipe_apt, '-|') // die "failed to fork(): $!";
if ($aptpid == 0) { if ($aptpid == 0) {
# child: default signal handlers
$SIG{'INT'} = 'DEFAULT';
$SIG{'HUP'} = 'DEFAULT';
$SIG{'PIPE'} = 'DEFAULT';
$SIG{'TERM'} = 'DEFAULT';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
close $rfh; close $rfh;
# Unset the close-on-exec flag, so that the file descriptor does not # Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec apt-get # get closed when we exec apt-get
@ -535,7 +600,15 @@ sub run_apt_progress {
# child will parse $rfh for the progress meter # child will parse $rfh for the progress meter
my $pid = fork() // die "failed to fork(): $!"; my $pid = fork() // die "failed to fork(): $!";
if ($pid == 0) { if ($pid == 0) {
# child # child: default signal handlers
$SIG{'INT'} = 'IGNORE';
$SIG{'HUP'} = 'IGNORE';
$SIG{'PIPE'} = 'IGNORE';
$SIG{'TERM'} = 'IGNORE';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
print_progress 0.0 if not $verbose; print_progress 0.0 if not $verbose;
while (my $line = <$rfh>) { while (my $line = <$rfh>) {
if ($line =~ /(pmstatus|dlstatus):[^:]+:(\d+\.\d+):.*/) { if ($line =~ /(pmstatus|dlstatus):[^:]+:(\d+\.\d+):.*/) {
@ -548,7 +621,17 @@ sub run_apt_progress {
exit 0; exit 0;
} }
# parent # parent: ignore signals
# by using "local", the original is automatically restored once the
# function returns
local $SIG{'INT'} = $ignore;
local $SIG{'HUP'} = $ignore;
local $SIG{'PIPE'} = $ignore;
local $SIG{'TERM'} = $ignore;
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
my $has_error = 0; my $has_error = 0;
# apt-get doesn't report a non-zero exit if the update failed. Thus, we # apt-get doesn't report a non-zero exit if the update failed. Thus, we
# have to parse its output. See #778357, #776152, #696335 and #745735 # have to parse its output. See #778357, #776152, #696335 and #745735
@ -575,6 +658,10 @@ sub run_apt_progress {
waitpid $pid, 0; waitpid $pid, 0;
$? == 0 or die "progress parsing failed"; $? == 0 or die "progress parsing failed";
if ($got_signal) {
die "run_apt_progress() received signal: $got_signal";
}
# only print apt failure after progress output finished or otherwise it # only print apt failure after progress output finished or otherwise it
# might interfere # might interfere
if ($fail) { if ($fail) {
@ -583,6 +670,193 @@ sub run_apt_progress {
} }
} }
sub run_chroot(&$) {
my $cmd = shift;
my $options = shift;
my @cleanup_tasks = ();
my $cleanup = sub {
my $signal = $_[0];
while (my $task = pop @cleanup_tasks) {
$task->();
}
if ($signal) {
print STDERR "W: pid $PID cought signal: $signal\n";
exit 1;
}
};
local $SIG{INT} = $cleanup;
local $SIG{HUP} = $cleanup;
local $SIG{PIPE} = $cleanup;
local $SIG{TERM} = $cleanup;
eval {
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
# if more than essential should be installed, make the system look
# more like a real one by creating or bind-mounting the device nodes
foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor) = @{$file};
next if $fname eq './dev/';
if ($type == 0) { # normal file
die "type 0 not implemented";
} elsif ($type == 1) { # hardlink
die "type 1 not implemented";
} elsif ($type == 2) { # symlink
if (!$options->{havemknod}) {
if ($options->{mode} eq 'fakechroot' and $linkname =~ /^\/proc/) {
# there is no /proc in fakechroot mode
next;
}
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
push @cleanup_tasks, sub {
unlink "$options->{root}/$fname" or warn "cannot unlink $fname: $!";
}
}
symlink $linkname, "$options->{root}/$fname" or die "cannot create symlink $fname";
}
} elsif ($type == 3 or $type == 4) { # character/block special
if (!$options->{havemknod}) {
open my $fh, '>', "$options->{root}/$fname" or die "cannot open $options->{root}/$fname: $!";
close $fh;
if ($options->{mode} eq 'unshare') {
push @cleanup_tasks, sub {
0 == system('umount', '--no-mtab', "$options->{root}/$fname") or warn "umount $fname failed: $?";
unlink "$options->{root}/$fname" or warn "cannot unlink $fname: $!";
};
} elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/$fname") or warn "umount failed: $?";
unlink "$options->{root}/$fname" or warn "cannot unlink $fname: $!";
};
} else {
die "unknown mode: $options->{mode}";
}
0 == system('mount', '-o', 'bind', "/$fname", "$options->{root}/$fname") or die "mount $fname failed: $?";
}
} elsif ($type == 5) { # directory
if (!$options->{havemknod}) {
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
push @cleanup_tasks, sub {
rmdir "$options->{root}/$fname" or warn "cannot rmdir $fname: $!";
}
}
make_path "$options->{root}/$fname" or die "cannot make_path $fname";
chmod $mode, "$options->{root}/$fname" or die "cannot chmod $fname: $!";
}
if ($options->{mode} eq 'unshare') {
push @cleanup_tasks, sub {
0 == system('umount', '--no-mtab', "$options->{root}/$fname") or warn "umount $fname failed: $?";
};
} elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/$fname") or warn "umount $fname failed: $?";
};
} else {
die "unknown mode: $options->{mode}";
}
0 == system('mount', '-o', 'bind', "/$fname", "$options->{root}/$fname") or die "mount $fname failed: $?";
} else {
die "unsupported type: $type";
}
}
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /dev bind-mounted already through --bind=/dev
} else {
die "unknown mode: $options->{mode}";
}
# We can only mount /proc and /sys after extracting the essential
# set because if we mount it before, then base-files will not be able
# to extract those
if ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/sys") or warn "umount /sys failed: $?";
};
0 == system('mount', '-t', 'sysfs', '-o', 'nosuid,nodev,noexec', 'sys', "$options->{root}/sys") or die "mount /sys failed: $?";
} elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where we
# do a real mount. But we also need to unmount in unshare mode because
# otherwise, even with the --one-file-system tar option, the
# permissions of the mount source will be stored and not the mount
# target (the directory)
push @cleanup_tasks, sub {
# since we cannot write to /etc/mtab we need --no-mtab
# unmounting /sys only seems to be successful with --lazy
0 == system('umount', '--no-mtab', '--lazy', "$options->{root}/sys") or warn "umount /sys failed: $?";
};
# without the network namespace unshared, we cannot mount a new
# sysfs. Since we need network, we just bind-mount.
#
# we have to rbind because just using bind results in "wrong fs
# type, bad option, bad superblock" error
0 == system('mount', '-o', 'rbind', '/sys', "$options->{root}/sys") or die "mount /sys failed: $?";
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /proc bind-mounted already through --bind=/proc
} else {
die "unknown mode: $options->{mode}";
}
if ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/proc") or die "umount /proc failed: $?";
};
0 == system('mount', '-t', 'proc', 'proc', "$options->{root}/proc") or die "mount /proc failed: $?";
} elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where we
# do a real mount. But we also need to unmount in unshare mode because
# otherwise, even with the --one-file-system tar option, the
# permissions of the mount source will be stored and not the mount
# target (the directory)
push @cleanup_tasks, sub {
# since we cannot write to /etc/mtab we need --no-mtab
0 == system('umount', '--no-mtab', "$options->{root}/proc") or die "umount /proc failed: $?";
};
0 == system('mount', '-t', 'proc', 'proc', "$options->{root}/proc") or die "mount /proc failed: $?";
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /sys bind-mounted already through --bind=/sys
} else {
die "unknown mode: $options->{mode}";
}
# prevent daemons from starting
{
open my $fh, '>', "$options->{root}/usr/sbin/policy-rc.d" or die "cannot open policy-rc.d: $!";
print $fh "#!/bin/sh\n";
print $fh "exit 101\n";
close $fh;
chmod 0755, "$options->{root}/usr/sbin/policy-rc.d" or die "cannot chmod policy-rc.d: $!";
}
{
move("$options->{root}/sbin/start-stop-daemon", "$options->{root}/sbin/start-stop-daemon.REAL") or die "cannot move start-stop-daemon";
open my $fh, '>', "$options->{root}/sbin/start-stop-daemon" or die "cannot open policy-rc.d: $!";
print $fh "#!/bin/sh\n";
print $fh "echo \"Warning: Fake start-stop-daemon called, doing nothing\">&2\n";
close $fh;
chmod 0755, "$options->{root}/sbin/start-stop-daemon" or die "cannot chmod start-stop-daemon: $!";
}
&{$cmd}();
# cleanup
move("$options->{root}/sbin/start-stop-daemon.REAL", "$options->{root}/sbin/start-stop-daemon") or die "cannot move start-stop-daemon";
unlink "$options->{root}/usr/sbin/policy-rc.d" or die "cannot unlink policy-rc.d";
};
my $error = $@;
# we use the cleanup function to do the unmounting
$cleanup->(0);
if ($error) {
die "run_chroot failed: $error";
}
}
sub setup { sub setup {
my $options = shift; my $options = shift;
@ -1285,169 +1559,15 @@ sub setup {
} }
} }
if (any { $_ eq $options->{mode} } ('root', 'unshare')) { run_chroot {
# if more than essential should be installed, make the system look
# more like a real one by creating or bind-mounting the device nodes
foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor) = @{$file};
next if $fname eq './dev/';
if ($type == 0) { # normal file
die "type 0 not implemented";
} elsif ($type == 1) { # hardlink
die "type 1 not implemented";
} elsif ($type == 2) { # symlink
if (!$options->{havemknod}) {
if ($options->{mode} eq 'fakechroot' and $linkname =~ /^\/proc/) {
# there is no /proc in fakechroot mode
next;
}
symlink $linkname, "$options->{root}/$fname" or die "cannot create symlink $fname";
}
} elsif ($type == 3 or $type == 4) { # character/block special
if (!$options->{havemknod}) {
open my $fh, '>', "$options->{root}/$fname" or die "cannot open $options->{root}/$fname: $!";
close $fh;
0 == system('mount', '-o', 'bind', "/$fname", "$options->{root}/$fname") or die "mount failed: $?";
}
} elsif ($type == 5) { # directory
if (!$options->{havemknod}) {
make_path "$options->{root}/$fname";
chmod $mode, "$options->{root}/$fname" or die "cannot chmod $fname: $!";
}
0 == system('mount', '-o', 'bind', "/$fname", "$options->{root}/$fname") or die "mount failed: $?";
} else {
die "unsupported type: $type";
}
}
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /dev bind-mounted already through --bind=/dev
} else {
die "unknown mode: $options->{mode}";
}
# We can only mount /proc and /sys after extracting the essential
# set because if we mount it before, then base-files will not be able
# to extract those
if ($options->{mode} eq 'unshare') {
# without the network namespace unshared, we cannot mount a new
# sysfs. Since we need network, we just bind-mount.
#
# we have to rbind because just using bind results in "wrong fs
# type, bad option, bad superblock" error
0 == system('mount', '-o', 'rbind', '/sys', "$options->{root}/sys") or die "mount failed: $?";
} elsif ($options->{mode} eq 'root') {
0 == system('mount', '-t', 'sysfs', '-o', 'nosuid,nodev,noexec', 'sys', "$options->{root}/sys") or die "mount failed: $?";
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /proc bind-mounted already through --bind=/proc
} else {
die "unknown mode: $options->{mode}";
}
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
0 == system('mount', '-t', 'proc', 'proc', "$options->{root}/proc") or die "mount failed: $?";
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# we cannot mount in fakechroot and proot mode
# in proot mode we have /sys bind-mounted already through --bind=/sys
} else {
die "unknown mode: $options->{mode}";
}
# prevent daemons from starting
{
open my $fh, '>', "$options->{root}/usr/sbin/policy-rc.d" or die "cannot open policy-rc.d: $!";
print $fh "#!/bin/sh\n";
print $fh "exit 101\n";
close $fh;
chmod 0755, "$options->{root}/usr/sbin/policy-rc.d" or die "cannot chmod policy-rc.d: $!";
}
{
move("$options->{root}/sbin/start-stop-daemon", "$options->{root}/sbin/start-stop-daemon.REAL") or die "cannot move start-stop-daemon";
open my $fh, '>', "$options->{root}/sbin/start-stop-daemon" or die "cannot open policy-rc.d: $!";
print $fh "#!/bin/sh\n";
print $fh "echo \"Warning: Fake start-stop-daemon called, doing nothing\">&2\n";
close $fh;
chmod 0755, "$options->{root}/sbin/start-stop-daemon" or die "cannot chmod start-stop-daemon: $!";
}
print STDERR "I: installing remaining packages inside the chroot...\n"; print STDERR "I: installing remaining packages inside the chroot...\n";
run_apt_progress({ run_apt_progress({
ARGV => [@chrootcmd, 'apt-get', '--yes', 'install'], ARGV => [@chrootcmd, 'apt-get', '--yes', 'install'],
PKGS => [keys %pkgs_to_install], PKGS => [keys %pkgs_to_install],
VERBOSE => $options->{verbose} VERBOSE => $options->{verbose}
}); });
} $options;
# cleanup
move("$options->{root}/sbin/start-stop-daemon.REAL", "$options->{root}/sbin/start-stop-daemon") or die "cannot move start-stop-daemon";
unlink "$options->{root}/usr/sbin/policy-rc.d" or die "cannot unlink policy-rc.d";
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
foreach my $file (@devfiles) {
my ($fname, undef, $type, $linkname, undef, undef) = @{$file};
next if $fname eq './dev/';
if ($type == 0) { # normal file
die "type 0 not implemented";
} elsif ($type == 1) { # hardlink
die "type 1 not implemented";
} elsif ($type == 2) { # symlink
if (!$options->{havemknod}) {
unlink "$options->{root}/$fname" or die "cannot unlink $fname: $!";
}
} elsif ($type == 3 or $type == 4) { # character/block special
if (!$options->{havemknod}) {
if ($options->{mode} eq 'unshare') {
# FIXME: handle umount by a signal handler in
# case of CTRL+C
0 == system('umount', '--no-mtab', "$options->{root}/$fname") or die "umount failed: $?";
} elsif ($options->{mode} eq 'root') {
0 == system('umount', "$options->{root}/$fname") or die "umount failed: $?";
} elsif (any { $_ eq $options->{mode} } ('fakechroot', 'proot')) {
die "impossible";
} else {
die "unknown mode: $options->{mode}";
}
unlink "$options->{root}/$fname";
}
} elsif ($type == 5) { # directory
if ($options->{mode} eq 'unshare') {
0 == system('umount', '--no-mtab', "$options->{root}/$fname") or die "umount failed: $?";
} elsif ($options->{mode} eq 'root') {
0 == system('umount', "$options->{root}/$fname") or die "umount failed: $?";
} elsif (any { $_ eq $options->{mode} } ('fakechroot', 'proot')) {
die "impossible";
} else {
die "unknown mode: $options->{mode}";
}
if (!$options->{havemknod}) {
rmdir "$options->{root}/$fname" or die "cannot rmdir $fname: $!";
}
} else {
die "unsupported type: $type";
}
}
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# nothing was mounted
} else {
die "unknown mode: $options->{mode}";
}
# naturally we have to clean up after ourselves in sudo mode where we
# do a real mount. But we also need to unmount in unshare mode because
# otherwise, even with the --one-file-system tar option, the
# permissions of the mount source will be stored and not the mount
# target (the directory)
if ($options->{mode} eq 'unshare') {
# since we cannot write to /etc/mtab we need --no-mtab
# unmounting /sys only seems to be successful with --lazy
0 == system('umount', '--no-mtab', '--lazy', "$options->{root}/sys") or die "umount failed: $?";
0 == system('umount', '--no-mtab', "$options->{root}/proc") or die "umount failed: $?";
} elsif ($options->{mode} eq 'root') {
0 == system('umount', "$options->{root}/sys") or die "umount failed: $?";
0 == system('umount', "$options->{root}/proc") or die "umount failed: $?";
} elsif (any { $_ eq $options->{mode} } ('proot', 'fakechroot')) {
# nothing was mounted
} else {
die "unknown mode: $options->{mode}";
}
} }
} else { } else {
die "unknown variant: $options->{variant}"; die "unknown variant: $options->{variant}";
@ -1457,8 +1577,7 @@ sub setup {
} }
if (scalar @{$options->{customize}} > 0) { if (scalar @{$options->{customize}} > 0) {
# FIXME: should have stuff mounted and start-stop-daemon and run_chroot {
# policy-rc.d setup
foreach my $script (@{$options->{customize}}) { foreach my $script (@{$options->{customize}}) {
if ( -x $script || $script !~ m/[^\w@\%+=:,.\/-]/a) { if ( -x $script || $script !~ m/[^\w@\%+=:,.\/-]/a) {
print STDERR "I: running customize script directly: $script $options->{root}\n"; print STDERR "I: running customize script directly: $script $options->{root}\n";
@ -1472,6 +1591,7 @@ sub setup {
0 == system('sh', '-c', $script, 'exec', $options->{root}) or die "customization script failed: $script"; 0 == system('sh', '-c', $script, 'exec', $options->{root}) or die "customization script failed: $script";
} }
} }
} $options;
} }
# clean up temporary configuration file # clean up temporary configuration file
@ -1951,10 +2071,25 @@ sub main() {
my $exitstatus = 0; my $exitstatus = 0;
my @taropts = ('--sort=name', "--mtime=\@$mtime", '--clamp-mtime', '--numeric-owner', '--one-file-system', '-c', '--exclude=./dev'); my @taropts = ('--sort=name', "--mtime=\@$mtime", '--clamp-mtime', '--numeric-owner', '--one-file-system', '-c', '--exclude=./dev');
push @taropts, @tar_compress_opts; push @taropts, @tar_compress_opts;
# disable signals so that we can fork and change behaviour of the signal
# handler in the parent and child without getting interrupted
my $sigset = POSIX::SigSet->new(SIGINT, SIGHUP, SIGPIPE, SIGTERM);
POSIX::sigprocmask(SIG_BLOCK, $sigset) or die "Can't block signals: $!\n";
my $pid; my $pid;
pipe my $rfh, my $wfh; pipe my $rfh, my $wfh;
if ($options->{mode} eq 'unshare') { if ($options->{mode} eq 'unshare') {
$pid = get_unshare_cmd { $pid = get_unshare_cmd {
# child
$SIG{'INT'} = 'DEFAULT';
$SIG{'HUP'} = 'DEFAULT';
$SIG{'PIPE'} = 'DEFAULT';
$SIG{'TERM'} = 'DEFAULT';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
close $rfh; close $rfh;
open(STDOUT, '>&', STDERR); open(STDOUT, '>&', STDERR);
@ -1983,6 +2118,14 @@ sub main() {
} elsif (any { $_ eq $options->{mode} } ('root', 'fakechroot', 'proot', 'chrootless')) { } elsif (any { $_ eq $options->{mode} } ('root', 'fakechroot', 'proot', 'chrootless')) {
$pid = fork() // die "fork() failed: $!"; $pid = fork() // die "fork() failed: $!";
if ($pid == 0) { if ($pid == 0) {
$SIG{'INT'} = 'DEFAULT';
$SIG{'HUP'} = 'DEFAULT';
$SIG{'PIPE'} = 'DEFAULT';
$SIG{'TERM'} = 'DEFAULT';
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
close $rfh; close $rfh;
open(STDOUT, '>&', STDERR); open(STDOUT, '>&', STDERR);
@ -2029,6 +2172,23 @@ sub main() {
die "unknown mode: $options->{mode}"; die "unknown mode: $options->{mode}";
} }
# parent
my $got_signal = 0;
my $waiting_for = "setup";
my $ignore = sub {
$got_signal = shift;
print STDERR "I: main() received signal $got_signal: waiting for $waiting_for...\n";
};
$SIG{'INT'} = $ignore;
$SIG{'HUP'} = $ignore;
$SIG{'PIPE'} = $ignore;
$SIG{'TERM'} = $ignore;
# unblock all delayed signals (and possibly handle them)
POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock signals: $!\n";
close $wfh; close $wfh;
if ($options->{maketar}) { if ($options->{maketar}) {
@ -2052,6 +2212,9 @@ sub main() {
$exitstatus = 1; $exitstatus = 1;
} }
# change signal handler message
$waiting_for = "cleanup";
if ($options->{maketar} and -e $options->{root}) { if ($options->{maketar} and -e $options->{root}) {
if ($options->{mode} eq 'unshare') { if ($options->{mode} eq 'unshare') {
# We don't have permissions to remove the directory outside # We don't have permissions to remove the directory outside
@ -2088,6 +2251,10 @@ sub main() {
} }
} }
if ($got_signal) {
$exitstatus = 1;
}
exit $exitstatus; exit $exitstatus;
} }