reject non option cmdline arguments
[vd_agent/hramrach.git] / vdagentd.c
blobad749f5c9ed59063aab9b1b6adb6d6ae21949c58
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 = "/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 VDAgentMonitorsConfig *mon_config = NULL;
54 static uint32_t *capabilities = NULL;
55 static int capabilities_size = 0;
56 static int uinput_width = 0;
57 static int uinput_height = 0;
58 static const char *active_session = NULL;
59 static struct udscs_connection *active_session_conn = NULL;
60 static int agent_owns_clipboard = 0;
62 /* utility functions */
63 /* vdagentd <-> spice-client communication handling */
64 static void send_capabilities(struct vdagent_virtio_port *port,
65 uint32_t request)
67 VDAgentAnnounceCapabilities *caps;
68 uint32_t size;
70 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
71 caps = calloc(1, size);
72 if (!caps) {
73 fprintf(stderr,
74 "out of memory allocating capabilities array (write)\n");
75 return;
78 caps->request = request;
79 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
80 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
81 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
82 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
84 vdagent_virtio_port_write(port, VDP_CLIENT_PORT,
85 VD_AGENT_ANNOUNCE_CAPABILITIES, 0,
86 (uint8_t *)caps, size);
87 free(caps);
90 static void do_client_monitors(struct vdagent_virtio_port *port, int port_nr,
91 VDAgentMessage *message_header, VDAgentMonitorsConfig *new_monitors)
93 VDAgentReply reply;
94 uint32_t size;
96 /* Store monitor config to send to agents when they connect */
97 size = sizeof(VDAgentMonitorsConfig) +
98 new_monitors->num_of_monitors * sizeof(VDAgentMonConfig);
99 if (message_header->size != size) {
100 fprintf(stderr, "invalid message size for VDAgentMonitorsConfig\n");
101 return;
104 if (!mon_config ||
105 mon_config->num_of_monitors != new_monitors->num_of_monitors) {
106 free(mon_config);
107 mon_config = malloc(size);
108 if (!mon_config) {
109 fprintf(stderr, "out of memory allocating monitors config\n");
110 return;
113 memcpy(mon_config, new_monitors, size);
115 /* Send monitor config to currently connected agents */
116 udscs_server_write_all(server, VDAGENTD_MONITORS_CONFIG, 0,
117 (uint8_t *)mon_config, size);
119 /* Acknowledge reception of monitors config to spice server / client */
120 reply.type = VD_AGENT_MONITORS_CONFIG;
121 reply.error = VD_AGENT_SUCCESS;
122 vdagent_virtio_port_write(port, port_nr, VD_AGENT_REPLY, 0,
123 (uint8_t *)&reply, sizeof(reply));
126 static void do_client_capabilities(struct vdagent_virtio_port *port,
127 VDAgentMessage *message_header,
128 VDAgentAnnounceCapabilities *caps)
130 capabilities_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message_header->size);
132 free(capabilities);
133 capabilities = malloc(capabilities_size * sizeof(uint32_t));
134 if (!capabilities) {
135 fprintf(stderr,
136 "out of memory allocating capabilities array (read)\n");
137 capabilities_size = 0;
138 return;
140 memcpy(capabilities, caps->caps, capabilities_size * sizeof(uint32_t));
141 if (caps->request)
142 send_capabilities(port, 0);
145 static void do_client_clipboard(struct vdagent_virtio_port *port,
146 VDAgentMessage *message_header, uint8_t *message_data)
148 uint32_t type = 0, opaque = 0, size = 0;
149 uint8_t *data = NULL;
151 if (!active_session_conn) {
152 fprintf(stderr,
153 "Could not find an agent connnection belonging to the "
154 "active session, ignoring client clipboard request\n");
155 return;
158 switch (message_header->type) {
159 case VD_AGENT_CLIPBOARD_GRAB:
160 type = VDAGENTD_CLIPBOARD_GRAB;
161 data = message_data;
162 size = message_header->size;
163 agent_owns_clipboard = 0;
164 break;
165 case VD_AGENT_CLIPBOARD_REQUEST: {
166 VDAgentClipboardRequest *req = (VDAgentClipboardRequest *)message_data;
167 type = VDAGENTD_CLIPBOARD_REQUEST;
168 opaque = req->type;
169 break;
171 case VD_AGENT_CLIPBOARD: {
172 VDAgentClipboard *clipboard = (VDAgentClipboard *)message_data;
173 type = VDAGENTD_CLIPBOARD_DATA;
174 opaque = clipboard->type;
175 size = message_header->size - sizeof(VDAgentClipboard);
176 data = clipboard->data;
177 break;
179 case VD_AGENT_CLIPBOARD_RELEASE:
180 type = VDAGENTD_CLIPBOARD_RELEASE;
181 break;
184 udscs_write(active_session_conn, type, opaque, data, size);
187 int virtio_port_read_complete(
188 struct vdagent_virtio_port *port,
189 VDIChunkHeader *chunk_header,
190 VDAgentMessage *message_header,
191 uint8_t *data)
193 uint32_t min_size = 0;
195 if (message_header->protocol != VD_AGENT_PROTOCOL) {
196 fprintf(stderr, "message with wrong protocol version ignoring\n");
197 return 0;
200 switch (message_header->type) {
201 case VD_AGENT_MOUSE_STATE:
202 if (message_header->size != sizeof(VDAgentMouseState))
203 goto size_error;
204 uinput_do_mouse((VDAgentMouseState *)data, debug > 1);
205 break;
206 case VD_AGENT_MONITORS_CONFIG:
207 if (message_header->size < sizeof(VDAgentMonitorsConfig))
208 goto size_error;
209 do_client_monitors(port, chunk_header->port, message_header,
210 (VDAgentMonitorsConfig *)data);
211 break;
212 case VD_AGENT_ANNOUNCE_CAPABILITIES:
213 if (message_header->size < sizeof(VDAgentAnnounceCapabilities))
214 goto size_error;
215 do_client_capabilities(port, message_header,
216 (VDAgentAnnounceCapabilities *)data);
217 break;
218 case VD_AGENT_CLIPBOARD_GRAB:
219 case VD_AGENT_CLIPBOARD_REQUEST:
220 case VD_AGENT_CLIPBOARD:
221 case VD_AGENT_CLIPBOARD_RELEASE:
222 switch (message_header->type) {
223 case VD_AGENT_CLIPBOARD_GRAB:
224 min_size = sizeof(VDAgentClipboardGrab); break;
225 case VD_AGENT_CLIPBOARD_REQUEST:
226 min_size = sizeof(VDAgentClipboardRequest); break;
227 case VD_AGENT_CLIPBOARD:
228 min_size = sizeof(VDAgentClipboard); break;
230 if (message_header->size < min_size)
231 goto size_error;
232 do_client_clipboard(port, message_header, data);
233 break;
234 default:
235 if (debug)
236 fprintf(stderr, "unknown message type %d\n", message_header->type);
237 break;
240 return 0;
242 size_error:
243 fprintf(stderr, "read: invalid message size: %u for message type: %u\n",
244 message_header->size, message_header->type);
245 return 0;
248 /* vdagentd <-> vdagent communication handling */
249 void do_agent_clipboard(struct udscs_connection *conn,
250 struct udscs_message_header *header, const uint8_t *data)
252 if (!VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
253 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
254 goto error;
256 /* Check that this agent is from the currently active session */
257 if (conn != active_session_conn) {
258 fprintf(stderr, "Clipboard request from agent which is not in the active session?\n");
259 goto error;
262 switch (header->type) {
263 case VDAGENTD_CLIPBOARD_GRAB:
264 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
265 VD_AGENT_CLIPBOARD_GRAB, 0,
266 data, header->size);
267 agent_owns_clipboard = 1;
268 break;
269 case VDAGENTD_CLIPBOARD_REQUEST: {
270 VDAgentClipboardRequest req = { .type = header->opaque };
271 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
272 VD_AGENT_CLIPBOARD_REQUEST, 0,
273 (uint8_t *)&req, sizeof(req));
274 break;
276 case VDAGENTD_CLIPBOARD_DATA: {
277 VDAgentClipboard *clipboard;
278 uint32_t size = sizeof(*clipboard) + header->size;
280 clipboard = calloc(1, size);
281 if (!clipboard) {
282 fprintf(stderr,
283 "out of memory allocating clipboard (write)\n");
284 return;
286 clipboard->type = header->opaque;
287 memcpy(clipboard->data, data, header->size);
289 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
290 VD_AGENT_CLIPBOARD, 0,
291 (uint8_t *)clipboard, size);
292 free(clipboard);
293 break;
295 case VDAGENTD_CLIPBOARD_RELEASE:
296 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
297 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
298 agent_owns_clipboard = 0;
299 break;
302 return;
304 error:
305 if (header->type == VDAGENTD_CLIPBOARD_REQUEST) {
306 /* Let the agent know no answer is coming */
307 udscs_write(conn, VDAGENTD_CLIPBOARD_DATA,
308 VD_AGENT_CLIPBOARD_NONE, NULL, 0);
312 /* When we open the vdagent virtio channel, the server automatically goes into
313 client mouse mode, so we can only have the channel open when we know the
314 active session resolution. This function checks that we have an agent in the
315 active session, and that it has told us its resolution. If these conditions
316 are met it sets the uinput tablet device's resolution and opens the virtio
317 channel (if it is not already open). If these conditions are not met, it
318 closes both. */
319 static void check_xorg_resolution(void) {
320 struct agent_data *agent_data = udscs_get_user_data(active_session_conn);
322 if (agent_data && agent_data->width) {
323 /* FIXME objectify uinput and let it handle all this */
324 if (agent_data->width != uinput_width ||
325 agent_data->height != uinput_height) {
326 if (uinput_width)
327 uinput_close();
328 uinput_setup(uinput, agent_data->width, agent_data->height);
329 uinput_width = agent_data->width;
330 uinput_height = agent_data->height;
332 if (!virtio_port) {
333 fprintf(stderr, "opening vdagent virtio channel\n");
334 virtio_port = vdagent_virtio_port_create(portdev,
335 virtio_port_read_complete,
336 NULL);
337 if (!virtio_port)
338 exit(1);
340 send_capabilities(virtio_port, 1);
342 } else {
343 if (uinput_width) {
344 uinput_close();
345 uinput_width = uinput_height = 0;
347 vdagent_virtio_port_flush(&virtio_port);
348 vdagent_virtio_port_destroy(&virtio_port);
349 fprintf(stderr, "closed vdagent virtio channel\n");
353 static int connection_matches_active_session(struct udscs_connection **connp,
354 void *priv)
356 struct udscs_connection **conn_ret = (struct udscs_connection **)priv;
357 struct agent_data *agent_data = udscs_get_user_data(*connp);
359 /* Check if this connection matches the currently active session */
360 if (!agent_data->session || !active_session)
361 return 0;
362 if (strcmp(agent_data->session, active_session))
363 return 0;
365 *conn_ret = *connp;
366 return 1;
369 void update_active_session_connection(void)
371 struct udscs_connection *new_conn = NULL;
372 int n;
374 n = udscs_server_for_all_clients(server, connection_matches_active_session,
375 (void*)&new_conn);
376 if (n != 1)
377 new_conn = NULL;
379 if (new_conn == active_session_conn)
380 return;
382 active_session_conn = new_conn;
384 if (agent_owns_clipboard) {
385 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
386 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
387 agent_owns_clipboard = 0;
390 check_xorg_resolution();
393 void agent_connect(struct udscs_connection *conn)
395 uint32_t pid;
396 struct agent_data *agent_data;
398 agent_data = calloc(1, sizeof(*agent_data));
399 if (!agent_data) {
400 fprintf(stderr, "Out of memory allocating agent data, disconnecting\n");
401 udscs_destroy_connection(&conn);
402 return;
405 pid = udscs_get_peer_cred(conn).pid;
406 agent_data->session = console_kit_session_for_pid(console_kit, pid);
407 udscs_set_user_data(conn, (void *)agent_data);
408 update_active_session_connection();
410 if (mon_config)
411 udscs_write(conn, VDAGENTD_MONITORS_CONFIG, 0, (uint8_t *)mon_config,
412 sizeof(VDAgentMonitorsConfig) +
413 mon_config->num_of_monitors * sizeof(VDAgentMonConfig));
416 void agent_disconnect(struct udscs_connection *conn)
418 struct agent_data *agent_data = udscs_get_user_data(conn);
420 free(agent_data->session);
421 agent_data->session = NULL;
422 update_active_session_connection();
424 free(agent_data);
427 void agent_read_complete(struct udscs_connection **connp,
428 struct udscs_message_header *header, const uint8_t *data)
430 struct agent_data *agent_data = udscs_get_user_data(*connp);
432 switch (header->type) {
433 case VDAGENTD_GUEST_XORG_RESOLUTION: {
434 struct vdagentd_guest_xorg_resolution *res =
435 (struct vdagentd_guest_xorg_resolution *)data;
437 if (header->size != sizeof(*res)) {
438 fprintf(stderr,
439 "guest xorg resolution message has wrong size, disconnecting agent\n");
440 udscs_destroy_connection(connp);
441 return;
444 agent_data->width = res->width;
445 agent_data->height = res->height;
446 check_xorg_resolution();
447 break;
449 case VDAGENTD_CLIPBOARD_GRAB:
450 case VDAGENTD_CLIPBOARD_REQUEST:
451 case VDAGENTD_CLIPBOARD_DATA:
452 case VDAGENTD_CLIPBOARD_RELEASE:
453 do_agent_clipboard(*connp, header, data);
454 break;
455 default:
456 fprintf(stderr, "unknown message from vdagent: %u, ignoring\n",
457 header->type);
461 /* main */
463 static void usage(FILE *fp)
465 fprintf(fp,
466 "vdagentd\n"
467 "options:\n"
468 " -h print this text\n"
469 " -d print debug messages (and don't daemonize)\n"
470 " -s <port> set virtio serial port [%s]\n"
471 " -u <dev> set uinput device [%s]\n",
472 portdev, uinput);
475 void daemonize(void)
477 /* detach from terminal */
478 switch (fork()) {
479 case -1:
480 perror("fork");
481 exit(1);
482 case 0:
483 close(0); close(1); close(2);
484 setsid();
485 open("/dev/null",O_RDWR); dup(0); dup(0);
486 break;
487 default:
488 exit(0);
492 void main_loop(void)
494 fd_set readfds, writefds;
495 int n, nfds, ck_fd = 0;
497 /* FIXME catch sigquit and set a flag to quit */
498 for (;;) {
499 FD_ZERO(&readfds);
500 FD_ZERO(&writefds);
502 nfds = udscs_server_fill_fds(server, &readfds, &writefds);
503 n = vdagent_virtio_port_fill_fds(virtio_port, &readfds, &writefds);
504 if (n >= nfds)
505 nfds = n + 1;
507 ck_fd = console_kit_get_fd(console_kit);
508 FD_SET(ck_fd, &readfds);
509 if (ck_fd >= nfds)
510 nfds = ck_fd + 1;
512 n = select(nfds, &readfds, &writefds, NULL, NULL);
513 if (n == -1) {
514 if (errno == EINTR)
515 continue;
516 perror("select");
517 exit(1);
520 udscs_server_handle_fds(server, &readfds, &writefds);
521 vdagent_virtio_port_handle_fds(&virtio_port, &readfds, &writefds);
522 if (FD_ISSET(ck_fd, &readfds)) {
523 active_session = console_kit_get_active_session(console_kit);
524 update_active_session_connection();
525 check_xorg_resolution();
526 if (!active_session) {
527 fprintf(stderr, "Fatal error: could not get active session\n");
528 exit(1);
534 int main(int argc, char *argv[])
536 int c;
538 for (;;) {
539 if (-1 == (c = getopt(argc, argv, "-dhx:y:s:u:")))
540 break;
541 switch (c) {
542 case 'd':
543 debug++;
544 break;
545 case 's':
546 portdev = optarg;
547 break;
548 case 'u':
549 uinput = optarg;
550 break;
551 case 'h':
552 usage(stdout);
553 exit(0);
554 default:
555 usage(stderr);
556 exit(1);
560 /* Setup communication with vdagent process(es) */
561 server = udscs_create_server(VDAGENTD_SOCKET, agent_connect,
562 agent_read_complete, agent_disconnect,
563 vdagentd_messages, VDAGENTD_NO_MESSAGES,
564 debug? stderr:NULL, stderr);
565 if (!server)
566 exit(1);
567 if (chmod(VDAGENTD_SOCKET, 0666)) {
568 fprintf(stderr, "could not change permissions on %s: %s\n",
569 VDAGENTD_SOCKET, strerror(errno));
572 console_kit = console_kit_create(stderr);
573 if (!console_kit)
574 exit(1);
575 active_session = console_kit_get_active_session(console_kit);
576 if (!active_session)
577 exit(1);
579 if (!debug)
580 daemonize();
582 main_loop();
584 udscs_destroy_server(server);
586 return 0;