uploaded spice-vdagent
[platform/adaptation/emulator/spice-vdagent.git] / src / vdagent-file-xfers.c
1 /*  vdagent file xfers code
2
3     Copyright 2013 Red Hat, Inc.
4
5     Red Hat Authors:
6     Hans de Goede <hdegoede@redhat.com>
7
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.
12
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.
17
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/>.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <inttypes.h>
29 #include <string.h>
30 #include <syslog.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <spice/vd_agent.h>
37 #include <glib.h>
38
39 #include "vdagentd-proto.h"
40 #include "vdagent-file-xfers.h"
41 #include "glib-compat.h"
42
43 struct vdagent_file_xfers {
44     GHashTable *xfers;
45     struct udscs_connection *vdagentd;
46     char *save_dir;
47     int open_save_dir;
48     int debug;
49 };
50
51 typedef struct AgentFileXferTask {
52     uint32_t                       id;
53     int                            file_fd;
54     uint64_t                       read_bytes;
55     char                           *file_name;
56     uint64_t                       file_size;
57     int                            file_xfer_nr;
58     int                            file_xfer_total;
59     int                            debug;
60 } AgentFileXferTask;
61
62 static void vdagent_file_xfer_task_free(gpointer data)
63 {
64     AgentFileXferTask *task = data;
65
66     g_return_if_fail(task != NULL);
67
68     if (task->file_fd > 0) {
69         syslog(LOG_ERR, "file-xfer: Removing task %u and file %s due to error",
70                task->id, task->file_name);
71         close(task->file_fd);
72         unlink(task->file_name);
73     } else if (task->debug)
74         syslog(LOG_DEBUG, "file-xfer: Removing task %u %s",
75                task->id, task->file_name);
76
77     g_free(task->file_name);
78     g_free(task);
79 }
80
81 struct vdagent_file_xfers *vdagent_file_xfers_create(
82     struct udscs_connection *vdagentd, const char *save_dir,
83     int open_save_dir, int debug)
84 {
85     struct vdagent_file_xfers *xfers;
86
87     xfers = g_malloc(sizeof(*xfers));
88     xfers->xfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
89                                          NULL, vdagent_file_xfer_task_free);
90     xfers->vdagentd = vdagentd;
91     xfers->save_dir = g_strdup(save_dir);
92     xfers->open_save_dir = open_save_dir;
93     xfers->debug = debug;
94
95     return xfers;
96 }
97
98 void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfers)
99 {
100     g_hash_table_destroy(xfers->xfers);
101     g_free(xfers->save_dir);
102     g_free(xfers);
103 }
104
105 AgentFileXferTask *vdagent_file_xfers_get_task(
106     struct vdagent_file_xfers *xfers, uint32_t id)
107 {
108     AgentFileXferTask *task;
109
110     task = g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(id));
111     if (task == NULL)
112         syslog(LOG_ERR, "file-xfer: error can not find task %u", id);
113
114     return task;
115 }
116
117 /* Parse start message then create a new file xfer task */
118 static AgentFileXferTask *vdagent_parse_start_msg(
119     VDAgentFileXferStartMessage *msg)
120 {
121     GKeyFile *keyfile = NULL;
122     AgentFileXferTask *task = NULL;
123     GError *error = NULL;
124
125     keyfile = g_key_file_new();
126     if (g_key_file_load_from_data(keyfile,
127                                   (const gchar *)msg->data,
128                                   -1,
129                                   G_KEY_FILE_NONE, &error) == FALSE) {
130         syslog(LOG_ERR, "file-xfer: failed to load keyfile: %s",
131                error->message);
132         goto error;
133     }
134     task = g_new0(AgentFileXferTask, 1);
135     task->id = msg->id;
136     task->file_name = g_key_file_get_string(
137         keyfile, "vdagent-file-xfer", "name", &error);
138     if (error) {
139         syslog(LOG_ERR, "file-xfer: failed to parse filename: %s",
140                error->message);
141         goto error;
142     }
143     task->file_size = g_key_file_get_uint64(
144         keyfile, "vdagent-file-xfer", "size", &error);
145     if (error) {
146         syslog(LOG_ERR, "file-xfer: failed to parse filesize: %s",
147                error->message);
148         goto error;
149     }
150     /* These are set for xfers which are part of a multi-file xfer */
151     task->file_xfer_nr = g_key_file_get_integer(
152         keyfile, "vdagent-file-xfer", "file-xfer-nr", NULL);
153     task->file_xfer_total = g_key_file_get_integer(
154         keyfile, "vdagent-file-xfer", "file-xfer-total", NULL);
155
156     g_key_file_free(keyfile);
157     return task;
158
159 error:
160     g_clear_error(&error);
161     if (task)
162         vdagent_file_xfer_task_free(task);
163     if (keyfile)
164         g_key_file_free(keyfile);
165     return NULL;
166 }
167
168 void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
169     VDAgentFileXferStartMessage *msg)
170 {
171     AgentFileXferTask *task;
172     char *dir = NULL, *path = NULL, *file_path = NULL;
173     struct stat st;
174     int i;
175
176     if (g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(msg->id))) {
177         syslog(LOG_ERR, "file-xfer: error id %u already exists, ignoring!",
178                msg->id);
179         return;
180     }
181
182     task = vdagent_parse_start_msg(msg);
183     if (task == NULL) {
184         goto error;
185     }
186
187     task->debug = xfers->debug;
188
189     file_path = g_build_filename(xfers->save_dir, task->file_name, NULL);
190
191     dir = g_path_get_dirname(file_path);
192     if (g_mkdir_with_parents(dir, S_IRWXU) == -1) {
193         syslog(LOG_ERR, "file-xfer: Failed to create dir %s", dir);
194         goto error;
195     }
196
197     path = g_strdup(file_path);
198     for (i = 0; i < 64 && (stat(path, &st) == 0 || errno != ENOENT); i++) {
199         g_free(path);
200         path = g_strdup_printf("%s (%d)", file_path, i + 1);
201     }
202     g_free(task->file_name);
203     task->file_name = path;
204     if (i == 64) {
205         syslog(LOG_ERR, "file-xfer: more then 63 copies of %s exist?",
206                file_path);
207         goto error;
208     }
209
210     task->file_fd = open(path, O_CREAT | O_WRONLY, 0644);
211     if (task->file_fd == -1) {
212         syslog(LOG_ERR, "file-xfer: failed to create file %s: %s",
213                path, strerror(errno));
214         goto error;
215     }
216
217     if (ftruncate(task->file_fd, task->file_size) < 0) {
218         syslog(LOG_ERR, "file-xfer: err reserving %"PRIu64" bytes for %s: %s",
219                task->file_size, path, strerror(errno));
220         goto error;
221     }
222
223     g_hash_table_insert(xfers->xfers, GUINT_TO_POINTER(msg->id), task);
224
225     if (xfers->debug)
226         syslog(LOG_DEBUG, "file-xfer: Adding task %u %s %"PRIu64" bytes",
227                task->id, path, task->file_size);
228
229     udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
230                 msg->id, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA, NULL, 0);
231     g_free(file_path);
232     g_free(dir);
233     return ;
234
235 error:
236     udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
237                 msg->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
238     if (task)
239         vdagent_file_xfer_task_free(task);
240     g_free(file_path);
241     g_free(dir);
242 }
243
244 void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers,
245     VDAgentFileXferStatusMessage *msg)
246 {
247     AgentFileXferTask *task;
248
249     task = vdagent_file_xfers_get_task(xfers, msg->id);
250     if (!task)
251         return;
252
253     switch (msg->result) {
254     case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
255         syslog(LOG_ERR, "file-xfer: task %u %s received unexpected 0 response",
256                task->id, task->file_name);
257         break;
258     default:
259         /* Cancel or Error, remove this task */
260         g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
261     }
262 }
263
264 void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers,
265     VDAgentFileXferDataMessage *msg)
266 {
267     AgentFileXferTask *task;
268     int len, status = -1;
269
270     task = vdagent_file_xfers_get_task(xfers, msg->id);
271     if (!task)
272         return;
273
274     len = write(task->file_fd, msg->data, msg->size);
275     if (len == msg->size) {
276         task->read_bytes += msg->size;
277         if (task->read_bytes >= task->file_size) {
278             if (task->read_bytes == task->file_size) {
279                 if (xfers->debug)
280                     syslog(LOG_DEBUG, "file-xfer: task %u %s has completed",
281                            task->id, task->file_name);
282                 close(task->file_fd);
283                 task->file_fd = -1;
284                 if (xfers->open_save_dir &&
285                         task->file_xfer_nr == task->file_xfer_total) {
286                     char buf[PATH_MAX];
287                     snprintf(buf, PATH_MAX, "xdg-open '%s'&", xfers->save_dir);
288                     status = system(buf);
289                 }
290                 status = VD_AGENT_FILE_XFER_STATUS_SUCCESS;
291             } else {
292                 syslog(LOG_ERR, "file-xfer: error received too much data");
293                 status = VD_AGENT_FILE_XFER_STATUS_ERROR;
294             }
295         }
296     } else {
297         syslog(LOG_ERR, "file-xfer: error writing %s: %s", task->file_name,
298                strerror(errno));
299         status = VD_AGENT_FILE_XFER_STATUS_ERROR;
300     }
301
302     if (status != -1) {
303         udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
304                     msg->id, status, NULL, 0);
305         g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
306     }
307 }