mirror of https://github.com/tongzx/nt5src
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
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;
|
|
}
|