/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS system libraries
 * FILE:            lib/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] == '}')

/* 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;

/* 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 */
