Commit ec5b0d84 authored by Pavel Emelyanov's avatar Pavel Emelyanov

test: Rework the ext mount plugin test

The existing set of shell scripts do hard-to-debug things and mess
with the root filesystem. We can make it better.

First, not to play with the system / the process that will be run in
a new mount namespace is statically compiled .c file. And this "init"
does a very simple thing -- waits for SIGTERM and check that the
given filepath contains the given string.

Second, the namespace's root will be some subdir, instead of system
/ bind-mount-ed into a subdir. This makes it easier to keep things
together and makes 100% sure the external bind mount cannot be
accessed by custom path.
Signed-off-by: 's avatarPavel Emelyanov <xemul@parallels.com>
parent b508ebcb
all: ext-mount.so
all: ext-mount.so ns_init
ext-mount.so: ext-mount.c
gcc -g -Werror -Wall -shared -nostartfiles ext-mount.c -o ext-mount.so -iquote ../../../include -fPIC
ns_init: ns_init.o
gcc -static $< -o $@
ns_init.o: ns_init.c
gcc -c $< -o $@
run: all
./run.sh
......@@ -34,9 +34,9 @@ int cr_plugin_dump_ext_mount(char *mountpoint, int id)
return -ENOTSUP;
}
dst = getenv("dfile");
dst = getenv("EMP_MOUNTPOINT");
if (!dst) {
pr_err("No dfile env\n");
pr_err("No EMP_MOUNTPOINT env\n");
return -1;
}
......@@ -74,9 +74,9 @@ int cr_plugin_restore_ext_mount(int id, char *mountpoint, char *old_root, int *i
}
close(fd);
src_file = getenv("sfpath");
src_file = getenv("EMP_ROOT_P");
if (!src_file) {
pr_err("Can't get sfpath env\n");
pr_err("Can't get EMP_ROOT_P env\n");
return -1;
}
......
#define _GNU_SOURCE
#include <sched.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mount.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
static void sigh(int sig)
{
}
int main(int argc, char **argv)
{
int start[2];
char res;
pid_t pid;
/*
* Usage:
* run <pidfile> <root> <log-file-name> <file-to-check> <contents-to-check>
*/
if (getpid() == 1) {
int fd;
struct sigaction sa = {};
sigset_t mask;
if (setsid() == -1) {
fprintf(stderr, "setsid: %m\n");
return 1;
}
sa.sa_handler = sigh;
sigaction(SIGTERM, &sa, NULL);
if (chdir(argv[2]))
return 1;
fd = open(argv[3], O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
if (fd < 0)
return 1;
dup2(fd, 1);
dup2(fd, 2);
close(fd);
close(0);
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
fprintf(stderr, "mount(/, S_REC | MS_PRIVATE)): %m");
return 1;
}
mkdir("oldm");
if (pivot_root(".", "./oldm") < 0)
return 1;
umount2("/oldm", MNT_DETACH);
mkdir("/proc");
if (mount("zdtm_proc", "/proc", "proc", 0, NULL)) {
fprintf(stderr, "mount(/proc): %m");
return 1;
}
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);
fd = atoi(argv[1]);
write(fd, "!", 1);
close(fd);
sigemptyset(&mask);
sigsuspend(&mask);
printf("Woken UP\n");
printf("Reading %s for [%s]\n", argv[4], argv[5]);
{
FILE *f;
char buf[128];
f = fopen(argv[4], "r");
if (!f)
perror("No file with message");
else {
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), f);
fclose(f);
printf("Got [%s]\n", buf);
if (!strcmp(buf, argv[5]))
printf("PASS\n");
}
}
exit(0);
}
if (unshare(CLONE_NEWNS | CLONE_NEWPID))
return 1;
pipe(start);
pid = fork();
if (pid == 0) {
char *nargv[7], aux[10];
close(start[0]);
sprintf(aux, "%d", start[1]);
nargv[0] = argv[0];
nargv[1] = aux;
nargv[2] = argv[2];
nargv[3] = argv[3];
nargv[4] = argv[4];
nargv[5] = argv[5];
nargv[6] = NULL;
execv(argv[0], nargv);
exit(0);
}
close(start[1]);
res = 'F';
read(start[0], &res, 1);
if (res != '!') {
printf("Failed to start\n");
return 1;
}
printf("Container w/ tests started\n");
{
FILE *pidf;
pidf = fopen(argv[1], "w");
fprintf(pidf, "%d", pid);
fclose(pidf);
}
return 0;
}
......@@ -7,51 +7,100 @@ function fail {
exit 1
}
make || fail "Can't compile library"
make || fail "Can't compile library or ns init"
criu="../../../criu"
finf="finish"
outf="run_output"
pidfile="pid_wait"
tempd="temp_dir"
sfile="source_file"
tdir="test_dir"
dfile="dest_file"
mesg="msg-$((RANDOM % 128))"
export finf
export outf
export pidfile
export sfile
export dfile
export tempd
export mesg
export tdir
mkdir dump/
mkdir $tdir
mount --bind "/" ${tdir} || fail "Can't bind root"
mount --make-rprivate "${tdir}"
unshare --mount ./run_ns.sh || fail "Can't unshare ns"
cat $pidfile
sleep 2
$criu dump -t $(cat $pidfile) -D dump/ -o dump.log -v4 --lib $(pwd) && echo OK
sleep 1
mkdir $tempd
mount -t tmpfs none "$tempd"
echo "$mesg" > "$tempd/$sfile"
sfpath="/$(pwd)/$tempd/$sfile"
export sfpath
$criu restore -D dump/ -o restore.log -v4 --lib $(pwd) --root "$(pwd)/$tdir" -d && echo OK
umount "$tempd"
touch $finf
sleep 1 # Shitty, but...
tail $outf
umount ${tdir}
# New root for namespace
NSROOT="nsroot"
# External file with contents (exported for plugin.restore)
EMP_ROOT="external_file"
export EMP_ROOT_P="$(pwd)/$EMP_ROOT"
# Internal file as seen from namespace (exported for plugin.dump)
export EMP_MOUNTPOINT="file"
# Message in a file to check visibility
FMESSAGE="tram-pam-pam"
# Binary of namespace's init
NS_INIT="ns_init"
# File with namespace init pid
PIDF="pidf"
start_ns()
{
#
# Prepare the namespace's FS layout
#
mkdir $NSROOT
echo -n "$FMESSAGE" > "$EMP_ROOT"
mount --bind "$NSROOT" "$NSROOT"
mount --make-private "$NSROOT"
touch "$NSROOT/$EMP_MOUNTPOINT"
mount --bind "$EMP_ROOT" "$NSROOT/$EMP_MOUNTPOINT" || fail "Can't prepare fs for ns"
#
# Start the namespace's init
#
cp $NS_INIT "$NSROOT/"
"./$NSROOT/$NS_INIT" "$PIDF" "$NSROOT" "log" "$EMP_MOUNTPOINT" "$FMESSAGE" || fail "Can't start namespace"
umount "$NSROOT/$EMP_MOUNTPOINT"
echo "Namespace started, pid $(cat $PIDF)"
}
stop_ns()
{
#
# Kill the init
#
kill -TERM $(cat $PIDF)
sleep 2 # Shitty, but...
umount $NSROOT
if [ -z "$1" ]; then
rm -f "$NSROOT/log"
else
mv "$NSROOT/log" "$1"
fi
rm -f "$PIDF" "$EMP_ROOT" "$NSROOT/$NS_INIT" "$NSROOT/log" "$NSROOT/$EMP_MOUNTPOINT"
rmdir "$NSROOT/oldm"
rmdir "$NSROOT/proc"
rmdir "$NSROOT"
}
DDIR="dump"
rm -rf $DDIR
mkdir $DDIR
chk_pass()
{
tail -n1 $1 | fgrep -q "PASS"
}
#
# Test 1: handle external mount with plugin
#
test_plugin()
{
echo "=== Testing how plugin works"
mkdir "$DDIR/plugin/"
start_ns
$criu dump -D "$DDIR/plugin/" -v4 -o "dump.log" --lib=$(pwd) \
-t $(cat pidf) || { stop_ns; return 1; }
$criu restore -D "$DDIR/plugin/" -v4 -o "rstr.log" --lib=$(pwd) \
-d --root="$(pwd)/$NSROOT" --pidfile=$PIDF || { stop_ns; return 1; }
echo "Restored, checking results"
mv "$DDIR/plugin/$PIDF" .
stop_ns "$DDIR/plugin/ns.log"
chk_pass "$DDIR/plugin/ns.log"
}
test_plugin || exit 1
echo "All tests passed"
exit 0
#!/bin/bash
set -x
set -e
odir="mexold"
# finf & outf came from parent
cur="$(pwd)"
function fail {
echo $@
exit 1
}
# Don't mirror further bind mounts in the original namespace
mount --make-rprivate "/"
# Clean previous stuff
rm -rf "$tempd" "$finf" "$outf" "/$odir"
mkdir "$tempd"
touch "$tdir/$cur/$tdir/$dfile"
# Create source file. Make it on a new mountpoint to "hide"
# it in the target mount tree (see below)
mount -t tmpfs none "$tempd"
echo "$mesg" > "$tempd/$sfile"
# Create destination file. It's a bind mount to the source one.
mount --bind "$tempd/$sfile" "$tdir/$cur/$tdir/$dfile"
# Make clean and small mounts set
cd "$tdir"
mkdir "$odir"
pivot_root "." "./$odir"
mount -t proc none "/proc"
umount -lif "/$odir"
# This would show root, proc and the bind mount to some "unknown"
# file. Unknown, since it's on a tempfs mount that is not seen
cat "/proc/self/mountinfo"
set +e
cd "$cur"
# Will be in "logs" so that caller can do "sanity eye-check"
ls
cat "$tempd/$sfile"
cat "$tdir/$dfile"
# Start waiting for C/R on us
# Exec also fixes the maps/exe/fd links relative to new mounts
exec setsid "./run_wait.sh" "$tdir/$dfile" "$mesg" < /dev/null > "$outf" 2>&1 &
#!/bin/bash
echo $$ > $pidfile
echo "My mounts (before)"
cat "/proc/self/mountinfo"
while [ ! -e "$finf" ]; do
echo "WAIT"
sleep 1;
done
echo "My mounts (after)"
cat "/proc/self/mountinfo"
if fgrep "$2" "$1" ; then
echo "PASS"
else
echo "FAIL"
fi
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment