[channels,drive] implement replaceable backends

Allow the drive channel to implement a backend just like the printer
channel does. This way the existing file based backend can easily be
replaced by something else (database, ...)
This commit is contained in:
Armin Novak 2024-11-06 14:51:56 +01:00 committed by akallabeth
parent a17967f5eb
commit 52eefa838c
7 changed files with 996 additions and 621 deletions

View File

@ -27,3 +27,4 @@ set(${MODULE_PREFIX}_LIBS
winpr freerdp winpr freerdp
) )
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "file" "")

File diff suppressed because it is too large Load Diff

View File

@ -27,8 +27,8 @@
#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H #define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/file.h>
#include <freerdp/channels/log.h> #include <freerdp/channels/log.h>
#include <freerdp/client/drive.h>
#define TAG CHANNELS_TAG("drive.client") #define TAG CHANNELS_TAG("drive.client")
@ -37,16 +37,17 @@ typedef struct S_DRIVE_FILE DRIVE_FILE;
BOOL drive_file_free(DRIVE_FILE* file); BOOL drive_file_free(DRIVE_FILE* file);
WINPR_ATTR_MALLOC(drive_file_free, 1) WINPR_ATTR_MALLOC(drive_file_free, 1)
DRIVE_FILE* drive_file_new(const char* backend, const WCHAR* base_path, const WCHAR* path, DRIVE_FILE* drive_file_new(rdpContext* context, const rdpDriveDriver* backend,
UINT32 PathWCharLength, UINT32 id, UINT32 DesiredAccess, const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
UINT32 CreateDisposition, UINT32 CreateOptions, UINT32 FileAttributes, UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
UINT32 SharedAccess); UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess);
const uintptr_t drive_file_get_id(DRIVE_FILE* drive); uintptr_t drive_file_get_id(DRIVE_FILE* drive);
BOOL drive_file_is_dir_not_empty(DRIVE_FILE* drive); BOOL drive_file_is_dir_not_empty(DRIVE_FILE* drive);
char* drive_file_resolve_path(const char* backend, const char* what); char* drive_file_resolve_path(const rdpDriveDriver* backend, const char* what);
char* drive_file_resolve_name(const char* backend, const char* path, const char* suggested); char* drive_file_resolve_name(const rdpDriveDriver* backend, const char* path,
const char* suggested);
BOOL drive_file_open(DRIVE_FILE* file); BOOL drive_file_open(DRIVE_FILE* file);
BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset); BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset);

View File

@ -30,8 +30,6 @@
#include <winpr/crt.h> #include <winpr/crt.h>
#include <winpr/assert.h> #include <winpr/assert.h>
#include <winpr/path.h>
#include <winpr/file.h>
#include <winpr/string.h> #include <winpr/string.h>
#include <winpr/synch.h> #include <winpr/synch.h>
#include <winpr/thread.h> #include <winpr/thread.h>
@ -43,6 +41,7 @@
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <freerdp/channels/rdpdr.h> #include <freerdp/channels/rdpdr.h>
#include <freerdp/client/drive.h>
#include "drive_file.h" #include "drive_file.h"
@ -61,7 +60,7 @@ typedef struct
DEVMAN* devman; DEVMAN* devman;
rdpContext* rdpcontext; rdpContext* rdpcontext;
char* backend; const rdpDriveDriver* backend;
} DRIVE_DEVICE; } DRIVE_DEVICE;
static DWORD drive_map_windows_err(DWORD fs_errno) static DWORD drive_map_windows_err(DWORD fs_errno)
@ -181,9 +180,9 @@ static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp)
path = Stream_ConstPointer(irp->input); path = Stream_ConstPointer(irp->input);
FileId = irp->devman->id_sequence++; FileId = irp->devman->id_sequence++;
file = drive_file_new(drive->backend, drive->path, path, PathLength / sizeof(WCHAR), FileId, file = drive_file_new(drive->rdpcontext, drive->backend, drive->path, path,
DesiredAccess, CreateDisposition, CreateOptions, FileAttributes, PathLength / sizeof(WCHAR), FileId, DesiredAccess, CreateDisposition,
SharedAccess); CreateOptions, FileAttributes, SharedAccess);
if (!file) if (!file)
{ {
@ -193,9 +192,9 @@ static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp)
} }
else else
{ {
void* key = (void*)drive_file_get_id(file); const uintptr_t key = drive_file_get_id(file);
if (!ListDictionary_Add(drive->files, key, file)) if (!ListDictionary_Add(drive->files, (const void*)key, file))
{ {
WLog_ERR(TAG, "ListDictionary_Add failed!"); WLog_ERR(TAG, "ListDictionary_Add failed!");
return ERROR_INTERNAL_ERROR; return ERROR_INTERNAL_ERROR;
@ -268,19 +267,15 @@ static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp)
*/ */
static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp) static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp)
{ {
DRIVE_FILE* file = NULL;
UINT32 Length = 0;
UINT64 Offset = 0;
if (!drive || !irp || !irp->output || !irp->Complete) if (!drive || !irp || !irp->output || !irp->Complete)
return ERROR_INVALID_PARAMETER; return ERROR_INVALID_PARAMETER;
if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12)) if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
return ERROR_INVALID_DATA; return ERROR_INVALID_DATA;
Stream_Read_UINT32(irp->input, Length); UINT32 Length = Stream_Get_UINT32(irp->input);
Stream_Read_UINT64(irp->input, Offset); const UINT64 Offset = Stream_Get_UINT64(irp->input);
file = drive_get_file_by_id(drive, irp->FileId); DRIVE_FILE* file = drive_get_file_by_id(drive, irp->FileId);
if (!file) if (!file)
{ {
@ -607,7 +602,6 @@ static UINT drive_process_irp_silent_ignore(DRIVE_DEVICE* drive, IRP* irp)
*/ */
static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp) static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp)
{ {
const WCHAR* path = NULL;
DRIVE_FILE* file = NULL; DRIVE_FILE* file = NULL;
BYTE InitialQuery = 0; BYTE InitialQuery = 0;
UINT32 PathLength = 0; UINT32 PathLength = 0;
@ -623,7 +617,7 @@ static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp)
Stream_Read_UINT8(irp->input, InitialQuery); Stream_Read_UINT8(irp->input, InitialQuery);
Stream_Read_UINT32(irp->input, PathLength); Stream_Read_UINT32(irp->input, PathLength);
Stream_Seek(irp->input, 23); /* Padding */ Stream_Seek(irp->input, 23); /* Padding */
path = Stream_ConstPointer(irp->input); const WCHAR* path = Stream_PointerAs(irp->input, const WCHAR);
if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength)) if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength))
return ERROR_INVALID_DATA; return ERROR_INVALID_DATA;
@ -835,7 +829,6 @@ static UINT drive_free_int(DRIVE_DEVICE* drive)
MessageQueue_Free(drive->IrpQueue); MessageQueue_Free(drive->IrpQueue);
Stream_Free(drive->device.data, TRUE); Stream_Free(drive->device.data, TRUE);
free(drive->path); free(drive->path);
free(drive->backend);
free(drive); free(drive);
return error; return error;
} }
@ -886,23 +879,41 @@ static void drive_message_free(void* obj)
irp->Discard(irp); irp->Discard(irp);
} }
WINPR_ATTR_MALLOC(drive_free_int, 1) static const rdpDriveDriver* drive_load_backend(const char* backend)
static DRIVE_DEVICE* drive_new(rdpContext* rdpcontext, const char* backend, const char* path,
const char* name, BOOL automount)
{ {
typedef UINT(VCAPITYPE * backend_load_t)(const rdpDriveDriver**);
static backend_load_t func = NULL;
if (!func)
{
PVIRTUALCHANNELENTRY entry = freerdp_load_channel_addin_entry("drive", backend, NULL, 0);
func = WINPR_FUNC_PTR_CAST(entry, backend_load_t);
if (!func)
return NULL;
}
const rdpDriveDriver* drive = NULL;
const UINT rc = func(&drive);
if (rc != CHANNEL_RC_OK)
return NULL;
return drive;
}
WINPR_ATTR_MALLOC(drive_free_int, 1)
static DRIVE_DEVICE* drive_new(rdpContext* rdpcontext, const rdpDriveDriver* backend,
const char* path, const char* name, BOOL automount)
{
WINPR_ASSERT(backend);
size_t pathLength = strnlen(path, MAX_PATH); size_t pathLength = strnlen(path, MAX_PATH);
DRIVE_DEVICE* drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE)); DRIVE_DEVICE* drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE));
if (!drive) if (!drive)
return NULL; return NULL;
if (backend) drive->backend = backend;
{
drive->backend = _strdup(backend);
if (!drive->backend)
goto out_error;
}
drive->device.type = RDPDR_DTYP_FILESYSTEM; drive->device.type = RDPDR_DTYP_FILESYSTEM;
drive->device.IRPRequest = drive_irp_request; drive->device.IRPRequest = drive_irp_request;
drive->device.Free = drive_free; drive->device.Free = drive_free;
@ -990,8 +1001,8 @@ out_error:
* @return 0 on success, otherwise a Win32 error code * @return 0 on success, otherwise a Win32 error code
*/ */
static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints,
const char* backend, const char* rawname, const char* rawpath, const char* backendstr, const char* rawname,
BOOL automount) const char* rawpath, BOOL automount)
{ {
UINT error = ERROR_INTERNAL_ERROR; UINT error = ERROR_INTERNAL_ERROR;
DRIVE_DEVICE* drive = NULL; DRIVE_DEVICE* drive = NULL;
@ -1003,6 +1014,10 @@ static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints,
return ERROR_INVALID_PARAMETER; return ERROR_INVALID_PARAMETER;
} }
const rdpDriveDriver* backend = drive_load_backend(backendstr);
if (!backend)
return ERROR_INVALID_PARAMETER;
char* path = drive_file_resolve_path(backend, rawpath); char* path = drive_file_resolve_path(backend, rawpath);
if (!path) if (!path)
{ {

View File

@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2024 Armin Novak <armin.novak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
define_channel_client_subsystem("drive" "file" "")
set(${MODULE_PREFIX}_SRCS
drive_backend_file.c
)
set(${MODULE_PREFIX}_LIBS
winpr
)
include_directories(..)
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")

View File

@ -0,0 +1,579 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* File System Virtual Channel
*
* Copyright 2024 Armin Novak <armin.novak@thincast.com>
* Copyright 2024 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include <winpr/wtsapi.h>
#include <winpr/path.h>
#include <freerdp/config.h>
#include <freerdp/api.h>
#include <freerdp/client/drive.h>
#include <winpr/file.h>
#include <freerdp/channels/log.h>
#define TAG CHANNELS_TAG("drive.client.backend.file")
struct rdp_drive_context
{
UINT32 dwDesiredAccess;
UINT32 dwShareMode;
UINT32 dwCreationDisposition;
UINT32 dwFlagsAndAttributes;
HANDLE file_handle;
HANDLE find_handle;
WIN32_FIND_DATAW find_data;
BY_HANDLE_FILE_INFORMATION file_by_handle;
wLog* log;
WCHAR* base_path;
WCHAR* filename;
size_t filename_len;
WCHAR* fullpath;
bool isDirectory;
rdpContext* context;
};
static BOOL drive_file_fix_path(WCHAR* path, size_t length)
{
if ((length == 0) || (length > UINT32_MAX))
return FALSE;
WINPR_ASSERT(path);
for (size_t i = 0; i < length; i++)
{
if (path[i] == L'\\')
path[i] = L'/';
}
#ifdef WIN32
if ((length == 3) && (path[1] == L':') && (path[2] == L'/'))
return FALSE;
#else
if ((length == 1) && (path[0] == L'/'))
return FALSE;
#endif
if ((length > 0) && (path[length - 1] == L'/'))
path[length - 1] = L'\0';
return TRUE;
}
WINPR_ATTR_MALLOC(free, 1)
static char* drive_file_resolve_path(const char* what)
{
if (!what)
return NULL;
/* Special case: path[0] == '*' -> export all drives
* Special case: path[0] == '%' -> user home dir
*/
if (strcmp(what, "%") == 0)
return GetKnownPath(KNOWN_PATH_HOME);
#ifndef WIN32
if (strcmp(what, "*") == 0)
return _strdup("/");
#else
if (strcmp(what, "*") == 0)
{
const size_t len = GetLogicalDriveStringsA(0, NULL);
char* devlist = calloc(len + 1, sizeof(char*));
if (!devlist)
return NULL;
/* Enumerate all devices: */
DWORD rc = GetLogicalDriveStringsA(len, devlist);
if (rc != len)
{
free(devlist);
return NULL;
}
char* path = NULL;
for (size_t i = 0;; i++)
{
char* dev = &devlist[i * sizeof(char*)];
if (!*dev)
break;
if (*dev <= 'B')
continue;
path = _strdup(dev);
break;
}
free(devlist);
return path;
}
#endif
return _strdup(what);
}
WINPR_ATTR_MALLOC(free, 1)
static char* drive_file_resolve_name(const char* path, const char* suggested)
{
if (!path)
return NULL;
if (strcmp(path, "*") == 0)
{
if (suggested)
{
char* str = NULL;
size_t len = 0;
(void)winpr_asprintf(&str, &len, "[%s] %s", suggested, path);
return str;
}
return _strdup(path);
}
if (strcmp(path, "%") == 0)
return GetKnownPath(KNOWN_PATH_HOME);
if (suggested)
return _strdup(suggested);
return _strdup(path);
}
WINPR_ATTR_MALLOC(free, 1)
static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path,
size_t PathWCharLength)
{
BOOL ok = FALSE;
WCHAR* fullpath = NULL;
if (!base_path || (!path && (PathWCharLength > 0)))
goto fail;
const size_t base_path_length = _wcsnlen(base_path, MAX_PATH);
const size_t length = base_path_length + PathWCharLength + 1;
fullpath = (WCHAR*)calloc(length, sizeof(WCHAR));
if (!fullpath)
goto fail;
CopyMemory(fullpath, base_path, base_path_length * sizeof(WCHAR));
if (path)
CopyMemory(&fullpath[base_path_length], path, PathWCharLength * sizeof(WCHAR));
if (!drive_file_fix_path(fullpath, length))
goto fail;
WCHAR* normalized = winpr_NormalizePathW(fullpath);
free(fullpath);
fullpath = normalized;
ok = winpr_PathIsRootOfW(base_path, fullpath);
fail:
if (!ok)
{
free(fullpath);
fullpath = NULL;
}
return fullpath;
}
static void drive_free(rdpDriveContext* file)
{
if (!file)
return;
if (file->file_handle != INVALID_HANDLE_VALUE)
(void)CloseHandle(file->file_handle);
if (file->find_handle != INVALID_HANDLE_VALUE)
FindClose(file->find_handle);
free(file->base_path);
free(file->filename);
free(file->fullpath);
free(file);
}
WINPR_ATTR_MALLOC(drive_free, 1)
static rdpDriveContext* drive_new(rdpContext* context)
{
WINPR_ASSERT(context);
rdpDriveContext* file = calloc(1, sizeof(rdpDriveContext));
if (!file)
return NULL;
file->log = WLog_Get(TAG);
file->context = context;
file->file_handle = INVALID_HANDLE_VALUE;
file->find_handle = INVALID_HANDLE_VALUE;
return file;
}
static bool drive_is_file(rdpDriveContext* context)
{
if (!context)
return false;
if (context->isDirectory)
return false;
return true;
}
static bool drive_exists(rdpDriveContext* context)
{
if (!context)
return false;
return PathFileExistsW(context->fullpath);
}
static bool drive_empty(rdpDriveContext* context)
{
if (!context || !context->isDirectory)
return false;
return PathIsDirectoryEmptyW(context->fullpath);
}
static SSIZE_T drive_seek(rdpDriveContext* context, SSIZE_T offset, int whence)
{
if (!drive_exists(context) || !drive_is_file(context))
return -1;
LARGE_INTEGER li = { .QuadPart = offset };
return SetFilePointerEx(context->file_handle, li, NULL, (DWORD)whence);
}
static SSIZE_T drive_read(rdpDriveContext* context, void* buf, size_t nbyte)
{
if (!drive_exists(context) || !drive_is_file(context))
return -1;
if (nbyte > UINT32_MAX)
return -1;
DWORD read = 0;
if (!ReadFile(context->file_handle, buf, (UINT32)nbyte, &read, NULL))
return -1;
return read;
}
static SSIZE_T drive_write(rdpDriveContext* context, const void* buf, size_t nbyte)
{
SSIZE_T rc = 0;
if (!drive_exists(context) || !drive_is_file(context))
return -1;
do
{
const DWORD towrite = (nbyte > UINT32_MAX) ? UINT32_MAX : (DWORD)nbyte;
DWORD written = 0;
if (!WriteFile(context->file_handle, buf, towrite, &written, NULL))
return -1;
if (written > towrite)
return -1;
nbyte -= written;
rc += written;
} while (nbyte > 0);
return rc;
}
static bool drive_remove(rdpDriveContext* context)
{
if (!context)
return false;
if (!PathFileExistsW(context->fullpath))
return false;
if (context->isDirectory)
return winpr_RemoveDirectory_RecursiveW(context->fullpath);
return DeleteFileW(context->fullpath);
}
static UINT32 drive_getFileAttributes(rdpDriveContext* context)
{
if (!drive_exists(context))
return 0;
return GetFileAttributesW(context->fullpath);
}
static bool drive_setFileAttributesAndTimes(rdpDriveContext* context, UINT64 CreationTime,
UINT64 LastAccessTime, UINT64 LastWriteTime,
UINT64 ChangeTime, DWORD FileAttributes)
{
if (!drive_exists(context))
return false;
if (!SetFileAttributesW(context->fullpath, FileAttributes))
return false;
union
{
UINT64 u64;
FILETIME ft;
} c, a, w;
c.u64 = CreationTime;
a.u64 = LastAccessTime;
w.u64 = LastWriteTime;
FILETIME* pc = (CreationTime > 0) ? &c.ft : NULL;
FILETIME* pa = (LastAccessTime > 0) ? &a.ft : NULL;
FILETIME* pw = (LastWriteTime > 0) ? &w.ft : NULL;
WINPR_UNUSED(ChangeTime);
return SetFileTime(context->file_handle, pc, pa, pw);
}
static bool drive_setSize(rdpDriveContext* context, INT64 size)
{
WINPR_ASSERT(context);
if (!drive_exists(context) || !drive_is_file(context))
{
WLog_Print(context->log, WLOG_ERROR, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")",
context->fullpath, size, GetLastError());
return false;
}
const LARGE_INTEGER liSize = { .QuadPart = size };
if (!SetFilePointerEx(context->file_handle, liSize, NULL, FILE_BEGIN))
{
WLog_Print(context->log, WLOG_ERROR, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")",
context->fullpath, size, GetLastError());
return false;
}
if (SetEndOfFile(context->file_handle) == 0)
{
WLog_Print(context->log, WLOG_ERROR, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")",
context->fullpath, size, GetLastError());
return false;
}
return true;
}
static const WIN32_FIND_DATAW* drive_first(rdpDriveContext* context, const WCHAR* query,
size_t numCharacters)
{
if (!context || !query)
return NULL;
const size_t len = _wcsnlen(query, numCharacters + 1);
if (len > numCharacters)
return NULL;
if (context->find_handle != INVALID_HANDLE_VALUE)
CloseHandle(context->find_handle);
WCHAR* ent_path = drive_file_combine_fullpath(context->base_path, query, numCharacters);
if (!ent_path)
return NULL;
context->find_handle = FindFirstFileW(ent_path, &context->find_data);
free(ent_path);
if (context->find_handle == INVALID_HANDLE_VALUE)
return NULL;
return &context->find_data;
}
static const WIN32_FIND_DATAW* drive_next(rdpDriveContext* context)
{
if (!context || (context->find_handle == INVALID_HANDLE_VALUE))
return NULL;
if (!FindNextFileW(context->find_handle, &context->find_data))
return NULL;
return &context->find_data;
}
static bool drive_check_existing(rdpDriveContext* context)
{
WINPR_ASSERT(context);
if (!context->fullpath)
return false;
if (PathFileExistsW(context->fullpath))
return false;
return true;
}
static bool drive_createDirectory(rdpDriveContext* context)
{
if (!drive_check_existing(context))
return false;
if (!CreateDirectoryW(context->fullpath, NULL))
return false;
context->isDirectory = true;
return true;
}
static bool drive_createFile(rdpDriveContext* context, UINT32 dwDesiredAccess, UINT32 dwShareMode,
UINT32 dwCreationDisposition, UINT32 dwFlagsAndAttributes)
{
WINPR_ASSERT(context);
if (!context->fullpath || context->isDirectory)
return false;
context->file_handle = CreateFileW(context->fullpath, dwDesiredAccess, dwShareMode, NULL,
dwCreationDisposition, dwFlagsAndAttributes, NULL);
return context->file_handle != INVALID_HANDLE_VALUE;
}
static bool drive_update_path(rdpDriveContext* context)
{
WINPR_ASSERT(context);
free(context->fullpath);
context->fullpath = NULL;
if (!context->base_path || !context->filename)
return false;
context->fullpath =
drive_file_combine_fullpath(context->base_path, context->filename, context->filename_len);
return context->fullpath != NULL;
}
static bool drive_setPath(rdpDriveContext* context, const WCHAR* base_path, const WCHAR* filename,
size_t nbFilenameChar)
{
WINPR_ASSERT(context);
WCHAR* cbase_path = NULL;
WCHAR* cfilename = NULL;
if (base_path)
cbase_path = _wcsdup(base_path);
if (filename)
cfilename = wcsndup(filename, nbFilenameChar);
free(context->base_path);
free(context->filename);
context->base_path = cbase_path;
context->filename = cfilename;
context->filename_len = nbFilenameChar;
return drive_update_path(context);
}
static const BY_HANDLE_FILE_INFORMATION* drive_getFileAttributeData(rdpDriveContext* context)
{
if (!drive_exists(context))
return NULL;
HANDLE hFile = CreateFileW(context->fullpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
const BOOL rc = GetFileInformationByHandle(hFile, &context->file_by_handle);
CloseHandle(hFile);
if (rc)
goto out;
}
WIN32_FILE_ATTRIBUTE_DATA data = { 0 };
if (!GetFileAttributesExW(context->fullpath, GetFileExInfoStandard, &data))
return NULL;
const BY_HANDLE_FILE_INFORMATION empty = { 0 };
context->file_by_handle = empty;
context->file_by_handle.dwFileAttributes = data.dwFileAttributes;
context->file_by_handle.ftCreationTime = data.ftCreationTime;
context->file_by_handle.ftLastAccessTime = data.ftLastAccessTime;
context->file_by_handle.ftLastWriteTime = data.ftLastWriteTime;
context->file_by_handle.nFileSizeHigh = data.nFileSizeHigh;
context->file_by_handle.nFileSizeLow = data.nFileSizeLow;
out:
return &context->file_by_handle;
}
static bool drive_move(rdpDriveContext* context, const WCHAR* newName, size_t numCharacters,
bool replaceIfExists)
{
WINPR_ASSERT(context);
if (!newName || (numCharacters == 0))
return false;
const size_t len = _wcsnlen(newName, numCharacters + 1);
if (len > numCharacters)
return false;
bool reopen = false;
if (!context->isDirectory)
{
if (context->file_handle != INVALID_HANDLE_VALUE)
{
CloseHandle(context->file_handle);
context->file_handle = INVALID_HANDLE_VALUE;
reopen = true;
}
}
WCHAR* newpath = drive_file_combine_fullpath(context->base_path, newName, numCharacters);
if (!newpath)
return false;
const DWORD flags = replaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0;
const BOOL res = MoveFileExW(context->fullpath, newpath, flags);
free(newpath);
if (!res)
return false;
if (!drive_setPath(context, context->base_path, newName, numCharacters))
return false;
if (reopen)
return drive_createFile(context, context->dwDesiredAccess, context->dwShareMode,
context->dwCreationDisposition, context->dwFlagsAndAttributes);
return true;
}
static const rdpDriveDriver s_driver = { .resolve_path = drive_file_resolve_path,
.resolve_name = drive_file_resolve_name,
.createDirectory = drive_createDirectory,
.createFile = drive_createFile,
.new = drive_new,
.free = drive_free,
.seek = drive_seek,
.read = drive_read,
.write = drive_write,
.remove = drive_remove,
.move = drive_move,
.exists = drive_exists,
.empty = drive_empty,
.setSize = drive_setSize,
.getFileAttributes = drive_getFileAttributes,
.setFileAttributesAndTimes =
drive_setFileAttributesAndTimes,
.setPath = drive_setPath,
.first = drive_first,
.next = drive_next,
.getFileAttributeData = drive_getFileAttributeData };
FREERDP_ENTRY_POINT(UINT VCAPITYPE file_freerdp_drive_client_subsystem_entry(void* arg))
{
const rdpDriveDriver** ppDriver = (const rdpDriveDriver**)arg;
if (!ppDriver)
return ERROR_INVALID_PARAMETER;
*ppDriver = &s_driver;
return CHANNEL_RC_OK;
}

View File

@ -0,0 +1,154 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Drive Virtual Channel
*
* Copyright 2024 Armin Novak <armin.novak@gmail.com>
* Copyright 2024 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <freerdp/channels/rdpdr.h>
#ifdef __cplusplus
extern "C"
{
#endif
/** @struct rdpDriveContext
* @briefopaque type holding a drive context
* @since version 3.10.0
*/
typedef struct rdp_drive_context rdpDriveContext;
/** @struct rdpDriveDriver
* @brief This structure contains all function pointers required to implement a drive channel
* backend.
* @since version 3.10.0
*
* @var rdpDriveDriver::resolve_path
* Member 'resolve_path' takes a path with wildcards '%' or '*' as input and resolves these to
* an absolute local path. Returns this resolved path as allocated string. Returns \b NULL in
* case of failure.
*
* @var rdpDriveDriver::resolve_name
* Member 'resolve_name' takes the path to redirect and an optional name suggestion and converts
* these to a usable name for the drive redirection. Preferred is the suggested name, but the
* supplied path is used as fallback. In any case, forbidden symbols are replaced before a newly
* allocated string is returned. Returns \b NULL in case of failure.
*
* @var rdpDriveDriver::new
* Member 'new' allocates a new \b rdpDriveContext for a given \b rdpContext. Returns \b NULL in
* case of failure.
*
* @var rdpDriveDriver::free
* Member 'free' cleans up a previously allocated \b rdpDriveContext. Argument may be \b NULL
*
* @var rdpDriveDriver::setPath
* Member 'setPath' initializes a \b rdpDriveContext. \b base_path argument is the (local)
* absolute path to prefix, \b filename the path this context is for.
*
* @var rdpDriveDriver::createDirectory
* Create a directory for a given context. Fails if the directory can not be created or the
* context is not holding a directory
*
* @var rdpDriveDriver::createFile
* Create or open a file for a given context. Fails if the context holds a directory or the file
* creation failed.
*
* @var rdpDriveDriver::seek
* Position the file pointer in an opened file. Fails if the file is not open, the seek can not
* be done or the context is a directory.
*
* @var rdpDriveDriver::read
* Read data from an opened file. Fails if the file can not be read, is not open or the context
* holds a directory.
*
* @var rdpDriveDriver::write
* Write data to an opened file. Fails if the file can not be written to, the file is not open
* or the context holds a directory.
*
* @var rdpDriveDriver::remove
* Delete a file or directory identified by context (recursively)
*
* @var rdpDriveDriver::move
* Move a file or directory from the name the context holds to the new name supplied by \b
* newName. Optionally overwrite existing entries.
*
* @var rdpDriveDriver::exists
* Check a given context (file or directory) already exists
*
* @var rdpDriveDriver::empty
* Check if a given context is a directory and if it is empty.
*
* @var rdpDriveDriver::setSize
* Set the file size for a given context.
*
* @var rdpDriveDriver::getFileAttributes
* Return the file attributes of a given context
*
* @var rdpDriveDriver::setFileAttributesAndTimes
* Update file attributes and times for a given context
*
* @var rdpDriveDriver::first
* Reset a directory iterator and return the first entry found or \b NULL in case of failure.
*
* @var rdpDriveDriver::next
* Get the next directory iterator or \b NULL in case of no more elements.
*
* @var rdpDriveDriver::getFileAttributeData
* Get file attribute data for a given context. Returns the attribute data or \b NULL in case of
* failure.
*/
typedef struct rdp_drive_driver
{
char* (*resolve_path)(const char* what);
char* (*resolve_name)(const char* path, const char* suggested);
rdpDriveContext* (*new)(rdpContext* context);
void (*free)(rdpDriveContext*);
bool (*setPath)(rdpDriveContext* context, const WCHAR* base_path, const WCHAR* filename,
size_t nbFileNameChar);
bool (*createDirectory)(rdpDriveContext* context);
bool (*createFile)(rdpDriveContext* context, UINT32 dwDesiredAccess, UINT32 dwShareMode,
UINT32 dwCreationDisposition, UINT32 dwFlagsAndAttributes);
SSIZE_T (*seek)(rdpDriveContext* context, SSIZE_T offset, int whence);
SSIZE_T (*read)(rdpDriveContext* context, void* buf, size_t nbyte);
SSIZE_T (*write)(rdpDriveContext* context, const void* buf, size_t nbyte);
bool (*remove)(rdpDriveContext* context);
bool (*move)(rdpDriveContext* context, const WCHAR* newName, size_t numCharacters,
bool replaceIfExists);
bool (*exists)(rdpDriveContext* context);
bool (*empty)(rdpDriveContext* context);
bool (*setSize)(rdpDriveContext* context, INT64 size);
UINT32 (*getFileAttributes)(rdpDriveContext* context);
bool (*setFileAttributesAndTimes)(rdpDriveContext* context, UINT64 CreationTime,
UINT64 LastAccessTime, UINT64 LastWriteTime,
UINT64 ChangeTime, UINT32 dwFileAttributes);
const WIN32_FIND_DATAW* (*first)(rdpDriveContext* context, const WCHAR* query,
size_t numCharacters);
const WIN32_FIND_DATAW* (*next)(rdpDriveContext* context);
const BY_HANDLE_FILE_INFORMATION* (*getFileAttributeData)(rdpDriveContext* context);
} rdpDriveDriver;
#ifdef __cplusplus
}
#endif