aboutsummaryrefslogtreecommitdiff
path: root/chardev/char-pty.c
diff options
context:
space:
mode:
Diffstat (limited to 'chardev/char-pty.c')
-rw-r--r--chardev/char-pty.c300
1 files changed, 300 insertions, 0 deletions
diff --git a/chardev/char-pty.c b/chardev/char-pty.c
new file mode 100644
index 0000000000..27eb85f505
--- /dev/null
+++ b/chardev/char-pty.c
@@ -0,0 +1,300 @@
+/*
+ * 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 "qemu-common.h"
+#include "sysemu/char.h"
+#include "io/channel-file.h"
+#include "qemu/sockets.h"
+#include "qemu/error-report.h"
+
+#include "char-io.h"
+
+#if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
+ || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \
+ || defined(__GLIBC__)
+
+typedef struct {
+ Chardev parent;
+ QIOChannel *ioc;
+ int read_bytes;
+
+ /* Protected by the Chardev chr_write_lock. */
+ int connected;
+ guint timer_tag;
+ guint open_tag;
+} PtyChardev;
+
+#define PTY_CHARDEV(obj) OBJECT_CHECK(PtyChardev, (obj), TYPE_CHARDEV_PTY)
+
+static void pty_chr_update_read_handler_locked(Chardev *chr);
+static void pty_chr_state(Chardev *chr, int connected);
+
+static gboolean pty_chr_timer(gpointer opaque)
+{
+ struct Chardev *chr = CHARDEV(opaque);
+ PtyChardev *s = PTY_CHARDEV(opaque);
+
+ qemu_mutex_lock(&chr->chr_write_lock);
+ s->timer_tag = 0;
+ s->open_tag = 0;
+ if (!s->connected) {
+ /* Next poll ... */
+ pty_chr_update_read_handler_locked(chr);
+ }
+ qemu_mutex_unlock(&chr->chr_write_lock);
+ return FALSE;
+}
+
+/* Called with chr_write_lock held. */
+static void pty_chr_rearm_timer(Chardev *chr, int ms)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+ char *name;
+
+ if (s->timer_tag) {
+ g_source_remove(s->timer_tag);
+ s->timer_tag = 0;
+ }
+
+ if (ms == 1000) {
+ name = g_strdup_printf("pty-timer-secs-%s", chr->label);
+ s->timer_tag = g_timeout_add_seconds(1, pty_chr_timer, chr);
+ } else {
+ name = g_strdup_printf("pty-timer-ms-%s", chr->label);
+ s->timer_tag = g_timeout_add(ms, pty_chr_timer, chr);
+ }
+ g_source_set_name_by_id(s->timer_tag, name);
+ g_free(name);
+}
+
+/* Called with chr_write_lock held. */
+static void pty_chr_update_read_handler_locked(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;
+ do {
+ rc = g_poll(&pfd, 1, 0);
+ } while (rc == -1 && errno == EINTR);
+ assert(rc >= 0);
+
+ if (pfd.revents & G_IO_HUP) {
+ pty_chr_state(chr, 0);
+ } else {
+ pty_chr_state(chr, 1);
+ }
+}
+
+static void pty_chr_update_read_handler(Chardev *chr,
+ GMainContext *context)
+{
+ qemu_mutex_lock(&chr->chr_write_lock);
+ pty_chr_update_read_handler_locked(chr);
+ qemu_mutex_unlock(&chr->chr_write_lock);
+}
+
+/* Called with chr_write_lock held. */
+static int char_pty_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+
+ if (!s->connected) {
+ /* guest sends data, check for (re-)connect */
+ pty_chr_update_read_handler_locked(chr);
+ if (!s->connected) {
+ return 0;
+ }
+ }
+ return io_channel_send(s->ioc, buf, 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 gboolean qemu_chr_be_generic_open_func(gpointer opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ PtyChardev *s = PTY_CHARDEV(opaque);
+
+ s->open_tag = 0;
+ qemu_chr_be_generic_open(chr);
+ return FALSE;
+}
+
+/* Called with chr_write_lock held. */
+static void pty_chr_state(Chardev *chr, int connected)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+
+ if (!connected) {
+ if (s->open_tag) {
+ g_source_remove(s->open_tag);
+ s->open_tag = 0;
+ }
+ 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 {
+ if (s->timer_tag) {
+ g_source_remove(s->timer_tag);
+ s->timer_tag = 0;
+ }
+ if (!s->connected) {
+ g_assert(s->open_tag == 0);
+ s->connected = 1;
+ s->open_tag = g_idle_add(qemu_chr_be_generic_open_func, chr);
+ }
+ if (!chr->fd_in_tag) {
+ chr->fd_in_tag = io_add_watch_poll(chr, s->ioc,
+ pty_chr_read_poll,
+ pty_chr_read,
+ chr, NULL);
+ }
+ }
+}
+
+static void char_pty_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ PtyChardev *s = PTY_CHARDEV(obj);
+
+ qemu_mutex_lock(&chr->chr_write_lock);
+ pty_chr_state(chr, 0);
+ object_unref(OBJECT(s->ioc));
+ if (s->timer_tag) {
+ g_source_remove(s->timer_tag);
+ s->timer_tag = 0;
+ }
+ qemu_mutex_unlock(&chr->chr_write_lock);
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+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;
+
+ 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);
+ qemu_set_nonblock(master_fd);
+
+ chr->filename = g_strdup_printf("pty:%s", pty_name);
+ error_report("char device redirected to %s (label %s)",
+ 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(QIO_CHANNEL(s->ioc), name);
+ g_free(name);
+ s->timer_tag = 0;
+ *be_opened = false;
+}
+
+static void char_pty_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ 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);
+
+#endif