|  | /* | 
|  | * QEMU System Emulator | 
|  | * | 
|  | * Copyright (c) 2003-2008 Fabrice Bellard | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | * of this software and associated documentation files (the "Software"), to deal | 
|  | * in the Software without restriction, including without limitation the rights | 
|  | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | * copies of the Software, and to permit persons to whom the Software is | 
|  | * furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | 
|  | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | * THE SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "qapi/error.h" | 
|  | #include "chardev/char.h" | 
|  | #include "io/channel-file.h" | 
|  | #include "qemu/sockets.h" | 
|  | #include "qemu/error-report.h" | 
|  | #include "qemu/module.h" | 
|  | #include "qemu/option.h" | 
|  | #include "qemu/qemu-print.h" | 
|  |  | 
|  | #include "chardev/char-io.h" | 
|  | #include "qom/object.h" | 
|  |  | 
|  | struct PtyChardev { | 
|  | Chardev parent; | 
|  | QIOChannel *ioc; | 
|  | int read_bytes; | 
|  |  | 
|  | int connected; | 
|  | GSource *timer_src; | 
|  | char *path; | 
|  | }; | 
|  | typedef struct PtyChardev PtyChardev; | 
|  |  | 
|  | DECLARE_INSTANCE_CHECKER(PtyChardev, PTY_CHARDEV, | 
|  | TYPE_CHARDEV_PTY) | 
|  |  | 
|  | static void pty_chr_state(Chardev *chr, int connected); | 
|  |  | 
|  | static void pty_chr_timer_cancel(PtyChardev *s) | 
|  | { | 
|  | if (s->timer_src) { | 
|  | g_source_destroy(s->timer_src); | 
|  | g_source_unref(s->timer_src); | 
|  | s->timer_src = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | static gboolean pty_chr_timer(gpointer opaque) | 
|  | { | 
|  | struct Chardev *chr = CHARDEV(opaque); | 
|  | PtyChardev *s = PTY_CHARDEV(opaque); | 
|  |  | 
|  | pty_chr_timer_cancel(s); | 
|  | if (!s->connected) { | 
|  | /* Next poll ... */ | 
|  | qemu_chr_be_update_read_handlers(chr, chr->gcontext); | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static void pty_chr_rearm_timer(Chardev *chr, int ms) | 
|  | { | 
|  | PtyChardev *s = PTY_CHARDEV(chr); | 
|  | char *name; | 
|  |  | 
|  | pty_chr_timer_cancel(s); | 
|  | name = g_strdup_printf("pty-timer-%s", chr->label); | 
|  | s->timer_src = qemu_chr_timeout_add_ms(chr, ms, pty_chr_timer, chr); | 
|  | g_source_set_name(s->timer_src, name); | 
|  | g_free(name); | 
|  | } | 
|  |  | 
|  | static void pty_chr_update_read_handler(Chardev *chr) | 
|  | { | 
|  | PtyChardev *s = PTY_CHARDEV(chr); | 
|  | GPollFD pfd; | 
|  | int rc; | 
|  | QIOChannelFile *fioc = QIO_CHANNEL_FILE(s->ioc); | 
|  |  | 
|  | pfd.fd = fioc->fd; | 
|  | pfd.events = G_IO_OUT; | 
|  | pfd.revents = 0; | 
|  | rc = RETRY_ON_EINTR(g_poll(&pfd, 1, 0)); | 
|  | assert(rc >= 0); | 
|  |  | 
|  | if (pfd.revents & G_IO_HUP) { | 
|  | pty_chr_state(chr, 0); | 
|  | } else { | 
|  | pty_chr_state(chr, 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int char_pty_chr_write(Chardev *chr, const uint8_t *buf, int len) | 
|  | { | 
|  | PtyChardev *s = PTY_CHARDEV(chr); | 
|  | GPollFD pfd; | 
|  | int rc; | 
|  |  | 
|  | if (s->connected) { | 
|  | return io_channel_send(s->ioc, buf, len); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The other side might already be re-connected, but the timer might | 
|  | * not have fired yet. So let's check here whether we can write again: | 
|  | */ | 
|  | pfd.fd = QIO_CHANNEL_FILE(s->ioc)->fd; | 
|  | pfd.events = G_IO_OUT; | 
|  | pfd.revents = 0; | 
|  | rc = RETRY_ON_EINTR(g_poll(&pfd, 1, 0)); | 
|  | g_assert(rc >= 0); | 
|  | if (!(pfd.revents & G_IO_HUP) && (pfd.revents & G_IO_OUT)) { | 
|  | io_channel_send(s->ioc, buf, len); | 
|  | } | 
|  |  | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static GSource *pty_chr_add_watch(Chardev *chr, GIOCondition cond) | 
|  | { | 
|  | PtyChardev *s = PTY_CHARDEV(chr); | 
|  | if (!s->connected) { | 
|  | return NULL; | 
|  | } | 
|  | return qio_channel_create_watch(s->ioc, cond); | 
|  | } | 
|  |  | 
|  | static int pty_chr_read_poll(void *opaque) | 
|  | { | 
|  | Chardev *chr = CHARDEV(opaque); | 
|  | PtyChardev *s = PTY_CHARDEV(opaque); | 
|  |  | 
|  | s->read_bytes = qemu_chr_be_can_write(chr); | 
|  | return s->read_bytes; | 
|  | } | 
|  |  | 
|  | static gboolean pty_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) | 
|  | { | 
|  | Chardev *chr = CHARDEV(opaque); | 
|  | PtyChardev *s = PTY_CHARDEV(opaque); | 
|  | gsize len; | 
|  | uint8_t buf[CHR_READ_BUF_LEN]; | 
|  | ssize_t ret; | 
|  |  | 
|  | len = sizeof(buf); | 
|  | if (len > s->read_bytes) { | 
|  | len = s->read_bytes; | 
|  | } | 
|  | if (len == 0) { | 
|  | return TRUE; | 
|  | } | 
|  | ret = qio_channel_read(s->ioc, (char *)buf, len, NULL); | 
|  | if (ret <= 0) { | 
|  | pty_chr_state(chr, 0); | 
|  | return FALSE; | 
|  | } else { | 
|  | pty_chr_state(chr, 1); | 
|  | qemu_chr_be_write(chr, buf, ret); | 
|  | } | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static void pty_chr_state(Chardev *chr, int connected) | 
|  | { | 
|  | PtyChardev *s = PTY_CHARDEV(chr); | 
|  |  | 
|  | if (!connected) { | 
|  | remove_fd_in_watch(chr); | 
|  | s->connected = 0; | 
|  | /* (re-)connect poll interval for idle guests: once per second. | 
|  | * We check more frequently in case the guests sends data to | 
|  | * the virtual device linked to our pty. */ | 
|  | pty_chr_rearm_timer(chr, 1000); | 
|  | } else { | 
|  | pty_chr_timer_cancel(s); | 
|  | if (!s->connected) { | 
|  | s->connected = 1; | 
|  | qemu_chr_be_event(chr, CHR_EVENT_OPENED); | 
|  | } | 
|  | if (!chr->gsource) { | 
|  | chr->gsource = io_add_watch_poll(chr, s->ioc, | 
|  | pty_chr_read_poll, | 
|  | pty_chr_read, | 
|  | chr, chr->gcontext); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void char_pty_finalize(Object *obj) | 
|  | { | 
|  | Chardev *chr = CHARDEV(obj); | 
|  | PtyChardev *s = PTY_CHARDEV(obj); | 
|  |  | 
|  | /* unlink symlink */ | 
|  | if (s->path) { | 
|  | unlink(s->path); | 
|  | g_free(s->path); | 
|  | } | 
|  |  | 
|  | pty_chr_state(chr, 0); | 
|  | object_unref(OBJECT(s->ioc)); | 
|  | pty_chr_timer_cancel(s); | 
|  | qemu_chr_be_event(chr, CHR_EVENT_CLOSED); | 
|  | } | 
|  |  | 
|  | #if defined HAVE_PTY_H | 
|  | # include <pty.h> | 
|  | #elif defined CONFIG_BSD | 
|  | # include <termios.h> | 
|  | # if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) | 
|  | #  include <libutil.h> | 
|  | # else | 
|  | #  include <util.h> | 
|  | # endif | 
|  | #elif defined CONFIG_SOLARIS | 
|  | # include <termios.h> | 
|  | # include <stropts.h> | 
|  | #else | 
|  | # include <termios.h> | 
|  | #endif | 
|  |  | 
|  | #ifdef __sun__ | 
|  |  | 
|  | #if !defined(HAVE_OPENPTY) | 
|  | /* Once illumos has openpty(), this is going to be removed. */ | 
|  | static int openpty(int *amaster, int *aslave, char *name, | 
|  | struct termios *termp, struct winsize *winp) | 
|  | { | 
|  | const char *slave; | 
|  | int mfd = -1, sfd = -1; | 
|  |  | 
|  | *amaster = *aslave = -1; | 
|  |  | 
|  | mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); | 
|  | if (mfd < 0) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (grantpt(mfd) == -1 || unlockpt(mfd) == -1) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if ((slave = ptsname(mfd)) == NULL) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if ((sfd = open(slave, O_RDONLY | O_NOCTTY)) == -1) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (ioctl(sfd, I_PUSH, "ptem") == -1 || | 
|  | (termp != NULL && tcgetattr(sfd, termp) < 0)) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | *amaster = mfd; | 
|  | *aslave = sfd; | 
|  |  | 
|  | if (winp) { | 
|  | ioctl(sfd, TIOCSWINSZ, winp); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err: | 
|  | if (sfd != -1) { | 
|  | close(sfd); | 
|  | } | 
|  | close(mfd); | 
|  | return -1; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static 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 | 
|  |  | 
|  | /* like openpty() but also makes it raw; return master fd */ | 
|  | static int qemu_openpty_raw(int *aslave, char *pty_name) | 
|  | { | 
|  | int amaster; | 
|  | struct termios tty; | 
|  | #if defined(__OpenBSD__) || defined(__DragonFly__) | 
|  | char pty_buf[PATH_MAX]; | 
|  | #define q_ptsname(x) pty_buf | 
|  | #else | 
|  | char *pty_buf = NULL; | 
|  | #define q_ptsname(x) ptsname(x) | 
|  | #endif | 
|  |  | 
|  | if (openpty(&amaster, aslave, pty_buf, NULL, NULL) < 0) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* Set raw attributes on the pty. */ | 
|  | tcgetattr(*aslave, &tty); | 
|  | cfmakeraw(&tty); | 
|  | tcsetattr(*aslave, TCSAFLUSH, &tty); | 
|  |  | 
|  | if (pty_name) { | 
|  | strcpy(pty_name, q_ptsname(amaster)); | 
|  | } | 
|  |  | 
|  | return amaster; | 
|  | } | 
|  |  | 
|  | static void char_pty_open(Chardev *chr, | 
|  | ChardevBackend *backend, | 
|  | bool *be_opened, | 
|  | Error **errp) | 
|  | { | 
|  | PtyChardev *s; | 
|  | int master_fd, slave_fd; | 
|  | char pty_name[PATH_MAX]; | 
|  | char *name; | 
|  | char *path = backend->u.pty.data->path; | 
|  |  | 
|  | master_fd = qemu_openpty_raw(&slave_fd, pty_name); | 
|  | if (master_fd < 0) { | 
|  | error_setg_errno(errp, errno, "Failed to create PTY"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | close(slave_fd); | 
|  | if (!g_unix_set_fd_nonblocking(master_fd, true, NULL)) { | 
|  | error_setg_errno(errp, errno, "Failed to set FD nonblocking"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | chr->filename = g_strdup_printf("pty:%s", pty_name); | 
|  | qemu_printf("char device redirected to %s (label %s)\n", | 
|  | pty_name, chr->label); | 
|  |  | 
|  | s = PTY_CHARDEV(chr); | 
|  | s->ioc = QIO_CHANNEL(qio_channel_file_new_fd(master_fd)); | 
|  | name = g_strdup_printf("chardev-pty-%s", chr->label); | 
|  | qio_channel_set_name(s->ioc, name); | 
|  | g_free(name); | 
|  | s->timer_src = NULL; | 
|  | *be_opened = false; | 
|  |  | 
|  | /* create symbolic link */ | 
|  | if (path) { | 
|  | int res = symlink(pty_name, path); | 
|  |  | 
|  | if (res != 0) { | 
|  | error_setg_errno(errp, errno, "Failed to create PTY symlink"); | 
|  | } else { | 
|  | s->path = g_strdup(path); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void char_pty_parse(QemuOpts *opts, ChardevBackend *backend, | 
|  | Error **errp) | 
|  | { | 
|  | const char *path = qemu_opt_get(opts, "path"); | 
|  | ChardevPty *pty; | 
|  |  | 
|  | backend->type = CHARDEV_BACKEND_KIND_PTY; | 
|  | pty = backend->u.pty.data = g_new0(ChardevPty, 1); | 
|  | qemu_chr_parse_common(opts, qapi_ChardevPty_base(pty)); | 
|  | pty->path = g_strdup(path); | 
|  | } | 
|  |  | 
|  | static void char_pty_class_init(ObjectClass *oc, void *data) | 
|  | { | 
|  | ChardevClass *cc = CHARDEV_CLASS(oc); | 
|  |  | 
|  | cc->parse = char_pty_parse; | 
|  | cc->open = char_pty_open; | 
|  | cc->chr_write = char_pty_chr_write; | 
|  | cc->chr_update_read_handler = pty_chr_update_read_handler; | 
|  | cc->chr_add_watch = pty_chr_add_watch; | 
|  | } | 
|  |  | 
|  | static const TypeInfo char_pty_type_info = { | 
|  | .name = TYPE_CHARDEV_PTY, | 
|  | .parent = TYPE_CHARDEV, | 
|  | .instance_size = sizeof(PtyChardev), | 
|  | .instance_finalize = char_pty_finalize, | 
|  | .class_init = char_pty_class_init, | 
|  | }; | 
|  |  | 
|  | static void register_types(void) | 
|  | { | 
|  | type_register_static(&char_pty_type_info); | 
|  | } | 
|  |  | 
|  | type_init(register_types); |