[CRIU] [PATCH] tests: Pipe.c Test Program

Andrey Vagin avagin at openvz.org
Tue Dec 9 02:41:25 PST 2014


From: Saied Kazemi <saied at google.com>

Hi Saied,

This patch adds your test in the criu test system.

Signed-off-by: Andrey Vagin <avagin at openvz.org>
---
 test/Makefile       |   2 +-
 test/pipes/Makefile |  12 +
 test/pipes/pipe.c   | 673 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 686 insertions(+), 1 deletion(-)
 create mode 100644 test/pipes/Makefile
 create mode 100644 test/pipes/pipe.c

diff --git a/test/Makefile b/test/Makefile
index aa27f53..1fff105 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -13,7 +13,7 @@ all:
 
 .PHONY: all
 
-TESTS = unix-callback mem-snap rpc libcriu mounts/ext security
+TESTS = unix-callback mem-snap rpc libcriu mounts/ext security pipes
 
 other: .FORCE
 	for t in $(TESTS); do			\
diff --git a/test/pipes/Makefile b/test/pipes/Makefile
new file mode 100644
index 0000000..bd4c563
--- /dev/null
+++ b/test/pipes/Makefile
@@ -0,0 +1,12 @@
+CFLAGS += -Wall
+pipe: pipe.c
+clean:
+	rm -f pipe
+run:
+	./pipe - &&		\
+	./pipe -c &&		\
+	./pipe -cl &&		\
+	./pipe -d &&		\
+	./pipe -dc &&		\
+	./pipe -dcl &&		\
+	true
diff --git a/test/pipes/pipe.c b/test/pipes/pipe.c
new file mode 100644
index 0000000..4a0331c
--- /dev/null
+++ b/test/pipes/pipe.c
@@ -0,0 +1,673 @@
+/*
+ * A simple demo/test program using criu's --inherit-fd command line
+ * option to restore a process with (1) an external pipe and (2) a
+ * new log file.
+ *
+ * Note that it's possible to restore the process without --inherit-fd,
+ * but when it reads from or writes to the pipe, it will get a broken
+ * pipe signal.
+ *
+ * Also note that changing the log file during restore has nothing to do
+ * with the pipe.  It's just a nice feature for cases where it's desirable
+ * to have a restored process use a different file then the original one.
+ * 
+ * The parent process spawns a child that will write messages to its
+ * parent through a pipe.  After a couple of messages, parent invokes
+ * criu to checkpoint the child.  Since the child exits after checkpoint,
+ * its pipe will be broken.  Parent sets up a new pipe and invokes criu
+ * to restore the child using the new pipe (instead of the old one).
+ * The restored child exits after writing a couple more messages.
+ *
+ * To make sure that fd clashes are correctly handled during restore,
+ * child can optionally open a regular file and move it to a clashing fd.
+ *
+ * Make sure CRIU_BINARY defined below points to the right criu.
+ *
+ *	$ cc -Wall -o pipe pipe.c
+ *	$ sudo ./pipe -v
+ *
+ *      The following should all succeed:
+ *
+ *	$ sudo ./pipe -q && echo OK
+ *	$ sudo ./pipe -qc && echo OK
+ *	$ sudo ./pipe -qcl && echo OK
+ *	$ sudo ./pipe -qd && echo OK
+ *	$ sudo ./pipe -qdc && echo OK
+ *	$ sudo ./pipe -qdcl && echo OK
+ *
+ *      The following should all fail:
+ *
+ *	$ sudo ./pipe -qn || echo $?
+ *	$ sudo ./pipe -qo || echo $?
+ *	$ sudo ./pipe -qr || echo $?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+
+typedef void (*sighandler_t)(int);
+typedef unsigned long ulong;
+
+/* colors */
+#define CS_PARENT 		"\033[00;32m"
+#define CS_CHILD 		"\033[00;33m"
+#define CS_DUMP 		"\033[00;34m"
+#define CS_RESTORE 		"\033[00;35m"
+#define CE			"\033[0m"
+
+#define die(fmt, ...) do { \
+	if (!qflag) \
+		fprintf(stderr, fmt ": %m\n", __VA_ARGS__); \
+	if (getpid() == parent_pid) { \
+		(void)kill(0, 9); \
+		exit(1); \
+	} \
+	_exit(1); \
+} while (0)
+
+#define READ_FD		0	/* pipe read fd */
+#define WRITE_FD	1	/* pipe write fd */
+#define CLASH_FD	3	/* force inherit fd clash */
+
+#define MAX_FORKS	3	/* child, checkpoint, restore */
+
+#define CRIU_BINARY		"../../criu"
+#define IMG_DIR			"images"
+#define DUMP_LOG_FILE		"dump.log"
+#define RESTORE_LOG_FILE	"restore.log"
+#define RESTORE_PID_FILE	"restore.pid"
+#define INHERIT_FD_OPTION	"--inherit-fd"
+#define OLD_LOG_FILE		"/tmp/oldlog"
+#define NEW_LOG_FILE		"/tmp/newlog"
+
+/*
+ * Command line options (see usage()).
+ */
+char *cli_flags = "cdhlnoqrv";
+
+int cflag;
+int dflag;
+int lflag;
+int nflag;
+int oflag;
+int qflag;
+int rflag;
+int vflag;
+
+char pid_number[8];
+char inh_pipe_opt[16];
+char inh_pipe_arg[64];
+char inh_file_opt[16];
+char inh_file_arg[64];
+
+char *dump_argv[] = {
+	"criu", "dump",
+	"-D", IMG_DIR, "-o", DUMP_LOG_FILE,
+	"-v4", "-j",
+	"-t", pid_number,
+	NULL
+};
+
+char *restore_argv[] = {
+	"criu", "restore", "-d",
+	"-D", IMG_DIR, "-o", RESTORE_LOG_FILE,
+	"--pidfile", RESTORE_PID_FILE,
+	"-v4", "-j",
+	inh_pipe_opt, inh_pipe_arg,
+	inh_file_opt, inh_file_arg,
+	NULL
+};
+
+int max_msgs;
+int max_forks;
+int parent_pid;
+int child_pid;
+int criu_dump_pid;
+int criu_restore_pid;
+
+/* prototypes */
+void chld_handler(int signum);
+int parent(int *pipefd);
+int child(int *pipefd, int dupfd, int newfd);
+void checkpoint_child(int child_pid, int *pipefd);
+void restore_child(int *new_pipefd, char *old_pipe_name);
+void write_to_fd(int fd, char *name, int i, int newline);
+void ls_proc_fd(int fd);
+char *pipe_name(int fd);
+char *who(pid_t pid);
+void pipe_safe(int pipefd[2]);
+pid_t fork_safe(void);
+void signal_safe(int signum, sighandler_t handler);
+int open_safe(char *pathname, int flags);
+void close_safe(int fd);
+void write_safe(int fd, char *buf, int count);
+int read_safe(int fd, char *buf, int count);
+int dup_safe(int oldfd);
+void move_fd(int oldfd, int newfd);
+void mkdir_safe(char *dirname, int mode);
+void unlink_safe(char *pathname);
+void execv_safe(char *path, char *argv[], int ls);
+pid_t waitpid_safe(pid_t pid, int *status, int options, int id);
+void prctl_safe(int option, ulong arg2, ulong arg3, ulong arg4, ulong arg5);
+
+void usage(char *cmd)
+{
+	printf("Usage: %s [%s]\n", cmd, cli_flags);
+	printf("-c\tcause a clash during restore by opening %s as fd %d\n",
+		OLD_LOG_FILE, CLASH_FD);
+	printf("-d\tdup the pipe and write to it\n");
+	printf("-l\tchange log file from %s to %s during restore\n",
+		OLD_LOG_FILE, NEW_LOG_FILE);
+
+	printf("\n");
+	printf("The following flags should cause restore failure\n");
+	printf("-n\tdo not use the %s option\n", INHERIT_FD_OPTION);
+	printf("-o\topen the pipe via /proc/<pid>/fd and write to it\n");
+	printf("-r\tspecify read end of pipe during restore\n");
+
+	printf("\n");
+	printf("Miscellaneous flags\n");
+	printf("-h\tprint this help and exit\n");
+	printf("-q\tquiet mode, don't print anything\n");
+	printf("-v\tverbose mode (list contents of /proc/<pid>/fd)\n");
+
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+        int opt;
+	int pipefd[2];
+
+	max_msgs = 4;
+	while ((opt = getopt(argc, argv, cli_flags)) != -1) {
+		switch (opt) {
+		case 'c': cflag++; break;
+		case 'd': dflag++; max_msgs += 4; break;
+		case 'h': usage(argv[0]); return 0;
+		case 'l': lflag++; break;
+		case 'n': nflag++; break;
+		case 'o': oflag++; max_msgs += 4; break;
+		case 'q': qflag++; vflag = 0;break;
+		case 'r': rflag++; break;
+		case 'v': vflag++; qflag = 0; break;
+		default: usage(argv[0]); return 1;
+		}
+	}
+
+	setbuf(stdout, NULL);
+	setbuf(stderr, NULL);
+	mkdir_safe(IMG_DIR, 0700);
+
+	pipe_safe(pipefd);
+	child_pid = fork_safe();
+	if (child_pid > 0) {
+		parent_pid = getpid();
+
+		signal_safe(SIGCHLD, chld_handler);
+		prctl_safe(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
+
+		close_safe(pipefd[WRITE_FD]);
+
+		ret = parent(pipefd);
+	} else {
+		/* child */
+		int dupfd = -1;
+		int openfd = -1;
+
+		child_pid = getpid();
+
+		close_safe(pipefd[READ_FD]);
+
+		/* open a regular file and move it to CLASH_FD */
+		if (cflag)
+			move_fd(open_safe(OLD_LOG_FILE, O_WRONLY | O_APPEND | O_CREAT), CLASH_FD);
+
+		/* open additional descriptors on the pipe and use them all */
+		if (dflag)
+			dupfd = dup_safe(pipefd[WRITE_FD]);
+		if (oflag) {
+			char buf[128];
+			snprintf(buf, sizeof buf, "/proc/self/fd/%d", pipefd[WRITE_FD]);
+			openfd = open_safe(buf, O_WRONLY);
+		}
+
+		ret = child(pipefd, dupfd, openfd);
+	}
+
+	return ret;
+}
+
+/*
+ * Parent reads message from its pipe with the child.
+ * After a couple of messages, it checkpoints the child
+ * which causes the child to exit.  Parent then creates
+ * a new pipe and restores the child.
+ */
+int parent(int *pipefd)
+{
+	char buf[32];
+	char old_pipe[32];
+	int nread;
+
+	nread = 0;
+	while (max_forks <= MAX_FORKS) {
+		if (read_safe(pipefd[READ_FD], buf, sizeof buf) == 0)
+			continue;
+		nread++;
+		if (vflag && nread == 1)
+			ls_proc_fd(-1);
+
+		if (!qflag) {
+			printf("%s read %s from %s\n", who(0), buf,
+				pipe_name(pipefd[READ_FD]));
+		}
+
+		if (nread == (max_msgs / 2)) {
+			checkpoint_child(child_pid, pipefd);
+
+			if (!nflag) {
+				/* save the old pipe's name before closing it */
+				snprintf(old_pipe, sizeof old_pipe, "%s",
+					pipe_name(pipefd[READ_FD]));
+				close_safe(pipefd[READ_FD]);
+
+				/* create a new one */
+				if (!qflag)
+					printf("%s creating a new pipe\n", who(0));
+				pipe_safe(pipefd);
+			}
+			restore_child(pipefd, old_pipe);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Child sends a total of max_messages messages to its
+ * parent, half before checkpoint and half after restore.
+ */
+int child(int *pipefd, int dupfd, int openfd)
+{
+	int i;
+	int fd;
+	int num_wfds;
+	struct timespec req = { 1, 0 };
+
+	/*
+	 * Count the number of pipe descriptors we'll be
+	 * writing to.  At least 1 (for pipefd[WRITE_FD])
+	 * and at most 3.
+	 */
+	num_wfds = 1;
+	if (dupfd >= 0)
+		num_wfds++;
+	if (openfd >= 0)
+		num_wfds++;
+
+	for (i = 0; i < max_msgs; i++) {
+		/* print first time and after checkpoint */
+		if (vflag && (i == 0 || i == (max_msgs / 2)))
+			ls_proc_fd(-1);
+
+		switch (i % num_wfds) {
+		case 0: fd = pipefd[WRITE_FD]; break;
+		case 1: fd = dflag ? dupfd : openfd; break;
+		case 2: fd = openfd; break;
+		}
+
+		write_to_fd(fd, pipe_name(pipefd[WRITE_FD]), i+1, 0);
+		if (cflag)
+			write_to_fd(CLASH_FD, "log file", i+1, 1);
+
+		/*
+		 * Since sleep will be interrupted by C/R, make sure
+		 * to sleep an entire second to minimize the chance of
+		 * writing before criu restore has exited.  If criu is
+		 * still around and we write to a broken pipe, we'll be
+		 * killed but SIGCHLD will be delivered to criu instead
+		 * of parent.
+		 */
+		while (nanosleep(&req, NULL))
+			;
+		if (!qflag)
+			printf("\n");
+	}
+
+	return 0;
+}
+
+void chld_handler(int signum)
+{
+	int status;
+	pid_t pid;
+
+	pid = waitpid_safe(-1, &status, WNOHANG, 1);
+	if (WIFEXITED(status))
+		status = WEXITSTATUS(status);
+	if (pid == child_pid) {
+		if (!qflag) {
+			printf("%s %s exited with status %d\n", who(0),
+				who(pid), status); 
+		}
+		/* if child exited successfully, we're done */
+		if (status == 0)
+			exit(0);
+		/* checkpoint kills the child */
+		if (status != 9)
+			exit(status);
+	}
+}
+
+void checkpoint_child(int child_pid, int *pipefd)
+{
+	/* prepare -t <pid> */
+	snprintf(pid_number, sizeof pid_number, "%d", child_pid);
+
+	criu_dump_pid = fork_safe();
+	if (criu_dump_pid > 0) {
+		int status;
+		pid_t pid;
+
+		pid = waitpid_safe(criu_dump_pid, &status, 0, 2);
+		if (WIFEXITED(status))
+			status = WEXITSTATUS(status);
+		if (!qflag) {
+			printf("%s %s exited with status %d\n", who(0),
+				who(pid), status);
+		}
+		if (status)
+			exit(status);
+	} else {
+		close(pipefd[READ_FD]);
+		criu_dump_pid = getpid();
+		execv_safe(CRIU_BINARY, dump_argv, 0);
+	}
+}
+
+void restore_child(int *new_pipefd, char *old_pipe_name)
+{
+	char buf[64];
+
+	criu_restore_pid = fork_safe();
+	if (criu_restore_pid > 0) {
+		int status;
+		pid_t pid;
+
+		if (!nflag)
+			close_safe(new_pipefd[WRITE_FD]);
+
+		pid = waitpid_safe(criu_restore_pid, &status, 0, 3);
+		if (WIFEXITED(status))
+			status = WEXITSTATUS(status);
+		if (!qflag) {
+			printf("%s %s exited with status %d\n", who(0),
+				who(pid), status);
+		}
+		if (status)
+			exit(status);
+	} else {
+		criu_restore_pid = getpid();
+
+		if (!nflag) {
+			/*
+			 * We should close the read descriptor of the new pipe
+			 * and use its write descriptor to call criu restore.
+			 * But if rflag was set (for testing purposes), use the
+			 * read descriptor which should cause the application to
+			 * fail.
+			 *
+			 * Regardless of read or write descriptor, move it to a
+			 * clashing fd to test inherit fd clash resolve code.
+			 */
+			if (rflag)
+				move_fd(new_pipefd[READ_FD], CLASH_FD);
+			else {
+				close_safe(new_pipefd[READ_FD]);
+				move_fd(new_pipefd[WRITE_FD], CLASH_FD);
+			}
+
+			/* --inherit-fd fd[CLASH_FD]:pipe[xxxxxx] */
+			snprintf(inh_pipe_opt, sizeof inh_pipe_opt,
+				"%s", INHERIT_FD_OPTION);
+			snprintf(inh_pipe_arg, sizeof inh_pipe_arg, "fd[%d]:%s",
+				CLASH_FD, old_pipe_name);
+
+			if (lflag) {
+				/* create a new log file to replace the old one */
+				int filefd = open_safe(NEW_LOG_FILE, O_WRONLY | O_APPEND | O_CREAT);
+
+				/* --inherit-fd fd[x]:tmp/oldlog */
+				snprintf(inh_file_opt, sizeof inh_file_opt,
+					"%s", INHERIT_FD_OPTION);
+				snprintf(inh_file_arg, sizeof inh_file_arg,
+					"fd[%d]:%s", filefd, OLD_LOG_FILE + 1);
+			}
+		}
+
+		snprintf(buf, sizeof buf, "%s/%s", IMG_DIR, RESTORE_PID_FILE);
+		unlink_safe(buf);
+		execv_safe(CRIU_BINARY, restore_argv, 1);
+	}
+}
+
+void write_to_fd(int fd, char *name, int i, int newline)
+{
+	int n;
+	char buf[16];	/* fit "hello d\n" for small d */
+
+	n = snprintf(buf, sizeof buf, "hello %d", i);
+	if (!qflag)
+		printf("%s writing %s to %s via fd %d\n", who(0), buf, name, fd);
+
+	if (newline) {
+		buf[n++] = '\n';
+		buf[n] = '\0';
+	}
+	write_safe(fd, buf, strlen(buf));
+}
+
+void ls_proc_fd(int fd)
+{
+	char cmd[128];
+
+	if (qflag)
+		return;
+
+	if (fd == -1)
+		snprintf(cmd, sizeof cmd, "ls -l /proc/%d/fd", getpid());
+	else
+		snprintf(cmd, sizeof cmd, "ls -l /proc/%d/fd/%d", getpid(), fd);
+	printf("%s %s\n", who(0), cmd);
+	system(cmd);
+}
+
+char *pipe_name(int fd)
+{
+	static char pipe_name[64];
+	char path[64];
+
+	snprintf(path, sizeof path, "/proc/self/fd/%d", fd);
+	if (readlink(path, pipe_name, sizeof pipe_name) == -1)
+		die("readlink: path=%s", path);
+	return pipe_name;
+}
+
+/*
+ * Use two buffers to support two calls to 
+ * this function in a printf argument list.
+ */
+char *who(pid_t pid)
+{
+	static char pidstr1[64];
+	static char pidstr2[64];
+	static char *cp;
+	char *np;
+	char *ep;
+	int p;
+
+	p = pid ? pid : getpid();
+	if (p == parent_pid) {
+		np = "parent";
+		ep = CS_PARENT;
+	} else if (p == child_pid) {
+		np = "child";
+		ep = CS_CHILD;
+	} else if (p == criu_dump_pid) {
+		np = "dump";
+		ep = CS_DUMP;
+	} else if (p == criu_restore_pid) {
+		np = "restore";
+		ep = CS_RESTORE;
+	} else
+		np = "???";
+
+	cp = (cp == pidstr1) ? pidstr2 : pidstr1;
+	snprintf(cp, sizeof pidstr1, "%s[%s %d]", pid ? "" : ep, np, p);
+	return cp;
+}
+
+void pipe_safe(int pipefd[2])
+{
+	if (pipe(pipefd) == -1)
+		die("pipe: %p", pipefd);
+}
+
+pid_t fork_safe(void)
+{
+	pid_t pid;
+
+	if ((pid = fork()) == -1)
+		die("fork: pid=%d", pid);
+	max_forks++;
+	return pid;
+}
+
+void signal_safe(int signum, sighandler_t handler)
+{
+	if (signal(signum, handler) == SIG_ERR)
+		die("signal: signum=%d", signum);
+}
+
+int open_safe(char *pathname, int flags)
+{
+	int fd;
+
+	if ((fd = open(pathname, flags)) == -1)
+		die("open: pathname=%s", pathname);
+	return fd;
+}
+
+void close_safe(int fd)
+{
+	if (close(fd) == -1)
+		die("close: fd=%d", fd);
+}
+
+void write_safe(int fd, char *buf, int count)
+{
+	if (write(fd, buf, count) != count) {
+		die("write: fd=%d buf=\"%s\" count=%d errno=%d",
+			fd, buf, count, errno);
+	}
+}
+
+int read_safe(int fd, char *buf, int count)
+{
+	int n;
+
+	if ((n = read(fd, buf, count)) < 0)
+		die("read: fd=%d count=%d", fd, count);
+	buf[n] = '\0';
+	return n;
+}
+
+int dup_safe(int oldfd)
+{
+	int newfd;
+
+	if ((newfd = dup(oldfd)) == -1)
+		die("dup: oldfd=%d", oldfd);
+	return newfd;
+}
+
+int dup2_safe(int oldfd, int newfd)
+{
+	if (dup2(oldfd, newfd) != newfd)
+		die("dup2: oldfd=%d newfd=%d", oldfd, newfd);
+	return newfd;
+}
+
+void move_fd(int oldfd, int newfd)
+{
+	if (oldfd != newfd) {
+		dup2_safe(oldfd, newfd);
+		close_safe(oldfd);
+	}
+}
+
+void mkdir_safe(char *dirname, int mode)
+{
+	if (mkdir(dirname, mode) == -1 && errno != EEXIST)
+		die("mkdir dirname=%s mode=0x%x\n", dirname, mode);
+}
+
+void unlink_safe(char *pathname)
+{
+	if (unlink(pathname) == -1 && errno != ENOENT) {
+		die("unlink: pathname=%s\n", pathname);
+	}
+}
+
+void execv_safe(char *path, char *argv[], int ls)
+{
+	int i;
+	struct timespec req = { 0, 1000000 };
+
+	if (!qflag) {
+		printf("\n%s ", who(0));
+		for (i = 0; argv[i] != NULL; i++)
+			printf("%s ", argv[i]);
+		printf("\n");
+	}
+
+	/* give parent a chance to wait for us */
+	while (nanosleep(&req, NULL))
+		;
+
+	if (vflag && ls)
+		ls_proc_fd(-1);
+
+	execv(path, argv);
+	die("execv: path=%s", path);
+}
+
+pid_t waitpid_safe(pid_t pid, int *status, int options, int id)
+{
+	pid_t p;
+
+	p = waitpid(pid, status, options);
+	if (p == -1)
+		fprintf(stderr, "waitpid pid=%d id=%d %m\n", pid, id);
+	return p;
+}
+
+void prctl_safe(int option, ulong arg2, ulong arg3, ulong arg4, ulong arg5)
+{
+	if (prctl(option, arg2, arg3, arg4, arg5) == -1)
+		die("prctl: option=0x%x", option);
+}
-- 
1.9.3



More information about the CRIU mailing list