vdagent: Fix udscs_read_callback memory ownership bug
[vd_agent/hramrach.git] / src / vdagent / vdagent.c
blob085b84a83d88884617411ba139bc533c3615215c
1 /* vdagent.c xorg-client to vdagentd (daemon).
3 Copyright 2010-2013 Red Hat, Inc.
5 Red Hat Authors:
6 Hans de Goede <hdegoede@redhat.com>
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <sys/select.h>
35 #include <sys/stat.h>
36 #include <spice/vd_agent.h>
37 #include <glib.h>
38 #include <poll.h>
40 #include "udscs.h"
41 #include "vdagentd-proto.h"
42 #include "vdagentd-proto-strings.h"
43 #include "audio.h"
44 #include "x11.h"
45 #include "file-xfers.h"
47 static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
48 static const char *vdagentd_socket = VDAGENTD_SOCKET;
49 static int debug = 0;
50 static const char *fx_dir = NULL;
51 static int fx_open_dir = -1;
52 static struct vdagent_x11 *x11 = NULL;
53 static struct vdagent_file_xfers *vdagent_file_xfers = NULL;
54 static struct udscs_connection *client = NULL;
55 static int quit = 0;
56 static int version_mismatch = 0;
58 static void daemon_read_complete(struct udscs_connection **connp,
59 struct udscs_message_header *header, uint8_t *data)
61 switch (header->type) {
62 case VDAGENTD_MONITORS_CONFIG:
63 vdagent_x11_set_monitor_config(x11, (VDAgentMonitorsConfig *)data, 0);
64 break;
65 case VDAGENTD_CLIPBOARD_REQUEST:
66 vdagent_x11_clipboard_request(x11, header->arg1, header->arg2);
67 break;
68 case VDAGENTD_CLIPBOARD_GRAB:
69 vdagent_x11_clipboard_grab(x11, header->arg1, (uint32_t *)data,
70 header->size / sizeof(uint32_t));
71 break;
72 case VDAGENTD_CLIPBOARD_DATA:
73 vdagent_x11_clipboard_data(x11, header->arg1, header->arg2,
74 data, header->size);
75 /* vdagent_x11_clipboard_data takes ownership of the data (or frees
76 it immediately) */
77 break;
78 case VDAGENTD_CLIPBOARD_RELEASE:
79 vdagent_x11_clipboard_release(x11, header->arg1);
80 break;
81 case VDAGENTD_VERSION:
82 if (strcmp((char *)data, VERSION) != 0) {
83 syslog(LOG_INFO, "vdagentd version mismatch: got %s expected %s",
84 data, VERSION);
85 udscs_destroy_connection(connp);
86 version_mismatch = 1;
88 break;
89 case VDAGENTD_FILE_XFER_START:
90 if (vdagent_file_xfers != NULL) {
91 vdagent_file_xfers_start(vdagent_file_xfers,
92 (VDAgentFileXferStartMessage *)data);
93 } else {
94 vdagent_file_xfers_error(*connp,
95 ((VDAgentFileXferStartMessage *)data)->id);
97 break;
98 case VDAGENTD_FILE_XFER_STATUS:
99 if (vdagent_file_xfers != NULL) {
100 vdagent_file_xfers_status(vdagent_file_xfers,
101 (VDAgentFileXferStatusMessage *)data);
102 } else {
103 vdagent_file_xfers_error(*connp,
104 ((VDAgentFileXferStatusMessage *)data)->id);
106 break;
107 case VDAGENTD_FILE_XFER_DISABLE:
108 if (debug)
109 syslog(LOG_DEBUG, "Disabling file-xfers");
111 if (vdagent_file_xfers != NULL) {
112 vdagent_file_xfers_destroy(vdagent_file_xfers);
113 vdagent_file_xfers = NULL;
115 break;
116 case VDAGENTD_AUDIO_VOLUME_SYNC: {
117 VDAgentAudioVolumeSync *avs = (VDAgentAudioVolumeSync *)data;
118 if (avs->is_playback) {
119 vdagent_audio_playback_sync(avs->mute, avs->nchannels, avs->volume);
120 } else {
121 vdagent_audio_record_sync(avs->mute, avs->nchannels, avs->volume);
123 break;
125 case VDAGENTD_FILE_XFER_DATA:
126 if (vdagent_file_xfers != NULL) {
127 vdagent_file_xfers_data(vdagent_file_xfers,
128 (VDAgentFileXferDataMessage *)data);
129 } else {
130 vdagent_file_xfers_error(*connp,
131 ((VDAgentFileXferDataMessage *)data)->id);
133 break;
134 case VDAGENTD_CLIENT_DISCONNECTED:
135 vdagent_x11_client_disconnected(x11);
136 if (vdagent_file_xfers != NULL) {
137 vdagent_file_xfers_destroy(vdagent_file_xfers);
138 vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir,
139 fx_open_dir, debug);
141 break;
142 default:
143 syslog(LOG_ERR, "Unknown message from vdagentd type: %d, ignoring",
144 header->type);
148 static int client_setup(int reconnect)
150 while (!quit) {
151 client = udscs_connect(vdagentd_socket, daemon_read_complete, NULL,
152 vdagentd_messages, VDAGENTD_NO_MESSAGES,
153 debug);
154 if (client || !reconnect || quit) {
155 break;
157 sleep(1);
159 return client == NULL;
162 static void usage(FILE *fp)
164 fprintf(fp,
165 "Usage: spice-vdagent [OPTIONS]\n\n"
166 "Spice guest agent X11 session agent, version %s.\n\n"
167 "Options:\n"
168 " -h print this text\n"
169 " -d log debug messages\n"
170 " -s <port> set virtio serial port\n"
171 " -S <filename> set udcs socket\n"
172 " -x don't daemonize\n"
173 " -f <dir|xdg-desktop|xdg-download> file xfer save dir\n"
174 " -o <0|1> open dir on file xfer completion\n",
175 VERSION);
178 static void quit_handler(int sig)
180 quit = 1;
183 /* When we daemonize, it is useful to have the main process
184 wait to make sure the X connection worked. We wait up
185 to 10 seconds to get an 'all clear' from the child
186 before we exit. If we don't, we're able to exit with a
187 status that indicates an error occured */
188 static void wait_and_exit(int s)
190 char buf[4];
191 struct pollfd p;
192 p.fd = s;
193 p.events = POLLIN;
195 if (poll(&p, 1, 10000) > 0)
196 if (read(s, buf, sizeof(buf)) > 0)
197 exit(0);
199 exit(1);
202 static int daemonize(void)
204 int x;
205 int fd[2];
207 if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
208 syslog(LOG_ERR, "socketpair : %s", strerror(errno));
209 exit(1);
212 /* detach from terminal */
213 switch (fork()) {
214 case 0:
215 close(0); close(1); close(2);
216 setsid();
217 x = open("/dev/null", O_RDWR); x = dup(x); x = dup(x);
218 close(fd[0]);
219 return fd[1];
220 case -1:
221 syslog(LOG_ERR, "fork: %s", strerror(errno));
222 exit(1);
223 default:
224 close(fd[1]);
225 wait_and_exit(fd[0]);
228 return 0;
231 static int file_test(const char *path)
233 struct stat buffer;
235 return stat(path, &buffer);
238 int main(int argc, char *argv[])
240 fd_set readfds, writefds;
241 int c, n, nfds, x11_fd;
242 int do_daemonize = 1;
243 int parent_socket = 0;
244 int x11_sync = 0;
245 struct sigaction act;
247 for (;;) {
248 if (-1 == (c = getopt(argc, argv, "-dxhys:f:o:S:")))
249 break;
250 switch (c) {
251 case 'd':
252 debug++;
253 break;
254 case 's':
255 portdev = optarg;
256 break;
257 case 'x':
258 do_daemonize = 0;
259 break;
260 case 'y':
261 x11_sync = 1;
262 break;
263 case 'h':
264 usage(stdout);
265 return 0;
266 case 'f':
267 fx_dir = optarg;
268 break;
269 case 'o':
270 fx_open_dir = atoi(optarg);
271 break;
272 case 'S':
273 vdagentd_socket = optarg;
274 break;
275 default:
276 fputs("\n", stderr);
277 usage(stderr);
278 return 1;
282 memset(&act, 0, sizeof(act));
283 act.sa_flags = SA_RESTART;
284 act.sa_handler = quit_handler;
285 sigaction(SIGINT, &act, NULL);
286 sigaction(SIGHUP, &act, NULL);
287 sigaction(SIGTERM, &act, NULL);
288 sigaction(SIGQUIT, &act, NULL);
290 openlog("spice-vdagent", do_daemonize ? LOG_PID : (LOG_PID | LOG_PERROR),
291 LOG_USER);
293 if (file_test(portdev) != 0) {
294 syslog(LOG_ERR, "Cannot access vdagent virtio channel %s", portdev);
295 return 1;
298 if (do_daemonize)
299 parent_socket = daemonize();
301 reconnect:
302 if (version_mismatch) {
303 syslog(LOG_INFO, "Version mismatch, restarting");
304 sleep(1);
305 execvp(argv[0], argv);
308 if (client_setup(do_daemonize)) {
309 return 1;
312 x11 = vdagent_x11_create(client, debug, x11_sync);
313 if (!x11) {
314 udscs_destroy_connection(&client);
315 return 1;
318 if (!fx_dir) {
319 if (vdagent_x11_has_icons_on_desktop(x11))
320 fx_dir = "xdg-desktop";
321 else
322 fx_dir = "xdg-download";
324 if (fx_open_dir == -1)
325 fx_open_dir = !vdagent_x11_has_icons_on_desktop(x11);
326 if (!strcmp(fx_dir, "xdg-desktop"))
327 fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
328 else if (!strcmp(fx_dir, "xdg-download"))
329 fx_dir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);
330 if (fx_dir) {
331 vdagent_file_xfers = vdagent_file_xfers_create(client, fx_dir,
332 fx_open_dir, debug);
333 } else {
334 syslog(LOG_WARNING,
335 "warning could not get file xfer save dir, file transfers will be disabled");
336 vdagent_file_xfers = NULL;
339 if (parent_socket) {
340 if (write(parent_socket, "OK", 2) != 2)
341 syslog(LOG_WARNING, "Parent already gone.");
342 close(parent_socket);
343 parent_socket = 0;
346 while (client && !quit) {
347 FD_ZERO(&readfds);
348 FD_ZERO(&writefds);
350 nfds = udscs_client_fill_fds(client, &readfds, &writefds);
351 x11_fd = vdagent_x11_get_fd(x11);
352 FD_SET(x11_fd, &readfds);
353 if (x11_fd >= nfds)
354 nfds = x11_fd + 1;
356 n = select(nfds, &readfds, &writefds, NULL, NULL);
357 if (n == -1) {
358 if (errno == EINTR)
359 continue;
360 syslog(LOG_ERR, "Fatal error select: %s", strerror(errno));
361 break;
364 if (FD_ISSET(x11_fd, &readfds))
365 vdagent_x11_do_read(x11);
366 udscs_client_handle_fds(&client, &readfds, &writefds);
369 if (vdagent_file_xfers != NULL) {
370 vdagent_file_xfers_destroy(vdagent_file_xfers);
372 vdagent_x11_destroy(x11, client == NULL);
373 udscs_destroy_connection(&client);
374 if (!quit && do_daemonize)
375 goto reconnect;
377 return 0;