Remove unneeded internal module now that we use Linux::Clone.
This commit is contained in:
parent
865dc0b66d
commit
58f401e837
5 changed files with 80 additions and 132 deletions
6
Changes
6
Changes
|
@ -1,5 +1,11 @@
|
||||||
Revision history for Sys::Linux::Namespaces
|
Revision history for Sys::Linux::Namespaces
|
||||||
|
|
||||||
|
0.013 - May 7 2017
|
||||||
|
* Force target mount option to be an absolute path
|
||||||
|
* Do kill child subprocess on any signal by default. This helps prevent zombie PID 1 processes.
|
||||||
|
* Switch to an existing implementation of the unshare and clone syscall wrappers.
|
||||||
|
* Use the clone syscall instead of unshare+fork when we're running a coderef. This creates fewer processes, and has better handling of CLONE_NEWPID.
|
||||||
|
|
||||||
0.012 - May 4 2017
|
0.012 - May 4 2017
|
||||||
* Fix borken mount options
|
* Fix borken mount options
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,24 @@ use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
use Sys::Linux::Mount qw/:all/;
|
use Sys::Linux::Mount qw/:all/;
|
||||||
use Sys::Linux::Unshare qw/:all/;
|
#use Sys::Linux::Unshare qw/:all/;
|
||||||
|
use Linux::Clone;
|
||||||
use POSIX qw/_exit/;
|
use POSIX qw/_exit/;
|
||||||
|
use Time::HiRes qw/sleep/;
|
||||||
|
|
||||||
use Moo;
|
use Moo;
|
||||||
use Carp qw/croak/;
|
use Carp qw/croak/;
|
||||||
|
|
||||||
has no_proc => (is => 'rw');
|
has no_proc => (is => 'rw');
|
||||||
|
has term_child => (is => 'rw', default => 1);
|
||||||
|
|
||||||
|
our $debug = 0;
|
||||||
|
sub debug {
|
||||||
|
print STDERR @_ if $debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
our $VERSION = v0.013;
|
||||||
|
my @signames = keys %SIG; # capture before anyone has probably localized it.
|
||||||
|
|
||||||
for my $p (qw/tmp mount pid net ipc user uts sysvsem/) {
|
for my $p (qw/tmp mount pid net ipc user uts sysvsem/) {
|
||||||
my $pp = "private_$p";
|
my $pp = "private_$p";
|
||||||
|
@ -22,13 +33,13 @@ sub _uflags {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $uflags = 0;
|
my $uflags = 0;
|
||||||
|
|
||||||
$uflags |= CLONE_NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc));
|
$uflags |= Linux::Clone::NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc));
|
||||||
$uflags |= CLONE_NEWPID if ($self->private_pid);
|
$uflags |= Linux::Clone::NEWPID if ($self->private_pid);
|
||||||
$uflags |= CLONE_NEWNET if ($self->private_net);
|
$uflags |= Linux::Clone::NEWNET if ($self->private_net);
|
||||||
$uflags |= CLONE_NEWIPC if ($self->private_ipc);
|
$uflags |= Linux::Clone::NEWIPC if ($self->private_ipc);
|
||||||
$uflags |= CLONE_NEWUSER if ($self->private_user);
|
$uflags |= Linux::Clone::NEWUSER if ($self->private_user);
|
||||||
$uflags |= CLONE_NEWUTS if ($self->private_uts);
|
$uflags |= Linux::Clone::NEWUTS if ($self->private_uts);
|
||||||
$uflags |= CLONE_SYSVSEM if ($self->private_sysvsem);
|
$uflags |= Linux::Clone::SYSVSEM if ($self->private_sysvsem);
|
||||||
|
|
||||||
return $uflags;
|
return $uflags;
|
||||||
}
|
}
|
||||||
|
@ -37,16 +48,29 @@ sub _subprocess {
|
||||||
my ($self, $code, %args) = @_;
|
my ($self, $code, %args) = @_;
|
||||||
croak "_subprocess requires a CODE ref" unless ref $code eq 'CODE';
|
croak "_subprocess requires a CODE ref" unless ref $code eq 'CODE';
|
||||||
|
|
||||||
my $pid = fork();
|
debug "Forking\n";
|
||||||
|
my $pid = Linux::Clone::clone (sub {
|
||||||
|
local $$ = POSIX::getpid(); # try to fix up $$ if we can.
|
||||||
|
local %SIG = map {$_ => sub {debug "Got signal in $$, exiting"; _exit(0)}} @signames;
|
||||||
|
debug "Inside Child $$\n";
|
||||||
|
|
||||||
|
$code->(%args);
|
||||||
|
_exit(0); # always exit with 0
|
||||||
|
}, 0, POSIX::SIGCHLD | $self->_uflags);
|
||||||
|
|
||||||
croak "Failed to fork: $!" if ($pid < 0);
|
croak "Failed to fork: $!" if ($pid < 0);
|
||||||
if ($pid) {
|
|
||||||
waitpid($pid, 0); # block and wait on child
|
my $sighandler =
|
||||||
return $?;
|
local %SIG = map {my $q=$_; $q => sub {
|
||||||
} else {
|
debug "got signal $q in $$\n";
|
||||||
$code->(%args);
|
kill 'TERM', $pid;
|
||||||
_exit(0);
|
sleep(0.2);
|
||||||
}
|
kill 'KILL', $pid;
|
||||||
|
kill 'KILL', $pid;
|
||||||
|
}} ($self->term_child ? @signames : ());
|
||||||
|
|
||||||
|
waitpid($pid, 0);
|
||||||
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
sub pre_setup {
|
sub pre_setup {
|
||||||
|
@ -85,7 +109,7 @@ sub setup {
|
||||||
my $uflags = $self->_uflags;
|
my $uflags = $self->_uflags;
|
||||||
$self->pre_setup(%args);
|
$self->pre_setup(%args);
|
||||||
|
|
||||||
unshare($uflags);
|
Linux::Clone::unshare($uflags);
|
||||||
$self->post_setup(%args);
|
$self->post_setup(%args);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -99,22 +123,11 @@ sub run {
|
||||||
|
|
||||||
croak "Run must be given a codref to run" unless ref $code eq "CODE";
|
croak "Run must be given a codref to run" unless ref $code eq "CODE";
|
||||||
|
|
||||||
$self->_subprocess(sub {
|
|
||||||
$self->pre_setup(%args);
|
$self->pre_setup(%args);
|
||||||
my $uflags = $self->_uflags;
|
|
||||||
unshare($uflags);
|
|
||||||
|
|
||||||
# We've just unshared, if we wanted a private pid space we MUST fork again.
|
|
||||||
if ($self->private_pid) {
|
|
||||||
$self->_subprocess(sub {
|
$self->_subprocess(sub {
|
||||||
$self->post_setup(%args);
|
$self->post_setup(%args);
|
||||||
$code->(%args);
|
$code->(%args);
|
||||||
}, %args);
|
}, %args);
|
||||||
} else {
|
|
||||||
$self->setup(%args);
|
|
||||||
$code->(%args);
|
|
||||||
}
|
|
||||||
}, %args);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -186,6 +199,10 @@ Create a private PID namespace. This requires the use of C<< ->run() >>.
|
||||||
This requires a C<code> parameter either to C<new()> or to C<setup()>
|
This requires a C<code> parameter either to C<new()> or to C<setup()>
|
||||||
Also sets up a private /proc mount by default
|
Also sets up a private /proc mount by default
|
||||||
|
|
||||||
|
=item term_child
|
||||||
|
|
||||||
|
Send a term signal to the child process on any signal, followed shortly by a kill signal. This is the default behavior to prevent zombied processes.
|
||||||
|
|
||||||
=item no_proc
|
=item no_proc
|
||||||
|
|
||||||
Don't setup a private /proc mount when doing private_pid
|
Don't setup a private /proc mount when doing private_pid
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
package Sys::Linux::Unshare;
|
|
||||||
|
|
||||||
#use strict;
|
|
||||||
use warnings;
|
|
||||||
use Data::Dumper;
|
|
||||||
require Exporter;
|
|
||||||
our @ISA = qw/Exporter/;
|
|
||||||
use Carp qw/croak/;
|
|
||||||
|
|
||||||
require XSLoader;
|
|
||||||
|
|
||||||
XSLoader::load();
|
|
||||||
|
|
||||||
my @unshare_consts = qw/CSIGNAL CLONE_VM CLONE_FS CLONE_FILES CLONE_SIGHAND CLONE_PTRACE CLONE_VFORK CLONE_PARENT CLONE_THREAD CLONE_NEWNS CLONE_SYSVSEM CLONE_SETTLS CLONE_PARENT_SETTID CLONE_CHILD_CLEARTID CLONE_DETACHED CLONE_UNTRACED CLONE_CHILD_SETTID CLONE_NEWCGROUP CLONE_NEWUTS CLONE_NEWIPC CLONE_NEWUSER CLONE_NEWPID CLONE_NEWNET CLONE_IO/;
|
|
||||||
|
|
||||||
our @EXPORT_OK = (@unshare_consts, qw/unshare/);
|
|
||||||
|
|
||||||
our %EXPORT_TAGS = (
|
|
||||||
'consts' => \@unshare_consts,
|
|
||||||
'all' => [@unshare_consts, qw/unshare/],
|
|
||||||
);
|
|
||||||
|
|
||||||
sub clone {
|
|
||||||
my ($flags) = @_;
|
|
||||||
local $! = 0;
|
|
||||||
my $ret_pid = _clone_sys($flags);
|
|
||||||
|
|
||||||
if ($ret_pid < 0) {
|
|
||||||
croak "Clone call failed: $ret_pid $!";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ret_pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub unshare {
|
|
||||||
my ($flags) = @_;
|
|
||||||
|
|
||||||
local $! = 0;
|
|
||||||
my $ret = _unshare_sys($flags);
|
|
||||||
|
|
||||||
if ($ret != 0) {
|
|
||||||
croak "unshare failed $ret $!";
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
use constant {CSIGNAL => 0x000000ff,
|
|
||||||
CLONE_VM => 0x00000100,
|
|
||||||
CLONE_FS => 0x00000200,
|
|
||||||
CLONE_FILES => 0x00000400,
|
|
||||||
CLONE_SIGHAND => 0x00000800,
|
|
||||||
CLONE_PTRACE => 0x00002000,
|
|
||||||
CLONE_VFORK => 0x00004000,
|
|
||||||
CLONE_PARENT => 0x00008000,
|
|
||||||
CLONE_THREAD => 0x00010000,
|
|
||||||
CLONE_NEWNS => 0x00020000,
|
|
||||||
CLONE_SYSVSEM => 0x00040000,
|
|
||||||
CLONE_SETTLS => 0x00080000,
|
|
||||||
CLONE_PARENT_SETTID => 0x00100000,
|
|
||||||
CLONE_CHILD_CLEARTID => 0x00200000,
|
|
||||||
CLONE_DETACHED => 0x00400000,
|
|
||||||
CLONE_UNTRACED => 0x00800000,
|
|
||||||
CLONE_CHILD_SETTID => 0x01000000,
|
|
||||||
CLONE_NEWCGROUP => 0x02000000,
|
|
||||||
CLONE_NEWUTS => 0x04000000,
|
|
||||||
CLONE_NEWIPC => 0x08000000,
|
|
||||||
CLONE_NEWUSER => 0x10000000,
|
|
||||||
CLONE_NEWPID => 0x20000000,
|
|
||||||
CLONE_NEWNET => 0x40000000,
|
|
||||||
CLONE_IO => 0x80000000};
|
|
||||||
|
|
||||||
1;
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster)
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
#include "EXTERN.h" // globals/constant import locations
|
|
||||||
#include "perl.h" // Perl symbols, structures and constants definition
|
|
||||||
#include "XSUB.h" // xsubpp functions and macros
|
|
||||||
#include <sched.h>
|
|
||||||
|
|
||||||
// additional c code goes here
|
|
||||||
|
|
||||||
MODULE = Sys::Linux::Unshare PACKAGE = Sys::Linux::Unshare
|
|
||||||
PROTOTYPES: ENABLE
|
|
||||||
|
|
||||||
# XS code goes here
|
|
||||||
|
|
||||||
# XS comments begin with " #" to avoid them being interpreted as pre-processor
|
|
||||||
# directives
|
|
||||||
|
|
||||||
SV * _unshare_sys(int flags)
|
|
||||||
CODE:
|
|
||||||
ST(0) = sv_newmortal();
|
|
||||||
sv_setiv(ST(0), unshare(flags));
|
|
||||||
|
|
||||||
SV * _clone_sys(int flags)
|
|
||||||
CODE:
|
|
||||||
ST(0) = sv_newmortal();
|
|
||||||
sv_setiv(ST(0), clone(NULL, NULL, CLONE_CHILD_CLEARTID|SIGCHLD|flags, NULL));
|
|
|
@ -7,6 +7,7 @@ use Test::SharedFork;
|
||||||
|
|
||||||
# test 1
|
# test 1
|
||||||
use Sys::Linux::Namespace;
|
use Sys::Linux::Namespace;
|
||||||
|
$Sys::Linux::Namespace::debug = 1;
|
||||||
|
|
||||||
# test 2
|
# test 2
|
||||||
SKIP: {
|
SKIP: {
|
||||||
|
@ -19,13 +20,37 @@ SKIP: {
|
||||||
|
|
||||||
ok(my $pid_ns = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1), "Setup pid object");
|
ok(my $pid_ns = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1), "Setup pid object");
|
||||||
|
|
||||||
$pid_ns->run(code => sub {
|
$ret = $pid_ns->run(code => sub {
|
||||||
is($$, 1, "We're init");
|
is($$, 1, "We're init");
|
||||||
is_deeply([grep {m|/proc/\d+/|} glob '/proc/*/'], ['/proc/1/'], "Only /proc/1/ exists");
|
is_deeply([grep {m|/proc/\d+/|} glob '/proc/*/'], ['/proc/1/'], "Only /proc/1/ exists");
|
||||||
});
|
});
|
||||||
|
|
||||||
ok($ret, "run code in sandbox");
|
ok($ret, "run code in sandbox");
|
||||||
|
|
||||||
|
alarm(5);
|
||||||
|
$pid_ns->run(code => sub {
|
||||||
|
is($$, 1, "Alarmed init");
|
||||||
|
sleep(10);
|
||||||
|
fail("signal propogation didn't happen");
|
||||||
|
});
|
||||||
|
|
||||||
|
alarm(5);
|
||||||
|
$pid_ns->run(code => sub {
|
||||||
|
is($$, 1, "Second alarmed init");
|
||||||
|
|
||||||
|
my $pid = fork();
|
||||||
|
|
||||||
|
isnt($pid, undef, "Fork succeeded");
|
||||||
|
if (!$pid) {
|
||||||
|
sleep(30); # sleep a gigantic amount of time in the child
|
||||||
|
# We should never happen here, because our parent PID 1 should be destroyed by the kernel first
|
||||||
|
fail("Child of PID 1 lived, $$");
|
||||||
|
} else {
|
||||||
|
waitpid($pid, 0); # wait forever
|
||||||
|
fail("PID 1 never got reaped");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ok($namespace->setup(), "Setup namespace in current process");
|
ok($namespace->setup(), "Setup namespace in current process");
|
||||||
is_deeply([glob "/tmp/*"], [], "No files present in /tmp");
|
is_deeply([glob "/tmp/*"], [], "No files present in /tmp");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue