From: Corey Minyard <cminy...@mvista.com> Allow a socket that connects to reconnect on a periodic basis if it fails to connect at startup or if the connection drops while in use.
Signed-off-by: Corey Minyard <cminy...@mvista.com> --- include/sysemu/char.h | 16 ++++- qemu-char.c | 165 +++++++++++++++++++++++++++++++++++++++++++++----- qemu-options.hx | 11 +++- 3 files changed, 173 insertions(+), 19 deletions(-) diff --git a/include/sysemu/char.h b/include/sysemu/char.h index f6844dd..1800c54 100644 --- a/include/sysemu/char.h +++ b/include/sysemu/char.h @@ -53,6 +53,17 @@ typedef struct { typedef void IOEventHandler(void *opaque, int event); +struct ReconData { + void *opaque; + uint64_t recon_time; + struct ReconHandlers *handlers; + void (*state_change)(void *open_opaque, int is_open); + void *state_change_opaque; + void (*reconnected)(struct ReconData *r); + void (*connection_lost)(struct ReconData *r); + void (*shutdown)(struct ReconData *r); +}; + struct CharDriverState { void (*init)(struct CharDriverState *s); int (*chr_write)(struct CharDriverState *s, const uint8_t *buf, int len); @@ -82,6 +93,8 @@ struct CharDriverState { guint fd_in_tag; QemuOpts *opts; QTAILQ_ENTRY(CharDriverState) next; + GSList *backend; + struct ReconData *recon; }; /** @@ -293,7 +306,8 @@ CharDriverState *qemu_chr_find(const char *name); QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename); void register_char_driver(const char *name, - CharDriverState *(*open)(CharDriverState *, QemuOpts *)); + CharDriverState *(*open)(CharDriverState *, QemuOpts *), + int (*recon_setup)(CharDriverState *)); void register_char_driver_qapi(const char *name, ChardevBackendKind kind, void (*parse)(QemuOpts *opts, ChardevBackend *backend, Error **errp)); diff --git a/qemu-char.c b/qemu-char.c index fc688cf..d9838aa 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -88,6 +88,44 @@ /***********************************************************/ /* character device */ +struct GenericReconData { + QEMUTimer *recon_timer; +}; + +static void generic_recon_timeout(void *opaque) +{ + struct ReconData *r = opaque; + struct GenericReconData *d = r->opaque; + + timer_mod(d->recon_timer, + (get_clock() + (r->recon_time * get_ticks_per_sec()))); + r->state_change(r->state_change_opaque, 0); +} + +static void generic_reconnected(struct ReconData *r) +{ + struct GenericReconData *d = r->opaque; + + timer_del(d->recon_timer); +} + +static void generic_connection_lost(struct ReconData *r) +{ + struct GenericReconData *d = r->opaque; + + r->state_change(r->state_change_opaque, 1); + timer_mod(d->recon_timer, + (get_clock() + (r->recon_time * get_ticks_per_sec()))); +} + +static void generic_recon_shutdown(struct ReconData *r) +{ + struct GenericReconData *d = r->opaque; + timer_free(d->recon_timer); + free(d); + r->opaque = NULL; +} + static QTAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs = QTAILQ_HEAD_INITIALIZER(chardevs); @@ -96,9 +134,15 @@ void qemu_chr_be_event(CharDriverState *s, int event) /* Keep track if the char device is open */ switch (event) { case CHR_EVENT_OPENED: + if (s->recon) { + s->recon->reconnected(s->recon); + } s->be_open = 1; break; case CHR_EVENT_CLOSED: + if (s->recon) { + s->recon->connection_lost(s->recon); + } s->be_open = 0; break; } @@ -2582,6 +2626,7 @@ static void tcp_chr_close(CharDriverState *chr) closesocket(s->listen_fd); } g_free(s); + chr->opaque = NULL; qemu_chr_be_event(chr, CHR_EVENT_CLOSED); } @@ -2641,8 +2686,6 @@ static CharDriverState *qemu_chr_open_socket_fd(CharDriverState *chr, chr->get_msgfd = tcp_get_msgfd; chr->chr_add_client = tcp_chr_add_client; chr->chr_add_watch = tcp_chr_add_watch; - /* be isn't opened until we get a connection */ - chr->explicit_be_open = true; if (is_listen) { s->listen_fd = fd; @@ -2680,6 +2723,9 @@ static CharDriverState *qemu_chr_open_socket(CharDriverState *chr, bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true); bool is_unix = qemu_opt_get(opts, "path") != NULL; + /* be isn't opened until we get a connection */ + chr->explicit_be_open = true; + if (is_unix) { if (is_listen) { fd = unix_listen_opts(opts, &local_err); @@ -2716,8 +2762,9 @@ static CharDriverState *qemu_chr_open_socket(CharDriverState *chr, if (fd >= 0) { closesocket(fd); } - if (chr) { + if (chr && chr->opaque) { g_free(chr->opaque); + chr->opaque = NULL; } return NULL; } @@ -3128,6 +3175,7 @@ static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend, typedef struct CharDriver { const char *name; + int (*recon_setup)(CharDriverState *); /* old, pre qapi */ CharDriverState *(*open)(CharDriverState *chr, QemuOpts *opts); /* new, qapi-based */ @@ -3138,13 +3186,15 @@ typedef struct CharDriver { static GSList *backends; void register_char_driver(const char *name, - CharDriverState *(*open)(CharDriverState*,QemuOpts *)) + CharDriverState *(*open)(CharDriverState*,QemuOpts *), + int (*recon_setup)(CharDriverState *)) { CharDriver *s; s = g_malloc0(sizeof(*s)); s->name = g_strdup(name); s->open = open; + s->recon_setup = recon_setup; backends = g_slist_append(backends, s); } @@ -3162,6 +3212,50 @@ void register_char_driver_qapi(const char *name, ChardevBackendKind kind, backends = g_slist_append(backends, s); } +static void generic_recon_state_change(void *opaque, int is_open) +{ + struct CharDriverState *chr = opaque; + + if (!is_open) { + struct CharDriver *cd = chr->backend->data; + + if (chr->be_open) { + return; + } + + cd->open(chr, chr->opts); + } else { + void (*chr_close)(struct CharDriverState *chr) = chr->chr_close; + + if (chr_close) { + chr->chr_close = NULL; + chr_close(chr); + } + } +} + +static int generic_recon_setup(struct CharDriverState *chr) +{ + struct GenericReconData *d; + + d = g_malloc(sizeof(*d)); + chr->recon->opaque = d; + + chr->recon->reconnected = generic_reconnected; + chr->recon->connection_lost = generic_connection_lost; + chr->recon->shutdown = generic_recon_shutdown; + chr->recon->state_change = generic_recon_state_change; + chr->recon->state_change_opaque = chr; + + d->recon_timer = timer_new(QEMU_CLOCK_REALTIME, SCALE_NS, + generic_recon_timeout, chr->recon); + + /* Make sure it connects in time. */ + timer_mod(d->recon_timer, + (get_clock() + (chr->recon->recon_time * get_ticks_per_sec()))); + return 1; +} + CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, void (*init)(struct CharDriverState *s), Error **errp) @@ -3169,6 +3263,7 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, CharDriver *cd; CharDriverState *chr; GSList *i; + uint64_t recon_time; if (qemu_opts_id(opts) == NULL) { error_setg(errp, "chardev: no id specified"); @@ -3244,11 +3339,41 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, } chr = g_malloc0(sizeof(CharDriverState)); - chr = cd->open(chr, opts); - if (!chr) { - error_setg(errp, "chardev: opening backend \"%s\" failed", - qemu_opt_get(opts, "backend")); - goto err; + + chr->backend = i; + recon_time = qemu_opt_get_number(opts, "reconnect", 0); + if (recon_time) { + CharDriver *d = chr->backend->data; + if (!d->recon_setup) { + g_free(chr); + fprintf(stderr, "chardev: reconnect not supported on %s\n", + qemu_opt_get(opts, "backend")); + return NULL; + } + if (qemu_opt_get_bool(opts, "server", 0)) { + g_free(chr); + fprintf(stderr, "chardev: server device cannot reconnect\n"); + return NULL; + } + chr->opts = opts; + chr->recon = g_malloc(sizeof(*chr->recon)); + chr->recon->recon_time = recon_time; + if (!d->recon_setup(chr)) { + g_free(chr->recon); + g_free(chr); + fprintf(stderr, "chardev: Unable to set up reconnect\n"); + return NULL; + } + } + + if (!cd->open(chr, opts)) { + if (!chr->recon) { + /* Reconnect is not enabled, give up */ + fprintf(stderr, "chardev: opening backend \"%s\" failed\n", + qemu_opt_get(opts, "backend")); + g_free(chr); + return NULL; + } } if (!chr->filename) @@ -3267,7 +3392,8 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, int len = strlen(qemu_opts_id(opts)) + 6; base->label = g_malloc(len); snprintf(base->label, len, "%s-base", qemu_opts_id(opts)); - chr = qemu_chr_open_mux(chr, base); + chr = g_malloc0(sizeof(CharDriverState)); + qemu_chr_open_mux(chr, base); chr->filename = base->filename; chr->avail_connections = MAX_MUX; QTAILQ_INSERT_TAIL(&chardevs, chr, next); @@ -3275,7 +3401,6 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, chr->avail_connections = 1; } chr->label = g_strdup(qemu_opts_id(opts)); - chr->opts = opts; return chr; err: @@ -3378,9 +3503,16 @@ void qemu_chr_fe_release(CharDriverState *s) void qemu_chr_delete(CharDriverState *chr) { + void (*chr_close)(struct CharDriverState *chr) = chr->chr_close; + QTAILQ_REMOVE(&chardevs, chr, next); - if (chr->chr_close) { - chr->chr_close(chr); + if (chr_close) { + chr->chr_close = NULL; + chr_close(chr); + } + if (chr->recon) { + chr->recon->shutdown(chr->recon); + g_free(chr->recon); } g_free(chr->filename); g_free(chr->label); @@ -3515,6 +3647,9 @@ QemuOptsList qemu_chardev_opts = { .name = "mux", .type = QEMU_OPT_BOOL, },{ + .name = "reconnect", + .type = QEMU_OPT_NUMBER, + },{ .name = "signal", .type = QEMU_OPT_BOOL, },{ @@ -3811,8 +3946,8 @@ void qmp_chardev_remove(const char *id, Error **errp) static void register_types(void) { register_char_driver_qapi("null", CHARDEV_BACKEND_KIND_NULL, NULL); - register_char_driver("socket", qemu_chr_open_socket); - register_char_driver("udp", qemu_chr_open_udp); + register_char_driver("socket", qemu_chr_open_socket, generic_recon_setup); + register_char_driver("udp", qemu_chr_open_udp, NULL); register_char_driver_qapi("ringbuf", CHARDEV_BACKEND_KIND_RINGBUF, qemu_chr_parse_ringbuf); register_char_driver_qapi("file", CHARDEV_BACKEND_KIND_FILE, diff --git a/qemu-options.hx b/qemu-options.hx index 56e5fdf..1002a3d 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1787,8 +1787,9 @@ ETEXI DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, "-chardev null,id=id[,mux=on|off]\n" "-chardev socket,id=id[,host=host],port=host[,to=to][,ipv4][,ipv6][,nodelay]\n" - " [,server][,nowait][,telnet][,mux=on|off] (tcp)\n" - "-chardev socket,id=id,path=path[,server][,nowait][,telnet],[mux=on|off] (unix)\n" + " [,server][,nowait][,telnet][,mux=on|off][,reconnect=seconds] (tcp)\n" + "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,mux=on|off]\n" + " [,reconnect=seconds] (unix)\n" "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n" " [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n" "-chardev msmouse,id=id[,mux=on|off]\n" @@ -1860,7 +1861,7 @@ Options to each backend are described below. A void device. This device will not emit any data, and will drop any data it receives. The null backend does not take any options. -@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] +@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}] Create a two-way stream socket, which can be either a TCP or a unix socket. A unix socket will be created if @option{path} is specified. Behaviour is @@ -1874,6 +1875,10 @@ connect to a listening socket. @option{telnet} specifies that traffic on the socket should interpret telnet escape sequences. +@option{reconnect} specifies that if the client socket does not connect at +startup, or if the client socket is closed for some reason (like the other +end exited), wait the given number of seconds and attempt to reconnect. + TCP and unix socket options are given below: @table @option -- 1.8.3.1