[pve-devel] [PATCH qemu-server v3 1/3] add qmeventd

Thomas Lamprecht t.lamprecht at proxmox.com
Wed Oct 31 09:04:58 CET 2018


Am 10/30/2018 um 04:06 PM schrieb Dominik Csapak:
> 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,
> executes qm cleanup with arguments that indicate if the
> vm was closed gracefully and whether the guest initiated it
> 
> this is useful if we want to cleanup after the qemu process exited,
> e.g. tap devices, vgpus, etc.
> 
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
>  Makefile         |  21 ++-
>  debian/control   |   1 +
>  debian/rules     |   2 +-
>  qmeventd.c       | 386 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  qmeventd.h       |  45 +++++++
>  qmeventd.rst     |  38 ++++++

Only reading the extension of the file above I thought you did it with Rust ;-)

So why restructured text and not or proven asciidoc?
(Not really oposed, but you nowhere mention the docs/man page at all, AFAICT,
and your reasoning for RsT while everything else is asciidoc would be good to
have). pve-doc-generator has infrastructure for .adoc to man...

Also why do you place this at the top level rootdir of the repo?

>  qmeventd.service |  10 ++
>  7 files changed, 499 insertions(+), 4 deletions(-)
>  create mode 100644 qmeventd.c
>  create mode 100644 qmeventd.h
>  create mode 100644 qmeventd.rst
>  create mode 100644 qmeventd.service
> 
> diff --git a/Makefile b/Makefile
> index c531d04..6c6f165 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -2,7 +2,10 @@ VERSION=5.0
>  PACKAGE=qemu-server
>  PKGREL=38
>  
> -CFLAGS= -O2 -Werror -Wall -Wtype-limits -Wl,-z,relro 
> +CFLAGS=-O2 -Werror -Wall -Wextra -Wpedantic -Wtype-limits -Wl,-z,relro
> +JSON_CFLAGS=$(shell pkg-config --cflags json-c)
> +JSON_LIBS=$(shell pkg-config --libs json-c)
> +RST2MAN=/usr/bin/rst2man
>  
>  DESTDIR=
>  PREFIX=/usr
> @@ -10,6 +13,7 @@ BINDIR=${PREFIX}/bin
>  SBINDIR=${PREFIX}/sbin
>  BINDIR=${PREFIX}/bin
>  LIBDIR=${PREFIX}/lib/${PACKAGE}
> +SERVICEDIR=/lib/systemd/system
>  VARLIBDIR=/var/lib/${PACKAGE}
>  MANDIR=${PREFIX}/share/man
>  DOCDIR=${PREFIX}/share/doc
> @@ -36,6 +40,12 @@ all:
>  dinstall: deb
>  	dpkg -i ${DEB}
>  
> +qmeventd: qmeventd.c
> +	$(CC) $(CFLAGS) ${JSON_CFLAGS} -o $@ $< ${JSON_LIBS} 
> +
> +qmeventd.1: qmeventd.rst
> +	${RST2MAN} $< $@
> +
>  qm.bash-completion:
>  	PVE_GENERATING_DOCS=1 perl -I. -T -e "use PVE::CLI::qm; PVE::CLI::qm->generate_bash_completions();" >$@.tmp
>  	mv $@.tmp $@
> @@ -44,13 +54,14 @@ qmrestore.bash-completion:
>  	PVE_GENERATING_DOCS=1 perl -I. -T -e "use PVE::CLI::qmrestore; PVE::CLI::qmrestore->generate_bash_completions();" >$@.tmp
>  	mv $@.tmp $@
>  
> -PKGSOURCES=qm qm.1 qmrestore qmrestore.1 qmextract qm.conf.5 qm.bash-completion qmrestore.bash-completion
> +PKGSOURCES=qm qm.1 qmrestore qmrestore.1 qmextract qm.conf.5 qm.bash-completion qmrestore.bash-completion qmeventd qmeventd.1
>  
>  .PHONY: install
>  install: ${PKGSOURCES}
>  	install -d ${DESTDIR}/${SBINDIR}
>  	install -d ${DESTDIR}${LIBDIR}
>  	install -d ${DESTDIR}${VARLIBDIR}
> +	install -d ${DESTDIR}${SERVICEDIR}
>  	install -d ${DESTDIR}/${MAN1DIR}
>  	install -d ${DESTDIR}/${MAN5DIR}
>  	install -d ${DESTDIR}/usr/share/man/man5
> @@ -63,6 +74,8 @@ install: ${PKGSOURCES}
>  	make -C PVE install
>  	install -m 0755 qm ${DESTDIR}${SBINDIR}
>  	install -m 0755 qmrestore ${DESTDIR}${SBINDIR}
> +	install -m 0755 qmeventd ${DESTDIR}${SBINDIR}
> +	install -m 0644 qmeventd.service ${DESTDIR}${SERVICEDIR}
>  	install -m 0755 pve-bridge ${DESTDIR}${VARLIBDIR}/pve-bridge
>  	install -m 0755 pve-bridge-hotplug ${DESTDIR}${VARLIBDIR}/pve-bridge-hotplug
>  	install -m 0755 pve-bridgedown ${DESTDIR}${VARLIBDIR}/pve-bridgedown
> @@ -70,6 +83,8 @@ install: ${PKGSOURCES}
>  	install -m 0755 qmextract ${DESTDIR}${LIBDIR}
>  	install -m 0644 qm.1 ${DESTDIR}/${MAN1DIR}
>  	gzip -9 -n -f ${DESTDIR}/${MAN1DIR}/qm.1
> +	install -m 0644 qmeventd.1 ${DESTDIR}/${MAN1DIR}
> +	gzip -9 -n -f ${DESTDIR}/${MAN1DIR}/qmeventd.1
>  	install -m 0644 qmrestore.1 ${DESTDIR}/${MAN1DIR}
>  	gzip -9 -n -f ${DESTDIR}/${MAN1DIR}/qmrestore.1
>  	install -m 0644 qm.conf.5 ${DESTDIR}/${MAN5DIR}
> @@ -97,7 +112,7 @@ upload: ${DEB}
>  .PHONY: clean
>  clean:
>  	make cleanup-docgen
> -	rm -rf build *.deb *.buildinfo *.changes
> +	rm -rf build *.deb *.buildinfo *.changes qmeventd
>  	find . -name '*~' -exec rm {} ';'
>  
>  
> diff --git a/debian/control b/debian/control
> index f3b9ca0..5b3cb1f 100644
> --- a/debian/control
> +++ b/debian/control
> @@ -3,6 +3,7 @@ Section: admin
>  Priority: optional
>  Maintainer: Proxmox Support Team <support at proxmox.com>
>  Build-Depends: debhelper (>= 7.0.50~),
> +               docutils,
>                 libio-multiplex-perl,
>                 libpve-common-perl,
>                 libpve-guest-common-perl (>= 2.0-18),
> diff --git a/debian/rules b/debian/rules
> index 955dd78..97112b4 100755
> --- a/debian/rules
> +++ b/debian/rules
> @@ -10,4 +10,4 @@
>  #export DH_VERBOSE=1
>  
>  %:
> -	dh $@
> +	dh $@ --with systemd

compat level 10 should handle that, FYI.

> diff --git a/qmeventd.c b/qmeventd.c
> new file mode 100644
> index 0000000..9498cd0
> --- /dev/null
> +++ b/qmeventd.c
> @@ -0,0 +1,386 @@
> +/*
> +
> +    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>
> +
> +    qmevend listens on a given socket, and waits for qemu processes
> +    to connect
> +
> +    it then waits for shutdown events followed by the closing of the socket,
> +    it then calls /usr/sbin/qm cleanup with following arguments
> +
> +    /usr/sbin/qm cleanup VMID <graceful> <guest>
> +
> +    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
> +
> +*/
> +
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#include <errno.h>
> +#include <json.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/socket.h>
> +#include <sys/types.h>
> +#include <sys/un.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +
> +#include "qmeventd.h"
> +
> +static int verbose = 0;
> +static int epoll_fd = 0;
> +/*
> + * Helper functions
> + */
> +
> +static void
> +usage (char *argv[])
> +{
> +    fprintf(stderr, "Usage: %s [-f] [-v] PATH\n", argv[0]);

it's often practice to have a some static char* progname (or execname)
variable in the module and set this to argv[0] at the start of main.

No need to pass all arguments to this method?

> +    fprintf(stderr, "  -f       run in foreground (default: false)\n");
> +    fprintf(stderr, "  -v       verbose (default: false)\n");
> +    fprintf(stderr, "  PATH     use PATH for socket\n");
> +}
> +
> +static pid_t
> +get_pid_from_fd (int fd)
> +{
> +    struct ucred credentials;
> +    socklen_t len = sizeof(struct ucred);
> +    PERR_NEG(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &len), "getsockopt");
> +    return credentials.pid;
> +}
> +
> +/*
> + * reads the vmid from /proc/<pid>/cmdline
> + * after the '-id' argument
> + */
> +static unsigned int
> +get_vmid_from_pid (pid_t pid)
> +{
> +    unsigned int vmid = 0;
> +    int ch;
> +    FILE *fp;
> +    char filename[32] = { 0 };
> +    char buf[5] = { 0 };
> +    sprintf(filename, "/proc/%d/cmdline", pid);

why not snprintf ? (yeah, I see that filename ought to have enough
space for any reasonable pid range, still, and if it's only for
reducing the chance to introduce a copy-is-my-hobby issue from this...

> +    fp = fopen(filename, "r");
> +    if (fp == NULL) {
> +	perror("fopen cmdline");
> +	goto ret;
> +    }
> +
> +    while(fgets(buf, 5, fp) != NULL) {

this assumes that there's no argument (option or property) shorter than 4
characters, else you may get a phase shift, e.g:

AFAICT, from readin the man page, fgets only returns on, size-1 read, EOF or
newline, so a \0 can be read just fine with continuing, so if we had

|0|1| 2|3|4|5| 6|
|d|e|\0|-|i|d|\0|

(this would be from a '-k de -id XYZ' cmdline) your fgets would consume
the 'de -i' part at the first read and thus never match -id

Even if fgets would return on \0 there's still a problem below:

> +	if (strcmp(buf, "-id") == 0) {
> +	    int i = 0;
> +	    while ((ch = fgetc(fp)) != EOF) {
> +		if (ch <= '9' && ch >= '0') {
> +		    if (i >= VMID_LEN - 1) {
> +			fprintf(stderr, "vmid too long\n");
> +			vmid = 0;
> +			goto ret;
> +		    }
> +		    vmid *= 10;
> +		    vmid += (unsigned int)(ch - '0');
> +		    i++;
> +		} else if (ch == '\0') {
> +		    goto ret;
> +		} else {
> +		    fprintf(stderr, "invalid id\n");
> +		    vmid = 0;
> +		    goto ret;
> +		}
> +	    }
> +	} else {

also if above fgets read exact to a \0 boundary this will consume the next
argument without looking at it?

(FYI: didn't looked much at the rest of the code...)

> +	    while((ch = fgetc(fp)) != EOF && ch != '\0');
> +	}
> +    }
> +
> +ret:
> +    fclose(fp);
> +    return vmid;
> +}
> +
> +/*
> + * qmp handling functions
> + */
> +
> +void
> +handle_qmp_handshake(struct Client *client)
> +{
> +    VERBOSE_PRINT("%s: got QMP handshake\n", client->vmid);
> +    ssize_t wlen;
> +    do {
> +	wlen = write(client->fd, QMP_ANSWER, strlen(QMP_ANSWER) - 1);
> +    } while (wlen != strlen(QMP_ANSWER) - 1 && errno == EINTR);
> +    if (wlen != strlen(QMP_ANSWER) - 1) {
> +	fprintf(stderr, "%s: can not complete handshake\n", client->vmid);
> +	cleanup_client(client);
> +    }
> +}
> +
> +void
> +handle_qmp_event(struct Client *client, struct json_object *obj)
> +{
> +    struct json_object *event;
> +    if (!json_object_object_get_ex(obj, "event", &event)) {
> +	return;
> +    }
> +    VERBOSE_PRINT("%s: got QMP event: %s\n", client->vmid,
> +		  json_object_get_string(event));
> +    // event, check if shutdown and get guest parameter
> +    if (!strcmp(json_object_get_string(event), "SHUTDOWN")) {
> +	client->graceful = 1;
> +	struct json_object *data;
> +	struct json_object *guest;
> +	if (json_object_object_get_ex(obj, "data", &data) &&
> +	    json_object_object_get_ex(data, "guest", &guest)) {
> +	    client->guest = (unsigned short)json_object_get_boolean(guest);
> +	}
> +    }
> +}
> +
> +/*
> + * client management functions
> + */
> +
> +void
> +add_new_client(int client_fd)
> +{
> +    struct Client *client = calloc(sizeof(struct Client), 1);
> +    client->fd = client_fd;
> +    client->pid = get_pid_from_fd(client_fd);
> +    unsigned int vmid = get_vmid_from_pid(client->pid);
> +    if (vmid == 0) {
> +	fprintf(stderr, "invalid client\n");
> +	PERR_NEG(close(client_fd), "close invalid client");
> +	free(client);
> +	return;
> +    }
> +    snprintf(client->vmid, VMID_LEN, "%d", vmid);
> +
> +    struct epoll_event ev;
> +    ev.events = EPOLLIN;
> +    ev.data.ptr = client;
> +    PERR_NEG(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev),
> +	     "epoll_ctl client");
> +    VERBOSE_PRINT("added new client, pid: %d, vmid: %s\n", client->pid,
> +		client->vmid);
> +}
> +
> +void
> +cleanup_client(struct Client *client)
> +{
> +    VERBOSE_PRINT("%s: client exited, status: graceful: %d, guest: %d\n",
> +		  client->vmid, client->graceful, client->guest);
> +    PERR_NEG(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL), "epoll del");
> +    PERR_NEG(close(client->fd), "close client");
> +    unsigned short graceful = client->graceful;
> +    unsigned short guest = client->guest;
> +    char vmid[VMID_LEN + 1];
> +    strncpy(vmid, client->vmid, VMID_LEN + 1);
> +    free(client);
> +    VERBOSE_PRINT("%s: executing cleanup\n", vmid);
> +
> +    int pid = fork();
> +    if (pid < 0) {
> +	fprintf(stderr, "fork failed: %s\n", strerror(errno));
> +	return;
> +    }
> +    if (pid == 0) {
> +	char *script = "/usr/sbin/qm";
> +	char **args = malloc((size_t)(6) * sizeof(*args));
> +
> +	args[0] = script;
> +	args[1] = "cleanup";
> +	args[2] = vmid;
> +
> +	char shutdown_args[4] = {
> +	    graceful ? '1' : '0', 0,
> +	    guest    ? '1' : '0', 0,
> +	};
> +
> +	args[3] = &shutdown_args[0];
> +	args[4] = &shutdown_args[2];
> +	args[5] = NULL;
> +
> +	execvp(script, args);
> +	exit(1);
> +    }
> +}
> +
> +void
> +handle_client(struct Client *client)
> +{
> +    VERBOSE_PRINT("%s: entering handle\n", client->vmid);
> +    ssize_t len;
> +    len = read(client->fd, (client->buf+client->buflen), BUF_SIZE - client->buflen);
> +    VERBOSE_PRINT("%s: read %ld bytes\n", client->vmid, len);
> +    if (len == 0) {
> +	cleanup_client(client);
> +	return;
> +    }
> +    client->buflen += len;
> +
> +    struct json_tokener *tok = json_tokener_new();
> +    struct json_object *jobj = NULL;
> +    enum json_tokener_error jerr = json_tokener_success;
> +    while (jerr == json_tokener_success && client->buflen != 0) {
> +	jobj = json_tokener_parse_ex(tok, client->buf, (int)client->buflen);
> +	jerr = json_tokener_get_error(tok);
> +	unsigned int offset = (unsigned int)tok->char_offset;
> +	switch (jerr) {
> +	    case json_tokener_success:
> +		// move rest from buffer to front
> +		memmove(client->buf, client->buf + offset, client->buflen - offset);
> +		client->buflen -= offset;
> +		if (json_object_is_type(jobj, json_type_object)) {
> +		    struct json_object *obj;
> +		    if (json_object_object_get_ex(jobj, "QMP", &obj)) {
> +			handle_qmp_handshake(client);
> +		    } else if (json_object_object_get_ex(jobj, "event", &obj)) {
> +			handle_qmp_event(client, jobj);
> +		    } // else ignore message
> +		}
> +		break;
> +	    case json_tokener_continue:
> +		if (client->buflen >= BUF_SIZE) {
> +		    VERBOSE_PRINT("%s, msg too large, discarding buffer\n",
> +				  client->vmid);
> +		    memset(client->buf, 0, BUF_SIZE);
> +		    client->buflen = 0;
> +		} // else we have enough space try again after next read
> +		break;
> +	    case json_tokener_error_depth:
> +	    case json_tokener_error_parse_eof:
> +	    case json_tokener_error_parse_unexpected:
> +	    case json_tokener_error_parse_null:
> +	    case json_tokener_error_parse_boolean:
> +	    case json_tokener_error_parse_number:
> +	    case json_tokener_error_parse_array:
> +	    case json_tokener_error_parse_object_key_name:
> +	    case json_tokener_error_parse_object_key_sep:
> +	    case json_tokener_error_parse_object_value_sep:
> +	    case json_tokener_error_parse_string:
> +	    case json_tokener_error_parse_comment:
> +	    case json_tokener_error_size:
> +		VERBOSE_PRINT("%s: parse error: %d, discarding buffer\n",
> +			      client->vmid, jerr);
> +		memset(client->buf, 0, client->buflen);
> +		client->buflen = 0;
> +		break;
> +	}
> +	json_object_put(jobj);
> +    }
> +    json_tokener_free(tok);
> +}
> +
> +
> +int
> +main (int argc, char *argv[])
> +{
> +    int opt;
> +    int daemonize = 1;
> +    char *socket_path = NULL;
> +
> +    while ((opt = getopt(argc, argv, "hfv")) != -1) {
> +	switch (opt) {
> +	    case 'f':
> +		daemonize = 0;
> +		break;
> +	    case 'v':
> +		verbose = 1;
> +		break;
> +	    case 'h':
> +	    default:
> +		usage(argv);
> +		exit(EXIT_FAILURE);
> +	}
> +    }
> +
> +    if (optind >= argc) {
> +	usage(argv);
> +	exit(EXIT_FAILURE);
> +    }
> +
> +    socket_path = argv[optind];
> +
> +    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
> +    PERR_NEG(sock, "socket");
> +
> +    struct sockaddr_un addr;
> +    memset(&addr, 0, sizeof(addr));
> +    addr.sun_family = AF_UNIX;
> +    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
> +
> +    unlink(socket_path);
> +    PERR_NEG(bind(sock, (struct sockaddr*)&addr, sizeof(addr)), "bind");
> +
> +    struct epoll_event ev, events[1];
> +    epoll_fd = epoll_create1(0);
> +    PERR_NEG(epoll_fd, "epoll_create1");
> +
> +    ev.events = EPOLLIN;
> +    ev.data.fd = sock;
> +    PERR_NEG(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev), "epoll_ctl");
> +
> +    PERR_NEG(listen(sock, 10), "listen");
> +
> +    if (daemonize) {
> +	PERR_NEG(daemon(0, 1), "daemon");
> +    }
> +
> +    int should_exit = 0;
> +    int nevents, n;
> +
> +    while(!should_exit) {
> +	nevents = epoll_wait(epoll_fd, events, 1, -1);
> +	if (nevents < 0 && errno == EINTR) {
> +	    // signal happened, try again
> +	    continue;
> +	}
> +	PERR_NEG(nevents, "epoll_wait");
> +
> +	for (n = 0; n < nevents; n++) {
> +	    if (events[n].data.fd == sock) {
> +
> +		int conn_sock = accept(sock, NULL, NULL);
> +		PERR_NEG(conn_sock, "accept");
> +		int flags = fcntl(conn_sock, F_GETFL);
> +		PERR_NEG(fcntl(conn_sock, F_SETFL, flags | O_NONBLOCK), "fcntl");
> +
> +		add_new_client(conn_sock);
> +	    } else {
> +		handle_client((struct Client *)events[n].data.ptr);
> +	    }
> +	}
> +    }
> +}
> diff --git a/qmeventd.h b/qmeventd.h
> new file mode 100644
> index 0000000..39024e6
> --- /dev/null
> +++ b/qmeventd.h
> @@ -0,0 +1,45 @@
> +/*
> +
> +    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>
> +*/
> +
> +#define PERR_NEG(X,Y) do { if (X < 0) { perror(Y); exit(EXIT_FAILURE); }} while (0)
> +#define VERBOSE_PRINT(...) do { if (verbose) { printf(__VA_ARGS__); } } while (0)
> +
> +#define BUF_SIZE 4096
> +#define VMID_LEN 16
> +#define QMP_ANSWER "{\"execute\":\"qmp_capabilities\"}\n"
> +
> +struct Client {
> +    char buf[BUF_SIZE];
> +    char vmid[VMID_LEN];
> +    int fd;
> +    pid_t pid;
> +    unsigned int buflen;
> +    unsigned short graceful;
> +    unsigned short guest;
> +};
> +
> +void handle_qmp_handshake(struct Client *client);
> +void handle_qmp_event(struct Client *client, struct json_object *obj);
> +void handle_client(struct Client *client);
> +void add_new_client(int client_fd);
> +void cleanup_client(struct Client *client);
> diff --git a/qmeventd.rst b/qmeventd.rst
> new file mode 100644
> index 0000000..2eefa31
> --- /dev/null
> +++ b/qmeventd.rst
> @@ -0,0 +1,38 @@
> +========
> +qmeventd
> +========
> +
> +-------------------------
> +listen to qemu qmp events
> +-------------------------
> +
> +:Author: Proxmox Support Team <support at proxmox.com>
> +:Manual section: 1
> +:Manual group: qmeventd Manual
> +
> +SYNOPSIS
> +========
> +
> +``qmeventd`` [``-f``] [``-v``] PATH
> +
> +DESCRIPTION
> +===========
> +
> +``qmeventd`` is a daemon that listens on PATH for incoming connections from
> +a qemu qmp socket, and waits for SHUTDOWN events. When a client then
> +disconnects, it executes ``/usr/sbin/qm cleanup``. This makes it easy
> +to clean up leftover tap devices, vgpus, etc.
> +
> +``-v``
> +    Be verbose about (dis)connecting clients and their messages.
> +
> +``-f``
> +    Don't daemonize and run in foreground.
> +
> +``PATH``
> +    The path to listen on.
> +
> +BUGS
> +====
> +
> +Please report bugs at https://bugzilla.proxmox.com/
> diff --git a/qmeventd.service b/qmeventd.service
> new file mode 100644
> index 0000000..42a12c1
> --- /dev/null
> +++ b/qmeventd.service
> @@ -0,0 +1,10 @@
> +[Unit]
> +Description=PVE Qemu Event Daemon
> +ConditionPathExists=/usr/sbin/qmeventd

why this condition?? You're using a package system, so this condition is
met if the service file is there and thus the package is installed.
No need to add unnecessary conditons...

> +
> +[Service]
> +ExecStart=/usr/sbin/qmeventd /var/run/qemu-server/event.socket

s/event/qmeventd/ ?

> +Type=forking
> +
> +[Install]
> +WantedBy=multi-user.target
> 





More information about the pve-devel mailing list