Skip to content

Commit

Permalink
char: move pty chardev in its own file
Browse files Browse the repository at this point in the history
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Acked-by: Eric Blake <eblake@redhat.com>
  • Loading branch information
elmarco committed Jan 31, 2017
1 parent 1fcb384 commit c3b7d62
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 258 deletions.
1 change: 1 addition & 0 deletions chardev/Makefile.objs
Expand Up @@ -6,6 +6,7 @@ chardev-obj-y += char-io.o
chardev-obj-y += char-mux.o
chardev-obj-y += char-null.o
chardev-obj-y += char-pipe.o
chardev-obj-$(CONFIG_POSIX) += char-pty.o
chardev-obj-y += char-ringbuf.o
chardev-obj-y += char-socket.o
chardev-obj-y += char-stdio.o
Expand Down
300 changes: 300 additions & 0 deletions 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

0 comments on commit c3b7d62

Please sign in to comment.