Author: pschweitzer Date: Sun Jan 1 17:22:35 2012 New Revision: 54804
URL: http://svn.reactos.org/svn/reactos?rev=54804&view=rev Log: [KERNEL32] Reimplement CreateDirectoryA, CreateDirectoryExA, CreateDirectoryW, CreateDirectoryExW, RemoveDirectoryA, RemoveDirectoryW. This reimplementation now matches the w2k3 one, properly handles reparse points (especially mount points) and also brings several fixes.
Modified: trunk/reactos/dll/win32/kernel32/client/file/dir.c
Modified: trunk/reactos/dll/win32/kernel32/client/file/dir.c URL: http://svn.reactos.org/svn/reactos/trunk/reactos/dll/win32/kernel32/client/f... ============================================================================== --- trunk/reactos/dll/win32/kernel32/client/file/dir.c [iso-8859-1] (original) +++ trunk/reactos/dll/win32/kernel32/client/file/dir.c [iso-8859-1] Sun Jan 1 17:22:35 2012 @@ -2,15 +2,9 @@ * * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS system libraries - * FILE: lib/kernel32/file/dir.c + * FILE: lib/kernel32/client/file/dir.c * PURPOSE: Directory functions - * PROGRAMMER: Ariadne ( ariadne@xs4all.nl) - * UPDATE HISTORY: - * Created 01/11/98 - */ - -/* - * NOTES: Changed to using ZwCreateFile + * PROGRAMMER: Pierre Schweitzer (pierre@reactos.org) */
/* INCLUDES ******************************************************************/ @@ -18,7 +12,53 @@ #include <k32.h> #define NDEBUG #include <debug.h> -DEBUG_CHANNEL(kernel32file); + +/* 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] == '}') + +/* FIXME - Get it out of here */ +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +typedef struct _FILE_ATTRIBUTE_TAG_INFORMATION { + ULONG FileAttributes; + ULONG ReparseTag; +} FILE_ATTRIBUTE_TAG_INFORMATION, *PFILE_ATTRIBUTE_TAG_INFORMATION;
/* FUNCTIONS *****************************************************************/
@@ -27,559 +67,933 @@ */ BOOL WINAPI -CreateDirectoryA ( - LPCSTR lpPathName, - LPSECURITY_ATTRIBUTES lpSecurityAttributes - ) +CreateDirectoryA(IN LPCSTR lpPathName, + IN LPSECURITY_ATTRIBUTES lpSecurityAttributes) { - PWCHAR PathNameW; - - if (!(PathNameW = FilenameA2W(lpPathName, FALSE))) - return FALSE; - - return CreateDirectoryW (PathNameW, + PUNICODE_STRING PathNameW; + + PathNameW = Basep8BitStringToStaticUnicodeString(lpPathName); + if (!PathNameW) + { + return FALSE; + } + + return CreateDirectoryW(PathNameW->Buffer, lpSecurityAttributes); } -
/* * @implemented */ BOOL WINAPI -CreateDirectoryExA ( - LPCSTR lpTemplateDirectory, - LPCSTR lpNewDirectory, - LPSECURITY_ATTRIBUTES lpSecurityAttributes) +CreateDirectoryExA(IN LPCSTR lpTemplateDirectory, + IN LPCSTR lpNewDirectory, + IN LPSECURITY_ATTRIBUTES lpSecurityAttributes) { - PWCHAR TemplateDirectoryW; - PWCHAR NewDirectoryW; - BOOL ret; - - if (!(TemplateDirectoryW = FilenameA2W(lpTemplateDirectory, TRUE))) - return FALSE; - - if (!(NewDirectoryW = FilenameA2W(lpNewDirectory, FALSE))) - { - RtlFreeHeap (RtlGetProcessHeap (), - 0, - TemplateDirectoryW); - return FALSE; - } - - ret = CreateDirectoryExW (TemplateDirectoryW, - NewDirectoryW, + 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);
- RtlFreeHeap (RtlGetProcessHeap (), - 0, - TemplateDirectoryW); - - return ret; + RtlFreeUnicodeString(&NewDirectoryW); + + return ret; } -
/* * @implemented */ BOOL WINAPI -CreateDirectoryW ( - LPCWSTR lpPathName, - LPSECURITY_ATTRIBUTES lpSecurityAttributes - ) +CreateDirectoryW(IN LPCWSTR lpPathName, + IN LPSECURITY_ATTRIBUTES lpSecurityAttributes) { - OBJECT_ATTRIBUTES ObjectAttributes; - IO_STATUS_BLOCK IoStatusBlock; - UNICODE_STRING NtPathU; - HANDLE DirectoryHandle = NULL; - NTSTATUS Status; - - TRACE ("lpPathName %S lpSecurityAttributes %p\n", - lpPathName, lpSecurityAttributes); - - if (!RtlDosPathNameToNtPathName_U (lpPathName, - &NtPathU, - NULL, - NULL)) - { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - InitializeObjectAttributes(&ObjectAttributes, - &NtPathU, - OBJ_CASE_INSENSITIVE, - NULL, - (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); - - RtlFreeHeap (RtlGetProcessHeap (), - 0, - NtPathU.Buffer); - - if (!NT_SUCCESS(Status)) - { - WARN("NtCreateFile failed with Status %lx\n", Status); - BaseSetLastNTError(Status); - return FALSE; - } - - NtClose (DirectoryHandle); - + 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(GetProcessHeap(), 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(GetProcessHeap(), 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 ( - LPCWSTR lpTemplateDirectory, - LPCWSTR lpNewDirectory, - LPSECURITY_ATTRIBUTES lpSecurityAttributes - ) +CreateDirectoryExW(IN LPCWSTR lpTemplateDirectory, + IN LPCWSTR lpNewDirectory, + IN LPSECURITY_ATTRIBUTES lpSecurityAttributes) { - OBJECT_ATTRIBUTES ObjectAttributes; - IO_STATUS_BLOCK IoStatusBlock; - UNICODE_STRING NtPathU, NtTemplatePathU; - HANDLE DirectoryHandle = NULL; - HANDLE TemplateHandle = NULL; - FILE_EA_INFORMATION EaInformation; - FILE_BASIC_INFORMATION FileBasicInfo; - NTSTATUS Status; - ULONG OpenOptions, CreateOptions; - ACCESS_MASK DesiredAccess; - BOOLEAN ReparsePoint = FALSE; - PVOID EaBuffer = NULL; - ULONG EaLength = 0; - - OpenOptions = FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | - FILE_OPEN_FOR_BACKUP_INTENT; - CreateOptions = FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT; - DesiredAccess = FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES | - FILE_READ_ATTRIBUTES; - - TRACE ("lpTemplateDirectory %ws lpNewDirectory %ws lpSecurityAttributes %p\n", - lpTemplateDirectory, lpNewDirectory, lpSecurityAttributes); - - /* - * Translate the template directory path - */ - - if (!RtlDosPathNameToNtPathName_U (lpTemplateDirectory, - &NtTemplatePathU, - NULL, - NULL)) - { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - InitializeObjectAttributes(&ObjectAttributes, - &NtTemplatePathU, - OBJ_CASE_INSENSITIVE, - NULL, - NULL); - - /* - * Open the template directory - */ - -OpenTemplateDir: - Status = NtOpenFile (&TemplateHandle, - FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_READ_EA, - &ObjectAttributes, - &IoStatusBlock, - FILE_SHARE_READ | FILE_SHARE_WRITE, - OpenOptions); + DWORD Length; + NTSTATUS Status; + ULONG EaLength = 0; + PVOID EaBuffer = NULL; + BOOL ReparsePoint = FALSE; + IO_STATUS_BLOCK IoStatusBlock; + FILE_EA_INFORMATION FileEaInfo; + OBJECT_ATTRIBUTES ObjectAttributes; + FILE_BASIC_INFORMATION FileBasicInfo; + PREPARSE_DATA_BUFFER ReparseDataBuffer; + HANDLE TemplateHandle, DirectoryHandle; + 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(GetProcessHeap(), 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)) { - if (Status == STATUS_INVALID_PARAMETER && - (OpenOptions & FILE_OPEN_REPARSE_POINT)) - { - /* Some FSs (FAT) don't support reparse points, try opening - the directory without FILE_OPEN_REPARSE_POINT */ - OpenOptions &= ~FILE_OPEN_REPARSE_POINT; - - TRACE("Reparse points not supported, try with less options\n"); - - /* try again */ - goto OpenTemplateDir; - } - else - { - WARN("Failed to open the template directory: 0x%x\n", Status); - goto CleanupNoNtPath; - } - } - - /* - * Translate the new directory path and check if they're the same - */ - - if (!RtlDosPathNameToNtPathName_U (lpNewDirectory, - &NtPathU, - NULL, - NULL)) - { - Status = STATUS_OBJECT_PATH_NOT_FOUND; - goto CleanupNoNtPath; - } - - if (RtlEqualUnicodeString(&NtPathU, - &NtTemplatePathU, - TRUE)) - { - WARN("Both directory paths are the same!\n"); - Status = STATUS_OBJECT_NAME_INVALID; - goto Cleanup; - } - - InitializeObjectAttributes(&ObjectAttributes, - &NtPathU, - OBJ_CASE_INSENSITIVE, - NULL, - (lpSecurityAttributes ? lpSecurityAttributes->lpSecurityDescriptor : NULL)); - - /* - * Query the basic file attributes from the template directory - */ - - /* Make sure FILE_ATTRIBUTE_NORMAL is used in case the information - isn't set by the FS */ + RtlReleaseRelativeName(&TemplateRelativeName); + RtlFreeHeap(GetProcessHeap(), 0, TemplateBuffer); + BaseSetLastNTError(Status); + return FALSE; + } + + /* Request file attributes */ FileBasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; Status = NtQueryInformationFile(TemplateHandle, &IoStatusBlock, &FileBasicInfo, - sizeof(FILE_BASIC_INFORMATION), + sizeof(FileBasicInfo), FileBasicInformation); if (!NT_SUCCESS(Status)) { - WARN("Failed to query the basic directory attributes\n"); - goto Cleanup; - } - - /* clear the reparse point attribute if present. We're going to set the - reparse point later which will cause the attribute to be set */ + RtlReleaseRelativeName(&TemplateRelativeName); + RtlFreeHeap(GetProcessHeap(), 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(GetProcessHeap(), 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) { - FileBasicInfo.FileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT; - - /* writing the extended attributes requires the FILE_WRITE_DATA - access right */ - DesiredAccess |= FILE_WRITE_DATA; - - CreateOptions |= FILE_OPEN_REPARSE_POINT; + Status = NtQueryInformationFile(TemplateHandle, + &IoStatusBlock, + &FileTagInfo, + sizeof(FileTagInfo), + FileAttributeTagInformation); + if (!NT_SUCCESS(Status)) + { + RtlReleaseRelativeName(&TemplateRelativeName); + RtlFreeHeap(GetProcessHeap(), 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; } - - /* - * Read the Extended Attributes if present - */ - - for (;;) - { - Status = NtQueryInformationFile(TemplateHandle, - &IoStatusBlock, - &EaInformation, - sizeof(FILE_EA_INFORMATION), - FileEaInformation); - if (NT_SUCCESS(Status) && (EaInformation.EaSize != 0)) - { - EaBuffer = RtlAllocateHeap(RtlGetProcessHeap(), - 0, - EaInformation.EaSize); - if (EaBuffer == NULL) - { - Status = STATUS_INSUFFICIENT_RESOURCES; - break; - } - + } + + /* Get relative name of the directory */ + if (!RtlDosPathNameToRelativeNtPathName_U(lpNewDirectory, &NtPathU, NULL, &RelativeName)) + { + RtlReleaseRelativeName(&TemplateRelativeName); + RtlFreeHeap(GetProcessHeap(), 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(GetProcessHeap(), 0, TemplateBuffer); + RtlFreeHeap(GetProcessHeap(), 0, PathUBuffer); + NtClose(TemplateHandle); + SetLastError(ERROR_INVALID_NAME); + return FALSE; + } + + RtlReleaseRelativeName(&TemplateRelativeName); + RtlFreeHeap(GetProcessHeap(), 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(GetProcessHeap(), 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(GetProcessHeap(), 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(GetProcessHeap(), 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(GetProcessHeap(), 0, PathUBuffer); + CloseHandle(TemplateHandle); + BaseSetLastNTError(STATUS_NO_MEMORY); + return FALSE; + } + + /* Query EAs */ Status = NtQueryEaFile(TemplateHandle, &IoStatusBlock, EaBuffer, - EaInformation.EaSize, + EaLength, FALSE, NULL, 0, NULL, TRUE); - - if (NT_SUCCESS(Status)) - { - /* we successfully read the extended attributes */ - EaLength = EaInformation.EaSize; - break; + if (!NT_SUCCESS(Status)) + { + RtlFreeHeap(GetProcessHeap(), 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 { - RtlFreeHeap(RtlGetProcessHeap(), - 0, - EaBuffer); - EaBuffer = NULL; - - if (Status != STATUS_BUFFER_TOO_SMALL) - { - /* unless we just allocated not enough memory, break the loop - and just continue without copying extended attributes */ - break; - } - } - } - else - { - if (Status == STATUS_EAS_NOT_SUPPORTED) - { - /* Extended attributes are not supported, so, this is OK */ - /* FIXME: Would deserve a deeper look, comparing with Windows */ - Status = STATUS_SUCCESS; - } - /* failure or no extended attributes present, break the loop */ - break; - } - } - - if (!NT_SUCCESS(Status)) - { - WARN("Querying the EA data failed: 0x%x\n", Status); - goto Cleanup; - } - - /* - * Create the new directory - */ - - Status = NtCreateFile (&DirectoryHandle, - DesiredAccess, - &ObjectAttributes, - &IoStatusBlock, - NULL, - FileBasicInfo.FileAttributes, - FILE_SHARE_READ | FILE_SHARE_WRITE, - FILE_CREATE, - CreateOptions, - EaBuffer, - EaLength); - if (!NT_SUCCESS(Status)) - { - if (ReparsePoint && - (Status == STATUS_INVALID_PARAMETER || Status == STATUS_ACCESS_DENIED)) - { - /* The FS doesn't seem to support reparse points... */ - WARN("Cannot copy the hardlink, destination doesn't support reparse points!\n"); - } - - goto Cleanup; - } - - if (ReparsePoint) - { - /* - * Copy the reparse point - */ - - PREPARSE_GUID_DATA_BUFFER ReparseDataBuffer = - (PREPARSE_GUID_DATA_BUFFER)RtlAllocateHeap(RtlGetProcessHeap(), - 0, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - - if (ReparseDataBuffer == NULL) - { - Status = STATUS_INSUFFICIENT_RESOURCES; - goto Cleanup; - } - - /* query the size of the reparse data buffer structure */ - Status = NtFsControlFile(TemplateHandle, - NULL, - NULL, - NULL, - &IoStatusBlock, - FSCTL_GET_REPARSE_POINT, - NULL, - 0, - ReparseDataBuffer, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - if (NT_SUCCESS(Status)) - { - /* write the reparse point */ - Status = NtFsControlFile(DirectoryHandle, - NULL, - NULL, - NULL, - &IoStatusBlock, - FSCTL_SET_REPARSE_POINT, - ReparseDataBuffer, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, - NULL, - 0); - } - - RtlFreeHeap(RtlGetProcessHeap(), - 0, - ReparseDataBuffer); - - if (!NT_SUCCESS(Status)) - { - /* fail, we were unable to read the reparse point data! */ - WARN("Querying or setting the reparse point failed: 0x%x\n", Status); - goto Cleanup; - } - } - else - { - /* - * Copy alternate file streams, if existing - */ - - /* FIXME - enumerate and copy the file streams */ - } - - /* - * We successfully created the directory and copied all information - * from the template directory - */ - Status = STATUS_SUCCESS; - -Cleanup: - RtlFreeHeap (RtlGetProcessHeap (), - 0, - NtPathU.Buffer); - -CleanupNoNtPath: - if (TemplateHandle != NULL) - { - NtClose(TemplateHandle); - } - - RtlFreeHeap (RtlGetProcessHeap (), - 0, - NtTemplatePathU.Buffer); - - /* free the he extended attributes buffer */ - if (EaBuffer != NULL) - { - RtlFreeHeap (RtlGetProcessHeap (), - 0, - EaBuffer); - } - - if (DirectoryHandle != NULL) - { - NtClose(DirectoryHandle); - } - - if (!NT_SUCCESS(Status)) - { + RtlReleaseRelativeName(&RelativeName); + RtlFreeHeap(GetProcessHeap(), 0, PathUBuffer); + if (EaBuffer) + { + RtlFreeHeap(GetProcessHeap(), 0, EaBuffer); + } + CloseHandle(TemplateHandle); BaseSetLastNTError(Status); return FALSE; - } - + } + } + } + + RtlReleaseRelativeName(&RelativeName); + RtlFreeHeap(GetProcessHeap(), 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 + { + PVOID StreamInfo; + ULONG StreamSize = 0x1000; + for (;;) + { + StreamInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, StreamSize); + if (!StreamInfo) + { + BaseMarkFileForDelete(DirectoryHandle, FileBasicInfo.FileAttributes); + SetLastError(STATUS_NO_MEMORY); + break; + } + + /* Query stream information */ + Status = NtQueryInformationFile(TemplateHandle, + &IoStatusBlock, + StreamInfo, + StreamSize, + FileStreamInformation); + if (!NT_SUCCESS(Status)) + { + RtlFreeHeap(RtlGetProcessHeap(), 0, StreamInfo); + StreamInfo = NULL; + StreamSize << 1; + } + + /* 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 (StreamInfo) + { + RtlFreeHeap(RtlGetProcessHeap(), 0, StreamInfo); + } + + NtClose(TemplateHandle); + NtClose(DirectoryHandle); + return TRUE; + } + +#if 1 + /* FIXME: TODO */ + DPRINT1("Warning: streams copying is unimplemented!\n"); + RtlFreeHeap(RtlGetProcessHeap(), 0, StreamInfo); + NtClose(TemplateHandle); + NtClose(DirectoryHandle); +#endif return TRUE; + } } -
/* * @implemented */ BOOL WINAPI -RemoveDirectoryA ( - LPCSTR lpPathName - ) +RemoveDirectoryA(IN LPCSTR lpPathName) { - PWCHAR PathNameW; - - TRACE("RemoveDirectoryA(%s)\n",lpPathName); - - if (!(PathNameW = FilenameA2W(lpPathName, FALSE))) - return FALSE; - - return RemoveDirectoryW (PathNameW); + PUNICODE_STRING PathNameW; + + PathNameW = Basep8BitStringToStaticUnicodeString(lpPathName); + if (!PathNameW) + { + return FALSE; + } + + return RemoveDirectoryW(PathNameW->Buffer); } -
/* * @implemented */ BOOL WINAPI -RemoveDirectoryW ( - LPCWSTR lpPathName - ) +RemoveDirectoryW(IN LPCWSTR lpPathName) { - FILE_DISPOSITION_INFORMATION FileDispInfo; - OBJECT_ATTRIBUTES ObjectAttributes; - IO_STATUS_BLOCK IoStatusBlock; - UNICODE_STRING NtPathU; - HANDLE DirectoryHandle = NULL; - NTSTATUS Status; - - TRACE("lpPathName %S\n", lpPathName); - - if (!RtlDosPathNameToNtPathName_U (lpPathName, - &NtPathU, - NULL, - NULL)) - { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - InitializeObjectAttributes(&ObjectAttributes, - &NtPathU, - OBJ_CASE_INSENSITIVE, - NULL, - NULL); - - TRACE("NtPathU '%S'\n", NtPathU.Buffer); - + 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); - - RtlFreeUnicodeString(&NtPathU); - + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | + FILE_OPEN_FOR_BACKUP_INTENT); if (!NT_SUCCESS(Status)) { - WARN("Status 0x%08x\n", Status); - BaseSetLastNTError (Status); - return FALSE; - } - - FileDispInfo.DeleteFile = TRUE; - - Status = NtSetInformationFile (DirectoryHandle, - &IoStatusBlock, - &FileDispInfo, - sizeof(FILE_DISPOSITION_INFORMATION), - FileDispositionInformation); + 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);
- if (!NT_SUCCESS(Status)) - { - BaseSetLastNTError (Status); - return FALSE; - } - - return TRUE; + /* 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 */