| 1 | # GNU Guix --- Functional package management for GNU |
| 2 | # Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> |
| 3 | # Copyright © 2020 Eric Bavier <bavier@posteo.net> |
| 4 | # |
| 5 | # This file is part of GNU Guix. |
| 6 | # |
| 7 | # GNU Guix is free software; you can redistribute it and/or modify it |
| 8 | # under the terms of the GNU General Public License as published by |
| 9 | # the Free Software Foundation; either version 3 of the License, or (at |
| 10 | # your option) any later version. |
| 11 | # |
| 12 | # GNU Guix is distributed in the hope that it will be useful, but |
| 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | # GNU General Public License for more details. |
| 16 | # |
| 17 | # You should have received a copy of the GNU General Public License |
| 18 | # along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. |
| 19 | |
| 20 | # |
| 21 | # Test the 'guix pack --relocatable' using the external store, if any. |
| 22 | # |
| 23 | |
| 24 | guix pack --version |
| 25 | |
| 26 | # 'guix pack --relocatable' requires a C compiler and libc.a, which our |
| 27 | # bootstrap binaries don't provide. To make the test relatively inexpensive, |
| 28 | # run it on the user's global store if possible, on the grounds that binaries |
| 29 | # may already be there or can be built or downloaded inexpensively. |
| 30 | |
| 31 | storedir="`guile -c '(use-modules (guix config))(display %storedir)'`" |
| 32 | localstatedir="`guile -c '(use-modules (guix config))(display %localstatedir)'`" |
| 33 | NIX_STORE_DIR="$storedir" |
| 34 | GUIX_DAEMON_SOCKET="$localstatedir/guix/daemon-socket/socket" |
| 35 | export NIX_STORE_DIR GUIX_DAEMON_SOCKET |
| 36 | |
| 37 | if ! guile -c '(use-modules (guix)) (exit (false-if-exception (open-connection)))' |
| 38 | then |
| 39 | exit 77 |
| 40 | fi |
| 41 | |
| 42 | # Attempt to run the given command in a namespace where the store is |
| 43 | # invisible. This makes sure the presence of the store does not hide |
| 44 | # problems. |
| 45 | run_without_store () |
| 46 | { |
| 47 | if unshare -r true # Are user namespaces supported? |
| 48 | then |
| 49 | # Run that relocatable executable in a user namespace where we "erase" |
| 50 | # the store by mounting an empty file system on top of it. That way, |
| 51 | # we exercise the wrapper code that creates the user namespace and |
| 52 | # bind-mounts the store. |
| 53 | unshare -mrf sh -c 'mount -t tmpfs -o ro none "$NIX_STORE_DIR"; '"$*" |
| 54 | else |
| 55 | # Run the relocatable program in the current namespaces. This is a |
| 56 | # weak test because we're going to access store items from the host |
| 57 | # store. |
| 58 | sh -c "$*" |
| 59 | fi |
| 60 | } |
| 61 | |
| 62 | # Wait for the given file to show up. Error out if it doesn't show up in a |
| 63 | # timely fashion. |
| 64 | wait_for_file () |
| 65 | { |
| 66 | i=0 |
| 67 | while ! test -f "$1" && test $i -lt 20 |
| 68 | do |
| 69 | sleep 0.3 |
| 70 | i=`expr $i + 1` |
| 71 | done |
| 72 | test -f "$1" |
| 73 | } |
| 74 | |
| 75 | test_directory="`mktemp -d`" |
| 76 | export test_directory |
| 77 | trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT |
| 78 | |
| 79 | if unshare -r true |
| 80 | then |
| 81 | # Test the 'userns' execution engine. |
| 82 | tarball="`guix pack -R -S /Bin=bin sed`" |
| 83 | (cd "$test_directory"; tar xvf "$tarball") |
| 84 | |
| 85 | run_without_store "$test_directory/Bin/sed" --version > "$test_directory/output" |
| 86 | grep 'GNU sed' "$test_directory/output" |
| 87 | |
| 88 | # Same with an explicit engine. |
| 89 | run_without_store GUIX_EXECUTION_ENGINE="userns" \ |
| 90 | "$test_directory/Bin/sed" --version > "$test_directory/output" |
| 91 | grep 'GNU sed' "$test_directory/output" |
| 92 | |
| 93 | # Check whether the exit code is preserved. |
| 94 | ! run_without_store "$test_directory/Bin/sed" --does-not-exist |
| 95 | |
| 96 | chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* |
| 97 | else |
| 98 | echo "'userns' execution tests skipped" >&2 |
| 99 | fi |
| 100 | |
| 101 | case "`uname -m`" in |
| 102 | x86_64|i?86) |
| 103 | # Try '-RR' and PRoot. |
| 104 | tarball="`guix pack -RR -S /Bin=bin sed`" |
| 105 | tar tvf "$tarball" | grep /bin/proot |
| 106 | (cd "$test_directory"; tar xf "$tarball") |
| 107 | run_without_store GUIX_EXECUTION_ENGINE="proot" \ |
| 108 | "$test_directory/Bin/sed" --version > "$test_directory/output" |
| 109 | grep 'GNU sed' "$test_directory/output" |
| 110 | |
| 111 | # Now with fakechroot. |
| 112 | run_without_store GUIX_EXECUTION_ENGINE="fakechroot" \ |
| 113 | "$test_directory/Bin/sed" --version > "$test_directory/output" |
| 114 | grep 'GNU sed' "$test_directory/output" |
| 115 | unset GUIX_EXECUTION_ENGINE |
| 116 | |
| 117 | chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* |
| 118 | |
| 119 | if unshare -r true |
| 120 | then |
| 121 | # Check whether the store contains everything it should. Check |
| 122 | # once when erasing $STORE_PARENT ("/gnu") and once when erasing |
| 123 | # $NIX_STORE_DIR ("/gnu/store"). |
| 124 | tarball="`guix pack -RR -S /bin=bin bash-minimal`" |
| 125 | (cd "$test_directory"; tar xf "$tarball") |
| 126 | |
| 127 | STORE_PARENT="`dirname $NIX_STORE_DIR`" |
| 128 | export STORE_PARENT |
| 129 | |
| 130 | for engine in userns proot fakechroot |
| 131 | do |
| 132 | for i in $(guix gc -R $(guix build bash-minimal | grep -v -e '-doc$')) |
| 133 | do |
| 134 | unshare -mrf sh -c "mount -t tmpfs none \"$NIX_STORE_DIR\"; GUIX_EXECUTION_ENGINE=$engine $test_directory/bin/sh -c 'echo $NIX_STORE_DIR/*'" | grep $(basename $i) |
| 135 | unshare -mrf sh -c "mount -t tmpfs none \"$STORE_PARENT\"; GUIX_EXECUTION_ENGINE=$engine $test_directory/bin/sh -c 'echo $NIX_STORE_DIR/*'" | grep $(basename $i) |
| 136 | done |
| 137 | done |
| 138 | |
| 139 | chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* |
| 140 | fi |
| 141 | ;; |
| 142 | *) |
| 143 | echo "skipping PRoot and Fakechroot tests" >&2 |
| 144 | ;; |
| 145 | esac |
| 146 | |
| 147 | if unshare -r true |
| 148 | then |
| 149 | # Check what happens if the wrapped binary forks and leaves child |
| 150 | # processes behind, like a daemon. The root file system should remain |
| 151 | # available to those child processes. See <https://bugs.gnu.org/44261>. |
| 152 | cat > "$test_directory/manifest.scm" <<EOF |
| 153 | (use-modules (guix)) |
| 154 | |
| 155 | (define daemon |
| 156 | (program-file "daemon" |
| 157 | #~(begin |
| 158 | (use-modules (ice-9 match) |
| 159 | (ice-9 ftw)) |
| 160 | |
| 161 | (call-with-output-file "parent-store" |
| 162 | (lambda (port) |
| 163 | (write (scandir (ungexp (%store-prefix))) |
| 164 | port))) |
| 165 | |
| 166 | (match (primitive-fork) |
| 167 | (0 (sigaction SIGHUP (const #t)) |
| 168 | (call-with-output-file "pid" |
| 169 | (lambda (port) |
| 170 | (display (getpid) port))) |
| 171 | (pause) |
| 172 | (call-with-output-file "child-store" |
| 173 | (lambda (port) |
| 174 | (write (scandir (ungexp (%store-prefix))) |
| 175 | port)))) |
| 176 | (_ #t))))) |
| 177 | |
| 178 | (define package |
| 179 | (computed-file "package" |
| 180 | #~(let ((out (ungexp output))) |
| 181 | (mkdir out) |
| 182 | (mkdir (string-append out "/bin")) |
| 183 | (symlink (ungexp daemon) |
| 184 | (string-append out "/bin/daemon"))))) |
| 185 | |
| 186 | (manifest (list (manifest-entry |
| 187 | (name "daemon") |
| 188 | (version "0") |
| 189 | (item package)))) |
| 190 | EOF |
| 191 | |
| 192 | tarball="$(guix pack -S /bin=bin -R -m "$test_directory/manifest.scm")" |
| 193 | (cd "$test_directory"; tar xf "$tarball") |
| 194 | |
| 195 | # Run '/bin/daemon', which forks, then wait for the child, send it SIGHUP |
| 196 | # so that it dumps its view of the store, and make sure the child and |
| 197 | # parent both see the same store contents. |
| 198 | (cd "$test_directory"; run_without_store ./bin/daemon) |
| 199 | wait_for_file "$test_directory/pid" |
| 200 | kill -HUP $(cat "$test_directory/pid") |
| 201 | wait_for_file "$test_directory/child-store" |
| 202 | diff -u "$test_directory/parent-store" "$test_directory/child-store" |
| 203 | |
| 204 | chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* |
| 205 | fi |
| 206 | |
| 207 | # Ensure '-R' works with outputs other than "out". |
| 208 | tarball="`guix pack -R -S /share=share groff:doc`" |
| 209 | (cd "$test_directory"; tar xf "$tarball") |
| 210 | test -d "$test_directory/share/doc/groff/html" |
| 211 | chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* |
| 212 | |
| 213 | # Ensure '-R' applies to propagated inputs. Failing to do that, it would fail |
| 214 | # with a profile collision error in this case because 'python-scipy' |
| 215 | # propagates 'python-numpy'. See <https://bugs.gnu.org/42510>. |
| 216 | guix pack -RR python-numpy python-scipy --no-grafts -n |
| 217 | |
| 218 | # Check that packages that mix executable and support files (e.g. git) in the |
| 219 | # "binary" directories still work after wrapped. |
| 220 | cat >"$test_directory/manifest.scm" <<'EOF' |
| 221 | (use-modules (guix) (guix profiles) (guix search-paths) |
| 222 | (gnu packages bootstrap)) |
| 223 | (manifest |
| 224 | (list (manifest-entry |
| 225 | (name "test") (version "0") |
| 226 | (item (file-union "test" |
| 227 | `(("bin/hello" |
| 228 | ,(program-file |
| 229 | "hello" |
| 230 | #~(begin |
| 231 | (add-to-load-path (getenv "HELLO_EXEC_PATH")) |
| 232 | (display (load-from-path "msg"))(newline)) |
| 233 | #:guile %bootstrap-guile)) |
| 234 | ("libexec/hello/msg" |
| 235 | ,(plain-file "msg" "42"))))) |
| 236 | (search-paths |
| 237 | (list (search-path-specification |
| 238 | (variable "HELLO_EXEC_PATH") |
| 239 | (files '("libexec/hello")) |
| 240 | (separator #f))))))) |
| 241 | EOF |
| 242 | tarball="`guix pack -RR -S /opt= -m $test_directory/manifest.scm`" |
| 243 | (cd "$test_directory"; tar xvf "$tarball") |
| 244 | ( export GUIX_PROFILE=$test_directory/opt |
| 245 | . $GUIX_PROFILE/etc/profile |
| 246 | run_without_store "$test_directory/opt/bin/hello" > "$test_directory/output" ) |
| 247 | cat "$test_directory/output" |
| 248 | test "`cat $test_directory/output`" = "42" |