summaryrefslogtreecommitdiff
path: root/src/vdagent/file-xfers.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vdagent/file-xfers.c')
-rw-r--r--src/vdagent/file-xfers.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/vdagent/file-xfers.c b/src/vdagent/file-xfers.c
new file mode 100644
index 0000000..bfb2ce5
--- /dev/null
+++ b/src/vdagent/file-xfers.c
@@ -0,0 +1,325 @@
+/* vdagent file xfers code
+
+ Copyright 2013 - 2016 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <spice/vd_agent.h>
+#include <glib.h>
+
+#include "vdagentd-proto.h"
+#include "file-xfers.h"
+
+struct vdagent_file_xfers {
+ GHashTable *xfers;
+ struct udscs_connection *vdagentd;
+ char *save_dir;
+ int open_save_dir;
+ int debug;
+};
+
+typedef struct AgentFileXferTask {
+ uint32_t id;
+ int file_fd;
+ uint64_t read_bytes;
+ char *file_name;
+ uint64_t file_size;
+ int file_xfer_nr;
+ int file_xfer_total;
+ int debug;
+} AgentFileXferTask;
+
+static void vdagent_file_xfer_task_free(gpointer data)
+{
+ AgentFileXferTask *task = data;
+
+ g_return_if_fail(task != NULL);
+
+ if (task->file_fd > 0) {
+ syslog(LOG_ERR, "file-xfer: Removing task %u and file %s due to error",
+ task->id, task->file_name);
+ close(task->file_fd);
+ unlink(task->file_name);
+ } else if (task->debug)
+ syslog(LOG_DEBUG, "file-xfer: Removing task %u %s",
+ task->id, task->file_name);
+
+ g_free(task->file_name);
+ g_free(task);
+}
+
+struct vdagent_file_xfers *vdagent_file_xfers_create(
+ struct udscs_connection *vdagentd, const char *save_dir,
+ int open_save_dir, int debug)
+{
+ struct vdagent_file_xfers *xfers;
+
+ xfers = g_malloc(sizeof(*xfers));
+ xfers->xfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, vdagent_file_xfer_task_free);
+ xfers->vdagentd = vdagentd;
+ xfers->save_dir = g_strdup(save_dir);
+ xfers->open_save_dir = open_save_dir;
+ xfers->debug = debug;
+
+ return xfers;
+}
+
+void vdagent_file_xfers_destroy(struct vdagent_file_xfers *xfers)
+{
+ g_return_if_fail(xfers != NULL);
+
+ g_hash_table_destroy(xfers->xfers);
+ g_free(xfers->save_dir);
+ g_free(xfers);
+}
+
+static AgentFileXferTask *vdagent_file_xfers_get_task(
+ struct vdagent_file_xfers *xfers, uint32_t id)
+{
+ AgentFileXferTask *task;
+
+ g_return_val_if_fail(xfers != NULL, NULL);
+
+ task = g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(id));
+ if (task == NULL)
+ syslog(LOG_ERR, "file-xfer: error can not find task %u", id);
+
+ return task;
+}
+
+/* Parse start message then create a new file xfer task */
+static AgentFileXferTask *vdagent_parse_start_msg(
+ VDAgentFileXferStartMessage *msg)
+{
+ GKeyFile *keyfile = NULL;
+ AgentFileXferTask *task = NULL;
+ GError *error = NULL;
+
+ keyfile = g_key_file_new();
+ if (g_key_file_load_from_data(keyfile,
+ (const gchar *)msg->data,
+ -1,
+ G_KEY_FILE_NONE, &error) == FALSE) {
+ syslog(LOG_ERR, "file-xfer: failed to load keyfile: %s",
+ error->message);
+ goto error;
+ }
+ task = g_new0(AgentFileXferTask, 1);
+ task->id = msg->id;
+ task->file_name = g_key_file_get_string(
+ keyfile, "vdagent-file-xfer", "name", &error);
+ if (error) {
+ syslog(LOG_ERR, "file-xfer: failed to parse filename: %s",
+ error->message);
+ goto error;
+ }
+ task->file_size = g_key_file_get_uint64(
+ keyfile, "vdagent-file-xfer", "size", &error);
+ if (error) {
+ syslog(LOG_ERR, "file-xfer: failed to parse filesize: %s",
+ error->message);
+ goto error;
+ }
+ /* These are set for xfers which are part of a multi-file xfer */
+ task->file_xfer_nr = g_key_file_get_integer(
+ keyfile, "vdagent-file-xfer", "file-xfer-nr", NULL);
+ task->file_xfer_total = g_key_file_get_integer(
+ keyfile, "vdagent-file-xfer", "file-xfer-total", NULL);
+
+ g_key_file_free(keyfile);
+ return task;
+
+error:
+ g_clear_error(&error);
+ if (task)
+ vdagent_file_xfer_task_free(task);
+ if (keyfile)
+ g_key_file_free(keyfile);
+ return NULL;
+}
+
+void vdagent_file_xfers_start(struct vdagent_file_xfers *xfers,
+ VDAgentFileXferStartMessage *msg)
+{
+ AgentFileXferTask *task;
+ char *dir = NULL, *path = NULL, *file_path = NULL;
+ struct stat st;
+ int i;
+
+ g_return_if_fail(xfers != NULL);
+
+ if (g_hash_table_lookup(xfers->xfers, GUINT_TO_POINTER(msg->id))) {
+ syslog(LOG_ERR, "file-xfer: error id %u already exists, ignoring!",
+ msg->id);
+ return;
+ }
+
+ task = vdagent_parse_start_msg(msg);
+ if (task == NULL) {
+ goto error;
+ }
+
+ task->debug = xfers->debug;
+
+ file_path = g_build_filename(xfers->save_dir, task->file_name, NULL);
+
+ dir = g_path_get_dirname(file_path);
+ if (g_mkdir_with_parents(dir, S_IRWXU) == -1) {
+ syslog(LOG_ERR, "file-xfer: Failed to create dir %s", dir);
+ goto error;
+ }
+
+ path = g_strdup(file_path);
+ for (i = 0; i < 64 && (stat(path, &st) == 0 || errno != ENOENT); i++) {
+ g_free(path);
+ path = g_strdup_printf("%s (%d)", file_path, i + 1);
+ }
+ g_free(task->file_name);
+ task->file_name = path;
+ if (i == 64) {
+ syslog(LOG_ERR, "file-xfer: more then 63 copies of %s exist?",
+ file_path);
+ goto error;
+ }
+
+ task->file_fd = open(path, O_CREAT | O_WRONLY, 0644);
+ if (task->file_fd == -1) {
+ syslog(LOG_ERR, "file-xfer: failed to create file %s: %s",
+ path, strerror(errno));
+ goto error;
+ }
+
+ if (ftruncate(task->file_fd, task->file_size) < 0) {
+ syslog(LOG_ERR, "file-xfer: err reserving %"PRIu64" bytes for %s: %s",
+ task->file_size, path, strerror(errno));
+ goto error;
+ }
+
+ g_hash_table_insert(xfers->xfers, GUINT_TO_POINTER(msg->id), task);
+
+ if (xfers->debug)
+ syslog(LOG_DEBUG, "file-xfer: Adding task %u %s %"PRIu64" bytes",
+ task->id, path, task->file_size);
+
+ udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
+ msg->id, VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA, NULL, 0);
+ g_free(file_path);
+ g_free(dir);
+ return ;
+
+error:
+ udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
+ msg->id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
+ if (task)
+ vdagent_file_xfer_task_free(task);
+ g_free(file_path);
+ g_free(dir);
+}
+
+void vdagent_file_xfers_status(struct vdagent_file_xfers *xfers,
+ VDAgentFileXferStatusMessage *msg)
+{
+ AgentFileXferTask *task;
+
+ g_return_if_fail(xfers != NULL);
+
+ task = vdagent_file_xfers_get_task(xfers, msg->id);
+ if (!task)
+ return;
+
+ switch (msg->result) {
+ case VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA:
+ syslog(LOG_ERR, "file-xfer: task %u %s received unexpected 0 response",
+ task->id, task->file_name);
+ break;
+ default:
+ /* Cancel or Error, remove this task */
+ g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
+ }
+}
+
+void vdagent_file_xfers_data(struct vdagent_file_xfers *xfers,
+ VDAgentFileXferDataMessage *msg)
+{
+ AgentFileXferTask *task;
+ int len, status = -1;
+
+ g_return_if_fail(xfers != NULL);
+
+ task = vdagent_file_xfers_get_task(xfers, msg->id);
+ if (!task)
+ return;
+
+ len = write(task->file_fd, msg->data, msg->size);
+ if (len == msg->size) {
+ task->read_bytes += msg->size;
+ if (task->read_bytes >= task->file_size) {
+ if (task->read_bytes == task->file_size) {
+ if (xfers->debug)
+ syslog(LOG_DEBUG, "file-xfer: task %u %s has completed",
+ task->id, task->file_name);
+ close(task->file_fd);
+ task->file_fd = -1;
+ if (xfers->open_save_dir &&
+ task->file_xfer_nr == task->file_xfer_total &&
+ g_hash_table_size(xfers->xfers) == 1) {
+ char buf[PATH_MAX];
+ snprintf(buf, PATH_MAX, "xdg-open '%s'&", xfers->save_dir);
+ status = system(buf);
+ }
+ status = VD_AGENT_FILE_XFER_STATUS_SUCCESS;
+ } else {
+ syslog(LOG_ERR, "file-xfer: error received too much data");
+ status = VD_AGENT_FILE_XFER_STATUS_ERROR;
+ }
+ }
+ } else {
+ syslog(LOG_ERR, "file-xfer: error writing %s: %s", task->file_name,
+ strerror(errno));
+ status = VD_AGENT_FILE_XFER_STATUS_ERROR;
+ }
+
+ if (status != -1) {
+ udscs_write(xfers->vdagentd, VDAGENTD_FILE_XFER_STATUS,
+ msg->id, status, NULL, 0);
+ g_hash_table_remove(xfers->xfers, GUINT_TO_POINTER(msg->id));
+ }
+}
+
+void vdagent_file_xfers_error(struct udscs_connection *vdagentd, uint32_t msg_id)
+{
+ g_return_if_fail(vdagentd != NULL);
+
+ udscs_write(vdagentd, VDAGENTD_FILE_XFER_STATUS,
+ msg_id, VD_AGENT_FILE_XFER_STATUS_ERROR, NULL, 0);
+}