[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