summary refs log tree commit diff
diff options
context:
space:
mode:
authorNikolay Amiantov <ab@fmap.me>2015-02-05 18:14:28 +0300
committerNikolay Amiantov <ab@fmap.me>2015-02-05 19:46:25 +0300
commit4b3bb7b4489bffc35efdf8b972f8393beb2f870b (patch)
tree19fd86eddd22533ce2af2b03082f5a095d6e6b3a
parentb3ee378f5038886d08b2a17eb4ab5130ba7f77b8 (diff)
downloadnixpkgs-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-xpkgs/build-support/build-fhs-userenv/chroot-user.rb175
-rw-r--r--pkgs/build-support/build-fhs-userenv/default.nix36
-rw-r--r--pkgs/top-level/all-packages.nix8
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;