[tbb-commits] [rbm/master] Bug 40001: Add a container script
gk at torproject.org
gk at torproject.org
Thu Jan 7 17:21:05 UTC 2021
commit cfb4018d34400e43837e77754519b3c2761b7ae6
Author: Nicolas Vigier <boklm at torproject.org>
Date: Tue Jun 16 19:57:19 2020 +0200
Bug 40001: Add a container script
This script can be used to start/stop containers during rbm builds.
---
container | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 296 insertions(+)
diff --git a/container b/container
new file mode 100755
index 0000000..3f07f24
--- /dev/null
+++ b/container
@@ -0,0 +1,296 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use File::Basename;
+use lib dirname($0) . '/lib';
+use RBM qw(exit_error);
+use English;
+use Getopt::Long;
+use Path::Tiny;
+use File::Path qw(make_path);
+use File::Copy::Recursive qw(pathrmdir rcopy fcopy);
+require "syscall.ph";
+
+*CLONE_NEWNS = \0x20000;
+*CLONE_NEWUTS = \0x4000000;
+*CLONE_NEWIPC = \0x8000000;
+*CLONE_NEWUSER = \0x10000000;
+*CLONE_NEWPID = \0x20000000;
+*CLONE_NEWNET = \0x40000000;
+our (
+ $CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC,
+ $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET
+);
+
+my @bind_mounts = qw(/dev/console /dev/full /dev/null /dev/ptmx /dev/random
+ /dev/tty /dev/urandom /dev/zero);
+
+sub create_devfiles {
+ my ($rootfsdir) = @_;
+ my @dirs = qw(pts shm);
+ my %links = (
+ fd => '/proc/self/fd',
+ stderr => '/proc/self/fd/2',
+ stdin => '/proc/self/fd/0',
+ stdout => '/proc/self/fd/1',
+ );
+ make_path("$rootfsdir/dev");
+ foreach my $dir (@dirs) {
+ make_path("$rootfsdir/dev/$dir");
+ chmod 0755, "$rootfsdir/dev/$dir";
+ }
+ foreach my $l (keys %links) {
+ next if -e "$rootfsdir/dev/$l";
+ symlink $links{$l}, "$rootfsdir/dev/$l"
+ or exit_error "Error creating symlink $l";
+ }
+}
+
+sub do_mounts {
+ my ($rootfsdir) = @_;
+ foreach my $mount (@bind_mounts) {
+ open my $fh, '>', "$rootfsdir$mount"
+ or exit_error "error opening $rootfsdir$mount: $!";
+ close $fh;
+ system('mount', '-o', 'bind', $mount, "$rootfsdir$mount") == 0
+ or exit_error "Error bind mounting $mount";
+ }
+ system('mount', '-o', 'rbind', '/sys', "$rootfsdir/sys") == 0
+ or exit_error 'Error rbind mounting /sys';
+ system('mount', '-t', 'proc', 'proc', "$rootfsdir/proc") == 0
+ or exit_error 'Error mounting /proc';
+ chmod oct(1777), "$rootfsdir/dev/shm";
+ system('mount', '-t', 'tmpfs', 'none', "$rootfsdir/dev/shm") == 0
+ or exit_error "Error mounting /dev/shm";
+}
+
+sub do_unmounts {
+ my ($rootfsdir) = @_;
+ my @othermounts = qw(/proc /dev/shm);
+ foreach my $mount (@bind_mounts, @othermounts) {
+ system('umount', '--no-mtab', "$rootfsdir$mount") == 0
+ or warn "Error unmounting $mount";
+ }
+ system('umount', '--no-mtab', '--lazy', "$rootfsdir/sys") == 0
+ or warn "Error unmounting /sys";
+}
+
+sub extract_tar {
+ my ($rootfsdir, $tarfile) = @_;
+ make_path($rootfsdir);
+ system('tar', '--exclude=./dev', '-C', $rootfsdir, '-xf', $tarfile);
+};
+
+sub create_tar {
+ my ($rootfsdir, $tarfile) = @_;
+ system('tar', '--exclude=./dev', '-C', $rootfsdir, '-caf', $tarfile, '.');
+}
+
+sub run_chroot {
+ my ($rootfsdir, $cmd) = @_;
+ create_devfiles($rootfsdir);
+ do_mounts($rootfsdir);
+ fcopy('/etc/resolv.conf', "$rootfsdir/etc/resolv.conf");
+ local %ENV = ();
+ system('hostname', 'rbm');
+ my $res = system('/usr/sbin/chroot', $rootfsdir, @$cmd);
+ do_unmounts($rootfsdir);
+ return $res;
+}
+
+sub copy_file_to {
+ my ($rootfsdir, $src, $dst, $owner) = @_;
+ make_path("$rootfsdir/$dst");
+ my $filename = fileparse($src);
+ rcopy($src, "$rootfsdir/$dst/$filename")
+ or exit_error "Failed to copy $src to $rootfsdir/$dst/$filename";
+ return run_chroot($rootfsdir, ['chown', '-R', $owner, $dst]);
+}
+
+sub copy_file_from {
+ my ($rootfsdir, $src, $dst) = @_;
+ make_path($dst);
+ rcopy("$rootfsdir/$src", $dst)
+ or exit_error "Failed to copy $rootfsdir/$src to $dst";
+}
+
+sub get_guidmapcmd {
+ my ($guid) = (@_);
+ my $config_file = "/etc/sub${guid}";
+ my ($id) = ${guid} eq 'uid' ? ($UID) : split(' ', $GID);
+ my ($current_usergroup) = ${guid} eq 'uid' ? getpwuid($id) : getgrgid($id);
+ for my $line (path($config_file)->lines) {
+ chomp $line;
+ my ($usergroup, $lowerid, $count) = split(':', $line);
+ next unless ($usergroup eq $current_usergroup || $usergroup eq $id);
+ return "0 $id 1 1 $lowerid $count";
+ }
+ exit_error "Could not find $guid in $config_file";
+}
+
+sub unshare_run {
+ my ($s, $options) = @_;
+ my $f1 = fork();
+ if ($f1 == 0) {
+ my $ppid = $$;
+ pipe my $rfh, my $wfh;
+ my $pid = fork() // exit_error("fork() failed: $!");
+ if ($pid == 0) {
+ close $wfh;
+ exit_error("read() did not receive EOF")
+ unless sysread($rfh, my $c, 1) == 0;
+ my $uidmapcmd = get_guidmapcmd('uid');
+ exit_error("newuidmap $ppid $uidmapcmd failed: $!")
+ unless system("newuidmap $ppid $uidmapcmd") == 0;
+ my $gidmapcmd = get_guidmapcmd('gid');
+ exit_error("newgidmap $ppid $gidmapcmd failed: $!")
+ unless system("newgidmap $ppid $gidmapcmd") == 0;
+ exit 0;
+ }
+
+ my $unshare_flags = $CLONE_NEWUSER | $CLONE_NEWNS | $CLONE_NEWPID | $CLONE_NEWUTS
+ | $CLONE_NEWIPC;
+ $unshare_flags |= $CLONE_NEWNET if $options->{'disable-network'};
+ syscall &SYS_unshare, $unshare_flags;
+ close $wfh;
+ waitpid($pid, 0) or exit_error("waitpid() failed: $!");
+ exit_error("failed to set uidmap") if $? >> 8;
+ syscall(&SYS_setgid, 0) == 0 or exit_error("setgid failed: $!");
+ syscall(&SYS_setuid, 0) == 0 or exit_error("setuid failed: $!");
+ syscall(&SYS_setgroups, 0, 0) == 0 or exit_error("setgroups failed: $!");
+
+ my $f2 = fork() // exit_error("fork() failed: $!");
+ if ($f2) {
+ waitpid($f2, 0) or exit_error("waitpid() failed: $!");
+ exit $? >> 8;
+ }
+ my $res = $s->();
+ exit($res ? $res >> 8 : 0);
+ }
+ waitpid($f1, 0) or exit_error("waitpid() failed: $!");
+ exit $? >> 8;
+}
+
+my %actions = (
+ archive => {
+ descr => 'Archive a container directory',
+ usage => "$0 archive <container-dir> <dest-tarball>",
+ run => sub {
+ usageexit($ARGV[0]) unless @ARGV == 3;
+ unshare_run(
+ sub {
+ return create_tar($ARGV[1], $ARGV[2]);
+ }
+ );
+ },
+ },
+ extract => {
+ descr => 'Extract a container',
+ usage => "$0 extract <container-dir> <tarball>",
+ run => sub {
+ usageexit($ARGV[0]) unless @ARGV == 3;
+ exit_error "$ARGV[0] already exists" if -e $ARGV[1];
+ unshare_run(
+ sub {
+ return extract_tar($ARGV[1], $ARGV[2]);
+ }
+ );
+ },
+ },
+ remove => {
+ descr => "Remove a container directory",
+ usage => "$0 remove <container-dir>",
+ run => sub {
+ usageexit($ARGV[0]) unless @ARGV == 2;
+ exit_error "$ARGV[1] is not a directory" unless -d $ARGV[1];
+ unshare_run(
+ sub {
+ return pathrmdir($ARGV[1]);
+ }
+ );
+ },
+ },
+ put => {
+ descr => "Copy a file or directory to the container directory",
+ usage => "$0 put <container-dir> <src> <dst> <owner>",
+ run => sub {
+ usageexit($ARGV[0]) unless @ARGV == 5;
+ exit_error "$ARGV[1] is not a directory" unless -d $ARGV[1];
+ my (undef, @args) = @ARGV;
+ unshare_run(
+ sub {
+ return copy_file_to(@args);
+ }
+ );
+ },
+ },
+ get => {
+ descr => "Copy a file or a directory from the container directory",
+ usage => "$0 get <container-dir> <src> <dst>",
+ run => sub {
+ usageexit($ARGV[0]) unless @ARGV == 4;
+ exit_error "$ARGV[1] is not a directory" unless -d $ARGV[1];
+ my (undef, @args) = @ARGV;
+ unshare_run(
+ sub {
+ return copy_file_from(@args);
+ }
+ );
+ },
+ },
+ run => {
+ descr => "Run a command in a container",
+ usage => "$0 run [--disable-network] [--chroot=<container-dir>] [--] <command> [<arg>...]",
+ run => sub {
+ usageexit($_[0]) unless @_>= 2;
+ shift;
+ my @options = qw(disable-network! chroot=s);
+ my %val;
+ Getopt::Long::GetOptionsFromArray(\@_, \%val, @options) || exit 1;
+ my (@cmd) = @_;
+ unshare_run(
+ sub {
+ return $val{chroot} ? run_chroot($val{chroot}, [@cmd]) : system(@cmd);
+ },
+ \%val
+ );
+ },
+ },
+ usage => {
+ run => \&usage,
+ descr => 'Show usage information for an action',
+ },
+ '--help' => {
+ run => \&usage,
+ },
+);
+
+sub usage {
+ if ($_[1] && $actions{$_[1]} && $actions{$_[1]}->{usage}) {
+ print $actions{$_[1]}{usage}, "\n";
+ } else {
+ print STDERR "$0 <action> [options]\n";
+ print STDERR "$0 usage [action]\n\n";
+ print STDERR "Available actions:\n";
+ my @actions = grep { $actions{$_}->{descr} } keys %actions;
+ print STDERR map { " - $_: $actions{$_}->{descr}\n" } @actions;
+ print STDERR "\nSee '$0 usage <action>' for usage information\n";
+ }
+ exit 0;
+}
+sub usageexit {
+ my $cmd = shift;
+ print STDERR "Incorrect argument(s).\n";
+ print STDERR "See '$0 usage $cmd' for usage information\n";
+ exit 1;
+}
+
+
+if (@ARGV == 0 || !$actions{$ARGV[0]}) {
+ usage();
+ exit 1;
+}
+usage('usage', $ARGV[0]) if grep { $_ eq '--help' } @ARGV[1..(@ARGV - 1)];
+$actions{$ARGV[0]}->{run}->(@ARGV);
More information about the tbb-commits
mailing list