mirror of
https://github.com/reactos/reactos.git
synced 2024-08-23 19:58:25 +00:00
967 lines
33 KiB
C
967 lines
33 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS system libraries
|
|
* FILE: dll/win32/kernel32/client/file/dir.c
|
|
* PURPOSE: Directory functions
|
|
* PROGRAMMER: Pierre Schweitzer (pierre@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <k32.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* Short File Name length in chars (8.3) */
|
|
#define SFN_LENGTH 12
|
|
|
|
/* Match a volume name like:
|
|
* \\?\Volume{GUID}
|
|
*/
|
|
#define IS_VOLUME_NAME(s, l) \
|
|
((l == 96 || (l == 98 && s[48] == '\\')) && \
|
|
s[0] == '\\'&& (s[1] == '?' || s[1] == '\\') && \
|
|
s[2] == '?' && s[3] == '\\' && s[4] == 'V' && \
|
|
s[5] == 'o' && s[6] == 'l' && s[7] == 'u' && \
|
|
s[8] == 'm' && s[9] == 'e' && s[10] == '{' && \
|
|
s[19] == '-' && s[24] == '-' && s[29] == '-' && \
|
|
s[34] == '-' && s[47] == '}')
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
CreateDirectoryA(IN LPCSTR lpPathName,
|
|
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes)
|
|
{
|
|
PUNICODE_STRING PathNameW;
|
|
|
|
PathNameW = Basep8BitStringToStaticUnicodeString(lpPathName);
|
|
if (!PathNameW)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return CreateDirectoryW(PathNameW->Buffer,
|
|
lpSecurityAttributes);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
CreateDirectoryExA(IN LPCSTR lpTemplateDirectory,
|
|
IN LPCSTR lpNewDirectory,
|
|
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes)
|
|
{
|
|
PUNICODE_STRING TemplateDirectoryW;
|
|
UNICODE_STRING NewDirectoryW;
|
|
BOOL ret;
|
|
|
|
TemplateDirectoryW = Basep8BitStringToStaticUnicodeString(lpTemplateDirectory);
|
|
if (!TemplateDirectoryW)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!Basep8BitStringToDynamicUnicodeString(&NewDirectoryW, lpNewDirectory))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ret = CreateDirectoryExW(TemplateDirectoryW->Buffer,
|
|
NewDirectoryW.Buffer,
|
|
lpSecurityAttributes);
|
|
|
|
RtlFreeUnicodeString(&NewDirectoryW);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
CreateDirectoryW(IN LPCWSTR lpPathName,
|
|
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes)
|
|
{
|
|
DWORD Length;
|
|
NTSTATUS Status;
|
|
HANDLE DirectoryHandle;
|
|
UNICODE_STRING NtPathU;
|
|
PWSTR PathUBuffer, FilePart;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
RTL_RELATIVE_NAME_U RelativeName;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
/* Get relative name */
|
|
if (!RtlDosPathNameToRelativeNtPathName_U(lpPathName, &NtPathU, NULL, &RelativeName))
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Check if path length is < MAX_PATH (with space for file name).
|
|
* If not, prefix is required.
|
|
*/
|
|
if (NtPathU.Length > (MAX_PATH - SFN_LENGTH) * sizeof(WCHAR) && lpPathName[0] != L'\\' &&
|
|
lpPathName[1] != L'\\' && lpPathName[2] != L'?' && lpPathName[3] != L'\\')
|
|
{
|
|
/* Get file name position and full path length */
|
|
Length = GetFullPathNameW(lpPathName, 0, NULL, &FilePart);
|
|
if (Length == 0)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathU.Buffer);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Keep place for 8.3 file name */
|
|
Length += SFN_LENGTH;
|
|
/* No prefix, so, must be smaller than MAX_PATH */
|
|
if (Length > MAX_PATH)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(GetProcessHeap(), 0, NtPathU.Buffer);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Save buffer to allow later freeing */
|
|
PathUBuffer = NtPathU.Buffer;
|
|
|
|
/* If we have relative name (and root dir), use them instead */
|
|
if (RelativeName.RelativeName.Length != 0)
|
|
{
|
|
NtPathU.Length = RelativeName.RelativeName.Length;
|
|
NtPathU.MaximumLength = RelativeName.RelativeName.MaximumLength;
|
|
NtPathU.Buffer = RelativeName.RelativeName.Buffer;
|
|
}
|
|
else
|
|
{
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&NtPathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
(lpSecurityAttributes ? lpSecurityAttributes->lpSecurityDescriptor : NULL));
|
|
|
|
Status = NtCreateFile(&DirectoryHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
|
|
NULL,
|
|
0);
|
|
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
NtClose(DirectoryHandle);
|
|
return TRUE;
|
|
}
|
|
|
|
if (RtlIsDosDeviceName_U(lpPathName))
|
|
{
|
|
Status = STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
CreateDirectoryExW(IN LPCWSTR lpTemplateDirectory,
|
|
IN LPCWSTR lpNewDirectory,
|
|
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes)
|
|
{
|
|
DWORD Length;
|
|
NTSTATUS Status;
|
|
PVOID EaBuffer = NULL;
|
|
BOOL ReparsePoint = FALSE;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_EA_INFORMATION FileEaInfo;
|
|
ULONG EaLength = 0, StreamSize;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
FILE_BASIC_INFORMATION FileBasicInfo;
|
|
PREPARSE_DATA_BUFFER ReparseDataBuffer;
|
|
HANDLE TemplateHandle, DirectoryHandle;
|
|
PFILE_STREAM_INFORMATION FileStreamInfo;
|
|
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInfo;
|
|
UNICODE_STRING NtPathU, NtTemplatePathU, NewDirectory;
|
|
RTL_RELATIVE_NAME_U RelativeName, TemplateRelativeName;
|
|
PWSTR TemplateBuffer, PathUBuffer, FilePart, SubstituteName;
|
|
|
|
/* Get relative name of the template */
|
|
if (!RtlDosPathNameToRelativeNtPathName_U(lpTemplateDirectory, &NtTemplatePathU, NULL, &TemplateRelativeName))
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save buffer for further freeing */
|
|
TemplateBuffer = NtTemplatePathU.Buffer;
|
|
|
|
/* If we have relative name (and root dir), use them instead */
|
|
if (TemplateRelativeName.RelativeName.Length != 0)
|
|
{
|
|
NtTemplatePathU.Length = TemplateRelativeName.RelativeName.Length;
|
|
NtTemplatePathU.MaximumLength = TemplateRelativeName.RelativeName.MaximumLength;
|
|
NtTemplatePathU.Buffer = TemplateRelativeName.RelativeName.Buffer;
|
|
}
|
|
else
|
|
{
|
|
TemplateRelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&NtTemplatePathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Open template directory */
|
|
Status = NtOpenFile(&TemplateHandle,
|
|
FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_READ_EA,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status != STATUS_INVALID_PARAMETER)
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
OpenWithoutReparseSupport:
|
|
/* Opening failed due to lacking reparse points support in the FSD, try without */
|
|
Status = NtOpenFile(&TemplateHandle,
|
|
FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_READ_EA,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Request file attributes */
|
|
FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
Status = NtQueryInformationFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
&FileBasicInfo,
|
|
sizeof(FileBasicInfo),
|
|
FileBasicInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Request file attributes */
|
|
FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
Status = NtQueryInformationFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
&FileBasicInfo,
|
|
sizeof(FileBasicInfo),
|
|
FileBasicInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
/* If it is a reparse point, then get information about it */
|
|
if (FileBasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
{
|
|
Status = NtQueryInformationFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
&FileTagInfo,
|
|
sizeof(FileTagInfo),
|
|
FileAttributeTagInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Only mount points are supported, retry without if anything different */
|
|
if (FileTagInfo.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
CloseHandle(TemplateHandle);
|
|
goto OpenWithoutReparseSupport;
|
|
}
|
|
|
|
/* Mark we are playing with a reparse point */
|
|
ReparsePoint = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Get relative name of the directory */
|
|
if (!RtlDosPathNameToRelativeNtPathName_U(lpNewDirectory, &NtPathU, NULL, &RelativeName))
|
|
{
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
NtClose(TemplateHandle);
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save its buffer for further freeing */
|
|
PathUBuffer = NtPathU.Buffer;
|
|
|
|
/* Template & directory can't be the same */
|
|
if (RtlEqualUnicodeString(&NtPathU,
|
|
&NtTemplatePathU,
|
|
TRUE))
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
NtClose(TemplateHandle);
|
|
SetLastError(ERROR_INVALID_NAME);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlReleaseRelativeName(&TemplateRelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, TemplateBuffer);
|
|
|
|
/* Check if path length is < MAX_PATH (with space for file name).
|
|
* If not, prefix is required.
|
|
*/
|
|
if (NtPathU.Length > (MAX_PATH - SFN_LENGTH) * sizeof(WCHAR) && lpNewDirectory[0] != L'\\' &&
|
|
lpNewDirectory[1] != L'\\' && lpNewDirectory[2] != L'?' && lpNewDirectory[3] != L'\\')
|
|
{
|
|
/* Get file name position and full path length */
|
|
Length = GetFullPathNameW(lpNewDirectory, 0, NULL, &FilePart);
|
|
if (Length == 0)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Keep place for 8.3 file name */
|
|
Length += SFN_LENGTH;
|
|
/* No prefix, so, must be smaller than MAX_PATH */
|
|
if (Length > MAX_PATH)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* If we have relative name (and root dir), use them instead */
|
|
if (RelativeName.RelativeName.Length != 0)
|
|
{
|
|
NtPathU.Length = RelativeName.RelativeName.Length;
|
|
NtPathU.MaximumLength = RelativeName.RelativeName.MaximumLength;
|
|
NtPathU.Buffer = RelativeName.RelativeName.Buffer;
|
|
}
|
|
else
|
|
{
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
/* Get extended attributes */
|
|
Status = NtQueryInformationFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
&FileEaInfo,
|
|
sizeof(FileEaInfo),
|
|
FileEaInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Start reading extended attributes */
|
|
if (FileEaInfo.EaSize != 0)
|
|
{
|
|
for (EaLength = FileEaInfo.EaSize * 2; ; EaLength = EaLength * 2)
|
|
{
|
|
/* Allocate buffer for reading */
|
|
EaBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, EaLength);
|
|
if (!EaBuffer)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(STATUS_NO_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Query EAs */
|
|
Status = NtQueryEaFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
EaBuffer,
|
|
EaLength,
|
|
FALSE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
TRUE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, EaBuffer);
|
|
IoStatusBlock.Information = 0;
|
|
}
|
|
|
|
/* If we don't fail because of too small buffer, stop here */
|
|
if (Status != STATUS_BUFFER_OVERFLOW &&
|
|
Status != STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
EaLength = IoStatusBlock.Information;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&NtPathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
(lpSecurityAttributes ? lpSecurityAttributes->lpSecurityDescriptor : NULL));
|
|
|
|
/* Ensure attributes are valid */
|
|
FileBasicInfo.FileAttributes &= FILE_ATTRIBUTE_VALID_FLAGS;
|
|
|
|
/* Create the new directory */
|
|
Status = NtCreateFile(&DirectoryHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES |
|
|
FILE_READ_ATTRIBUTES | (FileBasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? FILE_ADD_FILE : 0),
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FileBasicInfo.FileAttributes,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT,
|
|
EaBuffer,
|
|
EaLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status == STATUS_INVALID_PARAMETER || Status == STATUS_ACCESS_DENIED)
|
|
{
|
|
/* If creation failed, it might be because FSD doesn't support reparse points
|
|
* Retry without asking for such support in case template is not a reparse point
|
|
*/
|
|
if (!ReparsePoint)
|
|
{
|
|
Status = NtCreateFile(&DirectoryHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE |
|
|
FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FileBasicInfo.FileAttributes,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT,
|
|
EaBuffer,
|
|
EaLength);
|
|
}
|
|
else
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
if (EaBuffer)
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, EaBuffer);
|
|
}
|
|
CloseHandle(TemplateHandle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
if (EaBuffer)
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, EaBuffer);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
NtClose(TemplateHandle);
|
|
if (RtlIsDosDeviceName_U(lpNewDirectory))
|
|
{
|
|
Status = STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* If template is a reparse point, copy reparse data */
|
|
if (ReparsePoint)
|
|
{
|
|
ReparseDataBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!ReparseDataBuffer)
|
|
{
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
SetLastError(STATUS_NO_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
/* First query data */
|
|
Status = NtFsControlFile(TemplateHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL,
|
|
0,
|
|
ReparseDataBuffer,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
SetLastError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Once again, ensure it is a mount point */
|
|
if (ReparseDataBuffer->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
SetLastError(STATUS_OBJECT_NAME_INVALID);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get volume name */
|
|
SubstituteName = (PWSTR)((ULONG_PTR)ReparseDataBuffer->MountPointReparseBuffer.PathBuffer +
|
|
ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameOffset);
|
|
if (IS_VOLUME_NAME(SubstituteName, ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameLength))
|
|
{
|
|
/* Prepare to define a new mount point for that volume */
|
|
RtlInitUnicodeString(&NewDirectory, lpNewDirectory);
|
|
NewDirectory.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, NewDirectory.Length + 2 * sizeof(WCHAR));
|
|
if (!NewDirectory.Buffer)
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlCopyMemory(&NewDirectory.Buffer, lpNewDirectory, NewDirectory.Length);
|
|
if (NewDirectory.Buffer[NewDirectory.Length / sizeof(WCHAR)] != L'\\')
|
|
{
|
|
NewDirectory.Buffer[NewDirectory.Length / sizeof(WCHAR)] = L'\\';
|
|
NewDirectory.Buffer[(NewDirectory.Length / sizeof(WCHAR)) + 1] = UNICODE_NULL;
|
|
}
|
|
|
|
/* Define a new mount point for that volume */
|
|
SetVolumeMountPointW(NewDirectory.Buffer, SubstituteName);
|
|
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, NewDirectory.Buffer);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Otherwise copy data raw */
|
|
Status = NtFsControlFile(DirectoryHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_SET_REPARSE_POINT,
|
|
ReparseDataBuffer,
|
|
ReparseDataBuffer->ReparseDataLength +
|
|
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer),
|
|
NULL,
|
|
0);
|
|
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
/* In case it's not a reparse point, handle streams on the file */
|
|
else
|
|
{
|
|
for (StreamSize = 0x1000; ; StreamSize = StreamSize * 2)
|
|
{
|
|
FileStreamInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, StreamSize);
|
|
if (!FileStreamInfo)
|
|
{
|
|
BaseMarkFileForDelete(DirectoryHandle, FileBasicInfo.FileAttributes);
|
|
SetLastError(STATUS_NO_MEMORY);
|
|
break;
|
|
}
|
|
|
|
/* Query stream information */
|
|
Status = NtQueryInformationFile(TemplateHandle,
|
|
&IoStatusBlock,
|
|
FileStreamInfo,
|
|
StreamSize,
|
|
FileStreamInformation);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
break;
|
|
}
|
|
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, FileStreamInfo);
|
|
FileStreamInfo = NULL;
|
|
|
|
/* If it failed, ensure that's not because of too small buffer */
|
|
if (Status != STATUS_BUFFER_OVERFLOW &&
|
|
Status != STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status) || IoStatusBlock.Information == 0)
|
|
{
|
|
if (FileStreamInfo)
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, FileStreamInfo);
|
|
}
|
|
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
return TRUE;
|
|
}
|
|
|
|
#if 1
|
|
/* FIXME: TODO */
|
|
DPRINT1("Warning: streams copying is unimplemented!\n");
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, FileStreamInfo);
|
|
NtClose(TemplateHandle);
|
|
NtClose(DirectoryHandle);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
RemoveDirectoryA(IN LPCSTR lpPathName)
|
|
{
|
|
PUNICODE_STRING PathNameW;
|
|
|
|
PathNameW = Basep8BitStringToStaticUnicodeString(lpPathName);
|
|
if (!PathNameW)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return RemoveDirectoryW(PathNameW->Buffer);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOL
|
|
WINAPI
|
|
RemoveDirectoryW(IN LPCWSTR lpPathName)
|
|
{
|
|
NTSTATUS Status;
|
|
DWORD BytesReturned;
|
|
HANDLE DirectoryHandle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
UNICODE_STRING NtPathU, PathName;
|
|
RTL_RELATIVE_NAME_U RelativeName;
|
|
PWSTR PathUBuffer, SubstituteName;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PREPARSE_DATA_BUFFER ReparseDataBuffer;
|
|
FILE_DISPOSITION_INFORMATION FileDispInfo;
|
|
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInfo;
|
|
|
|
/* Get relative name */
|
|
if (!RtlDosPathNameToRelativeNtPathName_U(lpPathName, &NtPathU, NULL, &RelativeName))
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save buffer to allow later freeing */
|
|
PathUBuffer = NtPathU.Buffer;
|
|
|
|
/* If we have relative name (and root dir), use them instead */
|
|
if (RelativeName.RelativeName.Length != 0)
|
|
{
|
|
NtPathU.Length = RelativeName.RelativeName.Length;
|
|
NtPathU.MaximumLength = RelativeName.RelativeName.MaximumLength;
|
|
NtPathU.Buffer = RelativeName.RelativeName.Buffer;
|
|
}
|
|
else
|
|
{
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&NtPathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
NULL);
|
|
|
|
/* Try to open directory */
|
|
Status = NtOpenFile(&DirectoryHandle,
|
|
DELETE | SYNCHRONIZE | FAILED_ACCESS_ACE_FLAG,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* We only accept failure for reparse points not being supported */
|
|
if (Status != STATUS_INVALID_PARAMETER)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Try to open, with reparse points support */
|
|
Status = NtOpenFile(&DirectoryHandle,
|
|
DELETE | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Success, mark directory */
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* Get information about file (and reparse point) */
|
|
Status = NtQueryInformationFile(DirectoryHandle,
|
|
&IoStatusBlock,
|
|
&FileTagInfo,
|
|
sizeof(FileTagInfo),
|
|
FileAttributeTagInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* FSD might not support querying reparse points information */
|
|
if (Status != STATUS_NOT_IMPLEMENTED &&
|
|
Status != STATUS_INVALID_PARAMETER)
|
|
{
|
|
goto CleanupHandle;
|
|
}
|
|
|
|
/* If that's the case, then just delete directory */
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* If that's not a reparse point, nothing more to do than just delete */
|
|
if (!(FileTagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
{
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* Check if that's a mount point */
|
|
if (FileTagInfo.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
/* It's not */
|
|
NtClose(DirectoryHandle);
|
|
|
|
/* So, try to reopen directory, ignoring mount point */
|
|
Status = NtOpenFile(&DirectoryHandle,
|
|
DELETE | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* It succeed, we can safely delete directory (and ignore reparse point) */
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* If it failed, only allow case where IO mount point was ignored */
|
|
if (Status != STATUS_IO_REPARSE_TAG_NOT_HANDLED)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Reopen with reparse point support */
|
|
Status = NtOpenFile(&DirectoryHandle,
|
|
DELETE | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* And mark for delete */
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Here, we have a mount point, prepare to query information about it */
|
|
ReparseDataBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!ReparseDataBuffer)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
NtClose(DirectoryHandle);
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Query */
|
|
if (!DeviceIoControl(DirectoryHandle,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL, 0,
|
|
ReparseDataBuffer,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
|
|
&BytesReturned,
|
|
NULL))
|
|
{
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* Get volume name */
|
|
SubstituteName = (PWSTR)((ULONG_PTR)ReparseDataBuffer->MountPointReparseBuffer.PathBuffer +
|
|
ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameOffset);
|
|
if (!IS_VOLUME_NAME(SubstituteName, ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameLength))
|
|
{
|
|
/* This is not a volume, we can safely delete */
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
goto MarkFileForDelete;
|
|
}
|
|
|
|
/* Prepare to delete mount point */
|
|
RtlInitUnicodeString(&PathName, lpPathName);
|
|
PathName.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, PathName.Length + 2 * sizeof(WCHAR));
|
|
if (!PathName.Buffer)
|
|
{
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
NtClose(DirectoryHandle);
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlCopyMemory(&PathName.Buffer, lpPathName, PathName.Length);
|
|
if (PathName.Buffer[PathName.Length / sizeof(WCHAR)] != L'\\')
|
|
{
|
|
PathName.Buffer[PathName.Length / sizeof(WCHAR)] = L'\\';
|
|
PathName.Buffer[(PathName.Length / sizeof(WCHAR)) + 1] = UNICODE_NULL;
|
|
}
|
|
|
|
/* Delete mount point for that volume */
|
|
DeleteVolumeMountPointW(PathName.Buffer);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathName.Buffer);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, ReparseDataBuffer);
|
|
|
|
/* And mark directory for delete */
|
|
MarkFileForDelete:
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
|
|
/* Mark & set */
|
|
FileDispInfo.DeleteFile = TRUE;
|
|
Status = NtSetInformationFile(DirectoryHandle,
|
|
&IoStatusBlock,
|
|
&FileDispInfo,
|
|
sizeof(FILE_DISPOSITION_INFORMATION),
|
|
FileDispositionInformation);
|
|
NtClose(DirectoryHandle);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
CleanupHandle:
|
|
NtClose(DirectoryHandle);
|
|
|
|
Cleanup:
|
|
RtlReleaseRelativeName(&RelativeName);
|
|
RtlFreeHeap(RtlGetProcessHeap(), 0, PathUBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* EOF */
|