[pve-devel] [RFC PATCH qemu-server] add qemumonitor.c
David Limbeck
d.limbeck at proxmox.com
Tue Oct 9 16:54:01 CEST 2018
One socket is exclusive to one client, so it needs another qmp socket.
On 10/9/18 4:50 PM, Alexandre DERUMIER wrote:
>>> this adds a program that can listen to qemu qmp events on a given socket
> Does it work in parallel with sending qmp command ?
>
> As far I remember, some year ago, it was not possible to have 2 qmp clients at
> the same time. (and needed some kind of proxy betweens clients and qemu)
>
> Maybe more complex, but t could be great to be able to catch any events,
> and use them in QemuServer.pm for example.
>
>
> ----- Mail original -----
> De: "Dominik Csapak" <d.csapak at proxmox.com>
> À: "pve-devel" <pve-devel at pve.proxmox.com>
> Envoyé: Mardi 9 Octobre 2018 14:49:22
> Objet: [pve-devel] [RFC PATCH qemu-server] add qemumonitor.c
>
> this adds a program that can listen to qemu qmp events on a given socket
> and if a shutdown event followed by a disconnected socket occurs,
> execute the given script with the given and additional arguments
>
> this is useful if we want to cleanup after the qemu process exited,
> e.g. tap devices, vgpus, etc.
>
> also we could implement a 'proper' reboot with applying pending changes
> and a stop/reboot hoook
>
> for now, this needs a not-yet applied patch[1] to qemu
> but this should be trivial to backport
>
> 1: https://lists.gnu.org/archive/html/qemu-devel/2018-10/msg01271.html
>
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
> sending this as rfc, without makefile/manpage/inclusion in the package/use/
> build-dependencies/etc.
>
> location and name of it are ofc subject to change :)
> i just want a general feedback of the code and the interface
>
> i had imagined starting this tool after a qemu start
> with a 'qm cleanup ID' tool to do the general cleanup
>
> the program links against libjansson4, a ~75k library with only libc6 as
> dependency, and the program uses about 100k RSS memory,
> so i think this is an acceptable overhead
> for a vm (with possibly multiple gbs of ram)
>
> qemumonitor.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 166 insertions(+)
> create mode 100644 qemumonitor.c
>
> diff --git a/qemumonitor.c b/qemumonitor.c
> new file mode 100644
> index 0000000..13dcfa2
> --- /dev/null
> +++ b/qemumonitor.c
> @@ -0,0 +1,166 @@
> +/*
> +
> + Copyright (C) 2018 Proxmox Server Solutions GmbH
> +
> + Copyright: qemumonitor is under GNU GPL, the GNU General Public License.
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; version 2 dated June, 1991.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
> + 02111-1307, USA.
> +
> + Author: Dominik Csapak <d.csapak at proxmox.com>
> +
> + qemumonitor connects to a given qmp socket, and waits for a
> + shutdown event followed by the closing of the socket,
> + it then calls the given script with following arguments
> +
> + SCRIPT [ARGUMENTS] <graceful> <guest> <was_reset>
> +
> + parameter explanation:
> +
> + graceful:
> + 1|0 depending if it saw a shutdown event before the socket closed
> +
> + guest:
> + 1|0 depending if the shutdown was requested from the guest
> +
> + was_reset:
> + 1|0 depending if the shutdown was actually a request
> +
> +*/
> +
> +#include <errno.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +
> +#include <jansson.h>
> +
> +typedef enum { false, true } bool;
> +typedef enum { STATE_PRECONNECTING, STATE_CONNECTING, STATE_CONNECTED } state_t;
> +
> +#define QMP_ANSWER "{ \"execute\":\"qmp_capabilities\" }\n"
> +
> +void usage(char *name);
> +
> +void usage(char *name)
> +{
> + fprintf(stderr, "Usage: %s SOCKET SCRIPT [ARGUMENTS..]\n", name);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + if (argc < 3) {
> + usage(argv[0]);
> + exit(EXIT_FAILURE);
> + }
> +
> + ssize_t len;
> + bool graceful_shutdown = false;
> + bool guest_requested = false;
> + bool was_reset = false;
> +
> + struct sockaddr_un serv_addr;
> + int sock;
> + FILE *socketfile;
> +
> + sock = socket(AF_UNIX, SOCK_STREAM, 0);
> + if (sock == -1) {
> + fprintf(stderr, "cannot create unix socket: %s\n", strerror(errno));
> + exit(EXIT_FAILURE);
> + }
> +
> + memset(&serv_addr, 0, sizeof(serv_addr));
> + serv_addr.sun_family = AF_UNIX;
> + memcpy(&(serv_addr.sun_path), argv[1], strlen(argv[1]));
> +
> + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
> + close(sock);
> + fprintf(stderr, "error connecting to %s: %s\n", argv[1], strerror(errno));
> + exit(EXIT_FAILURE);
> + }
> +
> + socketfile = fdopen(sock, "r");
> + if (socketfile == NULL) {
> + fclose(socketfile);
> + fprintf(stderr, "error opening %s: %s\n", argv[1], strerror(errno));
> + exit(EXIT_FAILURE);
> + }
> +
> + json_t *json;
> + json_error_t err;
> + bool guest;
> + bool reset;
> + const char *event;
> + state_t qmp_state = STATE_PRECONNECTING;
> +
> + while (!feof(socketfile)) {
> + json = json_loadf(socketfile, JSON_DISABLE_EOF_CHECK, &err);
> + if (json == NULL) {
> + // ignore parser errors
> + continue;
> + }
> + switch (qmp_state) {
> + case STATE_PRECONNECTING:
> + if (json_unpack(json, "{s: {s:{}, s:[]}}", "QMP", "version",
> + "capabilities") == 0) {
> + do {
> + len = write(sock, QMP_ANSWER, sizeof(QMP_ANSWER) - 1);
> + } while (len < 0 && errno == EINTR);
> + qmp_state = STATE_CONNECTING;
> + }
> + break;
> + case STATE_CONNECTING:
> + if (json_unpack(json, "{s:{}}", "return") == 0) {
> + qmp_state = STATE_CONNECTED;
> + if (daemon(0,0) == -1) {
> + fprintf(stderr, "cannot daemonize: %s\n",
> + strerror(errno));
> + exit(EXIT_FAILURE);
> + }
> + }
> + break;
> + case STATE_CONNECTED:
> + if (json_unpack(json, "{s:s, s:{s:b, s:b}}", "event", &event,
> + "data", "guest", &guest, "was_reset", &reset) == 0) {
> + if (strncmp("SHUTDOWN", event, sizeof("SHUTDOWN")) == 0) {
> + guest_requested = guest;
> + was_reset = reset;
> + graceful_shutdown = true;
> + }
> + }
> + break;
> + }
> + json_decref(json);
> + }
> + fclose(socketfile);
> +
> + char *script = argv[2];
> + char **args = (char **)malloc((unsigned long)(argc+2)*sizeof(char *));
> + for (int i = 0; i < argc - 2; i++) {
> + args[i] = argv[i+2];
> + }
> +
> + for (int i = argc - 2; i < argc + 1; i++) {
> + args[i] = malloc(2*sizeof(char));
> + }
> +
> + snprintf(args[argc-2], 2, "%d", graceful_shutdown);
> + snprintf(args[argc-1], 2, "%d", guest_requested);
> + snprintf(args[argc], 2, "%d", was_reset);
> + args[argc+1] = NULL;
> + execvp(script, args);
> + exit(1);
> +}
More information about the pve-devel
mailing list