| /* |
| * 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; |
| } |