]> git.proxmox.com Git - qemu-server.git/blob - qmeventd.c
d/copyright: update years
[qemu-server.git] / qmeventd.c
1 /*
2
3 Copyright (C) 2018 Proxmox Server Solutions GmbH
4
5 Copyright: qmeventd is under GNU GPL, the GNU General Public License.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; version 2 dated June, 1991.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20
21 Author: Dominik Csapak <d.csapak@proxmox.com>
22
23 qmeventd listens on a given socket, and waits for qemu processes
24 to connect
25
26 it then waits for shutdown events followed by the closing of the socket,
27 it then calls /usr/sbin/qm cleanup with following arguments
28
29 /usr/sbin/qm cleanup VMID <graceful> <guest>
30
31 parameter explanation:
32
33 graceful:
34 1|0 depending if it saw a shutdown event before the socket closed
35
36 guest:
37 1|0 depending if the shutdown was requested from the guest
38
39 */
40
41 #ifndef _GNU_SOURCE
42 #define _GNU_SOURCE
43 #endif
44
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <json.h>
48 #include <signal.h>
49 #include <stdbool.h>
50 #include <stdio.h>
51 #include <string.h>
52 #include <sys/epoll.h>
53 #include <sys/socket.h>
54 #include <sys/types.h>
55 #include <sys/un.h>
56 #include <sys/wait.h>
57 #include <unistd.h>
58
59 #include "qmeventd.h"
60
61 static int verbose = 0;
62 static int epoll_fd = 0;
63 static const char *progname;
64 /*
65 * Helper functions
66 */
67
68 static void
69 usage()
70 {
71 fprintf(stderr, "Usage: %s [-f] [-v] PATH\n", progname);
72 fprintf(stderr, " -f run in foreground (default: false)\n");
73 fprintf(stderr, " -v verbose (default: false)\n");
74 fprintf(stderr, " PATH use PATH for socket\n");
75 }
76
77 static pid_t
78 get_pid_from_fd(int fd)
79 {
80 struct ucred credentials = { .pid = 0, .uid = 0, .gid = 0 };
81 socklen_t len = sizeof(struct ucred);
82 log_neg(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &len), "getsockopt");
83 return credentials.pid;
84 }
85
86 /*
87 * reads the vmid from /proc/<pid>/cmdline
88 * after the '-id' argument
89 */
90 static unsigned long
91 get_vmid_from_pid(pid_t pid)
92 {
93 char filename[32] = { 0 };
94 int len = snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
95 if (len < 0) {
96 fprintf(stderr, "error during snprintf for %d: %s\n", pid,
97 strerror(errno));
98 return 0;
99 }
100 if ((size_t)len >= sizeof(filename)) {
101 fprintf(stderr, "error: pid %d too long\n", pid);
102 return 0;
103 }
104 FILE *fp = fopen(filename, "re");
105 if (fp == NULL) {
106 fprintf(stderr, "error opening %s: %s\n", filename, strerror(errno));
107 return 0;
108 }
109
110 unsigned long vmid = 0;
111 ssize_t rc = 0;
112 char *buf = NULL;
113 size_t buflen = 0;
114 while ((rc = getdelim(&buf, &buflen, '\0', fp)) >= 0) {
115 if (!strcmp(buf, "-id")) {
116 break;
117 }
118 }
119
120 if (rc < 0) {
121 goto err;
122 }
123
124 if (getdelim(&buf, &buflen, '\0', fp) >= 0) {
125 if (buf[0] == '-' || buf[0] == '\0') {
126 fprintf(stderr, "invalid vmid %s\n", buf);
127 goto ret;
128 }
129
130 errno = 0;
131 char *endptr = NULL;
132 vmid = strtoul(buf, &endptr, 10);
133 if (errno != 0) {
134 vmid = 0;
135 goto err;
136 } else if (*endptr != '\0') {
137 fprintf(stderr, "invalid vmid %s\n", buf);
138 vmid = 0;
139 }
140
141 goto ret;
142 }
143
144 err:
145 fprintf(stderr, "error parsing vmid for %d: %s\n", pid, strerror(errno));
146
147 ret:
148 free(buf);
149 fclose(fp);
150 return vmid;
151 }
152
153 static bool
154 must_write(int fd, const char *buf, size_t len)
155 {
156 ssize_t wlen;
157 do {
158 wlen = write(fd, buf, len);
159 } while (wlen < 0 && errno == EINTR);
160
161 return (wlen == (ssize_t)len);
162 }
163
164 /*
165 * qmp handling functions
166 */
167
168 void
169 handle_qmp_handshake(struct Client *client)
170 {
171 VERBOSE_PRINT("%s: got QMP handshake\n", client->vmid);
172 static const char qmp_answer[] = "{\"execute\":\"qmp_capabilities\"}\n";
173 if (!must_write(client->fd, qmp_answer, sizeof(qmp_answer) - 1)) {
174 fprintf(stderr, "%s: cannot complete handshake\n", client->vmid);
175 cleanup_client(client);
176 }
177 }
178
179 void
180 handle_qmp_event(struct Client *client, struct json_object *obj)
181 {
182 struct json_object *event;
183 if (!json_object_object_get_ex(obj, "event", &event)) {
184 return;
185 }
186 VERBOSE_PRINT("%s: got QMP event: %s\n", client->vmid,
187 json_object_get_string(event));
188 // event, check if shutdown and get guest parameter
189 if (!strcmp(json_object_get_string(event), "SHUTDOWN")) {
190 client->graceful = 1;
191 struct json_object *data;
192 struct json_object *guest;
193 if (json_object_object_get_ex(obj, "data", &data) &&
194 json_object_object_get_ex(data, "guest", &guest))
195 {
196 client->guest = (unsigned short)json_object_get_boolean(guest);
197 }
198 }
199 }
200
201 /*
202 * client management functions
203 */
204
205 void
206 add_new_client(int client_fd)
207 {
208 struct Client *client = calloc(sizeof(struct Client), 1);
209 client->fd = client_fd;
210 client->pid = get_pid_from_fd(client_fd);
211 if (client->pid == 0) {
212 fprintf(stderr, "could not get pid from client\n");
213 goto err;
214 }
215 unsigned long vmid = get_vmid_from_pid(client->pid);
216 int res = snprintf(client->vmid, sizeof(client->vmid), "%lu", vmid);
217 if (vmid == 0 || res < 0 || res >= (int)sizeof(client->vmid)) {
218 fprintf(stderr, "could not get vmid from pid %d\n", client->pid);
219 goto err;
220 }
221
222 struct epoll_event ev;
223 ev.events = EPOLLIN;
224 ev.data.ptr = client;
225 res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
226 if (res < 0) {
227 perror("epoll_ctl client add");
228 goto err;
229 }
230
231 VERBOSE_PRINT("added new client, pid: %d, vmid: %s\n", client->pid,
232 client->vmid);
233
234 return;
235 err:
236 (void)close(client_fd);
237 free(client);
238 }
239
240 void
241 cleanup_client(struct Client *client)
242 {
243 VERBOSE_PRINT("%s: client exited, status: graceful: %d, guest: %d\n",
244 client->vmid, client->graceful, client->guest);
245 log_neg(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client->fd, NULL), "epoll del");
246 (void)close(client->fd);
247
248 unsigned short graceful = client->graceful;
249 unsigned short guest = client->guest;
250 char vmid[sizeof(client->vmid)];
251 strncpy(vmid, client->vmid, sizeof(vmid));
252 free(client);
253 VERBOSE_PRINT("%s: executing cleanup\n", vmid);
254
255 int pid = fork();
256 if (pid < 0) {
257 fprintf(stderr, "fork failed: %s\n", strerror(errno));
258 return;
259 }
260 if (pid == 0) {
261 char *script = "/usr/sbin/qm";
262
263 char *args[] = {
264 script,
265 "cleanup",
266 vmid,
267 graceful ? "1" : "0",
268 guest ? "1" : "0",
269 NULL
270 };
271
272 execvp(script, args);
273 perror("execvp");
274 _exit(1);
275 }
276 }
277
278 void
279 handle_client(struct Client *client)
280 {
281 VERBOSE_PRINT("%s: entering handle\n", client->vmid);
282 ssize_t len;
283 do {
284 len = read(client->fd, (client->buf+client->buflen),
285 sizeof(client->buf) - client->buflen);
286 } while (len < 0 && errno == EINTR);
287
288 if (len < 0) {
289 if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
290 log_neg((int)len, "read");
291 cleanup_client(client);
292 }
293 return;
294 } else if (len == 0) {
295 VERBOSE_PRINT("%s: got EOF\n", client->vmid);
296 cleanup_client(client);
297 return;
298 }
299
300 VERBOSE_PRINT("%s: read %ld bytes\n", client->vmid, len);
301 client->buflen += len;
302
303 struct json_tokener *tok = json_tokener_new();
304 struct json_object *jobj = NULL;
305 enum json_tokener_error jerr = json_tokener_success;
306 while (jerr == json_tokener_success && client->buflen != 0) {
307 jobj = json_tokener_parse_ex(tok, client->buf, (int)client->buflen);
308 jerr = json_tokener_get_error(tok);
309 unsigned int offset = (unsigned int)tok->char_offset;
310 switch (jerr) {
311 case json_tokener_success:
312 // move rest from buffer to front
313 memmove(client->buf, client->buf + offset, client->buflen - offset);
314 client->buflen -= offset;
315 if (json_object_is_type(jobj, json_type_object)) {
316 struct json_object *obj;
317 if (json_object_object_get_ex(jobj, "QMP", &obj)) {
318 handle_qmp_handshake(client);
319 } else if (json_object_object_get_ex(jobj, "event", &obj)) {
320 handle_qmp_event(client, jobj);
321 } // else ignore message
322 }
323 break;
324 case json_tokener_continue:
325 if (client->buflen >= sizeof(client->buf)) {
326 VERBOSE_PRINT("%s, msg too large, discarding buffer\n",
327 client->vmid);
328 memset(client->buf, 0, sizeof(client->buf));
329 client->buflen = 0;
330 } // else we have enough space try again after next read
331 break;
332 default:
333 VERBOSE_PRINT("%s: parse error: %d, discarding buffer\n",
334 client->vmid, jerr);
335 memset(client->buf, 0, client->buflen);
336 client->buflen = 0;
337 break;
338 }
339 json_object_put(jobj);
340 }
341 json_tokener_free(tok);
342 }
343
344
345 int
346 main(int argc, char *argv[])
347 {
348 int opt;
349 int daemonize = 1;
350 char *socket_path = NULL;
351 progname = argv[0];
352
353 while ((opt = getopt(argc, argv, "hfv")) != -1) {
354 switch (opt) {
355 case 'f':
356 daemonize = 0;
357 break;
358 case 'v':
359 verbose = 1;
360 break;
361 case 'h':
362 usage();
363 exit(EXIT_SUCCESS);
364 break;
365 default:
366 usage();
367 exit(EXIT_FAILURE);
368 }
369 }
370
371 if (optind >= argc) {
372 usage();
373 exit(EXIT_FAILURE);
374 }
375
376 signal(SIGCHLD, SIG_IGN);
377
378 socket_path = argv[optind];
379
380 int sock = socket(AF_UNIX, SOCK_STREAM, 0);
381 bail_neg(sock, "socket");
382
383 struct sockaddr_un addr;
384 memset(&addr, 0, sizeof(addr));
385 addr.sun_family = AF_UNIX;
386 strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
387
388 unlink(socket_path);
389 bail_neg(bind(sock, (struct sockaddr*)&addr, sizeof(addr)), "bind");
390
391 struct epoll_event ev, events[1];
392 epoll_fd = epoll_create1(EPOLL_CLOEXEC);
393 bail_neg(epoll_fd, "epoll_create1");
394
395 ev.events = EPOLLIN;
396 ev.data.fd = sock;
397 bail_neg(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev), "epoll_ctl");
398
399 bail_neg(listen(sock, 10), "listen");
400
401 if (daemonize) {
402 bail_neg(daemon(0, 1), "daemon");
403 }
404
405 int nevents;
406
407 for(;;) {
408 nevents = epoll_wait(epoll_fd, events, 1, -1);
409 if (nevents < 0 && errno == EINTR) {
410 // signal happened, try again
411 continue;
412 }
413 bail_neg(nevents, "epoll_wait");
414
415 for (int n = 0; n < nevents; n++) {
416 if (events[n].data.fd == sock) {
417
418 int conn_sock = accept4(sock, NULL, NULL,
419 SOCK_NONBLOCK | SOCK_CLOEXEC);
420 log_neg(conn_sock, "accept");
421 if (conn_sock > -1) {
422 add_new_client(conn_sock);
423 }
424 } else {
425 handle_client((struct Client *)events[n].data.ptr);
426 }
427 }
428 }
429 }