[CRIU] Re: [PATCH 2/4] tty: Add checkpoint/restore for unix terminals v3

Cyrill Gorcunov gorcunov at openvz.org
Fri Sep 7 10:25:06 EDT 2012


On Fri, Sep 07, 2012 at 05:55:50PM +0400, Pavel Emelyanov wrote:
> On 09/07/2012 05:44 PM, Cyrill Gorcunov wrote:
> > On Fri, Sep 07, 2012 at 05:41:00PM +0400, Cyrill Gorcunov wrote:
> >>  
> >> -static int dump_chrdev(struct fd_parms *p, int lfd, const struct cr_fdset *set)
> >> +static int dump_chrdev(pid_t pid, struct fd_parms *p, int lfd, const struct cr_fdset *set)
> > 
> > sigh, i missed this redundant pid parameter. please ignore it, if anything else
> > will be fine for you i'll update it on top.
> 
> You haven't fixed all the comments from the previous attempt. Please do it.

OK, this one should fit the request. Note that I've not addressed comments on
crontol terminals code with purpose -- they are will be addressed in furter
patches which implement ctl tty functionality.

The "char *" from pty private no longer needed, so I dropped it.

As to "I don't see the tty contents dumping." -- at moment we can't
dump ttys buffers yet.

But one comment left untouched:

> +             winsize_conv(&w, info­>tfe­>winsize);
> +             if (ioctl(fd, TIOCSWINSZ, &w) < 0)
> +                     goto err;
> +     }
> Need checks for n_foo is enough.

I don;t understand what exactly you mean here. If you're
about c_cc array in termios structure -- I've added a check
in "./crtools check" for it. Or maybe you mean something else?

	Cyrill
-------------- next part --------------
>From e96be2a657c50e6eb962e8252480ef1f663c4866 Mon Sep 17 00:00:00 2001
From: Cyrill Gorcunov <gorcunov at openvz.org>
Date: Tue, 28 Aug 2012 21:34:12 +0400
Subject: [PATCH] tty: Add checkpoint/restore for unix terminals v4

Usually the PTYs represent a pair of links -- master peer and slave
peer. Master peer must be opened before slave. Internally, when kernel
creates master peer it also generates a slave interface in a form of
/dev/pts/N, where N is that named pty "index". Master/slave connection
unambiguously identified by this index.

Still, one master can carry multiple slaves -- for example a user opens
one master via /dev/ptmx and appropriate /dev/pts/N in sequence.
The result will be the following

master
`- slave 1
`- slave 2

both slave will have same master index but different file descriptors.
Still inside the kernel pty parameters are same for both slaves. Thus
only one slave parameters should be restored, there is no need to carry
all parameters for every slave peer we've found.

Not yet addressed problems:

- Also there is a second hardness with slave peers -- at moment of
  restore the master end might be already closed for any reason so
  to resolve such problem we need to open a fake master peer with
  proper index and hook a slave on it, then we close master peer.

- Need to figure out how to deal with ttys which have some
  data in buffers not yet flushed, at moment this data will
  be simply lost during c/r

- Need to restore control terminals

- Need to fetch tty flags such as exclusive/packet-mode

[ avagin@:
   - contol terminals restoration code complete rework
     (not addressed in this patch)
   - overall code redesign and simplification
]

v4:
 - drop redundant pid from dump_chrdev
 - make sure optional fown is passed on regular ptys
 - add a comments about zeroifying termios
 - get rid of redundant empty line in files.c

Signed-off-by: Cyrill Gorcunov <gorcunov at openvz.org>
CC: Andrey Vagin <avagin at openvz.org>
---
 Makefile              |    1 +
 cr-dump.c             |   11 +-
 cr-restore.c          |   10 +
 files.c               |    7 +
 image.c               |    1 +
 include/crtools.h     |    1 +
 include/image.h       |    1 +
 include/protobuf.h    |    1 +
 include/tty.h         |   24 ++
 protobuf.c            |    2 +
 protobuf/Makefile     |    1 +
 protobuf/fdinfo.proto |    1 +
 protobuf/tty.proto    |   52 ++++
 tty.c                 |  763 +++++++++++++++++++++++++++++++++++++++++++++++++
 14 files changed, 875 insertions(+), 1 deletions(-)
 create mode 100644 include/tty.h
 create mode 100644 protobuf/tty.proto
 create mode 100644 tty.c

diff --git a/Makefile b/Makefile
index c5ee775..5574b8c 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,7 @@ OBJS		+= inotify.o
 OBJS		+= signalfd.o
 OBJS		+= pstree.o
 OBJS		+= protobuf.o
+OBJS		+= tty.o
 
 PROTOBUF-LIB	:= protobuf/protobuf-lib.o
 
diff --git a/cr-dump.c b/cr-dump.c
index f710f44..99b3611 100644
--- a/cr-dump.c
+++ b/cr-dump.c
@@ -47,6 +47,7 @@
 #include "signalfd.h"
 #include "pstree.h"
 #include "mount.h"
+#include "tty.h"
 
 #include "protobuf.h"
 #include "protobuf/fdinfo.pb-c.h"
@@ -234,12 +235,20 @@ 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:
+		/*
+		 * FIXME
+		 *
+		 * Until control terminals are restored and standalone
+		 * slaves are supported skip stdin/out/err.
+		 */
 		if (p->fd < 3) {
 			pr_info("... Skipping tty ... %d\n", p->fd);
 			return 0;
 		}
+		return dump_tty(p, lfd, set);
 	}
 
 	return dump_unsupp_fd(p);
diff --git a/cr-restore.c b/cr-restore.c
index 0e54ef5..4cdbff5 100644
--- a/cr-restore.c
+++ b/cr-restore.c
@@ -51,6 +51,7 @@
 #include "inotify.h"
 #include "pstree.h"
 #include "net.h"
+#include "tty.h"
 
 #include "protobuf.h"
 #include "protobuf/sa.pb-c.h"
@@ -119,6 +120,9 @@ static int prepare_shared(void)
 	if (collect_inotify())
 		return -1;
 
+	if (collect_tty())
+		return -1;
+
 	for_each_pstree_item(pi) {
 		if (pi->state == TASK_HELPER)
 			continue;
@@ -133,6 +137,11 @@ static int prepare_shared(void)
 	}
 
 	mark_pipe_master();
+
+	ret = tty_prepare_shared();
+	if (ret)
+		goto err;
+
 	ret = resolve_unix_peers();
 
 	if (!ret) {
@@ -140,6 +149,7 @@ static int prepare_shared(void)
 		show_saved_files();
 	}
 
+err:
 	return ret;
 }
 
diff --git a/files.c b/files.c
index d7f7264..d815f5e 100644
--- a/files.c
+++ b/files.c
@@ -23,6 +23,7 @@
 #include "lock.h"
 #include "sockets.h"
 #include "pstree.h"
+#include "tty.h"
 
 #include "protobuf.h"
 #include "protobuf/fs.pb-c.h"
@@ -181,6 +182,12 @@ static int collect_fd(int pid, FdinfoEntry *e, struct rst_info *rst_info)
 	case FD_TYPES__EVENTPOLL:
 		list_add_tail(&new_le->ps_list, &rst_info->eventpoll);
 		break;
+	case FD_TYPES__TTY:
+		if (tty_is_master(new_le))
+			list_add_tail(&new_le->ps_list, &rst_info->tty_masters);
+		else
+			list_add_tail(&new_le->ps_list, &rst_info->tty_slaves);
+		break;
 	default:
 		list_add_tail(&new_le->ps_list, &rst_info->fds);
 		break;
diff --git a/image.c b/image.c
index 456cbce..c99f7b4 100644
--- a/image.c
+++ b/image.c
@@ -128,6 +128,7 @@ struct cr_fd_desc_tmpl fdset_template[CR_FD_MAX] = {
 	FD_ENTRY(IFADDR,	"ifaddr-%d",	 show_raw_image),
 	FD_ENTRY(ROUTE,		"route-%d",	 show_raw_image),
 	FD_ENTRY(TMPFS,		"tmpfs-%d.tar.gz", show_raw_image),
+	FD_ENTRY(TTY,		"tty",		 NULL),
 };
 
 static struct cr_fdset *alloc_cr_fdset(int nr)
diff --git a/include/crtools.h b/include/crtools.h
index 44dcac6..3ecc9af 100644
--- a/include/crtools.h
+++ b/include/crtools.h
@@ -66,6 +66,7 @@ enum {
 	CR_FD_PIPES_DATA,
 	CR_FD_FIFO,
 	CR_FD_FIFO_DATA,
+	CR_FD_TTY,
 	CR_FD_REMAP_FPATH,
 	CR_FD_EVENTFD,
 	CR_FD_EVENTPOLL,
diff --git a/include/image.h b/include/image.h
index 983d359..e4afbb9 100644
--- a/include/image.h
+++ b/include/image.h
@@ -59,6 +59,7 @@
 #define INOTIFY_WD_MAGIC	0x54562009 /* Svetlogorsk (Rauschen) */
 #define MOUNTPOINTS_MAGIC	0x55563928 /* Petushki */
 #define NETDEV_MAGIC		0x57373951 /* Yaroslavl */
+#define TTY_MAGIC		0x59433025 /* Pushkin */
 
 #define IFADDR_MAGIC		RAW_IMAGE_MAGIC
 #define ROUTE_MAGIC		RAW_IMAGE_MAGIC
diff --git a/include/protobuf.h b/include/protobuf.h
index fb6279a..2358053 100644
--- a/include/protobuf.h
+++ b/include/protobuf.h
@@ -41,6 +41,7 @@ enum {
 	PB_SIGNALFD,
 	PB_INOTIFY,
 	PB_INOTIFY_WD,
+	PB_TTY,
 
 	PB_MAX
 };
diff --git a/include/tty.h b/include/tty.h
new file mode 100644
index 0000000..66d4cdf
--- /dev/null
+++ b/include/tty.h
@@ -0,0 +1,24 @@
+#ifndef CR_TTY_H__
+#define CR_TTY_H__
+
+#include "files.h"
+#include "crtools.h"
+
+/* Kernel's limit */
+#define TERMIOS_NCC	19
+
+#define PTMX_PATH	"/dev/ptmx"
+#ifndef PTMX_MINOR
+# define PTMX_MINOR 2
+#endif
+#define PTS_FMT		"/dev/pts/%d"
+
+#define TTY_LOCKED	(1 << 0)
+#define TTY_EXCLUSIVE	(1 << 1)
+
+extern int dump_tty(struct fd_parms *p, int lfd, const struct cr_fdset *set);
+extern int collect_tty(void);
+extern int tty_is_master(struct fdinfo_list_entry *le);
+extern int tty_prepare_shared(void);
+
+#endif /* CR_TTY_H__ */
diff --git a/protobuf.c b/protobuf.c
index 18f3192..bf2a23a 100644
--- a/protobuf.c
+++ b/protobuf.c
@@ -48,6 +48,7 @@
 #include "protobuf/mnt.pb-c.h"
 #include "protobuf/netdev.pb-c.h"
 #include "protobuf/tcp-stream.pb-c.h"
+#include "protobuf/tty.pb-c.h"
 
 typedef size_t (*pb_getpksize_t)(void *obj);
 typedef size_t (*pb_pack_t)(void *obj, void *where);
@@ -122,6 +123,7 @@ void cr_pb_init(void)
 	CR_PB_DESC(MOUNTPOINTS,		Mnt,		mnt);
 	CR_PB_DESC(NETDEV,		NetDevice,	net_device);
 	CR_PB_DESC(PACKETSK,		PacketSock,	packet_sock);
+	CR_PB_DESC(TTY,			TtyFile,	tty_file);
 }
 
 /*
diff --git a/protobuf/Makefile b/protobuf/Makefile
index 5be3539..6a7f297 100644
--- a/protobuf/Makefile
+++ b/protobuf/Makefile
@@ -38,6 +38,7 @@ PROTO_FILES	+= creds.proto
 PROTO_FILES	+= vma.proto
 PROTO_FILES	+= core.proto
 PROTO_FILES	+= netdev.proto
+PROTO_FILES	+= tty.proto
 
 PROTO_HDRS	:= $(patsubst %.proto,%.pb-c.h,$(PROTO_FILES))
 PROTO_SRCS	:= $(patsubst %.proto,%.pb-c.c,$(PROTO_FILES))
diff --git a/protobuf/fdinfo.proto b/protobuf/fdinfo.proto
index 5ad78de..59cbcf8 100644
--- a/protobuf/fdinfo.proto
+++ b/protobuf/fdinfo.proto
@@ -10,6 +10,7 @@ enum fd_types {
 	INOTIFY		= 8;
 	SIGNALFD	= 9;
 	PACKETSK	= 10;
+	TTY		= 11;
 }
 
 message fdinfo_entry {
diff --git a/protobuf/tty.proto b/protobuf/tty.proto
new file mode 100644
index 0000000..d709dc0
--- /dev/null
+++ b/protobuf/tty.proto
@@ -0,0 +1,52 @@
+import "fown.proto";
+
+message winsize_entry {
+	required uint32			ws_row		= 1;
+	required uint32			ws_col		= 2;
+	required uint32			ws_xpixel	= 3;
+	required uint32			ws_ypixel	= 4;
+};
+
+message termios_entry {
+	required uint32			c_iflag		= 1;
+	required uint32			c_oflag		= 2;
+	required uint32			c_cflag		= 3;
+	required uint32			c_lflag		= 4;
+	required uint32			c_line		= 5;
+	required uint32			c_ispeed	= 6;
+	required uint32			c_ospeed	= 7;
+
+	repeated uint32			c_cc		= 8;
+}
+
+message tty_pty_entry {
+	required uint32			index		= 1;
+}
+
+enum TtyType {
+	UNKNOWN		= 0;
+	PTY		= 1;
+}
+
+message tty_file_entry {
+	required uint32			id		=  1;
+	required uint32			flags		=  2;
+	required uint32			sid		=  3;
+	required uint32			prgp		=  4;
+	required uint64			pos		=  5;
+	required uint64			rdev		=  6;
+
+	required TtyType		type		=  7;
+
+	required bool			locked		=  8;
+	required bool			exclusive	=  9;
+	required bool			packet_mode	= 10;
+
+	required bool			ctl_tty		= 11;
+
+	optional fown_entry		fown		= 12;
+	optional tty_pty_entry		pty		= 13;
+	optional termios_entry		termios		= 14;
+	optional termios_entry		termios_locked	= 15;
+	optional winsize_entry		winsize		= 16;
+}
diff --git a/tty.c b/tty.c
new file mode 100644
index 0000000..e8ebe38
--- /dev/null
+++ b/tty.c
@@ -0,0 +1,763 @@
+#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 "file-ids.h"
+
+#include "protobuf.h"
+#include "protobuf/tty.pb-c.h"
+
+#include "pstree.h"
+#include "tty.h"
+
+/*
+ * Here are some notes about overall TTY c/r design. At moment
+ * we support unix98 ptys only.
+ *
+ * Since we might need to extend the number supported TTY types
+ * in future the unix98 pty specific data lays in tty_private::pty
+ * structure.
+ *
+ * Usually the PTYs represent a pair of links -- master peer and slave
+ * peer. Master peer must be opened before slave. Internally, when kernel
+ * creates master peer it also generates a slave interface in a form of
+ * /dev/pts/N, where N is that named pty "index". Master/slave connection
+ * unambiguously identified by this index.
+ *
+ * Still, one master can carry multiple slaves -- for example a user opens
+ * one master via /dev/ptmx and appropriate /dev/pts/N in sequence.
+ * The result will be the following
+ *
+ * master
+ * `- slave 1
+ * `- slave 2
+ *
+ * both slave will have same master index but different file descriptors.
+ * Still inside the kernel pty parameters are same for both slaves. Thus
+ * only one slave parameters should be restored, there is no need to carry
+ * all parameters for every slave peer we've found.
+ *
+ * FIXME:
+ *
+ * - Need to find a way to restore standalone terminals, ie slaves
+ *   which have no master hooked on.
+ *
+ * - Need to restore control terminals.
+ */
+
+#undef	LOG_PREFIX
+#define LOG_PREFIX "tty: "
+
+struct tty_file_info;
+
+struct tty_private {
+	union {
+		/*
+		 * At moment only unix98 ptys are addressed
+		 * but this union structure should be big enough to
+		 * keep any data related to another type of ttys.
+		 */
+		struct {
+			struct list_head	sibling;
+		} pty;
+	};
+};
+
+struct tty_file_info {
+	struct list_head		list;
+	struct file_desc		d;
+	TtyFileEntry			*tfe;
+	struct tty_private		priv;
+
+	int				major;
+	int				minor;
+};
+
+static LIST_HEAD(all_ttys);
+
+/*
+ * Usually an application has not that many ttys opened.
+ * If this won't be enough in future we simply need to
+ * change tracking mechanism to some more extendable.
+ *
+ * This particular bitmap requires 128 bytes of memory.
+ * Pretty acceptable trade off in a sake of simplicity.
+ */
+#define MAX_TTYS 1024
+static DECLARE_BITMAP(tty_indices, MAX_TTYS);
+
+/*
+ * /dev/ptmx is a shared resource between all tasks
+ * so we need to serialize access to it.
+ */
+static mutex_t *ptmx_index_mutex;
+
+int tty_prepare_shared(void)
+{
+	ptmx_index_mutex = shmalloc(sizeof(*ptmx_index_mutex));
+	if (!ptmx_index_mutex) {
+		pr_perror("Can't create ptmx index mutex");
+		return -1;
+	}
+
+	mutex_init(ptmx_index_mutex);
+	return 0;
+}
+
+#define winsize_copy(d, s)				\
+	do {						\
+		ASSIGN_MEMBER((d), (s), ws_row);	\
+		ASSIGN_MEMBER((d), (s), ws_col);	\
+		ASSIGN_MEMBER((d), (s), ws_xpixel);	\
+		ASSIGN_MEMBER((d), (s), ws_ypixel);	\
+	} while (0)
+
+#define termios_copy(d, s)				\
+	do {						\
+		memcpy((d)->c_cc, (s)->c_cc,		\
+		       min(sizeof((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);		\
+	} while (0)
+
+static int parse_index(int lfd, int rdev, const struct fd_parms *p)
+{
+	int major = major(rdev);
+	int index = -1;
+
+	switch (major) {
+	case TTYAUX_MAJOR:
+		if (ioctl(lfd, TIOCGPTN, &index)) {
+			pr_perror("Can't obtain ptmx index\n");
+			return -1;
+		}
+		break;
+
+	case UNIX98_PTY_SLAVE_MAJOR: {
+		char path[PATH_MAX];
+		char link[32];
+		int len;
+
+		snprintf(link, sizeof(link), "/proc/self/fd/%d", lfd);
+		len = readlink(link, path, sizeof(path) - 1);
+		if (len < 0) {
+			pr_perror("Can't readlink %s", link);
+			return -1;
+		}
+		path[len] = '\0';
+
+		if (sscanf(path, PTS_FMT, &index) != 1) {
+			pr_err("Unexpected format on path %s\n", path);
+			return -1;
+		}
+		break;
+	}
+	}
+
+	return index;
+}
+
+static int pty_open_ptmx_index(int flags, int index)
+{
+	int fds[32], i, ret = -1, cur_idx;
+
+	memset(fds, 0xff, sizeof(fds));
+
+	mutex_lock(ptmx_index_mutex);
+
+	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;
+		}
+
+		pr_debug("\t\tptmx opened with index %d\n", cur_idx);
+
+		if (cur_idx == index) {
+			pr_info("ptmx opened with index %d\n", cur_idx);
+			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;
+
+		pr_err("Unable to open %s with specified index %d\n", PTMX_PATH, index);
+		break;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(fds); i++) {
+		if (fds[i] >= 0)
+			close(fds[i]);
+	}
+
+	mutex_unlock(ptmx_index_mutex);
+
+	return ret;
+}
+
+static int try_open_pts(int index, int flags, bool report)
+{
+	char path[64];
+	int fd;
+
+	snprintf(path, sizeof(path), PTS_FMT, index);
+	fd = open(path, flags);
+	if (fd < 0 && report) {
+		pr_err("Can't open terminal %s\n", path);
+		return -1;
+	}
+
+	return fd;
+}
+
+static int unlock_pty_master(int master)
+{
+	const int lock = 0;
+
+	/*
+	 * Usually when ptmx opened it gets locked
+	 * by kernel and we need to unlock it to be
+	 * able to connect slave peer.
+	 */
+	if (ioctl(master, TIOCSPTLCK, &lock)) {
+		pr_err("Unable to unlock pty master device %d\n", master);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int lock_pty_master(int master)
+{
+	const int lock = 1;
+
+	if (ioctl(master, TIOCSPTLCK, &lock)) {
+		pr_err("Unable to lock pty master device %d\n", master);
+		return -1;
+	}
+
+	return 0;
+}
+
+static char *tty_type(struct tty_file_info *info)
+{
+	static char *tty_types[] = {
+		[UNIX98_PTY_SLAVE_MAJOR]	= "pts",
+		[TTYAUX_MAJOR]			= "ptmx",
+	};
+	static char tty_unknown[]		= "unknown";
+
+	switch (info->major) {
+	case UNIX98_PTY_SLAVE_MAJOR:
+	case TTYAUX_MAJOR:
+		return tty_types[info->major];
+	}
+
+	return tty_unknown;
+}
+
+static bool pty_is_master(struct tty_file_info *info)
+{
+	return info->major == TTYAUX_MAJOR;
+}
+
+static void tty_show_pty_info(char *prefix, struct tty_file_info *info)
+{
+	pr_info("%s type %s id %#x index %d (create %d sid %d prgp %d)\n",
+		prefix, tty_type(info), info->tfe->id, info->tfe->pty->index,
+		pty_is_master(info), info->tfe->sid, info->tfe->prgp);
+}
+
+static int restore_tty_params(int fd, struct tty_file_info *info)
+{
+	int ret;
+
+	/*
+	 * Note sid and group should be restored from the task
+	 * context which sid/group matches the terminal's. Thus
+	 * we don't restore them here but need a special routine
+	 * for that.
+	 *
+	 * Note that if fown is missing -- something seriously
+	 * screwed here, we allow fown to not exsit just in one
+	 * case -- control terminals. But they are handled in
+	 * specific way via other code paths.
+	 */
+
+	if (!info->tfe->fown) {
+		pr_err("Unexpected lack of @fown in terminal %#x params\n",
+			info->tfe->id);
+		return -1;
+	}
+
+	ret = rst_file_params(fd, info->tfe->fown, info->tfe->flags);
+	if (ret)
+		return -1;
+
+	if (info->tfe->termios_locked) {
+		struct termios t = { };
+
+		/*
+		 * It's important to zeroify termios
+		 * because it contain @c_cc array which
+		 * is bigger than TERMIOS_NCC. Same applies
+		 * to winsize usage, we can't guarantee the
+		 * structure taked from system headers will
+		 * never be extended.
+		 */
+
+		termios_copy(&t, info->tfe->termios_locked);
+		if (ioctl(fd, TIOCSLCKTRMIOS, &t) < 0)
+			goto err;
+	}
+
+	if (info->tfe->termios) {
+		struct termios t = { };
+
+		termios_copy(&t, info->tfe->termios);
+		if (ioctl(fd, TCSETS, &t) < 0)
+			goto err;
+	}
+
+	if (info->tfe->winsize) {
+		struct winsize w = { };
+
+		winsize_copy(&w, info->tfe->winsize);
+		if (ioctl(fd, TIOCSWINSZ, &w) < 0)
+			goto err;
+	}
+
+	return 0;
+err:
+	pr_perror("Can't set tty params on %d", info->tfe->id);
+		return -1;
+}
+
+/*
+ * Unix98 pty slave peers requires the master peers being
+ * opened early, this function test if it's master pty.
+ */
+int tty_is_master(struct fdinfo_list_entry *le)
+{
+	struct tty_file_info *info = container_of(le->desc, struct tty_file_info, d);
+	return pty_is_master(info);
+}
+
+static int pty_open_slaves(struct tty_file_info *info)
+{
+	int sock = -1, fd = -1, ret = -1;
+	struct fdinfo_list_entry *fle;
+	struct tty_file_info *slave;
+	char pts_name[64];
+
+	snprintf(pts_name, sizeof(pts_name), PTS_FMT, info->tfe->pty->index);
+
+	sock = socket(PF_UNIX, SOCK_DGRAM, 0);
+	if (sock < 0) {
+		pr_perror("Can't create socket");
+		goto err;
+	}
+
+	list_for_each_entry(slave, &info->priv.pty.sibling, priv.pty.sibling) {
+		if (pty_is_master(slave)) {
+			pr_warn("Unexpected master peer %#x in slaves list\n",
+				slave->tfe->id);
+			continue;
+		}
+
+		if (slave->tfe->ctl_tty)
+			continue;
+
+		fd = open(pts_name, slave->tfe->flags);
+		if (fd < 0) {
+			pr_perror("Can't open slave %s", pts_name);
+			goto err;
+		}
+
+		fle = file_master(&slave->d);
+
+		pr_debug("send slave %#x fd %d connected on %s (pid %d)\n",
+			 slave->tfe->id, fd, pts_name, fle->pid);
+
+		if (send_fd_to_peer(fd, fle, sock)) {
+			pr_perror("Can't send file descriptor");
+			goto err;
+		}
+
+		close(fd);
+		fd = -1;
+	}
+	ret = 0;
+
+err:
+	close_safe(&fd);
+	close_safe(&sock);
+	return ret;
+}
+
+static int receive_tty(struct tty_file_info *info)
+{
+	struct fdinfo_list_entry *fle;
+	int fd;
+
+	fle = file_master(&info->d);
+	pr_info("\tWaiting tty fd %d (pid %d)\n", fle->fe->fd, fle->pid);
+
+	fd = recv_fd(fle->fe->fd);
+	close(fle->fe->fd);
+	if (fd < 0) {
+		pr_err("Can't get fd %d\n", fd);
+		return -1;
+	}
+
+	if (restore_tty_params(fd, info)) {
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+static int pty_open_ptmx(struct tty_file_info *info)
+{
+	int master = -1;
+	int pktmode = 1;
+
+	if (info->major != TTYAUX_MAJOR) {
+		pr_err("Can't restore pty without master\n");
+		return -1;
+	}
+
+	master = pty_open_ptmx_index(info->tfe->flags, info->tfe->pty->index);
+	if (master < 0) {
+		pr_perror("Can't open(index %d)", info->tfe->pty->index);
+		return -1;
+	}
+
+	unlock_pty_master(master);
+
+	if (restore_tty_params(master, info))
+		goto err;
+
+	/*
+	 * FIXME
+	 *
+	 * PTYs are safe to use packet mode. While there
+	 * is no way to fetch packet mode settings from
+	 * the kernel, without it we see echos missing
+	 * in `screen' application restore.
+	 */
+	if (ioctl(master, TIOCPKT, &pktmode) < 0) {
+		pr_perror("Can't set packed mode");
+		goto err;
+	}
+
+	if (pty_open_slaves(info))
+		goto err;
+
+	if (info->tfe->locked)
+		lock_pty_master(master);
+
+	return master;
+err:
+	close_safe(&master);
+	return -1;
+}
+
+static int tty_open(struct file_desc *d)
+{
+	struct tty_file_info *info = container_of(d, struct tty_file_info, d);
+
+	tty_show_pty_info("open", info);
+
+	if (!pty_is_master(info))
+		return receive_tty(info);
+
+	return pty_open_ptmx(info);
+
+}
+
+static int tty_transport(FdinfoEntry *fe, struct file_desc *d)
+{
+	struct tty_file_info *info = container_of(d, struct tty_file_info, d);
+	return pty_is_master(info) == false;
+}
+
+static struct file_desc_ops tty_desc_ops = {
+	.type		= FD_TYPES__TTY,
+	.open		= tty_open,
+	.want_transport = tty_transport,
+};
+
+static int tty_setup_slavery(void)
+{
+	struct tty_file_info *info, *peer, *m;
+
+	/*
+	 * FIXME
+	 *
+	 * Find which task should restore
+	 * control terminals.
+	 */
+
+	list_for_each_entry(info, &all_ttys, list) {
+		peer = info;
+		list_for_each_entry_safe_continue(peer, m, &all_ttys, list) {
+			if (peer->tfe->pty->index != info->tfe->pty->index)
+				continue;
+
+			list_add(&peer->priv.pty.sibling, &info->priv.pty.sibling);
+			list_del(&peer->list);
+		}
+	}
+
+	/*
+	 * Print out information about peers.
+	 */
+	list_for_each_entry(info, &all_ttys, list) {
+		tty_show_pty_info("head", info);
+		list_for_each_entry(peer, &info->priv.pty.sibling, priv.pty.sibling)
+			tty_show_pty_info("    `- sibling", peer);
+	}
+
+	return 0;
+}
+
+static int collect_one_tty(void *obj, ProtobufCMessage *msg)
+{
+	struct tty_file_info *info = obj;
+
+	info->tfe = pb_msg(msg, TtyFileEntry);
+	if (info->tfe->type != TTY_TYPE__PTY) {
+		pr_err("Unsuported tty type %d\n", info->tfe->type);
+		return -1;
+	}
+
+	memzero(&info->priv, sizeof(info->priv));
+
+	info->major = major(info->tfe->rdev);
+	info->minor = minor(info->tfe->rdev);
+
+	switch (info->major) {
+	case TTYAUX_MAJOR:
+	case UNIX98_PTY_SLAVE_MAJOR:
+		INIT_LIST_HEAD(&info->priv.pty.sibling);
+		break;
+	default:
+		pr_err("Unsupported tty type (major %d)\n", info->major);
+		return -1;
+	}
+
+	pr_info("Collected tty ID %#x\n", info->tfe->id);
+
+	list_add(&info->list, &all_ttys);
+	file_desc_add(&info->d, info->tfe->id, &tty_desc_ops);
+
+	return 0;
+}
+
+int collect_tty(void)
+{
+	int ret = collect_image(CR_FD_TTY, PB_TTY,
+				sizeof(struct tty_file_info),
+				collect_one_tty);
+	if (!ret)
+		ret = tty_setup_slavery();
+
+	return ret;
+}
+
+static void pty_mark_dumped(u64 rdev, int index)
+{
+	if (major(rdev) != TTYAUX_MAJOR)
+		set_bit(index, tty_indices);
+}
+
+static bool pty_is_dumped(u64 rdev, int index)
+{
+	return major(rdev) != TTYAUX_MAJOR &&
+		test_bit(index, tty_indices);
+}
+
+static int pty_get_flags(int lfd, TtyFileEntry *e)
+{
+	int index = e->pty->index, slave;
+
+	e->locked	= false;
+	e->exclusive	= false;
+	e->packet_mode	= false;
+
+	/*
+	 * FIXME
+	 *
+	 * At moment we fetch only locked flag which
+	 * make sense on master peer only.
+	 *
+	 * For exclusive and packet mode the kernel
+	 * patching is needed.
+	 */
+	if (major(e->rdev) != TTYAUX_MAJOR)
+		return 0;
+
+	slave = try_open_pts(index, O_RDONLY, false);
+	if (slave < 0) {
+		if (errno == EIO) {
+			e->locked = true;
+			return 0;
+		} else {
+			pr_err("Can't fetch slave peer index %d flags\n", index);
+			return -1;
+		}
+	}
+
+	close(slave);
+	return 0;
+}
+
+static int dump_one_pty(int lfd, u32 id, const struct fd_parms *p)
+{
+	TtyFileEntry e = TTY_FILE_ENTRY__INIT;
+	TermiosEntry termios = TERMIOS_ENTRY__INIT;
+	TermiosEntry termios_locked = TERMIOS_ENTRY__INIT;
+	WinsizeEntry winsize = WINSIZE_ENTRY__INIT;
+	TtyPtyEntry pty = TTY_PTY_ENTRY__INIT;
+	int ret;
+
+	pr_info("Dumping tty %d with id %#x\n", lfd, id);
+
+	e.id			= id;
+	e.flags			= p->flags;
+	e.pos			= p->pos;
+	e.rdev			= p->stat.st_rdev;
+	e.fown			= (FownEntry *)&p->fown;
+	e.pty			= &pty;
+	e.type			= TTY_TYPE__PTY;
+
+	ret = parse_index(lfd, e.rdev, p);
+	if (ret < 0)
+		return -1;
+
+	pty.index = ret;
+	if (pty.index > MAX_TTYS) {
+		pr_err("Index %d on tty %x is too big\n", pty.index, id);
+		return -1;
+	}
+
+	if (pty_get_flags(pty.index, &e))
+		return -1;
+
+	/*
+	 * FIXME
+	 *
+	 * Figure out how to fetch data buffered in terminal.
+	 * For a while simply flush before dumping.
+	 */
+	if (ioctl(lfd, TCFLSH, TCIOFLUSH)) {
+		pr_perror("Can't flush terminal\n");
+		return -1;
+	}
+
+	/*
+	 * It might be a case where many slave peers hooked on
+	 * a single /dev/pts/N device. So we dump parameters
+	 * only once.
+	 */
+	if (!pty_is_dumped(e.rdev, pty.index)) {
+		struct termios t;
+		struct winsize w;
+
+		ret = -1;
+
+		if (ioctl(lfd, TCGETS, &t) < 0) {
+			pr_perror("Can't get tty params on %d", p->fd);
+			goto out;
+		}
+
+		termios.n_c_cc		= TERMIOS_NCC;
+		termios.c_cc		= xmalloc(pb_repeated_size(&termios, c_cc));
+
+		termios_locked.n_c_cc	= TERMIOS_NCC;
+		termios_locked.c_cc	= xmalloc(pb_repeated_size(&termios_locked, c_cc));
+
+		if (!termios.c_cc || !termios_locked.c_cc)
+			goto out;
+
+		termios_copy(&termios, &t);
+
+		if (ioctl(lfd, TIOCGLCKTRMIOS, &t) < 0) {
+			pr_perror("Can't get tty locked params on %d", p->fd);
+			goto out;
+		}
+
+		termios_copy(&termios_locked, &t);
+
+		if (ioctl(lfd, TIOCGWINSZ, &w) < 0) {
+			pr_perror("Can't get tty window params on %d", p->fd);
+			goto out;
+		}
+
+		winsize_copy(&winsize, &w);
+
+		pty_mark_dumped(e.rdev, pty.index);
+
+		e.termios		= &termios;
+		e.termios_locked	= &termios_locked;
+		e.winsize		= &winsize;
+	}
+
+	ret = pb_write_one(fdset_fd(glob_fdset, CR_FD_TTY), &e, PB_TTY);
+
+out:
+	xfree(termios.c_cc);
+	xfree(termios_locked.c_cc);
+	return ret;
+}
+
+static const struct fdtype_ops tty_ops = {
+	.type	= FD_TYPES__TTY,
+	.dump	= dump_one_pty,
+};
+
+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