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.
2058 lines
63 KiB
2058 lines
63 KiB
/*++
|
|
|
|
Copyright (c) 1998-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
copyfile.c
|
|
|
|
Abstract:
|
|
|
|
This is where the kernel mode copyfile is performed. Really it is more
|
|
of a backup process then a copyfile.
|
|
|
|
The main funcion is SrBackupFile. This is called in response to a
|
|
file modification in order to preservce the old state of that file
|
|
being modified.
|
|
|
|
SrBackupFile was stolen from kernel32.dll:CopyFileExW. It was converted
|
|
to kernel mode and stripped down to handle just the SR backup
|
|
requirements.
|
|
|
|
SrCopyStream was also stolen from kernel32.dll and converted to kernel
|
|
mode. However the main data copy routing SrCopyDataBytes was written new.
|
|
|
|
Author:
|
|
|
|
Paul McDaniel (paulmcd) 03-Apr-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
//
|
|
// Private constants.
|
|
//
|
|
|
|
#define SR_CREATE_FLAGS (FILE_SEQUENTIAL_ONLY \
|
|
| FILE_WRITE_THROUGH \
|
|
| FILE_NO_INTERMEDIATE_BUFFERING \
|
|
| FILE_NON_DIRECTORY_FILE \
|
|
| FILE_OPEN_FOR_BACKUP_INTENT \
|
|
| FILE_SYNCHRONOUS_IO_NONALERT)
|
|
|
|
|
|
//
|
|
// Private types.
|
|
//
|
|
|
|
#define IS_VALID_HANDLE_FILE_CHANGE_CONTEXT(pObject) \
|
|
(((pObject) != NULL) && ((pObject)->Signature == SR_BACKUP_FILE_CONTEXT_TAG))
|
|
|
|
typedef struct _SR_BACKUP_FILE_CONTEXT
|
|
{
|
|
//
|
|
// NonPagedPool
|
|
//
|
|
|
|
//
|
|
// = SR_BACKUP_FILE_CONTEXT_TAG
|
|
//
|
|
|
|
ULONG Signature;
|
|
|
|
WORK_QUEUE_ITEM WorkItem;
|
|
|
|
KEVENT Event;
|
|
|
|
NTSTATUS Status;
|
|
|
|
SR_EVENT_TYPE EventType;
|
|
|
|
PFILE_OBJECT pFileObject;
|
|
|
|
PUNICODE_STRING pFileName;
|
|
|
|
PSR_DEVICE_EXTENSION pExtension;
|
|
|
|
PUNICODE_STRING pDestFileName;
|
|
|
|
BOOLEAN CopyDataStreams;
|
|
|
|
PACCESS_TOKEN pThreadToken;
|
|
|
|
} SR_BACKUP_FILE_CONTEXT, * PSR_BACKUP_FILE_CONTEXT;
|
|
|
|
//
|
|
// Private prototypes.
|
|
//
|
|
|
|
NTSTATUS
|
|
SrMarkFileForDelete (
|
|
HANDLE FileHandle
|
|
);
|
|
|
|
NTSTATUS
|
|
SrCopySecurityInformation (
|
|
IN HANDLE SourceFile,
|
|
IN HANDLE DestFile
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SrCopyStream (
|
|
IN HANDLE SourceFileHandle,
|
|
IN PDEVICE_OBJECT pTargetDeviceObject,
|
|
IN PUNICODE_STRING pDestFileName,
|
|
IN HANDLE DestFileHandle OPTIONAL,
|
|
IN PLARGE_INTEGER pFileSize,
|
|
OUT PHANDLE pDestFileHandle
|
|
);
|
|
|
|
NTSTATUS
|
|
SrCopyDataBytes (
|
|
IN HANDLE SourceFile,
|
|
IN HANDLE DestFile,
|
|
IN PLARGE_INTEGER FileSize,
|
|
IN ULONG SectorSize
|
|
);
|
|
|
|
BOOLEAN
|
|
SrIsFileEncrypted (
|
|
PSR_DEVICE_EXTENSION pExtension,
|
|
PFILE_OBJECT pFileObject
|
|
);
|
|
|
|
//
|
|
// linker commands
|
|
//
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, SrCopyDataBytes )
|
|
#pragma alloc_text( PAGE, SrCopySecurityInformation )
|
|
#pragma alloc_text( PAGE, SrCopyStream )
|
|
#pragma alloc_text( PAGE, SrBackupFile )
|
|
#pragma alloc_text( PAGE, SrMarkFileForDelete )
|
|
#pragma alloc_text( PAGE, SrBackupFileAndLog )
|
|
#pragma alloc_text( PAGE, SrIsFileEncrypted )
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine copies the all data from SourceFile to DestFile. To read
|
|
the data from the SourceFile, the file is memory mapped so that we bypass
|
|
any byte range locks that may be held on the file.
|
|
|
|
Arguments:
|
|
|
|
SourceFile - Handle to the file from which to copy.
|
|
|
|
DestFile - Handle for the file into which to copy
|
|
|
|
Length - the total size of the file (if it is less than the total size,
|
|
more bytes might be copied than Length ).
|
|
|
|
Return Value:
|
|
|
|
status of the copy
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
SrCopyDataBytes(
|
|
IN HANDLE SourceFile,
|
|
IN HANDLE DestFile,
|
|
IN PLARGE_INTEGER pFileSize,
|
|
IN ULONG SectorSize
|
|
)
|
|
{
|
|
#define MM_MAP_ALIGNMENT (64 * 1024 /*VACB_MAPPING_GRANULARITY*/) // The file offset granularity that MM enforces.
|
|
#define COPY_AMOUNT (64 * 1024) // How much we read or write at a time. Must be >= MM_MAP_ALIGNMENT
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
LARGE_INTEGER ByteOffset;
|
|
HANDLE SectionHandle = NULL;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( SourceFile != NULL );
|
|
ASSERT( DestFile != NULL );
|
|
ASSERT( SectorSize > 0 );
|
|
ASSERT( pFileSize != NULL );
|
|
ASSERT( pFileSize->QuadPart > 0 );
|
|
ASSERT( pFileSize->HighPart == 0 );
|
|
|
|
//
|
|
// We need to use the ObjectAttributes so that the section we create
|
|
// is a kernel handle.
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
NULL,
|
|
OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL );
|
|
|
|
Status = ZwCreateSection( &SectionHandle,
|
|
SECTION_MAP_READ | SECTION_QUERY,
|
|
&ObjectAttributes,
|
|
pFileSize,
|
|
PAGE_READONLY,
|
|
SEC_COMMIT,
|
|
SourceFile );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto SrCopyDataBytes_Exit;
|
|
}
|
|
|
|
ByteOffset.QuadPart = 0;
|
|
|
|
while (ByteOffset.QuadPart < pFileSize->QuadPart)
|
|
{
|
|
ULONG ValidBytes, BytesToCopy;
|
|
PCHAR MappedBuffer = NULL;
|
|
LARGE_INTEGER MappedOffset;
|
|
SIZE_T ViewSize;
|
|
PCHAR CopyIntoAddress;
|
|
|
|
//
|
|
// Set MappedOffset to the greatest, lower offset from ByteOffset that
|
|
// is align to the valid alignment allowed by the memory manager.
|
|
//
|
|
|
|
MappedOffset.QuadPart = ByteOffset.QuadPart - (ByteOffset.QuadPart % MM_MAP_ALIGNMENT);
|
|
ASSERT( (MappedOffset.QuadPart <= ByteOffset.QuadPart) &&
|
|
((MappedOffset.QuadPart + MM_MAP_ALIGNMENT) > ByteOffset.QuadPart) );
|
|
|
|
if ((pFileSize->QuadPart - MappedOffset.QuadPart) > COPY_AMOUNT)
|
|
{
|
|
//
|
|
// We can't map enough of the file to do the whole copy
|
|
// here, so only map COPY_AMOUNT on this pass.
|
|
//
|
|
ViewSize = COPY_AMOUNT;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We can map all the way to the end of the file.
|
|
//
|
|
ViewSize = (ULONG)(pFileSize->QuadPart - MappedOffset.QuadPart);
|
|
}
|
|
|
|
//
|
|
// Calculate the amount of the view size that contains valid data
|
|
// based on any adjustments we needed to do to make sure that
|
|
// the MappedOffset was correctly aligned.
|
|
//
|
|
|
|
ASSERT(ViewSize >=
|
|
(ULONG_PTR)(ByteOffset.QuadPart - MappedOffset.QuadPart));
|
|
ValidBytes = (ULONG)(ViewSize - (ULONG)(ByteOffset.QuadPart - MappedOffset.QuadPart));
|
|
|
|
//
|
|
// Now round ValidBytes up to a sector size.
|
|
//
|
|
|
|
BytesToCopy = ((ValidBytes + SectorSize - 1) / SectorSize) * SectorSize;
|
|
|
|
ASSERT(BytesToCopy <= COPY_AMOUNT);
|
|
|
|
//
|
|
// Map in the region from which we're about to copy.
|
|
//
|
|
Status = ZwMapViewOfSection( SectionHandle,
|
|
NtCurrentProcess(),
|
|
&MappedBuffer,
|
|
0, // zero bits
|
|
0, // commit size (ignored for mapped files)
|
|
&MappedOffset,
|
|
&ViewSize,
|
|
ViewUnmap,
|
|
0, // allocation type
|
|
PAGE_READONLY);
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
goto SrCopyDataBytes_Exit;
|
|
}
|
|
|
|
//
|
|
// We should have enough space allocated for the rounded up read
|
|
//
|
|
|
|
ASSERT( ViewSize >= BytesToCopy );
|
|
|
|
CopyIntoAddress = MappedBuffer + (ULONG)(ByteOffset.QuadPart - MappedOffset.QuadPart);
|
|
|
|
//
|
|
// Since this handle was opened synchronously, the IO Manager takes
|
|
// care of waiting until the operation is complete.
|
|
//
|
|
|
|
Status = ZwWriteFile( DestFile,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
MappedBuffer,
|
|
BytesToCopy,
|
|
&ByteOffset,
|
|
NULL );
|
|
|
|
//
|
|
// Whether or not we successfully wrote this block of data, we want
|
|
// to unmap the current view of the section.
|
|
//
|
|
|
|
ZwUnmapViewOfSection( NtCurrentProcess(), MappedBuffer );
|
|
NULLPTR( MappedBuffer );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
goto SrCopyDataBytes_Exit;
|
|
}
|
|
|
|
ASSERT( IoStatusBlock.Information == BytesToCopy );
|
|
ASSERT( BytesToCopy >= ValidBytes );
|
|
|
|
//
|
|
// Add in the number of valid data bytes that we actually copied
|
|
// into the file.
|
|
//
|
|
|
|
ByteOffset.QuadPart += ValidBytes;
|
|
|
|
//
|
|
// Check to see if we copied more bytes than we had of valid data.
|
|
// If we did, we need to truncate the file.
|
|
//
|
|
|
|
if (BytesToCopy > ValidBytes)
|
|
{
|
|
FILE_END_OF_FILE_INFORMATION EndOfFileInformation;
|
|
|
|
//
|
|
// Then truncate the file to this length.
|
|
//
|
|
|
|
EndOfFileInformation.EndOfFile.QuadPart = ByteOffset.QuadPart;
|
|
|
|
Status = ZwSetInformationFile( DestFile,
|
|
&IoStatusBlock,
|
|
&EndOfFileInformation,
|
|
sizeof(EndOfFileInformation),
|
|
FileEndOfFileInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto SrCopyDataBytes_Exit;
|
|
}
|
|
}
|
|
|
|
SrCopyDataBytes_Exit:
|
|
|
|
if (SectionHandle != NULL) {
|
|
ZwClose( SectionHandle );
|
|
NULLPTR( SectionHandle );
|
|
}
|
|
|
|
return Status;
|
|
#undef COPY_AMOUNT
|
|
#undef MM_MAP_ALIGNMENT
|
|
}
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is an internal routine that copies one or more of the DACL,
|
|
SACL, owner, and group from the source to the dest file.
|
|
|
|
Arguments:
|
|
|
|
SourceFile - Provides a handle to the source file.
|
|
|
|
DestFile - Provides a handle to the destination file.
|
|
|
|
DestFileAccess - The access flags that were used to open DestFile.
|
|
|
|
SecurityInformation - Specifies what security should be copied (bit
|
|
flag of the *_SECURITY_INFORMATION defines).
|
|
|
|
Context - All the information necessary to call the CopyFile callback routine.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The operation was successful.
|
|
|
|
FALSE- The operation failed. Extended error status is available
|
|
using GetLastError.
|
|
|
|
--*/
|
|
|
|
NTSTATUS
|
|
SrCopySecurityInformation(
|
|
IN HANDLE SourceFile,
|
|
IN HANDLE DestFile
|
|
)
|
|
{
|
|
PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG AdminsSidLength = RtlLengthRequiredSid(2);
|
|
ULONG Length = 256;
|
|
SECURITY_INFORMATION SecurityInformation;
|
|
PISECURITY_DESCRIPTOR_RELATIVE pRelative;
|
|
PSID pAdminsSid = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
try {
|
|
|
|
//
|
|
// ask for the dacl
|
|
//
|
|
|
|
SecurityInformation = DACL_SECURITY_INFORMATION;
|
|
|
|
//
|
|
// CODEWORK: paulmcd: 8/2000: the only reason we copy the dacl
|
|
// is to maintain security. we don't want to allow anyone to access
|
|
// this file that wasn't allowed to. we could make this perf better
|
|
// perhaps by just setting a system only dacl.
|
|
//
|
|
|
|
|
|
// Read in the security information from the source file
|
|
// (looping until we get a big enough buffer).
|
|
|
|
while (TRUE )
|
|
{
|
|
|
|
// Alloc a buffer to hold the security info.
|
|
|
|
pSecurityDescriptor = SR_ALLOCATE_ARRAY( PagedPool,
|
|
UCHAR,
|
|
Length,
|
|
SR_SECURITY_DATA_TAG );
|
|
|
|
if (NULL == pSecurityDescriptor)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
leave;
|
|
}
|
|
|
|
// Query the security info
|
|
|
|
Status = ZwQuerySecurityObject( SourceFile,
|
|
SecurityInformation,
|
|
pSecurityDescriptor,
|
|
Length - LongAlignSize(AdminsSidLength),// Leave room for our OWNER SID
|
|
&Length );
|
|
|
|
// Not enough buffer?
|
|
|
|
if (STATUS_BUFFER_TOO_SMALL == Status ||
|
|
STATUS_BUFFER_OVERFLOW == Status)
|
|
{
|
|
|
|
// Get a bigger buffer and try again.
|
|
|
|
SR_FREE_POOL( pSecurityDescriptor,
|
|
SR_SECURITY_DATA_TAG );
|
|
|
|
pSecurityDescriptor = NULL;
|
|
Length += LongAlignSize(AdminsSidLength);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
} // while( TRUE )
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// put the admins sid as the owner
|
|
//
|
|
|
|
pRelative = pSecurityDescriptor;
|
|
|
|
if ((pRelative->Revision == SECURITY_DESCRIPTOR_REVISION) &&
|
|
(pRelative->Control & SE_SELF_RELATIVE))
|
|
{
|
|
PUCHAR pBase = (PUCHAR)pRelative;
|
|
PUCHAR pNextFree = LongAlignPtr(pBase + sizeof(SECURITY_DESCRIPTOR_RELATIVE));
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
|
|
ASSERT(pRelative->Owner == 0);
|
|
ASSERT(pRelative->Group == 0);
|
|
ASSERT(pRelative->Sacl == 0);
|
|
|
|
if ((pRelative->Control & SE_DACL_PRESENT) &&
|
|
pRelative->Dacl > 0)
|
|
{
|
|
ASSERT(pRelative->Dacl == DIFF(pNextFree - pBase));
|
|
|
|
//
|
|
// slide the dacl down, we have room for it from allocating above
|
|
//
|
|
|
|
RtlMoveMemory(RtlOffsetToPointer(pBase, pRelative->Dacl + LongAlignSize(AdminsSidLength)),
|
|
RtlOffsetToPointer(pBase, pRelative->Dacl),
|
|
((PACL)(RtlOffsetToPointer(pBase, pRelative->Dacl)))->AclSize );
|
|
|
|
//
|
|
// and update the offset
|
|
//
|
|
|
|
pRelative->Dacl += LongAlignSize(AdminsSidLength);
|
|
}
|
|
|
|
//
|
|
// construct the local admin sid
|
|
//
|
|
|
|
pAdminsSid = SR_ALLOCATE_POOL( PagedPool,
|
|
AdminsSidLength,
|
|
SR_SECURITY_DATA_TAG );
|
|
|
|
if (pAdminsSid == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
leave;
|
|
}
|
|
|
|
Status = RtlInitializeSid(pAdminsSid, &NtAuthority, 2);
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
*RtlSubAuthoritySid(pAdminsSid, 0) = SECURITY_BUILTIN_DOMAIN_RID;
|
|
*RtlSubAuthoritySid(pAdminsSid, 1) = DOMAIN_ALIAS_RID_ADMINS;
|
|
|
|
//
|
|
// now put it in as the owner field, right after the header
|
|
//
|
|
|
|
RtlZeroMemory( pNextFree,
|
|
LongAlignSize(AdminsSidLength) );
|
|
|
|
RtlCopyMemory( pNextFree,
|
|
pAdminsSid,
|
|
AdminsSidLength );
|
|
|
|
pRelative->Owner = DIFF(pNextFree - pBase);
|
|
|
|
SecurityInformation |= OWNER_SECURITY_INFORMATION;
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pRelative->Revision == SECURITY_DESCRIPTOR_REVISION);
|
|
ASSERT(pRelative->Control & SE_SELF_RELATIVE);
|
|
}
|
|
|
|
|
|
//
|
|
// Set the security on the dest file.
|
|
//
|
|
|
|
Status = SrSetSecurityObjectAsSystem( DestFile,
|
|
SecurityInformation,
|
|
pSecurityDescriptor );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
|
|
} finally {
|
|
|
|
Status = FinallyUnwind(SrCopySecurityInformation, Status);
|
|
|
|
if (pSecurityDescriptor != NULL)
|
|
{
|
|
SR_FREE_POOL( pSecurityDescriptor, SR_SECURITY_DATA_TAG );
|
|
pSecurityDescriptor = NULL;
|
|
}
|
|
|
|
if (pAdminsSid != NULL)
|
|
{
|
|
SR_FREE_POOL( pAdminsSid, SR_SECURITY_DATA_TAG );
|
|
pAdminsSid = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
RETURN(Status);
|
|
|
|
} // SrCopySecurityInformation
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is an internal routine that copies an entire file (default data stream
|
|
only), or a single stream of a file. If the hTargetFile parameter is
|
|
present, then only a single stream of the output file is copied. Otherwise,
|
|
the entire file is copied.
|
|
|
|
Arguments:
|
|
|
|
SourceFileHandle - Provides a handle to the source file.
|
|
|
|
pNewFileName - Provides a name for the target file/stream. this is the
|
|
NT file name, not a win32 file name if a full name is passed,
|
|
otherwise it's just the stream name.
|
|
|
|
DestFileHandle - Optionally provides a handle to the target file. If the
|
|
stream being copied is an alternate data stream, then this handle must
|
|
be provided. NULL means it's not provided.
|
|
|
|
pFileSize - Provides the size of the input stream.
|
|
|
|
pDestFileHandle - Provides a variable to store the handle to the target file.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS code
|
|
|
|
--*/
|
|
|
|
NTSTATUS
|
|
SrCopyStream(
|
|
IN HANDLE SourceFileHandle,
|
|
IN PDEVICE_OBJECT pTargetDeviceObject,
|
|
IN PUNICODE_STRING pDestFileName,
|
|
IN HANDLE DestFileHandle OPTIONAL,
|
|
IN PLARGE_INTEGER pFileSize,
|
|
OUT PHANDLE pDestFileHandle
|
|
)
|
|
{
|
|
HANDLE DestFile = NULL;
|
|
NTSTATUS Status;
|
|
FILE_BASIC_INFORMATION FileBasicInformationData;
|
|
FILE_END_OF_FILE_INFORMATION EndOfFileInformation;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
ULONG DesiredAccess;
|
|
ULONG DestFileAccess;
|
|
ULONG CreateDisposition;
|
|
ULONG SourceFileAttributes;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
|
|
PFILE_FULL_EA_INFORMATION EaBuffer = NULL;
|
|
ULONG EaSize = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( SourceFileHandle != NULL );
|
|
ASSERT( pTargetDeviceObject != NULL );
|
|
ASSERT( pDestFileName != NULL );
|
|
ASSERT( pFileSize != NULL );
|
|
|
|
//
|
|
// Get times and attributes for the file if the entire file is being
|
|
// copied
|
|
//
|
|
|
|
Status = ZwQueryInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
(PVOID) &FileBasicInformationData,
|
|
sizeof(FileBasicInformationData),
|
|
FileBasicInformation );
|
|
|
|
SourceFileAttributes = NT_SUCCESS(Status) ?
|
|
FileBasicInformationData.FileAttributes :
|
|
0;
|
|
|
|
if (DestFileHandle == NULL)
|
|
{
|
|
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// A zero in the file's attributes informs latter DeleteFile that
|
|
// this code does not know what the actual file attributes are so
|
|
// that this code does not actually have to retrieve them for each
|
|
// stream, nor does it have to remember them across streams. The
|
|
// error path will simply get them if needed.
|
|
//
|
|
|
|
FileBasicInformationData.FileAttributes = 0;
|
|
}
|
|
|
|
//
|
|
// Create the destination file or alternate data stream
|
|
//
|
|
|
|
if (DestFileHandle == NULL)
|
|
{
|
|
ULONG CreateOptions = 0;
|
|
PFILE_FULL_EA_INFORMATION EaBufferToUse = NULL;
|
|
ULONG SourceFileFsAttributes = 0;
|
|
ULONG EaSizeToUse = 0;
|
|
|
|
ULONG DestFileAttributes = 0;
|
|
|
|
FILE_BASIC_INFORMATION DestBasicInformation;
|
|
|
|
// We're being called to copy the unnamed stream of the file, and
|
|
// we need to create the file itself.
|
|
|
|
//
|
|
// Determine the create options
|
|
//
|
|
|
|
CreateOptions = FILE_SYNCHRONOUS_IO_NONALERT
|
|
| FILE_WRITE_THROUGH
|
|
| FILE_NO_INTERMEDIATE_BUFFERING
|
|
| FILE_OPEN_FOR_BACKUP_INTENT ;
|
|
|
|
if (SourceFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
CreateOptions |= FILE_DIRECTORY_FILE;
|
|
else
|
|
CreateOptions |= FILE_NON_DIRECTORY_FILE | FILE_SEQUENTIAL_ONLY;
|
|
|
|
//
|
|
// Determine the create disposition
|
|
//
|
|
// the destination file will never exist (in our case)
|
|
//
|
|
|
|
CreateDisposition = FILE_CREATE;
|
|
|
|
//
|
|
// Determine what access is necessary based on what is being copied
|
|
//
|
|
|
|
DesiredAccess = SYNCHRONIZE
|
|
| FILE_READ_ATTRIBUTES
|
|
| GENERIC_WRITE
|
|
| DELETE;
|
|
|
|
if (SourceFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
// We may or may not be able to get FILE_WRITE_DATA access,
|
|
// necessary for setting compression.
|
|
//
|
|
DesiredAccess &= ~GENERIC_WRITE;
|
|
DesiredAccess |= FILE_WRITE_DATA
|
|
| FILE_WRITE_ATTRIBUTES
|
|
| FILE_WRITE_EA
|
|
| FILE_LIST_DIRECTORY;
|
|
}
|
|
|
|
//
|
|
// We need read access for compression, write_dac for the DACL
|
|
//
|
|
|
|
DesiredAccess |= GENERIC_READ | WRITE_DAC;
|
|
DesiredAccess |= WRITE_OWNER;
|
|
|
|
//
|
|
// we can get this as we always have SeSecurityPrivilege (kernelmode)
|
|
//
|
|
|
|
DesiredAccess |= ACCESS_SYSTEM_SECURITY;
|
|
|
|
//
|
|
// get the object attributes ready
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
pDestFileName,
|
|
OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL );
|
|
|
|
SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
|
|
SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation;
|
|
SecurityQualityOfService.EffectiveOnly = TRUE;
|
|
SecurityQualityOfService.Length = sizeof( SECURITY_QUALITY_OF_SERVICE );
|
|
|
|
ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService;
|
|
|
|
//
|
|
// Get the EAs
|
|
//
|
|
|
|
EaBuffer = NULL;
|
|
EaSize = 0;
|
|
|
|
//
|
|
// paulmcd: 5/25/2000 remove ea support until we get it into ntifs.h
|
|
// (the public header)
|
|
//
|
|
|
|
#ifdef EA_SUPPORT
|
|
|
|
Status = ZwQueryInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
&EaInfo,
|
|
sizeof(EaInfo),
|
|
FileEaInformation );
|
|
|
|
if (NT_SUCCESS(Status) && EaInfo.EaSize > 0)
|
|
{
|
|
|
|
EaSize = EaInfo.EaSize;
|
|
|
|
do
|
|
{
|
|
|
|
EaSize *= 2;
|
|
EaBuffer = (PFILE_FULL_EA_INFORMATION)
|
|
SR_ALLOCATE_ARRAY( PagedPool,
|
|
UCHAR,
|
|
EaSize,
|
|
SR_EA_DATA_TAG );
|
|
if (EaBuffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = ZwQueryEaFile( SourceFileHandle,
|
|
&IoStatus,
|
|
EaBuffer,
|
|
EaSize,
|
|
FALSE,
|
|
(PVOID)NULL,
|
|
0,
|
|
(PULONG)NULL,
|
|
TRUE );
|
|
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
SR_FREE_POOL(EaBuffer, SR_EA_DATA_TAG);
|
|
EaBuffer = NULL;
|
|
IoStatus.Information = 0;
|
|
}
|
|
|
|
} while ( Status == STATUS_BUFFER_OVERFLOW ||
|
|
Status == STATUS_BUFFER_TOO_SMALL );
|
|
|
|
|
|
EaSize = (ULONG)IoStatus.Information;
|
|
|
|
} // if ( NT_SUCCESS(Status) && EaInfo.EaSize )
|
|
|
|
#endif // EA_SUPPORT
|
|
|
|
//
|
|
// Open the destination file.
|
|
//
|
|
|
|
DestFileAccess = DesiredAccess;
|
|
EaBufferToUse = EaBuffer;
|
|
EaSizeToUse = EaSize;
|
|
|
|
//
|
|
// Turn off FILE_ATTRIBUTE_OFFLINE for destination
|
|
//
|
|
|
|
SourceFileAttributes &= ~FILE_ATTRIBUTE_OFFLINE;
|
|
|
|
while (DestFile == NULL)
|
|
{
|
|
//
|
|
// Attempt to create the destination
|
|
//
|
|
|
|
Status = SrIoCreateFile( &DestFile,
|
|
DestFileAccess,
|
|
&ObjectAttributes,
|
|
&IoStatus,
|
|
NULL,
|
|
SourceFileAttributes
|
|
& FILE_ATTRIBUTE_VALID_FLAGS,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
CreateDisposition,
|
|
CreateOptions,
|
|
EaBufferToUse,
|
|
EaSizeToUse,
|
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
|
pTargetDeviceObject );
|
|
|
|
// If this was successful, then break out of this while loop.
|
|
// The remaining code in this loop attempt to recover from the problem,
|
|
// then it loops back to the top and attempt the NtCreateFile again.
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
break; // while( TRUE )
|
|
}
|
|
|
|
//
|
|
// If the destination has not been successfully created/opened,
|
|
// see if it's because EAs aren't supported
|
|
//
|
|
|
|
if( EaBufferToUse != NULL &&
|
|
Status == STATUS_EAS_NOT_SUPPORTED )
|
|
{
|
|
|
|
// Attempt the create again, but don't use the EAs
|
|
|
|
EaBufferToUse = NULL;
|
|
EaSizeToUse = 0;
|
|
DestFileAccess = DesiredAccess;
|
|
continue;
|
|
|
|
} // if( EaBufferToUse != NULL ...
|
|
|
|
//
|
|
// completely failed! no more tricks.
|
|
//
|
|
|
|
DestFile = NULL;
|
|
goto end;
|
|
|
|
} // while (DestFile == NULL)
|
|
|
|
//
|
|
// If we reach this point, we've successfully opened the dest file.
|
|
//
|
|
|
|
//
|
|
// Get the File & FileSys attributes for the target volume, plus
|
|
// the FileSys attributes for the source volume.
|
|
//
|
|
|
|
SourceFileFsAttributes = 0;
|
|
DestFileAttributes = 0;
|
|
|
|
Status = ZwQueryInformationFile( DestFile,
|
|
&IoStatus,
|
|
&DestBasicInformation,
|
|
sizeof(DestBasicInformation),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
DestFileAttributes = DestBasicInformation.FileAttributes;
|
|
|
|
|
|
//
|
|
// If the source file is encrypted, check that the target was successfully
|
|
// set for encryption (e.g. it won't be for FAT).
|
|
//
|
|
|
|
if( (SourceFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
|
|
!(DestFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) )
|
|
{
|
|
//
|
|
// CODEWORK: paulmcd.. need to figure out how to appropriately
|
|
// handle the $EFS stream.
|
|
//
|
|
|
|
ASSERT(FALSE);
|
|
|
|
SrTrace(NOTIFY, ("sr!SrCopyStream(%wZ):failed to copy encryption\n",
|
|
pDestFileName ));
|
|
|
|
|
|
} // if( SourceFileAttributes & FILE_ATTRIBUTE_ENCRYPTED ...
|
|
|
|
}
|
|
else // if (DestFileHandle == NULL)
|
|
{
|
|
|
|
// We're copying a named stream.
|
|
|
|
//
|
|
// Create the output stream relative to the file specified by the
|
|
// DestFileHandle file handle.
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
pDestFileName,
|
|
OBJ_KERNEL_HANDLE,
|
|
DestFileHandle,
|
|
(PSECURITY_DESCRIPTOR)NULL );
|
|
|
|
DesiredAccess = GENERIC_WRITE | SYNCHRONIZE;
|
|
|
|
Status = SrIoCreateFile( &DestFile,
|
|
DesiredAccess,
|
|
&ObjectAttributes,
|
|
&IoStatus,
|
|
pFileSize,
|
|
SourceFileAttributes,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
FILE_OPEN_IF,
|
|
SR_CREATE_FLAGS,
|
|
NULL, // EaBuffer
|
|
0, // EaLength
|
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
|
pTargetDeviceObject );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
|
|
if (Status != STATUS_ACCESS_DENIED)
|
|
goto end;
|
|
|
|
//
|
|
// Determine whether or not this failed because the file
|
|
// is a readonly file. If so, change it to read/write,
|
|
// re-attempt the open, and set it back to readonly again.
|
|
//
|
|
|
|
Status = ZwQueryInformationFile( DestFileHandle,
|
|
&IoStatus,
|
|
(PVOID) &FileBasicInformationData,
|
|
sizeof(FileBasicInformationData),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
if (FileBasicInformationData.FileAttributes
|
|
& FILE_ATTRIBUTE_READONLY)
|
|
{
|
|
ULONG attributes = FileBasicInformationData.FileAttributes;
|
|
|
|
RtlZeroMemory( &FileBasicInformationData,
|
|
sizeof(FileBasicInformationData) );
|
|
|
|
FileBasicInformationData.FileAttributes
|
|
= FILE_ATTRIBUTE_NORMAL;
|
|
|
|
(VOID) ZwSetInformationFile( DestFileHandle,
|
|
&IoStatus,
|
|
&FileBasicInformationData,
|
|
sizeof(FileBasicInformationData),
|
|
FileBasicInformation );
|
|
|
|
Status = SrIoCreateFile( &DestFile,
|
|
DesiredAccess,
|
|
&ObjectAttributes,
|
|
&IoStatus,
|
|
pFileSize,
|
|
SourceFileAttributes,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
FILE_OPEN_IF,
|
|
SR_CREATE_FLAGS,
|
|
NULL, // EaBuffer
|
|
0, // EaLength
|
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
|
pTargetDeviceObject );
|
|
|
|
FileBasicInformationData.FileAttributes = attributes;
|
|
|
|
(VOID) ZwSetInformationFile( DestFileHandle,
|
|
&IoStatus,
|
|
&FileBasicInformationData,
|
|
sizeof(FileBasicInformationData),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// it wasn't read only... just fail, nothing else to try
|
|
//
|
|
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
} // else [if (DestFileHandle == NULL)]
|
|
|
|
//
|
|
// is there any stream data to copy?
|
|
//
|
|
|
|
if (pFileSize->QuadPart > 0)
|
|
{
|
|
//
|
|
// Preallocate the size of this file/stream so that extends do not
|
|
// occur.
|
|
//
|
|
|
|
EndOfFileInformation.EndOfFile = *pFileSize;
|
|
Status = ZwSetInformationFile( DestFile,
|
|
&IoStatus,
|
|
&EndOfFileInformation,
|
|
sizeof(EndOfFileInformation),
|
|
FileEndOfFileInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
//
|
|
// now copy the stream bits
|
|
//
|
|
|
|
Status = SrCopyDataBytes( SourceFileHandle,
|
|
DestFile,
|
|
pFileSize,
|
|
pTargetDeviceObject->SectorSize );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
if (DestFile != NULL)
|
|
{
|
|
SrMarkFileForDelete(DestFile);
|
|
|
|
ZwClose(DestFile);
|
|
DestFile = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// set the callers pointer
|
|
// (even if it's not valid, this clears the callers buffer)
|
|
//
|
|
|
|
*pDestFileHandle = DestFile;
|
|
|
|
if ( EaBuffer )
|
|
{
|
|
SR_FREE_POOL(EaBuffer, SR_EA_DATA_TAG);
|
|
}
|
|
|
|
RETURN(Status);
|
|
|
|
} // SrCopyStream
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
this routine will copy the source file to the dest file. the dest
|
|
file is opened create so it must not already exist. if the
|
|
CopyDataStreams is set all alternate streams are copied including the
|
|
default data stream. the DACL is copied to the dest file but the dest
|
|
file has the owner set to admins regardless of the source file object.
|
|
|
|
if it fails it cleans up and deletes the dest file.
|
|
|
|
it checks to make sure the volume has at least 50mb free prior to
|
|
the copy.
|
|
|
|
|
|
BUGBUG: paulmcd:8/2000: this routine does not copy the $EFS meta-data
|
|
|
|
Arguments:
|
|
|
|
pExtension - SR's device extension for the volume on which this file
|
|
resides.
|
|
pOriginalFileObject - the file object to which this operation is occuring.
|
|
This file object could represent a name data stream on the file.
|
|
pSourceFileName - The name of the file to backup (excluding any stream
|
|
component).
|
|
pDestFileName - The name of the destination file to which this file will
|
|
be copied.
|
|
CopyDataStreams - If TRUE, we should copy all the data streams of this
|
|
file.
|
|
pBytesWritten - Is set to the number of bytes written in the restore
|
|
location as a result of backing up this file.
|
|
pShortFileName - Is set to the short file name of the file we backed up
|
|
if we were able to successfully back up the file and this file has
|
|
a short name.
|
|
|
|
Return Value:
|
|
|
|
ULONG - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
SrBackupFile(
|
|
IN PSR_DEVICE_EXTENSION pExtension,
|
|
IN PFILE_OBJECT pOriginalFileObject,
|
|
IN PUNICODE_STRING pSourceFileName,
|
|
IN PUNICODE_STRING pDestFileName,
|
|
IN BOOLEAN CopyDataStreams,
|
|
OUT PULONGLONG pBytesWritten OPTIONAL,
|
|
OUT PUNICODE_STRING pShortFileName OPTIONAL
|
|
)
|
|
{
|
|
HANDLE SourceFileHandle = NULL;
|
|
HANDLE DestFile = NULL;
|
|
NTSTATUS Status;
|
|
HANDLE OutputStream;
|
|
HANDLE StreamHandle;
|
|
ULONG StreamInfoSize;
|
|
OBJECT_ATTRIBUTES objAttr;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
LARGE_INTEGER BytesToCopy;
|
|
UNICODE_STRING StreamName;
|
|
PFILE_OBJECT pSourceFileObject = NULL;
|
|
|
|
FILE_STANDARD_INFORMATION FileInformation;
|
|
FILE_BASIC_INFORMATION BasicInformation;
|
|
PFILE_STREAM_INFORMATION StreamInfo;
|
|
PFILE_STREAM_INFORMATION StreamInfoBase = NULL;
|
|
|
|
struct {
|
|
FILE_FS_ATTRIBUTE_INFORMATION Info;
|
|
WCHAR Buffer[ 50 ];
|
|
} FileFsAttrInfoBuffer;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pOriginalFileObject != NULL);
|
|
ASSERT(pSourceFileName != NULL);
|
|
|
|
try
|
|
{
|
|
if (pBytesWritten != NULL)
|
|
{
|
|
*pBytesWritten = 0;
|
|
}
|
|
|
|
//
|
|
// First open a new handle to the source file so that we don't
|
|
// interfere with the user's read offset.
|
|
//
|
|
|
|
InitializeObjectAttributes( &objAttr,
|
|
pSourceFileName,
|
|
OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL );
|
|
|
|
Status = SrIoCreateFile( &SourceFileHandle,
|
|
GENERIC_READ,
|
|
&objAttr,
|
|
&IoStatus,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
FILE_NON_DIRECTORY_FILE,
|
|
NULL,
|
|
0,
|
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
|
pExtension->pTargetDevice );
|
|
|
|
if (Status == STATUS_ACCESS_DENIED)
|
|
{
|
|
//
|
|
// This may be a file that is in the process of getting decrypted.
|
|
// Check to see if this file is currently encrypted. If so, we
|
|
// we assume that we got STATUS_ACCESS_DENIED because the file is
|
|
// in its transition state and keep going.
|
|
//
|
|
|
|
if (SrIsFileEncrypted( pExtension, pOriginalFileObject ))
|
|
{
|
|
Status = SR_STATUS_IGNORE_FILE;
|
|
leave;
|
|
}
|
|
else
|
|
{
|
|
CHECK_STATUS( Status );
|
|
leave;
|
|
}
|
|
}
|
|
else if (Status == STATUS_FILE_IS_A_DIRECTORY)
|
|
{
|
|
//
|
|
// We probably got to here because someone modified or deleted
|
|
// a named datastream on a directory. We don't support that,
|
|
// so we will just propagate this error up to the caller. They
|
|
// will know whether or not this is a reasonable error.
|
|
//
|
|
|
|
leave;
|
|
}
|
|
else if (!NT_SUCCESS( Status )) {
|
|
|
|
leave;
|
|
}
|
|
|
|
#if DBG
|
|
if (CopyDataStreams)
|
|
{
|
|
SrTrace(NOTIFY, ("sr!SrBackupFile: copying\n\t%wZ\n\tto %ws\n",
|
|
pSourceFileName,
|
|
SrpFindFilePartW(pDestFileName->Buffer) ));
|
|
|
|
}
|
|
else
|
|
{
|
|
SrTrace(NOTIFY, ("sr!SrBackupFile: copying [no data]\n\t%ws\n\tto %wZ\n",
|
|
SrpFindFilePartW(pSourceFileName->Buffer),
|
|
pDestFileName ));
|
|
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Now we have our own handle to this file and all IOs on this handle
|
|
// we start at our pTargetDevice.
|
|
//
|
|
|
|
//
|
|
// check for free space, we don't want to bank on the fact that
|
|
// the service is up and running to protect us from filling the disk
|
|
//
|
|
|
|
Status = SrCheckFreeDiskSpace( SourceFileHandle, pSourceFileName );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// does the caller want us to copy any actual $DATA?
|
|
//
|
|
|
|
if (CopyDataStreams)
|
|
{
|
|
//
|
|
// Size the source file to determine how much data is to be copied
|
|
//
|
|
|
|
Status = ZwQueryInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
(PVOID) &FileInformation,
|
|
sizeof(FileInformation),
|
|
FileStandardInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// copy the entire file
|
|
//
|
|
|
|
BytesToCopy = FileInformation.EndOfFile;
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// don't copy anything
|
|
//
|
|
|
|
BytesToCopy.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// Get the timestamp info as well.
|
|
//
|
|
|
|
Status = ZwQueryInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
(PVOID) &BasicInformation,
|
|
sizeof(BasicInformation),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// we don't support sparse or reparse points. If this
|
|
// file is either sparse or contains a resparse point, just
|
|
// skip it.
|
|
//
|
|
|
|
if (FlagOn( BasicInformation.FileAttributes,
|
|
FILE_ATTRIBUTE_SPARSE_FILE | FILE_ATTRIBUTE_REPARSE_POINT )) {
|
|
|
|
#if DBG
|
|
if (FlagOn( BasicInformation.FileAttributes,
|
|
FILE_ATTRIBUTE_SPARSE_FILE )) {
|
|
|
|
SrTrace( NOTIFY, ("sr!SrBackupFile: Ignoring sparse file [%wZ]\n",
|
|
pSourceFileName) );
|
|
}
|
|
|
|
if (FlagOn( BasicInformation.FileAttributes,
|
|
FILE_ATTRIBUTE_REPARSE_POINT )) {
|
|
|
|
SrTrace( NOTIFY, ("sr!SrBackupFile: Ignoring file with reparse point [%wZ]\n",
|
|
pSourceFileName) );
|
|
}
|
|
#endif
|
|
Status = SR_STATUS_IGNORE_FILE;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// are we supposed to copy the data? if so, check for the existence
|
|
// of alternate streams
|
|
//
|
|
|
|
if (CopyDataStreams)
|
|
{
|
|
//
|
|
// Obtain the full set of streams we have to copy. Since the Io
|
|
// subsystem does not provide us a way to find out how much space
|
|
// this information will take, we must iterate the call, doubling
|
|
// the buffer size upon each failure.
|
|
//
|
|
// If the underlying file system does not support stream enumeration,
|
|
// we end up with a NULL buffer. This is acceptable since we have
|
|
// at least a default data stream,
|
|
//
|
|
|
|
StreamInfoSize = 4096;
|
|
|
|
do
|
|
{
|
|
StreamInfoBase = (PFILE_STREAM_INFORMATION)
|
|
SR_ALLOCATE_ARRAY( PagedPool,
|
|
UCHAR,
|
|
StreamInfoSize,
|
|
SR_STREAM_DATA_TAG );
|
|
|
|
if (StreamInfoBase == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
leave;
|
|
}
|
|
|
|
Status = ZwQueryInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
(PVOID) StreamInfoBase,
|
|
StreamInfoSize,
|
|
FileStreamInformation );
|
|
|
|
if (Status == STATUS_INVALID_PARAMETER ||
|
|
!NT_SUCCESS( Status ))
|
|
{
|
|
//
|
|
// We failed the call. Free up the previous buffer and
|
|
// set up for another pass with a buffer twice as large
|
|
//
|
|
|
|
SR_FREE_POOL(StreamInfoBase, SR_STREAM_DATA_TAG);
|
|
StreamInfoBase = NULL;
|
|
StreamInfoSize *= 2;
|
|
}
|
|
else if( IoStatus.Information == 0 ) {
|
|
//
|
|
// There are no streams (SourceFileHandle must be a
|
|
// directory).
|
|
//
|
|
SR_FREE_POOL(StreamInfoBase, SR_STREAM_DATA_TAG);
|
|
StreamInfoBase = NULL;
|
|
}
|
|
|
|
} while ( Status == STATUS_BUFFER_OVERFLOW ||
|
|
Status == STATUS_BUFFER_TOO_SMALL );
|
|
|
|
//
|
|
// ignore status, failing to read the streams probably means there
|
|
// are no streams
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} // if (CopyDataStreams)
|
|
|
|
//
|
|
// Set the Basic Info to change only the filetimes
|
|
//
|
|
|
|
BasicInformation.FileAttributes = 0;
|
|
|
|
//
|
|
// Copy the default data stream, EAs, etc. to the output file
|
|
//
|
|
|
|
Status = SrCopyStream( SourceFileHandle,
|
|
pExtension->pTargetDevice,
|
|
pDestFileName,
|
|
NULL,
|
|
&BytesToCopy,
|
|
&DestFile );
|
|
|
|
//
|
|
// the default stream copy failed!
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// remember how much we just copied
|
|
//
|
|
|
|
if (pBytesWritten != NULL)
|
|
{
|
|
*pBytesWritten += BytesToCopy.QuadPart;
|
|
}
|
|
|
|
//
|
|
// If applicable, copy one or more of the the DACL, SACL, owner, and
|
|
// group.
|
|
//
|
|
|
|
|
|
Status = ZwQueryVolumeInformationFile( SourceFileHandle,
|
|
&IoStatus,
|
|
&FileFsAttrInfoBuffer.Info,
|
|
sizeof(FileFsAttrInfoBuffer),
|
|
FileFsAttributeInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
if (FileFsAttrInfoBuffer.Info.FileSystemAttributes & FILE_PERSISTENT_ACLS)
|
|
{
|
|
//
|
|
// copy the DACL to enforce the same security protection.
|
|
// do NOT copy the SACL to prevent useless auditing.
|
|
// SrCopySecurityInformation will make the OWNER admins to
|
|
// handle disk quota accounting.
|
|
//
|
|
|
|
Status = SrCopySecurityInformation(SourceFileHandle, DestFile);
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to determine whether or not this file has any alternate
|
|
// data streams associated with it. If it does, attempt to copy each
|
|
// to the output file. Note that the stream information may have
|
|
// already been obtained if a progress routine was requested.
|
|
//
|
|
|
|
if (StreamInfoBase != NULL)
|
|
{
|
|
StreamInfo = StreamInfoBase;
|
|
|
|
while (TRUE)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Skip the default data stream since we've already copied
|
|
// it. Alas, this code is NTFS specific and documented
|
|
// nowhere in the Io spec.
|
|
//
|
|
|
|
if (StreamInfo->StreamNameLength <= sizeof(WCHAR) ||
|
|
StreamInfo->StreamName[1] == ':')
|
|
{
|
|
if (StreamInfo->NextEntryOffset == 0)
|
|
break; // all done with streams
|
|
StreamInfo = (PFILE_STREAM_INFORMATION)((PCHAR) StreamInfo +
|
|
StreamInfo->NextEntryOffset);
|
|
continue; // Move on to the next stream
|
|
}
|
|
|
|
//
|
|
// Build a string descriptor for the name of the stream.
|
|
//
|
|
|
|
StreamName.Buffer = &StreamInfo->StreamName[0];
|
|
StreamName.Length = (USHORT) StreamInfo->StreamNameLength;
|
|
StreamName.MaximumLength = StreamName.Length;
|
|
|
|
//
|
|
// Open the source stream.
|
|
//
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
&StreamName,
|
|
OBJ_KERNEL_HANDLE,
|
|
SourceFileHandle,
|
|
NULL );
|
|
|
|
Status = SrIoCreateFile( &StreamHandle,
|
|
GENERIC_READ
|
|
|FILE_GENERIC_READ,
|
|
&ObjectAttributes,
|
|
&IoStatus,
|
|
NULL,
|
|
0,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
SR_CREATE_FLAGS,
|
|
NULL,
|
|
0,
|
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
|
pExtension->pTargetDevice );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
leave;
|
|
|
|
OutputStream = NULL;
|
|
|
|
Status = SrCopyStream( StreamHandle,
|
|
pExtension->pTargetDevice,
|
|
&StreamName,
|
|
DestFile,
|
|
&StreamInfo->StreamSize,
|
|
&OutputStream );
|
|
|
|
ZwClose(StreamHandle);
|
|
StreamHandle = NULL;
|
|
|
|
if (OutputStream != NULL)
|
|
{
|
|
//
|
|
// We set the last write time on all streams
|
|
// since there is a problem with RDR caching
|
|
// open handles and closing them out of order.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = ZwSetInformationFile( OutputStream,
|
|
&IoStatus,
|
|
&BasicInformation,
|
|
sizeof(BasicInformation),
|
|
FileBasicInformation );
|
|
}
|
|
|
|
ZwClose(OutputStream);
|
|
}
|
|
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// remember how much we just copied
|
|
//
|
|
|
|
if (pBytesWritten != NULL)
|
|
{
|
|
*pBytesWritten += StreamInfo->StreamSize.QuadPart;
|
|
}
|
|
|
|
//
|
|
// anymore streams?
|
|
//
|
|
|
|
if (StreamInfo->NextEntryOffset == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// move on to the next one
|
|
//
|
|
|
|
StreamInfo =
|
|
(PFILE_STREAM_INFORMATION)((PCHAR) StreamInfo +
|
|
StreamInfo->NextEntryOffset);
|
|
|
|
} // while (TRUE)
|
|
|
|
} // if ( StreamInfoBase != NULL )
|
|
|
|
|
|
//
|
|
// set the last write time for the default steam so that it matches the
|
|
// input file.
|
|
//
|
|
|
|
Status = ZwSetInformationFile( DestFile,
|
|
&IoStatus,
|
|
&BasicInformation,
|
|
sizeof(BasicInformation),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// Now get the short file name for the file that we have successfully
|
|
// backed up. If we are backing up this file in response to a
|
|
// modification of a named stream on this file, this is the only
|
|
// time we have a handle to the primary data stream.
|
|
//
|
|
|
|
if (pShortFileName != NULL)
|
|
{
|
|
|
|
Status = ObReferenceObjectByHandle( SourceFileHandle,
|
|
0,
|
|
*IoFileObjectType,
|
|
KernelMode,
|
|
&pSourceFileObject,
|
|
NULL );
|
|
if (!NT_SUCCESS( Status ))
|
|
leave;
|
|
|
|
//
|
|
// Use the pSourceFileObject to get the short name.
|
|
//
|
|
|
|
Status = SrGetShortFileName( pExtension,
|
|
pSourceFileObject,
|
|
pShortFileName );
|
|
|
|
if (STATUS_OBJECT_NAME_NOT_FOUND == Status)
|
|
{
|
|
//
|
|
// This file doesn't have a short name.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// We hit an unexpected error, so leave.
|
|
//
|
|
|
|
leave;
|
|
}
|
|
}
|
|
} finally {
|
|
|
|
//
|
|
// check for unhandled exceptions
|
|
//
|
|
|
|
Status = FinallyUnwind(SrBackupFile, Status);
|
|
|
|
//
|
|
// did we fail?
|
|
//
|
|
|
|
if ((Status != SR_STATUS_IGNORE_FILE) &&
|
|
!NT_SUCCESS( Status ))
|
|
{
|
|
if (DestFile != NULL)
|
|
{
|
|
//
|
|
// delete the dest file
|
|
//
|
|
|
|
SrMarkFileForDelete(DestFile);
|
|
}
|
|
}
|
|
|
|
if (DestFile != NULL)
|
|
{
|
|
ZwClose(DestFile);
|
|
DestFile = NULL;
|
|
}
|
|
|
|
if (pSourceFileObject != NULL)
|
|
{
|
|
ObDereferenceObject( pSourceFileObject );
|
|
NULLPTR( pSourceFileObject );
|
|
}
|
|
if (SourceFileHandle != NULL)
|
|
{
|
|
ZwClose(SourceFileHandle);
|
|
SourceFileHandle = NULL;
|
|
}
|
|
|
|
if (StreamInfoBase != NULL)
|
|
{
|
|
SR_FREE_POOL(StreamInfoBase, SR_STREAM_DATA_TAG);
|
|
StreamInfoBase = NULL;
|
|
}
|
|
} // finally
|
|
|
|
#if DBG
|
|
if (Status == STATUS_FILE_IS_A_DIRECTORY)
|
|
{
|
|
return Status;
|
|
}
|
|
#endif
|
|
|
|
RETURN(Status);
|
|
} // SrBackupFile
|
|
|
|
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine marks a file for delete, so that when the supplied handle
|
|
is closed, the file will actually be deleted.
|
|
|
|
Arguments:
|
|
|
|
FileHandle - Supplies a handle to the file that is to be marked for delete.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
NTSTATUS
|
|
SrMarkFileForDelete(
|
|
HANDLE FileHandle
|
|
)
|
|
{
|
|
#undef DeleteFile
|
|
|
|
FILE_DISPOSITION_INFORMATION DispositionInformation;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
FILE_BASIC_INFORMATION BasicInformation;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
BasicInformation.FileAttributes = 0;
|
|
|
|
Status = ZwQueryInformationFile( FileHandle,
|
|
&IoStatus,
|
|
&BasicInformation,
|
|
sizeof(BasicInformation),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
if (BasicInformation.FileAttributes & FILE_ATTRIBUTE_READONLY)
|
|
{
|
|
RtlZeroMemory(&BasicInformation, sizeof(BasicInformation));
|
|
|
|
BasicInformation.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
Status = ZwSetInformationFile( FileHandle,
|
|
&IoStatus,
|
|
&BasicInformation,
|
|
sizeof(BasicInformation),
|
|
FileBasicInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory(&DispositionInformation, sizeof(DispositionInformation));
|
|
|
|
DispositionInformation.DeleteFile = TRUE;
|
|
|
|
Status = ZwSetInformationFile( FileHandle,
|
|
&IoStatus,
|
|
&DispositionInformation,
|
|
sizeof(DispositionInformation),
|
|
FileDispositionInformation );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
goto end;
|
|
|
|
end:
|
|
RETURN(Status);
|
|
|
|
} // SrMarkFileForDelete
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
calls SrBackupFile then calls SrUpdateBytesWritten and SrLogEvent
|
|
|
|
Arguments:
|
|
|
|
EventType - the event that occurred
|
|
|
|
pFileObject - the file object that just changed
|
|
|
|
pFileName - the name of the file that changed
|
|
|
|
pDestFileName - the dest file to copy to
|
|
|
|
CopyDataStreams - should we copy the data streams.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
SrBackupFileAndLog(
|
|
IN PSR_DEVICE_EXTENSION pExtension,
|
|
IN SR_EVENT_TYPE EventType,
|
|
IN PFILE_OBJECT pFileObject,
|
|
IN PUNICODE_STRING pFileName,
|
|
IN PUNICODE_STRING pDestFileName,
|
|
IN BOOLEAN CopyDataStreams
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONGLONG BytesWritten;
|
|
WCHAR ShortFileNameBuffer[SR_SHORT_NAME_CHARS+1];
|
|
UNICODE_STRING ShortFileName;
|
|
|
|
PAGED_CODE();
|
|
|
|
RtlInitEmptyUnicodeString( &ShortFileName,
|
|
ShortFileNameBuffer,
|
|
sizeof(ShortFileNameBuffer) );
|
|
|
|
//
|
|
// backup the file
|
|
//
|
|
|
|
Status = SrBackupFile( pExtension,
|
|
pFileObject,
|
|
pFileName,
|
|
pDestFileName,
|
|
CopyDataStreams,
|
|
&BytesWritten,
|
|
&ShortFileName );
|
|
|
|
if (Status == SR_STATUS_IGNORE_FILE)
|
|
{
|
|
//
|
|
// During the backup process we realized that we wanted to ignore
|
|
// this file, so change this status to STATUS_SUCCESS and don't
|
|
// try to log this operation.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto SrBackupFileAndLog_Exit;
|
|
}
|
|
else if (!NT_SUCCESS_NO_DBGBREAK( Status ))
|
|
{
|
|
goto SrBackupFileAndLog_Exit;
|
|
}
|
|
|
|
//
|
|
// SrHandleFileOverwrite passes down SrEventInvalid which means it
|
|
// doesn't want it logged yet.
|
|
//
|
|
|
|
if (EventType != SrEventInvalid)
|
|
{
|
|
//
|
|
// Only update the bytes written if this is an event we want
|
|
// to log. Otherwise, this event doesn't affect the number
|
|
// of bytes in the store.
|
|
//
|
|
|
|
Status = SrUpdateBytesWritten(pExtension, BytesWritten);
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
goto SrBackupFileAndLog_Exit;
|
|
}
|
|
|
|
//
|
|
// Go ahead and log this event now.
|
|
//
|
|
|
|
Status = SrLogEvent( pExtension,
|
|
EventType,
|
|
pFileObject,
|
|
pFileName,
|
|
0,
|
|
pDestFileName,
|
|
NULL,
|
|
0,
|
|
&ShortFileName );
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
goto SrBackupFileAndLog_Exit;
|
|
}
|
|
}
|
|
|
|
SrBackupFileAndLog_Exit:
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// When dealing with modifications to streams on directories, this
|
|
// is a valid error code to return.
|
|
//
|
|
|
|
if (Status == STATUS_FILE_IS_A_DIRECTORY)
|
|
{
|
|
return Status;
|
|
}
|
|
#endif
|
|
|
|
RETURN(Status);
|
|
} // SrBackupFileAndLog
|
|
|
|
BOOLEAN
|
|
SrIsFileEncrypted (
|
|
PSR_DEVICE_EXTENSION pExtension,
|
|
PFILE_OBJECT pFileObject
|
|
)
|
|
{
|
|
FILE_BASIC_INFORMATION fileBasicInfo;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// First do a quick check to see if this volume supports encryption
|
|
// if we already have the file system attributes cached.
|
|
//
|
|
|
|
if (pExtension->CachedFsAttributes)
|
|
{
|
|
if (!FlagOn( pExtension->FsAttributes, FILE_SUPPORTS_ENCRYPTION ))
|
|
{
|
|
//
|
|
// The file system doesn't support encryption, therefore this
|
|
// file cannot be encrypted.
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
status = SrQueryInformationFile( pExtension->pTargetDevice,
|
|
pFileObject,
|
|
&fileBasicInfo,
|
|
sizeof( fileBasicInfo ),
|
|
FileBasicInformation,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS( status ))
|
|
{
|
|
//
|
|
// We couldn't read the basic information for this file, so we must
|
|
// assume that it is not encrypted.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (FlagOn( fileBasicInfo.FileAttributes, FILE_ATTRIBUTE_ENCRYPTED ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|