Update API and tests.
This commit is contained in:
parent
9b75577bda
commit
a38af218d7
7 changed files with 109 additions and 61 deletions
8
Changes
8
Changes
|
@ -1,6 +1,12 @@
|
||||||
Revision history for Sys::Linux::Namespaces
|
Revision history for Sys::Linux::Namespaces
|
||||||
|
|
||||||
0.003 - TBD
|
0.010 - May 4 2017
|
||||||
|
* More stable API
|
||||||
|
* Added umount support to Sys::Linux::Mount
|
||||||
|
* Setup a private /proc by default when using private_pid
|
||||||
|
* Better test handling of forked processes
|
||||||
|
|
||||||
|
0.003 - May 4 2017
|
||||||
* Fix language in docs
|
* Fix language in docs
|
||||||
|
|
||||||
0.002 - May 3 2017
|
0.002 - May 3 2017
|
||||||
|
|
|
@ -2,6 +2,8 @@ package Sys::Linux::Mount;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
use Carp qw/carp/;
|
||||||
|
|
||||||
require Exporter;
|
require Exporter;
|
||||||
our @ISA = qw/Exporter/;
|
our @ISA = qw/Exporter/;
|
||||||
require XSLoader;
|
require XSLoader;
|
||||||
|
@ -10,11 +12,11 @@ XSLoader::load();
|
||||||
|
|
||||||
my @mount_consts = qw/MS_RDONLY MS_NOSUID MS_NODEV MS_NOEXEC MS_SYNCHRONOUS MS_REMOUNT MS_MANDLOCK MS_DIRSYNC MS_NOATIME MS_NODIRATIME MS_BIND MS_MOVE MS_REC MS_SILENT MS_POSIXACL MS_UNBINDABLE MS_PRIVATE MS_SLAVE MS_SHARED MS_RELATIME MS_KERNMOUNT MS_I_VERSION MS_STRICTATIME MS_LAZYTIME MS_ACTIVE MS_NOUSER/;
|
my @mount_consts = qw/MS_RDONLY MS_NOSUID MS_NODEV MS_NOEXEC MS_SYNCHRONOUS MS_REMOUNT MS_MANDLOCK MS_DIRSYNC MS_NOATIME MS_NODIRATIME MS_BIND MS_MOVE MS_REC MS_SILENT MS_POSIXACL MS_UNBINDABLE MS_PRIVATE MS_SLAVE MS_SHARED MS_RELATIME MS_KERNMOUNT MS_I_VERSION MS_STRICTATIME MS_LAZYTIME MS_ACTIVE MS_NOUSER/;
|
||||||
|
|
||||||
our @EXPORT_OK = (@mount_consts, qw/mount/);
|
our @EXPORT_OK = (@mount_consts, qw/mount umount/);
|
||||||
|
|
||||||
our %EXPORT_TAGS = (
|
our %EXPORT_TAGS = (
|
||||||
'consts' => \@mount_consts,
|
'consts' => \@mount_consts,
|
||||||
'all' => [@mount_consts, qw/mount/],
|
'all' => [@mount_consts, qw/mount umount/],
|
||||||
);
|
);
|
||||||
|
|
||||||
sub mount {
|
sub mount {
|
||||||
|
@ -25,10 +27,19 @@ sub mount {
|
||||||
my $ret = _mount_sys($source//"", $target//"", $filesystem//"", $flags//0, $options_str//"");
|
my $ret = _mount_sys($source//"", $target//"", $filesystem//"", $flags//0, $options_str//"");
|
||||||
|
|
||||||
if ($ret != 0) {
|
if ($ret != 0) {
|
||||||
die "mount failed: $ret $!";
|
carp "mount failed: $ret $!";
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub umount {
|
||||||
|
my ($target) = @_;
|
||||||
|
|
||||||
|
carp "No argument given to umount()" unless $target;
|
||||||
|
|
||||||
|
my $ret = _umount_sys($target);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
use constant {MS_RDONLY => 1,
|
use constant {MS_RDONLY => 1,
|
||||||
|
|
|
@ -16,3 +16,8 @@ SV *_mount_sys(const char *source, const char *target, const char *filesystem, u
|
||||||
CODE:
|
CODE:
|
||||||
ST(0) = sv_newmortal();
|
ST(0) = sv_newmortal();
|
||||||
sv_setiv(ST(0), mount(source, target, filesystem, mountflags, (const void *) data));
|
sv_setiv(ST(0), mount(source, target, filesystem, mountflags, (const void *) data));
|
||||||
|
|
||||||
|
SV *_umount_sys(const char *target)
|
||||||
|
CODE:
|
||||||
|
ST(0) = sv_newmortal();
|
||||||
|
sv_setiv(ST(0), umount(target));
|
||||||
|
|
|
@ -11,13 +11,13 @@ use POSIX qw/_exit/;
|
||||||
use Moo;
|
use Moo;
|
||||||
use Carp qw/carp/;
|
use Carp qw/carp/;
|
||||||
|
|
||||||
|
has no_proc => (is => 'rw');
|
||||||
|
|
||||||
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";
|
||||||
has $pp => (is => 'rw');
|
has $pp => (is => 'rw');
|
||||||
}
|
}
|
||||||
|
|
||||||
has code => (is => 'rw'); # code to run in the namespace
|
|
||||||
|
|
||||||
sub _uflags {
|
sub _uflags {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $uflags = 0;
|
my $uflags = 0;
|
||||||
|
@ -53,10 +53,7 @@ sub pre_setup {
|
||||||
my ($self, %args) = @_;
|
my ($self, %args) = @_;
|
||||||
|
|
||||||
die "Private net is not yet supported" if $self->private_net;
|
die "Private net is not yet supported" if $self->private_net;
|
||||||
if ($self->private_pid &&
|
if ($self->private_pid && (ref $args{code} ne 'CODE' || !$args{_run})) {
|
||||||
((ref $self->code ne 'CODE') ||
|
|
||||||
(ref $args{code} ne 'CODE'))) {
|
|
||||||
|
|
||||||
die "Private PID space requires a coderef to become the new PID 1";
|
die "Private PID space requires a coderef to become the new PID 1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,15 +66,18 @@ sub post_setup {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($self->private_tmp) {
|
if ($self->private_tmp) {
|
||||||
if (ref $self->private_tmp eq 'HASH') {
|
my $data = undef;
|
||||||
|
$data = $self->private_tmp if (ref $self->private_tmp eq 'HASH');
|
||||||
|
|
||||||
|
eval {umount("/tmp")}; # ignore it if it wasn't mounted
|
||||||
mount("/tmp", "/tmp", "tmpfs", 0, undef);
|
mount("/tmp", "/tmp", "tmpfs", 0, undef);
|
||||||
mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, $self->private_tmp);
|
mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, $data);
|
||||||
} elsif (ref $self->private_tmp) { # TODO do this with a constraint?
|
|
||||||
die "Bad ref type passed as private_tmp";
|
|
||||||
} else {
|
|
||||||
mount("/tmp", "/tmp", "tmpfs", 0, undef);
|
|
||||||
mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, undef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($self->private_pid && !$self->no_proc) {
|
||||||
|
eval {umount("/proc")}; # ignore it if it wasn't mounted already
|
||||||
|
mount("/proc", "/proc", "proc", 0, undef);
|
||||||
|
mount("/proc", "/proc", "proc", MS_PRIVATE, undef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,30 +85,36 @@ sub setup {
|
||||||
my ($self, %args) = @_;
|
my ($self, %args) = @_;
|
||||||
|
|
||||||
my $uflags = $self->_uflags;
|
my $uflags = $self->_uflags;
|
||||||
|
|
||||||
$self->pre_setup(%args);
|
$self->pre_setup(%args);
|
||||||
|
|
||||||
my $code = $args{code} // $self->code();
|
|
||||||
|
|
||||||
if ($code) {
|
|
||||||
$self->_subprocess(sub {
|
|
||||||
unshare($uflags);
|
unshare($uflags);
|
||||||
|
$self->post_setup(%args);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub run {
|
||||||
|
my ($self, %args) = @_;
|
||||||
|
|
||||||
|
my $code = $args{code};
|
||||||
|
$args{_run} = 1;
|
||||||
|
|
||||||
|
carp "Run must be given a codref to run" unless ref $code eq "CODE";
|
||||||
|
my $uflags = $self->_uflags;
|
||||||
|
$self->pre_setup(%args);
|
||||||
|
|
||||||
|
$self->_subprocess(sub {
|
||||||
|
$self->setup(%args);
|
||||||
|
|
||||||
# We've just unshared, if we wanted a private pid space we MUST fork again.
|
# We've just unshared, if we wanted a private pid space we MUST fork again.
|
||||||
if ($self->private_pid) {
|
if ($self->private_pid) {
|
||||||
$self->_subprocess(sub {
|
$self->_subprocess(sub {
|
||||||
$self->post_setup(%args);
|
|
||||||
$code->(%args);
|
$code->(%args);
|
||||||
}, %args);
|
}, %args);
|
||||||
} else {
|
} else {
|
||||||
$self->post_setup(%args);
|
|
||||||
$code->(%args);
|
$code->(%args);
|
||||||
}
|
}
|
||||||
}, %args);
|
}, %args);
|
||||||
} else {
|
|
||||||
unshare($uflags);
|
|
||||||
$self->post_setup(%args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +133,7 @@ Sys::Linux::Namespace - A Module for setting up linux namespaces
|
||||||
# Create a namespace with a private /tmp
|
# Create a namespace with a private /tmp
|
||||||
my $ns1 = Sys::Linux::Namespace->new(private_tmp => 1);
|
my $ns1 = Sys::Linux::Namespace->new(private_tmp => 1);
|
||||||
|
|
||||||
$ns1->setup(code => sub {
|
$ns1->run(code => sub {
|
||||||
# This code has it's own completely private /tmp filesystem
|
# This code has it's own completely private /tmp filesystem
|
||||||
open(my $fh, "</tmp/private");
|
open(my $fh, "</tmp/private");
|
||||||
print $fh "Hello Void";
|
print $fh "Hello Void";
|
||||||
|
@ -137,7 +143,7 @@ Sys::Linux::Namespace - A Module for setting up linux namespaces
|
||||||
|
|
||||||
# Let's do it again, but this time with a private PID space too
|
# Let's do it again, but this time with a private PID space too
|
||||||
my $ns2 = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1);
|
my $ns2 = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1);
|
||||||
$ns2->setup(code => sub {
|
$ns2->run(code => sub {
|
||||||
# I will only see PID 1. I can fork anything I want and they will only see me
|
# I will only see PID 1. I can fork anything I want and they will only see me
|
||||||
# if I die they die too.
|
# if I die they die too.
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
@ -146,7 +152,7 @@ Sys::Linux::Namespace - A Module for setting up linux namespaces
|
||||||
# We're back to our previous global /tmp and PID namespace
|
# We're back to our previous global /tmp and PID namespace
|
||||||
# all processes and private filesystems have been removed
|
# all processes and private filesystems have been removed
|
||||||
|
|
||||||
# Now let's set up a private /tmp
|
# Now let's set up a private /tmp for the rest of the process
|
||||||
$ns1->setup();
|
$ns1->setup();
|
||||||
# We're now permanently (for this process) using a private /tmp.
|
# We're now permanently (for this process) using a private /tmp.
|
||||||
|
|
||||||
|
@ -164,11 +170,6 @@ All arguments are passed in like a hash.
|
||||||
|
|
||||||
=over 1
|
=over 1
|
||||||
|
|
||||||
=item code
|
|
||||||
|
|
||||||
A coderef to run when setting up the namespaces. This gets run in a child process that's isolated from the parent.
|
|
||||||
If you don't pass one in during construction or to C<setup> then the namespace changes will happen to the current process.
|
|
||||||
|
|
||||||
=item private_mount
|
=item private_mount
|
||||||
|
|
||||||
Setup a private mount namespace, this makes every currently mounted filesystem private to our process.
|
Setup a private mount namespace, this makes every currently mounted filesystem private to our process.
|
||||||
|
@ -181,7 +182,13 @@ Takes either a true value, or a hashref with options to pass to the mount syscal
|
||||||
|
|
||||||
=item private_pid
|
=item private_pid
|
||||||
|
|
||||||
Create a private PID namespace. This requires a C<code> parameter either to C<new()> or to C<setup()>
|
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()>
|
||||||
|
Also sets up a private /proc mount by default
|
||||||
|
|
||||||
|
=item no_proc
|
||||||
|
|
||||||
|
Don't setup a private /proc mount when doing private_pid
|
||||||
|
|
||||||
=item private_net
|
=item private_net
|
||||||
|
|
||||||
|
@ -209,11 +216,17 @@ Create a new System V Semaphore namespace. This will let you create new semapho
|
||||||
|
|
||||||
Engage the namespaces with all the configured options.
|
Engage the namespaces with all the configured options.
|
||||||
|
|
||||||
All arguments are passed by name like a hash.
|
=head2 C<run>
|
||||||
|
|
||||||
You may pass in a C<code> parameter to run in a child process, this overrides one provided during construction.
|
Engage the namespaces on an unsuspecting coderef. Arguments are passed in like a hash
|
||||||
|
|
||||||
Any other parameters are passed through to your coderef if present.
|
=over 1
|
||||||
|
|
||||||
|
=item code
|
||||||
|
|
||||||
|
The coderef to run. It will receive all arguments passed to C<< ->run() >> as a hash.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
=head1 AUTHOR
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use Test::More tests => 3;
|
use Test::More;
|
||||||
|
|
||||||
use_ok "Sys::Linux::Mount";
|
use_ok "Sys::Linux::Mount";
|
||||||
use_ok "Sys::Linux::Unshare";
|
use_ok "Sys::Linux::Unshare";
|
||||||
use_ok "Sys::Linux::Namespace";
|
use_ok "Sys::Linux::Namespace";
|
||||||
|
|
||||||
|
done_testing;
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
use Test::More tests => 4;
|
BEGIN {
|
||||||
|
$ENV{TMPDIR} = 't/tmp/'
|
||||||
|
}
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use Test::SharedFork;
|
||||||
|
|
||||||
# test 1
|
# test 1
|
||||||
use_ok("Sys::Linux::Namespace");
|
use_ok("Sys::Linux::Namespace");
|
||||||
|
|
||||||
# test 2
|
# test 2
|
||||||
SKIP: {
|
SKIP: {
|
||||||
skip "Need to be root to run test", 3 unless $< == 0;
|
skip "Need to be root to run test", 5 unless $< == 0;
|
||||||
ok(my $namespace = Sys::Linux::Namespace->new(private_tmp => 1), "Setup object");
|
ok(my $namespace = Sys::Linux::Namespace->new(private_tmp => 1), "Setup object");
|
||||||
|
|
||||||
ok($namespace->setup(code => sub {
|
my $ret = $namespace->run(code => sub {
|
||||||
|
is_deeply([glob "/tmp/*"], [], "No files present in /tmp");
|
||||||
|
});
|
||||||
|
|
||||||
|
ok($ret, "run code in sandbox");
|
||||||
|
|
||||||
|
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");
|
||||||
}), "run code in sandbox");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done_testing;
|
||||||
|
|
0
t/tmp/keep
Normal file
0
t/tmp/keep
Normal file
Loading…
Add table
Reference in a new issue