Files
xen/tools/console/client/main.c
T
Martin Lucina 8398ec706f xenconsole: Ensure exclusive access to console using locks
If more than one instance of xenconsole is run against the same DOMID
then each instance will only get some data. This change ensures
exclusive access to the console by obtaining an exclusive lock on
<XEN_LOCK_DIR>/xenconsole.<DOMID>.

The locking strategy used is based on
tools/libxl/libxl_internal.c:libxl__lock_domain_userdata().

Signed-off-by: Martin Lucina <martin@lucina.net>
Cc: Ian Jackson <ian.jackson@eu.citrix.com>
Cc: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
Cc: Ian Campbell <ian.campbell@citrix.com>
Cc: Wei Liu <wei.liu2@citrix.com>
Acked-by: Ian Jackson <ian.jackson@eu.citrix.com>
2015-07-27 14:54:55 +01:00

465 lines
11 KiB
C

/*\
* Copyright (C) International Business Machines Corp., 2005
* Author(s): Anthony Liguori <aliguori@us.ibm.com>
*
* Xen Console Daemon
*
* 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; under version 2 of the License.
*
* 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 <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <termios.h>
#include <signal.h>
#include <getopt.h>
#include <sys/select.h>
#include <err.h>
#include <string.h>
#ifdef __sun__
#include <sys/stropts.h>
#endif
#include <xenstore.h>
#include "xenctrl.h"
#include "_paths.h"
#define ESCAPE_CHARACTER 0x1d
static volatile sig_atomic_t received_signal = 0;
static char lockfile[sizeof (XEN_LOCK_DIR "/xenconsole.") + 8] = { 0 };
static int lockfd = -1;
static void sighandler(int signum)
{
received_signal = 1;
}
static bool write_sync(int fd, const void *data, size_t size)
{
size_t offset = 0;
ssize_t len;
while (offset < size) {
len = write(fd, data + offset, size - offset);
if (len < 1) {
return false;
}
offset += len;
}
return true;
}
static void usage(const char *program) {
printf("Usage: %s [OPTION] DOMID\n"
"Attaches to a virtual domain console\n"
"\n"
" -h, --help display this help and exit\n"
" -n, --num N use console number N\n"
, program);
}
#ifdef __sun__
void cfmakeraw(struct termios *termios_p)
{
termios_p->c_iflag &=
~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB);
termios_p->c_cflag |= CS8;
termios_p->c_cc[VMIN] = 0;
termios_p->c_cc[VTIME] = 0;
}
#endif
static int get_pty_fd(struct xs_handle *xs, char *path, int seconds)
/* Check for a pty in xenstore, open it and return its fd.
* Assumes there is already a watch set in the store for this path. */
{
struct timeval tv;
fd_set watch_fdset;
int xs_fd = xs_fileno(xs), pty_fd = -1;
int start, now;
unsigned int len = 0;
char *pty_path, **watch_paths;
start = now = time(NULL);
do {
tv.tv_usec = 0;
tv.tv_sec = (start + seconds) - now;
FD_ZERO(&watch_fdset);
FD_SET(xs_fd, &watch_fdset);
if (select(xs_fd + 1, &watch_fdset, NULL, NULL, &tv)) {
/* Read the watch to drain the buffer */
watch_paths = xs_read_watch(xs, &len);
free(watch_paths);
/* We only watch for one thing, so no need to
* disambiguate: just read the pty path */
pty_path = xs_read(xs, XBT_NULL, path, &len);
if (pty_path != NULL && pty_path[0] != '\0') {
pty_fd = open(pty_path, O_RDWR | O_NOCTTY);
if (pty_fd == -1)
warn("Could not open tty `%s'", pty_path);
}
free(pty_path);
}
} while (pty_fd == -1 && (now = time(NULL)) < start + seconds);
#ifdef __sun__
if (pty_fd != -1) {
struct termios term;
/*
* The pty may come from either xend (with pygrub) or
* xenconsoled. It may have tty semantics set up, or not.
* While it isn't strictly necessary to have those
* semantics here, it is good to have a consistent
* state that is the same as under Linux.
*
* If tcgetattr fails, they have not been set up,
* so go ahead and set them up now, by pushing the
* ptem and ldterm streams modules.
*/
if (tcgetattr(pty_fd, &term) < 0) {
ioctl(pty_fd, I_PUSH, "ptem");
ioctl(pty_fd, I_PUSH, "ldterm");
}
}
#endif
return pty_fd;
}
/* don't worry too much if setting terminal attributes fail */
static void init_term(int fd, struct termios *old)
{
struct termios new_term;
if (tcgetattr(fd, old) == -1)
return;
new_term = *old;
cfmakeraw(&new_term);
tcsetattr(fd, TCSANOW, &new_term);
}
static void restore_term(int fd, struct termios *old)
{
tcsetattr(fd, TCSANOW, old);
}
static int console_loop(int fd, struct xs_handle *xs, char *pty_path,
bool interactive)
{
int ret, xs_fd = xs_fileno(xs), max_fd = -1;
do {
fd_set fds;
FD_ZERO(&fds);
if (interactive) {
FD_SET(STDIN_FILENO, &fds);
max_fd = STDIN_FILENO;
}
FD_SET(xs_fd, &fds);
if (xs_fd > max_fd) max_fd = xs_fd;
if (fd != -1) FD_SET(fd, &fds);
if (fd > max_fd) max_fd = fd;
ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
return -1;
}
if (FD_ISSET(xs_fileno(xs), &fds)) {
int newfd = get_pty_fd(xs, pty_path, 0);
if (fd != -1)
close(fd);
if (newfd == -1)
/* Console PTY has become invalid */
return 0;
fd = newfd;
continue;
}
if (FD_ISSET(STDIN_FILENO, &fds)) {
ssize_t len;
char msg[60];
len = read(STDIN_FILENO, msg, sizeof(msg));
if (len == 1 && msg[0] == ESCAPE_CHARACTER) {
return 0;
}
if (len == 0 || len == -1) {
if (len == -1 &&
(errno == EINTR || errno == EAGAIN)) {
continue;
}
return -1;
}
if (!write_sync(fd, msg, len)) {
close(fd);
fd = -1;
continue;
}
}
if (fd != -1 && FD_ISSET(fd, &fds)) {
ssize_t len;
char msg[512];
len = read(fd, msg, sizeof(msg));
if (len == 0 || len == -1) {
if (len == -1 &&
(errno == EINTR || errno == EAGAIN)) {
continue;
}
close(fd);
fd = -1;
continue;
}
if (!write_sync(STDOUT_FILENO, msg, len)) {
perror("write() failed");
return -1;
}
}
} while (received_signal == 0);
return 0;
}
typedef enum {
CONSOLE_INVAL,
CONSOLE_PV,
CONSOLE_SERIAL,
} console_type;
static struct termios stdin_old_attr;
static void restore_term_stdin(void)
{
restore_term(STDIN_FILENO, &stdin_old_attr);
}
/* The following locking strategy is based on that from
* libxl__domain_userdata_lock(), with the difference that we want to fail if we
* cannot acquire the lock rather than wait indefinitely.
*/
static void console_lock(int domid)
{
struct stat stab, fstab;
int fd;
snprintf(lockfile, sizeof lockfile, "%s%d", XEN_LOCK_DIR "/xenconsole.", domid);
while (true) {
fd = open(lockfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
err(errno, "Could not open %s", lockfile);
while (flock(fd, LOCK_EX | LOCK_NB)) {
if (errno == EINTR)
continue;
else
err(errno, "Could not lock %s", lockfile);
}
if (fstat(fd, &fstab))
err(errno, "Could not fstat %s", lockfile);
if (stat(lockfile, &stab)) {
if (errno != ENOENT)
err(errno, "Could not stat %s", lockfile);
} else {
if (stab.st_dev == fstab.st_dev && stab.st_ino == fstab.st_ino)
break;
}
close(fd);
}
lockfd = fd;
return;
}
static void console_unlock(void)
{
if (lockfile[0] && lockfd != -1) {
unlink(lockfile);
close(lockfd);
}
}
int main(int argc, char **argv)
{
struct termios attr;
int domid;
char *sopt = "hn:";
int ch;
unsigned int num = 0;
int opt_ind=0;
struct option lopt[] = {
{ "type", 1, 0, 't' },
{ "num", 1, 0, 'n' },
{ "help", 0, 0, 'h' },
{ 0 },
};
char *dom_path = NULL, *path = NULL;
int spty, xsfd;
struct xs_handle *xs;
char *end;
console_type type = CONSOLE_INVAL;
bool interactive = 0;
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
interactive = 1;
while((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
switch(ch) {
case 'h':
usage(argv[0]);
exit(0);
break;
case 'n':
num = atoi(optarg);
break;
case 't':
if (!strcmp(optarg, "serial"))
type = CONSOLE_SERIAL;
else if (!strcmp(optarg, "pv"))
type = CONSOLE_PV;
else {
fprintf(stderr, "Invalid type argument\n");
fprintf(stderr, "Console types supported are: serial, pv\n");
exit(EINVAL);
}
break;
default:
fprintf(stderr, "Invalid argument\n");
fprintf(stderr, "Try `%s --help' for more information.\n",
argv[0]);
exit(EINVAL);
}
}
if (optind >= argc) {
fprintf(stderr, "DOMID should be specified\n");
fprintf(stderr, "Try `%s --help' for more information.\n",
argv[0]);
exit(EINVAL);
}
domid = strtol(argv[optind], &end, 10);
if (end && *end) {
fprintf(stderr, "Invalid DOMID `%s'\n", argv[optind]);
fprintf(stderr, "Try `%s --help' for more information.\n",
argv[0]);
exit(EINVAL);
}
xs = xs_daemon_open();
if (xs == NULL) {
err(errno, "Could not contact XenStore");
}
signal(SIGTERM, sighandler);
dom_path = xs_get_domain_path(xs, domid);
if (dom_path == NULL)
err(errno, "xs_get_domain_path()");
if (type == CONSOLE_INVAL) {
xc_dominfo_t xcinfo;
xc_interface *xc_handle = xc_interface_open(0,0,0);
if (xc_handle == NULL)
err(errno, "Could not open xc interface");
if ( (xc_domain_getinfo(xc_handle, domid, 1, &xcinfo) != 1) ||
(xcinfo.domid != domid) ) {
xc_interface_close(xc_handle);
err(errno, "Failed to get domain information");
}
/* default to pv console for pv guests and serial for hvm guests */
if (xcinfo.hvm)
type = CONSOLE_SERIAL;
else
type = CONSOLE_PV;
xc_interface_close(xc_handle);
}
path = malloc(strlen(dom_path) + strlen("/device/console/0/tty") + 5);
if (path == NULL)
err(ENOMEM, "malloc");
if (type == CONSOLE_SERIAL)
snprintf(path, strlen(dom_path) + strlen("/serial/0/tty") + 5, "%s/serial/%d/tty", dom_path, num);
else {
if (num == 0)
snprintf(path, strlen(dom_path) + strlen("/console/tty") + 1, "%s/console/tty", dom_path);
else
snprintf(path, strlen(dom_path) + strlen("/device/console/%d/tty") + 5, "%s/device/console/%d/tty", dom_path, num);
}
/* FIXME consoled currently does not assume domain-0 doesn't have a
console which is good when we break domain-0 up. To keep us
user friendly, we'll bail out here since no data will ever show
up on domain-0. */
if (domid == 0) {
fprintf(stderr, "Can't specify Domain-0\n");
exit(EINVAL);
}
console_lock(domid);
atexit(console_unlock);
/* Set a watch on this domain's console pty */
if (!xs_watch(xs, path, ""))
err(errno, "Can't set watch for console pty");
xsfd = xs_fileno(xs);
/* Wait a little bit for tty to appear. There is a race
condition that occurs after xend creates a domain. This code
might be running before consoled has noticed the new domain
and setup a pty for it. */
spty = get_pty_fd(xs, path, 5);
if (spty == -1) {
err(errno, "Could not read tty from store");
}
init_term(spty, &attr);
if (interactive) {
init_term(STDIN_FILENO, &stdin_old_attr);
atexit(restore_term_stdin); /* if this fails, oh dear */
}
console_loop(spty, xs, path, interactive);
free(path);
free(dom_path);
return 0;
}