1286 lines
40 KiB
C
1286 lines
40 KiB
C
/*++
|
|
|
|
Copyright (c) 1990 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dir.c
|
|
|
|
Abstract:
|
|
|
|
This module implements Win32 Directory functions.
|
|
|
|
Author:
|
|
|
|
Mark Lucovsky (markl) 26-Sep-1990
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "basedll.h"
|
|
#include "mountmgr.h"
|
|
|
|
BOOL
|
|
APIENTRY
|
|
CreateDirectoryA(
|
|
LPCSTR lpPathName,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ANSI thunk to CreateDirectoryW
|
|
|
|
--*/
|
|
|
|
{
|
|
PUNICODE_STRING Unicode;
|
|
|
|
Unicode = Basep8BitStringToStaticUnicodeString( lpPathName );
|
|
if (Unicode == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return ( CreateDirectoryW((LPCWSTR)Unicode->Buffer,lpSecurityAttributes) );
|
|
}
|
|
|
|
BOOL
|
|
APIENTRY
|
|
CreateDirectoryW(
|
|
LPCWSTR lpPathName,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A directory can be created using CreateDirectory.
|
|
|
|
This API causes a directory with the specified pathname to be
|
|
created. If the underlying file system supports security on files
|
|
and directories, then the SecurityDescriptor argument is applied to
|
|
the new directory.
|
|
|
|
This call is similar to DOS (int 21h, function 39h) and OS/2's
|
|
DosCreateDir.
|
|
|
|
Arguments:
|
|
|
|
lpPathName - Supplies the pathname of the directory to be created.
|
|
|
|
lpSecurityAttributes - An optional parameter that, if present, and
|
|
supported on the target file system supplies a security
|
|
descriptor for the new directory.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The operation was successful.
|
|
|
|
FALSE/NULL - The operation failed. Extended error status is available
|
|
using GetLastError.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HANDLE Handle;
|
|
UNICODE_STRING FileName;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
BOOLEAN TranslationStatus;
|
|
RTL_RELATIVE_NAME RelativeName;
|
|
PVOID FreeBuffer;
|
|
|
|
TranslationStatus = RtlDosPathNameToNtPathName_U(
|
|
lpPathName,
|
|
&FileName,
|
|
NULL,
|
|
&RelativeName
|
|
);
|
|
|
|
if ( !TranslationStatus ) {
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// dont create a directory unless there is room in the directory for
|
|
// at least an 8.3 name. This way everyone will be able to delete all
|
|
// files in the directory by using del *.* which expands to path+\*.*
|
|
//
|
|
|
|
if ( FileName.Length > ((MAX_PATH-12)<<1) ) {
|
|
DWORD L;
|
|
LPWSTR lp;
|
|
|
|
if ( !(lpPathName[0] == '\\' && lpPathName[1] == '\\' &&
|
|
lpPathName[2] == '?' && lpPathName[3] == '\\') ) {
|
|
L = GetFullPathNameW(lpPathName,0,NULL,&lp);
|
|
if ( !L || L+12 > MAX_PATH ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0,FileName.Buffer);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
FreeBuffer = FileName.Buffer;
|
|
|
|
if ( RelativeName.RelativeName.Length ) {
|
|
FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
|
|
}
|
|
else {
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
&FileName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
NULL
|
|
);
|
|
|
|
if ( ARGUMENT_PRESENT(lpSecurityAttributes) ) {
|
|
Obja.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor;
|
|
}
|
|
|
|
Status = NtCreateFile(
|
|
&Handle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
|
&Obja,
|
|
&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,
|
|
0L
|
|
);
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0,FreeBuffer);
|
|
if ( NT_SUCCESS(Status) ) {
|
|
NtClose(Handle);
|
|
return TRUE;
|
|
}
|
|
else {
|
|
if ( RtlIsDosDeviceName_U((LPWSTR)lpPathName) ) {
|
|
Status = STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
APIENTRY
|
|
CreateDirectoryExA(
|
|
LPCSTR lpTemplateDirectory,
|
|
LPCSTR lpNewDirectory,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ANSI thunk to CreateDirectoryFromTemplateW
|
|
|
|
--*/
|
|
|
|
{
|
|
PUNICODE_STRING StaticUnicode;
|
|
UNICODE_STRING DynamicUnicode;
|
|
BOOL b;
|
|
|
|
StaticUnicode = Basep8BitStringToStaticUnicodeString( lpTemplateDirectory );
|
|
if (StaticUnicode == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!Basep8BitStringToDynamicUnicodeString( &DynamicUnicode, lpNewDirectory )) {
|
|
return FALSE;
|
|
}
|
|
|
|
b = CreateDirectoryExW(
|
|
(LPCWSTR)StaticUnicode->Buffer,
|
|
(LPCWSTR)DynamicUnicode.Buffer,
|
|
lpSecurityAttributes
|
|
);
|
|
|
|
RtlFreeUnicodeString(&DynamicUnicode);
|
|
|
|
return b;
|
|
}
|
|
|
|
BOOL
|
|
APIENTRY
|
|
CreateDirectoryExW(
|
|
LPCWSTR lpTemplateDirectory,
|
|
LPCWSTR lpNewDirectory,
|
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A directory can be created using CreateDirectoryEx, retaining the
|
|
attributes of the original directory file.
|
|
|
|
This API causes a directory with the specified pathname to be
|
|
created. If the underlying file system supports security on files
|
|
and directories, then the SecurityDescriptor argument is applied to
|
|
the new directory. The other attributes of the template directory are
|
|
retained when creating the new directory.
|
|
|
|
If the original directory is a volume mount point then the new directory
|
|
is also a volume mount point to the same volume as the original one.
|
|
|
|
Arguments:
|
|
|
|
lpTemplateDirectory - Supplies the pathname of the directory to be used as
|
|
a template when creating the new directory.
|
|
|
|
lpPathName - Supplies the pathname of the directory to be created.
|
|
|
|
lpSecurityAttributes - An optional parameter that, if present, and
|
|
supported on the target file system supplies a security
|
|
descriptor for the new directory.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The operation was successful.
|
|
|
|
FALSE/NULL - The operation failed. Extended error status is available
|
|
using GetLastError.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HANDLE SourceFile;
|
|
HANDLE DestFile;
|
|
UNICODE_STRING PathName;
|
|
UNICODE_STRING TargetName;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
BOOLEAN TranslationStatus;
|
|
BOOLEAN IsNameGrafting = FALSE;
|
|
UNICODE_STRING VolumeName;
|
|
UNICODE_STRING MountPoint;
|
|
PWCHAR VolumeMountPoint = NULL;
|
|
RTL_RELATIVE_NAME RelativeName;
|
|
PVOID FreePathBuffer;
|
|
PVOID FreeTargetBuffer;
|
|
UNICODE_STRING StreamName;
|
|
WCHAR FileName[MAXIMUM_FILENAME_LENGTH+1];
|
|
HANDLE StreamHandle;
|
|
HANDLE OutputStream;
|
|
PFILE_STREAM_INFORMATION StreamInfo;
|
|
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
|
|
PFILE_STREAM_INFORMATION StreamInfoBase;
|
|
PFILE_FULL_EA_INFORMATION EaBuffer;
|
|
FILE_EA_INFORMATION EaInfo;
|
|
FILE_BASIC_INFORMATION BasicInfo;
|
|
ULONG EaSize;
|
|
ULONG StreamInfoSize;
|
|
ULONG CopySize;
|
|
ULONG i;
|
|
ULONG DesiredAccess = 0;
|
|
DWORD Options;
|
|
DWORD b;
|
|
LPCOPYFILE_CONTEXT CopyFileContext = NULL;
|
|
|
|
//
|
|
// Process the input template directory name and then open the directory
|
|
// file, ensuring that it really is a directory.
|
|
//
|
|
|
|
TranslationStatus = RtlDosPathNameToNtPathName_U(
|
|
lpTemplateDirectory,
|
|
&PathName,
|
|
NULL,
|
|
&RelativeName
|
|
);
|
|
|
|
if ( !TranslationStatus ) {
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
FreePathBuffer = PathName.Buffer;
|
|
|
|
if ( RelativeName.RelativeName.Length ) {
|
|
PathName = *(PUNICODE_STRING)&RelativeName.RelativeName;
|
|
}
|
|
else {
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
&PathName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Inhibit the reparse behavior using FILE_OPEN_REPARSE_POINT.
|
|
//
|
|
|
|
Status = NtOpenFile(
|
|
&SourceFile,
|
|
FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_READ_ATTRIBUTES,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
//
|
|
// Back level file systems may not support reparse points and thus not
|
|
// support symbolic links.
|
|
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
|
|
//
|
|
|
|
if ( Status == STATUS_INVALID_PARAMETER ) {
|
|
//
|
|
// Re-open not inhibiting the reparse behavior.
|
|
//
|
|
|
|
Status = NtOpenFile(
|
|
&SourceFile,
|
|
FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_READ_ATTRIBUTES,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// See whether we have a name grafting operation.
|
|
//
|
|
|
|
BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
Status = NtQueryInformationFile(
|
|
SourceFile,
|
|
&IoStatusBlock,
|
|
(PVOID)&BasicInfo,
|
|
sizeof(BasicInfo),
|
|
FileBasicInformation
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
CloseHandle(SourceFile);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) {
|
|
Status = NtQueryInformationFile(
|
|
SourceFile,
|
|
&IoStatusBlock,
|
|
(PVOID)&FileTagInformation,
|
|
sizeof(FileTagInformation),
|
|
FileAttributeTagInformation
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
CloseHandle(SourceFile);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( FileTagInformation.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT ) {
|
|
//
|
|
// Close and re-open not inhibiting the reparse behavior.
|
|
//
|
|
|
|
CloseHandle(SourceFile);
|
|
|
|
Status = NtOpenFile(
|
|
&SourceFile,
|
|
FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_READ_ATTRIBUTES,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
IsNameGrafting = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
TranslationStatus = RtlDosPathNameToNtPathName_U(
|
|
lpNewDirectory,
|
|
&TargetName,
|
|
NULL,
|
|
&RelativeName
|
|
);
|
|
|
|
if ( !TranslationStatus ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
NtClose(SourceFile);
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
FreeTargetBuffer = TargetName.Buffer;
|
|
|
|
//
|
|
// Verify that the source and target are different.
|
|
//
|
|
|
|
if ( RtlEqualUnicodeString(&PathName, &TargetName, TRUE) ) {
|
|
//
|
|
// Do nothing. Source and target are the same.
|
|
//
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
NtClose(SourceFile);
|
|
SetLastError(ERROR_INVALID_NAME);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreePathBuffer);
|
|
|
|
//
|
|
// Do not create a directory unless there is room in the directory for
|
|
// at least an 8.3 name. This way everyone will be able to delete all
|
|
// files in the directory by using del *.* which expands to path+\*.*
|
|
//
|
|
|
|
if ( TargetName.Length > ((MAX_PATH-12)<<1) ) {
|
|
DWORD L;
|
|
LPWSTR lp;
|
|
if ( !(lpNewDirectory[0] == '\\' && lpNewDirectory[1] == '\\' &&
|
|
lpNewDirectory[2] == '?' && lpNewDirectory[3] == '\\') ) {
|
|
L = GetFullPathNameW(lpNewDirectory,0,NULL,&lp);
|
|
if ( !L || L+12 > MAX_PATH ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
CloseHandle(SourceFile);
|
|
SetLastError(ERROR_FILENAME_EXCED_RANGE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( RelativeName.RelativeName.Length ) {
|
|
TargetName = *(PUNICODE_STRING)&RelativeName.RelativeName;
|
|
}
|
|
else {
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
EaBuffer = NULL;
|
|
EaSize = 0;
|
|
|
|
Status = NtQueryInformationFile(
|
|
SourceFile,
|
|
&IoStatusBlock,
|
|
&EaInfo,
|
|
sizeof(EaInfo),
|
|
FileEaInformation
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
CloseHandle(SourceFile);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) && EaInfo.EaSize ) {
|
|
EaSize = EaInfo.EaSize;
|
|
do {
|
|
EaSize *= 2;
|
|
EaBuffer = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), EaSize);
|
|
if ( !EaBuffer ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
CloseHandle(SourceFile);
|
|
BaseSetLastNTError(STATUS_NO_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
Status = NtQueryEaFile(
|
|
SourceFile,
|
|
&IoStatusBlock,
|
|
EaBuffer,
|
|
EaSize,
|
|
FALSE,
|
|
(PVOID)NULL,
|
|
0,
|
|
(PULONG)NULL,
|
|
TRUE
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, EaBuffer);
|
|
EaBuffer = NULL;
|
|
IoStatusBlock.Information = 0;
|
|
}
|
|
} while ( Status == STATUS_BUFFER_OVERFLOW ||
|
|
Status == STATUS_BUFFER_TOO_SMALL );
|
|
EaSize = (ULONG)IoStatusBlock.Information;
|
|
}
|
|
|
|
//
|
|
// Open/create the destination directory.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
&TargetName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
NULL
|
|
);
|
|
|
|
if ( ARGUMENT_PRESENT(lpSecurityAttributes) ) {
|
|
Obja.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor;
|
|
}
|
|
|
|
DesiredAccess = FILE_LIST_DIRECTORY | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | SYNCHRONIZE;
|
|
if ( BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) {
|
|
//
|
|
// To set the reparse point at the target one needs FILE_WRITE_DATA.
|
|
//
|
|
|
|
DesiredAccess |= FILE_WRITE_DATA;
|
|
}
|
|
|
|
//
|
|
// Clear the reparse attribute before creating the target. Only the
|
|
// name grafting use of reparse points is preserved at this level.
|
|
// Open first inhibiting the reparse behavior.
|
|
//
|
|
|
|
BasicInfo.FileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
Status = NtCreateFile(
|
|
&DestFile,
|
|
DesiredAccess,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
BasicInfo.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,
|
|
EaSize
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
//
|
|
// Back level file systems may not support reparse points.
|
|
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
|
|
//
|
|
|
|
if ( (Status == STATUS_INVALID_PARAMETER) ||
|
|
(Status == STATUS_ACCESS_DENIED) ) {
|
|
//
|
|
// Either the FS does not support reparse points or we do not have enough
|
|
// access to the target.
|
|
//
|
|
|
|
if ( IsNameGrafting ) {
|
|
//
|
|
// We need to return error, as the target cannot be opened correctly.
|
|
//
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
if ( EaBuffer ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, EaBuffer);
|
|
}
|
|
CloseHandle(SourceFile);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
Status = NtCreateFile(
|
|
&DestFile,
|
|
FILE_LIST_DIRECTORY | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
BasicInfo.FileAttributes,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_CREATE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
|
|
EaBuffer,
|
|
EaSize
|
|
);
|
|
}
|
|
}
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeTargetBuffer);
|
|
|
|
if ( EaBuffer ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, EaBuffer);
|
|
}
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NtClose(SourceFile);
|
|
if ( RtlIsDosDeviceName_U((LPWSTR)lpNewDirectory) ) {
|
|
Status = STATUS_NOT_A_DIRECTORY;
|
|
}
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
else {
|
|
if ( IsNameGrafting ) {
|
|
|
|
PREPARSE_DATA_BUFFER ReparseBufferHeader = NULL;
|
|
PUCHAR ReparseBuffer = NULL;
|
|
|
|
//
|
|
// Allocate the buffer to get/set the reparse point.
|
|
//
|
|
|
|
ReparseBuffer = RtlAllocateHeap(
|
|
RtlProcessHeap(),
|
|
MAKE_TAG( TMP_TAG ),
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
|
|
if ( ReparseBuffer == NULL) {
|
|
NtClose(SourceFile);
|
|
NtClose(DestFile);
|
|
BaseSetLastNTError(STATUS_NO_MEMORY);
|
|
//
|
|
// Notice that we leave behind the target directory.
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Get the data in the reparse point.
|
|
//
|
|
|
|
Status = NtFsControlFile(
|
|
SourceFile,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL, // Input buffer
|
|
0, // Input buffer length
|
|
ReparseBuffer, // Output buffer
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE // Output buffer length
|
|
);
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, ReparseBuffer);
|
|
NtClose(SourceFile);
|
|
NtClose(DestFile);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Defensive sanity check. The reparse buffer should be name grafting.
|
|
//
|
|
|
|
ReparseBufferHeader = (PREPARSE_DATA_BUFFER)ReparseBuffer;
|
|
|
|
if ( ReparseBufferHeader->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, ReparseBuffer);
|
|
NtClose(SourceFile);
|
|
NtClose(DestFile);
|
|
BaseSetLastNTError(STATUS_OBJECT_NAME_INVALID);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Finish up the creation of the target directory.
|
|
//
|
|
|
|
VolumeName.Length = VolumeName.MaximumLength =
|
|
ReparseBufferHeader->MountPointReparseBuffer.SubstituteNameLength;
|
|
VolumeName.Buffer = (PWCHAR)
|
|
((PCHAR) ReparseBufferHeader->MountPointReparseBuffer.PathBuffer +
|
|
ReparseBufferHeader->MountPointReparseBuffer.SubstituteNameOffset);
|
|
|
|
if (MOUNTMGR_IS_NT_VOLUME_NAME_WB(&VolumeName)) {
|
|
//
|
|
// Set the volume mount point and be done.
|
|
//
|
|
// SetVolumeMountPoint requires the mount point name
|
|
// to have a trailing backslash.
|
|
//
|
|
|
|
RtlInitUnicodeString(&MountPoint, lpNewDirectory);
|
|
VolumeMountPoint = RtlAllocateHeap(RtlProcessHeap(),
|
|
MAKE_TAG(TMP_TAG),
|
|
MountPoint.Length +
|
|
2*sizeof(WCHAR));
|
|
if (!VolumeMountPoint) {
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
b = FALSE;
|
|
}
|
|
else {
|
|
|
|
RtlCopyMemory(VolumeMountPoint, MountPoint.Buffer,
|
|
MountPoint.Length);
|
|
VolumeMountPoint[MountPoint.Length/sizeof(WCHAR)] = 0;
|
|
|
|
if (MountPoint.Buffer[MountPoint.Length/sizeof(WCHAR) - 1] != '\\') {
|
|
VolumeMountPoint[MountPoint.Length/sizeof(WCHAR)] = '\\';
|
|
VolumeMountPoint[MountPoint.Length/sizeof(WCHAR) + 1] = 0;
|
|
}
|
|
|
|
//
|
|
// The volume name should be like "\\?\Volume{guid}\"
|
|
//
|
|
|
|
VolumeName.Buffer[1] = '\\';
|
|
|
|
b = SetVolumeMountPointW(
|
|
VolumeMountPoint,
|
|
VolumeName.Buffer
|
|
);
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, VolumeMountPoint);
|
|
VolumeName.Buffer[1] = '?';
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Copy the directory junction and be done.
|
|
//
|
|
|
|
b = TRUE;
|
|
Status = NtFsControlFile(
|
|
DestFile,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_SET_REPARSE_POINT,
|
|
ReparseBuffer,
|
|
FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + ReparseBufferHeader->ReparseDataLength,
|
|
NULL, // Output buffer
|
|
0 // Output buffer length
|
|
);
|
|
}
|
|
|
|
//
|
|
// Free the buffer.
|
|
//
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, ReparseBuffer);
|
|
|
|
//
|
|
// Close all files and return appropriatelly.
|
|
//
|
|
|
|
NtClose(SourceFile);
|
|
NtClose(DestFile);
|
|
|
|
if ( !b ) {
|
|
//
|
|
// No need to set the last error as SetVolumeMountPointW has done it.
|
|
//
|
|
return FALSE;
|
|
}
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
//
|
|
// The source directory was a name grafting directory.
|
|
// No data streams are copied.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Attempt to determine whether or not this file has any alternate
|
|
// data streams associated with it. If it does, attempt to copy each
|
|
// to the output file. If any copy fails, simply drop the error on
|
|
// the floor, and continue.
|
|
//
|
|
|
|
StreamInfoSize = 4096;
|
|
CopySize = 4096;
|
|
do {
|
|
StreamInfoBase = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), StreamInfoSize);
|
|
if ( !StreamInfoBase ) {
|
|
BaseMarkFileForDelete(DestFile, BasicInfo.FileAttributes);
|
|
BaseSetLastNTError(STATUS_NO_MEMORY);
|
|
b = FALSE;
|
|
break;
|
|
}
|
|
Status = NtQueryInformationFile(
|
|
SourceFile,
|
|
&IoStatusBlock,
|
|
(PVOID) StreamInfoBase,
|
|
StreamInfoSize,
|
|
FileStreamInformation
|
|
);
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, StreamInfoBase);
|
|
StreamInfoBase = NULL;
|
|
StreamInfoSize *= 2;
|
|
}
|
|
} while ( Status == STATUS_BUFFER_OVERFLOW ||
|
|
Status == STATUS_BUFFER_TOO_SMALL );
|
|
|
|
//
|
|
// Directories do not always have a stream
|
|
//
|
|
|
|
if ( NT_SUCCESS(Status) && IoStatusBlock.Information ) {
|
|
StreamInfo = StreamInfoBase;
|
|
|
|
for (;;) {
|
|
|
|
DWORD DestFileFsAttributes = 0;
|
|
|
|
//
|
|
// Build a string descriptor for the name of the stream.
|
|
//
|
|
|
|
StreamName.Buffer = &StreamInfo->StreamName[0];
|
|
StreamName.Length = (USHORT) StreamInfo->StreamNameLength;
|
|
StreamName.MaximumLength = StreamName.Length;
|
|
|
|
//
|
|
// Open the source stream.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
&StreamName,
|
|
0,
|
|
SourceFile,
|
|
NULL
|
|
);
|
|
Status = NtCreateFile(
|
|
&StreamHandle,
|
|
GENERIC_READ | SYNCHRONIZE,
|
|
&Obja,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
0,
|
|
FILE_SHARE_READ,
|
|
FILE_OPEN,
|
|
FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL,
|
|
0
|
|
);
|
|
if ( NT_SUCCESS(Status) ) {
|
|
for ( i = 0; i < (ULONG) StreamName.Length >> 1; i++ ) {
|
|
FileName[i] = StreamName.Buffer[i];
|
|
}
|
|
FileName[i] = L'\0';
|
|
OutputStream = (HANDLE)NULL;
|
|
Options = 0;
|
|
b = BaseCopyStream(
|
|
NULL,
|
|
StreamHandle,
|
|
GENERIC_READ | SYNCHRONIZE,
|
|
FileName,
|
|
DestFile,
|
|
&StreamInfo->StreamSize,
|
|
&Options,
|
|
&OutputStream,
|
|
&CopySize,
|
|
&CopyFileContext,
|
|
(LPRESTART_STATE)NULL,
|
|
(BOOL)FALSE,
|
|
(DWORD)0,
|
|
&DestFileFsAttributes
|
|
);
|
|
NtClose(StreamHandle);
|
|
if ( OutputStream ) {
|
|
NtClose(OutputStream);
|
|
}
|
|
}
|
|
|
|
if ( StreamInfo->NextEntryOffset ) {
|
|
StreamInfo = (PFILE_STREAM_INFORMATION)((PCHAR) StreamInfo + StreamInfo->NextEntryOffset);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
if ( StreamInfoBase ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, StreamInfoBase);
|
|
}
|
|
b = TRUE;
|
|
}
|
|
NtClose(SourceFile);
|
|
if ( DestFile ) {
|
|
NtClose(DestFile);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
BOOL
|
|
APIENTRY
|
|
RemoveDirectoryA(
|
|
LPCSTR lpPathName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ANSI thunk to RemoveDirectoryW
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PUNICODE_STRING Unicode;
|
|
|
|
Unicode = Basep8BitStringToStaticUnicodeString( lpPathName );
|
|
if (Unicode == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return ( RemoveDirectoryW((LPCWSTR)Unicode->Buffer) );
|
|
}
|
|
|
|
BOOL
|
|
APIENTRY
|
|
RemoveDirectoryW(
|
|
LPCWSTR lpPathName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
An existing directory can be removed using RemoveDirectory.
|
|
|
|
This API causes a directory with the specified pathname to be
|
|
deleted. The directory must be empty before this call can succeed.
|
|
|
|
This call is similar to DOS (int 21h, function 3Ah) and OS/2's
|
|
DosDeleteDir.
|
|
|
|
Arguments:
|
|
|
|
lpPathName - Supplies the pathname of the directory to be removed.
|
|
The path must specify an empty directory to which the caller has
|
|
delete access.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The operation was successful.
|
|
|
|
FALSE/NULL - The operation failed. Extended error status is available
|
|
using GetLastError.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES Obja;
|
|
HANDLE Handle;
|
|
UNICODE_STRING FileName;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_DISPOSITION_INFORMATION Disposition;
|
|
BOOLEAN TranslationStatus;
|
|
RTL_RELATIVE_NAME RelativeName;
|
|
PVOID FreeBuffer;
|
|
BOOLEAN IsNameGrafting = FALSE;
|
|
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
|
|
PREPARSE_DATA_BUFFER reparse;
|
|
BOOL b;
|
|
DWORD bytes;
|
|
UNICODE_STRING mountName;
|
|
PWCHAR volumeMountPoint;
|
|
|
|
|
|
TranslationStatus = RtlDosPathNameToNtPathName_U(
|
|
lpPathName,
|
|
&FileName,
|
|
NULL,
|
|
&RelativeName
|
|
);
|
|
|
|
if ( !TranslationStatus ) {
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return FALSE;
|
|
}
|
|
|
|
FreeBuffer = FileName.Buffer;
|
|
|
|
if ( RelativeName.RelativeName.Length ) {
|
|
FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
|
|
}
|
|
else {
|
|
RelativeName.ContainingDirectory = NULL;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&Obja,
|
|
&FileName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
RelativeName.ContainingDirectory,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Open the directory for delete access.
|
|
// Inhibit the reparse behavior using FILE_OPEN_REPARSE_POINT.
|
|
//
|
|
|
|
Status = NtOpenFile(
|
|
&Handle,
|
|
DELETE | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
|
|
&Obja,
|
|
&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) ) {
|
|
//
|
|
// Back level file systems may not support reparse points and thus not
|
|
// support symbolic links.
|
|
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
|
|
//
|
|
|
|
if ( Status == STATUS_INVALID_PARAMETER ) {
|
|
//
|
|
// Re-open not inhibiting the reparse behavior and not needing to read the attributes.
|
|
//
|
|
|
|
Status = NtOpenFile(
|
|
&Handle,
|
|
DELETE | SYNCHRONIZE,
|
|
&Obja,
|
|
&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) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// If we found a reparse point that is not a name grafting operation,
|
|
// either a symbolic link or a mount point, we re-open without
|
|
// inhibiting the reparse behavior.
|
|
//
|
|
|
|
Status = NtQueryInformationFile(
|
|
Handle,
|
|
&IoStatusBlock,
|
|
(PVOID) &FileTagInformation,
|
|
sizeof(FileTagInformation),
|
|
FileAttributeTagInformation
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
//
|
|
// Not all File Systems implement all information classes.
|
|
// The value STATUS_INVALID_PARAMETER is returned when a non-supported
|
|
// information class is requested to a back-level File System. As all the
|
|
// parameters to NtQueryInformationFile are correct, we can infer that
|
|
// we found a back-level system.
|
|
//
|
|
// If FileAttributeTagInformation is not implemented, we assume that
|
|
// the file at hand is not a reparse point.
|
|
//
|
|
|
|
if ( (Status != STATUS_NOT_IMPLEMENTED) &&
|
|
(Status != STATUS_INVALID_PARAMETER) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
NtClose(Handle);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) &&
|
|
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ) {
|
|
if ( FileTagInformation.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT ) {
|
|
|
|
//
|
|
// If this is a volume mount point then fail with
|
|
// "directory not empty".
|
|
//
|
|
|
|
reparse = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG(TMP_TAG),
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!reparse) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
NtClose(Handle);
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
b = DeviceIoControl(Handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
|
reparse, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
|
|
&bytes, NULL);
|
|
|
|
if (b) {
|
|
|
|
mountName.Length = mountName.MaximumLength =
|
|
reparse->MountPointReparseBuffer.SubstituteNameLength;
|
|
mountName.Buffer = (PWCHAR)
|
|
((PCHAR) reparse->MountPointReparseBuffer.PathBuffer +
|
|
reparse->MountPointReparseBuffer.SubstituteNameOffset);
|
|
|
|
if (MOUNTMGR_IS_VOLUME_NAME(&mountName)) {
|
|
|
|
RtlInitUnicodeString(&mountName, lpPathName);
|
|
volumeMountPoint = RtlAllocateHeap(RtlProcessHeap(),
|
|
MAKE_TAG(TMP_TAG),
|
|
mountName.Length +
|
|
2*sizeof(WCHAR));
|
|
if (!volumeMountPoint) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, reparse);
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
NtClose(Handle);
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlCopyMemory(volumeMountPoint, mountName.Buffer,
|
|
mountName.Length);
|
|
volumeMountPoint[mountName.Length/sizeof(WCHAR)] = 0;
|
|
|
|
if (mountName.Buffer[mountName.Length/sizeof(WCHAR) - 1] != '\\') {
|
|
volumeMountPoint[mountName.Length/sizeof(WCHAR)] = '\\';
|
|
volumeMountPoint[mountName.Length/sizeof(WCHAR) + 1] = 0;
|
|
}
|
|
|
|
DeleteVolumeMountPointW(volumeMountPoint);
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, volumeMountPoint);
|
|
}
|
|
}
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, reparse);
|
|
IsNameGrafting = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) &&
|
|
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
|
!IsNameGrafting) {
|
|
//
|
|
// Re-open without inhibiting the reparse behavior and not needing to
|
|
// read the attributes.
|
|
//
|
|
|
|
NtClose(Handle);
|
|
Status = NtOpenFile(
|
|
&Handle,
|
|
DELETE | SYNCHRONIZE,
|
|
&Obja,
|
|
&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) ) {
|
|
//
|
|
// When the FS Filter is absent, delete it any way.
|
|
//
|
|
|
|
if ( Status == STATUS_IO_REPARSE_TAG_NOT_HANDLED ) {
|
|
//
|
|
// We re-open (possible 3rd open) for delete access inhibiting the reparse behavior.
|
|
//
|
|
|
|
Status = NtOpenFile(
|
|
&Handle,
|
|
DELETE | SYNCHRONIZE,
|
|
&Obja,
|
|
&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) ) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
|
|
|
|
//
|
|
// Delete the file
|
|
//
|
|
#undef DeleteFile
|
|
Disposition.DeleteFile = TRUE;
|
|
|
|
Status = NtSetInformationFile(
|
|
Handle,
|
|
&IoStatusBlock,
|
|
&Disposition,
|
|
sizeof(Disposition),
|
|
FileDispositionInformation
|
|
);
|
|
|
|
NtClose(Handle);
|
|
if ( NT_SUCCESS(Status) ) {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
BaseSetLastNTError(Status);
|
|
return FALSE;
|
|
}
|
|
}
|