forked from josch/mmdebstrap
handle INT, HUB, PIPE and TERM signals, wait for child processes and clean up mounts
This commit is contained in:
parent
1c7e0c86f0
commit
7f58c4596a
1 changed files with 347 additions and 180 deletions
491
mmdebstrap
491
mmdebstrap
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue