2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-22 09:58:09 +00:00
criu/tty.c
Cyrill Gorcunov 4fd669fca6 tty: Drop TTY_SUBTYPE_SLAVE from /dev/console and /dev/tty
They are confusing code readers. Lets save these @subtypes
for Unix98 pty peers where they are really important.

Signed-off-by: Cyrill Gorcunov <gorcunov@openvz.org>
Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
2015-04-02 20:20:23 +03:00

1611 lines
36 KiB
C

#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 "asm/types.h"
#include "syscall.h"
#include "files.h"
#include "cr_options.h"
#include "imgset.h"
#include "servicefd.h"
#include "image.h"
#include "util.h"
#include "log.h"
#include "list.h"
#include "util-pie.h"
#include "proc_parse.h"
#include "file-ids.h"
#include "files-reg.h"
#include "namespaces.h"
#include "protobuf.h"
#include "protobuf/tty.pb-c.h"
#include "parasite-syscall.h"
#include "parasite.h"
#include "pstree.h"
#include "tty.h"
/*
* Here are some notes about overall TTY c/r design. At moment
* we support unix98 ptys only. Supporting legacy BSD terminals
* is impossible without help from the kernel side -- the indices
* of such terminals are not reported anywhere in the kernel so that
* we can't figure out active pairs.
*
* 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.
*
* Note the /dev/pts/ is rather convenient agreement and internally the
* kernel doesn't care where exactly the inodes of ptys are laying --
* it depends on "devpts" mount point path.
*/
#undef LOG_PREFIX
#define LOG_PREFIX "tty: "
struct tty_info_entry {
struct list_head list;
TtyInfoEntry *tie;
};
struct tty_info {
struct list_head list;
struct file_desc d;
struct file_desc *reg_d;
TtyFileEntry *tfe;
TtyInfoEntry *tie;
struct list_head sibling;
struct tty_driver *driver;
bool create;
bool inherit;
};
struct tty_dump_info {
struct list_head list;
u32 id;
pid_t sid;
pid_t pgrp;
int fd;
struct tty_driver *driver;
};
static LIST_HEAD(all_tty_info_entries);
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 256 bytes of memory.
* Pretty acceptable trade off in a sake of simplicity.
*/
#define MAX_TTYS 1024
/*
* Custom indices should be even numbers just in case if we
* need odds for pair numbering someday.
*/
#define MAX_PTY_INDEX 1000
#define CONSOLE_INDEX 1002
#define VT_INDEX 1004
#define CTTY_INDEX 1006
#define INDEX_ERR (MAX_TTYS + 1)
static DECLARE_BITMAP(tty_bitmap, (MAX_TTYS << 1));
static DECLARE_BITMAP(tty_active_pairs, (MAX_TTYS << 1));
struct tty_driver {
short type;
short subtype;
char *name;
int index;
int (*fd_get_index)(int fd, const struct fd_parms *p);
int (*img_get_index)(struct tty_info *ti);
int (*open)(struct tty_info *ti);
};
#define TTY_SUBTYPE_MASTER 0x0001
#define TTY_SUBTYPE_SLAVE 0x0002
static int ptm_fd_get_index(int fd, const struct fd_parms *p)
{
int index;
if (ioctl(fd, TIOCGPTN, &index)) {
pr_perror("Can't obtain ptmx index");
return INDEX_ERR;
}
if (index > MAX_PTY_INDEX) {
pr_err("Index %d on ptmx is too big\n", index);
return INDEX_ERR;
}
return index;
}
static int pty_get_index(struct tty_info *ti)
{
return ti->tie->pty->index;
}
static int pty_open_ptmx(struct tty_info *info);
static struct tty_driver ptm_driver = {
.type = TTY_TYPE__PTY,
.subtype = TTY_SUBTYPE_MASTER,
.name = "ptmx",
.fd_get_index = ptm_fd_get_index,
.img_get_index = pty_get_index,
.open = pty_open_ptmx,
};
static int open_simple_tty(struct tty_info *info);
static struct tty_driver console_driver = {
.type = TTY_TYPE__CONSOLE,
.name = "console",
.index = CONSOLE_INDEX,
.open = open_simple_tty,
};
static struct tty_driver ctty_driver = {
.type = TTY_TYPE__CTTY,
.name = "ctty",
.index = CTTY_INDEX,
.open = open_simple_tty,
};
static struct tty_driver vt_driver = {
.type = TTY_TYPE__VT,
.name = "vt",
.index = VT_INDEX,
.open = open_simple_tty,
};
static int pts_fd_get_index(int fd, const struct fd_parms *p)
{
int index;
const struct fd_link *link = p->link;
char *pos = strrchr(link->name, '/');
if (!pos || pos == (link->name + link->len - 1)) {
pr_err("Unexpected format on path %s\n", link->name + 1);
return INDEX_ERR;
}
index = atoi(pos + 1);
if (index > MAX_PTY_INDEX) {
pr_err("Index %d on pts is too big\n", index);
return INDEX_ERR;
}
return index;
}
static struct tty_driver pts_driver = {
.type = TTY_TYPE__PTY,
.subtype = TTY_SUBTYPE_SLAVE,
.name = "pts",
.fd_get_index = pts_fd_get_index,
.img_get_index = pty_get_index,
.open = pty_open_ptmx,
};
struct tty_driver *get_tty_driver(int major, int minor)
{
switch (major) {
case TTYAUX_MAJOR:
if (minor == 2)
return &ptm_driver;
else if (minor == 1)
return &console_driver;
else if (minor == 0)
return &ctty_driver;
break;
case TTY_MAJOR:
if (minor > MIN_NR_CONSOLES && minor < MAX_NR_CONSOLES)
/*
* Minors [MIN_NR_CONSOLES; MAX_NR_CONSOLES] stand
* for consoles (virtual terminals, VT in terms
* of kernel).
*/
return &vt_driver;
case UNIX98_PTY_MASTER_MAJOR ... (UNIX98_PTY_MASTER_MAJOR + UNIX98_PTY_MAJOR_COUNT - 1):
return &ptm_driver;
case UNIX98_PTY_SLAVE_MAJOR:
return &pts_driver;
}
return NULL;
}
static inline int is_pty(struct tty_driver *driver)
{
return driver->type == TTY_TYPE__PTY;
}
/*
* /dev/ptmx is a shared resource between all tasks
* so we need to serialize access to it.
*/
static mutex_t *tty_mutex;
static bool tty_is_master(struct tty_info *info);
int prepare_shared_tty(void)
{
tty_mutex = shmalloc(sizeof(*tty_mutex));
if (!tty_mutex) {
pr_err("Can't create ptmx index mutex\n");
return -1;
}
mutex_init(tty_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 { \
struct termios __t; \
\
memcpy((d)->c_cc, (s)->c_cc, \
sizeof(__t.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 tty_gen_id(struct tty_driver *driver, int index)
{
return (index << 1) + (driver->subtype == TTY_SUBTYPE_MASTER);
}
static int tty_get_index(u32 id)
{
return id >> 1;
}
/* Make sure the active pairs do exist */
int tty_verify_active_pairs(void)
{
unsigned long i, unpaired_slaves = 0;
for_each_bit(i, tty_active_pairs) {
if ((i % 2) == 0) {
if (test_bit(i + 1, tty_active_pairs)) {
i++;
continue;
}
if (!opts.shell_job) {
pr_err("Found slave peer index %d without "
"correspond master peer\n",
tty_get_index(i));
return -1;
}
pr_debug("Unpaired slave %d\n", tty_get_index(i));
if (++unpaired_slaves > 1) {
pr_err("Only one slave external peer "
"is allowed (index %d)\n",
tty_get_index(i));
return -1;
}
}
}
return 0;
}
static int tty_test_and_set(int bit, unsigned long *bitmap)
{
int ret;
ret = test_bit(bit, bitmap);
if (!ret)
set_bit(bit, bitmap);
return ret;
}
/*
* Generate a regular file object in case if such is missed
* in the image file, ie obsolete interface has been used on
* checkpoint.
*/
static struct file_desc *pty_alloc_reg(struct tty_info *info, bool add)
{
TtyFileEntry *tfe = info->tfe;
const size_t namelen = 64;
struct reg_file_info *r;
static struct file_desc_ops noops = {};
r = xzalloc(sizeof(*r) + sizeof(*r->rfe) + namelen);
if (!r)
return NULL;
r->rfe = (void *)r + sizeof(*r);
reg_file_entry__init(r->rfe);
r->rfe->name = (void *)r + sizeof(*r) + sizeof(*r->rfe);
if (tty_is_master(info))
strcpy(r->rfe->name, "/dev/ptmx");
else
snprintf(r->rfe->name, namelen, "/dev/pts/%u",
info->tie->pty->index);
if (add)
file_desc_add(&r->d, tfe->id, &noops);
else
file_desc_init(&r->d, tfe->id, &noops);
r->rfe->id = tfe->id;
r->rfe->flags = tfe->flags;
r->rfe->fown = tfe->fown;
r->path = &r->rfe->name[1];
return &r->d;
}
/*
* In case if we need to open a fake pty (for example
* a master peer which were deleted at checkpoint moment,
* or open a slave peer when restoring control terminal)
* we need to create a new reg-file object taking @info
* as a template. Here is a trick though: the @info might
* represent master peer while we need to allocate a slave
* one and the reverse. For such case taking path from the
* @info as a template we generate that named 'inverted-path'.
*
* For example if the master peer was /dev/pts/ptmx with index 1,
* the inverted path is /dev/pts/1, for inverted slaves it's simplier
* we just add 'ptmx' postfix.
*/
static struct reg_file_info *pty_alloc_fake_reg(struct tty_info *info, int subtype)
{
struct reg_file_info *new, *orig;
struct file_desc *fake_desc;
pr_debug("Allocating fake descriptor for %#x (reg_d %p)\n",
info->tfe->id, info->reg_d);
BUG_ON(!info->reg_d);
BUG_ON(!is_pty(info->driver));
fake_desc = pty_alloc_reg(info, false);
if (!fake_desc)
return NULL;
orig = container_of(info->reg_d, struct reg_file_info, d);
new = container_of(fake_desc, struct reg_file_info, d);
if ((subtype == TTY_SUBTYPE_MASTER && tty_is_master(info)) ||
(subtype == TTY_SUBTYPE_SLAVE && !tty_is_master(info))) {
new->path = xstrdup(orig->path);
new->rfe->name = &new->path[1];
} else {
char *pos = strrchr(orig->rfe->name, '/');
size_t len = strlen(orig->rfe->name) + 1;
size_t slash_at = pos - orig->rfe->name;
char *inverted_path = xmalloc(len + 32);
BUG_ON(!pos || !inverted_path);
memcpy(inverted_path, orig->rfe->name, slash_at + 1);
if (subtype == TTY_SUBTYPE_MASTER)
strcat(inverted_path, "ptmx");
else {
if (slash_at >= 3 && strncmp(&inverted_path[slash_at - 3], "pts", 3))
snprintf(&inverted_path[slash_at + 1], 10, "pts/%u",
info->tie->pty->index);
else
snprintf(&inverted_path[slash_at + 1], 10, "%u",
info->tie->pty->index);
}
new->rfe->name = inverted_path;
new->path = &inverted_path[1];
}
return new;
}
#define pty_alloc_fake_master(info) pty_alloc_fake_reg(info, TTY_SUBTYPE_MASTER)
#define pty_alloc_fake_slave(info) pty_alloc_fake_reg(info, TTY_SUBTYPE_SLAVE)
static void pty_free_fake_reg(struct reg_file_info **r)
{
if (*r) {
xfree((*r)->rfe->name);
xfree((*r));
*r = NULL;
}
}
static int open_tty_reg(struct file_desc *reg_d, u32 flags)
{
return open_path(reg_d, do_open_reg_noseek_flags, &flags);
}
static char *path_from_reg(struct file_desc *d)
{
struct reg_file_info *rfi = container_of(d, struct reg_file_info, d);
return rfi->path;
}
static int pty_open_ptmx_index(struct file_desc *d, int index, int flags)
{
int fds[32], i, ret = -1, cur_idx;
memset(fds, 0xff, sizeof(fds));
mutex_lock(tty_mutex);
for (i = 0; i < ARRAY_SIZE(fds); i++) {
fds[i] = open_tty_reg(d, flags);
if (fds[i] < 0) {
pr_perror("Can't open %s", path_from_reg(d));
break;
}
if (ioctl(fds[i], TIOCGPTN, &cur_idx)) {
pr_perror("Can't obtain current index on %s",
path_from_reg(d));
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",
path_from_reg(d), index);
break;
}
for (i = 0; i < ARRAY_SIZE(fds); i++) {
if (fds[i] >= 0)
close(fds[i]);
}
mutex_unlock(tty_mutex);
return ret;
}
static int unlock_pty(int fd)
{
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(fd, TIOCSPTLCK, &lock)) {
pr_err("Unable to unlock pty device via y%d\n", fd);
return -1;
}
return 0;
}
static int lock_pty(int fd)
{
const int lock = 1;
if (ioctl(fd, TIOCSPTLCK, &lock)) {
pr_err("Unable to lock pty device via %d\n", fd);
return -1;
}
return 0;
}
static int tty_set_sid(int fd)
{
if (ioctl(fd, TIOCSCTTY, 1)) {
pr_perror("Can't set sid on terminal fd %d", fd);
return -1;
}
return 0;
}
static int tty_set_prgp(int fd, int group)
{
if (ioctl(fd, TIOCSPGRP, &group)) {
pr_perror("Failed to set group %d on %d", group, fd);
return -1;
}
return 0;
}
static int tty_restore_ctl_terminal(struct file_desc *d, int fd)
{
struct tty_info *info = container_of(d, struct tty_info, d);
struct tty_driver *driver = info->driver;
struct reg_file_info *fake = NULL;
struct file_desc *slave_d;
int slave = -1, ret = -1, index = -1;
if (!is_service_fd(fd, CTL_TTY_OFF))
return 0;
if (driver->img_get_index)
index = driver->img_get_index(info);
else
index = driver->index;
if (is_pty(info->driver)) {
fake = pty_alloc_fake_slave(info);
if (!fake)
goto err;
slave_d = &fake->d;
} else
slave_d = info->reg_d;
slave = open_tty_reg(slave_d, O_RDONLY);
if (slave < 0) {
pr_perror("Can't open %s", path_from_reg(slave_d));
goto err;
}
pr_info("Restore session %d by %d tty (index %d)\n",
info->tie->sid, (int)getpid(), index);
ret = tty_set_sid(slave);
if (!ret)
ret = tty_set_prgp(slave, info->tie->pgrp);
close(slave);
err:
pty_free_fake_reg(&fake);
close(fd);
return ret;
}
static bool tty_is_master(struct tty_info *info)
{
if (info->driver->subtype == TTY_SUBTYPE_MASTER)
return true;
switch (info->driver->type) {
case TTY_TYPE__CONSOLE:
case TTY_TYPE__CTTY:
return true;
case TTY_TYPE__VT:
if (!opts.shell_job)
return true;
}
return false;
}
static bool tty_is_hung(struct tty_info *info)
{
return info->tie->termios == NULL;
}
static bool tty_has_active_pair(struct tty_info *info)
{
int d = tty_is_master(info) ? -1 : + 1;
return test_bit(info->tfe->tty_info_id + d,
tty_active_pairs);
}
static void tty_show_pty_info(char *prefix, struct tty_info *info)
{
int index = -1;
struct tty_driver *driver = info->driver;
if (driver->img_get_index)
index = driver->img_get_index(info);
else
index = driver->index;
pr_info("%s driver %s id %#x index %d (master %d sid %d pgrp %d inherit %d)\n",
prefix, info->driver->name, info->tfe->id, index,
tty_is_master(info), info->tie->sid, info->tie->pgrp, info->inherit);
}
struct tty_parms {
int tty_id;
unsigned has;
#define HAS_TERMIOS_L 0x1
#define HAS_TERMIOS 0x2
#define HAS_WINS 0x4
struct termios tl;
struct termios t;
struct winsize w;
};
static int do_restore_tty_parms(void *arg, int fd)
{
struct tty_parms *p = arg;
/*
* Only locked termios need CAP_SYS_ADMIN, but we
* restore them all here, since the regular tremios
* restore is affected by locked and thus we would
* have to do synchronous usernsd call which is not
* nice.
*
* Window size is restored here as it might depend
* on termios too. Just to be on the safe side.
*/
if ((p->has & HAS_TERMIOS_L) &&
ioctl(fd, TIOCSLCKTRMIOS, &p->tl) < 0)
goto err;
if ((p->has & HAS_TERMIOS) &&
ioctl(fd, TCSETS, &p->t) < 0)
goto err;
if ((p->has & HAS_WINS) &&
ioctl(fd, TIOCSWINSZ, &p->w) < 0)
goto err;
return 0;
err:
pr_perror("Can't set tty params on %d", p->tty_id);
return -1;
}
static int restore_tty_params(int fd, struct tty_info *info)
{
struct tty_parms p;
/*
* 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 taken from the system headers will
* never be extended.
*/
p.has = 0;
p.tty_id = info->tfe->id;
if (info->tie->termios_locked) {
memzero(&p.tl, sizeof(p.tl));
p.has |= HAS_TERMIOS_L;
termios_copy(&p.tl, info->tie->termios_locked);
}
if (info->tie->termios) {
memzero(&p.t, sizeof(p.t));
p.has |= HAS_TERMIOS;
termios_copy(&p.t, info->tie->termios);
}
if (info->tie->winsize) {
memzero(&p.w, sizeof(p.w));
p.has |= HAS_WINS;
winsize_copy(&p.w, info->tie->winsize);
}
return userns_call(do_restore_tty_parms, UNS_ASYNC, &p, sizeof(p), fd);
}
static int pty_open_slaves(struct tty_info *info)
{
int sock = -1, fd = -1, ret = -1;
struct fdinfo_list_entry *fle;
struct tty_info *slave;
sock = socket(PF_UNIX, SOCK_DGRAM, 0);
if (sock < 0) {
pr_perror("Can't create socket");
goto err;
}
list_for_each_entry(slave, &info->sibling, sibling) {
BUG_ON(tty_is_master(slave));
fd = open_tty_reg(slave->reg_d, slave->tfe->flags | O_NOCTTY);
if (fd < 0) {
pr_perror("Can't open slave %s", path_from_reg(slave->reg_d));
goto err;
}
if (restore_tty_params(fd, slave))
goto err;
fle = file_master(&slave->d);
pr_debug("send slave %#x fd %d connected on %s (pid %d)\n",
slave->tfe->id, fd, path_from_reg(slave->reg_d), 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_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 (rst_file_params(fd, info->tfe->fown, info->tfe->flags))
close_safe(&fd);
return fd;
}
static int pty_open_unpaired_slave(struct file_desc *d, struct tty_info *slave)
{
struct reg_file_info *fake = NULL;
int master = -1, ret = -1, fd = -1;
/*
* We may have 2 cases here: the slave either need to
* be inherited, either it requires a fake master.
*/
if (likely(slave->inherit)) {
fd = dup(get_service_fd(SELF_STDIN_OFF));
if (fd < 0) {
pr_perror("Can't dup SELF_STDIN_OFF");
return -1;
}
pr_info("Migrated slave peer %x -> to fd %d\n",
slave->tfe->id, fd);
} else {
fake = pty_alloc_fake_master(slave);
if (!fake)
goto err;
master = pty_open_ptmx_index(&fake->d, slave->tie->pty->index, O_RDONLY);
if (master < 0) {
pr_perror("Can't open fale %x (index %d)",
slave->tfe->id, slave->tie->pty->index);
goto err;
}
unlock_pty(master);
fd = open_tty_reg(slave->reg_d, slave->tfe->flags | O_NOCTTY);
if (fd < 0) {
pr_perror("Can't open slave %s", path_from_reg(slave->reg_d));
goto err;
}
}
if (restore_tty_params(fd, slave))
goto err;
/*
* If tty is migrated we need to set its group
* to the parent group, because signals on key
* presses are delivered to a group of terminal.
*
* Note, at this point the group/session should
* be already restored properly thus we can simply
* use syscalls instead of lookup via process tree.
*/
if (likely(slave->inherit)) {
/*
* The restoration procedure only works if we're
* migrating not a session leader, otherwise it's
* not allowed to restore a group and one better to
* checkpoint complete process tree together with
* the process which keeps the master peer.
*/
if (root_item->sid != root_item->pid.virt) {
pr_debug("Restore inherited group %d\n",
getpgid(getppid()));
if (tty_set_prgp(fd, getpgid(getppid())))
goto err;
}
}
if (pty_open_slaves(slave))
goto err;
ret = fd;
fd = -1;
err:
close_safe(&master);
close_safe(&fd);
pty_free_fake_reg(&fake);
return ret;
}
static int pty_open_ptmx(struct tty_info *info)
{
int master = -1;
master = pty_open_ptmx_index(info->reg_d, info->tie->pty->index, info->tfe->flags);
if (master < 0) {
pr_perror("Can't open %x (index %d)",
info->tfe->id, info->tie->pty->index);
return -1;
}
unlock_pty(master);
if (restore_tty_params(master, info))
goto err;
if (info->tie->packet_mode) {
int packet_mode = 1;
if (ioctl(master, TIOCPKT, &packet_mode) < 0) {
pr_perror("Can't set packed mode on %x",
info->tfe->id);
goto err;
}
}
if (pty_open_slaves(info))
goto err;
if (info->tie->locked)
lock_pty(master);
return master;
err:
close_safe(&master);
return -1;
}
static int open_simple_tty(struct tty_info *info)
{
int fd = -1;
fd = open_tty_reg(info->reg_d, info->tfe->flags);
if (fd < 0) {
pr_perror("Can't open %s %x",
info->driver->name, info->tfe->id);
return -1;
}
if (restore_tty_params(fd, info))
goto err;
return fd;
err:
close_safe(&fd);
return -1;
}
static int tty_open(struct file_desc *d)
{
struct tty_info *info = container_of(d, struct tty_info, d);
tty_show_pty_info("open", info);
if (!info->create)
return receive_tty(info);
if (is_pty(info->driver) && !tty_is_master(info))
return pty_open_unpaired_slave(d, info);
return info->driver->open(info);
}
static int tty_transport(FdinfoEntry *fe, struct file_desc *d)
{
struct tty_info *info = container_of(d, struct tty_info, d);
return !info->create;
}
static void tty_collect_fd(struct file_desc *d, struct fdinfo_list_entry *fle,
struct rst_info *ri)
{
struct tty_info *info = container_of(d, struct tty_info, d);
struct list_head *tgt;
/*
* Unix98 pty slave peers requires the master peers being
* opened before them. In turn, current ttys should be opened
* after the slave peers so session must alread exist.
*/
if (tty_is_master(info) && info->driver->type != TTY_TYPE__CTTY)
tgt = &ri->fds;
else if (info->driver->type == TTY_TYPE__CTTY)
tgt = &ri->tty_ctty;
else
tgt = &ri->tty_slaves;
list_add_tail(&fle->ps_list, tgt);
}
static struct file_desc_ops tty_desc_ops = {
.type = FD_TYPES__TTY,
.open = tty_open,
.post_open = tty_restore_ctl_terminal,
.want_transport = tty_transport,
.collect_fd = tty_collect_fd,
};
static struct pstree_item *find_first_sid(int sid)
{
struct pstree_item *item;
for_each_pstree_item(item) {
if (item->sid == sid)
return item;
}
return NULL;
}
static int tty_find_restoring_task(struct tty_info *info)
{
struct pstree_item *item;
/*
* The overall scenario is the following (note
* we might have corrupted image so don't believe
* anything).
*
* SID is present on a peer
* ------------------------
*
* - if it's master peer and we have as well a slave
* peer then prefer restore controlling terminal
* via slave peer
*
* - if it's master peer without slave, there must be
* a SID leader who will be restoring the peer
*
* - if it's a slave peer and no session leader found
* than we need an option to inherit terminal
*
* No SID present on a peer
* ------------------------
*
* - if it's a master peer than we are in good shape
* and continue in a normal way, we're the peer keepers
*
* - if it's a slave peer and no appropriate master peer
* found we need an option to inherit terminal
*
* In any case if it's hungup peer, then we jump out
* early since it will require fake master peer and
* rather non-usable anyway.
*/
if (tty_is_hung(info)) {
pr_debug("Hungup terminal found id %x\n", info->tfe->id);
return 0;
}
/*
* Current tty should be skipped here: the
* underlied _real_ pty (or anything else
* driver in future) should restore the
* session.
*/
if (info->driver->type == TTY_TYPE__CTTY)
return 0;
if (info->tie->sid) {
if (!tty_is_master(info)) {
if (tty_has_active_pair(info))
return 0;
else
goto shell_job;
}
/*
* Find out the task which is session leader
* and it can restore the controlling terminal
* for us.
*/
item = find_first_sid(info->tie->sid);
if (item && item->pid.virt == item->sid) {
pr_info("Set a control terminal %x to %d\n",
info->tfe->id, info->tie->sid);
return prepare_ctl_tty(item->pid.virt,
rsti(item),
info->tfe->id);
}
goto notask;
} else {
if (tty_is_master(info))
return 0;
if (tty_has_active_pair(info))
return 0;
}
shell_job:
if (opts.shell_job) {
pr_info("Inherit terminal for id %x\n", info->tfe->id);
info->inherit = true;
return 0;
}
notask:
pr_err("No task found with sid %d\n", info->tie->sid);
return -1;
}
static int tty_setup_orphan_slavery(void)
{
struct tty_info *info, *peer, *m;
list_for_each_entry(info, &all_ttys, list) {
struct fdinfo_list_entry *a, *b;
bool has_leader = false;
if (tty_is_master(info))
continue;
a = file_master(&info->d);
m = info;
list_for_each_entry(peer, &info->sibling, sibling) {
if (tty_is_master(peer)) {
has_leader = true;
break;
}
/*
* Same check as in pipes and files -- need to
* order slave ends so that they do not dead lock
* waiting for each other.
*/
b = file_master(&peer->d);
if (fdinfo_rst_prio(b, a)) {
a = b;
m = peer;
}
}
if (!has_leader) {
m->create = true;
pr_debug("Found orphan slave fake leader (%#x)\n",
m->tfe->id);
}
}
return 0;
}
int tty_setup_slavery(void)
{
struct tty_info *info, *peer, *m;
list_for_each_entry(info, &all_ttys, list) {
if (tty_find_restoring_task(info))
return -1;
if (!is_pty(info->driver))
continue;
peer = info;
list_for_each_entry_safe_continue(peer, m, &all_ttys, list) {
if (!is_pty(peer->driver))
continue;
if (peer->tie->pty->index != info->tie->pty->index)
continue;
if (tty_find_restoring_task(peer))
return -1;
list_add(&peer->sibling, &info->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->sibling, sibling)
tty_show_pty_info(" `- sibling", peer);
}
return tty_setup_orphan_slavery();
}
static int verify_termios(u32 id, TermiosEntry *e)
{
if (e && e->n_c_cc < TERMIOS_NCC) {
pr_err("pty ID %#x n_c_cc (%d) has wrong value\n",
id, (int)e->n_c_cc);
return -1;
}
return 0;
}
#define term_opts_missing_cmp(p, op) \
(!(p)->tie->termios op \
!(p)->tie->termios_locked op \
!(p)->tie->winsize)
#define term_opts_missing_any(p) \
term_opts_missing_cmp(p, ||)
#define term_opts_missing_all(p) \
term_opts_missing_cmp(p, &&)
static int verify_info(struct tty_info *info)
{
if (!info->driver) {
pr_err("Unknown driver master peer %x\n", info->tfe->id);
return -1;
}
/*
* Master peer must have all parameters present,
* while slave peer must have either all parameters present
* or don't have them at all.
*/
if (term_opts_missing_any(info)) {
if (tty_is_master(info)) {
pr_err("Corrupted master peer %x\n", info->tfe->id);
return -1;
} else if (!term_opts_missing_all(info)) {
pr_err("Corrupted slave peer %x\n", info->tfe->id);
return -1;
}
}
if (verify_termios(info->tfe->id, info->tie->termios_locked) ||
verify_termios(info->tfe->id, info->tie->termios))
return -1;
if (info->tie->termios && info->tfe->tty_info_id > (MAX_TTYS << 1))
return -1;
return 0;
}
static TtyInfoEntry *lookup_tty_info_entry(u32 id)
{
struct tty_info_entry *e;
list_for_each_entry(e, &all_tty_info_entries, list) {
if (e->tie->id == id)
return e->tie;
}
return NULL;
}
static int collect_one_tty_info_entry(void *obj, ProtobufCMessage *msg)
{
struct tty_info_entry *info = obj;
info->tie = pb_msg(msg, TtyInfoEntry);
switch (info->tie->type) {
case TTY_TYPE__PTY:
if (!info->tie->pty) {
pr_err("No PTY data found (id %x), corrupted image?\n",
info->tie->id);
return -1;
}
break;
case TTY_TYPE__CTTY:
case TTY_TYPE__CONSOLE:
case TTY_TYPE__VT:
if (info->tie->pty) {
pr_err("PTY data found (id %x), corrupted image?\n",
info->tie->id);
return -1;
}
break;
default:
pr_err("Unexpected TTY type %d (id %x)\n",
info->tie->type, info->tie->id);
return -1;
}
INIT_LIST_HEAD(&info->list);
list_add(&info->list, &all_tty_info_entries);
return 0;
}
struct collect_image_info tty_info_cinfo = {
.fd_type = CR_FD_TTY_INFO,
.pb_type = PB_TTY_INFO,
.priv_size = sizeof(struct tty_info_entry),
.collect = collect_one_tty_info_entry,
};
static int collect_one_tty(void *obj, ProtobufCMessage *msg)
{
struct tty_info *info = obj;
info->tfe = pb_msg(msg, TtyFileEntry);
info->tie = lookup_tty_info_entry(info->tfe->tty_info_id);
if (!info->tie) {
pr_err("No tty-info-id %x found on id %x\n",
info->tfe->tty_info_id, info->tfe->id);
return -1;
}
INIT_LIST_HEAD(&info->sibling);
info->driver = get_tty_driver(major(info->tie->rdev), minor(info->tie->rdev));
info->create = tty_is_master(info);
info->inherit = false;
if (verify_info(info))
return -1;
/*
* The image might have no reg file record in old CRIU, so
* lets don't fail for a while. After a couple of releases
* simply require the record to present.
*/
info->reg_d = try_collect_special_file(info->tfe->id, 1);
if (!info->reg_d) {
if (is_pty(info->driver)) {
info->reg_d = pty_alloc_reg(info, true);
if (!info->reg_d) {
pr_err("Can't generate new reg descriptor for id %#x\n",
info->tfe->id);
return -1;
}
} else {
pr_err("No reg_d descriptor for id %#x\n", info->tfe->id);
return -1;
}
}
/*
* The tty peers which have no @termios are hung up,
* so don't mark them as active, we create them with
* faked master and they are rather a rudiment which
* can't be used. Most likely they appear if a user has
* dumped program when it was closing a peer.
*/
if (is_pty(info->driver) && info->tie->termios)
tty_test_and_set(info->tfe->tty_info_id, tty_active_pairs);
pr_info("Collected tty ID %#x (%s)\n", info->tfe->id, info->driver->name);
list_add(&info->list, &all_ttys);
return file_desc_add(&info->d, info->tfe->id, &tty_desc_ops);
}
struct collect_image_info tty_cinfo = {
.fd_type = CR_FD_TTY_FILES,
.pb_type = PB_TTY_FILE,
.priv_size = sizeof(struct tty_info),
.collect = collect_one_tty,
};
/* Make sure the ttys we're dumping do belong our process tree */
int dump_verify_tty_sids(void)
{
struct tty_dump_info *dinfo, *n;
int ret = 0;
/*
* There might be a cases where we get sid/pgid on
* slave peer. For example the application is running
* with redirection and we're migrating shell job.
*
* # ./app < /dev/zero > /dev/zero &2>1
*
* Which produce a tree like
* PID PPID PGID SID
* root 23786 23784 23786 23786 pts/0 \_ -bash
* root 24246 23786 24246 23786 pts/0 \_ ./app
*
* And the application goes background, then we dump
* it from the same shell.
*
* In this case we simply zap sid/pgid and inherit
* the peer from the current terminal on restore.
*/
list_for_each_entry_safe(dinfo, n, &all_ttys, list) {
if (!ret && dinfo->sid) {
struct pstree_item *item = find_first_sid(dinfo->sid);
if (!item || item->pid.virt != dinfo->sid) {
if (!opts.shell_job) {
pr_err("Found dangling tty with sid %d pgid %d (%s) on peer fd %d.\n",
dinfo->sid, dinfo->pgrp,
dinfo->driver->name, dinfo->fd);
/*
* First thing people do with criu is dump smth
* run from shell. This is typical pitfall, warn
* user about it explicitly.
*/
pr_msg("Task attached to shell terminal. "
"Consider using --" OPT_SHELL_JOB " option. "
"More details on http://criu.org/Simple_loop\n");
ret = -1;
}
}
}
xfree(dinfo);
}
return ret;
}
static int dump_tty_info(int lfd, u32 id, const struct fd_parms *p, struct tty_driver *driver, int index)
{
TtyInfoEntry info = TTY_INFO_ENTRY__INIT;
TermiosEntry termios = TERMIOS_ENTRY__INIT;
TermiosEntry termios_locked = TERMIOS_ENTRY__INIT;
WinsizeEntry winsize = WINSIZE_ENTRY__INIT;
TtyPtyEntry pty = TTY_PTY_ENTRY__INIT;
struct parasite_tty_args *pti;
struct tty_dump_info *dinfo;
struct termios t;
struct winsize w;
int ret = -1;
/*
* Make sure the structures the system provides us
* correlates well with protobuf templates.
*/
BUILD_BUG_ON(ARRAY_SIZE(t.c_cc) < TERMIOS_NCC);
BUILD_BUG_ON(sizeof(termios.c_cc) != sizeof(void *));
BUILD_BUG_ON((sizeof(termios.c_cc) * TERMIOS_NCC) < sizeof(t.c_cc));
pti = parasite_dump_tty(p->ctl, p->fd, driver->type);
if (!pti)
return -1;
dinfo = xmalloc(sizeof(*dinfo));
if (!dinfo)
return -1;
dinfo->id = id;
dinfo->sid = pti->sid;
dinfo->pgrp = pti->pgrp;
dinfo->fd = p->fd;
dinfo->driver = driver;
list_add_tail(&dinfo->list, &all_ttys);
info.id = id;
info.sid = pti->sid;
info.pgrp = pti->pgrp;
info.rdev = p->stat.st_rdev;
info.locked = pti->st_lock;
info.exclusive = pti->st_excl;
info.packet_mode = pti->st_pckt;
info.type = driver->type;
if (info.type == TTY_TYPE__PTY) {
info.pty = &pty;
pty.index = index;
}
/*
* Nothing we can do on hanging up terminal,
* just write out minimum information we can
* gather.
*/
if (pti->hangup)
return pb_write_one(img_from_set(glob_imgset, CR_FD_TTY_INFO), &info, PB_TTY_INFO);
/*
* Now trace the paired/unpaired ttys. For example
* the task might have slave peer assigned but no
* master peer. Such "detached" master peers are
* not yet supported by our tool and better to
* inform a user about such situation.
*/
if (is_pty(driver))
tty_test_and_set(id, tty_active_pairs);
info.termios = &termios;
info.termios_locked = &termios_locked;
info.winsize = &winsize;
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;
memzero(&t, sizeof(t));
if (ioctl(lfd, TCGETS, &t) < 0) {
pr_perror("Can't get tty params on %x", id);
goto out;
}
termios_copy(&termios, &t);
memzero(&t, sizeof(t));
if (ioctl(lfd, TIOCGLCKTRMIOS, &t) < 0) {
pr_perror("Can't get tty locked params on %x", id);
goto out;
}
termios_copy(&termios_locked, &t);
memzero(&w, sizeof(w));
if (ioctl(lfd, TIOCGWINSZ, &w) < 0) {
pr_perror("Can't get tty window params on %x", id);
goto out;
}
winsize_copy(&winsize, &w);
ret = pb_write_one(img_from_set(glob_imgset, CR_FD_TTY_INFO), &info, PB_TTY_INFO);
out:
xfree(termios.c_cc);
xfree(termios_locked.c_cc);
return ret;
}
static int dump_one_tty(int lfd, u32 id, const struct fd_parms *p)
{
TtyFileEntry e = TTY_FILE_ENTRY__INIT;
int ret = 0, index = -1;
struct tty_driver *driver;
pr_info("Dumping tty %d with id %#x\n", lfd, id);
if (dump_one_reg_file(lfd, id, p))
return -1;
driver = get_tty_driver(major(p->stat.st_rdev), minor(p->stat.st_rdev));
if (driver->fd_get_index)
index = driver->fd_get_index(lfd, p);
else
index = driver->index;
if (index == INDEX_ERR) {
pr_info("Can't obtain index on tty %d id %#x\n", lfd, id);
return -1;
}
e.id = id;
e.tty_info_id = tty_gen_id(driver, index);
e.flags = p->flags;
e.fown = (FownEntry *)&p->fown;
/*
* FIXME
*
* Figure out how to fetch data buffered in terminal.
* For a while simply flush before dumping. Note
* we don't check for errors here since it makes
* no sense anyway, the buffered data is not handled
* properly yet.
*
* Note as well that if we have only one peer here
* the external end might be sending the data to us
* again and again while kernel buffer is not full,
* this might lead to endless SIGTTOU signal delivery
* to the dumpee, ruining checkpoint procedure.
*
* So simply do not flush the line while we dump
* parameters tty never was being a guaranteed delivery
* transport anyway.
*/
if (!tty_test_and_set(e.tty_info_id, tty_bitmap))
ret = dump_tty_info(lfd, e.tty_info_id, p, driver, index);
if (!ret)
ret = pb_write_one(img_from_set(glob_imgset, CR_FD_TTY_FILES), &e, PB_TTY_FILE);
return ret;
}
const struct fdtype_ops tty_dump_ops = {
.type = FD_TYPES__TTY,
.dump = dump_one_tty,
};
int tty_prep_fds(void)
{
if (!opts.shell_job)
return 0;
if (!isatty(STDIN_FILENO)) {
pr_err("Standard stream is not a terminal, aborting\n");
return -1;
}
if (install_service_fd(SELF_STDIN_OFF, STDIN_FILENO) < 0) {
pr_perror("Can't dup stdin to SELF_STDIN_OFF");
return -1;
}
return 0;
}
void tty_fini_fds(void)
{
close_service_fd(SELF_STDIN_OFF);
}