1 /* vdagent file xfers code
3 Copyright 2013 Red Hat, Inc.
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/>.
35 #include <sys/types.h>
36 #include <spice/vd_agent.h>
39 #include "vdagentd-proto.h"
40 #include "vdagent-file-xfers.h"
41 #include "glib-compat.h"
43 struct vdagent_file_xfers
{
45 struct udscs_connection
*vdagentd
;
51 typedef struct AgentFileXferTask
{
62 static void vdagent_file_xfer_task_free(gpointer data
)
64 AgentFileXferTask
*task
= data
;
66 g_return_if_fail(task
!= NULL
);
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
);
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
);
77 g_free(task
->file_name
);
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
)
85 struct vdagent_file_xfers
*xfers
;
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
;
98 void vdagent_file_xfers_destroy(struct vdagent_file_xfers
*xfers
)
100 g_return_if_fail(xfers
!= NULL
);
102 g_hash_table_destroy(xfers
->xfers
);
103 g_free(xfers
->save_dir
);
107 AgentFileXferTask
*vdagent_file_xfers_get_task(
108 struct vdagent_file_xfers
*xfers
, uint32_t id
)
110 AgentFileXferTask
*task
;
112 g_return_val_if_fail(xfers
!= NULL
, NULL
);
114 task
= g_hash_table_lookup(xfers
->xfers
, GUINT_TO_POINTER(id
));
116 syslog(LOG_ERR
, "file-xfer: error can not find task %u", id
);
121 /* Parse start message then create a new file xfer task */
122 static AgentFileXferTask
*vdagent_parse_start_msg(
123 VDAgentFileXferStartMessage
*msg
)
125 GKeyFile
*keyfile
= NULL
;
126 AgentFileXferTask
*task
= NULL
;
127 GError
*error
= NULL
;
129 keyfile
= g_key_file_new();
130 if (g_key_file_load_from_data(keyfile
,
131 (const gchar
*)msg
->data
,
133 G_KEY_FILE_NONE
, &error
) == FALSE
) {
134 syslog(LOG_ERR
, "file-xfer: failed to load keyfile: %s",
138 task
= g_new0(AgentFileXferTask
, 1);
140 task
->file_name
= g_key_file_get_string(
141 keyfile
, "vdagent-file-xfer", "name", &error
);
143 syslog(LOG_ERR
, "file-xfer: failed to parse filename: %s",
147 task
->file_size
= g_key_file_get_uint64(
148 keyfile
, "vdagent-file-xfer", "size", &error
);
150 syslog(LOG_ERR
, "file-xfer: failed to parse filesize: %s",
154 /* These are set for xfers which are part of a multi-file xfer */
155 task
->file_xfer_nr
= g_key_file_get_integer(
156 keyfile
, "vdagent-file-xfer", "file-xfer-nr", NULL
);
157 task
->file_xfer_total
= g_key_file_get_integer(
158 keyfile
, "vdagent-file-xfer", "file-xfer-total", NULL
);
160 g_key_file_free(keyfile
);
164 g_clear_error(&error
);
166 vdagent_file_xfer_task_free(task
);
168 g_key_file_free(keyfile
);
172 void vdagent_file_xfers_start(struct vdagent_file_xfers
*xfers
,
173 VDAgentFileXferStartMessage
*msg
)
175 AgentFileXferTask
*task
;
176 char *dir
= NULL
, *path
= NULL
, *file_path
= NULL
;
180 g_return_if_fail(xfers
!= NULL
);
182 if (g_hash_table_lookup(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
))) {
183 syslog(LOG_ERR
, "file-xfer: error id %u already exists, ignoring!",
188 task
= vdagent_parse_start_msg(msg
);
193 task
->debug
= xfers
->debug
;
195 file_path
= g_build_filename(xfers
->save_dir
, task
->file_name
, NULL
);
197 dir
= g_path_get_dirname(file_path
);
198 if (g_mkdir_with_parents(dir
, S_IRWXU
) == -1) {
199 syslog(LOG_ERR
, "file-xfer: Failed to create dir %s", dir
);
203 path
= g_strdup(file_path
);
204 for (i
= 0; i
< 64 && (stat(path
, &st
) == 0 || errno
!= ENOENT
); i
++) {
206 path
= g_strdup_printf("%s (%d)", file_path
, i
+ 1);
208 g_free(task
->file_name
);
209 task
->file_name
= path
;
211 syslog(LOG_ERR
, "file-xfer: more then 63 copies of %s exist?",
216 task
->file_fd
= open(path
, O_CREAT
| O_WRONLY
, 0644);
217 if (task
->file_fd
== -1) {
218 syslog(LOG_ERR
, "file-xfer: failed to create file %s: %s",
219 path
, strerror(errno
));
223 if (ftruncate(task
->file_fd
, task
->file_size
) < 0) {
224 syslog(LOG_ERR
, "file-xfer: err reserving %"PRIu64
" bytes for %s: %s",
225 task
->file_size
, path
, strerror(errno
));
229 g_hash_table_insert(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
), task
);
232 syslog(LOG_DEBUG
, "file-xfer: Adding task %u %s %"PRIu64
" bytes",
233 task
->id
, path
, task
->file_size
);
235 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
236 msg
->id
, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA
, NULL
, 0);
242 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
243 msg
->id
, VD_AGENT_FILE_XFER_STATUS_ERROR
, NULL
, 0);
245 vdagent_file_xfer_task_free(task
);
250 void vdagent_file_xfers_status(struct vdagent_file_xfers
*xfers
,
251 VDAgentFileXferStatusMessage
*msg
)
253 AgentFileXferTask
*task
;
255 g_return_if_fail(xfers
!= NULL
);
257 task
= vdagent_file_xfers_get_task(xfers
, msg
->id
);
261 switch (msg
->result
) {
262 case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA
:
263 syslog(LOG_ERR
, "file-xfer: task %u %s received unexpected 0 response",
264 task
->id
, task
->file_name
);
267 /* Cancel or Error, remove this task */
268 g_hash_table_remove(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
));
272 void vdagent_file_xfers_data(struct vdagent_file_xfers
*xfers
,
273 VDAgentFileXferDataMessage
*msg
)
275 AgentFileXferTask
*task
;
276 int len
, status
= -1;
278 g_return_if_fail(xfers
!= NULL
);
280 task
= vdagent_file_xfers_get_task(xfers
, msg
->id
);
284 len
= write(task
->file_fd
, msg
->data
, msg
->size
);
285 if (len
== msg
->size
) {
286 task
->read_bytes
+= msg
->size
;
287 if (task
->read_bytes
>= task
->file_size
) {
288 if (task
->read_bytes
== task
->file_size
) {
290 syslog(LOG_DEBUG
, "file-xfer: task %u %s has completed",
291 task
->id
, task
->file_name
);
292 close(task
->file_fd
);
294 if (xfers
->open_save_dir
&&
295 task
->file_xfer_nr
== task
->file_xfer_total
&&
296 g_hash_table_size(xfers
->xfers
) == 1) {
298 snprintf(buf
, PATH_MAX
, "xdg-open '%s'&", xfers
->save_dir
);
299 status
= system(buf
);
301 status
= VD_AGENT_FILE_XFER_STATUS_SUCCESS
;
303 syslog(LOG_ERR
, "file-xfer: error received too much data");
304 status
= VD_AGENT_FILE_XFER_STATUS_ERROR
;
308 syslog(LOG_ERR
, "file-xfer: error writing %s: %s", task
->file_name
,
310 status
= VD_AGENT_FILE_XFER_STATUS_ERROR
;
314 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
315 msg
->id
, status
, NULL
, 0);
316 g_hash_table_remove(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
));
320 void vdagent_file_xfers_error(struct udscs_connection
*vdagentd
, uint32_t msg_id
)
322 g_return_if_fail(vdagentd
!= NULL
);
324 udscs_write(vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
325 msg_id
, VD_AGENT_FILE_XFER_STATUS_ERROR
, NULL
, 0);