[CRIU] [PATCH 2/2] tty: Add checkpoint/restore for ttys

Cyrill Gorcunov gorcunov at openvz.org
Fri Jun 15 05:32:46 EDT 2012


At moment it handles unix98 ttys only. At least
that's what being tested with 'screen' session.

The base idea is to save/restore tty parameters with
TCGETS/TCSETS ioctls.

Signed-off-by: Cyrill Gorcunov <gorcunov at openvz.org>
---
 Makefile          |    1 +
 cr-dump.c         |    7 +-
 cr-restore.c      |    4 +
 cr-show.c         |   33 ++++
 crtools.c         |    1 +
 include/crtools.h |    2 +
 include/image.h   |   25 +++
 include/tty.h     |   15 ++
 tty.c             |  502 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 588 insertions(+), 2 deletions(-)
 create mode 100644 include/tty.h
 create mode 100644 tty.c

diff --git a/Makefile b/Makefile
index 44b6854..c4e5a58 100644
--- a/Makefile
+++ b/Makefile
@@ -55,6 +55,7 @@ OBJS		+= eventfd.o
 OBJS		+= eventpoll.o
 OBJS		+= mount.o
 OBJS		+= inotify.o
+OBJS		+= tty.o
 
 DEPS		:= $(patsubst %.o,%.d,$(OBJS))
 
diff --git a/cr-dump.c b/cr-dump.c
index 47f2667..555c125 100644
--- a/cr-dump.c
+++ b/cr-dump.c
@@ -41,6 +41,7 @@
 #include "eventfd.h"
 #include "eventpoll.h"
 #include "inotify.h"
+#include "tty.h"
 
 #ifndef CONFIG_X86_64
 # error No x86-32 support yet
@@ -443,12 +444,14 @@ static int dump_chrdev(struct fd_parms *p, int lfd, const struct cr_fdset *set)
 	switch (maj) {
 	case MEM_MAJOR:
 		return dump_reg_file(p, lfd, set);
-	case TTY_MAJOR:
+	case TTYAUX_MAJOR:
+	case UNIX98_PTY_MASTER_MAJOR ... (UNIX98_PTY_MASTER_MAJOR + UNIX98_PTY_MAJOR_COUNT - 1):
 	case UNIX98_PTY_SLAVE_MAJOR:
 		if (p->fd < 3) {
 			pr_info("... Skipping tty ... %d\n", p->fd);
 			return 0;
-		}
+		} else
+			return dump_tty(p, lfd, set);
 	}
 
 	return dump_unsupp_fd(p);
diff --git a/cr-restore.c b/cr-restore.c
index 9a17cd3..1c44716 100644
--- a/cr-restore.c
+++ b/cr-restore.c
@@ -45,6 +45,7 @@
 #include "shmem.h"
 #include "mount.h"
 #include "inotify.h"
+#include "tty.h"
 
 static struct task_entries *task_entries;
 
@@ -290,6 +291,9 @@ static int prepare_shared(void)
 	if (collect_pipes())
 		return -1;
 
+	if (collect_tty())
+		return -1;
+
 	if (collect_inet_sockets())
 		return -1;
 
diff --git a/cr-show.c b/cr-show.c
index 27de63a..296257a 100644
--- a/cr-show.c
+++ b/cr-show.c
@@ -62,6 +62,7 @@ static char *fdtype2s(u8 type)
 		[FDINFO_REG] = "reg",
 		[FDINFO_INETSK] = "isk",
 		[FDINFO_PIPE] = "pipe",
+		[FDINFO_TTY] = "tty",
 		[FDINFO_UNIXSK] = "usk",
 		[FDINFO_EVENTFD] = "efd",
 		[FDINFO_EVENTPOLL] = "epl",
@@ -227,6 +228,38 @@ out:
 	pr_img_tail(CR_FD_PIPES);
 }
 
+void show_tty(int fd, struct cr_options *o)
+{
+	struct tty_file_entry e;
+	int ret;
+
+	pr_img_head(CR_FD_TTY);
+
+	while (1) {
+		ret = read_img_eof(fd, &e);
+		if (ret <= 0)
+			goto out;
+		pr_msg("id: 0x%-8x flags: 0x%-8x rdev: 0x%-16lx index 0x%-8x ",
+		       e.id, e.flags, e.rdev, e.index);
+		show_fown_cont(&e.fown);
+
+		if (e.len) {
+			int ret = read(fd, local_buf, e.len);
+
+			if (ret != e.len) {
+				pr_perror("Can't read %d bytes", e.len);
+				goto out;
+			}
+			local_buf[e.len] = 0;
+			pr_msg(" --> %s", local_buf);
+		}
+		pr_msg("\n");
+	}
+
+out:
+	pr_img_tail(CR_FD_TTY);
+}
+
 void show_fs(int fd_fs, struct cr_options *o)
 {
 	struct fs_entry fe;
diff --git a/crtools.c b/crtools.c
index fc0e010..536ec57 100644
--- a/crtools.c
+++ b/crtools.c
@@ -60,6 +60,7 @@ struct cr_fd_desc_tmpl fdset_template[CR_FD_MAX] = {
 	FD_ENTRY(VMAS,		"vmas-%d",	 show_vmas),
 	FD_ENTRY(PIPES,		"pipes",	 show_pipes),
 	FD_ENTRY(PIPES_DATA,	"pipes-data",	 show_pipes_data),
+	FD_ENTRY(TTY,		"tty",		 show_tty),
 	FD_ENTRY(PSTREE,	"pstree",	 show_pstree),
 	FD_ENTRY(SIGACT,	"sigacts-%d",	 show_sigacts),
 	FD_ENTRY(UNIXSK,	"unixsk",	 show_unixsk),
diff --git a/include/crtools.h b/include/crtools.h
index 798d196..557f0f1 100644
--- a/include/crtools.h
+++ b/include/crtools.h
@@ -54,6 +54,7 @@ enum {
 	CR_FD_UNIXSK,
 	CR_FD_PIPES,
 	CR_FD_PIPES_DATA,
+	CR_FD_TTY,
 	CR_FD_REMAP_FPATH,
 	CR_FD_EVENTFD,
 	CR_FD_EVENTPOLL,
@@ -113,6 +114,7 @@ void show_remap_files(int fd, struct cr_options *o);
 void show_ghost_file(int fd, struct cr_options *o);
 void show_fown_cont(fown_t *fown);
 void show_eventfds(int fd, struct cr_options *o);
+void show_tty(int fd, struct cr_options *o);
 
 extern void print_data(unsigned long addr, unsigned char *data, size_t size);
 extern struct cr_fd_desc_tmpl fdset_template[CR_FD_MAX];
diff --git a/include/image.h b/include/image.h
index eed7389..0c7b2fd 100644
--- a/include/image.h
+++ b/include/image.h
@@ -40,11 +40,13 @@
 #define INOTIFY_MAGIC		0x48424431 /* Volgograd */
 #define INOTIFY_WD_MAGIC	0x54562009 /* Svetlogorsk (Rauschen) */
 #define MOUNTPOINTS_MAGIC	0x55563928 /* Petushki */
+#define TTY_MAGIC		0x59433025 /* Pushkin */
 
 enum fd_types {
 	FDINFO_UND,
 	FDINFO_REG,
 	FDINFO_PIPE,
+	FDINFO_TTY,
 	FDINFO_INETSK,
 	FDINFO_UNIXSK,
 	FDINFO_EVENTFD,
@@ -154,6 +156,29 @@ enum {
 	PIPE_TYPE_MAX,
 };
 
+typedef struct {
+	u16	c_iflag;
+	u16	c_oflag;
+	u16	c_cflag;
+	u16	c_lflag;
+	u8	c_line;
+	u8	c_cc[19];
+	u16	c_ispeed;
+	u16	c_ospeed;
+} __packed term2_t;
+
+struct tty_file_entry {
+	u32	id;
+	u16	flags;
+	u16	len;
+	u64	pos;
+	u64	rdev;
+	u32	index;
+	fown_t	fown;
+	term2_t	term2;
+	u8	name[0];
+} __packed;
+
 struct pipe_entry {
 	u32	id;
 	u32	pipe_id;
diff --git a/include/tty.h b/include/tty.h
new file mode 100644
index 0000000..9fa6055
--- /dev/null
+++ b/include/tty.h
@@ -0,0 +1,15 @@
+#ifndef CR_TTY_H__
+#define CR_TTY_H__
+
+#include "files.h"
+#include "crtools.h"
+
+#define PTMX_PATH "/dev/ptmx"
+#ifndef PTMX_MINOR
+# define PTMX_MINOR 2
+#endif
+
+extern int dump_tty(struct fd_parms *p, int lfd, const struct cr_fdset *set);
+extern int collect_tty(void);
+
+#endif /* CR_TTY_H__ */
diff --git a/tty.c b/tty.c
new file mode 100644
index 0000000..5ed3fdc
--- /dev/null
+++ b/tty.c
@@ -0,0 +1,502 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <linux/major.h>
+
+#include "compiler.h"
+#include "types.h"
+
+#include "syscall.h"
+#include "files.h"
+#include "crtools.h"
+#include "image.h"
+#include "util.h"
+#include "log.h"
+#include "list.h"
+#include "util-net.h"
+#include "proc_parse.h"
+
+#include "tty.h"
+
+struct tty_file_info;
+
+struct pty_unix98_priv {
+	struct tty_file_info	*slave;		/* pointer to slave info if there */
+	bool			fake_ptmx;	/* fake ptmx needed to create slave */
+	bool			slave_here;	/* slave in the same ns as a master */
+};
+
+struct tty_private {
+  union {
+	struct pty_unix98_priv	unix98;
+  };
+};
+
+struct tty_file_info {
+	struct list_head	list;
+	struct file_desc	d;
+	struct tty_file_entry	tfe;
+	struct tty_private	priv;
+
+	char			*path;
+	int			major;
+	int			minor;
+	bool			create;
+};
+
+static LIST_HEAD(all_ttys);
+
+static void from_termio(term2_t *d, struct termio *s)
+{
+	BUILD_BUG_ON(sizeof(d->c_cc) < sizeof(s->c_cc));
+
+	memzero(d, sizeof(*d));
+	memcpy(d->c_cc, s->c_cc, sizeof(s->c_cc));
+	ASSIGN_MEMBER(d, s, c_iflag);
+	ASSIGN_MEMBER(d, s, c_oflag);
+	ASSIGN_MEMBER(d, s, c_cflag);
+	ASSIGN_MEMBER(d, s, c_lflag);
+	ASSIGN_MEMBER(d, s, c_line);
+}
+
+static void to_termio(struct termio *d, term2_t *s)
+{
+	memzero(d, sizeof(*d));
+	memcpy(d->c_cc, s->c_cc, sizeof(d->c_cc));
+	ASSIGN_MEMBER(d, s, c_iflag);
+	ASSIGN_MEMBER(d, s, c_oflag);
+	ASSIGN_MEMBER(d, s, c_cflag);
+	ASSIGN_MEMBER(d, s, c_lflag);
+	ASSIGN_MEMBER(d, s, c_line);
+}
+
+#define PTMX_INDEX_ANY	-1
+
+static int tty_open_ptmx_index(int flags, int index)
+{
+	int fds[32], i, ret = -1, cur_idx;
+
+	memset(fds, 0xff, sizeof(fds));
+
+	for (i = 0; i < ARRAY_SIZE(fds); i++) {
+		fds[i] = open(PTMX_PATH, flags);
+		if (fds[i] < 0) {
+			pr_perror("Can't open %s", PTMX_PATH);
+			break;
+		}
+
+		if (ioctl(fds[i], TIOCGPTN, &cur_idx)) {
+			pr_perror("Can't obtain current index on %s", PTMX_PATH);
+			break;
+		}
+
+		/*
+		 * Index match or any index requested.
+		 */
+		if (cur_idx == index || index == PTMX_INDEX_ANY) {
+			ret = fds[i];
+			fds[i] = -1;
+			break;
+		}
+
+
+		/*
+		 * Maybe indices are already borrowed by
+		 * someone else, so no need to continue.
+		 */
+		if (cur_idx < index && (index - cur_idx) < ARRAY_SIZE(fds))
+			continue;
+		break;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(fds); i++) {
+		if (fds[i] >= 0)
+			close(fds[i]);
+	}
+
+	if (ret < 0)
+		pr_err("Unable to open %s with specified index %d\n",
+		       PTMX_PATH, index);
+
+	return ret;
+}
+
+static int unlock_pty_master(int master)
+{
+	const int lock = 0;
+
+	if (ioctl(master, TIOCSPTLCK, &lock)) {
+		pr_err("Unable to unlock pty master device %d\n", master);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int receive_tty_pts(struct tty_file_info *info)
+{
+	struct fdinfo_list_entry *fle;
+	struct file_desc *info_desc;
+	char path[64];
+	int tmp, fd;
+
+	info_desc = find_file_desc_raw(FDINFO_TTY, info->tfe.id);
+	fle = file_master(info_desc);
+	pr_info("\tWaiting tty fd %d\n", fle->fe.fd);
+
+	tmp = recv_fd(fle->fe.fd);
+	close(fle->fe.fd);
+	if (tmp < 0) {
+		pr_err("Can't get fd %d\n", tmp);
+		return -1;
+	}
+
+	snprintf(path, sizeof(path), "/proc/self/fd/%d", tmp);
+	fd = open(path, info->tfe.flags);
+	close(tmp);
+
+	return fd;
+}
+
+static int tty_loopup_open_pts(struct tty_file_info *info)
+{
+	int slave_fd, master_fd = -1;
+
+	if (!info->create)
+		return receive_tty_pts(info);
+
+	slave_fd = open(info->path, info->tfe.flags);
+	if (slave_fd >= 0)
+		goto out;
+
+	if (info->priv.unix98.fake_ptmx) {
+
+		/* If we faile here we won't connect slave anyway */
+		master_fd = tty_open_ptmx_index(info->tfe.flags | O_RDWR, info->tfe.index);
+		if (master_fd >= 0)
+			unlock_pty_master(master_fd);
+		else
+			pr_err("Can't open fake ptmx for %s (%d)\n",
+			       info->path, info->tfe.index);
+	}
+
+	slave_fd = open(info->path, info->tfe.flags);
+	if (slave_fd < 0) {
+		pr_perror("Can't open %s\n", info->path);
+		goto out;
+	}
+
+out:
+	close_safe(&master_fd);
+	return slave_fd;
+}
+
+static int tty_open_ptmx(struct tty_file_info *info)
+{
+	struct tty_file_info *slave = info->priv.unix98.slave;
+	int master_fd = -1;
+	int slave_fd = -1;
+	int sock = -1;
+	int ret = -1;
+
+	if (info->minor != PTMX_MINOR || strcmp(info->path, PTMX_PATH)) {
+		pr_err("Unexpected tty path %s (%d %d)\n",
+		       info->path, info->minor, PTMX_MINOR);
+		return -1;
+	}
+
+	if (slave) {
+		struct fdinfo_list_entry *fle;
+		struct file_desc *slave_desc;
+		int index;
+
+		if (info->priv.unix98.fake_ptmx)
+			index = -1;
+		else
+			index = slave->tfe.index;
+
+		master_fd = tty_open_ptmx_index(info->tfe.flags, index);
+		if (master_fd < 0)
+			goto err;
+
+		if (unlock_pty_master(master_fd))
+			goto err;
+
+		sock = socket(PF_UNIX, SOCK_DGRAM, 0);
+		if (sock < 0) {
+			pr_perror("Can't create socket");
+			goto err;
+		}
+
+		slave_fd = open(slave->path, slave->tfe.flags);
+		if (slave_fd < 0) {
+			pr_perror("Can't open slave %s", slave->path);
+			goto err;
+		}
+
+		slave_desc = find_file_desc_raw(FDINFO_TTY, slave->tfe.id);
+		fle = file_master(slave_desc);
+
+		list_for_each_entry(fle, &slave_desc->fd_info_head, desc_list) {
+			if (fle->pid == getpid())
+				continue;
+			if (send_fd_to_peer(slave_fd, fle, sock)) {
+				pr_perror("Can't send file descriptor");
+				goto err;
+			}
+		}
+
+		ret = master_fd, master_fd = -1;
+	} else
+		ret = tty_open_ptmx_index(info->tfe.flags, info->tfe.index);
+
+err:
+	close_safe(&master_fd);
+	close_safe(&slave_fd);
+	close_safe(&sock);
+	return ret;
+}
+
+static int tty_lookup_open(struct tty_file_info *info)
+{
+	switch (info->major) {
+	case UNIX98_PTY_SLAVE_MAJOR:
+		return tty_loopup_open_pts(info);
+	case TTYAUX_MAJOR:
+		return tty_open_ptmx(info);
+	}
+
+	BUG_ON(1);
+	return -1;
+}
+
+static int tty_open(struct file_desc *d)
+{
+	struct tty_file_info *info = container_of(d, struct tty_file_info, d);
+	int tmp, ret = -1;
+	struct termio t;
+
+	tmp = tty_lookup_open(info);
+	if (tmp < 0) {
+		pr_perror("Can't open tty id %#08x [%s]",
+			  info->tfe.id, info->path);
+		return -1;
+	}
+
+	if (rst_file_params(tmp, &info->tfe.fown, info->tfe.flags)) {
+		pr_perror("Can't restore params on tfe %#08x",
+			  info->tfe.id);
+		goto err;
+	}
+
+	to_termio(&t, &info->tfe.term2);
+	if (ioctl(tmp, TCSETS, &t) < 0) {
+		pr_perror("Can't set tty params on %d [%s]",
+			  info->tfe.id, info->path);
+		goto err;
+	}
+
+	ret = tmp, tmp = -1;
+err:
+	close_safe(&tmp);
+	return ret;
+}
+
+static int want_transport(struct fdinfo_entry *fe, struct file_desc *d)
+{
+	struct tty_file_info *info = container_of(d, struct tty_file_info, d);
+	return info->create == false;
+}
+
+static struct file_desc_ops tty_desc_ops = {
+	.type		= FDINFO_TTY,
+	.open		= tty_open,
+	.want_transport	= want_transport,
+};
+
+static int tty_handle_priv(struct list_head *all_ttys_head)
+{
+	struct tty_file_info *info;
+
+	list_for_each_entry(info, all_ttys_head, list) {
+		struct tty_file_info *entry;
+		int master;
+
+		/*
+		 * At moment we're only interested in UNIX98_PTY_SLAVE_MAJOR
+		 * devices since they require to open ptmx with appropriate
+		 * index.
+		 */
+		if (info->major == TTYAUX_MAJOR ||
+		    info->major != UNIX98_PTY_SLAVE_MAJOR)
+			continue;
+
+		/*
+		 * Setup the ptmx index here, the kernel
+		 * doesn't provide any other interface :/
+		 */
+		if (sscanf(info->path, "/dev/pts/%d", &master) != 1) {
+			pr_err("Unexpected format on path %s\n", info->path);
+			return -1;
+		}
+		info->tfe.index = master;
+
+		/*
+		 * By default we assume the fake ptmx
+		 * connection is needed, until contrary
+		 * proved.
+		 */
+		info->priv.unix98.fake_ptmx = true;
+
+		/* Finally connect slave to a master device */
+		list_for_each_entry(entry, all_ttys_head, list) {
+			if (entry->major != TTYAUX_MAJOR ||
+			    entry->tfe.index != master)
+				continue;
+
+			entry->priv.unix98.fake_ptmx = false;
+			entry->priv.unix98.slave = info;
+			info->create = false;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int collect_tty(void)
+{
+	struct tty_file_info *info = NULL;
+	int fd, ret = -1;
+
+	fd = open_image_ro(CR_FD_TTY);
+	if (fd < 0)
+		return -1;
+
+	while (1) {
+		int len;
+
+		info = xzalloc(sizeof(*info));
+		if (!info) {
+			ret = -1;
+			break;
+		}
+
+		ret = read_img_eof(fd, &info->tfe);
+		if (ret <= 0)
+			break;
+
+		len = info->tfe.len;
+		info->path = xmalloc(len + 1);
+		if (!info->path) {
+			ret = -1;
+			break;
+		}
+
+		ret = read_img_buf(fd, info->path, len);
+		if (ret < 0)
+			break;
+
+		info->path[len] = '\0';
+		info->create = true;
+		info->major = major(info->tfe.rdev);
+		info->minor = minor(info->tfe.rdev);
+
+		pr_info("Collected tty [%s] ID %#x\n", info->path, info->tfe.id);
+
+		/* Partial ordering is needed for search speedup */
+		switch (info->major) {
+		case TTYAUX_MAJOR:
+			list_add(&info->list, &all_ttys);
+			break;
+		case UNIX98_PTY_SLAVE_MAJOR:
+			list_add_tail(&info->list, &all_ttys);
+			break;
+		default:
+			pr_err("Unsupported tty type %s (major %d)\n",
+			       info->path, info->major);
+			ret = -1;
+			goto out;
+		}
+	}
+
+	ret = tty_handle_priv(&all_ttys);
+
+	if (!ret) {
+		list_for_each_entry(info, &all_ttys, list)
+			file_desc_add(&info->d, info->tfe.id, &tty_desc_ops);
+		info = NULL;
+	}
+
+out:
+	if (info)
+		xfree(info->path);
+	xfree(info);
+	close(fd);
+	return ret;
+}
+
+static int dump_one_tty(int lfd, u32 id, const struct fd_parms *p)
+{
+	struct tty_file_entry e = { };
+	struct termio t;
+	char *path;
+	int fd, len;
+
+	pr_info("Dumping tty %d with id %#x\n", lfd, id);
+
+	path = read_proc_selffd_link(lfd, &len);
+	if (!path)
+		return -1;
+
+	if (ioctl(lfd, TCGETS, &t) < 0) {
+		pr_perror("Can't get tty params on %d [%s]", p->fd, path);
+		return -1;
+	}
+
+	from_termio(&e.term2, &t);
+
+	e.id	= id;
+	e.flags	= p->flags;
+	e.len	= len;
+	e.pos	= p->pos;
+	e.fown	= p->fown;
+	e.rdev	= p->stat.st_rdev;
+
+	/*
+	 * This ioctl is implemented for unix98 ptys,
+	 * so simply ignore error code if not implemented.
+	 */
+	if (ioctl(lfd, TIOCGPTN, &e.index))
+		e.index = 0;
+
+	fd = fdset_fd(glob_fdset, CR_FD_TTY);
+	if (write_img(fd, &e))
+		return -1;
+
+	pr_info("Dumping path for tty %d fd via self %d [%s]\n", p->fd, lfd, path);
+	if (write_img_buf(fd, path, e.len))
+		return -1;
+
+	return 0;
+}
+
+static const struct fdtype_ops tty_ops = {
+	.type		= FDINFO_TTY,
+	.make_gen_id	= make_gen_id,
+	.dump		= dump_one_tty,
+};
+
+int dump_tty(struct fd_parms *p, int lfd, const struct cr_fdset *set)
+{
+	return do_dump_gen_file(p, lfd, &tty_ops, set);
+}
-- 
1.7.7.6



More information about the CRIU mailing list