vdagent-x11: add support for logging to a file
[vd_agent.git] / vdagentd.c
blob326e55038ab2d736d02b9cdbfcea0034ec2d55c0
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 <sys/select.h>
30 #include <sys/stat.h>
31 #include <spice/vd_agent.h>
33 #include "udscs.h"
34 #include "vdagentd-proto.h"
35 #include "vdagentd-proto-strings.h"
36 #include "vdagentd-uinput.h"
37 #include "vdagent-virtio-port.h"
38 #include "console-kit.h"
40 struct agent_data {
41 char *session;
42 int width;
43 int height;
46 /* variables */
47 static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
48 static const char *uinput_device = "/dev/uinput";
49 static int debug = 0;
50 static struct udscs_server *server = NULL;
51 static struct vdagent_virtio_port *virtio_port = NULL;
52 static struct console_kit *console_kit = NULL;
53 static struct vdagentd_uinput *uinput = NULL;
54 static VDAgentMonitorsConfig *mon_config = NULL;
55 static uint32_t *capabilities = NULL;
56 static int capabilities_size = 0;
57 static const char *active_session = NULL;
58 static struct udscs_connection *active_session_conn = NULL;
59 static int agent_owns_clipboard = 0;
61 /* utility functions */
62 /* vdagentd <-> spice-client communication handling */
63 static void send_capabilities(struct vdagent_virtio_port *port,
64 uint32_t request)
66 VDAgentAnnounceCapabilities *caps;
67 uint32_t size;
69 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
70 caps = calloc(1, size);
71 if (!caps) {
72 fprintf(stderr,
73 "out of memory allocating capabilities array (write)\n");
74 return;
77 caps->request = request;
78 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
79 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
80 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
81 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
83 vdagent_virtio_port_write(port, VDP_CLIENT_PORT,
84 VD_AGENT_ANNOUNCE_CAPABILITIES, 0,
85 (uint8_t *)caps, size);
86 free(caps);
89 static void do_client_monitors(struct vdagent_virtio_port *port, int port_nr,
90 VDAgentMessage *message_header, VDAgentMonitorsConfig *new_monitors)
92 VDAgentReply reply;
93 uint32_t size;
95 /* Store monitor config to send to agents when they connect */
96 size = sizeof(VDAgentMonitorsConfig) +
97 new_monitors->num_of_monitors * sizeof(VDAgentMonConfig);
98 if (message_header->size != size) {
99 fprintf(stderr, "invalid message size for VDAgentMonitorsConfig\n");
100 return;
103 if (!mon_config ||
104 mon_config->num_of_monitors != new_monitors->num_of_monitors) {
105 free(mon_config);
106 mon_config = malloc(size);
107 if (!mon_config) {
108 fprintf(stderr, "out of memory allocating monitors config\n");
109 return;
112 memcpy(mon_config, new_monitors, size);
114 /* Send monitor config to currently connected agents */
115 udscs_server_write_all(server, VDAGENTD_MONITORS_CONFIG, 0,
116 (uint8_t *)mon_config, size);
118 /* Acknowledge reception of monitors config to spice server / client */
119 reply.type = VD_AGENT_MONITORS_CONFIG;
120 reply.error = VD_AGENT_SUCCESS;
121 vdagent_virtio_port_write(port, port_nr, VD_AGENT_REPLY, 0,
122 (uint8_t *)&reply, sizeof(reply));
125 static void do_client_capabilities(struct vdagent_virtio_port *port,
126 VDAgentMessage *message_header,
127 VDAgentAnnounceCapabilities *caps)
129 capabilities_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message_header->size);
131 free(capabilities);
132 capabilities = malloc(capabilities_size * sizeof(uint32_t));
133 if (!capabilities) {
134 fprintf(stderr,
135 "out of memory allocating capabilities array (read)\n");
136 capabilities_size = 0;
137 return;
139 memcpy(capabilities, caps->caps, capabilities_size * sizeof(uint32_t));
140 if (caps->request)
141 send_capabilities(port, 0);
144 static void do_client_clipboard(struct vdagent_virtio_port *port,
145 VDAgentMessage *message_header, uint8_t *message_data)
147 uint32_t type = 0, opaque = 0, size = 0;
148 uint8_t *data = NULL;
150 if (!active_session_conn) {
151 fprintf(stderr,
152 "Could not find an agent connnection belonging to the "
153 "active session, ignoring client clipboard request\n");
154 return;
157 switch (message_header->type) {
158 case VD_AGENT_CLIPBOARD_GRAB:
159 type = VDAGENTD_CLIPBOARD_GRAB;
160 data = message_data;
161 size = message_header->size;
162 agent_owns_clipboard = 0;
163 break;
164 case VD_AGENT_CLIPBOARD_REQUEST: {
165 VDAgentClipboardRequest *req = (VDAgentClipboardRequest *)message_data;
166 type = VDAGENTD_CLIPBOARD_REQUEST;
167 opaque = req->type;
168 break;
170 case VD_AGENT_CLIPBOARD: {
171 VDAgentClipboard *clipboard = (VDAgentClipboard *)message_data;
172 type = VDAGENTD_CLIPBOARD_DATA;
173 opaque = clipboard->type;
174 size = message_header->size - sizeof(VDAgentClipboard);
175 data = clipboard->data;
176 break;
178 case VD_AGENT_CLIPBOARD_RELEASE:
179 type = VDAGENTD_CLIPBOARD_RELEASE;
180 break;
183 udscs_write(active_session_conn, type, opaque, data, size);
186 int virtio_port_read_complete(
187 struct vdagent_virtio_port *port,
188 VDIChunkHeader *chunk_header,
189 VDAgentMessage *message_header,
190 uint8_t *data)
192 uint32_t min_size = 0;
194 if (message_header->protocol != VD_AGENT_PROTOCOL) {
195 fprintf(stderr, "message with wrong protocol version ignoring\n");
196 return 0;
199 switch (message_header->type) {
200 case VD_AGENT_MOUSE_STATE:
201 if (message_header->size != sizeof(VDAgentMouseState))
202 goto size_error;
203 vdagentd_uinput_do_mouse(&uinput, (VDAgentMouseState *)data);
204 if (!uinput) {
205 fprintf(stderr, "Fatal uinput error\n");
206 exit(1);
208 break;
209 case VD_AGENT_MONITORS_CONFIG:
210 if (message_header->size < sizeof(VDAgentMonitorsConfig))
211 goto size_error;
212 do_client_monitors(port, chunk_header->port, message_header,
213 (VDAgentMonitorsConfig *)data);
214 break;
215 case VD_AGENT_ANNOUNCE_CAPABILITIES:
216 if (message_header->size < sizeof(VDAgentAnnounceCapabilities))
217 goto size_error;
218 do_client_capabilities(port, message_header,
219 (VDAgentAnnounceCapabilities *)data);
220 break;
221 case VD_AGENT_CLIPBOARD_GRAB:
222 case VD_AGENT_CLIPBOARD_REQUEST:
223 case VD_AGENT_CLIPBOARD:
224 case VD_AGENT_CLIPBOARD_RELEASE:
225 switch (message_header->type) {
226 case VD_AGENT_CLIPBOARD_GRAB:
227 min_size = sizeof(VDAgentClipboardGrab); break;
228 case VD_AGENT_CLIPBOARD_REQUEST:
229 min_size = sizeof(VDAgentClipboardRequest); break;
230 case VD_AGENT_CLIPBOARD:
231 min_size = sizeof(VDAgentClipboard); break;
233 if (message_header->size < min_size)
234 goto size_error;
235 do_client_clipboard(port, message_header, data);
236 break;
237 default:
238 if (debug)
239 fprintf(stderr, "unknown message type %d\n", message_header->type);
240 break;
243 return 0;
245 size_error:
246 fprintf(stderr, "read: invalid message size: %u for message type: %u\n",
247 message_header->size, message_header->type);
248 return 0;
251 /* vdagentd <-> vdagent communication handling */
252 void do_agent_clipboard(struct udscs_connection *conn,
253 struct udscs_message_header *header, const uint8_t *data)
255 if (!VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
256 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
257 goto error;
259 /* Check that this agent is from the currently active session */
260 if (conn != active_session_conn) {
261 fprintf(stderr, "Clipboard request from agent which is not in the active session?\n");
262 goto error;
265 switch (header->type) {
266 case VDAGENTD_CLIPBOARD_GRAB:
267 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
268 VD_AGENT_CLIPBOARD_GRAB, 0,
269 data, header->size);
270 agent_owns_clipboard = 1;
271 break;
272 case VDAGENTD_CLIPBOARD_REQUEST: {
273 VDAgentClipboardRequest req = { .type = header->opaque };
274 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
275 VD_AGENT_CLIPBOARD_REQUEST, 0,
276 (uint8_t *)&req, sizeof(req));
277 break;
279 case VDAGENTD_CLIPBOARD_DATA: {
280 VDAgentClipboard *clipboard;
281 uint32_t size = sizeof(*clipboard) + header->size;
283 clipboard = calloc(1, size);
284 if (!clipboard) {
285 fprintf(stderr,
286 "out of memory allocating clipboard (write)\n");
287 return;
289 clipboard->type = header->opaque;
290 memcpy(clipboard->data, data, header->size);
292 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
293 VD_AGENT_CLIPBOARD, 0,
294 (uint8_t *)clipboard, size);
295 free(clipboard);
296 break;
298 case VDAGENTD_CLIPBOARD_RELEASE:
299 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
300 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
301 agent_owns_clipboard = 0;
302 break;
305 return;
307 error:
308 if (header->type == VDAGENTD_CLIPBOARD_REQUEST) {
309 /* Let the agent know no answer is coming */
310 udscs_write(conn, VDAGENTD_CLIPBOARD_DATA,
311 VD_AGENT_CLIPBOARD_NONE, NULL, 0);
315 /* When we open the vdagent virtio channel, the server automatically goes into
316 client mouse mode, so we can only have the channel open when we know the
317 active session resolution. This function checks that we have an agent in the
318 active session, and that it has told us its resolution. If these conditions
319 are met it sets the uinput tablet device's resolution and opens the virtio
320 channel (if it is not already open). If these conditions are not met, it
321 closes both. */
322 static void check_xorg_resolution(void) {
323 struct agent_data *agent_data = udscs_get_user_data(active_session_conn);
325 if (agent_data && agent_data->width) {
326 if (!uinput)
327 uinput = vdagentd_uinput_create(uinput_device,
328 agent_data->width,
329 agent_data->height,
330 stderr, debug > 1);
331 else
332 vdagentd_uinput_update_size(&uinput, agent_data->width,
333 agent_data->height);
334 if (!uinput) {
335 fprintf(stderr, "Fatal uinput error\n");
336 exit(1);
339 if (!virtio_port) {
340 fprintf(stderr, "opening vdagent virtio channel\n");
341 virtio_port = vdagent_virtio_port_create(portdev,
342 virtio_port_read_complete,
343 NULL, stderr);
344 if (!virtio_port) {
345 fprintf(stderr, "Fatal error opening vdagent virtio channel\n");
346 exit(1);
348 send_capabilities(virtio_port, 1);
350 } else {
351 vdagentd_uinput_destroy(&uinput);
352 vdagent_virtio_port_flush(&virtio_port);
353 vdagent_virtio_port_destroy(&virtio_port);
354 fprintf(stderr, "closed vdagent virtio channel\n");
358 static int connection_matches_active_session(struct udscs_connection **connp,
359 void *priv)
361 struct udscs_connection **conn_ret = (struct udscs_connection **)priv;
362 struct agent_data *agent_data = udscs_get_user_data(*connp);
364 /* Check if this connection matches the currently active session */
365 if (!agent_data->session || !active_session)
366 return 0;
367 if (strcmp(agent_data->session, active_session))
368 return 0;
370 *conn_ret = *connp;
371 return 1;
374 void update_active_session_connection(void)
376 struct udscs_connection *new_conn = NULL;
377 int n;
379 n = udscs_server_for_all_clients(server, connection_matches_active_session,
380 (void*)&new_conn);
381 if (n != 1)
382 new_conn = NULL;
384 if (new_conn == active_session_conn)
385 return;
387 active_session_conn = new_conn;
389 if (agent_owns_clipboard) {
390 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
391 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
392 agent_owns_clipboard = 0;
395 check_xorg_resolution();
398 void agent_connect(struct udscs_connection *conn)
400 uint32_t pid;
401 struct agent_data *agent_data;
403 agent_data = calloc(1, sizeof(*agent_data));
404 if (!agent_data) {
405 fprintf(stderr, "Out of memory allocating agent data, disconnecting\n");
406 udscs_destroy_connection(&conn);
407 return;
410 pid = udscs_get_peer_cred(conn).pid;
411 agent_data->session = console_kit_session_for_pid(console_kit, pid);
412 udscs_set_user_data(conn, (void *)agent_data);
413 update_active_session_connection();
415 if (mon_config)
416 udscs_write(conn, VDAGENTD_MONITORS_CONFIG, 0, (uint8_t *)mon_config,
417 sizeof(VDAgentMonitorsConfig) +
418 mon_config->num_of_monitors * sizeof(VDAgentMonConfig));
421 void agent_disconnect(struct udscs_connection *conn)
423 struct agent_data *agent_data = udscs_get_user_data(conn);
425 free(agent_data->session);
426 agent_data->session = NULL;
427 update_active_session_connection();
429 free(agent_data);
432 void agent_read_complete(struct udscs_connection **connp,
433 struct udscs_message_header *header, const uint8_t *data)
435 struct agent_data *agent_data = udscs_get_user_data(*connp);
437 switch (header->type) {
438 case VDAGENTD_GUEST_XORG_RESOLUTION: {
439 struct vdagentd_guest_xorg_resolution *res =
440 (struct vdagentd_guest_xorg_resolution *)data;
442 if (header->size != sizeof(*res)) {
443 fprintf(stderr,
444 "guest xorg resolution message has wrong size, disconnecting agent\n");
445 udscs_destroy_connection(connp);
446 return;
449 agent_data->width = res->width;
450 agent_data->height = res->height;
451 check_xorg_resolution();
452 break;
454 case VDAGENTD_CLIPBOARD_GRAB:
455 case VDAGENTD_CLIPBOARD_REQUEST:
456 case VDAGENTD_CLIPBOARD_DATA:
457 case VDAGENTD_CLIPBOARD_RELEASE:
458 do_agent_clipboard(*connp, header, data);
459 break;
460 default:
461 fprintf(stderr, "unknown message from vdagent: %u, ignoring\n",
462 header->type);
466 /* main */
468 static void usage(FILE *fp)
470 fprintf(fp,
471 "vdagentd\n"
472 "options:\n"
473 " -h print this text\n"
474 " -d print debug messages (and don't daemonize)\n"
475 " -s <port> set virtio serial port [%s]\n"
476 " -u <dev> set uinput device [%s]\n",
477 portdev, uinput_device);
480 void daemonize(void)
482 /* detach from terminal */
483 switch (fork()) {
484 case -1:
485 perror("fork");
486 exit(1);
487 case 0:
488 close(0); close(1); close(2);
489 setsid();
490 open("/dev/null",O_RDWR); dup(0); dup(0);
491 break;
492 default:
493 exit(0);
497 void main_loop(void)
499 fd_set readfds, writefds;
500 int n, nfds, ck_fd = 0;
502 /* FIXME catch sigquit and set a flag to quit */
503 for (;;) {
504 FD_ZERO(&readfds);
505 FD_ZERO(&writefds);
507 nfds = udscs_server_fill_fds(server, &readfds, &writefds);
508 n = vdagent_virtio_port_fill_fds(virtio_port, &readfds, &writefds);
509 if (n >= nfds)
510 nfds = n + 1;
512 ck_fd = console_kit_get_fd(console_kit);
513 FD_SET(ck_fd, &readfds);
514 if (ck_fd >= nfds)
515 nfds = ck_fd + 1;
517 n = select(nfds, &readfds, &writefds, NULL, NULL);
518 if (n == -1) {
519 if (errno == EINTR)
520 continue;
521 perror("select");
522 exit(1);
525 udscs_server_handle_fds(server, &readfds, &writefds);
526 vdagent_virtio_port_handle_fds(&virtio_port, &readfds, &writefds);
527 if (FD_ISSET(ck_fd, &readfds)) {
528 active_session = console_kit_get_active_session(console_kit);
529 update_active_session_connection();
530 check_xorg_resolution();
531 if (!active_session) {
532 fprintf(stderr, "Fatal error: could not get active session\n");
533 exit(1);
539 int main(int argc, char *argv[])
541 int c;
543 for (;;) {
544 if (-1 == (c = getopt(argc, argv, "-dhx:y:s:u:")))
545 break;
546 switch (c) {
547 case 'd':
548 debug++;
549 break;
550 case 's':
551 portdev = optarg;
552 break;
553 case 'u':
554 uinput_device = optarg;
555 break;
556 case 'h':
557 usage(stdout);
558 exit(0);
559 default:
560 usage(stderr);
561 exit(1);
565 /* Setup communication with vdagent process(es) */
566 server = udscs_create_server(VDAGENTD_SOCKET, agent_connect,
567 agent_read_complete, agent_disconnect,
568 vdagentd_messages, VDAGENTD_NO_MESSAGES,
569 debug? stderr:NULL, stderr);
570 if (!server)
571 exit(1);
572 if (chmod(VDAGENTD_SOCKET, 0666)) {
573 fprintf(stderr, "could not change permissions on %s: %s\n",
574 VDAGENTD_SOCKET, strerror(errno));
577 console_kit = console_kit_create(stderr);
578 if (!console_kit)
579 exit(1);
580 active_session = console_kit_get_active_session(console_kit);
581 if (!active_session)
582 exit(1);
584 if (!debug)
585 daemonize();
587 main_loop();
589 udscs_destroy_server(server);
591 return 0;