[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