1 /* vdagentd.c vdagentd (daemon) code
3 Copyright 2010 Red Hat, Inc.
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/>.
30 #include <sys/select.h>
32 #include <spice/vd_agent.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"
48 static const char *logfilename
= "/var/log/spice-vdagentd/spice-vdagentd.log";
49 static const char *pidfilename
= "/var/run/spice-vdagentd/spice-vdagentd.pid";
50 static const char *portdev
= "/dev/virtio-ports/com.redhat.spice.0";
51 static const char *uinput_device
= "/dev/uinput";
53 static struct udscs_server
*server
= NULL
;
54 static struct vdagent_virtio_port
*virtio_port
= NULL
;
55 static struct console_kit
*console_kit
= NULL
;
56 static struct vdagentd_uinput
*uinput
= NULL
;
57 static VDAgentMonitorsConfig
*mon_config
= NULL
;
58 static uint32_t *capabilities
= NULL
;
59 static int capabilities_size
= 0;
60 static const char *active_session
= NULL
;
61 static struct udscs_connection
*active_session_conn
= NULL
;
62 static int agent_owns_clipboard
= 0;
63 static FILE *logfile
= NULL
;
65 static int retval
= 0;
67 /* utility functions */
68 /* vdagentd <-> spice-client communication handling */
69 static void send_capabilities(struct vdagent_virtio_port
*port
,
72 VDAgentAnnounceCapabilities
*caps
;
75 size
= sizeof(*caps
) + VD_AGENT_CAPS_BYTES
;
76 caps
= calloc(1, size
);
79 "out of memory allocating capabilities array (write)\n");
83 caps
->request
= request
;
84 VD_AGENT_SET_CAPABILITY(caps
->caps
, VD_AGENT_CAP_MOUSE_STATE
);
85 VD_AGENT_SET_CAPABILITY(caps
->caps
, VD_AGENT_CAP_MONITORS_CONFIG
);
86 VD_AGENT_SET_CAPABILITY(caps
->caps
, VD_AGENT_CAP_REPLY
);
87 VD_AGENT_SET_CAPABILITY(caps
->caps
, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND
);
89 vdagent_virtio_port_write(port
, VDP_CLIENT_PORT
,
90 VD_AGENT_ANNOUNCE_CAPABILITIES
, 0,
91 (uint8_t *)caps
, size
);
95 static void do_client_monitors(struct vdagent_virtio_port
*port
, int port_nr
,
96 VDAgentMessage
*message_header
, VDAgentMonitorsConfig
*new_monitors
)
101 /* Store monitor config to send to agents when they connect */
102 size
= sizeof(VDAgentMonitorsConfig
) +
103 new_monitors
->num_of_monitors
* sizeof(VDAgentMonConfig
);
104 if (message_header
->size
!= size
) {
105 fprintf(logfile
, "invalid message size for VDAgentMonitorsConfig\n");
110 mon_config
->num_of_monitors
!= new_monitors
->num_of_monitors
) {
112 mon_config
= malloc(size
);
114 fprintf(logfile
, "out of memory allocating monitors config\n");
118 memcpy(mon_config
, new_monitors
, size
);
120 /* Send monitor config to currently connected agents */
121 udscs_server_write_all(server
, VDAGENTD_MONITORS_CONFIG
, 0,
122 (uint8_t *)mon_config
, size
);
124 /* Acknowledge reception of monitors config to spice server / client */
125 reply
.type
= VD_AGENT_MONITORS_CONFIG
;
126 reply
.error
= VD_AGENT_SUCCESS
;
127 vdagent_virtio_port_write(port
, port_nr
, VD_AGENT_REPLY
, 0,
128 (uint8_t *)&reply
, sizeof(reply
));
131 static void do_client_capabilities(struct vdagent_virtio_port
*port
,
132 VDAgentMessage
*message_header
,
133 VDAgentAnnounceCapabilities
*caps
)
135 int new_size
= VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message_header
->size
);
137 if (capabilities_size
!= new_size
) {
138 capabilities_size
= new_size
;
140 capabilities
= malloc(capabilities_size
* sizeof(uint32_t));
143 "out of memory allocating capabilities array (read)\n");
144 capabilities_size
= 0;
148 memcpy(capabilities
, caps
->caps
, capabilities_size
* sizeof(uint32_t));
150 send_capabilities(port
, 0);
153 static void do_client_clipboard(struct vdagent_virtio_port
*port
,
154 VDAgentMessage
*message_header
, uint8_t *message_data
)
156 uint32_t type
= 0, opaque
= 0, size
= 0;
157 uint8_t *data
= NULL
;
159 if (!active_session_conn
) {
161 "Could not find an agent connnection belonging to the "
162 "active session, ignoring client clipboard request\n");
166 switch (message_header
->type
) {
167 case VD_AGENT_CLIPBOARD_GRAB
:
168 type
= VDAGENTD_CLIPBOARD_GRAB
;
170 size
= message_header
->size
;
171 agent_owns_clipboard
= 0;
173 case VD_AGENT_CLIPBOARD_REQUEST
: {
174 VDAgentClipboardRequest
*req
= (VDAgentClipboardRequest
*)message_data
;
175 type
= VDAGENTD_CLIPBOARD_REQUEST
;
179 case VD_AGENT_CLIPBOARD
: {
180 VDAgentClipboard
*clipboard
= (VDAgentClipboard
*)message_data
;
181 type
= VDAGENTD_CLIPBOARD_DATA
;
182 opaque
= clipboard
->type
;
183 size
= message_header
->size
- sizeof(VDAgentClipboard
);
184 data
= clipboard
->data
;
187 case VD_AGENT_CLIPBOARD_RELEASE
:
188 type
= VDAGENTD_CLIPBOARD_RELEASE
;
192 udscs_write(active_session_conn
, type
, opaque
, data
, size
);
195 int virtio_port_read_complete(
196 struct vdagent_virtio_port
*port
,
197 VDIChunkHeader
*chunk_header
,
198 VDAgentMessage
*message_header
,
201 uint32_t min_size
= 0;
203 if (message_header
->protocol
!= VD_AGENT_PROTOCOL
) {
204 fprintf(logfile
, "message with wrong protocol version ignoring\n");
208 switch (message_header
->type
) {
209 case VD_AGENT_MOUSE_STATE
:
210 if (message_header
->size
!= sizeof(VDAgentMouseState
))
212 vdagentd_uinput_do_mouse(&uinput
, (VDAgentMouseState
*)data
);
214 /* Try to re-open the tablet */
215 struct agent_data
*agent_data
=
216 udscs_get_user_data(active_session_conn
);
218 uinput
= vdagentd_uinput_create(uinput_device
,
223 fprintf(logfile
, "Fatal uinput error\n");
229 case VD_AGENT_MONITORS_CONFIG
:
230 if (message_header
->size
< sizeof(VDAgentMonitorsConfig
))
232 do_client_monitors(port
, chunk_header
->port
, message_header
,
233 (VDAgentMonitorsConfig
*)data
);
235 case VD_AGENT_ANNOUNCE_CAPABILITIES
:
236 if (message_header
->size
< sizeof(VDAgentAnnounceCapabilities
))
238 do_client_capabilities(port
, message_header
,
239 (VDAgentAnnounceCapabilities
*)data
);
241 case VD_AGENT_CLIPBOARD_GRAB
:
242 case VD_AGENT_CLIPBOARD_REQUEST
:
243 case VD_AGENT_CLIPBOARD
:
244 case VD_AGENT_CLIPBOARD_RELEASE
:
245 switch (message_header
->type
) {
246 case VD_AGENT_CLIPBOARD_GRAB
:
247 min_size
= sizeof(VDAgentClipboardGrab
); break;
248 case VD_AGENT_CLIPBOARD_REQUEST
:
249 min_size
= sizeof(VDAgentClipboardRequest
); break;
250 case VD_AGENT_CLIPBOARD
:
251 min_size
= sizeof(VDAgentClipboard
); break;
253 if (message_header
->size
< min_size
)
255 do_client_clipboard(port
, message_header
, data
);
259 fprintf(logfile
, "unknown message type %d\n", message_header
->type
);
266 fprintf(logfile
, "read: invalid message size: %u for message type: %u\n",
267 message_header
->size
, message_header
->type
);
271 /* vdagentd <-> vdagent communication handling */
272 void do_agent_clipboard(struct udscs_connection
*conn
,
273 struct udscs_message_header
*header
, const uint8_t *data
)
275 if (!VD_AGENT_HAS_CAPABILITY(capabilities
, capabilities_size
,
276 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND
))
279 /* Check that this agent is from the currently active session */
280 if (conn
!= active_session_conn
) {
281 fprintf(logfile
, "Clipboard request from agent "
282 "which is not in the active session?\n");
288 "Clipboard request from agent but no client connection\n");
292 switch (header
->type
) {
293 case VDAGENTD_CLIPBOARD_GRAB
:
294 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
295 VD_AGENT_CLIPBOARD_GRAB
, 0,
297 agent_owns_clipboard
= 1;
299 case VDAGENTD_CLIPBOARD_REQUEST
: {
300 VDAgentClipboardRequest req
= { .type
= header
->opaque
};
301 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
302 VD_AGENT_CLIPBOARD_REQUEST
, 0,
303 (uint8_t *)&req
, sizeof(req
));
306 case VDAGENTD_CLIPBOARD_DATA
: {
307 VDAgentClipboard
*clipboard
;
308 uint32_t size
= sizeof(*clipboard
) + header
->size
;
310 clipboard
= calloc(1, size
);
313 "out of memory allocating clipboard (write)\n");
316 clipboard
->type
= header
->opaque
;
317 memcpy(clipboard
->data
, data
, header
->size
);
319 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
320 VD_AGENT_CLIPBOARD
, 0,
321 (uint8_t *)clipboard
, size
);
325 case VDAGENTD_CLIPBOARD_RELEASE
:
326 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
327 VD_AGENT_CLIPBOARD_RELEASE
, 0, NULL
, 0);
328 agent_owns_clipboard
= 0;
335 if (header
->type
== VDAGENTD_CLIPBOARD_REQUEST
) {
336 /* Let the agent know no answer is coming */
337 udscs_write(conn
, VDAGENTD_CLIPBOARD_DATA
,
338 VD_AGENT_CLIPBOARD_NONE
, NULL
, 0);
342 /* When we open the vdagent virtio channel, the server automatically goes into
343 client mouse mode, so we can only have the channel open when we know the
344 active session resolution. This function checks that we have an agent in the
345 active session, and that it has told us its resolution. If these conditions
346 are met it sets the uinput tablet device's resolution and opens the virtio
347 channel (if it is not already open). If these conditions are not met, it
349 static void check_xorg_resolution(void) {
350 struct agent_data
*agent_data
= udscs_get_user_data(active_session_conn
);
352 if (agent_data
&& agent_data
->width
) {
354 uinput
= vdagentd_uinput_create(uinput_device
,
359 vdagentd_uinput_update_size(&uinput
, agent_data
->width
,
362 fprintf(logfile
, "Fatal uinput error\n");
369 fprintf(logfile
, "opening vdagent virtio channel\n");
370 virtio_port
= vdagent_virtio_port_create(portdev
,
371 virtio_port_read_complete
,
375 "Fatal error opening vdagent virtio channel\n");
380 send_capabilities(virtio_port
, 1);
383 vdagentd_uinput_destroy(&uinput
);
385 vdagent_virtio_port_flush(&virtio_port
);
386 vdagent_virtio_port_destroy(&virtio_port
);
387 fprintf(logfile
, "closed vdagent virtio channel\n");
392 static int connection_matches_active_session(struct udscs_connection
**connp
,
395 struct udscs_connection
**conn_ret
= (struct udscs_connection
**)priv
;
396 struct agent_data
*agent_data
= udscs_get_user_data(*connp
);
398 /* Check if this connection matches the currently active session */
399 if (!agent_data
->session
|| !active_session
)
401 if (strcmp(agent_data
->session
, active_session
))
408 void update_active_session_connection(void)
410 struct udscs_connection
*new_conn
= NULL
;
414 active_session
= console_kit_get_active_session(console_kit
);
416 n
= udscs_server_for_all_clients(server
, connection_matches_active_session
,
421 if (new_conn
== active_session_conn
)
424 active_session_conn
= new_conn
;
426 if (agent_owns_clipboard
&& virtio_port
)
427 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
428 VD_AGENT_CLIPBOARD_RELEASE
, 0, NULL
, 0);
429 agent_owns_clipboard
= 0;
431 check_xorg_resolution();
434 void agent_connect(struct udscs_connection
*conn
)
437 struct agent_data
*agent_data
;
439 agent_data
= calloc(1, sizeof(*agent_data
));
441 fprintf(logfile
, "Out of memory allocating agent data, disconnecting\n");
442 udscs_destroy_connection(&conn
);
446 pid
= udscs_get_peer_cred(conn
).pid
;
447 agent_data
->session
= console_kit_session_for_pid(console_kit
, pid
);
448 udscs_set_user_data(conn
, (void *)agent_data
);
449 update_active_session_connection();
452 udscs_write(conn
, VDAGENTD_MONITORS_CONFIG
, 0, (uint8_t *)mon_config
,
453 sizeof(VDAgentMonitorsConfig
) +
454 mon_config
->num_of_monitors
* sizeof(VDAgentMonConfig
));
457 void agent_disconnect(struct udscs_connection
*conn
)
459 struct agent_data
*agent_data
= udscs_get_user_data(conn
);
461 free(agent_data
->session
);
462 agent_data
->session
= NULL
;
463 update_active_session_connection();
468 void agent_read_complete(struct udscs_connection
**connp
,
469 struct udscs_message_header
*header
, const uint8_t *data
)
471 struct agent_data
*agent_data
= udscs_get_user_data(*connp
);
473 switch (header
->type
) {
474 case VDAGENTD_GUEST_XORG_RESOLUTION
: {
475 struct vdagentd_guest_xorg_resolution
*res
=
476 (struct vdagentd_guest_xorg_resolution
*)data
;
478 if (header
->size
!= sizeof(*res
)) {
480 "guest xorg resolution message has wrong size, disconnecting agent\n");
481 udscs_destroy_connection(connp
);
485 agent_data
->width
= res
->width
;
486 agent_data
->height
= res
->height
;
487 check_xorg_resolution();
490 case VDAGENTD_CLIPBOARD_GRAB
:
491 case VDAGENTD_CLIPBOARD_REQUEST
:
492 case VDAGENTD_CLIPBOARD_DATA
:
493 case VDAGENTD_CLIPBOARD_RELEASE
:
494 do_agent_clipboard(*connp
, header
, data
);
497 fprintf(logfile
, "unknown message from vdagent: %u, ignoring\n",
504 static void usage(FILE *fp
)
509 " -h print this text\n"
510 " -d log debug messages (use twice for extra info)\n"
511 " -s <port> set virtio serial port [%s]\n"
512 " -u <dev> set uinput device [%s]\n"
513 " -x don't daemonize (and log to logfile)\n",
514 portdev
, uinput_device
);
522 /* detach from terminal */
525 close(0); close(1); close(2);
527 x
= open("/dev/null", O_RDWR
); dup(x
); dup(x
);
528 pidfile
= fopen(pidfilename
, "w");
530 fprintf(pidfile
, "%d\n", (int)getpid());
535 fprintf(logfile
, "fork: %s\n", strerror(errno
));
538 udscs_destroy_server(server
);
539 if (logfile
!= stderr
)
547 fd_set readfds
, writefds
;
548 int n
, nfds
, ck_fd
= 0;
554 nfds
= udscs_server_fill_fds(server
, &readfds
, &writefds
);
555 n
= vdagent_virtio_port_fill_fds(virtio_port
, &readfds
, &writefds
);
559 ck_fd
= console_kit_get_fd(console_kit
);
560 FD_SET(ck_fd
, &readfds
);
564 n
= select(nfds
, &readfds
, &writefds
, NULL
, NULL
);
568 fprintf(logfile
, "Fatal error select: %s\n", strerror(errno
));
573 udscs_server_handle_fds(server
, &readfds
, &writefds
);
576 vdagent_virtio_port_handle_fds(&virtio_port
, &readfds
, &writefds
);
579 "AIIEEE lost spice client connection, reconnecting\n");
580 virtio_port
= vdagent_virtio_port_create(portdev
,
581 virtio_port_read_complete
,
586 "Fatal error opening vdagent virtio channel\n");
592 if (FD_ISSET(ck_fd
, &readfds
)) {
593 active_session
= console_kit_get_active_session(console_kit
);
594 update_active_session_connection();
600 static void quit_handler(int sig
)
605 int main(int argc
, char *argv
[])
608 int do_daemonize
= 1;
609 struct sigaction act
;
612 if (-1 == (c
= getopt(argc
, argv
, "-dhxs:u:")))
622 uinput_device
= optarg
;
636 memset(&act
, 0, sizeof(act
));
637 act
.sa_flags
= SA_RESTART
;
638 act
.sa_handler
= quit_handler
;
639 sigaction(SIGINT
, &act
, NULL
);
640 sigaction(SIGHUP
, &act
, NULL
);
641 sigaction(SIGTERM
, &act
, NULL
);
642 sigaction(SIGQUIT
, &act
, NULL
);
645 logfile
= fopen(logfilename
, "a");
647 fprintf(stderr
, "Error opening %s: %s\n", logfilename
,
654 /* Setup communication with vdagent process(es) */
655 server
= udscs_create_server(VDAGENTD_SOCKET
, agent_connect
,
656 agent_read_complete
, agent_disconnect
,
657 vdagentd_messages
, VDAGENTD_NO_MESSAGES
,
658 debug
? logfile
:NULL
, logfile
);
660 fprintf(logfile
, "Fatal could not create server socket %s\n",
662 if (logfile
!= stderr
)
666 if (chmod(VDAGENTD_SOCKET
, 0666)) {
667 fprintf(logfile
, "Fatal could not change permissions on %s: %s\n",
668 VDAGENTD_SOCKET
, strerror(errno
));
669 udscs_destroy_server(server
);
670 if (logfile
!= stderr
)
678 console_kit
= console_kit_create(logfile
);
680 fprintf(logfile
, "Fatal could not connect to console kit\n");
681 udscs_destroy_server(server
);
682 if (logfile
!= stderr
)
689 if (agent_owns_clipboard
&& virtio_port
)
690 vdagent_virtio_port_write(virtio_port
, VDP_CLIENT_PORT
,
691 VD_AGENT_CLIPBOARD_RELEASE
, 0, NULL
, 0);
693 vdagentd_uinput_destroy(&uinput
);
694 vdagent_virtio_port_flush(&virtio_port
);
695 vdagent_virtio_port_destroy(&virtio_port
);
696 console_kit_destroy(console_kit
);
697 udscs_destroy_server(server
);
698 if (unlink(VDAGENTD_SOCKET
) != 0)
699 fprintf(logfile
, "unlink %s: %s\n", VDAGENTD_SOCKET
, strerror(errno
));
700 fprintf(logfile
, "vdagentd quiting, returning status %d\n", retval
);
701 if (logfile
!= stderr
)