blob: 73d6aa03b17972be7d7c61ea629e61e74f2e029e [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2012, Parallels, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <linux/vzcalluser.h>
#include <linux/limits.h>
#include <sys/wait.h>
#include <termios.h>
#include <pty.h>
#include <grp.h>
#include <pwd.h>
#include <err.h>
#include <sys/ioctl.h>
#include "vzerror.h"
#include "logger.h"
#include "env.h"
#include "util.h"
#define DEV_TTY "/dev/tty"
#ifndef TIOSAK
#define TIOSAK _IO('T', 0x66) /* "Secure Attention Key" */
#endif
static volatile sig_atomic_t child_term;
static volatile sig_atomic_t win_changed;
static struct termios s_tios;
extern char *_proc_title;
extern int _proc_title_len;
static void set_proc_title(char *tty)
{
char *p;
p = tty;
if (p != NULL && !strncmp(p, "/dev/", 5))
p += 5;
memset(_proc_title, 0, _proc_title_len);
snprintf(_proc_title, _proc_title_len - 1, "vzctl: %s",
p != NULL ? p : "");
}
static int pty_alloc(int *master, int *slave, struct termios *tios,
struct winsize *ws)
{
char name[PATH_MAX];
if (openpty(master, slave, name, tios, ws) < 0) {
logger(-1, errno, "Unable to open pty");
return -1;
}
set_proc_title(name);
logger(2, 0, "Open %s", name);
return 0;
}
static void set_ctty(int ttyfd)
{
int fd;
if ((fd = open(DEV_TTY, O_RDWR | O_NOCTTY)) >= 0) {
ioctl(fd, TIOCNOTTY, NULL);
close(fd);
}
if (setsid() < 0)
logger(-1, errno, "setsid");
if (ioctl(ttyfd, TIOCSCTTY, NULL) < 0)
logger(-1, errno, "Failed to connect to controlling tty");
setpgrp();
}
static void raw_off(void)
{
if (tcsetattr(0, TCSADRAIN, &s_tios) == -1)
logger(-1, errno, "Unable to restore term attr");
}
static void raw_on(void)
{
struct termios tios;
if (tcgetattr(0, &tios) == -1) {
logger(-1, errno, "Unable to get term attr");
return;
}
memcpy(&s_tios, &tios, sizeof(struct termios));
cfmakeraw(&tios);
if (tcsetattr(0, TCSADRAIN, &tios) == -1)
logger(-1, errno, "Unable to set raw mode");
}
static int winchange(int info, int ptyfd)
{
int ret;
struct winsize ws;
ret = read(info, &ws, sizeof(ws));
if (ret < 0)
return -1;
else if (ret != sizeof(ws))
return 0;
ioctl(ptyfd, TIOCSWINSZ, &ws);
return 0;
}
static void child_handler(int sig)
{
child_term = 1;
}
static void winchange_handler(int sig)
{
win_changed = 1;
}
static int stdredir(int rdfd, int wrfd)
{
int lenr, lenw, lentotal, lenremain;
char buf[10240];
char *p;
if ((lenr = read(rdfd, buf, sizeof(buf)-1)) > 0) {
lentotal = 0;
lenremain = lenr;
p = buf;
while (lentotal < lenr) {
while ((lenw = write(wrfd, p, lenremain)) < 0)
if (errno != EINTR && errno != EAGAIN)
break;
if (lenw < 0) {
return -1;
} else {
lentotal += lenw;
lenremain -= lenw;
p += lenw;
}
}
} else if (lenr == 0) {
return -1;
} else {
if (errno == EAGAIN)
return 1;
else if (errno != EINTR)
return -1;
}
return 0;
}
static void e_loop(int r_in, int w_in, int r_out, int w_out, int info)
{
int n, fl = 0;
fd_set rd_set;
set_not_blk(r_in);
set_not_blk(r_out);
while (!child_term) {
/* Process SIGWINCH
* read winsize from stdin and send announce to the other end.
*/
if (win_changed) {
struct winsize ws;
if (!ioctl(r_in, TIOCGWINSZ, &ws))
write(info, &ws, sizeof(ws));
win_changed = 0;
}
FD_ZERO(&rd_set);
if (!(fl & 1))
FD_SET(r_in, &rd_set);
if (!(fl & 2))
FD_SET(r_out, &rd_set);
if (!(fl & 4))
FD_SET(info, &rd_set);
n = select(FD_SETSIZE, &rd_set, NULL, NULL, NULL);
if (n > 0) {
if (FD_ISSET(r_in, &rd_set))
if (stdredir(r_in, w_in) < 0) {
close(w_in);
fl |= 1;
}
if (FD_ISSET(r_out, &rd_set))
if (stdredir(r_out, w_out) < 0) {
close(r_out);
fl |= 2;
break;
}
if (FD_ISSET(info, &rd_set)) {
if (winchange(info, w_in) < 0)
fl |= 4;
}
} else if (n < 0 && errno != EINTR) {
close(r_out);
logger(-1, errno, "Error in select()");
break;
}
}
/* Flush fds */
if (!(fl & 2))
while (stdredir(r_out, w_out) == 0);
}
static void preload_lib()
{
/* Preload libnss */
(void)getpwnam("root");
endpwent();
(void)getgrnam("root");
endgrent();
}
int do_enter(vps_handler *h, envid_t veid, const char *root,
int argc, char **argv)
{
int pid, ret, status;
int in[2], out[2], st[2], info[2];
struct sigaction act;
int i;
int fd_flags[2];
/* Save stdin and stdout file descriptor flags */
for (i = 0; i < 2; i++)
{
fd_flags[i] = fcntl(i, F_GETFL);
if (fd_flags[i] < 0) {
logger(-1, errno, "Unable to get fd%d flags", i);
return VZ_SYSTEM_ERROR;
}
}
if (pipe(in) < 0 || pipe(out) < 0 || pipe(st) < 0 || pipe(info) < 0) {
logger(-1, errno, "Unable to create pipe");
return VZ_RESOURCE_ERROR;
}
if ((ret = h->setcontext(veid)))
return ret;
preload_lib();
child_term = 0;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDSTOP;
act.sa_handler = child_handler;
sigaction(SIGCHLD, &act, NULL);
act.sa_handler = SIG_IGN;
act.sa_flags = 0;
sigaction(SIGPIPE, &act, NULL);
act.sa_handler = winchange_handler;
sigaction(SIGWINCH, &act, NULL);
if ((pid = fork()) < 0) {
logger(-1, errno, "Unable to fork");
ret = VZ_RESOURCE_ERROR;
return ret;
} else if (pid == 0) {
int master, slave;
struct termios tios;
struct winsize ws;
/* get terminal settings from 0 */
ioctl(0, TIOCGWINSZ, &ws);
tcgetattr(0, &tios);
close(in[1]); close(out[0]); close(st[0]); close(info[1]);
/* list of skipped fds -1 the end mark */
close_fds(1, in[0], out[1], st[1], info[0], h->vzfd, -1);
dup2(out[1], 1);
dup2(out[1], 2);
if ((ret = h->enter(h, veid, root, 0)))
goto err;
if ((ret = pty_alloc(&master, &slave, &tios, &ws)))
goto err;
child_term = 0;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDSTOP;
act.sa_handler = child_handler;
sigaction(SIGCHLD, &act, NULL);
if ((pid = fork()) == 0) {
char buf[64];
char *term;
char *arg[] = {NULL, NULL};
char *env[] = {ENV_PATH,
"HISTFILE=/dev/null",
"USER=root", "HOME=/root", "LOGNAME=root",
NULL, /* for TERM */
NULL};
close(master);
set_ctty(slave);
dup2(slave, 0);
dup2(slave, 1);
dup2(slave, 2);
/* Close the extra descriptor for the pseudo tty. */
close(slave);
/* Close other ends of our pipes */
close(in[0]);
close(out[1]);
close(st[1]);
close(info[0]);
if ((term = getenv("TERM")) != NULL) {
snprintf(buf, sizeof(buf), "TERM=%s", term);
env[ARRAY_SIZE(env) - 2] = buf;
}
arg[0] = "-bash";
execve("/bin/bash", arg, env);
arg[0] = "-sh";
execve("/bin/sh", arg, env);
logger(-1, errno, "Enter failed: unable to exec sh");
exit(1);
} else if (pid < 0) {
logger(-1, errno, "Unable to fork");
ret = VZ_RESOURCE_ERROR;
write(st[1], &ret, sizeof(ret));
exit(ret);
}
close(slave);
close(st[1]);
e_loop(in[0], master, master, out[1], info[0]);
while ((ret = waitpid(pid, &status, 0)) == -1)
if (errno != EINTR)
break;
close(master);
exit(0);
err:
write(st[1], &ret, sizeof(ret));
exit(ret);
}
close(in[0]); close(out[1]); close(st[1]); close(info[0]);
/* wait for pts allocation */
ret = read(st[0], &status, sizeof(status));
if (!ret) {
fprintf(stdout, "entered into CT %d\n", veid);
raw_on();
if (argc) {
/* pass command line arguments into CT */
for (i = 0; i < argc; i++) {
if (write(in[1], argv[i],
strlen(argv[i])) < 0) {
fprintf(stdout,
"failed to pass "
"command arguments into CT "
"%d\n", veid);
break;
}
/* separate every argument by space */
if (write(in[1], " ", 1) < 0) {
fprintf(stdout,
"failed to pass "
"command arguments into CT "
"%d\n", veid);
break;
}
}
/* run command by 'Enter' key emulation */
if (write(in[1], "\n", 1) < 0)
fprintf(stdout,
"failed to finish "
"command arguments sequence "
"while passing into CT %d\n", veid);
}
e_loop(fileno(stdin), in[1], out[0], fileno(stdout), info[1]);
} else {
fprintf(stdout, "enter into CT %d failed\n", veid);
set_not_blk(out[0]);
while (stdredir(out[0], fileno(stdout)) == 0)
;
}
while ((waitpid(pid, &status, 0)) == -1)
if (errno != EINTR)
break;
if (WIFSIGNALED(status))
fprintf(stdout, "got signal %d\n", WTERMSIG(status));
if (!ret) {
raw_off();
fprintf(stdout, "exited from CT %d\n", veid);
}
close(in[1]); close(out[0]);
/* Restore fd flags */
for (i = 0; i < 2; i++)
fcntl(i, F_SETFL, fd_flags[i]);
return ret ? status : 0;
}
/* CT console implementation */
static int tty;
static void console_winch(int sig)
{
struct winsize ws;
if (ioctl(0, TIOCGWINSZ, &ws))
warn("Unable to get window size");
else {
if (sig == 0) { /* just attached */
/* force a redraw by "changing" the term size */
ws.ws_row++;
ioctl(tty, TIOCSWINSZ, &ws);
ws.ws_row--;
}
if (ioctl(tty, TIOCSWINSZ, &ws))
warn("Unable to set window size");
}
}
static void sak(void)
{
ioctl(tty, TIOSAK);
}
/* @ttyno: 0 is tty1, 1 is tty2 etc.
*
* Note tty1 is used for console
*/
int do_console_attach(vps_handler *h, envid_t veid, int ttyno)
{
struct vzctl_ve_configure c;
int pid, status;
char buf;
const char esc = 27;
const char enter = 13;
int new_line = 1;
int ret = VZ_SYSTEM_ERROR;
if (ttyno < 0) /* undef */
ttyno = 0; /* tty1 aka console by default */
child_term = 0;
c.veid = veid;
c.key = VE_CONFIGURE_OPEN_TTY;
c.val = ttyno;
c.size = 0;
tty = ioctl(h->vzfd, VZCTL_VE_CONFIGURE, &c);
if (tty < 0) {
fprintf(stderr, "Error in VE_CONFIGURE_OPEN_TTY: %s\n",
strerror(errno));
return VZ_SYSTEM_ERROR;
}
signal(SIGCHLD, child_handler);
signal(SIGWINCH, console_winch);
console_winch(0);
fprintf(stderr, "Attached to CT %d (ESC . to detach)\n", veid);
if ((pid = fork()) < 0) {
fprintf(stderr, "Unable to fork: %s\n", strerror(errno));
return VZ_RESOURCE_ERROR;
}
if (pid == 0) {
char bigbuf[4096];
ssize_t nread;
while (1) {
if ((nread = read(tty, &bigbuf, sizeof bigbuf)) <= 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
if (nread < 0)
err(1, "tty read error: %m");
exit(nread < 0 ? 2 : 0 );
}
if (write(1, &bigbuf, nread) < 0) {
err(1, "stdout write error: %m");
exit(3);
}
}
exit(0);
}
raw_on();
#define TREAD(buf) \
do { \
if (read(0, &buf, sizeof buf) <= 0) { \
warn("stdin read error"); \
goto err; \
} \
} while (0)
#define TWRITE(buf) \
do { \
if (write(tty, &buf, sizeof buf) <= 0) { \
warn("tty write error"); \
goto err; \
} \
} while (0)
while (!child_term) {
TREAD(buf);
if (buf == esc && new_line) {
TREAD(buf);
switch (buf) {
case '.':
goto out;
case '!':
sak();
continue;
default:
TWRITE(esc);
break;
}
}
TWRITE(buf);
new_line = (buf == enter);
}
out:
ret = 0;
err:
kill(pid, SIGKILL);
while (waitpid(pid, &status, 0) == -1)
if (errno != EINTR)
break;
raw_off();
fprintf(stderr, "\nDetached from CT %d\n", veid);
return ret;
}