diff options
author | Nikolay Amiantov <ab@fmap.me> | 2015-02-05 18:14:28 +0300 |
---|---|---|
committer | Nikolay Amiantov <ab@fmap.me> | 2015-02-05 19:46:25 +0300 |
commit | 4b3bb7b4489bffc35efdf8b972f8393beb2f870b (patch) | |
tree | 19fd86eddd22533ce2af2b03082f5a095d6e6b3a | |
parent | b3ee378f5038886d08b2a17eb4ab5130ba7f77b8 (diff) | |
download | nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar.gz nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar.bz2 nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar.lz nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar.xz nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.tar.zst nixpkgs-4b3bb7b4489bffc35efdf8b972f8393beb2f870b.zip |
userFHSEnv: add build tool
-rwxr-xr-x | pkgs/build-support/build-fhs-userenv/chroot-user.rb | 175 | ||||
-rw-r--r-- | pkgs/build-support/build-fhs-userenv/default.nix | 36 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 8 |
3 files changed, 219 insertions, 0 deletions
diff --git a/pkgs/build-support/build-fhs-userenv/chroot-user.rb b/pkgs/build-support/build-fhs-userenv/chroot-user.rb new file mode 100755 index 00000000000..857ccd58cd7 --- /dev/null +++ b/pkgs/build-support/build-fhs-userenv/chroot-user.rb @@ -0,0 +1,175 @@ +#!/usr/bin/env ruby + +# Bind mounts hierarchy: [from, to (relative)] +# If 'to' is nil, path will be the same +mounts = [ ['/nix/store', nil], + ['/dev', nil], + ['/proc', nil], + ['/sys', nil], + ['/etc', 'host-etc'], + ['/home', nil], + ['/var', nil], + ['/run', nil], + ['/root', nil], + ].map! { |x| [ x[0], x[1].nil? ? x[0].sub(/^\/*/, '') : x[1] ] } + +# Create directories +mkdirs = ['tmp', + ] + +# Symlinks: [from, to (dir)] +symlinks = + # /etc symlinks: [file name, prefix in host-etc] + [ ['passwd', ''], + ['group', ''], + ['shadow', ''], + ['hosts', ''], + ['resolv.conf', ''], + ['nsswitch.conf', ''], + ['pam.d', 'static'], + ['fonts/fonts.conf', 'static'], + ['fonts/conf.d/00-nixos.conf', 'static'], + ].map! { |x| [ "host-etc/#{x[1]}/#{x[0]}", "etc/#{File.dirname x[0]}" ] } + +require 'tmpdir' +require 'fileutils' +require 'pathname' +require 'set' +require 'fiddle' + +def write_file(path, str) + File.open(path, 'w') { |file| file.write str } +end + +# Import C standard library and several needed calls +$libc = Fiddle.dlopen nil + +def make_fcall(name, args, output) + c = Fiddle::Function.new $libc[name], args, output + lambda do |*args| + ret = c.call *args + raise SystemCallError.new Fiddle.last_error if ret < 0 + return ret + end +end + +$fork = make_fcall 'fork', [], Fiddle::TYPE_INT + +CLONE_NEWNS = 0x00020000 +CLONE_NEWUSER = 0x10000000 +$unshare = make_fcall 'unshare', [Fiddle::TYPE_INT], Fiddle::TYPE_INT + +MS_BIND = 0x1000 +MS_REC = 0x4000 +$mount = make_fcall 'mount', [Fiddle::TYPE_VOIDP, + Fiddle::TYPE_VOIDP, + Fiddle::TYPE_VOIDP, + Fiddle::TYPE_LONG, + Fiddle::TYPE_VOIDP], + Fiddle::TYPE_INT + +# Read command line args +abort "Usage: chrootenv swdir program args..." unless ARGV.length >= 2 +swdir = Pathname.new ARGV[0] +execp = ARGV.drop 1 + +# Create temporary directory for root and chdir +root = Dir.mktmpdir 'chrootenv' + +# Fork process; we need this to do a proper cleanup because +# child process will chroot into temporary directory. +# We use imported 'fork' instead of native to overcome +# CRuby's meddling with threads; this should be safe because +# we don't use threads at all. +$cpid = $fork.call +if $cpid == 0 + # Save user UID and GID + uid = Process.uid + gid = Process.gid + + # Create new mount and user namespaces + # CLONE_NEWUSER requires a program to be non-threaded, hence + # native fork above. + $unshare.call CLONE_NEWNS | CLONE_NEWUSER + + # Map users and groups to the parent namespace + write_file '/proc/self/setgroups', 'deny' + write_file '/proc/self/uid_map', "#{uid} #{uid} 1" + write_file '/proc/self/gid_map', "#{gid} #{gid} 1" + + # Do mkdirs + mkdirs.each { |x| FileUtils.mkdir_p x } + + # Do rbind mounts. + mounts.each do |x| + to = "#{root}/#{x[1]}" + FileUtils.mkdir_p to + $mount.call x[0], to, nil, MS_BIND | MS_REC, nil + end + + # Chroot! + Dir.chroot root + Dir.chdir '/' + + # Do symlinks + symlinks.each do |x| + FileUtils.mkdir_p x[1] + FileUtils.ln_s x[0], x[1] + end + + # Symlink swdir hierarchy + mount_dirs = Set.new mounts.map { |x| Pathname.new x[1] } + link_swdir = lambda do |swdir, prefix| + swdir.find do |path| + rel = prefix.join path.relative_path_from(swdir) + # Don't symlink anything in binded or symlinked directories + Find.prune if mount_dirs.include? rel or rel.symlink? + if not rel.directory? + # File does not exist; make a symlink and bail out + rel.make_symlink path + Find.prune + end + # Recursively follow symlinks + link_swdir.call path.readlink, rel if path.symlink? + end + end + link_swdir.call swdir, Pathname.new('') + + # New environment + oldenv = ENV.to_h + ENV.replace({ 'PS1' => oldenv['PS1'], + 'TERM' => oldenv['TERM'], + 'DISPLAY' => oldenv['DISPLAY'], + 'HOME' => oldenv['HOME'], + 'PATH' => '/bin:/sbin', + 'XDG_RUNTIME_DIR' => oldenv['XDG_RUNTIME_DIR'], + }) + + # Finally, exec! + exec *execp +end + +# Wait for a child. If we catch a signal, resend it to child and continue +# waiting. +def wait_child + begin + Process.wait + + # Return child's exit code + if $?.exited? + exit $?.exitstatus + else + exit 1 + end + rescue SignalException => e + Process.kill e.signo, $cpid + wait_child + end +end + +begin + wait_child +ensure + # Cleanup + FileUtils.rm_rf root, secure: true +end diff --git a/pkgs/build-support/build-fhs-userenv/default.nix b/pkgs/build-support/build-fhs-userenv/default.nix new file mode 100644 index 00000000000..b3bbc19dda8 --- /dev/null +++ b/pkgs/build-support/build-fhs-userenv/default.nix @@ -0,0 +1,36 @@ +{ writeTextFile, stdenv, ruby } : { env, runScript } : + +let + name = env.pname; + + # Sandboxing script + chroot-user = writeTextFile { + name = "chroot-user"; + executable = true; + destination = "/bin/chroot-user"; + text = '' + #! ${ruby}/bin/ruby + ${builtins.readFile ./chroot-user.rb} + ''; + }; + +in stdenv.mkDerivation { + name = "${name}-userenv"; + buildInputs = [ ruby ]; + buildCommand = '' + mkdir -p $out/bin + cat > $out/bin/${name} <<EOF + #! ${stdenv.shell} + exec ${chroot-user}/bin/chroot-user ${env} $out/libexec/run + EOF + chmod +x $out/bin/${name} + + mkdir -p $out/libexec + cat > $out/libexec/run <<EOF + #! ${stdenv.shell} + source /etc/profile + ${runScript} + EOF + chmod +x $out/libexec/run + ''; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 16f2c4af730..123d7164862 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -266,11 +266,19 @@ let }; chrootFHSEnv = callPackage ../build-support/build-fhs-chrootenv { }; + userFHSEnv = callPackage ../build-support/build-fhs-userenv { + ruby = ruby_2_1_3; + }; buildFHSChrootEnv = args: chrootFHSEnv { env = buildFHSEnv args; }; + buildFHSUserEnv = args: userFHSEnv { + env = buildFHSEnv (removeAttrs args [ "runScript" ]); + runScript = args.runScript; + }; + dotnetenv = import ../build-support/dotnetenv { inherit stdenv; dotnetfx = dotnetfx40; |