From patchwork Wed Mar 5 00:38:52 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Corey Minyard X-Patchwork-Id: 326558 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 5C1C12C0084 for ; Wed, 5 Mar 2014 11:59:19 +1100 (EST) Received: from localhost ([::1]:49479 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WKzuP-0006Lg-IO for incoming@patchwork.ozlabs.org; Tue, 04 Mar 2014 19:42:05 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:53392) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WKzsJ-00033o-6i for qemu-devel@nongnu.org; Tue, 04 Mar 2014 19:40:01 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1WKzsB-0000Af-ST for qemu-devel@nongnu.org; Tue, 04 Mar 2014 19:39:55 -0500 Received: from mail-qa0-x233.google.com ([2607:f8b0:400d:c00::233]:54516) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WKzsB-0000AO-DY for qemu-devel@nongnu.org; Tue, 04 Mar 2014 19:39:47 -0500 Received: by mail-qa0-f51.google.com with SMTP id cm18so325104qab.10 for ; Tue, 04 Mar 2014 16:39:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=QUdvSb6r/LBvYpK/DvrNLkbms+Mn4Fe3q/D07TfGx/Y=; b=S6XmYUskyvyEuZDhe/WtVVqr3ZUlpAKaOBlC9uGV86Qt+wLETcggP01si9zglI0Fjz akPYtHk79q/KoVy4HWqsk2aDc1v3tqkoAU7G5N5U7MkLdImgyvZOq6k3BimfDvGRG36k HgxB91+j8pyaRdHlwIze+d5xQyDHdBkg/SUiupZoywbuNTeFomz99C1+lfJZ0X0Ftfnt cDd+dnPf7vwYIzYQj9L26nx38+gcSJf8k8WB1zE/n7T+07bUF9rPcDiFAogi6B/E3CQI 0G1DcD3P7sa/2K3Qai/FsG2dcmqF25fT9zySUNSgFH1Xeiwgj+F5g6yFh9myruNKxbr9 xgTA== X-Received: by 10.140.98.203 with SMTP id o69mr3089576qge.102.1393979986709; Tue, 04 Mar 2014 16:39:46 -0800 (PST) Received: from t430.minyard.home (pool-173-57-152-84.dllstx.fios.verizon.net. [173.57.152.84]) by mx.google.com with ESMTPSA id x3sm4129545oek.3.2014.03.04.16.39.45 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 04 Mar 2014 16:39:45 -0800 (PST) Received: from t430.minyard.home (t430.minyard.home [127.0.0.1]) by t430.minyard.home (8.14.7/8.14.7) with ESMTP id s250dYb0009883; Tue, 4 Mar 2014 18:39:44 -0600 Received: (from cminyard@localhost) by t430.minyard.home (8.14.7/8.14.7/Submit) id s250dOiY009870; Tue, 4 Mar 2014 18:39:24 -0600 From: minyard@acm.org To: qemu-devel@nongnu.org Date: Tue, 4 Mar 2014 18:38:52 -0600 Message-Id: <1393979937-9082-3-git-send-email-minyard@acm.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1393979937-9082-1-git-send-email-minyard@acm.org> References: <1393979937-9082-1-git-send-email-minyard@acm.org> X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2607:f8b0:400d:c00::233 Cc: bcketchum@gmail.com, Corey Minyard , hwd@huawei.com, afaerber@suse.de, mst@redhat.com Subject: [Qemu-devel] [PATCH 2/7] qemu-char: Allow a chardev to reconnect if disconnected X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Corey Minyard 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 --- 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