windows-nt/Source/XPSP1/NT/base/win32/client/backup.c

3132 lines
75 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//
// Copyright (c) 1991 Microsoft Corporation & Maynard Electornics
//
// Module Name:
//
// backup.c
//
// Abstract:
//
// This module implements Win32 Backup APIs
//
// Author:
//
// Steve DeVos (@Maynard) 2 March, 1992 15:38:24
//
// Revision History:
#include <basedll.h>
#pragma hdrstop
#include <windows.h>
#define CWCMAX_STREAMNAME 512
#define CB_NAMELESSHEADER FIELD_OFFSET(WIN32_STREAM_ID, cStreamName)
typedef struct
{
DWORD BufferSize;
DWORD AllocSize;
BYTE *Buffer;
} BUFFER;
//
// BACKUPCONTEXT is the structure used to note the state of the backup.
//
typedef struct
{
//
// Public header describing current stream. Since this structure precedes
// a variable-length stream name, we must reserve space for that name
// following the header.
//
WIN32_STREAM_ID head;
union {
WCHAR awcName[CWCMAX_STREAMNAME];
} ex ;
LARGE_INTEGER cbSparseOffset ;
//
// Offset in the current segment of the backup stream. This includes
// the size of the above header (including variable length name).
//
LONGLONG liStreamOffset;
//
// BackupRead machine state
//
DWORD StreamIndex;
//
// Calculated size of the above header.
//
DWORD cbHeader;
//
// Handle to alternate data stream
//
HANDLE hAlternate;
//
// Buffers
//
BUFFER DataBuffer; // Data buffer
DWORD dwSparseMapSize ; // size of the sparse file map
DWORD dwSparseMapOffset ; // offset into the sparse map
BOOLEAN fSparseBlockStart ; // TRUE if start of sparse block
BOOLEAN fSparseHandAlt ; // TRUE if sparse stream is alt stream
DWORD iNameBuffer; // Offset into stream name buffer
BUFFER StreamNameBuffer; // Stream name buffer
BOOLEAN NamesReady; // TRUE if stream name buffer has data in it
BOOLEAN fStreamStart; // TRUE if start of new stream
BOOLEAN fMultiStreamType; // TRUE if stream type has > 1 stream hdr
BOOLEAN fAccessError; // TRUE if access to a stream was denied
DWORD fAttribs; // object attributes...
} BACKUPCONTEXT;
//
// BACKUPIOFRAME describes the current user BackupRead/Write request
//
typedef struct
{
BYTE *pIoBuffer;
DWORD *pcbTransferred;
DWORD cbRequest;
BOOLEAN fProcessSecurity;
} BACKUPIOFRAME;
#define CBMIN_BUFFER 1024
#define BufferOverflow(s) \
((s) == STATUS_BUFFER_OVERFLOW || (s) == STATUS_BUFFER_TOO_SMALL)
int mwStreamList[] =
{
BACKUP_SECURITY_DATA,
BACKUP_REPARSE_DATA,
BACKUP_DATA,
BACKUP_EA_DATA,
BACKUP_ALTERNATE_DATA,
BACKUP_OBJECT_ID,
BACKUP_INVALID,
};
__inline VOID *
BackupAlloc (DWORD cb)
/*++
Routine Description:
This is an internal routine that wraps heap allocation with tags.
Arguments:
cb - size of block to allocate
Return Value:
pointer to allocated memory or NULL
--*/
{
return RtlAllocateHeap( RtlProcessHeap( ), MAKE_TAG( BACKUP_TAG ), cb );
}
__inline VOID
BackupFree (IN VOID *pv)
/*++
Routine Description:
This is an internal routine that wraps heap freeing.
Arguments:
pv - memory to be freed
Return Value:
None.
--*/
{
RtlFreeHeap( RtlProcessHeap( ), 0, pv );
}
BOOL
GrowBuffer (IN OUT BUFFER *Buffer, IN DWORD cbNew)
/*++
Routine Description:
Attempt to grow the buffer in the backup context.
Arguments:
Buffer - pointer to buffer
cbNew - size of buffer to allocate
Return Value:
TRUE if buffer was successfully allocated.
--*/
{
VOID *pv;
if ( Buffer->AllocSize < cbNew ) {
pv = BackupAlloc( cbNew );
if (pv == NULL) {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
RtlCopyMemory( pv, Buffer->Buffer, Buffer->BufferSize );
BackupFree( Buffer->Buffer );
Buffer->Buffer = pv;
Buffer->AllocSize = cbNew ;
}
Buffer->BufferSize = cbNew;
return TRUE;
}
__inline VOID
FreeBuffer (IN OUT BUFFER *Buffer)
/*++
Routine Description:
Free the buffer
Arguments:
Buffer - pointer to buffer
Return Value:
Nothing
--*/
{
if (Buffer->Buffer != NULL) {
BackupFree( Buffer->Buffer );
Buffer->Buffer = NULL;
}
}
VOID ResetAccessDate( HANDLE hand )
{
LONGLONG tmp_time = -1 ;
FILETIME *time_ptr ;
time_ptr = (FILETIME *)(&tmp_time);
if (hand != INVALID_HANDLE_VALUE) {
SetFileTime( hand,
time_ptr,
time_ptr,
time_ptr ) ;
}
}
VOID
FreeContext (IN OUT LPVOID *lpContext)
/*++
Routine Description:
Free a backup context and release all resources assigned to it.
Arguments:
lpContext - pointer to pointer backup context
Return Value:
None.
--*/
{
BACKUPCONTEXT *pbuc = *lpContext;
if (pbuc != INVALID_HANDLE_VALUE) {
FreeBuffer( &pbuc->DataBuffer );
FreeBuffer( &pbuc->StreamNameBuffer );
ResetAccessDate( pbuc->hAlternate ) ;
if (pbuc->hAlternate != INVALID_HANDLE_VALUE) {
CloseHandle( pbuc->hAlternate );
}
BackupFree(pbuc);
*lpContext = INVALID_HANDLE_VALUE;
}
}
BACKUPCONTEXT *
AllocContext (IN DWORD cbBuffer)
/*++
Routine Description:
Allocate a backup context with a buffer of a specified size
Arguments:
cbBuffer - desired length of the buffer
Return Value:
pointer to initialized backupcontext or NULL if out of memory.
--*/
{
BACKUPCONTEXT *pbuc;
pbuc = BackupAlloc( sizeof( *pbuc ));
if (pbuc != NULL) {
RtlZeroMemory( pbuc, sizeof( *pbuc ));
pbuc->fStreamStart = TRUE;
if (cbBuffer != 0 && !GrowBuffer( &pbuc->DataBuffer, cbBuffer )) {
BackupFree( pbuc );
pbuc = NULL;
}
}
if (pbuc == NULL) {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
}
return(pbuc);
}
LONGLONG
ComputeRemainingSize (IN BACKUPCONTEXT *pbuc)
/*++
Routine Description:
(Re)Compute the number of bytes required to store the current
stream. This needs to take into account the header length as
well.
Arguments:
pbuc - backup context
Return Value:
Amount of data still remaining to transfer. Includes header
size.
--*/
{
LARGE_INTEGER ret_size ;
ret_size.QuadPart = pbuc->cbHeader + pbuc->head.Size.QuadPart
- pbuc->liStreamOffset;
//
// since the internally we treat the sparse buffer offset
// as part of the header and since the caller need to see it
// as part of the data, this code make the internal correction.
//
if ( pbuc->head.dwStreamId == BACKUP_SPARSE_BLOCK ) {
ret_size.QuadPart -= sizeof(LARGE_INTEGER) ;
}
return ret_size.QuadPart ;
}
DWORD
ComputeRequestSize (BACKUPCONTEXT *pbuc, DWORD cbrequest)
/*++
Routine Description:
Given a transfer size request, return the number of
bytes remaining that can safely be returned to the
caller
Arguments:
pbuc - context of call
cbRequest - desired transfer size
Return Value:
amount of data available to return.
--*/
{
LONGLONG licbRemain;
licbRemain = ComputeRemainingSize( pbuc );
return (DWORD) min( cbrequest, licbRemain );
}
VOID
ReportTransfer(BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif, DWORD cbtransferred)
/*++
Routine Description:
Note that a transfer has occurred and update contexts
Arguments:
pbuc - context of call
pbif - BACKUPIOFRAME of call detailing call
cbtransferred - amount successfully transferred
Return Value:
None.
--*/
{
pbuc->liStreamOffset += cbtransferred;
*pbif->pcbTransferred += cbtransferred;
pbif->cbRequest -= cbtransferred;
pbif->pIoBuffer += cbtransferred;
}
VOID
BackupReadBuffer (BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from the buffer in the backup
context.
Arguments:
pbuc - context of call
pbif - frame describing desired user BackupRead request
Return Value:
None.
--*/
{
DWORD cbrequest;
BYTE *pb;
//
// Determine size of allowable transfer and pointer to source
// data
//
cbrequest = ComputeRequestSize( pbuc, pbif->cbRequest );
pb = &pbuc->DataBuffer.Buffer[ pbuc->liStreamOffset - pbuc->cbHeader ];
//
// Move the data to the user's buffer
//
RtlCopyMemory(pbif->pIoBuffer, pb, cbrequest);
//
// Update statistics
//
ReportTransfer(pbuc, pbif, cbrequest);
}
BOOL
BackupReadStream (HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from the stream.
Arguments:
hFile - handle to file for transfer
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
DWORD cbrequest;
DWORD cbtransferred;
BOOL fSuccess;
if (pbuc->fSparseBlockStart) {
PFILE_ALLOCATED_RANGE_BUFFER range_buf ;
LARGE_INTEGER licbFile ;
range_buf = (PFILE_ALLOCATED_RANGE_BUFFER)(pbuc->DataBuffer.Buffer + pbuc->dwSparseMapOffset) ;
pbuc->head.Size.QuadPart = range_buf->Length.QuadPart + sizeof(LARGE_INTEGER) ;
pbuc->head.dwStreamId = BACKUP_SPARSE_BLOCK ;
pbuc->head.dwStreamAttributes = STREAM_SPARSE_ATTRIBUTE;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER + sizeof( LARGE_INTEGER ) ;
pbuc->cbSparseOffset = range_buf->FileOffset ;
RtlCopyMemory( pbuc->head.cStreamName, &pbuc->cbSparseOffset, sizeof( LARGE_INTEGER ) ) ;
pbuc->fSparseBlockStart = FALSE;
licbFile.HighPart = 0;
licbFile.HighPart = range_buf->FileOffset.HighPart;
licbFile.LowPart = SetFilePointer( hFile,
range_buf->FileOffset.LowPart,
&licbFile.HighPart,
FILE_BEGIN );
if ( licbFile.QuadPart != range_buf->FileOffset.QuadPart ) {
pbuc->fAccessError = TRUE;
return FALSE ;
} else {
return TRUE ;
}
}
if (pbuc->liStreamOffset < pbuc->cbHeader) {
return TRUE ;
}
cbrequest = ComputeRequestSize( pbuc, pbif->cbRequest );
fSuccess = ReadFile( hFile, pbif->pIoBuffer, cbrequest, &cbtransferred, NULL );
if (cbtransferred != 0) {
ReportTransfer( pbuc, pbif, cbtransferred );
} else if (fSuccess && cbrequest != 0) {
SetLastError( ERROR_IO_DEVICE );
fSuccess = FALSE;
}
return(fSuccess);
}
BOOL
BackupGetSparseMap (HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Reads the sparse data map.
Arguments:
hFile - handle to file for transfer
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
FILE_ALLOCATED_RANGE_BUFFER req_buf ;
PFILE_ALLOCATED_RANGE_BUFFER last_ret_buf ;
DWORD out_buf_size ;
DWORD data_size = 4096 ;
IO_STATUS_BLOCK iosb ;
LARGE_INTEGER file_size ;
NTSTATUS Status ;
BOOLEAN empty_file = FALSE ;
req_buf.FileOffset.QuadPart = 0 ;
pbuc->dwSparseMapSize = 0 ;
pbuc->dwSparseMapOffset = 0 ;
pbuc->fSparseBlockStart = FALSE ;
req_buf.Length.LowPart = GetFileSize( hFile,
&req_buf.Length.HighPart );
file_size = req_buf.Length ;
do {
if ( GrowBuffer( &pbuc->DataBuffer,
data_size ) ) {
iosb.Information = 0 ;
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_QUERY_ALLOCATED_RANGES,
&req_buf,
sizeof( req_buf ),
pbuc->DataBuffer.Buffer + pbuc->dwSparseMapSize,
pbuc->DataBuffer.AllocSize - pbuc->dwSparseMapSize ) ;
out_buf_size = 0 ;
if ((Status == STATUS_BUFFER_OVERFLOW) || NT_SUCCESS( Status ) ) {
out_buf_size = (DWORD)iosb.Information ;
if ( out_buf_size == 0 ) {
empty_file = TRUE ;
}
}
if ( out_buf_size != 0 ) {
pbuc->dwSparseMapSize += out_buf_size ;
last_ret_buf =
(PFILE_ALLOCATED_RANGE_BUFFER)(pbuc->DataBuffer.Buffer +
pbuc->dwSparseMapSize -
sizeof(FILE_ALLOCATED_RANGE_BUFFER)) ;
req_buf.FileOffset = last_ret_buf->FileOffset ;
req_buf.FileOffset.QuadPart += last_ret_buf->Length.QuadPart ;
//
// if we can't fit any more in the buffer lets increase
// the size and get more data otherwise assume were done.
//
if ( pbuc->dwSparseMapSize + sizeof(FILE_ALLOCATED_RANGE_BUFFER) >
pbuc->DataBuffer.AllocSize ) {
data_size += 4096 ;
} else {
break ;
}
} else {
// reallocate for one more buffer entry
if ( out_buf_size + sizeof(FILE_ALLOCATED_RANGE_BUFFER) > data_size ) {
data_size += 4096 ;
continue ;
}
break ;
}
} else {
pbuc->dwSparseMapSize = 0 ;
break ;
}
} while ( TRUE ) ;
//
// if there are RANGE_BUFFERS and it isn't simply the whole file, then
// go into sparse read mode.
//
// hold on to your hat...
// If there are no allocated ranges and the file is NOT 0 length
// then we want to manufacture a record for the file length.
//
if ( (empty_file && ( file_size.QuadPart != 0 )) || (pbuc->dwSparseMapSize >= sizeof( FILE_ALLOCATED_RANGE_BUFFER) ) ) {
last_ret_buf = (PFILE_ALLOCATED_RANGE_BUFFER)(pbuc->DataBuffer.Buffer ) ;
if ( empty_file ||
( last_ret_buf->FileOffset.QuadPart != 0 ) ||
( last_ret_buf->Length.QuadPart != file_size.QuadPart ) ) {
// first lets add a record for the EOF marker
pbuc->dwSparseMapSize += sizeof(FILE_ALLOCATED_RANGE_BUFFER) ;
last_ret_buf =
(PFILE_ALLOCATED_RANGE_BUFFER)(pbuc->DataBuffer.Buffer +
pbuc->dwSparseMapSize -
sizeof(FILE_ALLOCATED_RANGE_BUFFER)) ;
last_ret_buf->FileOffset.QuadPart = file_size.QuadPart ;
last_ret_buf->Length.QuadPart = 0 ;
pbuc->fSparseBlockStart = TRUE ;
return TRUE ;
}
}
pbuc->dwSparseMapSize = 0 ;
return FALSE ;
}
BOOL
BackupReadData (HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Read default data for a user BackupRead request.
Arguments:
hFile - handle to file for transfer
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
LARGE_INTEGER licbFile ;
//
// If the context is not initialized for this transfer,
// set up based on file size.
//
if (pbuc->fStreamStart) {
if (pbuc->fAttribs & FILE_ATTRIBUTE_ENCRYPTED) {
return TRUE;
}
if (pbuc->fAttribs & FILE_ATTRIBUTE_DIRECTORY) {
return TRUE;
}
licbFile.LowPart = GetFileSize( hFile, &licbFile.HighPart );
if (licbFile.QuadPart == 0) {
return TRUE;
}
if (licbFile.LowPart == 0xffffffff && GetLastError() != NO_ERROR) {
return FALSE;
}
pbuc->head.dwStreamId = BACKUP_DATA;
pbuc->head.dwStreamAttributes = STREAM_NORMAL_ATTRIBUTE;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER;
pbuc->fStreamStart = FALSE;
if ( BackupGetSparseMap( hFile, pbuc, pbif ) ) {
pbuc->head.Size.QuadPart = 0 ;
pbuc->head.dwStreamAttributes = STREAM_SPARSE_ATTRIBUTE;
} else {
pbuc->head.Size = licbFile;
licbFile.HighPart = 0;
SetFilePointer( hFile, 0, &licbFile.HighPart, FILE_BEGIN );
}
return TRUE;
}
//
// If there's more data for us to read, then go and
// get it from the stream
//
return BackupReadStream( hFile, pbuc, pbif );
}
BOOL
BackupReadAlternateData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from alternate data streams.
Arguments:
hFile - handle to base file for transfer
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
//
// If we haven't started transferring alternate data streams then
// buffer all the stream information from the file system
//
if (pbuc->fStreamStart) {
NTSTATUS Status;
FILE_STREAM_INFORMATION *pfsi;
IO_STATUS_BLOCK iosb;
if (pbuc->fAttribs & FILE_ATTRIBUTE_ENCRYPTED) {
if ( !(pbuc->fAttribs & FILE_ATTRIBUTE_DIRECTORY) ) {
return TRUE;
}
}
//
// Loop, growing the names buffer, until it is large enough to
// contain all the alternate data
//
if (!pbuc->NamesReady) {
if (!GrowBuffer( &pbuc->StreamNameBuffer, 1024 ) ) {
return FALSE;
}
while (TRUE) {
//
// Resize the buffer. If we cannot grow it, then fail.
//
Status = NtQueryInformationFile(
hFile,
&iosb,
pbuc->StreamNameBuffer.Buffer,
pbuc->StreamNameBuffer.BufferSize,
FileStreamInformation);
//
// If we succeeded in reading some data, set the buffer
// up and finish initializing
//
if (NT_SUCCESS(Status) && iosb.Information != 0) {
pbuc->iNameBuffer = 0;
pbuc->NamesReady = TRUE;
break;
}
//
// If the error was not due to overflow, then skip
// all alternate streams
//
if (!BufferOverflow(Status)) {
return TRUE;
}
//
// simply inlarge the buffer and try again.
//
if (!GrowBuffer( &pbuc->StreamNameBuffer,
pbuc->StreamNameBuffer.BufferSize * 2)) {
return FALSE;
}
}
}
pbuc->hAlternate = INVALID_HANDLE_VALUE;
pbuc->fStreamStart = FALSE;
pfsi = (FILE_STREAM_INFORMATION *) &pbuc->StreamNameBuffer.Buffer[pbuc->iNameBuffer];
//
// Skip first stream if it is the default data stream. This
// code is NTFS-specific and relies on behaviour not documented anywhere.
//
if (pfsi->StreamNameLength >= 2 * sizeof(WCHAR) &&
pfsi->StreamName[1] == ':') {
if (pfsi->NextEntryOffset == 0) {
return TRUE; // No more, do next stream type
}
pbuc->iNameBuffer += pfsi->NextEntryOffset;
}
pbuc->head.Size.LowPart = 1;
//
// If we don't have an open stream
//
} else if (pbuc->hAlternate == INVALID_HANDLE_VALUE) {
NTSTATUS Status;
PFILE_STREAM_INFORMATION pfsi;
UNICODE_STRING strName;
OBJECT_ATTRIBUTES oa;
IO_STATUS_BLOCK iosb;
DWORD reparse_flg = 0 ;
pbuc->head.Size.QuadPart = 0;
//
// Form the relative name of the stream and try to
// open it relative to the base file
//
pfsi = (FILE_STREAM_INFORMATION *) &pbuc->StreamNameBuffer.Buffer[pbuc->iNameBuffer];
strName.Length = (USHORT) pfsi->StreamNameLength;
strName.MaximumLength = strName.Length;
strName.Buffer = pfsi->StreamName;
if (pbuc->fAttribs & FILE_ATTRIBUTE_REPARSE_POINT ) {
reparse_flg = FILE_OPEN_REPARSE_POINT ;
}
InitializeObjectAttributes(
&oa,
&strName,
OBJ_CASE_INSENSITIVE,
hFile,
NULL);
Status = NtOpenFile(
&pbuc->hAlternate,
FILE_READ_DATA | SYNCHRONIZE,
&oa,
&iosb,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | reparse_flg);
//
// If we did not succeed, skip this entry and set up for another stream
//
if (!NT_SUCCESS( Status )) {
pbuc->iNameBuffer += pfsi->NextEntryOffset;
if (pfsi->NextEntryOffset != 0) {
pbuc->head.Size.LowPart = 1;
pbuc->fMultiStreamType = TRUE; // more to come
}
SetLastError( ERROR_SHARING_VIOLATION );
return FALSE;
}
// if we can't lock all records, return an error
if (!LockFile( pbuc->hAlternate, 0, 0, 0xffffffff, 0xffffffff )) {
SetLastError( ERROR_SHARING_VIOLATION );
return FALSE;
}
//
// Perform common header initialization
//
pbuc->head.dwStreamAttributes = STREAM_NORMAL_ATTRIBUTE;
pbuc->head.dwStreamNameSize = pfsi->StreamNameLength;
pbuc->cbHeader = CB_NAMELESSHEADER + pfsi->StreamNameLength;
RtlCopyMemory(
pbuc->head.cStreamName,
pfsi->StreamName,
pfsi->StreamNameLength);
//
// Advance to the next stream in the stream information block
//
if (pfsi->NextEntryOffset != 0) {
pbuc->iNameBuffer += pfsi->NextEntryOffset;
pbuc->fMultiStreamType = TRUE;
}
//
// If we are a data stream, set up for data stream copy
//
if (BasepIsDataAttribute( pfsi->StreamNameLength, pfsi->StreamName )) {
pbuc->head.dwStreamId = BACKUP_ALTERNATE_DATA;
if ( BackupGetSparseMap( pbuc->hAlternate, pbuc, pbif ) ) {
pbuc->head.Size.QuadPart = 0 ;
pbuc->head.dwStreamAttributes = STREAM_SPARSE_ATTRIBUTE;
} else {
pbuc->head.Size.LowPart = GetFileSize(
pbuc->hAlternate,
&pbuc->head.Size.HighPart );
}
}
//
// If we need to return the name
//
} else if ( pbuc->liStreamOffset < pbuc->cbHeader) {
return TRUE ;
//
// If there is more data in this stream to transfer
//
} else if ( (pbuc->head.dwStreamId == BACKUP_ALTERNATE_DATA) ||
(pbuc->head.dwStreamId == BACKUP_SPARSE_BLOCK) ) {
return BackupReadStream( pbuc->hAlternate, pbuc, pbif );
}
return TRUE;
}
BOOL
BackupReadEaData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from EA data.
Arguments:
hFile - handle to file with EAs
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
//
// If we are just starting out on the EA data
//
if (pbuc->fStreamStart) {
IO_STATUS_BLOCK iosb;
//
// Loop trying to read all EA data into the buffer and
// resize the buffer if necessary
//
while (TRUE) {
NTSTATUS Status;
FILE_EA_INFORMATION fei;
Status = NtQueryEaFile(
hFile,
&iosb,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize,
FALSE,
NULL,
0,
0,
(BOOLEAN) TRUE );
//
// If we successfully read all the data, go complete
// the initialization
//
if (NT_SUCCESS( Status ) && iosb.Information != 0) {
pbuc->NamesReady = TRUE;
break;
}
//
// If we received a status OTHER than buffer overflow then
// skip EA's altogether
//
if (!BufferOverflow(Status)) {
return TRUE;
}
//
// Get a stab at the total EA size
//
Status = NtQueryInformationFile(
hFile,
&iosb,
&fei,
sizeof(fei),
FileEaInformation);
//
// This call should never have failed (since we were able to
// QueryEaFile) above. However, if it does, skip EAs altogether
//
if (!NT_SUCCESS(Status)) {
return TRUE;
}
//
// Resize the buffer to something that seems reasonable. No guarantees
// about whether this will work or not... If we couldn't grow the buffer
// fail this call.
//
if (!GrowBuffer( &pbuc->DataBuffer, (fei.EaSize * 5) / 4)) {
pbuc->fAccessError = TRUE;
return FALSE;
}
}
//
// Set up the header for the EA stream
//
pbuc->head.dwStreamId = BACKUP_EA_DATA;
pbuc->head.dwStreamAttributes = STREAM_NORMAL_ATTRIBUTE;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER;
pbuc->head.Size.QuadPart = iosb.Information;
pbuc->fStreamStart = FALSE;
//
// If we have more data in the buffer to read then go
// copy it out.
//
} else if (pbuc->liStreamOffset >= pbuc->cbHeader) {
BackupReadBuffer( pbuc, pbif );
}
return TRUE;
}
BOOL
BackupReadObjectId(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from NtObject ID data.
Arguments:
hFile - handle to file with EAs
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
//
// If we are just starting out on the Object ID data
//
if (pbuc->fStreamStart) {
IO_STATUS_BLOCK iosb;
NTSTATUS Status ;
if (!GrowBuffer( &pbuc->DataBuffer, 1024 ) ) {
pbuc->fAccessError = TRUE;
return FALSE;
}
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_GET_OBJECT_ID,
NULL,
0,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize ) ;
if ( !NT_SUCCESS(Status) ) {
return TRUE ;
}
//
// Set up the header for the Object ID stream
//
pbuc->NamesReady = TRUE;
pbuc->head.dwStreamId = BACKUP_OBJECT_ID ;
pbuc->head.dwStreamAttributes = STREAM_NORMAL_ATTRIBUTE;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER;
pbuc->head.Size.QuadPart = iosb.Information;
pbuc->fStreamStart = FALSE;
//
// If we have more data in the buffer to read then go
// copy it out.
//
} else if (pbuc->liStreamOffset >= pbuc->cbHeader) {
BackupReadBuffer( pbuc, pbif );
}
return TRUE;
}
BOOL
BackupReadReparseData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
Perform a read to user buffer from Reparse tag data.
Arguments:
hFile - handle to file with EAs
pbuc - context of call
pbif - frame describing BackupRead request
Return Value:
True if transfer succeeded..
--*/
{
IO_STATUS_BLOCK iosb;
PREPARSE_DATA_BUFFER rp_buf_ptr ;
NTSTATUS Status ;
struct RP_SUMMARY {
USHORT tag ;
USHORT rp_size ;
} *rp_summary_ptr =(struct RP_SUMMARY*) &(iosb.Information) ;
//
// If the object is not a reparse then simply return
//
if ( !(pbuc->fAttribs & FILE_ATTRIBUTE_REPARSE_POINT) ) {
return TRUE ;
}
//
// If we are just starting out on the ReParse data
//
if (pbuc->fStreamStart) {
//
// Loop trying to read all EA data into the buffer and
// resize the buffer if necessary
//
// for some reason a TOO_SMALL error is not setting the information
// member of the iosb....
rp_summary_ptr->rp_size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE ;
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize ) ;
if ( BufferOverflow( Status ) ) {
if ( rp_summary_ptr->rp_size == 0 ) {
rp_summary_ptr->rp_size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE ;
}
if (!GrowBuffer( &pbuc->DataBuffer,
rp_summary_ptr->rp_size ) ) {
pbuc->fAccessError = TRUE;
return FALSE;
}
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize ) ;
}
//
// If we successfully read all the data, go complete
// the initialization
//
if ( !NT_SUCCESS( Status ) ) {
return TRUE ;
}
//
// Set up the header for the ReParse stream
//
rp_buf_ptr = (PREPARSE_DATA_BUFFER)(pbuc->DataBuffer.Buffer) ;
pbuc->NamesReady = TRUE;
pbuc->head.dwStreamId = BACKUP_REPARSE_DATA;
pbuc->head.dwStreamAttributes = STREAM_NORMAL_ATTRIBUTE;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER;
if ( IsReparseTagMicrosoft( rp_buf_ptr->ReparseTag ) ) {
pbuc->head.Size.QuadPart = rp_buf_ptr->ReparseDataLength +
FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) ;
} else {
pbuc->head.Size.QuadPart = rp_buf_ptr->ReparseDataLength +
FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer) ;
}
pbuc->fStreamStart = FALSE;
//
// If we have more data in the buffer to read then go
// copy it out.
//
} else if (pbuc->liStreamOffset >= pbuc->cbHeader) {
BackupReadBuffer( pbuc, pbif );
}
return TRUE;
}
BOOL
BackupReadSecurityData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
{
//
// If we are to skip security then do so.
//
if (!pbif->fProcessSecurity) {
return TRUE;
}
//
// If we are just starting out on the security data
//
if (pbuc->fStreamStart) {
//
// Loop trying to read all security data into the buffer and
// resize the buffer if necessary
//
while (TRUE) {
NTSTATUS Status;
DWORD cbSecurityInfo;
RtlZeroMemory( pbuc->DataBuffer.Buffer, pbuc->DataBuffer.BufferSize );
Status = NtQuerySecurityObject(
hFile,
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION |
SACL_SECURITY_INFORMATION,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize,
&cbSecurityInfo );
//
// If we failed but it wasn't due to buffer overflow
//
if (!NT_SUCCESS( Status ) && !BufferOverflow( Status )) {
//
// Try reading everything but SACL
//
Status = NtQuerySecurityObject(
hFile,
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION,
pbuc->DataBuffer.Buffer,
pbuc->DataBuffer.BufferSize,
&cbSecurityInfo );
}
//
// If we got it all, then go continue initialization
//
if (NT_SUCCESS( Status )) {
pbuc->NamesReady = TRUE;
break;
}
//
// If not due to overflowing buffer, skip security altogether
//
if (!BufferOverflow( Status )) {
return TRUE;
}
//
// Resize the buffer to the expected size. If we fail, fail
// the entire call
//
if (!GrowBuffer( &pbuc->DataBuffer, cbSecurityInfo )) {
return FALSE;
}
}
//
// Initialize the stream header
//
pbuc->head.dwStreamId = BACKUP_SECURITY_DATA;
pbuc->head.dwStreamAttributes = STREAM_CONTAINS_SECURITY;
pbuc->head.dwStreamNameSize = 0;
pbuc->cbHeader = CB_NAMELESSHEADER;
pbuc->head.Size.QuadPart = RtlLengthSecurityDescriptor(pbuc->DataBuffer.Buffer);
pbuc->fStreamStart = FALSE;
//
// If there is more data in the buffer to transfer, go
// do it
//
} else if (pbuc->liStreamOffset >= pbuc->cbHeader) {
BackupReadBuffer( pbuc, pbif );
}
return TRUE;
}
VOID
BackupTestRestartStream(BACKUPCONTEXT *pbuc)
{
LONGLONG licbRemain;
licbRemain = ComputeRemainingSize( pbuc );
if (licbRemain == 0) {
if ( pbuc->dwSparseMapOffset != pbuc->dwSparseMapSize ) { // only true at backup
if ( !pbuc->fSparseBlockStart ) {
pbuc->dwSparseMapOffset += sizeof ( FILE_ALLOCATED_RANGE_BUFFER ) ;
}
}
if ( pbuc->dwSparseMapOffset != pbuc->dwSparseMapSize ) { // only true at backup
pbuc->fSparseBlockStart = TRUE ;
pbuc->cbHeader = 0 ;
pbuc->liStreamOffset = 0;
} else {
if ( !pbuc->fSparseHandAlt && (pbuc->hAlternate != NULL)) {
CloseHandle(pbuc->hAlternate); // releases any locks
pbuc->hAlternate = NULL;
}
pbuc->cbHeader = 0;
pbuc->fStreamStart = TRUE;
pbuc->fSparseBlockStart = TRUE;
pbuc->liStreamOffset = 0; // for BackupWrite
if (!pbuc->fMultiStreamType) { // for BackupRead
pbuc->StreamIndex++;
pbuc->head.dwStreamId = mwStreamList[pbuc->StreamIndex] ;
pbuc->NamesReady = FALSE;
}
}
}
}
// Routine Description:
//
// Data can be Backed up from an object using BackupRead.
//
// This API is used to read data from an object. After the
// read completes, the file pointer is adjusted by the number of bytes
// actually read. A return value of TRUE coupled with a bytes read of
// 0 indicates that end of file has been reached.
//
// Arguments:
//
// hFile - Supplies an open handle to a file that is to be read. The
// file handle must have been created with GENERIC_READ access.
//
// lpBuffer - Supplies the address of a buffer to receive the data read
// from the file.
//
// nNumberOfBytesToRead - Supplies the number of bytes to read from the
// file.
//
// lpNumberOfBytesRead - Returns the number of bytes read by this call.
// This parameter is always set to 0 before doing any IO or error
// checking.
//
// bAbort - If TRUE, then all resources associated with the context will
// be released.
//
// bProcessSecurity - If TRUE, then the NTFS ACL data will be read.
// If FALSE, then the ACL stream will be skipped.
//
// lpContext - Points to a buffer pointer setup and maintained by
// BackupRead.
//
//
// Return Value:
//
// TRUE - The operation was successul.
//
// FALSE - The operation failed. Extended error status is available
// using GetLastError.
//
//
// NOTE:
// The NT File Replication Service (NTFRS) performs an MD5 checksum on the
// stream of data returned by BackupRead(). If the sequence of file information
// returned changes then two machines, one downlevel and one uplevel will
// compute different MD5 checksums for the same file data. Under certain
// conditions this will cause needless file replication. Bear this in mind
// if a change in the returned data sequence is contemplated. The sources for
// NTFRS are in \nt\private\net\svcimgs\ntrepl.
//
BOOL WINAPI
BackupRead(
HANDLE hFile,
LPBYTE lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
BOOL bAbort,
BOOL bProcessSecurity,
LPVOID *lpContext)
{
BACKUPCONTEXT *pbuc;
BACKUPIOFRAME bif;
BOOL fSuccess = FALSE;
IO_STATUS_BLOCK iosb ;
pbuc = *lpContext;
bif.pIoBuffer = lpBuffer;
bif.cbRequest = nNumberOfBytesToRead;
bif.pcbTransferred = lpNumberOfBytesRead;
bif.fProcessSecurity = (BOOLEAN)bProcessSecurity;
if (bAbort) {
if (pbuc != NULL) {
ResetAccessDate( hFile ) ;
FreeContext(lpContext);
}
return TRUE;
}
*bif.pcbTransferred = 0;
if (pbuc == INVALID_HANDLE_VALUE || bif.cbRequest == 0) {
return TRUE;
}
if (pbuc != NULL && mwStreamList[pbuc->StreamIndex] == BACKUP_INVALID) {
ResetAccessDate( hFile ) ;
FreeContext(lpContext);
return TRUE;
}
// Allocate our Context Control Block on first call.
if (pbuc == NULL) {
pbuc = AllocContext(CBMIN_BUFFER); // Alloc initial buffer
// ok, we allocated the context, Lets initialize it.
if (pbuc != NULL) {
NTSTATUS Status ;
FILE_BASIC_INFORMATION fbi;
Status = NtQueryInformationFile(
hFile,
&iosb,
&fbi,
sizeof(fbi),
FileBasicInformation );
if ( NT_SUCCESS( Status ) ) {
pbuc->fAttribs = fbi.FileAttributes ;
} else {
BaseSetLastNTError( Status );
return FALSE ;
}
}
}
if (pbuc != NULL) {
*lpContext = pbuc;
do {
if (pbuc->fStreamStart) {
pbuc->head.Size.QuadPart = 0;
pbuc->liStreamOffset = 0;
pbuc->dwSparseMapOffset = 0;
pbuc->dwSparseMapSize = 0;
pbuc->fMultiStreamType = FALSE;
}
fSuccess = TRUE;
switch (mwStreamList[pbuc->StreamIndex]) {
case BACKUP_DATA:
fSuccess = BackupReadData(hFile, pbuc, &bif);
break;
case BACKUP_ALTERNATE_DATA:
fSuccess = BackupReadAlternateData(hFile, pbuc, &bif);
break;
case BACKUP_EA_DATA:
fSuccess = BackupReadEaData(hFile, pbuc, &bif);
break;
case BACKUP_OBJECT_ID:
fSuccess = BackupReadObjectId(hFile, pbuc, &bif);
break;
case BACKUP_REPARSE_DATA:
fSuccess = BackupReadReparseData(hFile, pbuc, &bif);
break;
case BACKUP_SECURITY_DATA:
fSuccess = BackupReadSecurityData(hFile, pbuc, &bif);
break;
default:
pbuc->StreamIndex++;
pbuc->fStreamStart = TRUE;
break;
}
// if we're in the phase of reading the header, copy the header
if (pbuc->liStreamOffset < pbuc->cbHeader) {
DWORD cbrequest;
// Send the current stream header;
cbrequest =
(ULONG)min( pbuc->cbHeader - pbuc->liStreamOffset,
bif.cbRequest);
RtlCopyMemory(
bif.pIoBuffer,
(BYTE *) &pbuc->head + pbuc->liStreamOffset,
cbrequest);
ReportTransfer(pbuc, &bif, cbrequest);
}
//
// if we are at the end of a stream then
// start at the beginning of the next stream
//
if (pbuc->liStreamOffset >= pbuc->cbHeader) {
BackupTestRestartStream(pbuc);
}
} while (fSuccess &&
mwStreamList[pbuc->StreamIndex] != BACKUP_INVALID &&
bif.cbRequest != 0);
}
if (fSuccess && *bif.pcbTransferred == 0) {
ResetAccessDate( hFile ) ;
FreeContext(lpContext);
}
return(fSuccess);
}
// Routine Description:
//
// Data can be skiped during BackupRead or BackupWrite by using
// BackupSeek.
//
// This API is used to seek forward from the current position the
// specified number of bytes. This function does not seek over a
// stream header. The number of bytes actually seeked is returned.
// If a caller wants to seek to the start of the next stream it can
// pass 0xffffffff, 0xffffffff as the amount to seek. The number of
// bytes actually skiped over is returned.
//
// Arguments:
//
// hFile - Supplies an open handle to a file that is to be read. The
// file handle must have been created with GENERIC_READ or
// GENERIC_WRITE access.
//
// dwLowBytesToSeek - Specifies the low 32 bits of the number of bytes
// requested to seek.
//
// dwHighBytesToSeek - Specifies the high 32 bits of the number of bytes
// requested to seek.
//
// lpdwLowBytesSeeked - Points to the buffer where the low 32 bits of the
// actual number of bytes to seek is to be placed.
//
// lpdwHighBytesSeeked - Points to the buffer where the high 32 bits of the
// actual number of bytes to seek is to be placed.
//
// bAbort - If true, then all resources associated with the context will
// be released.
//
// lpContext - Points to a buffer pointer setup and maintained by
// BackupRead.
//
//
// Return Value:
//
// TRUE - The operation successfuly seeked the requested number of bytes.
//
// FALSE - The requested number of bytes could not be seeked. The number
// of bytes actually seeked is returned.
BOOL WINAPI
BackupSeek(
HANDLE hFile,
DWORD dwLowBytesToSeek,
DWORD dwHighBytesToSeek,
LPDWORD lpdwLowBytesSeeked,
LPDWORD lpdwHighBytesSeeked,
LPVOID *lpContext)
{
BACKUPCONTEXT *pbuc;
LONGLONG licbRemain;
LARGE_INTEGER licbRequest;
BOOL fSuccess;
LARGE_INTEGER sparse_bytes ;
pbuc = *lpContext;
sparse_bytes.QuadPart = 0 ;
*lpdwHighBytesSeeked = 0;
*lpdwLowBytesSeeked = 0;
if (pbuc == INVALID_HANDLE_VALUE || pbuc == NULL || pbuc->fStreamStart) {
return FALSE;
}
if (pbuc->liStreamOffset < pbuc->cbHeader) {
return FALSE;
}
//
// If we made it here, we are in the middle of a stream
//
licbRemain = ComputeRemainingSize( pbuc );
licbRequest.LowPart = dwLowBytesToSeek;
licbRequest.HighPart = dwHighBytesToSeek & 0x7fffffff;
if (licbRequest.QuadPart > licbRemain) {
licbRequest.QuadPart = licbRemain;
}
fSuccess = TRUE;
switch (pbuc->head.dwStreamId) {
case BACKUP_EA_DATA:
case BACKUP_SECURITY_DATA:
case BACKUP_OBJECT_ID :
case BACKUP_REPARSE_DATA :
// assume less than 2gig of data
break;
case BACKUP_SPARSE_BLOCK :
if ( pbuc->liStreamOffset < sizeof(LARGE_INTEGER) ) {
sparse_bytes.QuadPart = ( sizeof(LARGE_INTEGER) - pbuc->liStreamOffset ) ;
if ( sparse_bytes.QuadPart < licbRequest.QuadPart ) {
licbRequest.QuadPart -= sparse_bytes.QuadPart ;
} else {
licbRequest.QuadPart = 0 ;
}
}
case BACKUP_DATA:
case BACKUP_ALTERNATE_DATA:
{
LARGE_INTEGER liCurPos;
LARGE_INTEGER liNewPos;
HANDLE hf;
// set up the correct handle to seek with
if (pbuc->head.dwStreamId == BACKUP_DATA) {
hf = hFile;
}
else {
hf = pbuc->hAlternate;
}
// first, let's get the current position
liCurPos.HighPart = 0;
liCurPos.LowPart = SetFilePointer(
hf,
0,
&liCurPos.HighPart,
FILE_CURRENT);
// Now seek the requested number of bytes
liNewPos.HighPart = licbRequest.HighPart;
liNewPos.LowPart = SetFilePointer(
hf,
licbRequest.LowPart,
&liNewPos.HighPart,
FILE_CURRENT);
// Assume that we seek the requested amount because if we do not,
// subsequent reads will fail and the caller will never be able
// to read to the next stream.
break;
}
default:
break;
}
if (dwHighBytesToSeek != (DWORD) licbRequest.HighPart ||
dwLowBytesToSeek != licbRequest.LowPart) {
fSuccess = FALSE;
}
licbRequest.QuadPart += sparse_bytes.QuadPart ;
pbuc->liStreamOffset += licbRequest.QuadPart ;
*lpdwLowBytesSeeked = licbRequest.LowPart;
*lpdwHighBytesSeeked = licbRequest.HighPart;
BackupTestRestartStream(pbuc);
if (!fSuccess) {
SetLastError(ERROR_SEEK);
}
return(fSuccess);
}
BOOL
BackupWriteHeader(BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif, DWORD cbHeader)
/*++
Routine Description:
This is an internal routine that fills our internal backup header
from the user's data.
Arguments:
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
cbHeader - size of header to fill
Return Value:
None.
--*/
{
//
// Determine how much data we can transfer into our header.
//
DWORD cbrequest =
(DWORD) min( pbif->cbRequest, cbHeader - pbuc->liStreamOffset );
//
// Copy from user buffer into header
//
if ( pbuc->liStreamOffset+cbrequest > CWCMAX_STREAMNAME + CB_NAMELESSHEADER ) {
return FALSE ;
}
RtlCopyMemory(
(CHAR *) &pbuc->head + pbuc->liStreamOffset,
pbif->pIoBuffer,
cbrequest);
//
// Update transfer statistics
//
ReportTransfer(pbuc, pbif, cbrequest);
//
// If we've filled up the header, mark the header as complete
// even though we might need more if names are present
//
if (pbuc->liStreamOffset == cbHeader) {
pbuc->cbHeader = cbHeader;
}
return TRUE ;
}
typedef enum {
BRB_FAIL,
BRB_DONE,
BRB_MORE,
} BUFFERSTATUS;
BUFFERSTATUS
BackupWriteBuffer(BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that fills our internal buffer
from the user's data.
Arguments:
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
BRB_FAIL if an error occurred (out of memory)
BRB_DONE if buffer is full or was successfully filled
BRB_MORE if buffer is partially full
--*/
{
DWORD cbrequest;
//
// If we're starting out on the buffer, we make sure
// we have a buffer to contain all of the data since
// the Nt calls we'll use must have all the data
// present
//
if (pbuc->fStreamStart) {
pbuc->fStreamStart = FALSE;
if (pbuc->DataBuffer.BufferSize < pbuc->head.Size.QuadPart &&
!GrowBuffer( &pbuc->DataBuffer, pbuc->head.Size.LowPart )) {
return(BRB_FAIL);
}
}
//
// Determine how much data from the user buffer is
// needed to fill our buffer
//
cbrequest = ComputeRequestSize( pbuc, pbif->cbRequest );
//
// Fill in the next portion of the buffer
//
RtlCopyMemory(
pbuc->DataBuffer.Buffer + pbuc->liStreamOffset - pbuc->cbHeader,
pbif->pIoBuffer,
cbrequest);
//
// Update transfer statistics
//
ReportTransfer(pbuc, pbif, cbrequest);
//
// If we've entirely filled the buffer, let our caller know
//
return ComputeRemainingSize( pbuc ) == 0 ? BRB_DONE : BRB_MORE;
}
BOOL
BackupWriteSparse(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that writes sparse block of stream data from
the user's buffer into the output handle. The BACKUPCONTEXT contains
the total length of data to be output.
Arguments:
hFile - output file handle
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if data was successfully written, FALSE otherwise.
--*/
{
LARGE_INTEGER licbFile ;
DWORD cbrequest;
DWORD cbtransferred;
BOOL fSuccess;
if ( pbuc->fSparseBlockStart ) {
RtlCopyMemory( &pbuc->cbSparseOffset, pbuc->head.cStreamName, sizeof( LARGE_INTEGER ) ) ;
licbFile = pbuc->cbSparseOffset;
licbFile.LowPart = SetFilePointer( pbuc->fSparseHandAlt?pbuc->hAlternate:hFile,
licbFile.LowPart,
&licbFile.HighPart,
FILE_BEGIN );
if ( licbFile.QuadPart != pbuc->cbSparseOffset.QuadPart ) {
return FALSE ;
}
if ( pbuc->head.Size.QuadPart == sizeof( LARGE_INTEGER ) ) {
SetEndOfFile(pbuc->fSparseHandAlt?pbuc->hAlternate:hFile) ;
}
pbuc->fSparseBlockStart = FALSE ;
}
//
// Determine how much data from the user buffer is
// needed to be written into the stream and perform
// the transfer.
//
cbrequest = ComputeRequestSize(pbuc, pbif->cbRequest);
fSuccess = WriteFile(
pbuc->fSparseHandAlt?pbuc->hAlternate:hFile,
pbif->pIoBuffer,
cbrequest,
&cbtransferred,
NULL);
//
// Update transfer statistics
//
ReportTransfer(pbuc, pbif, cbtransferred);
return(fSuccess);
return TRUE ;
}
BOOL
BackupWriteStream(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that writes stream data from the user's
buffer into the output handle. The BACKUPCONTEXT contains the total
length of data to be output.
Arguments:
hFile - output file handle
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if data was successfully written, FALSE otherwise.
--*/
{
DWORD cbrequest;
DWORD cbtransferred;
BOOL fSuccess;
IO_STATUS_BLOCK iosb;
if ( pbuc->fStreamStart ) {
if ( pbuc->head.dwStreamAttributes & STREAM_SPARSE_ATTRIBUTE ) {
// if it was sparse when be backed it up make is sparse again.
NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_SET_SPARSE ,
NULL,
0,
NULL,
0 ) ;
} else {
LARGE_INTEGER end_of_file ;
end_of_file.QuadPart = pbuc->head.Size.QuadPart ;
SetFilePointer( hFile,
end_of_file.LowPart,
&end_of_file.HighPart,
FILE_BEGIN );
SetEndOfFile(hFile) ;
end_of_file.QuadPart = 0 ;
SetFilePointer( hFile,
end_of_file.LowPart,
&end_of_file.HighPart,
FILE_BEGIN );
}
pbuc->fStreamStart = FALSE;
}
//
// Determine how much data from the user buffer is
// needed to be written into the stream and perform
// the transfer.
//
cbrequest = ComputeRequestSize(pbuc, pbif->cbRequest);
fSuccess = WriteFile(
hFile,
pbif->pIoBuffer,
cbrequest,
&cbtransferred,
NULL);
//
// Update transfer statistics
//
ReportTransfer(pbuc, pbif, cbtransferred);
return(fSuccess);
}
BOOL
BackupWriteAlternateData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that overwrites an alternate data stream with
data from the user's buffer.
Arguments:
hFile - handle to the file itself. This is not the handle to the stream
being overwritten.
pbuc - CONTEXT of call. This contains the name of the stream.
pbif - IOCONTEXT of call
Return Value:
TRUE if data was successfully written, FALSE otherwise.
--*/
{
//
// If we are just starting out on this stream then attempt to
// overwrite the new stream.
//
if (pbuc->fStreamStart) {
NTSTATUS Status;
UNICODE_STRING strName;
OBJECT_ATTRIBUTES oa;
IO_STATUS_BLOCK iosb;
DWORD reparse_flg = 0 ;
strName.Length = (USHORT) pbuc->head.dwStreamNameSize;
strName.MaximumLength = strName.Length;
strName.Buffer = pbuc->head.cStreamName;
if (pbuc->hAlternate != INVALID_HANDLE_VALUE) {
CloseHandle(pbuc->hAlternate);
pbuc->hAlternate = INVALID_HANDLE_VALUE;
pbuc->fSparseHandAlt = FALSE ;
}
if (pbuc->fAttribs & FILE_ATTRIBUTE_REPARSE_POINT ) {
reparse_flg = FILE_OPEN_REPARSE_POINT ;
}
InitializeObjectAttributes(
&oa,
&strName,
OBJ_CASE_INSENSITIVE,
hFile,
NULL);
Status = NtCreateFile(
&pbuc->hAlternate,
FILE_WRITE_DATA | SYNCHRONIZE,
&oa,
&iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE|FILE_SHARE_DELETE,
FILE_OVERWRITE_IF,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | reparse_flg,
NULL,
0L);
//
// If we failed, map the error, record the failure, and return failure.
//
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
pbuc->fAccessError = TRUE;
return FALSE;
}
if ( pbuc->head.dwStreamAttributes & STREAM_SPARSE_ATTRIBUTE ) {
pbuc->fSparseHandAlt = TRUE ;
// if it was sparse when be backed it up make is sparse again.
NtFsControlFile( pbuc->hAlternate,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_SET_SPARSE ,
NULL,
0,
NULL,
0 ) ;
}
// don't reset stream start because WriteStream will do it.
}
//
// If we have no handle for the transfer, record this failure
// and return failure.
//
if (pbuc->hAlternate == INVALID_HANDLE_VALUE) {
pbuc->fAccessError = TRUE;
return FALSE;
}
//
// Let the normal stream copy perform the transfer
//
return BackupWriteStream( pbuc->hAlternate, pbuc, pbif );
}
BOOL
BackupWriteEaData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that writes EA data on the file from
the user's buffer
Arguments:
hFile - handle to output file
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if EA data was successfully written, FALSE otherwise.
--*/
{
NTSTATUS Status;
IO_STATUS_BLOCK iosb;
//
// Attempt to fill up the buffer from the input.
//
switch (BackupWriteBuffer( pbuc, pbif )) {
default:
case BRB_FAIL:
return FALSE;
case BRB_MORE:
return TRUE;
case BRB_DONE:
break;
}
//
// The buffer is now completely filled with our EA data. Set the
// EA data on the file.
//
Status = NtSetEaFile(
hFile,
&iosb,
pbuc->DataBuffer.Buffer,
pbuc->head.Size.LowPart );
//
// If we failed, map the error and return failure
//
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
return FALSE;
}
return TRUE;
}
BOOL
BackupWriteReparseData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that writes Reparse data on the file from
the user's buffer
Arguments:
hFile - handle to output file
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if EA data was successfully written, FALSE otherwise.
--*/
{
NTSTATUS Status;
IO_STATUS_BLOCK iosb;
DWORD *rp_tag_ptr ;
//
// Attempt to fill up the buffer from the input.
//
switch (BackupWriteBuffer( pbuc, pbif )) {
default:
case BRB_FAIL:
return FALSE;
case BRB_MORE:
return TRUE;
case BRB_DONE:
break;
}
//
// The buffer is now completely filled with our Reparse data. Set the
// Reparse data on the file.
//
rp_tag_ptr = (DWORD *)(pbuc->DataBuffer.Buffer) ;
pbuc->fAttribs |= FILE_ATTRIBUTE_REPARSE_POINT ;
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_SET_REPARSE_POINT,
pbuc->DataBuffer.Buffer,
pbuc->head.Size.LowPart,
NULL,
0 ) ;
//
// If we failed, map the error and return failure
//
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
return FALSE;
}
return TRUE;
}
BOOL
BackupWriteObjectId(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that writes the Object IDa on the file from
the user's buffer. Birth ids are made reborn. i.e. the volume id component
of the birth id is changed to the current volume's id, and the object id
component of the birth id is changed to the current object id.
Arguments:
hFile - handle to output file
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if Object ID was successfully written, FALSE otherwise.
--*/
{
IO_STATUS_BLOCK iosb;
NTSTATUS Status ;
FILE_FS_OBJECTID_INFORMATION fsobOID;
GUID guidZero;
//
// Attempt to fill up the buffer from the input.
//
switch (BackupWriteBuffer( pbuc, pbif )) {
default:
case BRB_FAIL:
return FALSE;
case BRB_MORE:
return TRUE;
case BRB_DONE:
break;
}
//
// Zero out the birth ID (the extended 48 bytes)
//
memset(&pbuc->DataBuffer.Buffer[sizeof(GUID)], 0, 3*sizeof(GUID));
//
// Set the ID on the file.
//
Status = NtFsControlFile( hFile,
NULL, // overlapped event handle
NULL, // Apc routine
NULL, // overlapped structure
&iosb,
FSCTL_SET_OBJECT_ID,
pbuc->DataBuffer.Buffer,
pbuc->head.Size.LowPart,
NULL,
0);
//
// Ignore errors
//
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
}
return( TRUE );
}
BOOL
BackupWriteSecurityData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that sets security information on the
file from data in the user's buffer.
Arguments:
hFile - handle to output file
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if security was successfully written, FALSE otherwise.
--*/
{
NTSTATUS Status;
SECURITY_INFORMATION si;
//
// Attempt to fill up the buffer from the input.
//
switch (BackupWriteBuffer(pbuc, pbif)) {
default:
case BRB_FAIL:
return FALSE;
case BRB_MORE:
return TRUE;
case BRB_DONE:
break;
}
//
// The buffer is now completely filled with our security data. If we
// are to ignore it, then return success
//
if (!pbif->fProcessSecurity) {
return TRUE;
}
//
// Find out what security information is present so we know what to
// set.
si = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION;
if (((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control & SE_DACL_PRESENT) {
si |= DACL_SECURITY_INFORMATION;
}
if (((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control & SE_SACL_PRESENT) {
si |= SACL_SECURITY_INFORMATION;
}
//
// If the security descriptor has AUTO_INHERITED set, set the appropriate REQ bits.
//
if (((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control & SE_DACL_AUTO_INHERITED) {
((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control |= SE_DACL_AUTO_INHERIT_REQ;
}
if (((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control & SE_SACL_AUTO_INHERITED) {
((PISECURITY_DESCRIPTOR) pbuc->DataBuffer.Buffer)->Control |= SE_SACL_AUTO_INHERIT_REQ;
}
Status = NtSetSecurityObject( hFile, si, pbuc->DataBuffer.Buffer );
if (!NT_SUCCESS( Status )) {
NTSTATUS Status2;
//
// If that didn't work, the caller is probably not running as Backup
// Operator, so we can't set the owner and group. Keep the current
// status code, and attempt to set the DACL and SACL while ignoring
// failures.
//
if (si & SACL_SECURITY_INFORMATION) {
NtSetSecurityObject(
hFile,
SACL_SECURITY_INFORMATION,
pbuc->DataBuffer.Buffer );
}
if (si & DACL_SECURITY_INFORMATION) {
Status = NtSetSecurityObject(
hFile,
DACL_SECURITY_INFORMATION,
pbuc->DataBuffer.Buffer);
}
Status2 = NtSetSecurityObject(
hFile,
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
pbuc->DataBuffer.Buffer);
if (NT_SUCCESS(Status)) {
Status = Status2;
}
}
if (!NT_SUCCESS(Status)) {
BaseSetLastNTError(Status);
return FALSE;
}
return TRUE;
}
BOOL
BackupWriteLinkData(HANDLE hFile, BACKUPCONTEXT *pbuc, BACKUPIOFRAME *pbif)
/*++
Routine Description:
This is an internal routine that establishes links based on the
user's data.
Arguments:
hFile - handle of file being restored
pbuc - CONTEXT of call
pbif - IOCONTEXT of call
Return Value:
TRUE if link was successfully established, FALSE otherwise.
--*/
{
FILE_LINK_INFORMATION *pfli;
WCHAR *pwc;
WCHAR *pwcSlash;
INT cbName;
INT cSlash;
WCHAR wcSave;
BOOL fSuccess;
//
// Attempt to fill up the buffer from the input.
//
switch (BackupWriteBuffer(pbuc, pbif)) {
default:
case BRB_FAIL:
return FALSE;
case BRB_MORE:
return TRUE;
case BRB_DONE:
break;
}
//
// The buffer is now completely filled with our link data.
// Find the last component of the name.
//
cSlash = 0;
pwcSlash = NULL;
pwc = (WCHAR *) pbuc->DataBuffer.Buffer;
cbName = sizeof(WCHAR);
while (*pwc != L'\0') {
if (*pwc == L'\\') {
pwcSlash = pwc;
cSlash++;
cbName = 0;
}
pwc++;
cbName += sizeof(WCHAR);
}
pfli = BackupAlloc( sizeof(*pfli) + cbName );
if (pfli == NULL) {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
RtlCopyMemory( pfli->FileName, pwcSlash + 1, cbName );
pfli->FileNameLength = cbName - sizeof(WCHAR);
if (cSlash > 1) {
wcSave = L'\\';
}
else {
wcSave = *pwcSlash++;
}
*pwcSlash = L'\0';
//
// Open the parent of the link target
//
pfli->RootDirectory = CreateFileW(
(WCHAR *) pbuc->DataBuffer.Buffer,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL| FILE_FLAG_BACKUP_SEMANTICS,
NULL );
*pwcSlash = wcSave;
pfli->ReplaceIfExists = TRUE;
fSuccess = TRUE;
if (pfli->RootDirectory == INVALID_HANDLE_VALUE) {
SetLastError( ERROR_FILE_NOT_FOUND );
fSuccess = FALSE;
}
else {
NTSTATUS Status;
IO_STATUS_BLOCK iosb;
Status = NtSetInformationFile(
hFile,
&iosb,
pfli,
sizeof(*pfli) + cbName,
FileLinkInformation );
CloseHandle( pfli->RootDirectory );
pfli->RootDirectory = INVALID_HANDLE_VALUE;
if (!NT_SUCCESS( Status )) {
BaseSetLastNTError( Status );
fSuccess = FALSE;
} else {
if (iosb.Information == FILE_OVERWRITTEN) {
SetLastError( ERROR_ALREADY_EXISTS );
} else {
SetLastError( 0 );
}
}
}
BackupFree( pfli );
return fSuccess;
}
// Routine Description:
//
// Data can be written to a file using BackupWrite.
//
// This API is used to Restore data to an object. After the
// write completes, the file pointer is adjusted by the number of bytes
// actually written.
//
// Unlike DOS, a NumberOfBytesToWrite value of zero does not truncate
// or extend the file. If this function is required, SetEndOfFile
// should be used.
//
// Arguments:
//
// hFile - Supplies an open handle to a file that is to be written. The
// file handle must have been created with GENERIC_WRITE access to
// the file.
//
// lpBuffer - Supplies the address of the data that is to be written to
// the file.
//
// nNumberOfBytesToWrite - Supplies the number of bytes to write to the
// file. Unlike DOS, a value of zero is interpreted a null write.
//
// lpNumberOfBytesWritten - Returns the number of bytes written by this
// call. Before doing any work or error processing, the API sets this
// to zero.
//
// bAbort - If true, then all resources associated with the context will
// be released.
//
// bProcessSecurity - If TRUE, then the NTFS ACL data will be written.
// If FALSE, then the ACL stream will be ignored.
//
// lpContext - Points to a buffer pointer setup and maintained by
// BackupRead.
//
//
// Return Value:
//
// TRUE - The operation was a success.
//
// FALSE - The operation failed. Extended error status is
// available using GetLastError.
BOOL WINAPI
BackupWrite(
HANDLE hFile,
LPBYTE lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
BOOL bAbort,
BOOL bProcessSecurity,
LPVOID *lpContext)
{
BACKUPCONTEXT *pbuc;
BACKUPIOFRAME bif;
BOOL fSuccess = FALSE;
pbuc = *lpContext;
bif.pIoBuffer = lpBuffer;
bif.cbRequest = nNumberOfBytesToWrite;
bif.pcbTransferred = lpNumberOfBytesWritten;
bif.fProcessSecurity = (BOOLEAN)bProcessSecurity;
//
// Allocate our Context Control Block on first call.
//
if (bAbort) {
if (pbuc != NULL) {
FreeContext(lpContext);
}
return TRUE;
}
*bif.pcbTransferred = 0;
if (pbuc == INVALID_HANDLE_VALUE) {
return TRUE;
}
// Allocate our Context Control Block on first call.
if (pbuc == NULL) {
pbuc = AllocContext(0); // No initial buffer
//
// If we have no space then return failure
//
if (pbuc == NULL) {
return FALSE;
}
}
*lpContext = pbuc;
do {
DWORD cbrequest;
LONGLONG licbRemain;
//
// If we do not have a complete header, go
// fill it in.
//
if (pbuc->cbHeader == 0) {
pbuc->fMultiStreamType = TRUE ; //restore does not auto inc stream index.
pbuc->fStreamStart = TRUE ;
BackupWriteHeader(pbuc, &bif, CB_NAMELESSHEADER) ;
}
//
// If no more data, then exit
//
if (bif.cbRequest == 0) {
return TRUE;
}
//
// If a stream name was expected, go read it in
//
if (pbuc->cbHeader == CB_NAMELESSHEADER &&
pbuc->head.dwStreamNameSize != 0) {
if ( !BackupWriteHeader(
pbuc,
&bif,
pbuc->cbHeader + pbuc->head.dwStreamNameSize) )
{
SetLastError( ERROR_INVALID_DATA );
return FALSE ;
}
//
// If no more data then exit
//
if (bif.cbRequest == 0) {
return TRUE;
}
}
if ( ( pbuc->cbHeader == CB_NAMELESSHEADER ) &&
( pbuc->head.dwStreamId == BACKUP_SPARSE_BLOCK ) ) {
BackupWriteHeader(
pbuc,
&bif,
pbuc->cbHeader + sizeof(LARGE_INTEGER) );
//
// If no more data then exit
//
if (bif.cbRequest == 0) {
if ( pbuc->cbHeader == CB_NAMELESSHEADER ) {
return TRUE;
}
}
}
//
// Determine amount of data remaining in user buffer
// that can be transferred as part of this section
// of the backup stream
//
cbrequest = ComputeRequestSize(pbuc, bif.cbRequest);
//
// Determine total amount of data left in this section
// of backup stream.
//
licbRemain = ComputeRemainingSize( pbuc );
//
// If we had an error in the transfer and we're done
// doing the transfer then pretend that we successfully
// completed the section.
//
if (pbuc->fAccessError && licbRemain == 0) {
ReportTransfer(pbuc, &bif, cbrequest);
continue;
}
//
// Begin or continue the transfer of data. We assume that there
// are no errors
//
pbuc->fAccessError = FALSE;
switch (pbuc->head.dwStreamId) {
case BACKUP_SPARSE_BLOCK :
fSuccess = BackupWriteSparse( hFile, pbuc, &bif ) ;
break ;
case BACKUP_DATA:
fSuccess = BackupWriteStream( hFile, pbuc, &bif );
break;
case BACKUP_ALTERNATE_DATA:
fSuccess = BackupWriteAlternateData( hFile, pbuc, &bif );
break;
case BACKUP_EA_DATA:
fSuccess = BackupWriteEaData( hFile, pbuc, &bif );
break;
case BACKUP_OBJECT_ID:
fSuccess = BackupWriteObjectId( hFile, pbuc, &bif );
break;
case BACKUP_REPARSE_DATA:
fSuccess = BackupWriteReparseData( hFile, pbuc, &bif );
break;
case BACKUP_SECURITY_DATA:
fSuccess = BackupWriteSecurityData( hFile, pbuc, &bif );
break;
case BACKUP_LINK:
fSuccess = BackupWriteLinkData( hFile, pbuc, &bif );
break;
default:
SetLastError(ERROR_INVALID_DATA);
fSuccess = FALSE;
break;
}
BackupTestRestartStream(pbuc);
} while (fSuccess && bif.cbRequest != 0);
if (fSuccess && *bif.pcbTransferred == 0) {
FreeContext(lpContext);
}
return(fSuccess);
}