summaryrefslogtreecommitdiffstats
path: root/channels/drive/client
diff options
context:
space:
mode:
Diffstat (limited to 'channels/drive/client')
-rw-r--r--channels/drive/client/CMakeLists.txt29
-rw-r--r--channels/drive/client/drive_file.c971
-rw-r--r--channels/drive/client/drive_file.h67
-rw-r--r--channels/drive/client/drive_main.c1120
4 files changed, 2187 insertions, 0 deletions
diff --git a/channels/drive/client/CMakeLists.txt b/channels/drive/client/CMakeLists.txt
new file mode 100644
index 0000000..a236f76
--- /dev/null
+++ b/channels/drive/client/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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("drive")
+
+set(${MODULE_PREFIX}_SRCS
+ drive_file.c
+ drive_file.h
+ drive_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry")
diff --git a/channels/drive/client/drive_file.c b/channels/drive/client/drive_file.c
new file mode 100644
index 0000000..31150ea
--- /dev/null
+++ b/channels/drive/client/drive_file.c
@@ -0,0 +1,971 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Gerald Richter
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 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 <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include "drive_file.h"
+
+#ifdef WITH_DEBUG_RDPDR
+#define DEBUG_WSTR(msg, wstr) \
+ do \
+ { \
+ char lpstr[1024] = { 0 }; \
+ ConvertWCharToUtf8(wstr, lpstr, ARRAYSIZE(lpstr)); \
+ WLog_DBG(TAG, msg, lpstr); \
+ } while (0)
+#else
+#define DEBUG_WSTR(msg, wstr) \
+ do \
+ { \
+ } while (0)
+#endif
+
+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;
+}
+
+static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path,
+ size_t PathWCharLength)
+{
+ BOOL ok = FALSE;
+ WCHAR* fullpath = NULL;
+ size_t length = 0;
+
+ if (!base_path || (!path && (PathWCharLength > 0)))
+ goto fail;
+
+ const size_t base_path_length = _wcsnlen(base_path, MAX_PATH);
+ 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;
+
+ /* Ensure the path does not contain sequences like '..' */
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+ if (_wcsstr(&fullpath[base_path_length], dotdot))
+ {
+ char abuffer[MAX_PATH] = { 0 };
+ ConvertWCharToUtf8(&fullpath[base_path_length], abuffer, ARRAYSIZE(abuffer));
+
+ WLog_WARN(TAG, "[rdpdr] received invalid file path '%s' from server, aborting!",
+ &abuffer[base_path_length]);
+ goto fail;
+ }
+
+ ok = TRUE;
+fail:
+ if (!ok)
+ {
+ free(fullpath);
+ fullpath = NULL;
+ }
+ return fullpath;
+}
+
+static BOOL drive_file_set_fullpath(DRIVE_FILE* file, WCHAR* fullpath)
+{
+ if (!file || !fullpath)
+ return FALSE;
+
+ const size_t len = _wcslen(fullpath);
+ free(file->fullpath);
+ file->fullpath = NULL;
+
+ if (len == 0)
+ return TRUE;
+
+ file->fullpath = fullpath;
+
+ const WCHAR sep[] = { PathGetSeparatorW(PATH_STYLE_NATIVE), '\0' };
+ WCHAR* filename = _wcsrchr(file->fullpath, *sep);
+ if (filename && _wcsncmp(filename, sep, ARRAYSIZE(sep)) == 0)
+ *filename = '\0';
+
+ return TRUE;
+}
+
+static BOOL drive_file_init(DRIVE_FILE* file)
+{
+ UINT CreateDisposition = 0;
+ DWORD dwAttr = GetFileAttributesW(file->fullpath);
+
+ if (dwAttr != INVALID_FILE_ATTRIBUTES)
+ {
+ /* The file exists */
+ file->is_dir = (dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0;
+
+ if (file->is_dir)
+ {
+ if (file->CreateDisposition == FILE_CREATE)
+ {
+ SetLastError(ERROR_ALREADY_EXISTS);
+ return FALSE;
+ }
+
+ if (file->CreateOptions & FILE_NON_DIRECTORY_FILE)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ if (file->CreateOptions & FILE_DIRECTORY_FILE)
+ {
+ SetLastError(ERROR_DIRECTORY);
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ file->is_dir = ((file->CreateOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE);
+
+ if (file->is_dir)
+ {
+ /* Should only create the directory if the disposition allows for it */
+ if ((file->CreateDisposition == FILE_OPEN_IF) ||
+ (file->CreateDisposition == FILE_CREATE))
+ {
+ if (CreateDirectoryW(file->fullpath, NULL) != 0)
+ {
+ return TRUE;
+ }
+ }
+
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return FALSE;
+ }
+ }
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ switch (file->CreateDisposition)
+ {
+ case FILE_SUPERSEDE: /* If the file already exists, replace it with the given file. If
+ it does not, create the given file. */
+ CreateDisposition = CREATE_ALWAYS;
+ break;
+
+ case FILE_OPEN: /* If the file already exists, open it instead of creating a new file.
+ If it does not, fail the request and do not create a new file. */
+ CreateDisposition = OPEN_EXISTING;
+ break;
+
+ case FILE_CREATE: /* If the file already exists, fail the request and do not create or
+ open the given file. If it does not, create the given file. */
+ CreateDisposition = CREATE_NEW;
+ break;
+
+ case FILE_OPEN_IF: /* If the file already exists, open it. If it does not, create the
+ given file. */
+ CreateDisposition = OPEN_ALWAYS;
+ break;
+
+ case FILE_OVERWRITE: /* If the file already exists, open it and overwrite it. If it does
+ not, fail the request. */
+ CreateDisposition = TRUNCATE_EXISTING;
+ break;
+
+ case FILE_OVERWRITE_IF: /* If the file already exists, open it and overwrite it. If it
+ does not, create the given file. */
+ CreateDisposition = CREATE_ALWAYS;
+ break;
+
+ default:
+ break;
+ }
+
+#ifndef WIN32
+ file->SharedAccess = 0;
+#endif
+ file->file_handle = CreateFileW(file->fullpath, file->DesiredAccess, file->SharedAccess,
+ NULL, CreateDisposition, file->FileAttributes, NULL);
+ }
+
+#ifdef WIN32
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ /* Get the error message, if any. */
+ DWORD errorMessageID = GetLastError();
+
+ if (errorMessageID != 0)
+ {
+ LPSTR messageBuffer = NULL;
+ size_t size =
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&messageBuffer, 0, NULL);
+ WLog_ERR(TAG, "Error in drive_file_init: %s %s", messageBuffer, file->fullpath);
+ /* Free the buffer. */
+ LocalFree(messageBuffer);
+ /* restore original error code */
+ SetLastError(errorMessageID);
+ }
+ }
+#endif
+
+ return file->file_handle != INVALID_HANDLE_VALUE;
+}
+
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
+ UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
+ UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess)
+{
+ DRIVE_FILE* file = NULL;
+
+ if (!base_path || (!path && (PathWCharLength > 0)))
+ return NULL;
+
+ file = (DRIVE_FILE*)calloc(1, sizeof(DRIVE_FILE));
+
+ if (!file)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return NULL;
+ }
+
+ file->file_handle = INVALID_HANDLE_VALUE;
+ file->find_handle = INVALID_HANDLE_VALUE;
+ file->id = id;
+ file->basepath = base_path;
+ file->FileAttributes = FileAttributes;
+ file->DesiredAccess = DesiredAccess;
+ file->CreateDisposition = CreateDisposition;
+ file->CreateOptions = CreateOptions;
+ file->SharedAccess = SharedAccess;
+ drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathWCharLength));
+
+ if (!drive_file_init(file))
+ {
+ DWORD lastError = GetLastError();
+ drive_file_free(file);
+ SetLastError(lastError);
+ return NULL;
+ }
+
+ return file;
+}
+
+BOOL drive_file_free(DRIVE_FILE* file)
+{
+ BOOL rc = FALSE;
+
+ if (!file)
+ return FALSE;
+
+ if (file->file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file->file_handle);
+ file->file_handle = INVALID_HANDLE_VALUE;
+ }
+
+ if (file->find_handle != INVALID_HANDLE_VALUE)
+ {
+ FindClose(file->find_handle);
+ file->find_handle = INVALID_HANDLE_VALUE;
+ }
+
+ if (file->delete_pending)
+ {
+ if (file->is_dir)
+ {
+ if (!winpr_RemoveDirectory_RecursiveW(file->fullpath))
+ goto fail;
+ }
+ else if (!DeleteFileW(file->fullpath))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ DEBUG_WSTR("Free %s", file->fullpath);
+ free(file->fullpath);
+ free(file);
+ return rc;
+}
+
+BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset)
+{
+ LARGE_INTEGER loffset;
+
+ if (!file)
+ return FALSE;
+
+ if (Offset > INT64_MAX)
+ return FALSE;
+
+ loffset.QuadPart = (LONGLONG)Offset;
+ return SetFilePointerEx(file->file_handle, loffset, NULL, FILE_BEGIN);
+}
+
+BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length)
+{
+ DWORD read = 0;
+
+ if (!file || !buffer || !Length)
+ return FALSE;
+
+ DEBUG_WSTR("Read file %s", file->fullpath);
+
+ if (ReadFile(file->file_handle, buffer, *Length, &read, NULL))
+ {
+ *Length = read;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length)
+{
+ DWORD written = 0;
+
+ if (!file || !buffer)
+ return FALSE;
+
+ DEBUG_WSTR("Write file %s", file->fullpath);
+
+ while (Length > 0)
+ {
+ if (!WriteFile(file->file_handle, buffer, Length, &written, NULL))
+ return FALSE;
+
+ Length -= written;
+ buffer += written;
+ }
+
+ return TRUE;
+}
+
+static BOOL drive_file_query_from_handle_information(const DRIVE_FILE* file,
+ const BY_HANDLE_FILE_INFORMATION* info,
+ UINT32 FsInformationClass, wStream* output)
+{
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 36))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 36); /* Length */
+ Stream_Write_UINT32(output, info->ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, info->ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, info->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, info->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */
+ /* Reserved(4), MUST NOT be added! */
+ break;
+
+ case FileStandardInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 22))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 22); /* Length */
+ Stream_Write_UINT32(output, info->nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, info->nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, info->nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, info->nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, info->nNumberOfLinks); /* NumberOfLinks */
+ Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */
+ Stream_Write_UINT8(output, info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
+ ? TRUE
+ : FALSE); /* Directory */
+ /* Reserved(2), MUST NOT be added! */
+ break;
+
+ case FileAttributeTagInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 8))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 8); /* Length */
+ Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, 0); /* ReparseTag */
+ break;
+
+ default:
+ /* Unhandled FsInformationClass */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL drive_file_query_from_attributes(const DRIVE_FILE* file,
+ const WIN32_FILE_ATTRIBUTE_DATA* attrib,
+ UINT32 FsInformationClass, wStream* output)
+{
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 36))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 36); /* Length */
+ Stream_Write_UINT32(output, attrib->ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output, attrib->ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ attrib->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ attrib->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */
+ /* Reserved(4), MUST NOT be added! */
+ break;
+
+ case FileStandardInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 22))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 22); /* Length */
+ Stream_Write_UINT32(output, attrib->nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, attrib->nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, 0); /* NumberOfLinks */
+ Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */
+ Stream_Write_UINT8(output, attrib->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
+ ? TRUE
+ : FALSE); /* Directory */
+ /* Reserved(2), MUST NOT be added! */
+ break;
+
+ case FileAttributeTagInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 8))
+ return FALSE;
+
+ Stream_Write_UINT32(output, 8); /* Length */
+ Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, 0); /* ReparseTag */
+ break;
+
+ default:
+ /* Unhandled FsInformationClass */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output)
+{
+ BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
+ BOOL status = 0;
+ HANDLE hFile = NULL;
+
+ if (!file || !output)
+ return FALSE;
+
+ hFile = CreateFileW(file->fullpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ status = GetFileInformationByHandle(hFile, &fileInformation);
+ CloseHandle(hFile);
+ if (!status)
+ goto out_fail;
+
+ if (!drive_file_query_from_handle_information(file, &fileInformation, FsInformationClass,
+ output))
+ goto out_fail;
+
+ return TRUE;
+ }
+
+ /* If we failed before (i.e. if information for a drive is queried) fall back to
+ * GetFileAttributesExW */
+ WIN32_FILE_ATTRIBUTE_DATA fileAttributes = { 0 };
+ if (!GetFileAttributesExW(file->fullpath, GetFileExInfoStandard, &fileAttributes))
+ goto out_fail;
+
+ if (!drive_file_query_from_attributes(file, &fileAttributes, FsInformationClass, output))
+ goto out_fail;
+
+ return TRUE;
+out_fail:
+ Stream_Write_UINT32(output, 0); /* Length */
+ return FALSE;
+}
+
+BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length,
+ wStream* input)
+{
+ INT64 size = 0;
+ WCHAR* fullpath = NULL;
+ ULARGE_INTEGER liCreationTime;
+ ULARGE_INTEGER liLastAccessTime;
+ ULARGE_INTEGER liLastWriteTime;
+ ULARGE_INTEGER liChangeTime;
+ FILETIME ftCreationTime;
+ FILETIME ftLastAccessTime;
+ FILETIME ftLastWriteTime;
+ FILETIME* pftCreationTime = NULL;
+ FILETIME* pftLastAccessTime = NULL;
+ FILETIME* pftLastWriteTime = NULL;
+ UINT32 FileAttributes = 0;
+ UINT32 FileNameLength = 0;
+ LARGE_INTEGER liSize;
+ UINT8 delete_pending = 0;
+ UINT8 ReplaceIfExists = 0;
+ DWORD attr = 0;
+
+ if (!file || !input)
+ return FALSE;
+
+ switch (FsInformationClass)
+ {
+ case FileBasicInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 36))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
+ Stream_Read_UINT64(input, liCreationTime.QuadPart);
+ Stream_Read_UINT64(input, liLastAccessTime.QuadPart);
+ Stream_Read_UINT64(input, liLastWriteTime.QuadPart);
+ Stream_Read_UINT64(input, liChangeTime.QuadPart);
+ Stream_Read_UINT32(input, FileAttributes);
+
+ if (!PathFileExistsW(file->fullpath))
+ return FALSE;
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ WLog_ERR(TAG, "Unable to set file time %s (%" PRId32 ")", file->fullpath,
+ GetLastError());
+ return FALSE;
+ }
+
+ if (liCreationTime.QuadPart != 0)
+ {
+ ftCreationTime.dwHighDateTime = liCreationTime.u.HighPart;
+ ftCreationTime.dwLowDateTime = liCreationTime.u.LowPart;
+ pftCreationTime = &ftCreationTime;
+ }
+
+ if (liLastAccessTime.QuadPart != 0)
+ {
+ ftLastAccessTime.dwHighDateTime = liLastAccessTime.u.HighPart;
+ ftLastAccessTime.dwLowDateTime = liLastAccessTime.u.LowPart;
+ pftLastAccessTime = &ftLastAccessTime;
+ }
+
+ if (liLastWriteTime.QuadPart != 0)
+ {
+ ftLastWriteTime.dwHighDateTime = liLastWriteTime.u.HighPart;
+ ftLastWriteTime.dwLowDateTime = liLastWriteTime.u.LowPart;
+ pftLastWriteTime = &ftLastWriteTime;
+ }
+
+ if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart)
+ {
+ ftLastWriteTime.dwHighDateTime = liChangeTime.u.HighPart;
+ ftLastWriteTime.dwLowDateTime = liChangeTime.u.LowPart;
+ pftLastWriteTime = &ftLastWriteTime;
+ }
+
+ DEBUG_WSTR("SetFileTime %s", file->fullpath);
+
+ SetFileAttributesW(file->fullpath, FileAttributes);
+ if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime,
+ pftLastWriteTime))
+ {
+ WLog_ERR(TAG, "Unable to set file time to %s", file->fullpath);
+ return FALSE;
+ }
+
+ break;
+
+ case FileEndOfFileInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232067.aspx */
+ case FileAllocationInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 8))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232076.aspx */
+ Stream_Read_INT64(input, size);
+
+ if (file->file_handle == INVALID_HANDLE_VALUE)
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ liSize.QuadPart = size;
+
+ if (!SetFilePointerEx(file->file_handle, liSize, NULL, FILE_BEGIN))
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ DEBUG_WSTR("Truncate %s", file->fullpath);
+
+ if (SetEndOfFile(file->file_handle) == 0)
+ {
+ WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath,
+ size, GetLastError());
+ return FALSE;
+ }
+
+ break;
+
+ case FileDispositionInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232098.aspx */
+ /* http://msdn.microsoft.com/en-us/library/cc241371.aspx */
+ if (file->is_dir && !PathIsDirectoryEmptyW(file->fullpath))
+ break; /* TODO: SetLastError ??? */
+
+ if (Length)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(input, delete_pending);
+ }
+ else
+ delete_pending = 1;
+
+ if (delete_pending)
+ {
+ DEBUG_WSTR("SetDeletePending %s", file->fullpath);
+ attr = GetFileAttributesW(file->fullpath);
+
+ if (attr & FILE_ATTRIBUTE_READONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return FALSE;
+ }
+ }
+
+ file->delete_pending = delete_pending;
+ break;
+
+ case FileRenameInformation:
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, 6))
+ return FALSE;
+
+ /* http://msdn.microsoft.com/en-us/library/cc232085.aspx */
+ Stream_Read_UINT8(input, ReplaceIfExists);
+ Stream_Seek_UINT8(input); /* RootDirectory */
+ Stream_Read_UINT32(input, FileNameLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, input, FileNameLength))
+ return FALSE;
+
+ fullpath = drive_file_combine_fullpath(file->basepath, Stream_ConstPointer(input),
+ FileNameLength / sizeof(WCHAR));
+
+ if (!fullpath)
+ return FALSE;
+
+#ifdef _WIN32
+
+ if (file->file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file->file_handle);
+ file->file_handle = INVALID_HANDLE_VALUE;
+ }
+
+#endif
+ DEBUG_WSTR("MoveFileExW %s", file->fullpath);
+
+ if (MoveFileExW(file->fullpath, fullpath,
+ MOVEFILE_COPY_ALLOWED |
+ (ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0)))
+ {
+ if (!drive_file_set_fullpath(file, fullpath))
+ return FALSE;
+ }
+ else
+ {
+ free(fullpath);
+ return FALSE;
+ }
+
+#ifdef _WIN32
+ drive_file_init(file);
+#endif
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
+ const WCHAR* path, UINT32 PathWCharLength, wStream* output)
+{
+ size_t length = 0;
+ WCHAR* ent_path = NULL;
+
+ if (!file || !path || !output)
+ return FALSE;
+
+ if (InitialQuery != 0)
+ {
+ /* release search handle */
+ if (file->find_handle != INVALID_HANDLE_VALUE)
+ FindClose(file->find_handle);
+
+ ent_path = drive_file_combine_fullpath(file->basepath, path, PathWCharLength);
+ /* open new search handle and retrieve the first entry */
+ file->find_handle = FindFirstFileW(ent_path, &file->find_data);
+ free(ent_path);
+
+ if (file->find_handle == INVALID_HANDLE_VALUE)
+ goto out_fail;
+ }
+ else if (!FindNextFileW(file->find_handle, &file->find_data))
+ goto out_fail;
+
+ length = _wcslen(file->find_data.cFileName) * 2;
+
+ switch (FsInformationClass)
+ {
+ case FileDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232097.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 64 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 64)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(64 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileFullDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232068.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 68 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 68)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(68 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write_UINT32(output, 0); /* EaSize */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileBothDirectoryInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232095.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 93 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 93)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(93 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(
+ output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output,
+ file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */
+ Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write_UINT32(output, 0); /* EaSize */
+ Stream_Write_UINT8(output, 0); /* ShortNameLength */
+ /* Reserved(1), MUST NOT be added! */
+ Stream_Zero(output, 24); /* ShortName */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ case FileNamesInformation:
+
+ /* http://msdn.microsoft.com/en-us/library/cc232077.aspx */
+ if (!Stream_EnsureRemainingCapacity(output, 4 + 12 + length))
+ goto out_fail;
+
+ if (length > UINT32_MAX - 12)
+ goto out_fail;
+
+ Stream_Write_UINT32(output, (UINT32)(12 + length)); /* Length */
+ Stream_Write_UINT32(output, 0); /* NextEntryOffset */
+ Stream_Write_UINT32(output, 0); /* FileIndex */
+ Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */
+ Stream_Write(output, file->find_data.cFileName, length);
+ break;
+
+ default:
+ WLog_ERR(TAG, "unhandled FsInformationClass %" PRIu32, FsInformationClass);
+ /* Unhandled FsInformationClass */
+ goto out_fail;
+ }
+
+ return TRUE;
+out_fail:
+ Stream_Write_UINT32(output, 0); /* Length */
+ Stream_Write_UINT8(output, 0); /* Padding */
+ return FALSE;
+}
diff --git a/channels/drive/client/drive_file.h b/channels/drive/client/drive_file.h
new file mode 100644
index 0000000..761295b
--- /dev/null
+++ b/channels/drive/client/drive_file.h
@@ -0,0 +1,67 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2012 Gerald Richter
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 Inuvika Inc.
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * 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.
+ */
+
+#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
+#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
+
+#include <winpr/stream.h>
+#include <winpr/file.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("drive.client")
+
+typedef struct
+{
+ UINT32 id;
+ BOOL is_dir;
+ HANDLE file_handle;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ const WCHAR* basepath;
+ WCHAR* fullpath;
+ BOOL delete_pending;
+ UINT32 FileAttributes;
+ UINT32 SharedAccess;
+ UINT32 DesiredAccess;
+ UINT32 CreateDisposition;
+ UINT32 CreateOptions;
+} DRIVE_FILE;
+
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
+ UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
+ UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess);
+BOOL drive_file_free(DRIVE_FILE* file);
+
+BOOL drive_file_open(DRIVE_FILE* file);
+BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset);
+BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length);
+BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length);
+BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output);
+BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length,
+ wStream* input);
+BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
+ const WCHAR* path, UINT32 PathWCharLength, wStream* output);
+
+#endif /* FREERDP_CHANNEL_DRIVE_FILE_H */
diff --git a/channels/drive/client/drive_main.c b/channels/drive/client/drive_main.c
new file mode 100644
index 0000000..0fdc2e0
--- /dev/null
+++ b/channels/drive/client/drive_main.c
@@ -0,0 +1,1120 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * File System Virtual Channel
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
+ *
+ * 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 <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/environment.h>
+#include <winpr/interlocked.h>
+#include <winpr/collections.h>
+#include <winpr/shell.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/rdpdr.h>
+
+#include "drive_file.h"
+
+typedef struct
+{
+ DEVICE device;
+
+ WCHAR* path;
+ BOOL automount;
+ UINT32 PathLength;
+ wListDictionary* files;
+
+ HANDLE thread;
+ wMessageQueue* IrpQueue;
+
+ DEVMAN* devman;
+
+ rdpContext* rdpcontext;
+} DRIVE_DEVICE;
+
+static DWORD drive_map_windows_err(DWORD fs_errno)
+{
+ DWORD rc = 0;
+
+ /* try to return NTSTATUS version of error code */
+
+ switch (fs_errno)
+ {
+ case STATUS_SUCCESS:
+ rc = STATUS_SUCCESS;
+ break;
+
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ rc = STATUS_ACCESS_DENIED;
+ break;
+
+ case ERROR_FILE_NOT_FOUND:
+ rc = STATUS_NO_SUCH_FILE;
+ break;
+
+ case ERROR_BUSY_DRIVE:
+ rc = STATUS_DEVICE_BUSY;
+ break;
+
+ case ERROR_INVALID_DRIVE:
+ rc = STATUS_NO_SUCH_DEVICE;
+ break;
+
+ case ERROR_NOT_READY:
+ rc = STATUS_NO_SUCH_DEVICE;
+ break;
+
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ rc = STATUS_OBJECT_NAME_COLLISION;
+ break;
+
+ case ERROR_INVALID_NAME:
+ rc = STATUS_NO_SUCH_FILE;
+ break;
+
+ case ERROR_INVALID_HANDLE:
+ rc = STATUS_INVALID_HANDLE;
+ break;
+
+ case ERROR_NO_MORE_FILES:
+ rc = STATUS_NO_MORE_FILES;
+ break;
+
+ case ERROR_DIRECTORY:
+ rc = STATUS_NOT_A_DIRECTORY;
+ break;
+
+ case ERROR_PATH_NOT_FOUND:
+ rc = STATUS_OBJECT_PATH_NOT_FOUND;
+ break;
+
+ default:
+ rc = STATUS_UNSUCCESSFUL;
+ WLog_ERR(TAG, "Error code not found: %" PRIu32 "", fs_errno);
+ break;
+ }
+
+ return rc;
+}
+
+static DRIVE_FILE* drive_get_file_by_id(DRIVE_DEVICE* drive, UINT32 id)
+{
+ DRIVE_FILE* file = NULL;
+ void* key = (void*)(size_t)id;
+
+ if (!drive)
+ return NULL;
+
+ file = (DRIVE_FILE*)ListDictionary_GetItemValue(drive->files, key);
+ return file;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FileId = 0;
+ DRIVE_FILE* file = NULL;
+ BYTE Information = 0;
+ UINT32 FileAttributes = 0;
+ UINT32 SharedAccess = 0;
+ UINT32 DesiredAccess = 0;
+ UINT32 CreateDisposition = 0;
+ UINT32 CreateOptions = 0;
+ UINT32 PathLength = 0;
+ UINT64 allocationSize = 0;
+ const WCHAR* path = NULL;
+
+ if (!drive || !irp || !irp->devman || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 6 * 4 + 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, DesiredAccess);
+ Stream_Read_UINT64(irp->input, allocationSize);
+ Stream_Read_UINT32(irp->input, FileAttributes);
+ Stream_Read_UINT32(irp->input, SharedAccess);
+ Stream_Read_UINT32(irp->input, CreateDisposition);
+ Stream_Read_UINT32(irp->input, CreateOptions);
+ Stream_Read_UINT32(irp->input, PathLength);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength))
+ return ERROR_INVALID_DATA;
+
+ path = Stream_ConstPointer(irp->input);
+ FileId = irp->devman->id_sequence++;
+ file = drive_file_new(drive->path, path, PathLength / sizeof(WCHAR), FileId, DesiredAccess,
+ CreateDisposition, CreateOptions, FileAttributes, SharedAccess);
+
+ if (!file)
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ FileId = 0;
+ Information = 0;
+ }
+ else
+ {
+ void* key = (void*)(size_t)file->id;
+
+ if (!ListDictionary_Add(drive->files, key, file))
+ {
+ WLog_ERR(TAG, "ListDictionary_Add failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ switch (CreateDisposition)
+ {
+ case FILE_SUPERSEDE:
+ case FILE_OPEN:
+ case FILE_CREATE:
+ case FILE_OVERWRITE:
+ Information = FILE_SUPERSEDED;
+ break;
+
+ case FILE_OPEN_IF:
+ Information = FILE_OPENED;
+ break;
+
+ case FILE_OVERWRITE_IF:
+ Information = FILE_OVERWRITTEN;
+ break;
+
+ default:
+ Information = 0;
+ break;
+ }
+ }
+
+ Stream_Write_UINT32(irp->output, FileId);
+ Stream_Write_UINT8(irp->output, Information);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp)
+{
+ void* key = NULL;
+ DRIVE_FILE* file = NULL;
+
+ if (!drive || !irp || !irp->Complete || !irp->output)
+ return ERROR_INVALID_PARAMETER;
+
+ file = drive_get_file_by_id(drive, irp->FileId);
+ key = (void*)(size_t)irp->FileId;
+
+ if (!file)
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ else
+ {
+ ListDictionary_Take(drive->files, key);
+
+ if (drive_file_free(file))
+ irp->IoStatus = STATUS_SUCCESS;
+ else
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ Stream_Zero(irp->output, 5); /* Padding(5) */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+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)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ }
+ else if (!drive_file_seek(file, Offset))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(irp->output, Length + 4))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+ else if (Length == 0)
+ Stream_Write_UINT32(irp->output, 0);
+ else
+ {
+ BYTE* buffer = Stream_PointerAs(irp->output, BYTE) + sizeof(UINT32);
+
+ if (!drive_file_read(file, buffer, &Length))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Stream_Write_UINT32(irp->output, 0);
+ }
+ else
+ {
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Seek(irp->output, Length);
+ }
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 Length = 0;
+ UINT64 Offset = 0;
+
+ if (!drive || !irp || !irp->input || !irp->output || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Read_UINT64(irp->input, Offset);
+ Stream_Seek(irp->input, 20); /* Padding */
+ const void* ptr = Stream_ConstPointer(irp->input);
+ if (!Stream_SafeSeek(irp->input, Length))
+ return ERROR_INVALID_DATA;
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Length = 0;
+ }
+ else if (!drive_file_seek(file, Offset))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+ else if (!drive_file_write(file, ptr, Length))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ Length = 0;
+ }
+
+ Stream_Write_UINT32(irp->output, Length);
+ Stream_Write_UINT8(irp->output, 0); /* Padding */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ }
+ else if (!drive_file_query_information(file, FsInformationClass, irp->output))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_set_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ DRIVE_FILE* file = NULL;
+ UINT32 FsInformationClass = 0;
+ UINT32 Length = 0;
+
+ if (!drive || !irp || !irp->Complete || !irp->input || !irp->output)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Read_UINT32(irp->input, Length);
+ Stream_Seek(irp->input, 24); /* Padding */
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (!file)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ }
+ else if (!drive_file_set_information(file, FsInformationClass, Length, irp->input))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ if (file && file->is_dir && !PathIsDirectoryEmptyW(file->fullpath))
+ irp->IoStatus = STATUS_DIRECTORY_NOT_EMPTY;
+
+ Stream_Write_UINT32(irp->output, Length);
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_volume_information(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FsInformationClass = 0;
+ wStream* output = NULL;
+ DWORD lpSectorsPerCluster = 0;
+ DWORD lpBytesPerSector = 0;
+ DWORD lpNumberOfFreeClusters = 0;
+ DWORD lpTotalNumberOfClusters = 0;
+ WIN32_FILE_ATTRIBUTE_DATA wfad = { 0 };
+ WCHAR LabelBuffer[32] = { 0 };
+
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ output = irp->output;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ GetDiskFreeSpaceW(drive->path, &lpSectorsPerCluster, &lpBytesPerSector, &lpNumberOfFreeClusters,
+ &lpTotalNumberOfClusters);
+
+ switch (FsInformationClass)
+ {
+ case FileFsVolumeInformation:
+ {
+ /* http://msdn.microsoft.com/en-us/library/cc232108.aspx */
+ const WCHAR* volumeLabel =
+ InitializeConstWCharFromUtf8("FREERDP", LabelBuffer, ARRAYSIZE(LabelBuffer));
+ const size_t volumeLabelLen = (_wcslen(volumeLabel) + 1) * sizeof(WCHAR);
+ const size_t length = 17ul + volumeLabelLen;
+
+ Stream_Write_UINT32(output, length); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ GetFileAttributesExW(drive->path, GetFileExInfoStandard, &wfad);
+ Stream_Write_UINT32(output, wfad.ftCreationTime.dwLowDateTime); /* VolumeCreationTime */
+ Stream_Write_UINT32(output,
+ wfad.ftCreationTime.dwHighDateTime); /* VolumeCreationTime */
+ Stream_Write_UINT32(output, lpNumberOfFreeClusters & 0xffff); /* VolumeSerialNumber */
+ Stream_Write_UINT32(output, volumeLabelLen); /* VolumeLabelLength */
+ Stream_Write_UINT8(output, 0); /* SupportsObjects */
+ /* Reserved(1), MUST NOT be added! */
+ Stream_Write(output, volumeLabel, volumeLabelLen); /* VolumeLabel (Unicode) */
+ }
+ break;
+
+ case FileFsSizeInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232107.aspx */
+ Stream_Write_UINT32(output, 24); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 24))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */
+ Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */
+ Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */
+ Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */
+ break;
+
+ case FileFsAttributeInformation:
+ {
+ /* http://msdn.microsoft.com/en-us/library/cc232101.aspx */
+ const WCHAR* diskType =
+ InitializeConstWCharFromUtf8("FAT32", LabelBuffer, ARRAYSIZE(LabelBuffer));
+ const size_t diskTypeLen = (wcslen(diskType) + 1) * sizeof(WCHAR);
+ const size_t length = 12ul + diskTypeLen;
+ Stream_Write_UINT32(output, length); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, length))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(output, FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES |
+ FILE_UNICODE_ON_DISK); /* FileSystemAttributes */
+ Stream_Write_UINT32(output, MAX_PATH); /* MaximumComponentNameLength */
+ Stream_Write_UINT32(output, diskTypeLen); /* FileSystemNameLength */
+ Stream_Write(output, diskType, diskTypeLen); /* FileSystemName (Unicode) */
+ }
+ break;
+
+ case FileFsFullSizeInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232104.aspx */
+ Stream_Write_UINT32(output, 32); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 32))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */
+ Stream_Write_UINT64(output,
+ lpNumberOfFreeClusters); /* CallerAvailableAllocationUnits */
+ Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */
+ Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */
+ Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */
+ break;
+
+ case FileFsDeviceInformation:
+ /* http://msdn.microsoft.com/en-us/library/cc232109.aspx */
+ Stream_Write_UINT32(output, 8); /* Length */
+
+ if (!Stream_EnsureRemainingCapacity(output, 8))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Write_UINT32(output, FILE_DEVICE_DISK); /* DeviceType */
+ Stream_Write_UINT32(output, 0); /* Characteristics */
+ break;
+
+ default:
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Stream_Write_UINT32(output, 0); /* Length */
+ break;
+ }
+
+ return irp->Complete(irp);
+}
+
+/* http://msdn.microsoft.com/en-us/library/cc241518.aspx */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_silent_ignore(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->output || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp)
+{
+ const WCHAR* path = NULL;
+ DRIVE_FILE* file = NULL;
+ BYTE InitialQuery = 0;
+ UINT32 PathLength = 0;
+ UINT32 FsInformationClass = 0;
+
+ if (!drive || !irp || !irp->Complete)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(irp->input, FsInformationClass);
+ Stream_Read_UINT8(irp->input, InitialQuery);
+ Stream_Read_UINT32(irp->input, PathLength);
+ Stream_Seek(irp->input, 23); /* Padding */
+ path = Stream_ConstPointer(irp->input);
+ if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength))
+ return ERROR_INVALID_DATA;
+
+ file = drive_get_file_by_id(drive, irp->FileId);
+
+ if (file == NULL)
+ {
+ irp->IoStatus = STATUS_UNSUCCESSFUL;
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ }
+ else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path,
+ PathLength / sizeof(WCHAR), irp->output))
+ {
+ irp->IoStatus = drive_map_windows_err(GetLastError());
+ }
+
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_directory_control(DRIVE_DEVICE* drive, IRP* irp)
+{
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ switch (irp->MinorFunction)
+ {
+ case IRP_MN_QUERY_DIRECTORY:
+ return drive_process_irp_query_directory(drive, irp);
+
+ case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* TODO */
+ return irp->Discard(irp);
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ Stream_Write_UINT32(irp->output, 0); /* Length */
+ return irp->Complete(irp);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp_device_control(DRIVE_DEVICE* drive, IRP* irp)
+{
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */
+ return irp->Complete(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_process_irp(DRIVE_DEVICE* drive, IRP* irp)
+{
+ UINT error = 0;
+
+ if (!drive || !irp)
+ return ERROR_INVALID_PARAMETER;
+
+ irp->IoStatus = STATUS_SUCCESS;
+
+ switch (irp->MajorFunction)
+ {
+ case IRP_MJ_CREATE:
+ error = drive_process_irp_create(drive, irp);
+ break;
+
+ case IRP_MJ_CLOSE:
+ error = drive_process_irp_close(drive, irp);
+ break;
+
+ case IRP_MJ_READ:
+ error = drive_process_irp_read(drive, irp);
+ break;
+
+ case IRP_MJ_WRITE:
+ error = drive_process_irp_write(drive, irp);
+ break;
+
+ case IRP_MJ_QUERY_INFORMATION:
+ error = drive_process_irp_query_information(drive, irp);
+ break;
+
+ case IRP_MJ_SET_INFORMATION:
+ error = drive_process_irp_set_information(drive, irp);
+ break;
+
+ case IRP_MJ_QUERY_VOLUME_INFORMATION:
+ error = drive_process_irp_query_volume_information(drive, irp);
+ break;
+
+ case IRP_MJ_LOCK_CONTROL:
+ error = drive_process_irp_silent_ignore(drive, irp);
+ break;
+
+ case IRP_MJ_DIRECTORY_CONTROL:
+ error = drive_process_irp_directory_control(drive, irp);
+ break;
+
+ case IRP_MJ_DEVICE_CONTROL:
+ error = drive_process_irp_device_control(drive, irp);
+ break;
+
+ default:
+ irp->IoStatus = STATUS_NOT_SUPPORTED;
+ error = irp->Complete(irp);
+ break;
+ }
+
+ return error;
+}
+
+static DWORD WINAPI drive_thread_func(LPVOID arg)
+{
+ IRP* irp = NULL;
+ wMessage message = { 0 };
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)arg;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ while (1)
+ {
+ if (!MessageQueue_Wait(drive->IrpQueue))
+ {
+ WLog_ERR(TAG, "MessageQueue_Wait failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE))
+ {
+ WLog_ERR(TAG, "MessageQueue_Peek failed!");
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+
+ if (message.id == WMQ_QUIT)
+ break;
+
+ irp = (IRP*)message.wParam;
+
+ if (irp)
+ {
+ if ((error = drive_process_irp(drive, irp)))
+ {
+ WLog_ERR(TAG, "drive_process_irp failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+
+fail:
+
+ if (error && drive && drive->rdpcontext)
+ setChannelError(drive->rdpcontext, error, "drive_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_irp_request(DEVICE* device, IRP* irp)
+{
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ if (!MessageQueue_Post(drive->IrpQueue, NULL, 0, (void*)irp, NULL))
+ {
+ WLog_ERR(TAG, "MessageQueue_Post failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT drive_free_int(DRIVE_DEVICE* drive)
+{
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ CloseHandle(drive->thread);
+ ListDictionary_Free(drive->files);
+ MessageQueue_Free(drive->IrpQueue);
+ Stream_Free(drive->device.data, TRUE);
+ free(drive->path);
+ free(drive);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_free(DEVICE* device)
+{
+ DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!drive)
+ return ERROR_INVALID_PARAMETER;
+
+ if (MessageQueue_PostQuit(drive->IrpQueue, 0) &&
+ (WaitForSingleObject(drive->thread, INFINITE) == WAIT_FAILED))
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ return drive_free_int(drive);
+}
+
+/**
+ * Helper function used for freeing list dictionary value object
+ */
+static void drive_file_objfree(void* obj)
+{
+ drive_file_free((DRIVE_FILE*)obj);
+}
+
+static void drive_message_free(void* obj)
+{
+ wMessage* msg = obj;
+ if (!msg)
+ return;
+ if (msg->id != 0)
+ return;
+
+ IRP* irp = (IRP*)msg->wParam;
+ if (!irp)
+ return;
+ WINPR_ASSERT(irp->Discard);
+ irp->Discard(irp);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, const char* name,
+ const char* path, BOOL automount)
+{
+ size_t length = 0;
+ DRIVE_DEVICE* drive = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ if (!pEntryPoints || !name || !path)
+ {
+ WLog_ERR(TAG, "[%s] Invalid parameters: pEntryPoints=%p, name=%p, path=%p", pEntryPoints,
+ name, path);
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ if (name[0] && path[0])
+ {
+ size_t pathLength = strnlen(path, MAX_PATH);
+ drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE));
+
+ if (!drive)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ drive->device.type = RDPDR_DTYP_FILESYSTEM;
+ drive->device.IRPRequest = drive_irp_request;
+ drive->device.Free = drive_free;
+ drive->rdpcontext = pEntryPoints->rdpcontext;
+ drive->automount = automount;
+ length = strlen(name);
+ drive->device.data = Stream_New(NULL, length + 1);
+
+ if (!drive->device.data)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ for (size_t i = 0; i < length; i++)
+ {
+ /* Filter 2.2.1.3 Device Announce Header (DEVICE_ANNOUNCE) forbidden symbols */
+ switch (name[i])
+ {
+ case ':':
+ case '<':
+ case '>':
+ case '\"':
+ case '/':
+ case '\\':
+ case '|':
+ case ' ':
+ Stream_Write_UINT8(drive->device.data, '_');
+ break;
+ default:
+ Stream_Write_UINT8(drive->device.data, (BYTE)name[i]);
+ break;
+ }
+ }
+ Stream_Write_UINT8(drive->device.data, '\0');
+
+ drive->device.name = (const char*)Stream_Buffer(drive->device.data);
+ if (!drive->device.name)
+ goto out_error;
+
+ if ((pathLength > 1) && (path[pathLength - 1] == '/'))
+ pathLength--;
+
+ drive->path = ConvertUtf8NToWCharAlloc(path, pathLength, NULL);
+ if (!drive->path)
+ {
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ drive->files = ListDictionary_New(TRUE);
+
+ if (!drive->files)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ ListDictionary_ValueObject(drive->files)->fnObjectFree = drive_file_objfree;
+ drive->IrpQueue = MessageQueue_New(NULL);
+
+ if (!drive->IrpQueue)
+ {
+ WLog_ERR(TAG, "ListDictionary_New failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out_error;
+ }
+
+ wObject* obj = MessageQueue_Object(drive->IrpQueue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = drive_message_free;
+
+ if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)drive)))
+ {
+ WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error);
+ goto out_error;
+ }
+
+ if (!(drive->thread =
+ CreateThread(NULL, 0, drive_thread_func, drive, CREATE_SUSPENDED, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ goto out_error;
+ }
+
+ ResumeThread(drive->thread);
+ }
+
+ return CHANNEL_RC_OK;
+out_error:
+ drive_free_int(drive);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT drive_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
+{
+ RDPDR_DRIVE* drive = NULL;
+ UINT error = 0;
+#ifdef WIN32
+ int len;
+ char devlist[512], buf[512];
+ char* bufdup;
+ char* devdup;
+#endif
+
+ WINPR_ASSERT(pEntryPoints);
+
+ drive = (RDPDR_DRIVE*)pEntryPoints->device;
+ WINPR_ASSERT(drive);
+
+#ifndef WIN32
+ if (strcmp(drive->Path, "*") == 0)
+ {
+ /* all drives */
+ free(drive->Path);
+ drive->Path = _strdup("/");
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ else if (strcmp(drive->Path, "%") == 0)
+ {
+ free(drive->Path);
+ drive->Path = GetKnownPath(KNOWN_PATH_HOME);
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ error =
+ drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, drive->automount);
+#else
+ /* Special case: path[0] == '*' -> export all drives */
+ /* Special case: path[0] == '%' -> user home dir */
+ if (strcmp(drive->Path, "%") == 0)
+ {
+ GetEnvironmentVariableA("USERPROFILE", buf, sizeof(buf));
+ PathCchAddBackslashA(buf, sizeof(buf));
+ free(drive->Path);
+ drive->Path = _strdup(buf);
+
+ if (!drive->Path)
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path,
+ drive->automount);
+ }
+ else if (strcmp(drive->Path, "*") == 0)
+ {
+ /* Enumerate all devices: */
+ GetLogicalDriveStringsA(sizeof(devlist) - 1, devlist);
+
+ for (size_t i = 0;; i++)
+ {
+ char* dev = &devlist[i * 4];
+ if (!*dev)
+ break;
+ if (*dev > 'B')
+ {
+ /* Suppress disk drives A and B to avoid pesty messages */
+ len = sprintf_s(buf, sizeof(buf) - 4, "%s", drive->device.Name);
+ buf[len] = '_';
+ buf[len + 1] = dev[0];
+ buf[len + 2] = 0;
+ buf[len + 3] = 0;
+
+ if (!(bufdup = _strdup(buf)))
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (!(devdup = _strdup(dev)))
+ {
+ WLog_ERR(TAG, "_strdup failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if ((error = drive_register_drive_path(pEntryPoints, bufdup, devdup, TRUE)))
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path,
+ drive->automount);
+ }
+
+#endif
+ return error;
+}