[pve-devel] [RFC PATCH qemu-server] add qemumonitor.c

Dominik Csapak d.csapak at proxmox.com
Tue Oct 9 14:49:22 CEST 2018


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);
+}
-- 
2.11.0





More information about the pve-devel mailing list