/*
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 .
*/
#include
#define __STDC_FORMAT_MACROS
#define __USE_MINGW_ANSI_STDIO 1
#include
#include
#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 (%" PRIu64 ")", 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 %" PRIu64, 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 = NULL;
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:
if (task) {
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);
}