[Spice-devel] [PATCH vdagent-win 2/2] vdagent: add support for client -> guest file transfers

Arnon Gilboa agilboa at redhat.com
Thu Jun 27 04:28:28 PDT 2013


rhbz#956146
---
 Makefile.am            |    2 +
 vdagent/file_xfer.cpp  |  186 ++++++++++++++++++++++++++++++++++++++++++++++++
 vdagent/file_xfer.h    |   51 +++++++++++++
 vdagent/vdagent.cpp    |   11 +++
 vdagent/vdagent.vcproj |    8 ++
 5 files changed, 258 insertions(+), 0 deletions(-)
 create mode 100644 vdagent/file_xfer.cpp
 create mode 100644 vdagent/file_xfer.h

diff --git a/Makefile.am b/Makefile.am
index 2b7bbc4..f907031 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,6 +32,8 @@ vdagent_SOURCES =			\
 	vdagent/desktop_layout.h	\
 	vdagent/display_setting.cpp	\
 	vdagent/display_setting.h	\
+        vdagent/file_xfer.cpp		\
+        vdagent/file_xfer.h		\
 	vdagent/vdagent.cpp		\
 	$(NULL)
 
diff --git a/vdagent/file_xfer.cpp b/vdagent/file_xfer.cpp
new file mode 100644
index 0000000..0550882
--- /dev/null
+++ b/vdagent/file_xfer.cpp
@@ -0,0 +1,186 @@
+/*
+   Copyright (C) 2013 Red Hat, Inc.
+
+   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 2 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/>.
+*/
+
+#include <shlobj.h>
+#include "file_xfer.h"
+
+FileXfer::~FileXfer()
+{
+    FileXferTasks::iterator iter;
+    FileXferTask* task;
+
+    for (iter = _tasks.begin(); iter != _tasks.end(); iter++) {
+        task = iter->second;
+        CloseHandle(task->handle);
+        DeleteFileA(task->name);
+        delete task;
+    }
+}
+
+void FileXfer::handle_start(VDAgentFileXferStartMessage* start,
+                            VDAgentFileXferStatusMessage* status)
+{
+    char* file_meta = (char*)start->data;
+    char file_path[MAX_PATH], file_name[MAX_PATH];
+    ULARGE_INTEGER free_bytes;
+    FileXferTask* task;
+    uint64_t file_size;
+    HANDLE handle;
+
+    status->id = start->id;
+    status->result = VD_AGENT_FILE_XFER_STATUS_ERROR;
+    if (!g_key_get_string(file_meta, "vdagent-file-xfer", "name", file_name) ||
+            !g_key_get_uint64(file_meta, "vdagent-file-xfer", "size", &file_size)) {
+        vd_printf("file id %u meta parsing failed", start->id);
+        return;
+    }
+    vd_printf("%u %s (%llu)", start->id, file_name, file_size);
+    if (FAILED(SHGetFolderPathA(NULL, CSIDL_COMMON_DESKTOPDIRECTORY | CSIDL_FLAG_CREATE, NULL,
+            SHGFP_TYPE_CURRENT, file_path))) {
+        vd_printf("failed getting desktop path");
+        return;
+    }
+    if (!GetDiskFreeSpaceExA(file_path, &free_bytes, NULL, NULL)) {
+        vd_printf("failed getting disk free space %lu", GetLastError());
+        return;
+    }
+    if (free_bytes.QuadPart < file_size) {
+        vd_printf("insufficient disk space %llu", free_bytes.QuadPart);
+        return;
+    }
+    strcat_s(file_path, MAX_PATH, "\\");
+    strcat_s(file_path, MAX_PATH, file_name);
+    handle = CreateFileA(file_path, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
+    if (handle == INVALID_HANDLE_VALUE) {
+        vd_printf("failed creating %s %lu", file_path, GetLastError());
+        return;
+    }
+    task = new FileXferTask(handle, file_size, file_path);
+    _tasks[start->id] = task;
+    status->result = VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA;
+}
+
+bool FileXfer::handle_data(VDAgentFileXferDataMessage* data,
+                           VDAgentFileXferStatusMessage* status)
+{
+    FileXferTasks::iterator iter;
+    FileXferTask* task;
+    DWORD written;
+
+    status->id = data->id;
+    status->result = VD_AGENT_FILE_XFER_STATUS_ERROR;
+    iter = _tasks.find(data->id);
+    if (iter == _tasks.end()) {
+        vd_printf("file id %u not found", data->id);
+        goto fin;
+    }
+    task = iter->second;
+    task->pos += data->size;
+    if (task->pos > task->size) {
+        vd_printf("file xfer is longer than expected");
+        goto fin;
+    }  
+    if (!WriteFile(task->handle, data->data, (DWORD)data->size,
+                   &written, NULL) || written != data->size) {
+        vd_printf("file write failed %lu", GetLastError());
+        goto fin;
+    }
+    if (task->pos < task->size) {
+        return false;
+    }
+    vd_printf("%u completed", iter->first);
+    status->result = VD_AGENT_FILE_XFER_STATUS_SUCCESS;
+fin:
+    CloseHandle(task->handle);
+    if (status->result != VD_AGENT_FILE_XFER_STATUS_SUCCESS) {
+        DeleteFileA(task->name);
+    }
+    _tasks.erase(iter);
+    delete task;
+    return true;
+}
+
+void FileXfer::handle_status(VDAgentFileXferStatusMessage* status)
+{
+    FileXferTasks::iterator iter;
+    FileXferTask* task;
+
+    vd_printf("id %u result %u", status->id, status->result); 
+    if (status->result != VD_AGENT_FILE_XFER_STATUS_CANCELLED) {
+        vd_printf("only cancel is premitted");
+        return;
+    }
+    iter = _tasks.find(status->id);
+    if (iter == _tasks.end()) {
+        vd_printf("file id %u not found", status->id);
+        return;
+    }
+    task = iter->second;
+    CloseHandle(task->handle);
+    DeleteFileA(task->name);
+    _tasks.erase(iter);
+    delete task;
+}
+
+bool FileXfer::dispatch(VDAgentMessage* msg, VDAgentFileXferStatusMessage* status)
+{
+    bool ret = false;
+
+    switch (msg->type) {
+    case VD_AGENT_FILE_XFER_START:
+        handle_start((VDAgentFileXferStartMessage*)msg->data, status);
+        ret = true;
+        break;
+    case VD_AGENT_FILE_XFER_DATA:
+        ret = handle_data((VDAgentFileXferDataMessage*)msg->data, status);
+        break;
+    case VD_AGENT_FILE_XFER_STATUS:
+        handle_status((VDAgentFileXferStatusMessage*)msg->data);
+        break;
+    default:
+        vd_printf("unsupported message type %u size %u", msg->type, msg->size);
+    }
+    return ret;
+}
+
+//minimal parsers for GKeyFile, supporting only key=value with no spaces.
+#define G_KEY_MAX_LEN 256
+
+bool FileXfer::g_key_get_string(char* data, const char* group, const char* key, char* value)
+{
+    char group_pfx[G_KEY_MAX_LEN], key_pfx[G_KEY_MAX_LEN];
+    char *group_pos, *key_pos, *next_group_pos;
+
+    sprintf_s(group_pfx, sizeof(group_pfx), "[%s]", group);
+    if (!(group_pos = strstr((char*)data, group_pfx))) return false;
+
+    sprintf_s(key_pfx, sizeof(key_pfx), "\n%s=", key);
+    if (!(key_pos = strstr(group_pos, key_pfx))) return false;
+
+    next_group_pos = strstr(group_pos + strlen(group_pfx), "[");
+    if (next_group_pos && key_pos > next_group_pos) return false; 
+
+    return !!sscanf_s(key_pos + strlen(key_pfx), "%s\n", value);
+}
+
+bool FileXfer::g_key_get_uint64(char* data, const char* group, const char* key, uint64_t* value)
+{
+    char str[G_KEY_MAX_LEN];
+
+    if (!g_key_get_string(data, group, key, str)) return false;
+    return !!sscanf_s(str, "%llu", value);
+}
diff --git a/vdagent/file_xfer.h b/vdagent/file_xfer.h
new file mode 100644
index 0000000..f2f397c
--- /dev/null
+++ b/vdagent/file_xfer.h
@@ -0,0 +1,51 @@
+/*
+   Copyright (C) 2013 Red Hat, Inc.
+
+   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 2 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/>.
+*/
+
+#ifndef _H_FILE_XFER
+#define _H_FILE_XFER
+
+#include <map>
+#include "vdcommon.h"
+
+typedef struct ALIGN_VC FileXferTask {
+    FileXferTask(HANDLE _handle, uint64_t _size, char* _name):
+        handle(_handle), size(_size), pos(0) { strcpy_s(name, MAX_PATH, _name); }
+    HANDLE handle;
+    uint64_t size;
+    uint64_t pos;
+    char name[MAX_PATH];
+} ALIGN_GCC FileXferTask;
+
+typedef std::map<uint32_t, FileXferTask*> FileXferTasks;
+
+class FileXfer {
+public:
+    ~FileXfer();
+    bool dispatch(VDAgentMessage* msg, VDAgentFileXferStatusMessage* status);
+
+private:
+    void handle_start(VDAgentFileXferStartMessage* start, VDAgentFileXferStatusMessage* status);
+    bool handle_data(VDAgentFileXferDataMessage* data, VDAgentFileXferStatusMessage* status);
+    void handle_status(VDAgentFileXferStatusMessage* status);
+    bool g_key_get_string(char* data, const char* group, const char* key, char* value);
+    bool g_key_get_uint64(char* data, const char* group, const char* key, uint64_t* value);
+
+private:
+    FileXferTasks _tasks;
+};
+
+#endif
diff --git a/vdagent/vdagent.cpp b/vdagent/vdagent.cpp
index af99961..e5567f3 100644
--- a/vdagent/vdagent.cpp
+++ b/vdagent/vdagent.cpp
@@ -18,6 +18,7 @@
 #include "vdcommon.h"
 #include "desktop_layout.h"
 #include "display_setting.h"
+#include "file_xfer.h"
 #include "ximage.h"
 #undef max
 #undef min
@@ -147,6 +148,7 @@ private:
     bool _desktop_switch;
     DesktopLayout* _desktop_layout;
     DisplaySetting _display_setting;
+    FileXfer _file_xfer;
     HANDLE _vio_serial;
     OVERLAPPED _read_overlapped;
     OVERLAPPED _write_overlapped;
@@ -1237,6 +1239,15 @@ void VDAgent::dispatch_message(VDAgentMessage* msg, uint32_t port)
     case VD_AGENT_ANNOUNCE_CAPABILITIES:
         res = handle_announce_capabilities((VDAgentAnnounceCapabilities*)msg->data, msg->size);
         break;
+    case VD_AGENT_FILE_XFER_START:
+    case VD_AGENT_FILE_XFER_STATUS:
+    case VD_AGENT_FILE_XFER_DATA: {
+        VDAgentFileXferStatusMessage status;
+        if (_file_xfer.dispatch(msg, &status)) {
+            write_message(VD_AGENT_FILE_XFER_STATUS, sizeof(status), &status);
+        }
+        break;
+    }
     case VD_AGENT_CLIENT_DISCONNECTED:
         vd_printf("Client disconnected, agent to be restarted");
         set_control_event(CONTROL_STOP);
diff --git a/vdagent/vdagent.vcproj b/vdagent/vdagent.vcproj
index ed8c58d..6943e5e 100644
--- a/vdagent/vdagent.vcproj
+++ b/vdagent/vdagent.vcproj
@@ -350,6 +350,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\file_xfer.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\vdagent.cpp"
 				>
 			</File>
@@ -376,6 +380,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\file_xfer.h"
+				>
+			</File>
+			<File
 				RelativePath=".\resource.h"
 				>
 			</File>
-- 
1.7.7.6



More information about the Spice-devel mailing list