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"
42 struct vdagent_file_xfers
{
44 struct udscs_connection
*vdagentd
;
50 typedef struct AgentFileXferTask
{
61 static void vdagent_file_xfer_task_free(gpointer data
)
63 AgentFileXferTask
*task
= data
;
65 g_return_if_fail(task
!= NULL
);
67 if (task
->file_fd
> 0) {
68 syslog(LOG_ERR
, "file-xfer: Removing task %u and file %s due to error",
69 task
->id
, task
->file_name
);
71 unlink(task
->file_name
);
72 } else if (task
->debug
)
73 syslog(LOG_DEBUG
, "file-xfer: Removing task %u %s",
74 task
->id
, task
->file_name
);
76 g_free(task
->file_name
);
80 struct vdagent_file_xfers
*vdagent_file_xfers_create(
81 struct udscs_connection
*vdagentd
, const char *save_dir
,
82 int open_save_dir
, int debug
)
84 struct vdagent_file_xfers
*xfers
;
86 xfers
= g_malloc(sizeof(*xfers
));
87 xfers
->xfers
= g_hash_table_new_full(g_direct_hash
, g_direct_equal
,
88 NULL
, vdagent_file_xfer_task_free
);
89 xfers
->vdagentd
= vdagentd
;
90 xfers
->save_dir
= g_strdup(save_dir
);
91 xfers
->open_save_dir
= open_save_dir
;
97 void vdagent_file_xfers_destroy(struct vdagent_file_xfers
*xfers
)
99 g_return_if_fail(xfers
!= NULL
);
101 g_hash_table_destroy(xfers
->xfers
);
102 g_free(xfers
->save_dir
);
106 static AgentFileXferTask
*vdagent_file_xfers_get_task(
107 struct vdagent_file_xfers
*xfers
, uint32_t id
)
109 AgentFileXferTask
*task
;
111 g_return_val_if_fail(xfers
!= NULL
, NULL
);
113 task
= g_hash_table_lookup(xfers
->xfers
, GUINT_TO_POINTER(id
));
115 syslog(LOG_ERR
, "file-xfer: error can not find task %u", id
);
120 /* Parse start message then create a new file xfer task */
121 static AgentFileXferTask
*vdagent_parse_start_msg(
122 VDAgentFileXferStartMessage
*msg
)
124 GKeyFile
*keyfile
= NULL
;
125 AgentFileXferTask
*task
= NULL
;
126 GError
*error
= NULL
;
128 keyfile
= g_key_file_new();
129 if (g_key_file_load_from_data(keyfile
,
130 (const gchar
*)msg
->data
,
132 G_KEY_FILE_NONE
, &error
) == FALSE
) {
133 syslog(LOG_ERR
, "file-xfer: failed to load keyfile: %s",
137 task
= g_new0(AgentFileXferTask
, 1);
139 task
->file_name
= g_key_file_get_string(
140 keyfile
, "vdagent-file-xfer", "name", &error
);
142 syslog(LOG_ERR
, "file-xfer: failed to parse filename: %s",
146 task
->file_size
= g_key_file_get_uint64(
147 keyfile
, "vdagent-file-xfer", "size", &error
);
149 syslog(LOG_ERR
, "file-xfer: failed to parse filesize: %s",
153 /* These are set for xfers which are part of a multi-file xfer */
154 task
->file_xfer_nr
= g_key_file_get_integer(
155 keyfile
, "vdagent-file-xfer", "file-xfer-nr", NULL
);
156 task
->file_xfer_total
= g_key_file_get_integer(
157 keyfile
, "vdagent-file-xfer", "file-xfer-total", NULL
);
159 g_key_file_free(keyfile
);
163 g_clear_error(&error
);
165 vdagent_file_xfer_task_free(task
);
167 g_key_file_free(keyfile
);
171 void vdagent_file_xfers_start(struct vdagent_file_xfers
*xfers
,
172 VDAgentFileXferStartMessage
*msg
)
174 AgentFileXferTask
*task
;
175 char *dir
= NULL
, *path
= NULL
, *file_path
= NULL
;
179 g_return_if_fail(xfers
!= NULL
);
181 if (g_hash_table_lookup(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
))) {
182 syslog(LOG_ERR
, "file-xfer: error id %u already exists, ignoring!",
187 task
= vdagent_parse_start_msg(msg
);
192 task
->debug
= xfers
->debug
;
194 file_path
= g_build_filename(xfers
->save_dir
, task
->file_name
, NULL
);
196 dir
= g_path_get_dirname(file_path
);
197 if (g_mkdir_with_parents(dir
, S_IRWXU
) == -1) {
198 syslog(LOG_ERR
, "file-xfer: Failed to create dir %s", dir
);
202 path
= g_strdup(file_path
);
203 for (i
= 0; i
< 64 && (stat(path
, &st
) == 0 || errno
!= ENOENT
); i
++) {
205 path
= g_strdup_printf("%s (%d)", file_path
, i
+ 1);
207 g_free(task
->file_name
);
208 task
->file_name
= path
;
210 syslog(LOG_ERR
, "file-xfer: more then 63 copies of %s exist?",
215 task
->file_fd
= open(path
, O_CREAT
| O_WRONLY
, 0644);
216 if (task
->file_fd
== -1) {
217 syslog(LOG_ERR
, "file-xfer: failed to create file %s: %s",
218 path
, strerror(errno
));
222 if (ftruncate(task
->file_fd
, task
->file_size
) < 0) {
223 syslog(LOG_ERR
, "file-xfer: err reserving %"PRIu64
" bytes for %s: %s",
224 task
->file_size
, path
, strerror(errno
));
228 g_hash_table_insert(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
), task
);
231 syslog(LOG_DEBUG
, "file-xfer: Adding task %u %s %"PRIu64
" bytes",
232 task
->id
, path
, task
->file_size
);
234 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
235 msg
->id
, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA
, NULL
, 0);
241 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
242 msg
->id
, VD_AGENT_FILE_XFER_STATUS_ERROR
, NULL
, 0);
244 vdagent_file_xfer_task_free(task
);
249 void vdagent_file_xfers_status(struct vdagent_file_xfers
*xfers
,
250 VDAgentFileXferStatusMessage
*msg
)
252 AgentFileXferTask
*task
;
254 g_return_if_fail(xfers
!= NULL
);
256 task
= vdagent_file_xfers_get_task(xfers
, msg
->id
);
260 switch (msg
->result
) {
261 case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA
:
262 syslog(LOG_ERR
, "file-xfer: task %u %s received unexpected 0 response",
263 task
->id
, task
->file_name
);
266 /* Cancel or Error, remove this task */
267 g_hash_table_remove(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
));
271 void vdagent_file_xfers_data(struct vdagent_file_xfers
*xfers
,
272 VDAgentFileXferDataMessage
*msg
)
274 AgentFileXferTask
*task
;
275 int len
, status
= -1;
277 g_return_if_fail(xfers
!= NULL
);
279 task
= vdagent_file_xfers_get_task(xfers
, msg
->id
);
283 len
= write(task
->file_fd
, msg
->data
, msg
->size
);
284 if (len
== msg
->size
) {
285 task
->read_bytes
+= msg
->size
;
286 if (task
->read_bytes
>= task
->file_size
) {
287 if (task
->read_bytes
== task
->file_size
) {
289 syslog(LOG_DEBUG
, "file-xfer: task %u %s has completed",
290 task
->id
, task
->file_name
);
291 close(task
->file_fd
);
293 if (xfers
->open_save_dir
&&
294 task
->file_xfer_nr
== task
->file_xfer_total
&&
295 g_hash_table_size(xfers
->xfers
) == 1) {
297 snprintf(buf
, PATH_MAX
, "xdg-open '%s'&", xfers
->save_dir
);
298 status
= system(buf
);
300 status
= VD_AGENT_FILE_XFER_STATUS_SUCCESS
;
302 syslog(LOG_ERR
, "file-xfer: error received too much data");
303 status
= VD_AGENT_FILE_XFER_STATUS_ERROR
;
307 syslog(LOG_ERR
, "file-xfer: error writing %s: %s", task
->file_name
,
309 status
= VD_AGENT_FILE_XFER_STATUS_ERROR
;
313 udscs_write(xfers
->vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
314 msg
->id
, status
, NULL
, 0);
315 g_hash_table_remove(xfers
->xfers
, GUINT_TO_POINTER(msg
->id
));
319 void vdagent_file_xfers_error(struct udscs_connection
*vdagentd
, uint32_t msg_id
)
321 g_return_if_fail(vdagentd
!= NULL
);
323 udscs_write(vdagentd
, VDAGENTD_FILE_XFER_STATUS
,
324 msg_id
, VD_AGENT_FILE_XFER_STATUS_ERROR
, NULL
, 0);