summary refs log tree commit diff
path: root/pkgs
diff options
context:
space:
mode:
authorArseniy Seroka <jagajaga@users.noreply.github.com>2015-02-05 21:57:54 +0300
committerArseniy Seroka <jagajaga@users.noreply.github.com>2015-02-05 21:57:54 +0300
commit5ccc4839cbb98a8462cd6972c2888f329c7947c4 (patch)
treeb43d66f5f8afe6eccfe88b3050673b912f57b332 /pkgs
parentd2bfb5ceb08bed179a996969119a4c72b8eb147a (diff)
parent3500978b8fa4410fa98b5213f21ab9912cc7b880 (diff)
downloadnixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar.gz
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar.bz2
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar.lz
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar.xz
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.tar.zst
nixlib-5ccc4839cbb98a8462cd6972c2888f329c7947c4.zip
Merge pull request #6177 from abbradar/fhs-userenv
add non-root temporary FHS chrootenv
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/build-support/build-fhs-chrootenv/default.nix188
-rw-r--r--pkgs/build-support/build-fhs-chrootenv/destroy.sh.in4
-rw-r--r--pkgs/build-support/build-fhs-chrootenv/env.nix177
-rw-r--r--pkgs/build-support/build-fhs-chrootenv/init.sh.in6
-rwxr-xr-xpkgs/build-support/build-fhs-userenv/chroot-user.rb175
-rw-r--r--pkgs/build-support/build-fhs-userenv/default.nix37
-rw-r--r--pkgs/games/steam/chrootenv.nix8
-rw-r--r--pkgs/top-level/all-packages.nix34
8 files changed, 432 insertions, 197 deletions
diff --git a/pkgs/build-support/build-fhs-chrootenv/default.nix b/pkgs/build-support/build-fhs-chrootenv/default.nix
index a4ba456d7a53..461f4762aba6 100644
--- a/pkgs/build-support/build-fhs-chrootenv/default.nix
+++ b/pkgs/build-support/build-fhs-chrootenv/default.nix
@@ -1,97 +1,6 @@
-{ buildEnv, nixpkgs, nixpkgs_i686, system
-, stdenv, glibc, glibc_multi, glibcLocales
-, bashInteractive, coreutils, less, shadow, su
-, gawk, gcc, gcc_multi, diffutils, findutils, gnused, gnugrep
-, gnutar, gzip, bzip2, xz
-} :
-{ name, pkgs ? [], profile ? ""
-, targetPkgs ? null, multiPkgs ? null
-, extraBuildCommands ? "", extraBuildCommandsMulti ? ""
-}:
-
-assert pkgs       != []   -> targetPkgs == null && multiPkgs == null;
-assert targetPkgs != null -> multiPkgs  != null;
-assert multiPkgs  != null -> targetPkgs != null;
-assert targetPkgs != null -> pkgs       == [];
-
-
-# HOWTO:
-# If pkgs is defined buildFHSChrootEnv will run in legacy mode. This means
-# it will build all pkgs contained in pkgs and basePkgs and then just merge
-# all of their contents together via buildEnv.
-#
-# The new way is to define both targetPkgs and multiPkgs. These two are
-# functions which get a pkgs environment supplied and should then return a list
-# of packages based this environment.
-# For example: targetPkgs = pkgs: [ pkgs.nmap ];
-#
-# All packages (most likeley programs) placed in targetPkgs will only be
-# installed once--matching the hosts architecture (64bit on x86_64 and 32bit on
-# x86). These packages will populate the chroot directory tree.
-#
-# Packages (most likeley libraries) defined in multiPkgs will be installed once
-# on x86 systems and twice on x86_64 systems.
-# On x86 they will just be merge with the packages defined in targetPkgs.
-# On x86_64 they will be added to targetPkgs and in addition their 32bit
-# versions will also be installed. The final directory should look as follows:
-# /lib will include 32bit libraries from multiPkgs
-# /lib32 will link to /lib
-# /lib64 will include 64bit libraries from multiPkgs and targetPkgs
-# /x86 will contain a complete 32bit environment composed by multiPkgs
+{ stdenv } : { env } :
 
 let
-  is64Bit       = system == "x86_64-linux";
-  # enable multi builds on x86_64 hosts if pakgs_target/multi are defined
-  isMultiBuild  = is64Bit && targetPkgs != null;
-  isTargetBuild = !isMultiBuild;
-
-  # list of packages (usually programs) which will only be installed for the
-  # hosts architecture
-  targetPaths = if targetPkgs == null
-                  then pkgs
-                  else targetPkgs nixpkgs ++ multiPkgs nixpkgs;
-
-  # list of pckages which should be build for both x86 and x86_64 on x86_64
-  # systems
-  multiPaths = if isMultiBuild
-                  then multiPkgs nixpkgs_i686
-                  else [];
-
-  # base packages of the chroot
-  # these match the hosts architecture, gcc/glibc_multi will be choosen
-  # on multi builds
-  choosenGcc = if isMultiBuild then gcc_multi else gcc;
-  basePkgs =
-    [ (if isMultiBuild then glibc_multi else glibc)
-      choosenGcc
-      bashInteractive coreutils less shadow su
-      gawk diffutils findutils gnused gnugrep
-      gnutar gzip bzip2 xz
-    ];
-
-  # Compose a global profile for the chroot environment
-  profilePkg = nixpkgs.stdenv.mkDerivation {
-    name         = "${name}-chrootenv-profile";
-    buildCommand = ''
-      mkdir -p $out/etc
-      cat >> $out/etc/profile << "EOF"
-      export PS1='${name}-chrootenv:\u@\h:\w\$ '
-      ${profile}
-      EOF
-    '';
-  };
-
-  # Composes a /usr like directory structure
-  staticUsrProfileTarget = buildEnv {
-    name = "system-profile-target";
-    paths = basePkgs ++ [ profilePkg ] ++ targetPaths;
-  };
-
-  staticUsrProfileMulti = buildEnv {
-    name = "system-profile-multi";
-    paths = multiPaths;
-  };
-
   # References to shell scripts that set up or tear down the environment
   initSh    = ./init.sh.in;
   mountSh   = ./mount.sh.in;
@@ -99,89 +8,16 @@ let
   umountSh  = ./umount.sh.in;
   destroySh = ./destroy.sh.in;
 
-  linkProfile = profile: ''
-    for i in ${profile}/{etc,bin,sbin,share,var}; do
-        if [ -x "$i" ]
-        then
-            ln -s "$i"
-        fi
-    done
-  '';
-
-  # the target profile is the actual profile that will be used for the chroot
-  setupTargetProfile = ''
-    ${linkProfile staticUsrProfileTarget}
-    ${setupLibDirs}
-
-    mkdir -m0755 usr
-    cd usr
-    ${linkProfile staticUsrProfileTarget}
-    ${setupLibDirs}
-    cd ..
-  '';
-
-  # this will happen on x86_64 host:
-  # /x86         -> links to the whole profile defined by multiPaths
-  # /lib, /lib32 -> links to 32bit binaries
-  # /lib64       -> links to 64bit binaries
-  # /usr/lib*    -> same as above
-  setupMultiProfile = if isTargetBuild then "" else ''
-    mkdir -m0755 x86
-    cd x86
-    ${linkProfile staticUsrProfileMulti}
-    cd ..
-  '';
-
-  setupLibDirs = if isTargetBuild then setupLibDirs_target
-                                  else setupLibDirs_multi;
-
-  # setup library paths only for the targeted architecture
-  setupLibDirs_target = ''
-    mkdir -m0755 lib
-
-    # copy content of targetPaths
-    cp -rsf ${staticUsrProfileTarget}/lib/* lib/
-  '';
-
-  # setup /lib, /lib32 and /lib64
-  setupLibDirs_multi = ''
-    mkdir -m0755 lib
-    mkdir -m0755 lib64
-    ln -s lib lib32
-
-    # copy glibc stuff
-    cp -rsf ${staticUsrProfileTarget}/lib/32/* lib/
-
-    # copy content of multiPaths (32bit libs)
-    cp -rsf ${staticUsrProfileMulti}/lib/* lib/
-
-    # copy content of targetPaths (64bit libs)
-    cp -rsf ${staticUsrProfileTarget}/lib/* lib64/
-
-    # most 64bit only libs put their stuff into /lib
-    # some pkgs (like gcc_multi) put 32bit libs into and /lib 64bit libs into /lib64
-    # by overwriting these we will hopefully catch all these cases
-    # in the end /lib should only contain 32bit and /lib64 only 64bit libs
-    cp -rsf ${staticUsrProfileTarget}/lib64/* lib64/
-
-    # copy gcc libs (and may overwrite exitsting wrongly placed libs)
-    cp -rsf ${choosenGcc.cc}/lib/*   lib/
-    cp -rsf ${choosenGcc.cc}/lib64/* lib64/
-  '';
+  name = env.pname;
 
 in stdenv.mkDerivation {
-  name         = "${name}-chrootenv";
+  name = "${name}-chrootenv";
+  preferLocalBuild = true;
   buildCommand = ''
-    mkdir -p "$out/sw"
-    cd "$out/sw"
-    ${setupTargetProfile}
-    ${setupMultiProfile}
-    cd ..
+    mkdir -p $out/bin
+    cd $out/bin
 
-    mkdir -p bin
-    cd bin
-
-    sed -e "s|@chrootEnv@|$out|g" \
+    sed -e "s|@chrootEnv@|${env}|g" \
         -e "s|@name@|${name}|g" \
         -e "s|@shell@|${stdenv.shell}|g" \
         ${initSh} > init-${name}-chrootenv
@@ -202,18 +38,10 @@ in stdenv.mkDerivation {
         ${umountSh} > umount-${name}-chrootenv
     chmod +x umount-${name}-chrootenv
 
-    sed -e "s|@chrootEnv@|$out|g" \
+    sed -e "s|@chrootEnv@|${env}|g" \
         -e "s|@shell@|${stdenv.shell}|g" \
         -e "s|@name@|${name}|g" \
         ${destroySh} > destroy-${name}-chrootenv
     chmod +x destroy-${name}-chrootenv
-
-    cd ..
-
-    cd "$out/sw"
-    ${extraBuildCommands}
-    cd "$out/sw"
-    ${if isMultiBuild then extraBuildCommandsMulti else ""}
-    cd ..
   '';
 }
diff --git a/pkgs/build-support/build-fhs-chrootenv/destroy.sh.in b/pkgs/build-support/build-fhs-chrootenv/destroy.sh.in
index 8ddf350913eb..015f742d85a5 100644
--- a/pkgs/build-support/build-fhs-chrootenv/destroy.sh.in
+++ b/pkgs/build-support/build-fhs-chrootenv/destroy.sh.in
@@ -6,9 +6,9 @@ chrootenvDest=/run/chrootenv/@name@
 rmdir $chrootenvDest/{dev,nix/store,nix,proc,sys,host-etc,home,var,run,tmp}
 
 # Remove symlinks to the software that should be part of the chroot system profile
-for i in @chrootEnv@/sw/*
+for i in @chrootEnv@/*
 do
-    if [ "$i" != "@chrootEnv@/sw/etc" ] && [ "$i" != "@chrootEnv@/sw/var" ]
+    if [ "$i" != "@chrootEnv@/etc" ] && [ "$i" != "@chrootEnv@/var" ]
     then
         rm $chrootenvDest/$(basename $i)
     fi
diff --git a/pkgs/build-support/build-fhs-chrootenv/env.nix b/pkgs/build-support/build-fhs-chrootenv/env.nix
new file mode 100644
index 000000000000..b810adefab16
--- /dev/null
+++ b/pkgs/build-support/build-fhs-chrootenv/env.nix
@@ -0,0 +1,177 @@
+{ nixpkgs, nixpkgs_i686, system
+} :
+{ name, pkgs ? [], profile ? ""
+, targetPkgs ? null, multiPkgs ? null
+, extraBuildCommands ? "", extraBuildCommandsMulti ? ""
+}:
+
+assert pkgs       != []   -> targetPkgs == null && multiPkgs == null;
+assert targetPkgs != null -> multiPkgs  != null;
+assert multiPkgs  != null -> targetPkgs != null;
+assert targetPkgs != null -> pkgs       == [];
+
+
+# HOWTO:
+# If pkgs is defined buildFHSEnv will run in legacy mode. This means
+# it will build all pkgs contained in pkgs and basePkgs and then just merge
+# all of their contents together via buildEnv.
+#
+# The new way is to define both targetPkgs and multiPkgs. These two are
+# functions which get a pkgs environment supplied and should then return a list
+# of packages based this environment.
+# For example: targetPkgs = pkgs: [ pkgs.nmap ];
+#
+# All packages (most likely programs) placed in targetPkgs will only be
+# installed once--matching the hosts architecture (64bit on x86_64 and 32bit on
+# x86). These packages will populate the chroot directory tree.
+#
+# Packages (most likeley libraries) defined in multiPkgs will be installed once
+# on x86 systems and twice on x86_64 systems.
+# On x86 they will just be merge with the packages defined in targetPkgs.
+# On x86_64 they will be added to targetPkgs and in addition their 32bit
+# versions will also be installed. The final directory should look as follows:
+# /lib will include 32bit libraries from multiPkgs
+# /lib32 will link to /lib
+# /lib64 will include 64bit libraries from multiPkgs and targetPkgs
+# /x86 will contain a complete 32bit environment composed by multiPkgs
+
+let
+  is64Bit       = system == "x86_64-linux";
+  # enable multi builds on x86_64 hosts if pakgs_target/multi are defined
+  isMultiBuild  = is64Bit && targetPkgs != null;
+  isTargetBuild = !isMultiBuild;
+
+  # list of packages (usually programs) which will only be installed for the
+  # hosts architecture
+  targetPaths = if targetPkgs == null
+                  then pkgs
+                  else targetPkgs nixpkgs ++ multiPkgs nixpkgs;
+
+  # list of pckages which should be build for both x86 and x86_64 on x86_64
+  # systems
+  multiPaths = if isMultiBuild
+                  then multiPkgs nixpkgs_i686
+                  else [];
+
+  # base packages of the chroot
+  # these match the hosts architecture, gcc/glibc_multi will be choosen
+  # on multi builds
+  chosenGcc = if isMultiBuild then nixpkgs.gcc_multi else nixpkgs.gcc;
+  basePkgs = with nixpkgs;
+    [ (if isMultiBuild then glibc_multi else glibc)
+      chosenGcc
+      bashInteractive coreutils less shadow su
+      gawk diffutils findutils gnused gnugrep
+      gnutar gzip bzip2 xz
+    ];
+
+  # Compose a global profile for the chroot environment
+  profilePkg = nixpkgs.stdenv.mkDerivation {
+    name         = "${name}-chrootenv-profile";
+    buildCommand = ''
+      mkdir -p $out/etc
+      cat >> $out/etc/profile << "EOF"
+      export PS1='${name}-chrootenv:\u@\h:\w\$ '
+      ${profile}
+      EOF
+    '';
+  };
+
+  # Composes a /usr like directory structure
+  staticUsrProfileTarget = nixpkgs.buildEnv {
+    name = "system-profile-target";
+    paths = basePkgs ++ [ profilePkg ] ++ targetPaths;
+  };
+
+  staticUsrProfileMulti = nixpkgs.buildEnv {
+    name = "system-profile-multi";
+    paths = multiPaths;
+  };
+
+  linkProfile = profile: ''
+    for i in ${profile}/{etc,bin,sbin,share,var}; do
+        if [ -x "$i" ]
+        then
+            ln -s "$i"
+        fi
+    done
+  '';
+
+  # the target profile is the actual profile that will be used for the chroot
+  setupTargetProfile = ''
+    ${linkProfile staticUsrProfileTarget}
+    ${setupLibDirs}
+
+    mkdir -m0755 usr
+    cd usr
+    ${linkProfile staticUsrProfileTarget}
+    ${setupLibDirs}
+    cd ..
+  '';
+
+  # this will happen on x86_64 host:
+  # /x86         -> links to the whole profile defined by multiPaths
+  # /lib, /lib32 -> links to 32bit binaries
+  # /lib64       -> links to 64bit binaries
+  # /usr/lib*    -> same as above
+  setupMultiProfile = if isTargetBuild then "" else ''
+    mkdir -m0755 x86
+    cd x86
+    ${linkProfile staticUsrProfileMulti}
+    cd ..
+  '';
+
+  setupLibDirs = if isTargetBuild then setupLibDirs_target
+                                  else setupLibDirs_multi;
+
+  # setup library paths only for the targeted architecture
+  setupLibDirs_target = ''
+    mkdir -m0755 lib
+
+    # copy content of targetPaths
+    cp -rsf ${staticUsrProfileTarget}/lib/* lib/
+  '';
+
+  # setup /lib, /lib32 and /lib64
+  setupLibDirs_multi = ''
+    mkdir -m0755 lib
+    mkdir -m0755 lib64
+    ln -s lib lib32
+
+    # copy glibc stuff
+    cp -rsf ${staticUsrProfileTarget}/lib/32/* lib/
+
+    # copy content of multiPaths (32bit libs)
+    cp -rsf ${staticUsrProfileMulti}/lib/* lib/
+
+    # copy content of targetPaths (64bit libs)
+    cp -rsf ${staticUsrProfileTarget}/lib/* lib64/
+
+    # most 64bit only libs put their stuff into /lib
+    # some pkgs (like gcc_multi) put 32bit libs into and /lib 64bit libs into /lib64
+    # by overwriting these we will hopefully catch all these cases
+    # in the end /lib should only contain 32bit and /lib64 only 64bit libs
+    cp -rsf ${staticUsrProfileTarget}/lib64/* lib64/
+
+    # copy gcc libs (and may overwrite exitsting wrongly placed libs)
+    cp -rsf ${chosenGcc.cc}/lib/*   lib/
+    cp -rsf ${chosenGcc.cc}/lib64/* lib64/
+  '';
+
+in nixpkgs.stdenv.mkDerivation {
+  name         = "${name}-fhs";
+  buildCommand = ''
+    mkdir -p $out
+    cd $out
+    ${setupTargetProfile}
+    ${setupMultiProfile}
+    cd $out
+    ${extraBuildCommands}
+    cd $out
+    ${if isMultiBuild then extraBuildCommandsMulti else ""}
+  '';
+  preferLocalBuild = true;
+  passthru = {
+    pname = name;
+  };
+}
diff --git a/pkgs/build-support/build-fhs-chrootenv/init.sh.in b/pkgs/build-support/build-fhs-chrootenv/init.sh.in
index 079ec09d60f7..f3bdad85fa74 100644
--- a/pkgs/build-support/build-fhs-chrootenv/init.sh.in
+++ b/pkgs/build-support/build-fhs-chrootenv/init.sh.in
@@ -6,9 +6,9 @@ chrootenvDest=/run/chrootenv/@name@
 mkdir -p $chrootenvDest/{nix/store,dev,proc,sys,host-etc,home,var,run}
 
 # Symlink the software that should be part of the chroot system profile
-for i in @chrootEnv@/sw/*
+for i in @chrootEnv@/*
 do
-    if [ "$i" != "@chrootEnv@/sw/etc" ] && [ "$i" != "@chrootEnv@/sw/var" ]
+    if [ "$i" != "@chrootEnv@/etc" ] && [ "$i" != "@chrootEnv@/var" ]
     then
         ln -s "$i" "$chrootenvDest"
     fi
@@ -18,7 +18,7 @@ done
 
 mkdir $chrootenvDest/etc
 
-for i in @chrootEnv@/sw/etc/*
+for i in @chrootEnv@/etc/*
 do
     ln -s "$i" $chrootenvDest/etc
 done
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 000000000000..857ccd58cd7f
--- /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 000000000000..57864b4934bb
--- /dev/null
+++ b/pkgs/build-support/build-fhs-userenv/default.nix
@@ -0,0 +1,37 @@
+{ 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 ];
+  preferLocalBuild = true;
+  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/games/steam/chrootenv.nix b/pkgs/games/steam/chrootenv.nix
index 404eaf4ce9f2..7ebd07752920 100644
--- a/pkgs/games/steam/chrootenv.nix
+++ b/pkgs/games/steam/chrootenv.nix
@@ -1,10 +1,10 @@
-{ buildFHSChrootEnv, config }:
+{ buildFHSUserEnv, config }:
 
-buildFHSChrootEnv {
+buildFHSUserEnv {
   name = "steam";
 
   targetPkgs = pkgs:
-    [ pkgs.steam
+    [ pkgs.steamOriginal
       pkgs.corefonts
       pkgs.curl
       pkgs.dbus
@@ -69,4 +69,6 @@ buildFHSChrootEnv {
     export LD_LIBRARY_PATH=/run/opengl-driver/lib:/run/opengl-driver-32/lib:/lib:/lib32:/lib64
     export PATH=$PATH:/usr/bin:/usr/sbin
   '';
+
+  runScript = "exec steam";
 }
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 4ac6232575d2..37b5f45412b0 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -260,17 +260,25 @@ let
     inherit (pkgs) runCommand perl;
   };
 
-  buildFHSChrootEnv = import ../build-support/build-fhs-chrootenv {
-    inherit buildEnv system;
-    inherit stdenv glibc glibc_multi glibcLocales;
-    inherit bashInteractive coreutils less shadow su;
-    inherit gawk gcc gcc_multi diffutils findutils gnused gnugrep;
-    inherit gnutar gzip bzip2 xz;
-
+  buildFHSEnv = callPackage ../build-support/build-fhs-chrootenv/env.nix {
     nixpkgs      = pkgs;
     nixpkgs_i686 = pkgsi686Linux;
   };
 
+  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;
@@ -12145,9 +12153,17 @@ let
 
   stardust = callPackage ../games/stardust {};
 
-  steam = callPackage ../games/steam {};
+  steamOriginal = callPackage ../games/steam { };
+
+  steam = callPackage ../games/steam/chrootenv.nix { };
 
-  steamChrootEnv = callPackage ../games/steam/chrootenv.nix { };
+  steamChrootEnv = steam.overrideDerivation (args: {
+    buildCommand = ''
+      ${args.buildCommand}
+      echo >&2 "'steamChrootEnv' is replaced with 'steam' now"
+      echo >&2 "You now need just to run 'steam' without root rights"
+    '';
+  });
 
   stuntrally = callPackage ../games/stuntrally { };