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 | 3 ++ qemu-char.c | 88 ++++++++++++++++++++++++++++++++++++++++++++------- qemu-options.hx | 11 +++++-- 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/include/sysemu/char.h b/include/sysemu/char.h index 44baf0f..3d21b5a 100644 --- a/include/sysemu/char.h +++ b/include/sysemu/char.h @@ -81,6 +81,9 @@ struct CharDriverState { guint fd_in_tag; QemuOpts *opts; QTAILQ_ENTRY(CharDriverState) next; + GSList *backend; + QEMUTimer *recon_timer; + uint64_t recon_time; }; /** diff --git a/qemu-char.c b/qemu-char.c index dd04cc0..23d7647 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -96,9 +96,22 @@ 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_timer) { + timer_del(s->recon_timer); + } s->be_open = 1; break; case CHR_EVENT_CLOSED: + if (s->recon_timer) { + void (*chr_close)(struct CharDriverState *chr) = s->chr_close; + if (chr_close) { + s->chr_close = NULL; + chr_close(s); + } + timer_mod(s->recon_timer, + (get_clock() + + (s->recon_time * get_ticks_per_sec()))); + } s->be_open = 0; break; } @@ -2583,6 +2596,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); } @@ -2642,8 +2656,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; @@ -2681,6 +2693,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); @@ -2717,8 +2732,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; } @@ -3163,6 +3179,20 @@ void register_char_driver_qapi(const char *name, ChardevBackendKind kind, backends = g_slist_append(backends, s); } +static void recon_timeout(void *opaque) +{ + CharDriverState *chr = opaque; + CharDriver *cd = chr->backend->data; + + if (chr->be_open) { + return; + } + + timer_mod(chr->recon_timer, + (get_clock() + (chr->recon_time * get_ticks_per_sec()))); + cd->open(chr, chr->opts); +} + CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, void (*init)(struct CharDriverState *s), Error **errp) @@ -3245,11 +3275,36 @@ 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; + chr->recon_time = qemu_opt_get_number(opts, "reconnect", 0); + if (chr->recon_time) { + if (strcmp(qemu_opt_get(opts, "backend"), "socket") != 0) { + g_free(chr); + fprintf(stderr, "chardev: reconnect only supported on sockets\n"); + 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_timer = timer_new(QEMU_CLOCK_REALTIME, SCALE_NS, + recon_timeout, chr); + + /* Make sure it connects in time. */ + timer_mod(chr->recon_timer, + (get_clock() + (chr->recon_time * get_ticks_per_sec()))); + } + + if (!cd->open(chr, opts)) { + if (!chr->recon_time) { + /* Reconnect is not enabled, give up */ + fprintf(stderr, "chardev: opening backend \"%s\" failed\n", + qemu_opt_get(opts, "backend")); + return NULL; + } } if (!chr->filename) @@ -3268,7 +3323,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); @@ -3276,7 +3332,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: @@ -3372,9 +3427,15 @@ 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_timer) { + timer_free(chr->recon_timer); } g_free(chr->filename); g_free(chr->label); @@ -3490,6 +3551,9 @@ QemuOptsList qemu_chardev_opts = { .name = "mux", .type = QEMU_OPT_BOOL, },{ + .name = "reconnect", + .type = QEMU_OPT_NUMBER, + },{ .name = "signal", .type = QEMU_OPT_BOOL, },{ diff --git a/qemu-options.hx b/qemu-options.hx index 5dc8b75..5bcfaa0 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1780,8 +1780,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" @@ -1853,7 +1854,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 @@ -1867,6 +1868,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 socket does not come up at startup, +or if the 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