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