Update API and tests.

This commit is contained in:
Ryan Voots 2017-05-04 13:51:34 -07:00
parent 9b75577bda
commit a38af218d7
7 changed files with 109 additions and 61 deletions

View file

@ -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

View file

@ -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,

View file

@ -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));

View file

@ -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

View file

@ -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;

View file

@ -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
View file