When writing system integration tests it often happens that I want to mount some tmpfses over directories like /etc/postgresql/ or /home, and run the whole script with an unshared mount namespace so that (1) it does not interfere with the real system, and (2) is guaranteed to clean up after itself (unmounting etc.) after it ends in any possible way (including SIGKILL, which breaks usual cleanup methods like “trap”, “finally”, “def tearDown()”, “atexit()” and so on).
In gvfs’ and postgresql-common’s tests, which both have been around for a while, I prepare a set of shell commands in a variable and pipe that into unshare -m sh, but that has some major problems: It doesn’t scale well to large programs, looks rather ugly, breaks syntax highlighting in editors, and it destroys the real stdin, so you cannot e. g. call a “bash -i” in your test for interactively debugging a failed test.
I just changed postgresql-common’s test runner to use unshare/tmpfses as well, and needed a better approach. What I eventually figured out preserves stdin, $0, and $@, and still looks like a normal script (i. e. not just a single big string). It still looks a bit hackish, but I can live with that:
#!/bin/sh set -e # call ourselves through unshare in a way that keeps normal stdin, $0, and CLI args unshare -uim sh -- -c "`tail -n +7 $0`" "$0" "$@" exit $? # unshared program starts here set -e echo "args: $@" echo "mounting tmpfs" mount -n -t tmpfs tmpfs /etc grep /etc /proc/mounts echo "done"
As Unix/Linux’ shebang parsing is rather limited, I didn’t find a way to do something like
#!/usr/bin/env unshare -m sh
If anyone knows a trick which avoids the “tail -n +7″ hack and having to pay attention to passing around “$@”, I’d appreciate a comment how to simplify this.
#1 by Michael on 2012/12/15 - 21:35
Zitieren
Would the following work?
#!/usr/bin/perl -eexec ‘unshare’, ‘-uim’, ‘sh’, @ARGV
#2 by pitti on 2012/12/15 - 22:08
Zitieren
Thanks, nice trick! The whole test suite of postgresql-common is perl, so it wouldn’t matter at all for this. I just wish there would be a similar way with using /bin/sh.
#3 by dp on 2012/12/15 - 23:46
Zitieren
You can avoid the hard coding of the starting lines number using
an hard coded comment, try replacing:
tail -n +7
with
sed -ne ‘/^# unshared program start/,$p’
or for more flexibility
awk ‘/^# unshared program start/,/# unshared program end/’
#4 by Anonymous on 2012/12/16 - 01:37
Zitieren
Assuming you trust the calling process, this seems slightly less ugly than tail and sh -c:
#!/bin/sh
if [ "$1" != "_unshared_" ]; then
exec unshare -m “$0″ _unshared_ “$@”
else
shift
fi
# rest of script
#5 by Jeff Epler on 2012/12/16 - 04:15
Zitieren
#3 has the right idea, but it would be even better if you could use some kind of environmental test instead of an explicit commandline item. second best would be to communicate via the environment instead: exec env UNSHARED=yes unshare -m “$0″ “$@”.
You can also pull this logic out to a script that is read via “.”, so instead of a repeated stanza it’s just
. run-unshared-please
if a qualified path if required.
#6 by Colin Watson on 2012/12/16 - 11:53
Zitieren
I agree with Jeff (#5): use an environment variable and self-re-exec. See os-prober for an example of this.
#7 by Anonymous on 2012/12/16 - 21:43
Zitieren
@Jeff, Colin: Note that either the environment variable or argument approach requires you to trust the caller. Given that, what makes an environment variable preferred?
Pingback: Martin Pitt: Running a script with unshared mount namespace - Bartle Doo