Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2455 lines
71 KiB

/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
filemisc.c
Abstract:
Misc file operations for Win32
Author:
Mark Lucovsky (markl) 26-Sep-1990
Revision History:
--*/
#include <basedll.h>
DWORD
BasepGetComputerNameFromNtPath (
PUNICODE_STRING NtPathName,
HANDLE hFile,
LPWSTR lpBuffer,
LPDWORD nSize
);
NTSTATUS
BasepMoveFileDelayed(
IN PUNICODE_STRING OldFileName,
IN PUNICODE_STRING NewFileName,
IN ULONG Index,
IN BOOL OkayToCreateNewValue
);
BOOL
APIENTRY
SetFileAttributesA(
LPCSTR lpFileName,
DWORD dwFileAttributes
)
/*++
Routine Description:
ANSI thunk to SetFileAttributesW
--*/
{
PUNICODE_STRING Unicode;
Unicode = Basep8BitStringToStaticUnicodeString( lpFileName );
if (Unicode == NULL) {
return FALSE;
}
return ( SetFileAttributesW(
(LPCWSTR)Unicode->Buffer,
dwFileAttributes
)
);
}
BOOL
APIENTRY
SetFileAttributesW(
LPCWSTR lpFileName,
DWORD dwFileAttributes
)
/*++
Routine Description:
The attributes of a file can be set using SetFileAttributes.
This API provides the same functionality as DOS (int 21h, function
43H with AL=1), and provides a subset of OS/2's DosSetFileInfo.
Arguments:
lpFileName - Supplies the file name of the file whose attributes are to
be set.
dwFileAttributes - Specifies the file attributes to be set for the
file. Any combination of flags is acceptable except that all
other flags override the normal file attribute,
FILE_ATTRIBUTE_NORMAL.
FileAttributes Flags:
FILE_ATTRIBUTE_NORMAL - A normal file should be created.
FILE_ATTRIBUTE_READONLY - A read-only file should be created.
FILE_ATTRIBUTE_HIDDEN - A hidden file should be created.
FILE_ATTRIBUTE_SYSTEM - A system file should be created.
FILE_ATTRIBUTE_ARCHIVE - The file should be marked so that it
will be archived.
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_BASIC_INFORMATION BasicInfo;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
&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 file inhibiting the reparse behavior.
//
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
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.
// We infer this is the case when the Status is STATUS_INVALID_PARAMETER.
//
if ( Status == STATUS_INVALID_PARAMETER ) {
//
// Open the file without inhibiting the reparse behavior.
//
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
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;
}
}
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
//
// Set the attributes
//
RtlZeroMemory(&BasicInfo,sizeof(BasicInfo));
BasicInfo.FileAttributes = (dwFileAttributes & FILE_ATTRIBUTE_VALID_SET_FLAGS) | FILE_ATTRIBUTE_NORMAL;
Status = NtSetInformationFile(
Handle,
&IoStatusBlock,
&BasicInfo,
sizeof(BasicInfo),
FileBasicInformation
);
NtClose(Handle);
if ( NT_SUCCESS(Status) ) {
return TRUE;
}
else {
BaseSetLastNTError(Status);
return FALSE;
}
}
DWORD
APIENTRY
GetFileAttributesA(
LPCSTR lpFileName
)
/*++
Routine Description:
ANSI thunk to GetFileAttributesW
--*/
{
PUNICODE_STRING Unicode;
Unicode = Basep8BitStringToStaticUnicodeString( lpFileName );
if (Unicode == NULL) {
return (DWORD)-1;
}
return ( GetFileAttributesW((LPCWSTR)Unicode->Buffer) );
}
DWORD
APIENTRY
GetFileAttributesW(
LPCWSTR lpFileName
)
/*++
Routine Description:
The attributes of a file can be obtained using GetFileAttributes.
This API provides the same functionality as DOS (int 21h, function
43H with AL=0), and provides a subset of OS/2's DosQueryFileInfo.
Arguments:
lpFileName - Supplies the file name of the file whose attributes are to
be set.
Return Value:
Not -1 - Returns the attributes of the specified file. Valid
returned attributes are:
FILE_ATTRIBUTE_NORMAL - The file is a normal file.
FILE_ATTRIBUTE_READONLY - The file is marked read-only.
FILE_ATTRIBUTE_HIDDEN - The file is marked as hidden.
FILE_ATTRIBUTE_SYSTEM - The file is marked as a system file.
FILE_ATTRIBUTE_ARCHIVE - The file is marked for archive.
FILE_ATTRIBUTE_DIRECTORY - The file is marked as a directory.
FILE_ATTRIBUTE_REPARSE_POINT - The file is marked as a reparse point.
FILE_ATTRIBUTE_VOLUME_LABEL - The file is marked as a volume lable.
0xffffffff - The operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING FileName;
FILE_BASIC_INFORMATION BasicInfo;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
&FileName,
NULL,
&RelativeName
);
if ( !TranslationStatus ) {
SetLastError(ERROR_PATH_NOT_FOUND);
return (DWORD)-1;
}
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 file
//
Status = NtQueryAttributesFile(
&Obja,
&BasicInfo
);
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if ( NT_SUCCESS(Status) ) {
return BasicInfo.FileAttributes;
}
else {
//
// Check for a device name.
//
if ( RtlIsDosDeviceName_U((PWSTR)lpFileName) ) {
return FILE_ATTRIBUTE_ARCHIVE;
}
BaseSetLastNTError(Status);
return (DWORD)-1;
}
}
BOOL
APIENTRY
GetFileAttributesExA(
LPCSTR lpFileName,
GET_FILEEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFileInformation
)
/*++
Routine Description:
ANSI thunk to GetFileAttributesExW
--*/
{
PUNICODE_STRING Unicode;
Unicode = Basep8BitStringToStaticUnicodeString( lpFileName );
if (Unicode == NULL) {
return FALSE;
}
return ( GetFileAttributesExW((LPCWSTR)Unicode->Buffer,fInfoLevelId,lpFileInformation) );
}
BOOL
APIENTRY
GetFileAttributesExW(
LPCWSTR lpFileName,
GET_FILEEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFileInformation
)
/*++
Routine Description:
The main attributes of a file can be obtained using GetFileAttributesEx.
Arguments:
lpFileName - Supplies the file name of the file whose attributes are to
be set.
fInfoLevelId - Supplies the info level indicating the information to be
returned about the file.
lpFileInformation - Supplies a buffer to receive the specified information
about the file.
Return Value:
TRUE - The operation was successful.
FALSE/NULL - The operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING FileName;
FILE_NETWORK_OPEN_INFORMATION NetworkInfo;
LPWIN32_FILE_ATTRIBUTE_DATA AttributeData;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
//
// Check the parameters. Note that for now there is only one info level,
// so there's no special code here to determine what to do.
//
if ( fInfoLevelId >= GetFileExMaxInfoLevel || fInfoLevelId < GetFileExInfoStandard ) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
&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
);
//
// Query the information about the file using the path-based NT service.
//
Status = NtQueryFullAttributesFile( &Obja, &NetworkInfo );
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if ( NT_SUCCESS(Status) ) {
AttributeData = (LPWIN32_FILE_ATTRIBUTE_DATA)lpFileInformation;
AttributeData->dwFileAttributes = NetworkInfo.FileAttributes;
AttributeData->ftCreationTime = *(PFILETIME)&NetworkInfo.CreationTime;
AttributeData->ftLastAccessTime = *(PFILETIME)&NetworkInfo.LastAccessTime;
AttributeData->ftLastWriteTime = *(PFILETIME)&NetworkInfo.LastWriteTime;
AttributeData->nFileSizeHigh = NetworkInfo.EndOfFile.HighPart;
AttributeData->nFileSizeLow = (DWORD)NetworkInfo.EndOfFile.LowPart;
return TRUE;
}
else {
BaseSetLastNTError(Status);
return FALSE;
}
}
BOOL
APIENTRY
DeleteFileA(
LPCSTR lpFileName
)
/*++
Routine Description:
ANSI thunk to DeleteFileW
--*/
{
PUNICODE_STRING Unicode;
Unicode = Basep8BitStringToStaticUnicodeString( lpFileName );
if (Unicode == NULL) {
return FALSE;
}
return ( DeleteFileW((LPCWSTR)Unicode->Buffer) );
}
BOOL
APIENTRY
DeleteFileW(
LPCWSTR lpFileName
)
/*++
Routine Description:
An existing file can be deleted using DeleteFile.
This API provides the same functionality as DOS (int 21h, function 41H)
and OS/2's DosDelete.
Arguments:
lpFileName - Supplies the file name of the file to be deleted.
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;
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
BOOLEAN fIsSymbolicLink = FALSE;
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
&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 file for delete access.
// Inhibit the reparse behavior using FILE_OPEN_REPARSE_POINT.
//
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)DELETE | FILE_READ_ATTRIBUTES,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_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 ) {
//
// Open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)DELETE,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT
);
if ( !NT_SUCCESS(Status) ) {
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
BaseSetLastNTError(Status);
return FALSE;
}
}
else {
//
// A second case of interest is when the caller does not have rights
// to read attributes yet it does have rights to delete the file.
// In this case Status is to be STATUS_ACCESS_DENIED.
//
if ( Status != STATUS_ACCESS_DENIED ) {
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
BaseSetLastNTError(Status);
return FALSE;
}
//
// Re-open inhibiting reparse point and not requiring read attributes.
//
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)DELETE,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT
);
if ( !NT_SUCCESS(Status) ) {
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
BaseSetLastNTError(Status);
return FALSE;
}
//
// If we are here, Handle is valid.
//
// Moreover, Handle is to a file for which the caller has DELETE right yet
// does not have FILE_READ_ATTRIBUTES rights.
//
// The underlying file may or not be a reparse point.
// As the caller does not have rights to read the attributes this code
// will delete this file without giving the opportunity to the
// appropriate manager of these reparse points to clean-up its internal
// state at this time.
//
}
}
else {
//
// If we found a reparse point that is not a symbolic link, 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 ) {
fIsSymbolicLink = TRUE;
}
}
if ( NT_SUCCESS(Status) &&
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
!fIsSymbolicLink) {
//
// Re-open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
NtClose(Handle);
Status = NtOpenFile(
&Handle,
(ACCESS_MASK)DELETE,
&Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_NON_DIRECTORY_FILE | 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,
(ACCESS_MASK)DELETE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_NON_DIRECTORY_FILE | 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;
}
}
//
// Ascii versions that thunk to the common code
//
BOOL
APIENTRY
MoveFileA(
LPCSTR lpExistingFileName,
LPCSTR lpNewFileName
)
{
return MoveFileWithProgressA( lpExistingFileName,
lpNewFileName,
(LPPROGRESS_ROUTINE)NULL,
NULL,
MOVEFILE_COPY_ALLOWED );
}
BOOL
APIENTRY
MoveFileExA(
LPCSTR lpExistingFileName,
LPCSTR lpNewFileName,
DWORD dwFlags
)
{
return MoveFileWithProgressA( lpExistingFileName,
lpNewFileName,
(LPPROGRESS_ROUTINE)NULL,
NULL,
dwFlags );
}
BOOL
APIENTRY
MoveFileWithProgressA(
LPCSTR lpExistingFileName,
LPCSTR lpNewFileName,
LPPROGRESS_ROUTINE lpProgressRoutine,
LPVOID lpData OPTIONAL,
DWORD dwFlags
)
/*++
Routine Description:
ANSI thunk to MoveFileWithProgressW
--*/
{
UNICODE_STRING UnicodeOldFileName;
UNICODE_STRING UnicodeNewFileName;
BOOL ReturnValue;
if ( !Basep8BitStringToDynamicUnicodeString(&UnicodeOldFileName, lpExistingFileName) ) {
return FALSE;
}
if ( ARGUMENT_PRESENT(lpNewFileName) ) {
if ( !Basep8BitStringToDynamicUnicodeString(&UnicodeNewFileName, lpNewFileName) ) {
RtlFreeUnicodeString(&UnicodeOldFileName);
return FALSE;
}
}
else {
UnicodeNewFileName.Buffer = NULL;
}
ReturnValue =
MoveFileWithProgressW( (LPCWSTR)UnicodeOldFileName.Buffer,
(LPCWSTR)UnicodeNewFileName.Buffer,
lpProgressRoutine,
lpData,
dwFlags
);
RtlFreeUnicodeString(&UnicodeOldFileName);
RtlFreeUnicodeString(&UnicodeNewFileName);
return ReturnValue;
}
typedef struct _HELPER_CONTEXT {
DWORD dwFlags;
LPPROGRESS_ROUTINE lpProgressRoutine;
LPVOID lpData;
} HELPER_CONTEXT, *PHELPER_CONTEXT;
DWORD
APIENTRY
BasepMoveFileCopyProgress(
LARGE_INTEGER TotalFileSize,
LARGE_INTEGER TotalBytesTransferred,
LARGE_INTEGER StreamSize,
LARGE_INTEGER StreamBytesTransferred,
DWORD dwStreamNumber,
DWORD dwCallbackReason,
HANDLE SourceFile,
HANDLE DestinationFile,
LPVOID lpData OPTIONAL
)
/*++
Routine Description:
Perform special actions when doing move-by-copy.
Arguments:
TotalFileSize - total number of bytes being transferred
TotalBytesTransferred - current progress through the file
StreamSize - total number of bytes being transferred in this stream
StreamBytesTransferred - current progress through this stream
dwStreamNumber - ordinal number of stream
dwCallbackReason - CopyFile's reason for calling us
SourceFile - source handle of transfer
DestinationFile - destination handle of transfer
lpData - pointer to HELPER_CONTEXT constructed by MoveFileWithProgressW.
Return Value:
PROGRESS_CONTINUE if no progress routine was specified, otherwise
the return value from the progress routine specified to
MoveFileWithProgress
--*/
{
PHELPER_CONTEXT Context = (PHELPER_CONTEXT)lpData;
//
// If we are finished with a stream and the caller
// specified WRITE_THROUGH then we make sure the file buffers
// actually made it out to disk.
//
if ((Context->dwFlags & MOVEFILE_WRITE_THROUGH) != 0
&& dwCallbackReason == CALLBACK_CHUNK_FINISHED
&& StreamBytesTransferred.QuadPart == StreamSize.QuadPart ) {
FlushFileBuffers(DestinationFile);
}
//
// If a callback routine was specified, call through him
//
if (Context->lpProgressRoutine == NULL) {
return PROGRESS_CONTINUE;
}
return (Context->lpProgressRoutine) (
TotalFileSize,
TotalBytesTransferred,
StreamSize,
StreamBytesTransferred,
dwStreamNumber,
dwCallbackReason,
SourceFile,
DestinationFile,
Context->lpData );
}
NTSTATUS
BasepNotifyTrackingService( PHANDLE SourceFile,
POBJECT_ATTRIBUTES SourceFileObjAttributes,
HANDLE DestFile,
PUNICODE_STRING NewFileName
)
{
NTSTATUS Status = STATUS_SUCCESS;
FILE_BASIC_INFORMATION BasicInformation;
IO_STATUS_BLOCK IoStatusBlock;
ULONG FileAttributes;
ULONG cchComputerName;
WCHAR ComputerName[ MAX_COMPUTERNAME_LENGTH + 1 ];
DWORD dwError;
BYTE FTIBuffer[ sizeof(FILE_TRACKING_INFORMATION) + MAX_COMPUTERNAME_LENGTH + 1 ];
PFILE_TRACKING_INFORMATION pfti = (PFILE_TRACKING_INFORMATION) &FTIBuffer[0];
try
{
cchComputerName = MAX_COMPUTERNAME_LENGTH + 1;
dwError = BasepGetComputerNameFromNtPath( NewFileName,
DestFile,
ComputerName,
&cchComputerName );
if (ERROR_SUCCESS != dwError) {
pfti->ObjectInformationLength = 0;
} else {
CHAR ComputerNameOemBuffer[ MAX_PATH ];
OEM_STRING ComputerNameOemString = { 0,
sizeof(ComputerNameOemBuffer),
ComputerNameOemBuffer };
UNICODE_STRING ComputerNameUnicodeString;
RtlInitUnicodeString( &ComputerNameUnicodeString,
ComputerName );
Status = RtlUnicodeStringToOemString( &ComputerNameOemString,
&ComputerNameUnicodeString,
FALSE ); // Don't allocate
if( !NT_SUCCESS(Status) ) {
leave;
}
memcpy( pfti->ObjectInformation,
ComputerNameOemString.Buffer,
ComputerNameOemString.Length );
pfti->ObjectInformation[ ComputerNameOemString.Length ] = '\0';
// Fill in the rest of the fti buffer, and set the file information
pfti->ObjectInformationLength = ComputerNameOemString.Length + 1;
}
pfti->DestinationFile = DestFile;
Status = NtSetInformationFile(
*SourceFile,
&IoStatusBlock,
pfti,
sizeof( FTIBuffer ),
FileTrackingInformation );
//
// Check to see if tracking failed because
// the source has a read-only attribute set.
//
if (Status != STATUS_ACCESS_DENIED) {
leave;
}
//
// reopen the source file and reset the read-only attribute
// so that we'll be able to open for write access.
//
CloseHandle(*SourceFile);
Status = NtOpenFile(
SourceFile,
SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
SourceFileObjAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT
);
if (!NT_SUCCESS(Status)) {
*SourceFile = INVALID_HANDLE_VALUE;
leave;
}
Status = NtQueryInformationFile(
*SourceFile,
&IoStatusBlock,
&BasicInformation,
sizeof(BasicInformation),
FileBasicInformation );
if (!NT_SUCCESS(Status)) {
leave;
}
//
// Reset the r/o bit and write the attributes back.
//
FileAttributes = BasicInformation.FileAttributes;
RtlZeroMemory(&BasicInformation, sizeof(BasicInformation));
BasicInformation.FileAttributes = FileAttributes & ~FILE_ATTRIBUTE_READONLY;
Status = NtSetInformationFile(
*SourceFile,
&IoStatusBlock,
&BasicInformation,
sizeof(BasicInformation),
FileBasicInformation);
if (!NT_SUCCESS(Status)) {
//
// If this fails, we can't track the file.
//
leave;
}
//
// Now that the r/o bit is reset, reopen for write access and
// retry the tracking notification.
//
else {
HANDLE hSourceRw;
Status = NtOpenFile(
&hSourceRw,
SYNCHRONIZE | GENERIC_WRITE,
SourceFileObjAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT
);
if (NT_SUCCESS(Status)) {
NtClose(*SourceFile);
*SourceFile = hSourceRw;
//
// Send the source machine a tracking notification.
//
Status = NtSetInformationFile( *SourceFile,
&IoStatusBlock,
pfti,
sizeof( FTIBuffer ),
FileTrackingInformation );
}
}
if (!NT_SUCCESS(Status)) {
//
// Try to put back the r/o bit -- don't assign Status here
// because we want to fail. If we crash here, we may leave
// the r/o attribute clear when it should be set, but there's
// not much we can do about it without a transaction.
//
BasicInformation.FileAttributes |= FILE_ATTRIBUTE_READONLY;
NtSetInformationFile(
*SourceFile,
&IoStatusBlock,
&BasicInformation,
sizeof(BasicInformation),
FileBasicInformation);
}
}
finally
{
}
return( Status );
}
BOOL
APIENTRY
MoveFileW(
LPCWSTR lpExistingFileName,
LPCWSTR lpNewFileName
)
{
return MoveFileWithProgressW( lpExistingFileName,
lpNewFileName,
(LPPROGRESS_ROUTINE)NULL,
NULL,
MOVEFILE_COPY_ALLOWED );
}
BOOL
APIENTRY
MoveFileExW(
LPCWSTR lpExistingFileName,
LPCWSTR lpNewFileName,
DWORD dwFlags
)
{
return MoveFileWithProgressW( lpExistingFileName,
lpNewFileName,
(LPPROGRESS_ROUTINE)NULL,
NULL,
dwFlags );
}
BOOL
APIENTRY
MoveFileWithProgressW(
LPCWSTR lpExistingFileName,
LPCWSTR lpNewFileName,
LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
LPVOID lpData OPTIONAL,
DWORD dwFlags
)
/*++
Routine Description:
An existing file can be renamed using MoveFileWithProgressW.
Arguments:
lpExistingFileName - Supplies the name of an existing file that is to be
renamed.
lpNewFileName - Supplies the new name for the existing file. The new
name must reside in the same file system/drive as the existing
file and must not already exist.
lpProgressRoutine - Supplies a callback routine that is notified.
lpData - Supplies context data passed to the progress routine.
dwFlags - Supplies optional flag bits to control the behavior of the
rename. The following bits are currently defined:
MOVEFILE_REPLACE_EXISTING - if the new file name exists, replace
it by renaming the old file name on top of the new file name.
MOVEFILE_COPY_ALLOWED - if the new file name is on a different
volume than the old file name, and causes the rename operation
to fail, then setting this flag allows the MoveFileEx API
call to simulate the rename with a call to CopyFile followed
by a call to DeleteFile to the delete the old file if the
CopyFile was successful.
MOVEFILE_DELAY_UNTIL_REBOOT - dont actually do the rename now, but
instead queue the rename so that it will happen the next time
the system boots. If this flag is set, then the lpNewFileName
parameter may be NULL, in which case a delay DeleteFile of
the old file name will occur the next time the system is
booted.
The delay rename/delete operations occur immediately after
AUTOCHK is run, but prior to creating any paging files, so
it can be used to delete paging files from previous boots
before they are reused.
MOVEFILE_WRITE_THROUGH - perform the rename operation in such a
way that the file has actually been moved on the disk before
the API returns to the caller. Note that this flag causes a
flush at the end of a copy operation (if one were allowed and
necessary), and has no effect if the rename operation is
delayed until the next reboot.
MOVEFILE_CREATE_HARDLINK - create a hard link from the new file name to
the existing file name. May not be specified with
MOVEFILE_DELAY_UNTIL_REBOOT
MOVEFILE_FAIL_IF_NOT_TRACKABLE - fail the move request if the file cannot
be tracked.
Return Value:
TRUE - The operation was successful.
FALSE/NULL - The operation failed. Extended error status is available
using GetLastError.
--*/
{
NTSTATUS Status;
BOOLEAN ReplaceIfExists;
OBJECT_ATTRIBUTES Obja;
HANDLE Handle = INVALID_HANDLE_VALUE;
UNICODE_STRING OldFileName;
UNICODE_STRING NewFileName;
IO_STATUS_BLOCK IoStatusBlock;
PFILE_RENAME_INFORMATION NewName;
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
UNICODE_STRING RelativeOldName;
ULONG OpenFlags;
BOOLEAN b = FALSE;
HELPER_CONTEXT Context;
NewFileName.Buffer = NULL;
OldFileName.Buffer = NULL;
try {
//
// if the target is a device, do not allow the rename !
//
if ( lpNewFileName ) {
if ( RtlIsDosDeviceName_U((PWSTR)lpNewFileName) ) {
BaseSetLastNTError( STATUS_OBJECT_NAME_COLLISION );
leave;
}
}
ReplaceIfExists = (dwFlags & MOVEFILE_REPLACE_EXISTING) != 0;
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpExistingFileName,
&OldFileName,
NULL,
&RelativeName
);
if ( !TranslationStatus ) {
BaseSetLastNTError( STATUS_OBJECT_PATH_NOT_FOUND );
leave;
}
//
// Cannot perform delayed-move-by-creating-hardlink
//
if ((dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT) != 0 &&
(dwFlags & MOVEFILE_CREATE_HARDLINK) != 0) {
BaseSetLastNTError( STATUS_INVALID_PARAMETER );
leave;
}
//
// Get a handle to the source of the move. We do this even for
// the delayed move in order to validate that we have delete
// access to the file.
//
if ( RelativeName.RelativeName.Length ) {
RelativeOldName = *(PUNICODE_STRING)&RelativeName.RelativeName;
} else {
RelativeOldName = OldFileName;
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(
&Obja,
&RelativeOldName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL
);
//
// Establish whether we are renaming a symbolic link or not by:
// (1) obtaining a handle to the local entity, and
// (2) finding whether a symbolic link was found.
//
// Open the file for delete access inhibiting the reparse
// point behavior.
//
OpenFlags = FILE_SYNCHRONOUS_IO_NONALERT |
FILE_OPEN_FOR_BACKUP_INTENT |
((dwFlags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0);
Status = NtOpenFile( &Handle,
FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_REPARSE_POINT | OpenFlags
);
if (!NT_SUCCESS( Status )) {
//
// The Open may fail for a number of reasons. If we're
// delaying the operation until reboot, it doesn't matter
// if we get a sharing violation or a non-existent file
// or a non-existent path.
//
if (((dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT) != 0)
&& (Status == STATUS_SHARING_VIOLATION
|| Status == STATUS_OBJECT_NAME_NOT_FOUND
|| Status == STATUS_OBJECT_PATH_NOT_FOUND)) {
Handle = INVALID_HANDLE_VALUE;
} else {
//
// 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 ) {
//
// Retry the open without reparse behaviour. This should be compatible
// with older file systems.
//
Status = NtOpenFile(
&Handle,
DELETE | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
OpenFlags
);
}
if ( !NT_SUCCESS( Status ) ) {
BaseSetLastNTError( Status );
leave;
}
}
} else {
//
// The open succeeded. If we do not find a symbolic link or a mount point,
// 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) ) {
BaseSetLastNTError( Status );
leave;
}
}
if ( NT_SUCCESS(Status) &&
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
FileTagInformation.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT ) {
//
// Open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
NtClose( Handle );
Handle = INVALID_HANDLE_VALUE;
Status = NtOpenFile(
&Handle,
DELETE | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
OpenFlags
);
if ( !NT_SUCCESS( Status ) ) {
BaseSetLastNTError( Status );
leave;
}
}
}
if (!(dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT) ||
(lpNewFileName != NULL)) {
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpNewFileName,
&NewFileName,
NULL,
NULL
);
if ( !TranslationStatus ) {
BaseSetLastNTError( STATUS_OBJECT_PATH_NOT_FOUND );
leave;
}
} else {
RtlInitUnicodeString( &NewFileName, NULL );
}
if (dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT) {
//
// (typical stevewo hack, preserved for sentimental value)
//
// If ReplaceIfExists is TRUE, prepend an exclamation point
// to the new filename in order to pass this bit of data
// along to the session manager.
//
if (ReplaceIfExists && NewFileName.Length != 0) {
PWSTR NewBuffer;
NewBuffer = RtlAllocateHeap( RtlProcessHeap(),
MAKE_TAG( TMP_TAG ),
NewFileName.Length + sizeof(WCHAR) );
if (NewBuffer == NULL) {
BaseSetLastNTError( STATUS_NO_MEMORY );
leave;
}
NewBuffer[0] = L'!';
CopyMemory(&NewBuffer[1], NewFileName.Buffer, NewFileName.Length);
NewFileName.Length += sizeof(WCHAR);
NewFileName.MaximumLength += sizeof(WCHAR);
RtlFreeHeap(RtlProcessHeap(), 0, NewFileName.Buffer);
NewFileName.Buffer = NewBuffer;
}
//
// Check to see if the existing file is on a remote share. If it
// is, flag the error rather than let the operation silently fail
// because the delayed operations are done before the net is
// available. Rather than open the file and do a hard core file type,
// we just check for UNC in the file name. This isn't perfect, but it is
// pretty good. Chances are we can not open and manipulate the file. That is
// why the caller is using the delay until reboot option !
//
if ( RtlDetermineDosPathNameType_U(lpExistingFileName) == RtlPathTypeUncAbsolute ) {
Status = STATUS_INVALID_PARAMETER;
}
//
// copy allowed is not permitted on delayed renames
//
else if ( dwFlags & MOVEFILE_COPY_ALLOWED ) {
Status = STATUS_INVALID_PARAMETER;
} else {
Status = BasepMoveFileDelayed( &OldFileName,
&NewFileName,
2,
FALSE );
if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
Status = BasepMoveFileDelayed( &OldFileName,
&NewFileName,
1,
TRUE );
if (Status == STATUS_INSUFFICIENT_RESOURCES) {
Status = BasepMoveFileDelayed( &OldFileName,
&NewFileName,
2,
TRUE );
}
}
}
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
leave;
}
b = TRUE;
leave;
}
//
// We must to the real move now.
//
NewName = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), NewFileName.Length+sizeof(*NewName));
if (NewName == NULL) {
BaseSetLastNTError( STATUS_NO_MEMORY );
leave;
}
RtlMoveMemory( NewName->FileName, NewFileName.Buffer, NewFileName.Length );
NewName->ReplaceIfExists = ReplaceIfExists;
NewName->RootDirectory = NULL;
NewName->FileNameLength = NewFileName.Length;
Status = NtSetInformationFile(
Handle,
&IoStatusBlock,
NewName,
NewFileName.Length+sizeof(*NewName),
(dwFlags & MOVEFILE_CREATE_HARDLINK) ?
FileLinkInformation :
FileRenameInformation
);
RtlFreeHeap(RtlProcessHeap(), 0, NewName);
if (NT_SUCCESS( Status )) {
b = TRUE;
leave;
}
if (Status != STATUS_NOT_SAME_DEVICE || (dwFlags & MOVEFILE_COPY_ALLOWED) == 0) {
BaseSetLastNTError( Status );
leave;
}
NtClose( Handle );
Handle = INVALID_HANDLE_VALUE;
//
// Perform a copy/delete. Handle link tracking.
//
{
HANDLE hSource = INVALID_HANDLE_VALUE;
HANDLE hDest = INVALID_HANDLE_VALUE;
Context.dwFlags = dwFlags;
Context.lpProgressRoutine = lpProgressRoutine;
Context.lpData = lpData;
b = (BOOLEAN)BasepCopyFileExW(
lpExistingFileName,
lpNewFileName,
BasepMoveFileCopyProgress,
&Context,
NULL,
(ReplaceIfExists ? 0 : COPY_FILE_FAIL_IF_EXISTS) | COPY_FILE_OPEN_SOURCE_FOR_WRITE,
0, // PrivCopyFile flags
&hSource,
&hDest
);
if ( b && hSource != INVALID_HANDLE_VALUE && hDest != INVALID_HANDLE_VALUE) {
//
// attempt to do tracking
//
Status = BasepNotifyTrackingService( &hSource,
&Obja,
hDest,
&NewFileName );
if ( !NT_SUCCESS(Status) &&
(dwFlags & MOVEFILE_FAIL_IF_NOT_TRACKABLE)) {
if (hDest != INVALID_HANDLE_VALUE)
CloseHandle( hDest );
hDest = INVALID_HANDLE_VALUE;
DeleteFileW( lpNewFileName );
b = FALSE;
BaseSetLastNTError( Status );
}
}
if (hSource != INVALID_HANDLE_VALUE) {
CloseHandle(hSource);
hSource = INVALID_HANDLE_VALUE;
}
if (hDest != INVALID_HANDLE_VALUE) {
CloseHandle(hDest);
hDest = INVALID_HANDLE_VALUE;
}
//
// the copy worked... Delete the source of the rename
// if it fails, try a set attributes and then a delete
//
if (b && !DeleteFileW( lpExistingFileName ) ) {
//
// If the delete fails, we will return true, but possibly
// leave the source dangling
//
SetFileAttributesW(lpExistingFileName,FILE_ATTRIBUTE_NORMAL);
DeleteFileW( lpExistingFileName );
}
}
} finally {
if (Handle != INVALID_HANDLE_VALUE) {
NtClose( Handle );
}
RtlFreeHeap( RtlProcessHeap(), 0, OldFileName.Buffer );
RtlFreeHeap( RtlProcessHeap(), 0, NewFileName.Buffer );
}
return b;
}
NTSTATUS
BasepMoveFileDelayed(
IN PUNICODE_STRING OldFileName,
IN PUNICODE_STRING NewFileName,
IN ULONG Index,
IN BOOL OkayToCreateNewValue
)
/*++
Routine Description:
Appends the given delayed move file operation to the registry
value that contains the list of move file operations to be
performed on the next boot.
Arguments:
OldFileName - Supplies the old file name
NewFileName - Supplies the new file name
Return Value:
NTSTATUS
--*/
{
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING KeyName;
UNICODE_STRING ValueName;
HANDLE KeyHandle;
PWSTR ValueData, s;
PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
ULONG ValueLength = 1024;
ULONG ReturnedLength;
WCHAR ValueNameBuf[64];
NTSTATUS Status;
RtlInitUnicodeString( &KeyName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager" );
if (Index == 1) {
RtlInitUnicodeString( &ValueName, L"PendingFileRenameOperations" );
} else {
swprintf(ValueNameBuf,L"PendingFileRenameOperations%d",Index);
RtlInitUnicodeString( &ValueName, ValueNameBuf );
}
InitializeObjectAttributes(
&Obja,
&KeyName,
OBJ_OPENIF | OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
Status = NtCreateKey( &KeyHandle,
GENERIC_READ | GENERIC_WRITE,
&Obja,
0,
NULL,
0,
NULL
);
if ( Status == STATUS_ACCESS_DENIED ) {
Status = NtCreateKey( &KeyHandle,
GENERIC_READ | GENERIC_WRITE,
&Obja,
0,
NULL,
REG_OPTION_BACKUP_RESTORE,
NULL
);
}
if (!NT_SUCCESS( Status )) {
return Status;
}
while (TRUE) {
ValueInfo = RtlAllocateHeap(RtlProcessHeap(),
MAKE_TAG(TMP_TAG),
ValueLength + OldFileName->Length + sizeof(WCHAR) +
NewFileName->Length + 2*sizeof(WCHAR));
if (ValueInfo == NULL) {
NtClose(KeyHandle);
return(STATUS_NO_MEMORY);
}
//
// File rename operations are stored in the registry in a
// single MULTI_SZ value. This allows the renames to be
// performed in the same order that they were originally
// requested. Each rename operation consists of a pair of
// NULL-terminated strings.
//
Status = NtQueryValueKey(KeyHandle,
&ValueName,
KeyValuePartialInformation,
ValueInfo,
ValueLength,
&ReturnedLength);
if (Status != STATUS_BUFFER_OVERFLOW) {
break;
}
//
// The existing value is too large for our buffer.
// Retry with a larger buffer.
//
ValueLength = ReturnedLength;
RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo);
}
if ((Status == STATUS_OBJECT_NAME_NOT_FOUND)
&& OkayToCreateNewValue) {
//
// The value does not currently exist. Create the
// value with our data.
//
s = ValueData = (PWSTR)ValueInfo;
} else if (NT_SUCCESS(Status)) {
//
// A value already exists, append our two strings to the
// MULTI_SZ.
//
ValueData = (PWSTR)(&ValueInfo->Data);
s = (PWSTR)((PCHAR)ValueData + ValueInfo->DataLength) - 1;
} else {
NtClose(KeyHandle);
RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo);
return(Status);
}
CopyMemory(s, OldFileName->Buffer, OldFileName->Length);
s += (OldFileName->Length/sizeof(WCHAR));
*s++ = L'\0';
CopyMemory(s, NewFileName->Buffer, NewFileName->Length);
s += (NewFileName->Length/sizeof(WCHAR));
*s++ = L'\0';
*s++ = L'\0';
Status = NtSetValueKey(KeyHandle,
&ValueName,
0,
REG_MULTI_SZ,
ValueData,
(ULONG)((s-ValueData)*sizeof(WCHAR)));
NtClose(KeyHandle);
RtlFreeHeap(RtlProcessHeap(), 0, ValueInfo);
return(Status);
}
NTSTATUS
BasepOpenFileForMove( IN LPCWSTR lpFileName,
OUT PUNICODE_STRING FileName,
OUT PVOID *FileNameFreeBuffer,
OUT PHANDLE Handle,
OUT POBJECT_ATTRIBUTES Obja,
IN ULONG DesiredAccess,
IN ULONG ShareAccess,
IN ULONG OpenOptions )
/*++
Routine Description:
Opens a file such that it may be used in MoveFile or MoveFileIdentity.
Arguments:
lpFileName - the file to open
FileName - lpFileName translated to an NT path
FileNameFreeBuffer - a buffer which needs to be freed when FileName
is no longer in use
Handle - Location in which to put the handle for the opened file.
Obja - Object attributes used to open the file
DesiredAccess - Access flags which must be set, in addition to
FILE_READ_ATTRIBUTES and SYNCHRONIZE which may also be set.
ShareAccess - Sharing flags which must be set, though additional
flags may also be set.
OpenOptions - FILE_OPEN_ flags which must be set, though
FILE_OPEN_REPARSE_POINT, FILE_SYNCHRONOUS_IO_NONALERT, and
FILE_OPEN_FOR_BACKUP_INTENT may also be set.
Return Value:
NTSTATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
BOOL TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
IO_STATUS_BLOCK IoStatusBlock;
try
{
FileName->Length = FileName->MaximumLength = 0;
FileName->Buffer = NULL;
*FileNameFreeBuffer = NULL;
//
// Initialize the object attributes
//
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
FileName,
NULL,
&RelativeName
);
if ( !TranslationStatus ) {
Status = STATUS_OBJECT_PATH_NOT_FOUND;
leave;
}
*FileNameFreeBuffer = FileName->Buffer;
if ( RelativeName.RelativeName.Length ) {
*FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
} else {
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(
Obja,
FileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL
);
//
// Establish whether we are handling a symbolic link or not by:
// (1) obtaining a handle to the local entity, and
// (2) finding whether a symbolic link was found.
//
// Open the file for delete access inhibiting the reparse
// point behavior.
//
OpenOptions |= (FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
Status = NtOpenFile( Handle,
FILE_READ_ATTRIBUTES | SYNCHRONIZE | DesiredAccess,
Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | ShareAccess,
FILE_OPEN_REPARSE_POINT | OpenOptions
);
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 ) {
//
// Retry the open without reparse behaviour. This should be compatible
// with older file systems.
//
Status = NtOpenFile(
Handle,
SYNCHRONIZE | DesiredAccess,
Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | ShareAccess,
OpenOptions
);
}
if ( !NT_SUCCESS( Status ) ) {
leave;
}
} else {
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
//
// The open succeeded. If we do not find a symbolic link or a mount point,
// 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) ) {
leave;
}
}
if ( NT_SUCCESS(Status) &&
(FileTagInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
FileTagInformation.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT ) {
//
// Open without inhibiting the reparse behavior and not needing to
// read the attributes.
//
NtClose( *Handle );
*Handle = INVALID_HANDLE_VALUE;
Status = NtOpenFile(
Handle,
SYNCHRONIZE | DesiredAccess,
Obja,
&IoStatusBlock,
FILE_SHARE_DELETE | FILE_SHARE_READ | ShareAccess,
OpenOptions
);
if ( !NT_SUCCESS( Status ) ) {
leave;
}
}
}
}
finally
{
}
return( Status );
}
BOOL
APIENTRY
PrivMoveFileIdentityW(
LPCWSTR lpOldFileName,
LPCWSTR lpNewFileName,
DWORD dwFlags
)
/*++
Routine Description:
Moves an identity from one file to another. The identity is composed
of the file's create date, and its object ID. The Object ID isn't
necessarily copied straight across; it's handled as if the actual
file were being moved by MoveFileWithProgressW.
Arguments:
lpOldFileName - Supplies the old file name
lpNewFileName - Supplies the new file name
Return Value:
TRUE if successful. Otherwise the error can be found by calling GetLastError().
--*/
{ // MOVE_FILEIDentityW
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS StatusIgnored = STATUS_SUCCESS;
HANDLE SourceFile = INVALID_HANDLE_VALUE;
HANDLE DestFile = INVALID_HANDLE_VALUE;
UNICODE_STRING SourceFileName = { 0, 0, NULL };
PVOID SourceFileNameFreeBuffer = NULL;
UNICODE_STRING DestFileName = { 0, 0, NULL };
PVOID DestFileNameFreeBuffer = NULL;
BOOL TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
OBJECT_ATTRIBUTES SourceObja;
OBJECT_ATTRIBUTES DestObja;
ULONG OpenFlags;
FILE_DISPOSITION_INFORMATION DispositionInformation = { TRUE };
IO_STATUS_BLOCK IoStatus;
FILE_BASIC_INFORMATION SourceBasicInfo;
FILE_BASIC_INFORMATION DestBasicInfo;
DWORD SourceFileAccess;
DWORD DestFileAccess;
try {
// Open the source file. It must be opened for write or the
// FileTrackingInformation call will fail.
SourceFileAccess = FILE_WRITE_DATA | FILE_READ_ATTRIBUTES;
if( dwFlags & PRIVMOVE_FILEID_DELETE_OLD_FILE ) {
SourceFileAccess |= DELETE;
}
while( TRUE ) {
Status = BasepOpenFileForMove( lpOldFileName,
&SourceFileName,
&SourceFileNameFreeBuffer,
&SourceFile,
&SourceObja,
SourceFileAccess,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN_NO_RECALL );
if( NT_SUCCESS(Status) ) {
break;
} else {
// We might be able to try again without requesting write access
if( (SourceFileAccess & FILE_WRITE_DATA) &&
(dwFlags & PRIVMOVE_FILEID_IGNORE_ID_ERRORS) ) {
// Try again without write access
SourceFileAccess &= ~FILE_WRITE_DATA;
if( NT_SUCCESS(StatusIgnored) ) {
StatusIgnored = Status;
}
Status = STATUS_SUCCESS;
} else {
// Nothing we can do.
break;
}
}
}
if( !NT_SUCCESS(Status) ) {
leave;
}
// Open the destination file
DestFileAccess = FILE_WRITE_ATTRIBUTES;
if( SourceFileAccess & FILE_WRITE_DATA )
DestFileAccess |= FILE_WRITE_DATA;
while( TRUE ) {
Status = BasepOpenFileForMove( lpNewFileName,
&DestFileName,
&DestFileNameFreeBuffer,
&DestFile,
&DestObja,
(SourceFileAccess & FILE_WRITE_DATA)
? FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA
: FILE_WRITE_ATTRIBUTES,
FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ,
FILE_OPEN_NO_RECALL );
if( NT_SUCCESS(Status) ) {
break;
} else {
// We might be able to try again without requesting write access
if( (DestFileAccess & FILE_WRITE_DATA) &&
(dwFlags & PRIVMOVE_FILEID_IGNORE_ID_ERRORS) ) {
// Try again
DestFileAccess &= ~FILE_WRITE_DATA;
if( NT_SUCCESS(StatusIgnored) ) {
StatusIgnored = Status;
}
Status = STATUS_SUCCESS;
} else {
// Nothing we can do.
break;
}
}
}
if( !NT_SUCCESS(Status) ) {
leave;
}
// Copy the create date to the dest file
Status = NtQueryInformationFile( SourceFile,
&IoStatus,
&SourceBasicInfo,
sizeof(SourceBasicInfo),
FileBasicInformation );
if( NT_SUCCESS(Status) ) {
RtlZeroMemory( &DestBasicInfo, sizeof(DestBasicInfo) );
DestBasicInfo.CreationTime = SourceBasicInfo.CreationTime;
Status = NtSetInformationFile( DestFile,
&IoStatus,
&DestBasicInfo,
sizeof(DestBasicInfo),
FileBasicInformation );
}
// If we had an error and can't ignore it, abort.
if( !NT_SUCCESS(Status) ) {
if( dwFlags & PRIVMOVE_FILEID_IGNORE_ID_ERRORS ) {
if( NT_SUCCESS(StatusIgnored) ) {
StatusIgnored = Status;
}
Status = STATUS_SUCCESS;
}
else {
leave;
}
}
// Transfer the tracking information to the dest file, but only if we
// were able to get write access to both files.
if( (DestFileAccess & FILE_WRITE_DATA) &&
(SourceFileAccess & FILE_WRITE_DATA) ) {
Status = BasepNotifyTrackingService( &SourceFile,
&SourceObja,
DestFile,
&DestFileName );
if( !NT_SUCCESS(Status) ) {
if( dwFlags & PRIVMOVE_FILEID_IGNORE_ID_ERRORS ) {
if( NT_SUCCESS(StatusIgnored) ) {
StatusIgnored = Status;
}
Status = STATUS_SUCCESS;
}
else {
leave;
}
}
}
}
finally
{
if( SourceFileNameFreeBuffer != NULL )
RtlFreeHeap( RtlProcessHeap(), 0, SourceFileNameFreeBuffer );
if( DestFileNameFreeBuffer != NULL )
RtlFreeHeap( RtlProcessHeap(), 0, DestFileNameFreeBuffer );
}
// If requested, delete the source file. DispositionInformation.DeleteFile
// has already been initialized to TRUE.
if( NT_SUCCESS(Status) && (dwFlags & PRIVMOVE_FILEID_DELETE_OLD_FILE) ) {
Status = NtSetInformationFile(
SourceFile,
&IoStatus,
&DispositionInformation,
sizeof(DispositionInformation),
FileDispositionInformation
);
}
if( DestFile != INVALID_HANDLE_VALUE )
NtClose( DestFile );
if( SourceFile != INVALID_HANDLE_VALUE )
NtClose( SourceFile );
if( !NT_SUCCESS(Status) ) {
BaseSetLastNTError(Status);
}
else if( !NT_SUCCESS(StatusIgnored) ) {
BaseSetLastNTError(StatusIgnored);
}
return( NT_SUCCESS(Status) );
}
DWORD
WINAPI
GetCompressedFileSizeA(
LPCSTR lpFileName,
LPDWORD lpFileSizeHigh
)
{
PUNICODE_STRING Unicode;
Unicode = Basep8BitStringToStaticUnicodeString( lpFileName );
if (Unicode == NULL) {
return (DWORD)-1;
}
return ( GetCompressedFileSizeW((LPCWSTR)Unicode->Buffer,lpFileSizeHigh) );
}
DWORD
WINAPI
GetCompressedFileSizeW(
LPCWSTR lpFileName,
LPDWORD lpFileSizeHigh
)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
HANDLE Handle;
UNICODE_STRING FileName;
IO_STATUS_BLOCK IoStatusBlock;
FILE_COMPRESSION_INFORMATION CompressionInfo;
BOOLEAN TranslationStatus;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
DWORD FileSizeLow;
TranslationStatus = RtlDosPathNameToNtPathName_U(
lpFileName,
&FileName,
NULL,
&RelativeName
);
if ( !TranslationStatus ) {
SetLastError(ERROR_PATH_NOT_FOUND);
return (DWORD)-1;
}
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 file
//
Status = NtOpenFile(
&Handle,
FILE_READ_ATTRIBUTES,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_FOR_BACKUP_INTENT
);
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
if ( !NT_SUCCESS(Status) ) {
BaseSetLastNTError(Status);
return (DWORD)-1;
}
//
// Get the compressed file size.
//
Status = NtQueryInformationFile(
Handle,
&IoStatusBlock,
&CompressionInfo,
sizeof(CompressionInfo),
FileCompressionInformation
);
if ( !NT_SUCCESS(Status) ) {
FileSizeLow = GetFileSize(Handle,lpFileSizeHigh);
NtClose(Handle);
return FileSizeLow;
}
NtClose(Handle);
if ( ARGUMENT_PRESENT(lpFileSizeHigh) ) {
*lpFileSizeHigh = (DWORD)CompressionInfo.CompressedFileSize.HighPart;
}
if (CompressionInfo.CompressedFileSize.LowPart == -1 ) {
SetLastError(0);
}
return CompressionInfo.CompressedFileSize.LowPart;
}