vdagent: catch quit / int signals and make them quit the main loop (iow exit cleanly)
[vd_agent/hramrach.git] / vdagentd.c
blobf100e946b6e69b0c601375e9c0b80cdeaf0feea0
1 /* vdagentd.c vdagentd (daemon) code
3 Copyright 2010 Red Hat, Inc.
5 Red Hat Authors:
6 Hans de Goede <hdegoede@redhat.com>
7 Gerd Hoffmann <kraxel@redhat.com>
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <sys/select.h>
31 #include <sys/stat.h>
32 #include <spice/vd_agent.h>
34 #include "udscs.h"
35 #include "vdagentd-proto.h"
36 #include "vdagentd-proto-strings.h"
37 #include "vdagentd-uinput.h"
38 #include "vdagent-virtio-port.h"
39 #include "console-kit.h"
41 struct agent_data {
42 char *session;
43 int width;
44 int height;
47 /* variables */
48 static const char *logfilename = "/var/log/vdagentd.log";
49 static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
50 static const char *uinput_device = "/dev/uinput";
51 static int debug = 0;
52 static struct udscs_server *server = NULL;
53 static struct vdagent_virtio_port *virtio_port = NULL;
54 static struct console_kit *console_kit = NULL;
55 static struct vdagentd_uinput *uinput = NULL;
56 static VDAgentMonitorsConfig *mon_config = NULL;
57 static uint32_t *capabilities = NULL;
58 static int capabilities_size = 0;
59 static const char *active_session = NULL;
60 static struct udscs_connection *active_session_conn = NULL;
61 static int agent_owns_clipboard = 0;
62 static FILE *logfile = NULL;
63 static int quit = 0;
64 static int retval = 0;
66 /* utility functions */
67 /* vdagentd <-> spice-client communication handling */
68 static void send_capabilities(struct vdagent_virtio_port *port,
69 uint32_t request)
71 VDAgentAnnounceCapabilities *caps;
72 uint32_t size;
74 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
75 caps = calloc(1, size);
76 if (!caps) {
77 fprintf(logfile,
78 "out of memory allocating capabilities array (write)\n");
79 return;
82 caps->request = request;
83 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
84 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
85 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
86 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
88 vdagent_virtio_port_write(port, VDP_CLIENT_PORT,
89 VD_AGENT_ANNOUNCE_CAPABILITIES, 0,
90 (uint8_t *)caps, size);
91 free(caps);
94 static void do_client_monitors(struct vdagent_virtio_port *port, int port_nr,
95 VDAgentMessage *message_header, VDAgentMonitorsConfig *new_monitors)
97 VDAgentReply reply;
98 uint32_t size;
100 /* Store monitor config to send to agents when they connect */
101 size = sizeof(VDAgentMonitorsConfig) +
102 new_monitors->num_of_monitors * sizeof(VDAgentMonConfig);
103 if (message_header->size != size) {
104 fprintf(logfile, "invalid message size for VDAgentMonitorsConfig\n");
105 return;
108 if (!mon_config ||
109 mon_config->num_of_monitors != new_monitors->num_of_monitors) {
110 free(mon_config);
111 mon_config = malloc(size);
112 if (!mon_config) {
113 fprintf(logfile, "out of memory allocating monitors config\n");
114 return;
117 memcpy(mon_config, new_monitors, size);
119 /* Send monitor config to currently connected agents */
120 udscs_server_write_all(server, VDAGENTD_MONITORS_CONFIG, 0,
121 (uint8_t *)mon_config, size);
123 /* Acknowledge reception of monitors config to spice server / client */
124 reply.type = VD_AGENT_MONITORS_CONFIG;
125 reply.error = VD_AGENT_SUCCESS;
126 vdagent_virtio_port_write(port, port_nr, VD_AGENT_REPLY, 0,
127 (uint8_t *)&reply, sizeof(reply));
130 static void do_client_capabilities(struct vdagent_virtio_port *port,
131 VDAgentMessage *message_header,
132 VDAgentAnnounceCapabilities *caps)
134 capabilities_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message_header->size);
136 free(capabilities);
137 capabilities = malloc(capabilities_size * sizeof(uint32_t));
138 if (!capabilities) {
139 fprintf(logfile,
140 "out of memory allocating capabilities array (read)\n");
141 capabilities_size = 0;
142 return;
144 memcpy(capabilities, caps->caps, capabilities_size * sizeof(uint32_t));
145 if (caps->request)
146 send_capabilities(port, 0);
149 static void do_client_clipboard(struct vdagent_virtio_port *port,
150 VDAgentMessage *message_header, uint8_t *message_data)
152 uint32_t type = 0, opaque = 0, size = 0;
153 uint8_t *data = NULL;
155 if (!active_session_conn) {
156 fprintf(logfile,
157 "Could not find an agent connnection belonging to the "
158 "active session, ignoring client clipboard request\n");
159 return;
162 switch (message_header->type) {
163 case VD_AGENT_CLIPBOARD_GRAB:
164 type = VDAGENTD_CLIPBOARD_GRAB;
165 data = message_data;
166 size = message_header->size;
167 agent_owns_clipboard = 0;
168 break;
169 case VD_AGENT_CLIPBOARD_REQUEST: {
170 VDAgentClipboardRequest *req = (VDAgentClipboardRequest *)message_data;
171 type = VDAGENTD_CLIPBOARD_REQUEST;
172 opaque = req->type;
173 break;
175 case VD_AGENT_CLIPBOARD: {
176 VDAgentClipboard *clipboard = (VDAgentClipboard *)message_data;
177 type = VDAGENTD_CLIPBOARD_DATA;
178 opaque = clipboard->type;
179 size = message_header->size - sizeof(VDAgentClipboard);
180 data = clipboard->data;
181 break;
183 case VD_AGENT_CLIPBOARD_RELEASE:
184 type = VDAGENTD_CLIPBOARD_RELEASE;
185 break;
188 udscs_write(active_session_conn, type, opaque, data, size);
191 int virtio_port_read_complete(
192 struct vdagent_virtio_port *port,
193 VDIChunkHeader *chunk_header,
194 VDAgentMessage *message_header,
195 uint8_t *data)
197 uint32_t min_size = 0;
199 if (message_header->protocol != VD_AGENT_PROTOCOL) {
200 fprintf(logfile, "message with wrong protocol version ignoring\n");
201 return 0;
204 switch (message_header->type) {
205 case VD_AGENT_MOUSE_STATE:
206 if (message_header->size != sizeof(VDAgentMouseState))
207 goto size_error;
208 vdagentd_uinput_do_mouse(&uinput, (VDAgentMouseState *)data);
209 if (!uinput) {
210 /* Try to re-open the tablet */
211 struct agent_data *agent_data =
212 udscs_get_user_data(active_session_conn);
213 if (agent_data)
214 uinput = vdagentd_uinput_create(uinput_device,
215 agent_data->width,
216 agent_data->height,
217 logfile, debug > 1);
218 if (!uinput) {
219 fprintf(logfile, "Fatal uinput error\n");
220 retval = 1;
221 quit = 1;
224 break;
225 case VD_AGENT_MONITORS_CONFIG:
226 if (message_header->size < sizeof(VDAgentMonitorsConfig))
227 goto size_error;
228 do_client_monitors(port, chunk_header->port, message_header,
229 (VDAgentMonitorsConfig *)data);
230 break;
231 case VD_AGENT_ANNOUNCE_CAPABILITIES:
232 if (message_header->size < sizeof(VDAgentAnnounceCapabilities))
233 goto size_error;
234 do_client_capabilities(port, message_header,
235 (VDAgentAnnounceCapabilities *)data);
236 break;
237 case VD_AGENT_CLIPBOARD_GRAB:
238 case VD_AGENT_CLIPBOARD_REQUEST:
239 case VD_AGENT_CLIPBOARD:
240 case VD_AGENT_CLIPBOARD_RELEASE:
241 switch (message_header->type) {
242 case VD_AGENT_CLIPBOARD_GRAB:
243 min_size = sizeof(VDAgentClipboardGrab); break;
244 case VD_AGENT_CLIPBOARD_REQUEST:
245 min_size = sizeof(VDAgentClipboardRequest); break;
246 case VD_AGENT_CLIPBOARD:
247 min_size = sizeof(VDAgentClipboard); break;
249 if (message_header->size < min_size)
250 goto size_error;
251 do_client_clipboard(port, message_header, data);
252 break;
253 default:
254 if (debug)
255 fprintf(logfile, "unknown message type %d\n", message_header->type);
256 break;
259 return 0;
261 size_error:
262 fprintf(logfile, "read: invalid message size: %u for message type: %u\n",
263 message_header->size, message_header->type);
264 return 0;
267 /* vdagentd <-> vdagent communication handling */
268 void do_agent_clipboard(struct udscs_connection *conn,
269 struct udscs_message_header *header, const uint8_t *data)
271 if (!VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
272 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
273 goto error;
275 /* Check that this agent is from the currently active session */
276 if (conn != active_session_conn) {
277 fprintf(logfile, "Clipboard request from agent "
278 "which is not in the active session?\n");
279 goto error;
282 if (!virtio_port) {
283 fprintf(logfile,
284 "Clipboard request from agent but no client connection\n");
285 goto error;
288 switch (header->type) {
289 case VDAGENTD_CLIPBOARD_GRAB:
290 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
291 VD_AGENT_CLIPBOARD_GRAB, 0,
292 data, header->size);
293 agent_owns_clipboard = 1;
294 break;
295 case VDAGENTD_CLIPBOARD_REQUEST: {
296 VDAgentClipboardRequest req = { .type = header->opaque };
297 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
298 VD_AGENT_CLIPBOARD_REQUEST, 0,
299 (uint8_t *)&req, sizeof(req));
300 break;
302 case VDAGENTD_CLIPBOARD_DATA: {
303 VDAgentClipboard *clipboard;
304 uint32_t size = sizeof(*clipboard) + header->size;
306 clipboard = calloc(1, size);
307 if (!clipboard) {
308 fprintf(logfile,
309 "out of memory allocating clipboard (write)\n");
310 return;
312 clipboard->type = header->opaque;
313 memcpy(clipboard->data, data, header->size);
315 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
316 VD_AGENT_CLIPBOARD, 0,
317 (uint8_t *)clipboard, size);
318 free(clipboard);
319 break;
321 case VDAGENTD_CLIPBOARD_RELEASE:
322 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
323 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
324 agent_owns_clipboard = 0;
325 break;
328 return;
330 error:
331 if (header->type == VDAGENTD_CLIPBOARD_REQUEST) {
332 /* Let the agent know no answer is coming */
333 udscs_write(conn, VDAGENTD_CLIPBOARD_DATA,
334 VD_AGENT_CLIPBOARD_NONE, NULL, 0);
338 /* When we open the vdagent virtio channel, the server automatically goes into
339 client mouse mode, so we can only have the channel open when we know the
340 active session resolution. This function checks that we have an agent in the
341 active session, and that it has told us its resolution. If these conditions
342 are met it sets the uinput tablet device's resolution and opens the virtio
343 channel (if it is not already open). If these conditions are not met, it
344 closes both. */
345 static void check_xorg_resolution(void) {
346 struct agent_data *agent_data = udscs_get_user_data(active_session_conn);
348 if (agent_data && agent_data->width) {
349 if (!uinput)
350 uinput = vdagentd_uinput_create(uinput_device,
351 agent_data->width,
352 agent_data->height,
353 logfile, debug > 1);
354 else
355 vdagentd_uinput_update_size(&uinput, agent_data->width,
356 agent_data->height);
357 if (!uinput) {
358 fprintf(logfile, "Fatal uinput error\n");
359 retval = 1;
360 quit = 1;
363 if (!virtio_port) {
364 fprintf(logfile, "opening vdagent virtio channel\n");
365 virtio_port = vdagent_virtio_port_create(portdev,
366 virtio_port_read_complete,
367 NULL, logfile);
368 if (!virtio_port) {
369 fprintf(logfile,
370 "Fatal error opening vdagent virtio channel\n");
371 retval = 1;
372 quit = 1;
373 return;
375 send_capabilities(virtio_port, 1);
377 } else {
378 vdagentd_uinput_destroy(&uinput);
379 if (virtio_port) {
380 vdagent_virtio_port_flush(&virtio_port);
381 vdagent_virtio_port_destroy(&virtio_port);
382 fprintf(logfile, "closed vdagent virtio channel\n");
387 static int connection_matches_active_session(struct udscs_connection **connp,
388 void *priv)
390 struct udscs_connection **conn_ret = (struct udscs_connection **)priv;
391 struct agent_data *agent_data = udscs_get_user_data(*connp);
393 /* Check if this connection matches the currently active session */
394 if (!agent_data->session || !active_session)
395 return 0;
396 if (strcmp(agent_data->session, active_session))
397 return 0;
399 *conn_ret = *connp;
400 return 1;
403 void update_active_session_connection(void)
405 struct udscs_connection *new_conn = NULL;
406 int n;
408 n = udscs_server_for_all_clients(server, connection_matches_active_session,
409 (void*)&new_conn);
410 if (n != 1)
411 new_conn = NULL;
413 if (new_conn == active_session_conn)
414 return;
416 active_session_conn = new_conn;
418 if (agent_owns_clipboard && virtio_port)
419 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
420 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
421 agent_owns_clipboard = 0;
423 check_xorg_resolution();
426 void agent_connect(struct udscs_connection *conn)
428 uint32_t pid;
429 struct agent_data *agent_data;
431 agent_data = calloc(1, sizeof(*agent_data));
432 if (!agent_data) {
433 fprintf(logfile, "Out of memory allocating agent data, disconnecting\n");
434 udscs_destroy_connection(&conn);
435 return;
438 pid = udscs_get_peer_cred(conn).pid;
439 agent_data->session = console_kit_session_for_pid(console_kit, pid);
440 udscs_set_user_data(conn, (void *)agent_data);
441 update_active_session_connection();
443 if (mon_config)
444 udscs_write(conn, VDAGENTD_MONITORS_CONFIG, 0, (uint8_t *)mon_config,
445 sizeof(VDAgentMonitorsConfig) +
446 mon_config->num_of_monitors * sizeof(VDAgentMonConfig));
449 void agent_disconnect(struct udscs_connection *conn)
451 struct agent_data *agent_data = udscs_get_user_data(conn);
453 free(agent_data->session);
454 agent_data->session = NULL;
455 update_active_session_connection();
457 free(agent_data);
460 void agent_read_complete(struct udscs_connection **connp,
461 struct udscs_message_header *header, const uint8_t *data)
463 struct agent_data *agent_data = udscs_get_user_data(*connp);
465 switch (header->type) {
466 case VDAGENTD_GUEST_XORG_RESOLUTION: {
467 struct vdagentd_guest_xorg_resolution *res =
468 (struct vdagentd_guest_xorg_resolution *)data;
470 if (header->size != sizeof(*res)) {
471 fprintf(logfile,
472 "guest xorg resolution message has wrong size, disconnecting agent\n");
473 udscs_destroy_connection(connp);
474 return;
477 agent_data->width = res->width;
478 agent_data->height = res->height;
479 check_xorg_resolution();
480 break;
482 case VDAGENTD_CLIPBOARD_GRAB:
483 case VDAGENTD_CLIPBOARD_REQUEST:
484 case VDAGENTD_CLIPBOARD_DATA:
485 case VDAGENTD_CLIPBOARD_RELEASE:
486 do_agent_clipboard(*connp, header, data);
487 break;
488 default:
489 fprintf(logfile, "unknown message from vdagent: %u, ignoring\n",
490 header->type);
494 /* main */
496 static void usage(FILE *fp)
498 fprintf(fp,
499 "vdagentd\n"
500 "options:\n"
501 " -h print this text\n"
502 " -d log debug messages (use twice for extra info)\n"
503 " -s <port> set virtio serial port [%s]\n"
504 " -u <dev> set uinput device [%s]\n"
505 " -x don't daemonize (and log to logfile)\n",
506 portdev, uinput_device);
509 void daemonize(void)
511 /* detach from terminal */
512 switch (fork()) {
513 case 0:
514 close(0); close(1); close(2);
515 setsid();
516 open("/dev/null",O_RDWR); dup(0); dup(0);
517 break;
518 case -1:
519 fprintf(logfile, "fork: %s\n", strerror(errno));
520 retval = 1;
521 default:
522 quit = 1;
526 void main_loop(void)
528 fd_set readfds, writefds;
529 int n, nfds, ck_fd = 0;
531 while (!quit) {
532 FD_ZERO(&readfds);
533 FD_ZERO(&writefds);
535 nfds = udscs_server_fill_fds(server, &readfds, &writefds);
536 n = vdagent_virtio_port_fill_fds(virtio_port, &readfds, &writefds);
537 if (n >= nfds)
538 nfds = n + 1;
540 ck_fd = console_kit_get_fd(console_kit);
541 FD_SET(ck_fd, &readfds);
542 if (ck_fd >= nfds)
543 nfds = ck_fd + 1;
545 n = select(nfds, &readfds, &writefds, NULL, NULL);
546 if (n == -1) {
547 if (errno == EINTR)
548 continue;
549 fprintf(logfile, "Fatal error select: %s\n", strerror(errno));
550 retval = 1;
551 break;
554 udscs_server_handle_fds(server, &readfds, &writefds);
556 if (virtio_port) {
557 vdagent_virtio_port_handle_fds(&virtio_port, &readfds, &writefds);
558 if (!virtio_port) {
559 fprintf(logfile,
560 "AIIEEE lost spice client connection, reconnecting\n");
561 virtio_port = vdagent_virtio_port_create(portdev,
562 virtio_port_read_complete,
563 NULL, logfile);
565 if (!virtio_port) {
566 fprintf(logfile,
567 "Fatal error opening vdagent virtio channel\n");
568 retval = 1;
569 break;
573 if (FD_ISSET(ck_fd, &readfds)) {
574 active_session = console_kit_get_active_session(console_kit);
575 update_active_session_connection();
576 if (!active_session) {
577 fprintf(logfile, "Fatal error: could not get active session\n");
578 retval = 1;
579 break;
582 fflush(logfile);
586 static void quit_handler(int sig)
588 quit = 1;
591 int main(int argc, char *argv[])
593 int c;
594 int do_daemonize = 1;
595 struct sigaction act;
597 for (;;) {
598 if (-1 == (c = getopt(argc, argv, "-dhxs:u:")))
599 break;
600 switch (c) {
601 case 'd':
602 debug++;
603 break;
604 case 's':
605 portdev = optarg;
606 break;
607 case 'u':
608 uinput_device = optarg;
609 break;
610 case 'x':
611 do_daemonize = 0;
612 break;
613 case 'h':
614 usage(stdout);
615 return 0;
616 default:
617 usage(stderr);
618 return 1;
622 memset(&act, 0, sizeof(act));
623 act.sa_flags = SA_RESTART;
624 act.sa_handler = quit_handler;
625 sigaction(SIGINT, &act, NULL);
626 sigaction(SIGHUP, &act, NULL);
627 sigaction(SIGTERM, &act, NULL);
628 sigaction(SIGQUIT, &act, NULL);
630 if (do_daemonize) {
631 logfile = fopen(logfilename, "a");
632 if (!logfile) {
633 fprintf(stderr, "Error opening %s: %s\n", logfilename,
634 strerror(errno));
635 logfile = stderr;
637 } else
638 logfile = stderr;
640 /* Setup communication with vdagent process(es) */
641 server = udscs_create_server(VDAGENTD_SOCKET, agent_connect,
642 agent_read_complete, agent_disconnect,
643 vdagentd_messages, VDAGENTD_NO_MESSAGES,
644 debug? logfile:NULL, logfile);
645 if (!server) {
646 fprintf(logfile, "Fatal could not create server socket %s\n",
647 VDAGENTD_SOCKET);
648 return 1;
650 if (chmod(VDAGENTD_SOCKET, 0666)) {
651 fprintf(logfile, "Fatal could not change permissions on %s: %s\n",
652 VDAGENTD_SOCKET, strerror(errno));
653 udscs_destroy_server(server);
654 return 1;
657 console_kit = console_kit_create(logfile);
658 if (!console_kit) {
659 fprintf(logfile, "Fatal could not connect to console kit\n");
660 udscs_destroy_server(server);
661 return 1;
663 active_session = console_kit_get_active_session(console_kit);
664 if (!active_session) {
665 fprintf(logfile, "Fatal could not get active session\n");
666 console_kit_destroy(console_kit);
667 udscs_destroy_server(server);
668 return 1;
671 if (do_daemonize)
672 daemonize();
674 main_loop();
676 if (agent_owns_clipboard && virtio_port)
677 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
678 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
680 vdagentd_uinput_destroy(&uinput);
681 vdagent_virtio_port_flush(&virtio_port);
682 vdagent_virtio_port_destroy(&virtio_port);
683 console_kit_destroy(console_kit);
684 udscs_destroy_server(server);
685 fprintf(logfile, "vdagentd quiting, returning status %d\n", retval);
686 if (logfile != stderr)
687 fclose(logfile);
689 return retval;