3553 lines
105 KiB
C
3553 lines
105 KiB
C
/*
|
|
* Copyright 2004-2020 Sandboxie Holdings, LLC
|
|
* Copyright 2020-2023 David Xanatos, xanasoft.com
|
|
*
|
|
* 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 3 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File (Dir)
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
#include "common/pool.h"
|
|
#include "common/map.h"
|
|
#include "common/pattern.h"
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Structures and Types
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
typedef struct _FILE_MERGE_CACHE_FILE {
|
|
|
|
LIST_ELEM list_elem;
|
|
ULONG info_len;
|
|
UNICODE_STRING name_uni;
|
|
FILE_ID_BOTH_DIR_INFORMATION info;
|
|
// ... space for filename immediately following
|
|
|
|
} FILE_MERGE_CACHE_FILE;
|
|
|
|
|
|
typedef struct _FILE_MERGE_FILE {
|
|
|
|
HANDLE handle;
|
|
FILE_SNAPSHOT* snapshot;
|
|
FILE_ID_BOTH_DIR_INFORMATION *info;
|
|
ULONG info_len;
|
|
WCHAR *name;
|
|
ULONG name_max_len;
|
|
UNICODE_STRING name_uni;
|
|
BOOLEAN have_entry;
|
|
BOOLEAN saved_have_entry;
|
|
BOOLEAN more_files;
|
|
BOOLEAN RestartScan;
|
|
BOOLEAN no_file_ids;
|
|
POOL *cache_pool;
|
|
LIST cache_list;
|
|
ULONG scram_key;
|
|
|
|
} FILE_MERGE_FILE;
|
|
|
|
|
|
typedef struct _FILE_MERGE {
|
|
|
|
LIST_ELEM list_elem;
|
|
|
|
HANDLE handle;
|
|
BOOLEAN cant_merge;
|
|
BOOLEAN first_request;
|
|
|
|
UNICODE_STRING file_mask;
|
|
FILE_MERGE_FILE* files; // copy file, snapshot_1 file, snapshot_2 file, ..., true file
|
|
ULONG files_count;
|
|
FILE_MERGE_FILE* true_ptr;
|
|
|
|
ULONG name_len; // in bytes, excluding NULL
|
|
WCHAR name[0];
|
|
|
|
} FILE_MERGE;
|
|
|
|
|
|
typedef struct _FILE_APC {
|
|
|
|
PIO_APC_ROUTINE routine;
|
|
void *context;
|
|
IO_STATUS_BLOCK *IoStatusBlock;
|
|
|
|
} FILE_APC;
|
|
|
|
|
|
typedef struct _FILE_FS_DEVICE_INFORMATION {
|
|
DEVICE_TYPE DeviceType;
|
|
ULONG Characteristics;
|
|
} FILE_FS_DEVICE_INFORMATION, *PFILE_FS_DEVICE_INFORMATION;
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Functions
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
static void File_InitRecoverFolders(void);
|
|
|
|
static NTSTATUS File_NtQueryDirectoryFile(
|
|
HANDLE FileHandle,
|
|
HANDLE Event,
|
|
PIO_APC_ROUTINE ApcRoutine,
|
|
void *ApcContext,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry,
|
|
UNICODE_STRING *FileMask,
|
|
BOOLEAN RestartScan);
|
|
|
|
static NTSTATUS File_NtQueryDirectoryFileEx(
|
|
HANDLE FileHandle,
|
|
HANDLE Event,
|
|
PIO_APC_ROUTINE ApcRoutine,
|
|
void *ApcContext,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry,
|
|
UNICODE_STRING *FileMask);
|
|
|
|
static void File_ApcStub(ULONG_PTR context);
|
|
|
|
static NTSTATUS File_Merge(
|
|
HANDLE FileHandle, WCHAR *TruePath, WCHAR *CopyPath,
|
|
BOOLEAN RestartScan, WCHAR **FileMask, FILE_MERGE **out_merge);
|
|
|
|
static NTSTATUS File_OpenForMerge(
|
|
FILE_MERGE *merge, WCHAR *TruePath, WCHAR *CopyPath);
|
|
|
|
static NTSTATUS File_MergeCache(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask, BOOLEAN ForceCache);
|
|
|
|
static NTSTATUS File_MergeCacheWin2000(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask,
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_area, ULONG info_area_len);
|
|
|
|
static NTSTATUS File_MergeDummy(
|
|
WCHAR *TruePath, FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask);
|
|
|
|
static void File_MergeFree(FILE_MERGE *merge);
|
|
|
|
static NTSTATUS File_GetMergedInformation(
|
|
FILE_MERGE *merge, WCHAR *TruePath, WCHAR *CopyPath,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry);
|
|
|
|
static NTSTATUS File_GetFullInformation(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask, BOOLEAN XorFileId);
|
|
|
|
static WCHAR *File_CopyFixedInformation(
|
|
FILE_ID_BOTH_DIR_INFORMATION *source, void *target,
|
|
FILE_INFORMATION_CLASS FileInformationClass);
|
|
|
|
static NTSTATUS File_NtClose(HANDLE FileHandle);
|
|
|
|
static NTSTATUS File_DeleteDirectory(
|
|
const WCHAR *FilePath, BOOLEAN JustCheck);
|
|
|
|
static NTSTATUS File_MarkChildrenDeleted(const WCHAR *ParentTruePath);
|
|
|
|
static ULONG File_RtlGetCurrentDirectory_U(ULONG buf_len, WCHAR *buf_ptr);
|
|
|
|
static NTSTATUS File_RtlSetCurrentDirectory_U(UNICODE_STRING *PathName);
|
|
|
|
static ULONG File_CallRtlGetFullPathName(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr);
|
|
|
|
static ULONG File_RtlGetFullPathName_U(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr);
|
|
|
|
static NTSTATUS File_RtlGetFullPathName_UEx(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr,
|
|
ULONG *ret_len_ptr);
|
|
|
|
static NTSTATUS File_NtQueryVolumeInformationFile(
|
|
HANDLE FileHandle,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
PVOID FsInformation,
|
|
ULONG Length,
|
|
ULONG FsInformationClass);
|
|
|
|
NTSTATUS File_NtCloseImpl(HANDLE FileHandle);
|
|
|
|
VOID File_NtCloseDir(HANDLE FileHandle, void* CloseParams);
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Variables
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
static CRITICAL_SECTION File_CurDir_CritSec;
|
|
static WCHAR *File_CurDir_LastInput = NULL;
|
|
static WCHAR *File_CurDir_LastOutput = NULL;
|
|
|
|
static LIST File_DirHandles;
|
|
static CRITICAL_SECTION File_DirHandles_CritSec;
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtQueryDirectoryFileEx
|
|
// Function added to Windows 10 RS4 build 17035
|
|
// Takes one less argument than the non Ex version: The last argument "RestartScan" has been removed.
|
|
// At the dispatch level "RestartScan" is a "don't care" so calling the non-Ex hook is safe.
|
|
//---------------------------------------------------------------------------
|
|
|
|
_FX NTSTATUS File_NtQueryDirectoryFileEx(
|
|
HANDLE FileHandle,
|
|
HANDLE Event,
|
|
PIO_APC_ROUTINE ApcRoutine,
|
|
void *ApcContext,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry,
|
|
UNICODE_STRING *FileMask)
|
|
{
|
|
return File_NtQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, FileInformation, Length, FileInformationClass, ReturnSingleEntry, FileMask, 0);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtQueryDirectoryFile
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_NtQueryDirectoryFile(
|
|
HANDLE FileHandle,
|
|
HANDLE Event,
|
|
PIO_APC_ROUTINE ApcRoutine,
|
|
void *ApcContext,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry,
|
|
UNICODE_STRING *FileMask,
|
|
BOOLEAN RestartScan)
|
|
{
|
|
ULONG LastError;
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
|
|
|
|
NTSTATUS status;
|
|
WCHAR *TruePath;
|
|
WCHAR *CopyPath;
|
|
UNICODE_STRING objname;
|
|
FILE_MERGE *merge;
|
|
WCHAR *file_mask;
|
|
ULONG FileFlags;
|
|
BOOLEAN merge_lock;
|
|
|
|
//
|
|
// not a recursive invocation, handle the call here
|
|
//
|
|
|
|
merge_lock = FALSE;
|
|
file_mask = NULL;
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
__try {
|
|
|
|
//
|
|
// get the paths for this FileHandle, and try to find the merge
|
|
// entry for them. if there is only one part of the true/copy pair,
|
|
// then we will get STATUS_BAD_INITIAL_PC, and pass the request
|
|
// to the system
|
|
//
|
|
|
|
RtlInitUnicodeString(&objname, L"");
|
|
|
|
status = File_GetName(
|
|
FileHandle, &objname, &TruePath, &CopyPath, &FileFlags);
|
|
|
|
//
|
|
// if the caller is trying to query the root directory of a device,
|
|
// we get an error return value, and have to add the trailing backslash
|
|
//
|
|
|
|
if (status == STATUS_BAD_INITIAL_PC && CopyPath) {
|
|
|
|
WCHAR *ptr = TruePath + wcslen(TruePath);
|
|
ptr[0] = L'\\';
|
|
ptr[1] = L'\0';
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// check if the path is open or closed
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
ULONG mp_flags = File_MatchPath(TruePath, &FileFlags);
|
|
|
|
BOOLEAN use_rule_specificity = (Dll_ProcessFlags & SBIE_FLAG_RULE_SPECIFICITY) != 0;
|
|
|
|
if (PATH_IS_CLOSED(mp_flags))
|
|
status = STATUS_ACCESS_DENIED;
|
|
|
|
else if (PATH_IS_WRITE(mp_flags) && !use_rule_specificity)
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
|
|
else if (PATH_IS_OPEN(mp_flags))
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
}
|
|
|
|
//
|
|
// if we have STATUS_BAD_INITIAL_PC at this point, the access is either
|
|
// to some non-disk device, or to an open path, so the OS must handle it.
|
|
// any other non-zero status is simply returned as error.
|
|
//
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
|
|
if (status == STATUS_BAD_INITIAL_PC) {
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
|
|
FileInformation, Length, FileInformationClass,
|
|
ReturnSingleEntry, FileMask, RestartScan);
|
|
}
|
|
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// capture FileMask
|
|
//
|
|
|
|
if (FileMask && FileMask->Length && FileMask->Buffer) {
|
|
|
|
ULONG FileMask_len;
|
|
|
|
FileMask_len = FileMask->Length & ~1;
|
|
file_mask = Dll_Alloc(FileMask_len + sizeof(WCHAR));
|
|
memcpy(file_mask, FileMask->Buffer, FileMask_len);
|
|
file_mask[FileMask_len / sizeof(WCHAR)] = L'\0';
|
|
}
|
|
|
|
//
|
|
// create (or find) a FILE_MERGE structure to serve the request
|
|
//
|
|
|
|
if (! ReturnSingleEntry)
|
|
RestartScan = FALSE;
|
|
|
|
status = File_Merge(
|
|
FileHandle, TruePath, CopyPath, RestartScan, &file_mask, &merge);
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
|
|
if (status == STATUS_BAD_INITIAL_PC) {
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
|
|
FileInformation, Length, FileInformationClass,
|
|
ReturnSingleEntry, FileMask, RestartScan);
|
|
}
|
|
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// we're handling the request here, first check the minimum length
|
|
//
|
|
|
|
merge_lock = TRUE;
|
|
|
|
status = File_GetMergedInformation(
|
|
merge, TruePath, CopyPath, IoStatusBlock,
|
|
FileInformation, Length, FileInformationClass, ReturnSingleEntry);
|
|
|
|
if (merge->first_request) {
|
|
|
|
if (status == STATUS_NO_MORE_FILES)
|
|
status = STATUS_NO_SUCH_FILE;
|
|
|
|
merge->first_request = FALSE;
|
|
}
|
|
|
|
LeaveCriticalSection(&File_DirHandles_CritSec);
|
|
merge_lock = FALSE;
|
|
|
|
if (Event)
|
|
SetEvent(Event);
|
|
|
|
if (ApcRoutine) {
|
|
|
|
FILE_APC *apc = Dll_Alloc(sizeof(FILE_APC));
|
|
apc->routine = ApcRoutine;
|
|
apc->context = ApcContext;
|
|
apc->IoStatusBlock = IoStatusBlock;
|
|
QueueUserAPC(File_ApcStub, GetCurrentThread(), (ULONG_PTR)apc);
|
|
}
|
|
|
|
//
|
|
// finish
|
|
//
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
status = GetExceptionCode();
|
|
}
|
|
|
|
if (merge_lock)
|
|
LeaveCriticalSection(&File_DirHandles_CritSec);
|
|
|
|
if (file_mask)
|
|
Dll_Free(file_mask);
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
SetLastError(LastError);
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_ApcStub
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX void File_ApcStub(ULONG_PTR context)
|
|
{
|
|
FILE_APC *apc = (FILE_APC *)context;
|
|
apc->routine(apc->context, apc->IoStatusBlock, 0);
|
|
Dll_Free(apc);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_Merge
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_Merge(
|
|
HANDLE FileHandle, WCHAR *TruePath, WCHAR *CopyPath,
|
|
BOOLEAN RestartScan, WCHAR **FileMask, FILE_MERGE **out_merge)
|
|
{
|
|
static const WCHAR *_tsclient = L"\\device\\mup\\tsclient\\";
|
|
NTSTATUS status;
|
|
ULONG TruePath_len;
|
|
FILE_MERGE *merge;
|
|
|
|
//
|
|
// if we have information cached for this handle, return it
|
|
//
|
|
|
|
TruePath_len = wcslen(TruePath) * sizeof(WCHAR);
|
|
|
|
EnterCriticalSection(&File_DirHandles_CritSec);
|
|
|
|
merge = List_Head(&File_DirHandles);
|
|
while (merge) {
|
|
|
|
FILE_MERGE *next = List_Next(merge);
|
|
|
|
if (merge->handle == FileHandle) {
|
|
|
|
if ((! RestartScan) &&
|
|
merge->name_len == TruePath_len &&
|
|
_wcsicmp(merge->name, TruePath) == 0) {
|
|
|
|
//
|
|
// we found a cached entry for the same handle, and
|
|
// the same file path, so we are going to use it.
|
|
//
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
Handle_UnRegisterHandler(merge->handle, File_NtCloseDir, NULL);
|
|
List_Remove(&File_DirHandles, merge);
|
|
File_MergeFree(merge);
|
|
}
|
|
}
|
|
|
|
merge = next;
|
|
}
|
|
|
|
//
|
|
// if we don't have a merge entry, create one. it is inserted
|
|
// at the end of the list, so the loop above always gets to
|
|
// look at all existing merge entries and discard stale ones
|
|
//
|
|
|
|
if (! merge) {
|
|
|
|
merge = Dll_Alloc(sizeof(FILE_MERGE) + TruePath_len + sizeof(WCHAR));
|
|
memzero(merge, sizeof(FILE_MERGE));
|
|
|
|
merge->files = Dll_Alloc(sizeof(FILE_MERGE_FILE) * (2 + File_Snapshot_Count));
|
|
memzero(merge->files, sizeof(FILE_MERGE_FILE) * (2 + File_Snapshot_Count));
|
|
|
|
merge->handle = FileHandle;
|
|
merge->cant_merge = FALSE;
|
|
merge->first_request = TRUE;
|
|
|
|
if (*FileMask) {
|
|
RtlInitUnicodeString(&merge->file_mask, *FileMask);
|
|
*FileMask = NULL;
|
|
}
|
|
|
|
merge->name_len = TruePath_len;
|
|
memcpy(merge->name, TruePath, TruePath_len + sizeof(WCHAR));
|
|
|
|
if (TruePath_len >= wcslen(_tsclient) * sizeof(WCHAR) &&
|
|
_wcsnicmp(TruePath, _tsclient, wcslen(_tsclient)) == 0) {
|
|
//
|
|
// shares provided by Remote Desktop can't provide file IDs
|
|
//
|
|
merge->files[0].no_file_ids = TRUE;
|
|
}
|
|
|
|
List_Insert_After(&File_DirHandles, NULL, merge);
|
|
Handle_RegisterHandler(merge->handle, File_NtCloseDir, NULL, FALSE);
|
|
}
|
|
|
|
//
|
|
// open the directory for the true path
|
|
//
|
|
|
|
if (merge->cant_merge) {
|
|
|
|
//
|
|
// if cant_merge is set, then we already know that either TruePath
|
|
// or CopyPath exist, but not both, so return special status
|
|
//
|
|
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
|
|
} else if (!merge->files[0].handle) {
|
|
|
|
//
|
|
// open the true and copy directories, if we haven't already.
|
|
// we don't check for merge->true_file.handle, because it is
|
|
// a possible scenario that only merge->copy_file.handle exists
|
|
//
|
|
|
|
status = File_OpenForMerge(merge, TruePath, CopyPath);
|
|
|
|
if (status == STATUS_BAD_INITIAL_PC)
|
|
merge->cant_merge = TRUE;
|
|
|
|
} else
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// finish
|
|
//
|
|
|
|
if (! NT_SUCCESS(status))
|
|
LeaveCriticalSection(&File_DirHandles_CritSec);
|
|
|
|
*out_merge = merge;
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_OpenForMerge
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_OpenForMerge(
|
|
FILE_MERGE *merge, WCHAR *TruePath, WCHAR *CopyPath)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
UNICODE_STRING objname;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
union {
|
|
FILE_BASIC_INFORMATION basic;
|
|
} info;
|
|
ULONG len;
|
|
WCHAR *ptr;
|
|
// BOOLEAN TruePathIsRoot;
|
|
BOOLEAN TruePathDeleted = FALSE; // indicates that one of the parent snapshots deleted the true directory
|
|
WCHAR* OriginalPath = NULL;
|
|
ULONG TruePathFlags = 0;
|
|
BOOLEAN NoCopyPath = FALSE;
|
|
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
InitializeObjectAttributes(
|
|
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
//
|
|
// open the copy file
|
|
//
|
|
|
|
if (File_Delete_v2) {
|
|
|
|
//
|
|
// test if the path is deleted and find the oldest snapshot with a relocation
|
|
//
|
|
|
|
WCHAR* OldTruePath = File_ResolveTruePath(TruePath, NULL, &TruePathFlags);
|
|
if (FILE_PATH_DELETED(TruePathFlags) && !FILE_PATH_RELOCATED(TruePathFlags))
|
|
TruePathDeleted = TRUE;
|
|
else if (OldTruePath) {
|
|
|
|
OriginalPath = TruePath;
|
|
|
|
if (File_Snapshot != NULL) {
|
|
|
|
//
|
|
// note: File_ResolveTruePath returns a buffer from the TMPL_NAME_BUFFER slot,
|
|
// which is reused byFile_MakeSnapshotPath, so we need to make non reusable copy
|
|
//
|
|
|
|
TruePath = Dll_GetTlsNameBuffer(TlsData, MISC_NAME_BUFFER, (wcslen(OldTruePath) + 1) * sizeof(WCHAR));
|
|
wcscpy(TruePath, OldTruePath);
|
|
}
|
|
else
|
|
TruePath = OldTruePath;
|
|
}
|
|
}
|
|
else {
|
|
if (File_CheckDeletedParent(CopyPath)) {
|
|
status = STATUS_OBJECT_PATH_NOT_FOUND;
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
RtlInitUnicodeString(&objname, CopyPath);
|
|
|
|
status = __sys_NtCreateFile(
|
|
&merge->files[0].handle,
|
|
FILE_GENERIC_READ, // DesiredAccess
|
|
&objattrs,
|
|
&IoStatusBlock,
|
|
NULL, // AllocationSize
|
|
0, // FileAttributes
|
|
FILE_SHARE_VALID_FLAGS, // ShareAccess
|
|
FILE_OPEN, // CreateDisposition
|
|
FILE_SYNCHRONOUS_IO_NONALERT, // CreateOptions
|
|
NULL, // EaBuffer
|
|
0); // EaLength
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// if the copy file exists, check if it is marked as deleted,
|
|
// and if so, close it and pretend it doesn't exist; otherwise
|
|
// make sure it is a directory file
|
|
//
|
|
|
|
status = __sys_NtQueryInformationFile(
|
|
merge->files[0].handle, &IoStatusBlock, &info,
|
|
sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
// if (!File_Delete_v2 &&
|
|
if (IS_DELETE_MARK(&info.basic.CreationTime)) {
|
|
|
|
status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
}
|
|
else if ((info.basic.FileAttributes &
|
|
FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
__sys_NtClose(merge->files[0].handle);
|
|
merge->files[0].handle = NULL;
|
|
goto finish;
|
|
}
|
|
|
|
//
|
|
// copy file passed all checks; indicate it is ready for use
|
|
//
|
|
|
|
merge->files[0].more_files = TRUE;
|
|
merge->files[0].RestartScan = TRUE;
|
|
merge->files_count++;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// if there is no copy file, we don't need to merge anything,
|
|
// and can let the system work directly on the true file
|
|
//
|
|
|
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
|
|
status == STATUS_OBJECT_PATH_NOT_FOUND ||
|
|
status == STATUS_ACCESS_DENIED) {
|
|
|
|
NoCopyPath = TRUE;
|
|
}
|
|
else
|
|
goto finish;
|
|
}
|
|
|
|
//
|
|
// Now open the parent snapshots if present, and it's aprent and so on....
|
|
//
|
|
|
|
if (File_Snapshot != NULL)
|
|
{
|
|
WCHAR* TempPath = TruePath;
|
|
|
|
for (FILE_SNAPSHOT* Cur_Snapshot = File_Snapshot; Cur_Snapshot != NULL; Cur_Snapshot = Cur_Snapshot->Parent)
|
|
{
|
|
WCHAR* TmplName = File_MakeSnapshotPath(Cur_Snapshot, CopyPath);
|
|
if (!TmplName)
|
|
break;
|
|
|
|
RtlInitUnicodeString(&objname, TmplName);
|
|
|
|
status = __sys_NtCreateFile(
|
|
&merge->files[merge->files_count].handle,
|
|
FILE_GENERIC_READ, // DesiredAccess
|
|
&objattrs,
|
|
&IoStatusBlock,
|
|
NULL, // AllocationSize
|
|
0, // FileAttributes
|
|
FILE_SHARE_VALID_FLAGS, // ShareAccess
|
|
FILE_OPEN, // CreateDisposition
|
|
FILE_SYNCHRONOUS_IO_NONALERT, // CreateOptions
|
|
NULL, // EaBuffer
|
|
0); // EaLength
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// if the copy file exists, check if it is marked as deleted,
|
|
// and if so, close it and pretend it doesn't exist; otherwise
|
|
// make sure it is a directory file
|
|
//
|
|
|
|
status = __sys_NtQueryInformationFile(
|
|
merge->files[merge->files_count].handle, &IoStatusBlock, &info,
|
|
sizeof(FILE_BASIC_INFORMATION), FileBasicInformation);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
// if (!File_Delete_v2 &&
|
|
if (IS_DELETE_MARK(&info.basic.CreationTime)) {
|
|
|
|
status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
}
|
|
else if ((info.basic.FileAttributes &
|
|
FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
__sys_NtClose(merge->files[merge->files_count].handle);
|
|
merge->files[merge->files_count].handle = NULL;
|
|
|
|
TruePathDeleted = TRUE;
|
|
break; // don't look any further
|
|
}
|
|
|
|
merge->files[merge->files_count].snapshot = Cur_Snapshot;
|
|
|
|
//
|
|
// copy file passed all checks; indicate it is ready for use
|
|
//
|
|
|
|
merge->files[merge->files_count].more_files = TRUE;
|
|
merge->files[merge->files_count].RestartScan = TRUE;
|
|
merge->files[merge->files_count].scram_key = Cur_Snapshot->ScramKey;
|
|
merge->files_count++;
|
|
|
|
}
|
|
// else: ignore the error, proceed to next snapshot
|
|
|
|
//
|
|
// check if we have a relocation and update CopyPath for the next snapshot accordingly
|
|
// since we don't need opypath anyware anymore we can alter it
|
|
//
|
|
|
|
if (File_Delete_v2) {
|
|
|
|
WCHAR* Relocation = NULL;
|
|
ULONG Flags = File_GetPathFlags_internal(&Cur_Snapshot->PathRoot, TempPath, &Relocation, TRUE);
|
|
if (FILE_PATH_DELETED(Flags))
|
|
break;
|
|
|
|
if (Relocation) {
|
|
|
|
if (!Cur_Snapshot->Parent)
|
|
break; // take a shortcut
|
|
|
|
TempPath = Dll_GetTlsNameBuffer(TlsData, TRUE_NAME_BUFFER, (wcslen(Relocation) + 1) * sizeof(WCHAR));
|
|
wcscpy(TempPath, Relocation);
|
|
|
|
//
|
|
// update the copy file name
|
|
//
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
WCHAR* TruePath2, *CopyPath2;
|
|
RtlInitUnicodeString(&objname, Relocation);
|
|
File_GetName(NULL, &objname, &TruePath2, &CopyPath2, NULL);
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
|
|
// note: pop leaves TruePath2 valid we can still use it
|
|
|
|
CopyPath = Dll_GetTlsNameBuffer(TlsData, COPY_NAME_BUFFER, (wcslen(CopyPath2) + 1) * sizeof(WCHAR));
|
|
wcscpy(CopyPath, CopyPath2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if there is no copy file, we don't need to merge anything,
|
|
// and can let the system work directly on the true file
|
|
//
|
|
|
|
if ((TruePathFlags & FILE_CHILDREN_DELETED_FLAG) == 0) // we need to do full merge if children ar marked deleted
|
|
if (merge->files_count == 0) {
|
|
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
|
|
goto finish;
|
|
}
|
|
|
|
if (TruePathDeleted)
|
|
goto skip_true_file;
|
|
|
|
//
|
|
// true path must end with a backslash, so that we are able to
|
|
// open the root directory of the volume device
|
|
//
|
|
|
|
// TruePathIsRoot = FALSE;
|
|
|
|
len = wcslen(TruePath) * sizeof(WCHAR);
|
|
if (len > sizeof(WCHAR)) {
|
|
ptr = &TruePath[len / sizeof(WCHAR) - 1];
|
|
if (*ptr != L'\\') {
|
|
ptr[1] = L'\\';
|
|
ptr[2] = L'\0';
|
|
len += sizeof(WCHAR);
|
|
}
|
|
else {
|
|
ptr = NULL;
|
|
// TruePathIsRoot = TRUE;
|
|
}
|
|
}
|
|
else
|
|
ptr = NULL;
|
|
|
|
// RtlInitUnicodeString(&objname, );
|
|
objname.Length = (USHORT)len;
|
|
objname.MaximumLength = objname.Length + sizeof(WCHAR);
|
|
objname.Buffer = TruePath;
|
|
|
|
//
|
|
// open the true file
|
|
//
|
|
|
|
merge->true_ptr = &merge->files[merge->files_count];
|
|
|
|
status = __sys_NtCreateFile(
|
|
&merge->true_ptr->handle,
|
|
FILE_GENERIC_READ, // DesiredAccess
|
|
&objattrs,
|
|
&IoStatusBlock,
|
|
NULL, // AllocationSize
|
|
0, // FileAttributes
|
|
FILE_SHARE_VALID_FLAGS, // ShareAccess
|
|
FILE_OPEN, // CreateDisposition
|
|
FILE_SYNCHRONOUS_IO_NONALERT | // CreateOptions
|
|
FILE_DIRECTORY_FILE,
|
|
NULL, // EaBuffer
|
|
0); // EaLength
|
|
|
|
if (ptr)
|
|
ptr[1] = L'\0';
|
|
|
|
//
|
|
// even if the true directory could not be opened because it isn't
|
|
// there, or because it is a file rather than a directory, we still
|
|
// go ahead, and will use only the copy path for the "merge".
|
|
// for any other error opening the true directory, we abort.
|
|
//
|
|
|
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
|
|
status == STATUS_OBJECT_PATH_NOT_FOUND ||
|
|
status == STATUS_ACCESS_DENIED) {
|
|
|
|
BOOLEAN use_rule_specificity = (Dll_ProcessFlags & SBIE_FLAG_RULE_SPECIFICITY) != 0;
|
|
|
|
//
|
|
// if rule specificity is enabled we may not have access to this true path
|
|
// but still have access to some sub paths, in this case instead of listing the
|
|
// true directory we parse the rule list and construct a cached dummy directory
|
|
//
|
|
|
|
if (use_rule_specificity) {
|
|
|
|
if (File_MergeDummy(TruePath, merge->true_ptr, &merge->file_mask) == STATUS_SUCCESS) {
|
|
|
|
merge->true_ptr->handle = NULL;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
merge->true_ptr->handle = NULL;
|
|
merge->true_ptr = NULL;
|
|
|
|
if (status != STATUS_NOT_A_DIRECTORY &&
|
|
status != STATUS_OBJECT_NAME_NOT_FOUND &&
|
|
status != STATUS_OBJECT_PATH_NOT_FOUND) {
|
|
|
|
for (ULONG i = 0; i < merge->files_count; i++) {
|
|
__sys_NtClose(merge->files[i].handle);
|
|
merge->files[i].handle = NULL;
|
|
}
|
|
|
|
if (status == STATUS_ACCESS_DENIED)
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
|
|
goto finish;
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// true file passed all checks; indicate it is ready for use
|
|
//
|
|
|
|
merge->true_ptr->more_files = TRUE;
|
|
merge->true_ptr->RestartScan = TRUE;
|
|
merge->files_count++;
|
|
}
|
|
|
|
skip_true_file:
|
|
|
|
//
|
|
// now that both copy and true directories were opened, we will need to
|
|
// merge them. for this to work, we need a sorted directory listing.
|
|
// NTFS is always sorted, but FAT isn't, so cache the listing if needed.
|
|
//
|
|
// note that if we don't have a true handle, we won't merge anything,
|
|
// so do not have to cache in advance. on the other hand, if the
|
|
// true path is cached, we also have to cache the copy path, to make
|
|
// sure the files will be ordered in the same sequence. and vice
|
|
// versa: if the copy path is cached, make sure the true path is cached
|
|
//
|
|
|
|
if (merge->true_ptr) {
|
|
|
|
BOOLEAN ForceCache = FALSE;
|
|
if (merge->name_len >= File_MupLen * sizeof(WCHAR)
|
|
&& _wcsnicmp(merge->name, File_Mup, File_MupLen) == 0) {
|
|
//
|
|
// remote shares have all kinds of quirks, for example:
|
|
// returning STATUS_BUFFER_OVERFLOW from NtQueryDirectoryFile
|
|
// used in File_GetFullInformation, but then returning
|
|
// STATUS_NO_MORE_FILES when called with a larger buffer.
|
|
// we work around this by caching everything in advance
|
|
//
|
|
ForceCache = TRUE;
|
|
}
|
|
|
|
//
|
|
// true dir may be actually a dummy dir
|
|
//
|
|
|
|
if (merge->true_ptr->handle) {
|
|
|
|
status = File_MergeCache(
|
|
merge->true_ptr, &merge->file_mask, ForceCache);
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
BOOLEAN HaveTrueCache = (merge->true_ptr->cache_pool != NULL);
|
|
BOOLEAN HaveCopyCache = FALSE;
|
|
|
|
for (ULONG i = 0; i < merge->files_count - 1; i++) {
|
|
|
|
status = File_MergeCache(
|
|
&merge->files[i], &merge->file_mask, HaveTrueCache);
|
|
|
|
if (NT_SUCCESS(status) && merge->files[i].cache_pool != NULL)
|
|
HaveCopyCache = TRUE;
|
|
}
|
|
|
|
if (!HaveTrueCache && HaveCopyCache) {
|
|
|
|
status = File_MergeCache(
|
|
merge->true_ptr, &merge->file_mask, TRUE);
|
|
}
|
|
}
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
|
|
for (ULONG i = 0; i < merge->files_count; i++) {
|
|
if (merge->files[i].handle) {
|
|
__sys_NtClose(merge->files[i].handle);
|
|
merge->files[i].handle = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
finish:
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MergeCache
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_MergeCache(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask, BOOLEAN ForceCache)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_ATTRIBUTE_TAG_INFORMATION taginfo;
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_area;
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_ptr;
|
|
LIST *cache_list;
|
|
FILE_MERGE_CACHE_FILE *cache_file;
|
|
FILE_MERGE_CACHE_FILE *ins_point;
|
|
ULONG len;
|
|
const ULONG INFO_AREA_LEN = 0x10000; // the size used by cmd.exe
|
|
|
|
//
|
|
// ZwQueryDirectoryFile for NTFS returns the entries sorted by ascending
|
|
// order of filename. for FAT there is no guarantee of such a sort
|
|
// order. but we need a sorted directory listing for the directory
|
|
// merging to work. on FAT, the following ZwQueryInformationFile will
|
|
// fail, and this is how we can tell NTFS apart from FAT
|
|
//
|
|
|
|
if (qfile->cache_pool) {
|
|
Pool_Delete(qfile->cache_pool);
|
|
qfile->cache_pool = NULL;
|
|
}
|
|
|
|
if (! ForceCache) {
|
|
|
|
status = __sys_NtQueryInformationFile(
|
|
qfile->handle, &IoStatusBlock,
|
|
&taginfo, sizeof(taginfo), FileAttributeTagInformation);
|
|
|
|
if (status != STATUS_INVALID_PARAMETER &&
|
|
status != STATUS_NOT_IMPLEMENTED)
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// prepare the cache pool
|
|
//
|
|
|
|
qfile->cache_pool = Pool_Create();
|
|
if (! qfile->cache_pool)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
cache_list = &qfile->cache_list;
|
|
List_Init(cache_list);
|
|
|
|
info_area = Pool_Alloc(qfile->cache_pool, INFO_AREA_LEN);
|
|
if (! info_area)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
//
|
|
// read entire directory, build a sorted files list
|
|
//
|
|
|
|
while (1) {
|
|
|
|
info_area->NextEntryOffset = tzuk;
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
qfile->handle,
|
|
NULL, NULL, NULL, // Event, ApcRoutine, ApcContext
|
|
&IoStatusBlock,
|
|
info_area, INFO_AREA_LEN,
|
|
FileIdBothDirectoryInformation,
|
|
FALSE, // ReturnSingleEntry
|
|
FileMask,
|
|
qfile->RestartScan);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW &&
|
|
info_area->NextEntryOffset != tzuk) {
|
|
// we got STATUS_BUFFER_OVERFLOW but buffer was filled
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (! NT_SUCCESS(status)) {
|
|
|
|
if (status == STATUS_INVALID_PARAMETER ||
|
|
status == STATUS_INVALID_INFO_CLASS ||
|
|
status == STATUS_INVALID_LEVEL ) {
|
|
|
|
//
|
|
// can't get file ids, try secondary approach
|
|
// NetApp drive returns STATUS_INVALID_LEVEL error
|
|
//
|
|
|
|
status = File_MergeCacheWin2000(qfile, FileMask,
|
|
info_area, INFO_AREA_LEN);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
qfile->RestartScan = FALSE;
|
|
|
|
info_ptr = info_area;
|
|
while (1) {
|
|
int cmp;
|
|
|
|
len = sizeof(FILE_MERGE_CACHE_FILE)
|
|
+ info_ptr->FileNameLength;
|
|
|
|
cache_file = Pool_Alloc(qfile->cache_pool, len);
|
|
if (! cache_file) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
len = sizeof(FILE_ID_BOTH_DIR_INFORMATION)
|
|
- sizeof(WCHAR) // the [1] from FileName[1]
|
|
+ info_ptr->FileNameLength;
|
|
memcpy(&cache_file->info, info_ptr, len);
|
|
cache_file->info.NextEntryOffset = 0;
|
|
cache_file->info_len = len;
|
|
|
|
cache_file->name_uni.Length = (USHORT)info_ptr->FileNameLength;
|
|
cache_file->name_uni.MaximumLength = cache_file->name_uni.Length;
|
|
cache_file->name_uni.Buffer = cache_file->info.FileName;
|
|
|
|
// insert file into the ordered list
|
|
|
|
ins_point = List_Head(cache_list);
|
|
cmp = -1;
|
|
while (ins_point) {
|
|
cmp = RtlCompareUnicodeString(
|
|
&ins_point->name_uni, &cache_file->name_uni,
|
|
TRUE); // CaseInSensitive
|
|
if ( (cmp > 0) || (cmp == 0) )
|
|
break;
|
|
ins_point = List_Next(ins_point);
|
|
}
|
|
// There is a bug with Isilon drives. NtQueryDirectoryFile does not return STATUS_NO_MORE_FILES but always returns STATUS_SUCCESS with the same file name.
|
|
// This causes an infinite loop in this code. So, if the name_uni we just received is the same as what we just added to the list, assume it is the Isilon bug
|
|
// and break out of this loop.
|
|
if (cmp == 0)
|
|
{
|
|
status = STATUS_NO_MORE_FILES;
|
|
break;
|
|
}
|
|
|
|
if (ins_point)
|
|
List_Insert_Before(cache_list, ins_point, cache_file);
|
|
else
|
|
List_Insert_After(cache_list, NULL, cache_file);
|
|
|
|
// process next file
|
|
|
|
if (info_ptr->NextEntryOffset == 0)
|
|
break;
|
|
info_ptr = (FILE_ID_BOTH_DIR_INFORMATION *)
|
|
((UCHAR *)info_ptr + info_ptr->NextEntryOffset);
|
|
}
|
|
|
|
if (! NT_SUCCESS(status))
|
|
break;
|
|
}
|
|
|
|
if (status == STATUS_NO_MORE_FILES || status == STATUS_NO_SUCH_FILE)
|
|
status = STATUS_SUCCESS;
|
|
|
|
Pool_Free(info_area, INFO_AREA_LEN);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MergeCacheWin2000
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_MergeCacheWin2000(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask,
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_area, ULONG info_area_len)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_BOTH_DIRECTORY_INFORMATION *info_ptr;
|
|
LIST *cache_list;
|
|
FILE_MERGE_CACHE_FILE *cache_file;
|
|
FILE_MERGE_CACHE_FILE *ins_point;
|
|
ULONG len;
|
|
|
|
//
|
|
// on Windows 2000, the query for FileIdBothDirectoryInformation in
|
|
// File_MergeCache may not work, here use an alternative info class
|
|
// that doesn't include the FileId field
|
|
//
|
|
// note that this function is useful also in more recent editions of
|
|
// Windows, as some filesystems or filesystem filters (like CafeAgent)
|
|
// will fail calls using FileIdBothDirectoryInformation information,
|
|
// see File_GetFullInformation
|
|
//
|
|
|
|
cache_list = &qfile->cache_list;
|
|
|
|
//
|
|
// read entire directory, build a sorted files list
|
|
//
|
|
|
|
while (1) {
|
|
|
|
info_area->NextEntryOffset = tzuk;
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
qfile->handle,
|
|
NULL, NULL, NULL, // Event, ApcRoutine, ApcContext
|
|
&IoStatusBlock,
|
|
info_area, info_area_len,
|
|
FileBothDirectoryInformation, // no FileId
|
|
FALSE, // ReturnSingleEntry
|
|
FileMask,
|
|
qfile->RestartScan);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW &&
|
|
info_area->NextEntryOffset != tzuk) {
|
|
// we got STATUS_BUFFER_OVERFLOW but buffer was filled
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (! NT_SUCCESS(status))
|
|
break;
|
|
|
|
qfile->RestartScan = FALSE;
|
|
|
|
info_ptr = (FILE_BOTH_DIRECTORY_INFORMATION *)info_area;
|
|
while (1) {
|
|
|
|
len = sizeof(FILE_MERGE_CACHE_FILE)
|
|
+ info_ptr->FileNameLength;
|
|
|
|
cache_file = Pool_Alloc(qfile->cache_pool, len);
|
|
if (! cache_file) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// FILE_ID_BOTH_DIR ... and FILE_BOTH_DIRECTORY ... begin with
|
|
// the same layout, but differ towards the end, so we can copy
|
|
// the common part and then add the final FileName
|
|
//
|
|
|
|
memcpy(&cache_file->info, info_ptr,
|
|
FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileId));
|
|
|
|
cache_file->info.FileId.QuadPart = 0;
|
|
|
|
memcpy(&cache_file->info.FileName,
|
|
info_ptr->FileName, info_ptr->FileNameLength);
|
|
|
|
len = sizeof(FILE_ID_BOTH_DIR_INFORMATION)
|
|
- sizeof(WCHAR) // the [1] from FileName[1]
|
|
+ info_ptr->FileNameLength;
|
|
|
|
cache_file->info.NextEntryOffset = 0;
|
|
cache_file->info_len = len;
|
|
|
|
cache_file->name_uni.Length = (USHORT)info_ptr->FileNameLength;
|
|
cache_file->name_uni.MaximumLength = cache_file->name_uni.Length;
|
|
cache_file->name_uni.Buffer = cache_file->info.FileName;
|
|
|
|
// insert file into the ordered list
|
|
|
|
ins_point = List_Head(cache_list);
|
|
while (ins_point) {
|
|
int cmp = RtlCompareUnicodeString(
|
|
&ins_point->name_uni, &cache_file->name_uni,
|
|
TRUE); // CaseInSensitive
|
|
if (cmp > 0)
|
|
break;
|
|
ins_point = List_Next(ins_point);
|
|
}
|
|
|
|
if (ins_point)
|
|
List_Insert_Before(cache_list, ins_point, cache_file);
|
|
else
|
|
List_Insert_After(cache_list, NULL, cache_file);
|
|
|
|
// process next file
|
|
|
|
if (info_ptr->NextEntryOffset == 0)
|
|
break;
|
|
info_ptr = (FILE_BOTH_DIRECTORY_INFORMATION *)
|
|
((UCHAR *)info_ptr + info_ptr->NextEntryOffset);
|
|
}
|
|
|
|
if (! NT_SUCCESS(status))
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MergeDummy
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_MergeDummy(
|
|
WCHAR *TruePath, FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask)
|
|
{
|
|
NTSTATUS status;
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_area;
|
|
FILE_ID_BOTH_DIR_INFORMATION *info_ptr;
|
|
LIST *cache_list;
|
|
FILE_MERGE_CACHE_FILE *cache_file;
|
|
FILE_MERGE_CACHE_FILE *ins_point;
|
|
ULONG len;
|
|
const ULONG INFO_AREA_LEN = 0x10000; // the size used by cmd.exe
|
|
|
|
|
|
if (qfile->cache_pool) {
|
|
Pool_Delete(qfile->cache_pool);
|
|
qfile->cache_pool = NULL;
|
|
}
|
|
|
|
//
|
|
// prepare the cache pool
|
|
//
|
|
|
|
qfile->cache_pool = Pool_Create();
|
|
if (! qfile->cache_pool)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
cache_list = &qfile->cache_list;
|
|
List_Init(cache_list);
|
|
|
|
info_area = Pool_Alloc(qfile->cache_pool, INFO_AREA_LEN);
|
|
if (! info_area)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
//
|
|
// create a dummy directory, build a sorted files list
|
|
//
|
|
|
|
PATTERN* mask = NULL;
|
|
if (FileMask->Buffer)
|
|
mask = Pattern_Create(qfile->cache_pool, FileMask->Buffer, TRUE, 0);
|
|
WCHAR* test_buf = Pool_Alloc(qfile->cache_pool, 0x1000);
|
|
|
|
LIST* lists[4];
|
|
SbieDll_GetReadablePaths(L'f', lists);
|
|
|
|
ULONG TruePathLen = wcslen(TruePath);
|
|
if (TruePathLen > 1 && TruePath[TruePathLen - 1] == L'\\')
|
|
TruePathLen--; // never take last \ into account
|
|
|
|
ULONG* PrevEntry = NULL;
|
|
info_ptr = info_area;
|
|
for (int i=0; lists[i] != NULL; i++) {
|
|
|
|
PATTERN* pat = List_Head(lists[i]);
|
|
while (pat) {
|
|
|
|
const WCHAR* patstr = Pattern_Source(pat);
|
|
|
|
if (_wcsnicmp(TruePath, patstr, TruePathLen) == 0 && patstr[TruePathLen] == L'\\') {
|
|
|
|
const WCHAR* ptr = &patstr[TruePathLen + 1];
|
|
WCHAR* end = wcschr(ptr, L'\\');
|
|
if(end == NULL) end = wcschr(ptr, L'*');
|
|
if(end == NULL) end = wcschr(ptr, L'\0');
|
|
ULONG name_len = (ULONG)(end - ptr);
|
|
|
|
if (mask) {
|
|
|
|
memcpy(test_buf, ptr, (name_len + 1) * sizeof(WCHAR));
|
|
_wcslwr(test_buf);
|
|
|
|
if (!Pattern_Match(mask, test_buf, name_len))
|
|
goto next;
|
|
}
|
|
|
|
//
|
|
// check if the true path exists
|
|
//
|
|
|
|
WCHAR* FakePath = Dll_AllocTemp(TruePathLen * sizeof(WCHAR) + 1 + name_len * sizeof(WCHAR) + 10);
|
|
|
|
wmemcpy(FakePath, TruePath, TruePathLen);
|
|
FakePath[TruePathLen] = L'\\';
|
|
end = &FakePath[TruePathLen + 1];
|
|
*end = L'\0';
|
|
wmemcpy(end, ptr, name_len);
|
|
end[name_len] = L'\0';
|
|
|
|
FILE_NETWORK_OPEN_INFORMATION info;
|
|
status = File_QueryFullAttributesDirectoryFile(FakePath, &info);
|
|
|
|
Dll_Free(FakePath);
|
|
|
|
//
|
|
// add directory entry
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
info_ptr->FileNameLength = name_len * sizeof(WCHAR);
|
|
memcpy(info_ptr->FileName, ptr, info_ptr->FileNameLength);
|
|
info_ptr->FileName[info_ptr->FileNameLength] = L'\0';
|
|
|
|
info_ptr->CreationTime = info.CreationTime;
|
|
info_ptr->LastAccessTime = info.LastAccessTime;
|
|
info_ptr->LastWriteTime = info.LastWriteTime;
|
|
info_ptr->ChangeTime = info.ChangeTime;
|
|
info_ptr->AllocationSize = info.AllocationSize;
|
|
info_ptr->EndOfFile = info.EndOfFile;
|
|
info_ptr->FileAttributes = info.FileAttributes;
|
|
|
|
|
|
//ULONG NextEntryOffset;
|
|
//ULONG FileIndex;
|
|
//ULONG EaInformationLength;
|
|
//CCHAR ShortNameLength;
|
|
//WCHAR ShortName[12];
|
|
//LARGE_INTEGER FileId;
|
|
|
|
|
|
info_ptr->FileId.QuadPart = -1;
|
|
|
|
PrevEntry = &info_ptr->NextEntryOffset;
|
|
|
|
info_ptr->NextEntryOffset = sizeof(FILE_ID_BOTH_DIR_INFORMATION) + info_ptr->FileNameLength + sizeof(WCHAR) + 16; // +16 some buffer space
|
|
|
|
ULONG tmp = (info_ptr->NextEntryOffset & 0x07);
|
|
if (tmp != 0) // fix alignment when needed
|
|
info_ptr->NextEntryOffset += 0x8 - tmp;
|
|
|
|
info_ptr = (FILE_ID_BOTH_DIR_INFORMATION*)
|
|
((UCHAR*)info_ptr + info_ptr->NextEntryOffset);
|
|
|
|
// todo: fix-me possible info_area buffer overflow!!!!
|
|
}
|
|
}
|
|
|
|
next:
|
|
pat = List_Next(pat);
|
|
}
|
|
}
|
|
|
|
Pool_Free(test_buf, 0x1000);
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
SbieDll_ReleaseFilePathLock();
|
|
|
|
if(mask)
|
|
Pattern_Free(mask);
|
|
|
|
if (PrevEntry == NULL) {
|
|
// no dummys created
|
|
status = STATUS_NO_MORE_ENTRIES;
|
|
goto finish;
|
|
}
|
|
*PrevEntry = 0;
|
|
|
|
qfile->RestartScan = FALSE;
|
|
|
|
info_ptr = info_area;
|
|
while (1) {
|
|
int cmp;
|
|
|
|
len = sizeof(FILE_MERGE_CACHE_FILE)
|
|
+ info_ptr->FileNameLength;
|
|
|
|
cache_file = Pool_Alloc(qfile->cache_pool, len);
|
|
if (! cache_file) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
len = sizeof(FILE_ID_BOTH_DIR_INFORMATION)
|
|
- sizeof(WCHAR) // the [1] from FileName[1]
|
|
+ info_ptr->FileNameLength;
|
|
memcpy(&cache_file->info, info_ptr, len);
|
|
cache_file->info.NextEntryOffset = 0;
|
|
cache_file->info_len = len;
|
|
|
|
cache_file->name_uni.Length = (USHORT)info_ptr->FileNameLength;
|
|
cache_file->name_uni.MaximumLength = cache_file->name_uni.Length;
|
|
cache_file->name_uni.Buffer = cache_file->info.FileName;
|
|
|
|
// insert file into the ordered list
|
|
|
|
ins_point = List_Head(cache_list);
|
|
cmp = -1;
|
|
while (ins_point) {
|
|
cmp = RtlCompareUnicodeString(
|
|
&ins_point->name_uni, &cache_file->name_uni,
|
|
TRUE); // CaseInSensitive
|
|
if ( (cmp > 0) || (cmp == 0) )
|
|
break;
|
|
ins_point = List_Next(ins_point);
|
|
}
|
|
|
|
if (cmp != 0) { // skip duplicates
|
|
|
|
if (ins_point)
|
|
List_Insert_Before(cache_list, ins_point, cache_file);
|
|
else
|
|
List_Insert_After(cache_list, NULL, cache_file);
|
|
}
|
|
|
|
if (info_ptr->NextEntryOffset == 0)
|
|
break;
|
|
info_ptr = (FILE_ID_BOTH_DIR_INFORMATION *)
|
|
((UCHAR *)info_ptr + info_ptr->NextEntryOffset);
|
|
}
|
|
|
|
finish:
|
|
|
|
Pool_Free(info_area, INFO_AREA_LEN);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MergeFree
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX void File_MergeFree(FILE_MERGE *merge)
|
|
{
|
|
if (merge->files)
|
|
{
|
|
for (ULONG i = 0; i < merge->files_count; i++)
|
|
{
|
|
if (merge->files[i].handle)
|
|
__sys_NtClose(merge->files[i].handle);
|
|
if (merge->files[i].info)
|
|
Dll_Free(merge->files[i].info);
|
|
if (merge->files[i].name)
|
|
Dll_Free(merge->files[i].name);
|
|
if (merge->files[i].cache_pool)
|
|
Pool_Delete(merge->files[i].cache_pool);
|
|
}
|
|
|
|
Dll_Free(merge->files);
|
|
}
|
|
|
|
if (merge->file_mask.Buffer)
|
|
Dll_Free(merge->file_mask.Buffer);
|
|
Dll_Free(merge);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_GetMergedInformation
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_GetMergedInformation(
|
|
FILE_MERGE *merge, WCHAR *TruePath, WCHAR *CopyPath,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
ULONG info_entry_length;
|
|
FILE_ID_BOTH_DIR_INFORMATION *ptr_info;
|
|
PVOID prev_entry;
|
|
PVOID next_entry;
|
|
WCHAR *name_ptr;
|
|
ULONG len;
|
|
ULONG TruePathLen = wcslen(TruePath);
|
|
ULONG CopyPathLen = wcslen(CopyPath);
|
|
|
|
info_entry_length = 0;
|
|
if (FileInformationClass == FileDirectoryInformation)
|
|
info_entry_length = sizeof(FILE_DIRECTORY_INFORMATION);
|
|
else if (FileInformationClass == FileFullDirectoryInformation)
|
|
info_entry_length = sizeof(FILE_FULL_DIRECTORY_INFORMATION);
|
|
else if (FileInformationClass == FileBothDirectoryInformation)
|
|
info_entry_length = sizeof(FILE_BOTH_DIRECTORY_INFORMATION);
|
|
else if (FileInformationClass == FileNamesInformation)
|
|
info_entry_length = sizeof(FILE_NAMES_INFORMATION);
|
|
else if (FileInformationClass == FileIdBothDirectoryInformation)
|
|
info_entry_length = sizeof(FILE_ID_BOTH_DIR_INFORMATION);
|
|
else if (FileInformationClass == FileIdFullDirectoryInformation)
|
|
info_entry_length = sizeof(FILE_ID_FULL_DIR_INFORMATION);
|
|
else
|
|
return STATUS_INVALID_INFO_CLASS;
|
|
|
|
if (info_entry_length > Length)
|
|
return STATUS_INFO_LENGTH_MISMATCH;
|
|
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
prev_entry = FileInformation;
|
|
next_entry = FileInformation;
|
|
|
|
IoStatusBlock->Information = 0; // reset count of bytes written
|
|
|
|
while (1) {
|
|
|
|
// get directory entries from both directories
|
|
|
|
for (ULONG i = 0; i < merge->files_count && NT_SUCCESS(status); i++)
|
|
{
|
|
status = File_GetFullInformation(
|
|
&merge->files[i], &merge->file_mask, TRUE);
|
|
}
|
|
if (! NT_SUCCESS(status))
|
|
break;
|
|
|
|
// find where we need to copy the next directory entry from:
|
|
// merge the directories in a sorted order, but prefer to
|
|
// take info from the copy directory if a file exists in both
|
|
|
|
ptr_info = NULL;
|
|
for (ULONG i = 0; i < merge->files_count; i++)
|
|
merge->files[i].saved_have_entry = merge->files[i].have_entry;
|
|
|
|
/*if (merge->files[0].have_entry && // both directories
|
|
merge->true_ptr && merge->true_ptr->have_entry) { // have an entry
|
|
|
|
int cmp = RtlCompareUnicodeString(
|
|
&merge->true_ptr->name_uni,
|
|
&merge->files[0].name_uni,
|
|
TRUE); // CaseInSensitive
|
|
|
|
if (cmp < 0) { // true name sorts before copy name
|
|
ptr_info = merge->true_ptr->info;
|
|
merge->true_ptr->have_entry = FALSE;
|
|
} else { // true name equal to or after copy name
|
|
ptr_info = merge->files[0].info;
|
|
merge->files[0].have_entry = FALSE;
|
|
if (cmp == 0) // equal
|
|
merge->true_ptr->have_entry = FALSE;
|
|
}
|
|
|
|
} else if (merge->files[0].have_entry) { // only copy
|
|
merge->files[0].have_entry = FALSE;
|
|
ptr_info = merge->files[0].info;
|
|
|
|
} else if (merge->true_ptr && merge->true_ptr->have_entry) { // only true
|
|
ptr_info = merge->true_ptr->info;
|
|
merge->true_ptr->have_entry = FALSE;
|
|
}*/
|
|
|
|
FILE_MERGE_FILE* best = &merge->files[0];
|
|
|
|
for (ULONG i = 1; i < merge->files_count; i++) {
|
|
|
|
FILE_MERGE_FILE* cur = &merge->files[i];
|
|
|
|
if (!best->have_entry) {
|
|
best = cur;
|
|
}
|
|
else if (cur->have_entry) {
|
|
|
|
int cmp = RtlCompareUnicodeString(&best->name_uni, &cur->name_uni, TRUE); // CaseInSensitive
|
|
|
|
if (cmp == 0) // equal - same file in both, use newer (best)
|
|
cur->have_entry = FALSE;
|
|
else if (cmp > 0)
|
|
best = cur;
|
|
}
|
|
}
|
|
|
|
if (best->have_entry) {
|
|
ptr_info = best->info;
|
|
best->have_entry = FALSE;
|
|
}
|
|
|
|
// if both directories are exhausted, reset the
|
|
// NextEntryOffset field of FILE_*_INFORMATION to
|
|
// indicate no more entries, and return status
|
|
|
|
if (! ptr_info) {
|
|
if (next_entry == FileInformation)
|
|
status = STATUS_NO_MORE_FILES;
|
|
else
|
|
status = STATUS_SUCCESS;
|
|
*(ULONG *)prev_entry = 0; // reset NextEntryOffset
|
|
break;
|
|
}
|
|
|
|
//if (ptr_info->FileId.QuadPart == -1) {
|
|
// WCHAR msg[1024];
|
|
// Sbie_snwprintf(msg, 1024, L"File_MergeDummy simulate %s", ptr_info->FileName);
|
|
// SbieApi_MonitorPutMsg(MONITOR_OTHER | MONITOR_TRACE, msg);
|
|
//}
|
|
|
|
if (File_Delete_v2) {
|
|
|
|
if ((merge->true_ptr && ptr_info == merge->true_ptr->info) // is in true path
|
|
|| ptr_info != merge->files[0].info) { // is in template
|
|
|
|
WCHAR* TruePath2 = Dll_GetTlsNameBuffer(TlsData, TRUE_NAME_BUFFER, ((TruePathLen + 1) * sizeof(WCHAR) + ptr_info->FileNameLength + sizeof(WCHAR)));
|
|
WCHAR* ptr = TruePath2;
|
|
wmemcpy(ptr, TruePath, TruePathLen);
|
|
ptr += TruePathLen;
|
|
if (ptr[-1] != L'\\') *ptr++ = L'\\';
|
|
wmemcpy(ptr, ptr_info->FileName, ptr_info->FileNameLength / sizeof(WCHAR));
|
|
ptr += ptr_info->FileNameLength / sizeof(WCHAR);
|
|
*ptr = L'\0';
|
|
|
|
WCHAR* CopyPath2 = Dll_GetTlsNameBuffer(TlsData, COPY_NAME_BUFFER, ((CopyPathLen + 1) * sizeof(WCHAR) + ptr_info->FileNameLength + sizeof(WCHAR)));
|
|
ptr = CopyPath2;
|
|
wmemcpy(ptr, CopyPath, CopyPathLen);
|
|
ptr += CopyPathLen;
|
|
if (ptr[-1] != L'\\') *ptr++ = L'\\';
|
|
wmemcpy(ptr, ptr_info->FileName, ptr_info->FileNameLength / sizeof(WCHAR));
|
|
ptr += ptr_info->FileNameLength / sizeof(WCHAR);
|
|
*ptr = L'\0';
|
|
|
|
//
|
|
// check if the file is listed as deleted
|
|
//
|
|
|
|
if (File_IsDeletedEx(TruePath2, CopyPath2, best->snapshot))
|
|
continue;
|
|
|
|
} //else // is in copy path - nothing to do
|
|
}
|
|
else {
|
|
|
|
// if the entry found was in the copy directory, then the file
|
|
// may be marked deleted (see Filesys_Mark_File_Deleted for
|
|
// details). if it is marked so, we pretend this entry does
|
|
// not exist by fetching the following one
|
|
|
|
if ((!merge->true_ptr || ptr_info != merge->true_ptr->info) &&
|
|
IS_DELETE_MARK(&ptr_info->CreationTime))
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// make sure caller has enough room in output buffer for the
|
|
// entry. for the first entry in the buffer, it is ok to have
|
|
// room for only the entry header, excluding filename, but
|
|
// less than that and we will return BUFFER_OVERFLOW. for
|
|
// later entries in a large buffer (second and on), we must
|
|
// have room for the entry including filename, or we stop here
|
|
// and let the entry be copied the next time around
|
|
//
|
|
|
|
len = info_entry_length
|
|
- sizeof(WCHAR); // the [1] from FileName[1]
|
|
if (next_entry != FileInformation)
|
|
len += ptr_info->FileNameLength;
|
|
|
|
if ((UCHAR *)next_entry - (UCHAR *)FileInformation + len > Length) {
|
|
|
|
// current entries have not been used yet,
|
|
// reset flags so they are used again next time
|
|
for (ULONG i = 0; i < merge->files_count; i++)
|
|
merge->files[i].have_entry = merge->files[i].saved_have_entry;
|
|
|
|
*(ULONG *)prev_entry = 0; // reset NextEntryOffset
|
|
|
|
if (next_entry == FileInformation)
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// by now we've selected which source buffer to use (from true
|
|
// file or from copy file), and verified the buffer is large
|
|
// enough to contain at least the fixed portion of the data.
|
|
// now copy the fields from the source buffer and place them
|
|
// in the caller's output buffer
|
|
//
|
|
|
|
// source=ptr_info
|
|
// target=next_entry
|
|
//
|
|
|
|
name_ptr = File_CopyFixedInformation(
|
|
ptr_info, next_entry, FileInformationClass);
|
|
|
|
// This structure must be aligned on a LONGLONG (8-byte)
|
|
// boundary. If a buffer contains two or more of these
|
|
// structures, the NextEntryOffset value in each entry,
|
|
// except the last, falls on an 8-byte boundary.
|
|
|
|
ULONG tmp = (*(ULONG*)next_entry & 0x07);
|
|
if (tmp != 0) // fix alignment when needed
|
|
*(ULONG*)next_entry += 0x8 - tmp;
|
|
|
|
// copy as much of the filename as there is room available
|
|
// in the caller's buffer. note that for the second and
|
|
// later entries in a large buffer we already checked that
|
|
// there is enough room for whole entry including filename
|
|
|
|
len = (UCHAR *)next_entry - (UCHAR *)FileInformation
|
|
+ info_entry_length
|
|
- sizeof(WCHAR) // the [1] from FileName[1]
|
|
+ ptr_info->FileNameLength;
|
|
if (len > Length)
|
|
len = ptr_info->FileNameLength - (len - Length);
|
|
else
|
|
len = ptr_info->FileNameLength;
|
|
|
|
memcpy(name_ptr, ptr_info->FileName, len);
|
|
|
|
// the following condition can only be met for the first
|
|
// entry in the buffer, so if we can't complete even
|
|
// the first one, we want to say BUFFER_OVERFLOW
|
|
|
|
if (len < ptr_info->FileNameLength) {
|
|
|
|
// current entries have not gotten used yet,
|
|
// reset flags so they are used again next time
|
|
for (ULONG i = 0; i < merge->files_count; i++)
|
|
merge->files[i].have_entry = merge->files[i].saved_have_entry;
|
|
|
|
*(ULONG *)prev_entry = 0; // reset NextEntryOffset
|
|
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
break;
|
|
}
|
|
|
|
prev_entry = next_entry;
|
|
(UCHAR *)next_entry += *(ULONG *)next_entry; // NextEntryOffset
|
|
|
|
if (ReturnSingleEntry ||
|
|
(UCHAR *)next_entry + info_entry_length >
|
|
(UCHAR *)FileInformation + Length) {
|
|
*(ULONG *)prev_entry = 0; // reset NextEntryOffset
|
|
break;
|
|
}
|
|
}
|
|
|
|
IoStatusBlock->Status = status;
|
|
IoStatusBlock->Information = // number of bytes written
|
|
(UCHAR *)next_entry - (UCHAR *)FileInformation;
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_GetFullInformation
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_GetFullInformation(
|
|
FILE_MERGE_FILE *qfile, UNICODE_STRING *FileMask, BOOLEAN XorFileId)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_MERGE_CACHE_FILE *cache_file;
|
|
|
|
// if we don't currently have a directory entry, get one
|
|
|
|
if ((! qfile->have_entry) && qfile->more_files) {
|
|
|
|
while (1) {
|
|
|
|
if (! qfile->info) {
|
|
qfile->info_len += 256;
|
|
qfile->info = Dll_Alloc(qfile->info_len);
|
|
}
|
|
|
|
// if we have pre-cached the entire directory listing,
|
|
// return a cache entry; otherwise use ZwQueryDirectoryFile
|
|
|
|
if (qfile->cache_pool) {
|
|
|
|
cache_file = List_Head(&qfile->cache_list);
|
|
if (cache_file) {
|
|
if (qfile->info_len >= cache_file->info_len) {
|
|
memcpy(qfile->info, &cache_file->info,
|
|
cache_file->info_len);
|
|
List_Remove(&qfile->cache_list, cache_file);
|
|
status = STATUS_SUCCESS;
|
|
} else
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
} else
|
|
status = STATUS_NO_MORE_FILES;
|
|
|
|
} else {
|
|
|
|
ULONG info_class = FileIdBothDirectoryInformation;
|
|
if (qfile->no_file_ids)
|
|
info_class = FileBothDirectoryInformation;
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
qfile->handle,
|
|
NULL, NULL, NULL, // Event, ApcRoutine, ApcContext
|
|
&IoStatusBlock,
|
|
qfile->info, qfile->info_len,
|
|
info_class, // File(Id)BothDirectoryInformation
|
|
TRUE, // ReturnSingleEntry
|
|
FileMask,
|
|
qfile->RestartScan);
|
|
|
|
//
|
|
// support for shares that don't support the info class
|
|
// FileIdBothDirectoryInformation, and support scrambling
|
|
// the FileID for files in the sandbox
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
FILE_ID_BOTH_DIR_INFORMATION *dst = qfile->info;
|
|
|
|
if (qfile->no_file_ids) {
|
|
|
|
FILE_BOTH_DIRECTORY_INFORMATION *src =
|
|
(FILE_BOTH_DIRECTORY_INFORMATION *)qfile->info;
|
|
memmove(dst->FileName,
|
|
src->FileName, src->FileNameLength);
|
|
dst->FileId.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// see File_NtQueryInformationFile for a discussion
|
|
// about why we have to scramble the sandbox FileId
|
|
//
|
|
|
|
if (XorFileId && dst->FileId.QuadPart) {
|
|
|
|
dst->FileId.LowPart ^= 0xFFFFFFFF;
|
|
dst->FileId.HighPart ^= 0xFFFFFFFF;
|
|
}
|
|
|
|
//
|
|
// if we failed to get information using file ids, then
|
|
// try an alternative approach without file ids, see also
|
|
// File_MergeCache and File_MergeCacheWin2000
|
|
// NetApp drive returns STATUS_INVALID_LEVEL error
|
|
//
|
|
|
|
} else if ((status == STATUS_INVALID_PARAMETER ||
|
|
status == STATUS_INVALID_LEVEL ||
|
|
status == STATUS_INVALID_INFO_CLASS)
|
|
&& (info_class == FileIdBothDirectoryInformation)) {
|
|
|
|
NTSTATUS status2;
|
|
qfile->no_file_ids = TRUE;
|
|
status2 = File_MergeCache(qfile, FileMask, TRUE);
|
|
if (NT_SUCCESS(status2))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Scramble the short file name to ensure each snapshot has unique short names
|
|
//
|
|
|
|
if (NT_SUCCESS(status) && qfile->scram_key && qfile->info->ShortNameLength > 0)
|
|
File_ScrambleShortName(qfile->info->ShortName, &qfile->info->ShortNameLength, qfile->scram_key);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW) {
|
|
|
|
Dll_Free(qfile->info);
|
|
qfile->info = NULL;
|
|
continue;
|
|
|
|
} else {
|
|
qfile->RestartScan = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
// we got an entry, now copy it into a null-terminated
|
|
// unicode-string pointed to by the name member of the
|
|
// FILESYS_DIR_QUERY_FILE structure. make sure that
|
|
// buffer is large enough to hold the name.
|
|
|
|
if (qfile->name_max_len <
|
|
qfile->info->FileNameLength + sizeof(WCHAR)) {
|
|
if (qfile->name)
|
|
Dll_Free(qfile->name);
|
|
qfile->name_max_len =
|
|
qfile->info->FileNameLength + sizeof(WCHAR);
|
|
qfile->name = Dll_Alloc(qfile->name_max_len);
|
|
}
|
|
|
|
if (qfile->name) {
|
|
memcpy(qfile->name, qfile->info->FileName,
|
|
qfile->info->FileNameLength);
|
|
qfile->name[qfile->info->FileNameLength / sizeof(WCHAR)] = 0;
|
|
|
|
qfile->name_uni.Length =
|
|
(USHORT)qfile->info->FileNameLength;
|
|
qfile->name_uni.MaximumLength =
|
|
(USHORT)(qfile->name_uni.Length + sizeof(WCHAR));
|
|
qfile->name_uni.Buffer = qfile->name;
|
|
|
|
qfile->have_entry = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
// we did not get an entry from ZwQueryDirectoryFile.
|
|
// if it was because the file listing is really finished,
|
|
// we don't consider that an error, but just mark this
|
|
// fact in the FILE_MERGE_FILE structure.
|
|
|
|
qfile->more_files = FALSE;
|
|
|
|
if (status == STATUS_NO_MORE_FILES ||
|
|
status == STATUS_NO_SUCH_FILE)
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
} else // no entry and no more files
|
|
status = STATUS_SUCCESS;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_CopyFixedInformation
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX WCHAR *File_CopyFixedInformation(
|
|
FILE_ID_BOTH_DIR_INFORMATION *source, void *target,
|
|
FILE_INFORMATION_CLASS FileInformationClass)
|
|
{
|
|
|
|
#define COPY_ULONG(y) tmp_info->y = source->y;
|
|
#define COPY_LARGE_INTEGER(y) tmp_info->y.QuadPart = source->y.QuadPart
|
|
#define COPY_BYTES(y,n) memcpy(tmp_info->y, source->y, n)
|
|
#define COPY_ARRAY(y) COPY_BYTES(y,sizeof(tmp_info->y))
|
|
|
|
//
|
|
// FileDirectoryInformation
|
|
//
|
|
|
|
if (FileInformationClass == FileDirectoryInformation) {
|
|
|
|
FILE_DIRECTORY_INFORMATION *tmp_info =
|
|
(FILE_DIRECTORY_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_LARGE_INTEGER(CreationTime);
|
|
COPY_LARGE_INTEGER(LastAccessTime);
|
|
COPY_LARGE_INTEGER(LastWriteTime);
|
|
COPY_LARGE_INTEGER(ChangeTime);
|
|
COPY_LARGE_INTEGER(EndOfFile);
|
|
COPY_LARGE_INTEGER(AllocationSize);
|
|
COPY_ULONG(FileAttributes);
|
|
COPY_ULONG(FileNameLength);
|
|
|
|
return tmp_info->FileName;
|
|
|
|
//
|
|
// FileFullDirectoryInformation
|
|
//
|
|
|
|
} else if (FileInformationClass == FileFullDirectoryInformation) {
|
|
|
|
FILE_FULL_DIRECTORY_INFORMATION *tmp_info =
|
|
(FILE_FULL_DIRECTORY_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_FULL_DIRECTORY_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_LARGE_INTEGER(CreationTime);
|
|
COPY_LARGE_INTEGER(LastAccessTime);
|
|
COPY_LARGE_INTEGER(LastWriteTime);
|
|
COPY_LARGE_INTEGER(ChangeTime);
|
|
COPY_LARGE_INTEGER(EndOfFile);
|
|
COPY_LARGE_INTEGER(AllocationSize);
|
|
COPY_ULONG(FileAttributes);
|
|
COPY_ULONG(FileNameLength);
|
|
COPY_ULONG(EaInformationLength);
|
|
|
|
return tmp_info->FileName;
|
|
|
|
//
|
|
// FileBothDirectoryInformation
|
|
//
|
|
|
|
} else if (FileInformationClass == FileBothDirectoryInformation) {
|
|
|
|
FILE_BOTH_DIRECTORY_INFORMATION *tmp_info =
|
|
(FILE_BOTH_DIRECTORY_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_BOTH_DIRECTORY_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_LARGE_INTEGER(CreationTime);
|
|
COPY_LARGE_INTEGER(LastAccessTime);
|
|
COPY_LARGE_INTEGER(LastWriteTime);
|
|
COPY_LARGE_INTEGER(ChangeTime);
|
|
COPY_LARGE_INTEGER(EndOfFile);
|
|
COPY_LARGE_INTEGER(AllocationSize);
|
|
COPY_ULONG(FileAttributes);
|
|
COPY_ULONG(FileNameLength);
|
|
COPY_ULONG(EaInformationLength);
|
|
COPY_ULONG(ShortNameLength);
|
|
COPY_ARRAY(ShortName);
|
|
|
|
return tmp_info->FileName;
|
|
|
|
//
|
|
// FileNamesInformation
|
|
//
|
|
|
|
} else if (FileInformationClass == FileNamesInformation) {
|
|
|
|
FILE_NAMES_INFORMATION *tmp_info =
|
|
(FILE_NAMES_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_ULONG(FileNameLength);
|
|
|
|
return tmp_info->FileName;
|
|
|
|
//
|
|
// FileIdBothDirectoryInformation
|
|
//
|
|
|
|
} else if (FileInformationClass == FileIdBothDirectoryInformation) {
|
|
|
|
FILE_ID_BOTH_DIR_INFORMATION *tmp_info =
|
|
(FILE_ID_BOTH_DIR_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_LARGE_INTEGER(CreationTime);
|
|
COPY_LARGE_INTEGER(LastAccessTime);
|
|
COPY_LARGE_INTEGER(LastWriteTime);
|
|
COPY_LARGE_INTEGER(ChangeTime);
|
|
COPY_LARGE_INTEGER(EndOfFile);
|
|
COPY_LARGE_INTEGER(AllocationSize);
|
|
COPY_ULONG(FileAttributes);
|
|
COPY_ULONG(FileNameLength);
|
|
COPY_ULONG(EaInformationLength);
|
|
COPY_ULONG(ShortNameLength);
|
|
COPY_ARRAY(ShortName);
|
|
COPY_LARGE_INTEGER(FileId);
|
|
|
|
return tmp_info->FileName;
|
|
|
|
//
|
|
// FileIdFullDirectoryInformation
|
|
//
|
|
|
|
} else if (FileInformationClass == FileIdFullDirectoryInformation) {
|
|
|
|
FILE_ID_FULL_DIR_INFORMATION *tmp_info =
|
|
(FILE_ID_FULL_DIR_INFORMATION *)target;
|
|
|
|
tmp_info->NextEntryOffset =
|
|
FIELD_OFFSET(FILE_ID_FULL_DIR_INFORMATION, FileName)
|
|
+ source->FileNameLength;
|
|
|
|
COPY_ULONG(FileIndex);
|
|
COPY_LARGE_INTEGER(CreationTime);
|
|
COPY_LARGE_INTEGER(LastAccessTime);
|
|
COPY_LARGE_INTEGER(LastWriteTime);
|
|
COPY_LARGE_INTEGER(ChangeTime);
|
|
COPY_LARGE_INTEGER(EndOfFile);
|
|
COPY_LARGE_INTEGER(AllocationSize);
|
|
COPY_ULONG(FileAttributes);
|
|
COPY_ULONG(FileNameLength);
|
|
COPY_ULONG(EaInformationLength);
|
|
COPY_LARGE_INTEGER(FileId);
|
|
|
|
return tmp_info->FileName;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
#undef COPY_ULONG
|
|
#undef COPY_LARGE_INTEGER
|
|
#undef COPY_BYTES
|
|
#undef COPY_ARRAY
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtClose
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_NtClose(HANDLE FileHandle)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
__try {
|
|
status = File_NtCloseImpl(FileHandle);
|
|
}
|
|
__except (GetExceptionCode() == EXCEPTION_INVALID_HANDLE ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
|
return STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
status = StopTailCallOptimization(status);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtCloseImpl
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_NtCloseImpl(HANDLE FileHandle)
|
|
{
|
|
ULONG LastError;
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
|
|
|
|
NTSTATUS status;
|
|
BOOLEAN DeleteOnClose = FALSE;
|
|
UNICODE_STRING uni;
|
|
WCHAR *DeletePath = NULL;
|
|
|
|
P_NtClose pSysNtClose = __sys_NtClose;
|
|
|
|
//
|
|
// handle a recursive invocation of NtClose,
|
|
// and requests to close a psuedo-handle
|
|
//
|
|
|
|
if (TlsData->file_NtClose_lock ||
|
|
FileHandle == NtCurrentProcess() ||
|
|
FileHandle == NtCurrentThread()) {
|
|
|
|
return pSysNtClose ? pSysNtClose(FileHandle) : NtClose(FileHandle);
|
|
}
|
|
|
|
TlsData->file_NtClose_lock = TRUE;
|
|
|
|
//
|
|
// close for a proxy pipe handle
|
|
//
|
|
|
|
if (((ULONG_PTR)FileHandle & PROXY_PIPE_MASK) == PROXY_PIPE_MASK) {
|
|
|
|
File_CloseProxyPipe(FileHandle);
|
|
|
|
TlsData->file_NtClose_lock = FALSE;
|
|
|
|
SetLastError(LastError);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// check the handle map and execute the close handlers if there is are any
|
|
// and prepare the DeleteOnClose if its set
|
|
//
|
|
|
|
Handle_ExecuteCloseHandler(FileHandle, &DeleteOnClose);
|
|
|
|
//
|
|
// prepare delete disposition if set
|
|
//
|
|
|
|
if (DeleteOnClose) {
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
__try {
|
|
|
|
WCHAR *TruePath, *CopyPath;
|
|
ULONG FileFlags;
|
|
|
|
RtlInitUnicodeString(&uni, L"");
|
|
status = File_GetName(
|
|
FileHandle, &uni, &TruePath, &CopyPath, &FileFlags);
|
|
|
|
ULONG len = wcslen(TruePath);
|
|
DeletePath = Dll_AllocTemp((len + 8) * sizeof(WCHAR));
|
|
wmemcpy(DeletePath, TruePath, len + 1);
|
|
|
|
if (Dll_ChromeSandbox) {
|
|
|
|
//
|
|
// if this is a Chrome sandbox process, we have
|
|
// to pass a DOS path to NtDeleteFile rather
|
|
// than a file handle
|
|
//
|
|
|
|
if (SbieDll_TranslateNtToDosPath(DeletePath)) {
|
|
len = wcslen(DeletePath);
|
|
wmemmove(DeletePath + 4, DeletePath, len + 1);
|
|
wmemcpy(DeletePath, File_BQQB, 4);
|
|
}
|
|
}
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
status = GetExceptionCode();
|
|
}
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
}
|
|
|
|
//
|
|
// close the handle
|
|
//
|
|
|
|
status = pSysNtClose ? pSysNtClose(FileHandle) : NtClose(FileHandle);
|
|
|
|
//
|
|
// finish
|
|
//
|
|
|
|
TlsData->file_NtClose_lock = FALSE;
|
|
|
|
//
|
|
// execute pending delete disposition
|
|
//
|
|
|
|
if (DeletePath) {
|
|
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
RtlInitUnicodeString(&uni, DeletePath);
|
|
InitializeObjectAttributes(
|
|
&objattrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
File_NtDeleteFileImpl(&objattrs);
|
|
|
|
Dll_Free(DeletePath);
|
|
}
|
|
|
|
SetLastError(LastError);
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtCloseDir
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX VOID File_NtCloseDir(HANDLE FileHandle, void* CloseParams)
|
|
{
|
|
FILE_MERGE *merge;
|
|
|
|
EnterCriticalSection(&File_DirHandles_CritSec);
|
|
|
|
merge = List_Head(&File_DirHandles);
|
|
while (merge) {
|
|
FILE_MERGE *next = List_Next(merge);
|
|
if (merge->handle == FileHandle) {
|
|
List_Remove(&File_DirHandles, merge);
|
|
File_MergeFree(merge);
|
|
}
|
|
merge = next;
|
|
}
|
|
|
|
LeaveCriticalSection(&File_DirHandles_CritSec);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_DeleteDirectory
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_DeleteDirectory(const WCHAR *FilePath, BOOLEAN JustCheck)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG FilePath_len;
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
UNICODE_STRING objname;
|
|
HANDLE handle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
ULONG info_len;
|
|
FILE_DIRECTORY_INFORMATION *info;
|
|
BOOLEAN is_dot1, is_dot2;
|
|
BOOLEAN RestartScan;
|
|
|
|
//
|
|
// open the directory
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
FilePath_len = wcslen(FilePath) * sizeof(WCHAR);
|
|
|
|
objname.Length = (USHORT)FilePath_len;
|
|
objname.MaximumLength = (USHORT)(objname.Length + sizeof(WCHAR));
|
|
objname.Buffer = (WCHAR *)FilePath;
|
|
|
|
status = __sys_NtCreateFile(
|
|
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL, 0,
|
|
FILE_SHARE_VALID_FLAGS, FILE_OPEN,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
|
|
|
if (! NT_SUCCESS(status))
|
|
return status;
|
|
|
|
//
|
|
// make sure no child files exist, other than "." and "..".
|
|
// we use our NtQueryDirectoryFile here, so it merges from
|
|
// both directories, and discards deleted entries
|
|
//
|
|
|
|
info_len = sizeof(FILE_DIRECTORY_INFORMATION)
|
|
+ 300 * sizeof(WCHAR);
|
|
info = (FILE_DIRECTORY_INFORMATION *)Dll_Alloc(info_len);
|
|
|
|
RestartScan = TRUE;
|
|
|
|
while (NT_SUCCESS(status)) {
|
|
|
|
status = NtQueryDirectoryFile(
|
|
handle, NULL, NULL, NULL, &IoStatusBlock,
|
|
info, info_len, FileDirectoryInformation,
|
|
TRUE, NULL, RestartScan);
|
|
|
|
RestartScan = FALSE;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
is_dot1 = (
|
|
info->FileNameLength == sizeof(WCHAR) &&
|
|
info->FileName[0] == L'.');
|
|
|
|
is_dot2 = (
|
|
info->FileNameLength == sizeof(WCHAR) * 2 &&
|
|
info->FileName[0] == L'.' &&
|
|
info->FileName[1] == L'.');
|
|
|
|
if (! (is_dot1 || is_dot2))
|
|
status = STATUS_DIRECTORY_NOT_EMPTY;
|
|
}
|
|
}
|
|
|
|
if (status == STATUS_NO_MORE_FILES || status == STATUS_NO_SUCH_FILE)
|
|
status = STATUS_SUCCESS;
|
|
|
|
if ((! NT_SUCCESS(status)) || JustCheck) {
|
|
Dll_Free(info);
|
|
NtClose(handle);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// now delete any files that physically exist within the copy
|
|
// directory, even if marked deleted. for this, we query the
|
|
// copy directory by directly calling the system
|
|
// NtQueryDirectoryFile with the copy FileHandle.
|
|
//
|
|
|
|
RestartScan = TRUE;
|
|
status = STATUS_SUCCESS;
|
|
|
|
while (NT_SUCCESS(status)) {
|
|
|
|
status = __sys_NtQueryDirectoryFile(
|
|
handle, NULL, NULL, NULL, &IoStatusBlock,
|
|
info, info_len, FileDirectoryInformation,
|
|
TRUE, NULL, RestartScan);
|
|
|
|
RestartScan = FALSE;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
is_dot1 = (
|
|
info->FileNameLength == sizeof(WCHAR) &&
|
|
info->FileName[0] == L'.');
|
|
|
|
is_dot2 = (
|
|
info->FileNameLength == sizeof(WCHAR) * 2 &&
|
|
info->FileName[0] == L'.' &&
|
|
info->FileName[1] == L'.');
|
|
|
|
if (! (is_dot1 || is_dot2)) {
|
|
|
|
ULONG TempPath_len = FilePath_len + info->FileNameLength
|
|
+ 2 * sizeof(WCHAR);
|
|
WCHAR *TempPath = Dll_AllocTemp(TempPath_len);
|
|
WCHAR *TempPtr = TempPath;
|
|
memcpy(TempPtr, FilePath, FilePath_len);
|
|
TempPtr += FilePath_len / sizeof(WCHAR);
|
|
*TempPtr = L'\\';
|
|
++TempPtr;
|
|
memcpy(TempPtr, info->FileName, info->FileNameLength);
|
|
TempPtr += info->FileNameLength / sizeof(WCHAR);
|
|
*TempPtr = L'\0';
|
|
|
|
objname.Length = (USHORT)(TempPath_len - sizeof(WCHAR));
|
|
objname.MaximumLength = (USHORT)TempPath_len;
|
|
objname.Buffer = TempPath;
|
|
|
|
status = __sys_NtDeleteFile(&objattrs);
|
|
|
|
Dll_Free(TempPath);
|
|
|
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// finish
|
|
//
|
|
|
|
Dll_Free(info);
|
|
NtClose(handle);
|
|
|
|
if (status == STATUS_NO_MORE_FILES || status == STATUS_NO_SUCH_FILE)
|
|
status = STATUS_SUCCESS;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MarkChildrenDeleted
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_MarkChildrenDeleted(const WCHAR *ParentTruePath)
|
|
{
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
|
|
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
UNICODE_STRING objname;
|
|
HANDLE handle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
ULONG info_len;
|
|
FILE_DIRECTORY_INFORMATION *info;
|
|
BOOLEAN is_dot1, is_dot2;
|
|
BOOLEAN RestartScan;
|
|
WCHAR *TruePath, *CopyPath;
|
|
|
|
//
|
|
// open the TruePath directory
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
RtlInitUnicodeString(&objname, ParentTruePath);
|
|
|
|
status = __sys_NtCreateFile(
|
|
&handle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, NULL, 0,
|
|
FILE_SHARE_VALID_FLAGS, FILE_OPEN,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
|
|
|
if (! NT_SUCCESS(status))
|
|
return status;
|
|
|
|
//
|
|
// enumerate the children in the TruePath directory. we can
|
|
// use the system NtQueryDirectoryFile because we know there is
|
|
// nothing to merge (CopyPath was just created and is empty)
|
|
//
|
|
|
|
info_len = sizeof(FILE_DIRECTORY_INFORMATION)
|
|
+ 300 * sizeof(WCHAR);
|
|
info = (FILE_DIRECTORY_INFORMATION *)Dll_Alloc(info_len);
|
|
|
|
RestartScan = TRUE;
|
|
|
|
while (NT_SUCCESS(status)) {
|
|
|
|
//status = __sys_NtQueryDirectoryFile(
|
|
status = NtQueryDirectoryFile(
|
|
handle, NULL, NULL, NULL, &IoStatusBlock,
|
|
info, info_len, FileDirectoryInformation,
|
|
TRUE, NULL, RestartScan);
|
|
|
|
RestartScan = FALSE;
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
is_dot1 = (
|
|
info->FileNameLength == sizeof(WCHAR) &&
|
|
info->FileName[0] == L'.');
|
|
|
|
is_dot2 = (
|
|
info->FileNameLength == sizeof(WCHAR) * 2 &&
|
|
info->FileName[0] == L'.' &&
|
|
info->FileName[1] == L'.');
|
|
|
|
if (! (is_dot1 || is_dot2)) {
|
|
|
|
//
|
|
// check if the file is an OpenFilePath
|
|
//
|
|
|
|
ULONG FileFlags, mp_flags;
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
objname.Length = (USHORT)info->FileNameLength;
|
|
objname.MaximumLength = objname.Length;
|
|
objname.Buffer = info->FileName;
|
|
|
|
status = File_GetName(
|
|
handle, &objname, &TruePath, &CopyPath, &FileFlags);
|
|
|
|
if (NT_SUCCESS(status))
|
|
mp_flags = File_MatchPath(TruePath, &FileFlags);
|
|
else
|
|
mp_flags = PATH_CLOSED_FLAG;
|
|
|
|
if (! mp_flags) {
|
|
|
|
//
|
|
// mark the child deleted in the copy directory
|
|
//
|
|
|
|
HANDLE handle2;
|
|
FILE_BASIC_INFORMATION info;
|
|
|
|
RtlInitUnicodeString(&objname, CopyPath);
|
|
|
|
status = __sys_NtCreateFile(
|
|
&handle2, FILE_GENERIC_WRITE, &objattrs,
|
|
&IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
|
|
FILE_SUPERSEDE, FILE_NON_DIRECTORY_FILE |
|
|
FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
memzero(&info, sizeof(FILE_BASIC_INFORMATION));
|
|
|
|
info.CreationTime.HighPart = DELETE_MARK_HIGH;
|
|
info.CreationTime.LowPart = DELETE_MARK_LOW;
|
|
|
|
status = __sys_NtSetInformationFile(
|
|
handle2, &IoStatusBlock,
|
|
&info, sizeof(FILE_BASIC_INFORMATION),
|
|
FileBasicInformation);
|
|
|
|
NtClose(handle2);
|
|
}
|
|
}
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
Dll_Free(info);
|
|
NtClose(handle);
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_RtlGetCurrentDirectory_U
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX ULONG File_RtlGetCurrentDirectory_U(ULONG buf_len, WCHAR *buf_ptr)
|
|
{
|
|
ULONG LastError;
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
|
|
|
|
WCHAR *TruePath;
|
|
WCHAR *path;
|
|
ULONG len, is_root;
|
|
|
|
//
|
|
// handle a recursive invocation of RtlGetCurrentDirectory_U
|
|
//
|
|
|
|
if (TlsData->file_GetCurDir_lock)
|
|
return __sys_RtlGetCurrentDirectory_U(buf_len, buf_ptr);
|
|
|
|
//
|
|
// get the current directory from the system
|
|
//
|
|
|
|
path = Dll_AllocTemp(8192);
|
|
len = __sys_RtlGetCurrentDirectory_U(8190, path);
|
|
if (len > 8190) {
|
|
Dll_Free(path);
|
|
SetLastError(LastError);
|
|
return __sys_RtlGetCurrentDirectory_U(buf_len, buf_ptr);
|
|
}
|
|
|
|
TlsData->file_GetCurDir_lock = TRUE;
|
|
|
|
//
|
|
// check if we already handled this directory
|
|
//
|
|
|
|
EnterCriticalSection(&File_CurDir_CritSec);
|
|
|
|
if (File_CurDir_LastInput && _wcsicmp(path, File_CurDir_LastInput) == 0)
|
|
goto already_handled;
|
|
|
|
//
|
|
// not handled yet, do handling now
|
|
//
|
|
|
|
if (File_CurDir_LastOutput)
|
|
Dll_Free(File_CurDir_LastOutput);
|
|
File_CurDir_LastOutput = NULL;
|
|
|
|
if (File_CurDir_LastInput)
|
|
Dll_Free(File_CurDir_LastInput);
|
|
len = wcslen(path) + 1;
|
|
File_CurDir_LastInput = Dll_Alloc(len * sizeof(WCHAR));
|
|
wmemcpy(File_CurDir_LastInput, path, len);
|
|
|
|
is_root = 0;
|
|
if (len >= 2 && path[len - 2] == L'\\')
|
|
is_root = 1;
|
|
|
|
TruePath = File_GetTruePathForBoxedPath(path, TRUE);
|
|
|
|
if (TruePath) {
|
|
|
|
Dll_Free(path);
|
|
path = TruePath;
|
|
|
|
len = wcslen(path) + 1;
|
|
if (len >= 2 && path[len - 2] == L'\\')
|
|
is_root = 0;
|
|
|
|
File_CurDir_LastOutput =
|
|
Dll_Alloc((len + is_root) * sizeof(WCHAR));
|
|
wmemcpy(File_CurDir_LastOutput, path, len);
|
|
if (is_root)
|
|
wmemcpy(File_CurDir_LastOutput + len - 1, L"\\\0", 2);
|
|
}
|
|
|
|
//
|
|
// return the handled directory
|
|
//
|
|
|
|
already_handled:
|
|
|
|
if (File_CurDir_LastOutput) {
|
|
|
|
len = (wcslen(File_CurDir_LastOutput) + 1) * sizeof(WCHAR);
|
|
if (buf_len >= len) {
|
|
memcpy(buf_ptr, File_CurDir_LastOutput, len);
|
|
len -= sizeof(WCHAR);
|
|
}
|
|
|
|
LeaveCriticalSection(&File_CurDir_CritSec);
|
|
|
|
} else {
|
|
|
|
LeaveCriticalSection(&File_CurDir_CritSec);
|
|
len = __sys_RtlGetCurrentDirectory_U(buf_len, buf_ptr);
|
|
}
|
|
|
|
Dll_Free(path);
|
|
|
|
TlsData->file_GetCurDir_lock = FALSE;
|
|
SetLastError(LastError);
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_RtlSetCurrentDirectory_U
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_RtlSetCurrentDirectory_U(UNICODE_STRING *PathName)
|
|
{
|
|
ULONG LastError;
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
|
|
|
|
NTSTATUS status = __sys_RtlSetCurrentDirectory_U(PathName);
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// RtlGetFullPathName_U gets the directory directly from the
|
|
// PEB rather than through RtlGetCurrentDirectory_U, so
|
|
// we could have a discrepancy between what the PEB contains
|
|
// and what our RtlGetFullPathName_U would return. fix it
|
|
// by calling RtlSetCurrentDirectory_U again
|
|
//
|
|
|
|
WCHAR *path = Dll_AllocTemp(8192);
|
|
|
|
ULONG len = File_RtlGetCurrentDirectory_U(8190, path);
|
|
if (len <= 8190) {
|
|
|
|
UNICODE_STRING uni;
|
|
path[8190 / sizeof(WCHAR)] = L'\0';
|
|
RtlInitUnicodeString(&uni, path);
|
|
|
|
status = __sys_RtlSetCurrentDirectory_U(&uni);
|
|
}
|
|
|
|
Dll_Free(path);
|
|
}
|
|
|
|
SetLastError(LastError);
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_CallRtlGetFullPathName
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX ULONG File_CallRtlGetFullPathName(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr)
|
|
{
|
|
ULONG ret_len;
|
|
|
|
if (__sys_RtlGetFullPathName_UEx) {
|
|
|
|
__sys_RtlGetFullPathName_UEx(
|
|
src, buf_len, buf_ptr, file_part_ptr, &ret_len);
|
|
|
|
} else {
|
|
|
|
ret_len = __sys_RtlGetFullPathName_U(
|
|
src, buf_len, buf_ptr, file_part_ptr);
|
|
}
|
|
|
|
return ret_len;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_RtlGetFullPathName_U
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX ULONG File_RtlGetFullPathName_U(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr)
|
|
{
|
|
ULONG LastError;
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(&LastError);
|
|
|
|
WCHAR *temp_buf;
|
|
WCHAR *temp_ptr;
|
|
ULONG ret_len;
|
|
|
|
if (src && File_CurDir_LastOutput && src[0] == *File_CurDir_LastOutput
|
|
&& src[1] == L':' && src[2] == L'.' && src[3] == L'\0') {
|
|
|
|
//
|
|
// if invoked for "X:." where X is the result of the translation
|
|
// done in File_RtlGetCurrentDirectory_U, then we need to override
|
|
// C:. with the result from File_RtlGetCurrentDirectory_U, to
|
|
// make sure the correct current directory is returned
|
|
//
|
|
|
|
return File_CallRtlGetFullPathName(
|
|
File_CurDir_LastOutput, buf_len, buf_ptr, file_part_ptr);
|
|
}
|
|
|
|
//
|
|
// remove sandbox prefix, except during process creation. the child
|
|
// process must have a real directory path, even if sandboxed
|
|
//
|
|
|
|
if (TlsData->proc_create_process) {
|
|
|
|
return File_CallRtlGetFullPathName(
|
|
src, buf_len, buf_ptr, file_part_ptr);
|
|
}
|
|
|
|
//
|
|
// get the path into an intermediate buffer that should be large
|
|
// enough for any possible path. but reset caller's buffers just
|
|
// as RtlGetFullPathName_U would do if it worked on those buffers
|
|
//
|
|
|
|
if (buf_ptr && buf_len >= sizeof(WCHAR))
|
|
*buf_ptr = L'\0';
|
|
|
|
if (file_part_ptr)
|
|
*file_part_ptr = NULL;
|
|
|
|
temp_buf = Dll_AllocTemp(8192);
|
|
temp_ptr = NULL;
|
|
|
|
ret_len = File_CallRtlGetFullPathName(src, 8192, temp_buf, &temp_ptr);
|
|
|
|
if (ret_len && ret_len <= 8192) {
|
|
|
|
//
|
|
// if the path we got is inside the sandbox, change it to
|
|
// the corresponding path outside the sandbox
|
|
//
|
|
|
|
BOOLEAN TrailingBackslash =
|
|
(temp_buf[ret_len / sizeof(WCHAR) - 1] == L'\\');
|
|
|
|
WCHAR *TruePath = File_GetTruePathForBoxedPath(temp_buf, TRUE);
|
|
|
|
if (TruePath) {
|
|
|
|
WCHAR *iptr = TruePath;
|
|
WCHAR *optr = temp_buf;
|
|
temp_ptr = NULL;
|
|
|
|
while (*iptr) {
|
|
*optr = *iptr;
|
|
++optr;
|
|
if (*iptr == L'\\')
|
|
temp_ptr = optr;
|
|
++iptr;
|
|
}
|
|
|
|
if (TrailingBackslash) {
|
|
*optr = L'\\';
|
|
++optr;
|
|
temp_ptr = optr;
|
|
}
|
|
|
|
*optr = L'\0';
|
|
ret_len = (optr - temp_buf) * sizeof(WCHAR);
|
|
|
|
Dll_Free(TruePath);
|
|
}
|
|
|
|
//
|
|
// copy the contents of the intermediate buffer to the caller
|
|
//
|
|
|
|
if (ret_len + sizeof(WCHAR) <= buf_len) {
|
|
|
|
memcpy(buf_ptr, temp_buf, ret_len);
|
|
buf_ptr[ret_len / sizeof(WCHAR)] = L'\0';
|
|
|
|
if (file_part_ptr && temp_ptr && *temp_ptr)
|
|
*file_part_ptr = temp_ptr - temp_buf + buf_ptr;
|
|
|
|
} else
|
|
ret_len += sizeof(WCHAR);
|
|
|
|
} else
|
|
ret_len = 0;
|
|
|
|
//
|
|
// finish
|
|
//
|
|
|
|
Dll_Free(temp_buf);
|
|
SetLastError(LastError);
|
|
return ret_len;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_RtlGetFullPathName_UEx
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_RtlGetFullPathName_UEx(
|
|
WCHAR *src, ULONG buf_len, WCHAR *buf_ptr, WCHAR **file_part_ptr,
|
|
ULONG *ret_len_ptr)
|
|
{
|
|
//
|
|
// On Windows 7, we have to hook RtlGetFullPathName_UEx instead of the
|
|
// normal version, but we can still use the normal version internally
|
|
//
|
|
|
|
ULONG ret_len =
|
|
File_RtlGetFullPathName_U(src, buf_len, buf_ptr, file_part_ptr);
|
|
if (ret_len_ptr)
|
|
*ret_len_ptr = ret_len;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_NtQueryVolumeInformationFile
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_NtQueryVolumeInformationFile(
|
|
HANDLE FileHandle,
|
|
IO_STATUS_BLOCK *IoStatusBlock,
|
|
PVOID FsInformation,
|
|
ULONG Length,
|
|
ULONG FsInformationClass)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE handle;
|
|
BOOLEAN IsBoxedPath;
|
|
WCHAR *path;
|
|
|
|
// NtQueryObject on a named pipe handle can hang when it is in pending read/write. See P.71 NT/2000 Native API Reference
|
|
// If the caller only asks about FileFsDeviceInformation and it is a named pipe, hook can return right away without the
|
|
// need to wait on NtQueryObject called by SbieDll_GetHandlePath
|
|
if (FsInformationClass == FileFsDeviceInformation && Length >= sizeof(FILE_FS_DEVICE_INFORMATION))
|
|
{
|
|
FILE_FS_DEVICE_INFORMATION devInfo = { 0 };
|
|
IO_STATUS_BLOCK ioStatusBlock = { 0 };
|
|
|
|
status = __sys_NtQueryVolumeInformationFile(FileHandle, &ioStatusBlock, &devInfo, sizeof(devInfo), FileFsDeviceInformation);
|
|
|
|
if (NT_SUCCESS(status) && devInfo.DeviceType == FILE_DEVICE_NAMED_PIPE)
|
|
{
|
|
if (IoStatusBlock)
|
|
*IoStatusBlock = ioStatusBlock;
|
|
|
|
memcpy(FsInformation, &devInfo, sizeof(devInfo));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if caller is querying volume info for \Sandbox\...\drive\X,
|
|
// then open the real drive X to get the correct result
|
|
//
|
|
|
|
handle = FileHandle;
|
|
|
|
status = SbieDll_GetHandlePath(FileHandle, NULL, &IsBoxedPath);
|
|
if (IsBoxedPath && (
|
|
NT_SUCCESS(status) || (status == STATUS_BAD_INITIAL_PC))) {
|
|
|
|
path = Dll_AllocTemp(8192);
|
|
status = SbieDll_GetHandlePath(FileHandle, path, NULL);
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
const FILE_DRIVE *drive =
|
|
File_GetDriveForPath(path, wcslen(path));
|
|
if (drive) {
|
|
|
|
//
|
|
// append a suffix backslash to open the drive root
|
|
//
|
|
|
|
UNICODE_STRING objname;
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
|
|
objname.Buffer = Dll_Alloc((drive->len + 4) * sizeof(WCHAR));
|
|
wmemcpy(objname.Buffer, drive->path, drive->len);
|
|
objname.Buffer[drive->len ] = L'\\';
|
|
objname.Buffer[drive->len + 1] = L'\0';
|
|
|
|
objname.Length = (USHORT)(drive->len + 1) * sizeof(WCHAR);
|
|
objname.MaximumLength = objname.Length + sizeof(WCHAR);
|
|
|
|
InitializeObjectAttributes(
|
|
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
if (FsInformationClass == FileFsSizeInformation ||
|
|
FsInformationClass == FileFsFullSizeInformation) {
|
|
|
|
//
|
|
// for these info classes we can use a simpler
|
|
// and faster open request, the same way the
|
|
// GetDiskFreeSpace API does it
|
|
//
|
|
|
|
status = __sys_NtOpenFile(
|
|
&handle, FILE_LIST_DIRECTORY | SYNCHRONIZE, &objattrs,
|
|
IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_DIRECTORY_FILE |
|
|
FILE_OPEN_FOR_FREE_SPACE_QUERY);
|
|
|
|
} else {
|
|
|
|
status = __sys_NtCreateFile(
|
|
&handle, GENERIC_READ | SYNCHRONIZE, &objattrs,
|
|
IoStatusBlock, NULL, 0, FILE_SHARE_VALID_FLAGS,
|
|
FILE_OPEN,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL, 0);
|
|
}
|
|
|
|
Dll_Free(objname.Buffer);
|
|
|
|
if (! NT_SUCCESS(status))
|
|
handle = FileHandle;
|
|
|
|
LeaveCriticalSection(File_DrivesAndLinks_CritSec);
|
|
}
|
|
}
|
|
|
|
Dll_Free(path);
|
|
}
|
|
|
|
|
|
status = __sys_NtQueryVolumeInformationFile(
|
|
handle, IoStatusBlock, FsInformation, Length, FsInformationClass);
|
|
|
|
if (handle != FileHandle)
|
|
__sys_NtClose(handle);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_CanonizePath
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
WCHAR* File_CanonizePath(const wchar_t* absolute_path, ULONG abs_path_len, const wchar_t* relative_path, ULONG rel_path_len)
|
|
{
|
|
ULONG i, j;
|
|
|
|
while(absolute_path[abs_path_len-1] == L'\\')
|
|
abs_path_len--;
|
|
|
|
WCHAR* result = Dll_Alloc((abs_path_len + rel_path_len + 1) * sizeof(wchar_t));
|
|
if (!result) return NULL;
|
|
wcsncpy(result, absolute_path, abs_path_len);
|
|
result[abs_path_len] = 0;
|
|
|
|
for (i = 0; i < rel_path_len; ) {
|
|
|
|
if (relative_path[i] == L'.' && relative_path[i + 1] == L'.' && (relative_path[i + 2] == L'\\' || relative_path[i + 2] == L'\0')) {
|
|
|
|
for (j = abs_path_len - 1; j >= 0 && result[j] != L'\\'; --j)
|
|
result[j] = L'\0';
|
|
if (j >= 0)
|
|
result[j] = L'\0';
|
|
|
|
abs_path_len = j;
|
|
|
|
i += 3;
|
|
|
|
} else if (relative_path[i] == L'.') {
|
|
|
|
i += 2;
|
|
|
|
} else {
|
|
|
|
for (j = i; j < rel_path_len && relative_path[j] != L'\\' && relative_path[j] != L'\0'; ++j)
|
|
;
|
|
|
|
result[abs_path_len] = L'\\';
|
|
wcsncpy(result + abs_path_len + 1, &relative_path[i], j - i);
|
|
result[abs_path_len + j - i + 1] = L'\0';
|
|
|
|
abs_path_len += j - i + 1;
|
|
|
|
i = j + 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_SetReparsePoint
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_SetReparsePoint(
|
|
HANDLE FileHandle, PREPARSE_DATA_BUFFER Data, ULONG DataLen)
|
|
{
|
|
THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
|
|
|
|
NTSTATUS status;
|
|
UNICODE_STRING objname;
|
|
OBJECT_ATTRIBUTES objattrs;
|
|
WCHAR *TruePath, *CopyPath;
|
|
//WCHAR *SourcePath = NULL, *TargetPath = NULL;
|
|
WCHAR* AbsolutePath = NULL;
|
|
ULONG FileFlags, mp_flags;
|
|
PREPARSE_DATA_BUFFER NewData = NULL;
|
|
ULONG NewDataLen;
|
|
IO_STATUS_BLOCK MyIoStatusBlock;
|
|
BOOLEAN MigrateTarget = FALSE;
|
|
|
|
if (! Data)
|
|
return STATUS_BAD_INITIAL_PC;
|
|
|
|
//
|
|
// get paths to source and target directories
|
|
//
|
|
|
|
Dll_PushTlsNameBuffer(TlsData);
|
|
|
|
__try {
|
|
USHORT SubstituteNameLength;
|
|
WCHAR* SubstituteNameBuffer;
|
|
USHORT PrintNameLength;
|
|
WCHAR* PrintNameBuffer;
|
|
|
|
//
|
|
// get copy path of reparse source
|
|
//
|
|
|
|
RtlInitUnicodeString(&objname, L"");
|
|
|
|
InitializeObjectAttributes(
|
|
&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
status = File_GetName(
|
|
FileHandle, &objname, &TruePath, &CopyPath, &FileFlags);
|
|
if (! NT_SUCCESS(status))
|
|
__leave;
|
|
|
|
//
|
|
// check if this is an open or closed path
|
|
//
|
|
|
|
mp_flags = File_MatchPath(TruePath, &FileFlags);
|
|
|
|
if (PATH_IS_OPEN(mp_flags)) {
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
__leave;
|
|
}
|
|
|
|
if (PATH_IS_CLOSED(mp_flags)) {
|
|
status = STATUS_ACCESS_DENIED;
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// get the absolute reparse target path
|
|
//
|
|
|
|
if (Data->ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
|
{
|
|
SubstituteNameLength = Data->SymbolicLinkReparseBuffer.SubstituteNameLength;
|
|
SubstituteNameBuffer = &Data->SymbolicLinkReparseBuffer.PathBuffer[Data->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
|
|
PrintNameLength = Data->SymbolicLinkReparseBuffer.PrintNameLength;
|
|
PrintNameBuffer = &Data->SymbolicLinkReparseBuffer.PathBuffer[Data->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR)];
|
|
if (Data->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) {
|
|
|
|
WCHAR* LinkName = wcsrchr(TruePath, L'\\');
|
|
AbsolutePath = File_CanonizePath(TruePath, (ULONG)(LinkName - TruePath), SubstituteNameBuffer, SubstituteNameLength / sizeof(wchar_t));
|
|
}
|
|
|
|
NewDataLen = (UFIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - UFIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer));
|
|
}
|
|
else if (Data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
SubstituteNameLength = Data->MountPointReparseBuffer.SubstituteNameLength;
|
|
SubstituteNameBuffer = &Data->MountPointReparseBuffer.PathBuffer[Data->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
|
|
PrintNameLength = Data->MountPointReparseBuffer.PrintNameLength;
|
|
PrintNameBuffer = &Data->MountPointReparseBuffer.PathBuffer[Data->MountPointReparseBuffer.PrintNameOffset/sizeof(WCHAR)];
|
|
|
|
NewDataLen = (UFIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) - UFIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer));
|
|
}
|
|
else {
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
__leave;
|
|
}
|
|
|
|
//if (File_Snapshot != NULL){
|
|
// WCHAR* TmplName = File_FindSnapshotPath(CopyPath);
|
|
// if (TmplName) CopyPath = TmplName;
|
|
//}
|
|
|
|
//SourcePath = Dll_Alloc((wcslen(CopyPath) + 4) * sizeof(WCHAR));
|
|
//wcscpy(SourcePath, CopyPath);
|
|
|
|
//
|
|
// get copy path of reparse target
|
|
//
|
|
|
|
if (AbsolutePath) {
|
|
objname.Length = wcslen(AbsolutePath) * sizeof(wchar_t);
|
|
objname.Buffer = AbsolutePath;
|
|
} else {
|
|
objname.Length = SubstituteNameLength;
|
|
objname.Buffer = SubstituteNameBuffer;
|
|
}
|
|
objname.MaximumLength = objname.Length;
|
|
|
|
status = File_GetName(NULL, &objname, &TruePath, &CopyPath, NULL);
|
|
if (! NT_SUCCESS(status))
|
|
__leave;
|
|
|
|
if (AbsolutePath) {
|
|
|
|
//
|
|
// We can allow for a relative path in the box but must ensure the hatget gets migrated
|
|
//
|
|
|
|
MigrateTarget = TRUE;
|
|
status = STATUS_BAD_INITIAL_PC;
|
|
__leave;
|
|
}
|
|
|
|
//TargetPath = Dll_Alloc((wcslen(CopyPath) + 4) * sizeof(WCHAR));
|
|
//wcscpy(TargetPath, CopyPath);
|
|
|
|
WCHAR* NewSubstituteNameBuffer = CopyPath;
|
|
WCHAR* OldPrintNameBuffer = PrintNameBuffer; // we don't need to change the display name
|
|
|
|
if (Data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
|
|
|
SbieDll_TranslateNtToDosPath(NewSubstituteNameBuffer);
|
|
memmove(NewSubstituteNameBuffer + 4, NewSubstituteNameBuffer, (wcslen(NewSubstituteNameBuffer) + 1) * sizeof(wchar_t));
|
|
wcsncpy(NewSubstituteNameBuffer, L"\\??\\", 4);
|
|
}
|
|
|
|
SubstituteNameLength = wcslen(NewSubstituteNameBuffer) * sizeof(WCHAR);
|
|
|
|
NewDataLen += SubstituteNameLength + sizeof(WCHAR) + PrintNameLength + sizeof(WCHAR) + 8;
|
|
NewData = Dll_Alloc(NewDataLen);
|
|
memzero(NewData, sizeof(REPARSE_DATA_BUFFER));
|
|
|
|
NewData->ReparseTag = Data->ReparseTag;
|
|
NewData->Reserved = 0; //Data->Reserved;
|
|
NewData->ReparseDataLength = (USHORT)NewDataLen - 8;
|
|
|
|
if (NewData->ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
|
{
|
|
NewData->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
|
|
NewData->SymbolicLinkReparseBuffer.SubstituteNameLength = SubstituteNameLength;
|
|
SubstituteNameBuffer = &NewData->SymbolicLinkReparseBuffer.PathBuffer[NewData->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
|
|
NewData->SymbolicLinkReparseBuffer.PrintNameLength = PrintNameLength;
|
|
NewData->SymbolicLinkReparseBuffer.PrintNameOffset = SubstituteNameLength + sizeof(WCHAR);
|
|
PrintNameBuffer = &NewData->SymbolicLinkReparseBuffer.PathBuffer[NewData->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR)];
|
|
|
|
}
|
|
else if (NewData->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
NewData->MountPointReparseBuffer.SubstituteNameOffset = 0;
|
|
NewData->MountPointReparseBuffer.SubstituteNameLength = SubstituteNameLength;
|
|
SubstituteNameBuffer = &NewData->MountPointReparseBuffer.PathBuffer[NewData->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
|
|
NewData->MountPointReparseBuffer.PrintNameLength = PrintNameLength;
|
|
NewData->MountPointReparseBuffer.PrintNameOffset = SubstituteNameLength + sizeof(WCHAR);
|
|
PrintNameBuffer = &NewData->MountPointReparseBuffer.PathBuffer[NewData->MountPointReparseBuffer.PrintNameOffset/sizeof(WCHAR)];
|
|
}
|
|
|
|
memcpy(SubstituteNameBuffer, NewSubstituteNameBuffer, SubstituteNameLength + sizeof(WCHAR));
|
|
memcpy(PrintNameBuffer, OldPrintNameBuffer, PrintNameLength + sizeof(WCHAR));
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
status = GetExceptionCode();
|
|
}
|
|
|
|
//
|
|
// since Curt's code in the driver handles reparsing and the driver is no longer blocking this operation,
|
|
// we can do it directly without the need to ask our service
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
status = __sys_NtFsControlFile(
|
|
FileHandle, NULL, NULL, NULL,
|
|
&MyIoStatusBlock, FSCTL_SET_REPARSE_POINT,
|
|
NewData, NewDataLen,
|
|
NULL, 0);
|
|
|
|
MigrateTarget = NT_SUCCESS(status);
|
|
}
|
|
|
|
/*
|
|
//
|
|
// send request to SbieSvc
|
|
//
|
|
|
|
if (SourcePath && TargetPath) {
|
|
|
|
ULONG req_len;
|
|
ULONG dst_ofs;
|
|
WCHAR *dst_path;
|
|
FILE_SET_REPARSE_POINT_REQ *req;
|
|
MSG_HEADER *rpl;
|
|
|
|
req_len = sizeof(FILE_SET_REPARSE_POINT_REQ)
|
|
+ (wcslen(SourcePath) + 4) * sizeof(WCHAR);
|
|
dst_ofs = req_len;
|
|
req_len += (wcslen(TargetPath) + 4) * sizeof(WCHAR);
|
|
|
|
req = (FILE_SET_REPARSE_POINT_REQ *)Dll_Alloc(req_len);
|
|
req->h.length = req_len;
|
|
req->h.msgid = MSGID_FILE_SET_REPARSE_POINT;
|
|
|
|
wcscpy(req->src_path, SourcePath);
|
|
req->src_path_len = wcslen(req->src_path) * sizeof(WCHAR);
|
|
|
|
dst_path = (WCHAR *)((UCHAR *)req + dst_ofs);
|
|
wcscpy(dst_path, TargetPath);
|
|
req->dst_path_ofs = dst_ofs;
|
|
req->dst_path_len = wcslen(dst_path) * sizeof(WCHAR);
|
|
|
|
rpl = SbieDll_CallServer(&req->h);
|
|
|
|
Dll_Free(req);
|
|
|
|
if (rpl) {
|
|
status = rpl->status;
|
|
Dll_Free(rpl);
|
|
} else
|
|
status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (SourcePath)
|
|
Dll_Free(SourcePath);
|
|
if (TargetPath)
|
|
Dll_Free(TargetPath);*/
|
|
|
|
if (MigrateTarget) {
|
|
|
|
//
|
|
// We must migrate the file or directory into the sandbox as the path reparsing by NtCreateFile
|
|
// is done by the kernel and we do not "manually" reparse the path before invoking it,
|
|
// hence there must be the expected file at the path we are linking to.
|
|
//
|
|
|
|
HANDLE SourceHandle;
|
|
if (NT_SUCCESS(File_OpenForRenameFile(&SourceHandle, TruePath)))
|
|
NtClose(SourceHandle);
|
|
}
|
|
|
|
if (AbsolutePath)
|
|
Dll_Free(AbsolutePath);
|
|
|
|
if (NewData)
|
|
Dll_Free(NewData);
|
|
|
|
Dll_PopTlsNameBuffer(TlsData);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_MyQueryDirectoryFile
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
_FX NTSTATUS File_MyQueryDirectoryFile(
|
|
HANDLE FileHandle,
|
|
void *FileInformation,
|
|
ULONG Length,
|
|
FILE_INFORMATION_CLASS FileInformationClass,
|
|
BOOLEAN ReturnSingleEntry,
|
|
UNICODE_STRING *FileMask,
|
|
BOOLEAN RestartScan)
|
|
{
|
|
IO_STATUS_BLOCK MyIoStatusBlock;
|
|
|
|
if (! __sys_NtQueryDirectoryFile)
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
return __sys_NtQueryDirectoryFile(
|
|
FileHandle, NULL, NULL, NULL, &MyIoStatusBlock,
|
|
FileInformation, Length, FileInformationClass,
|
|
ReturnSingleEntry, FileMask, RestartScan);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// File_CreateBaseFolders
|
|
//---------------------------------------------------------------------------
|
|
|
|
//#include <Knownfolders.h>
|
|
|
|
_FX void File_CreateBaseFolders()
|
|
{
|
|
//
|
|
// in privacy mode we need to pre create some folders or else programs may fail
|
|
//
|
|
|
|
//File_CreateBoxedPath(File_SysVolume);
|
|
//
|
|
//if (SbieApi_QueryConfBool(NULL, L"SeparateUserFolders", TRUE)) {
|
|
// File_CreateBoxedPath(File_AllUsers);
|
|
// File_CreateBoxedPath(File_CurrentUser);
|
|
//}
|
|
|
|
WCHAR* Folders[] = { L"SystemRoot", L"TEMP", L"USERPROFILE", //L"windir",
|
|
L"PUBLIC", L"ProgramData", L"LOCALAPPDATA", L"ALLUSERSPROFILE", L"APPDATA",
|
|
L"ProgramFiles", L"ProgramFiles(x86)", L"ProgramW6432",
|
|
//L"CommonProgramFiles", L"CommonProgramFiles(x86)", L"CommonProgramW6432",
|
|
NULL };
|
|
WCHAR path[MAX_PATH];
|
|
for (WCHAR** Folder = Folders; *Folder; Folder++) {
|
|
path[0] = 0;
|
|
GetEnvironmentVariable(*Folder, path, sizeof(path));
|
|
if (path[0]) {
|
|
WCHAR* pathNT = File_TranslateDosToNtPath(path);
|
|
if (pathNT) {
|
|
File_CreateBoxedPath(pathNT);
|
|
Dll_Free(pathNT);
|
|
}
|
|
}
|
|
}
|
|
}
|