tests: pack-relocatable: Ensure commands can run in the current namespace.
[jackhill/guix/guix.git] / tests / guix-pack-relocatable.sh
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"