mirror of https://github.com/lianthony/NT4.0
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.
3554 lines
95 KiB
3554 lines
95 KiB
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
file_dir.c
|
|
|
|
Abstract:
|
|
|
|
Routines to deal with directory and file structure.
|
|
|
|
Author:
|
|
|
|
Ted Miller (tedm) 6-Jan-1996
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
/*
|
|
|
|
Description of on-disk format for file/dir snapshot
|
|
---------------------------------------------------
|
|
|
|
The output of the directory tree snapshot routine
|
|
is an on-disk file.
|
|
|
|
+-------------------------------------------------------+
|
|
| |
|
|
| DIR_AND_FILE_HEADER structure: |
|
|
| |
|
|
| HeaderSize (DWORD) - sizeof(DIR_AND_FILE_HEADER) |
|
|
| plus bytes occupied by string immediately |
|
|
| following in the file (OriginalRootPath) |
|
|
| |
|
|
| TotalSize (DWORD) - total size of the file |
|
|
| |
|
|
| DirCount (DWORD) - number of directories in this |
|
|
| file |
|
|
| |
|
|
| OriginalRootPath (PCWSTR) - the contents of this |
|
|
| field are random in the on-disk representation |
|
|
| of this structure. The actual string follows |
|
|
| immediately in the file. |
|
|
| |
|
|
+-------------------------------------------------------+
|
|
| |
|
|
| Root path spec that this is a snapshot of. |
|
|
| (variable-length nul-terminated unicode string) |
|
|
| (OriginalRootPath member of DIR_AND_FILE_HEADER.) |
|
|
| |
|
|
+-------------------------------------------------------+
|
|
| |
|
|
| 0th directory DIR_INFO strcture -- fixed length |
|
|
| |
|
|
| 0th directory FILE_INFO array -- variable length, |
|
|
| the ElementSize and Used fields in the DIR_INFO |
|
|
| MY_ARRAY structure are sufficient to calculate |
|
|
| the on-disk size |
|
|
| |
|
|
| 0th directory string block -- variable length, |
|
|
| the Used field of the DIR_INFO STRINGBLOCK is |
|
|
| sufficient to calculate the on-disk size |
|
|
| |
|
|
+-------------------------------------------------------+
|
|
| |
|
|
| 1st directory DIR_INFO strcture |
|
|
| 1st directory FILE_INFO array |
|
|
| 1st directory string block |
|
|
| |
|
|
+-------------------------------------------------------+
|
|
.
|
|
.
|
|
.
|
|
etc.
|
|
|
|
Note: the directories are in sorted order -- ascending by
|
|
Win32 path (lexical string comparison).
|
|
|
|
The master drive snapshot routine builds up a set of these
|
|
files, which are then appended together and appended to
|
|
a DIR_AND_FILE_SNAP_HEADER structure.
|
|
|
|
*/
|
|
|
|
typedef struct _DIR_AND_FILE_SNAP_HEADER {
|
|
//
|
|
// Total size in bytes of the entire set
|
|
//
|
|
DWORD TotalSize;
|
|
//
|
|
// Bitmask indicating which drives have full snapshots
|
|
// in this dir-and-file set.
|
|
//
|
|
UINT DriveMap;
|
|
//
|
|
// Count of directory sets
|
|
//
|
|
UINT DirTreeCount;
|
|
|
|
} DIR_AND_FILE_SNAP_HEADER, *PDIR_AND_FILE_SNAP_HEADER;
|
|
|
|
|
|
//
|
|
// Define structure used as a header for a set of DIR_INFO
|
|
// structures.
|
|
//
|
|
typedef struct _DIR_AND_FILE_HEADER {
|
|
//
|
|
// Size in bytes of this header, including this struct
|
|
// and the RootPath, which immediately follows it in the file.
|
|
//
|
|
DWORD HeaderSize;
|
|
//
|
|
// Total size in bytes occupied by the entire set and header
|
|
//
|
|
DWORD TotalSize;
|
|
//
|
|
// Number of DIR_INFO structures in the set
|
|
//
|
|
DWORD DirCount;
|
|
//
|
|
// Root path of tree this snapshot is of.
|
|
// In memory, this is a pointer to some heap.
|
|
// In the on-disk representation, the OriginalRootPath is stored
|
|
// immediately following this structure and this field is random.
|
|
//
|
|
PCWSTR OriginalRootPath;
|
|
|
|
} DIR_AND_FILE_HEADER, *PDIR_AND_FILE_HEADER;
|
|
|
|
//
|
|
// Define structure that represents a file.
|
|
//
|
|
typedef struct _FILE_INFO {
|
|
|
|
DWORDLONG FileSize;
|
|
DWORDLONG Version;
|
|
|
|
FILETIME CreateTime;
|
|
FILETIME WriteTime;
|
|
|
|
DWORD Attributes;
|
|
|
|
union {
|
|
PWSTR FileName;
|
|
LONG FileNameId;
|
|
};
|
|
|
|
LANGID Language;
|
|
|
|
} FILE_INFO, *PFILE_INFO;
|
|
|
|
|
|
//
|
|
// Define structure that represents a directoryfull of files.
|
|
//
|
|
typedef struct _DIR_INFO {
|
|
//
|
|
// Win32 path of directory. This is relative to the OriginalRootPath
|
|
// in the DIR_AND_FILE_HEADER.
|
|
//
|
|
union {
|
|
PCWSTR DirectoryPath;
|
|
LONG DirectoryPathId; // in string block
|
|
};
|
|
|
|
//
|
|
// Array of FILE_INFO structures
|
|
//
|
|
MY_ARRAY Files;
|
|
//
|
|
// Block of data containing all strings in this struct
|
|
// and FILE_INFO structs
|
|
//
|
|
STRINGBLOCK StringBlock;
|
|
|
|
} DIR_INFO, *PDIR_INFO;
|
|
|
|
//
|
|
// Define structure that represents the subdirs in a directory.
|
|
//
|
|
typedef struct _SUBDIR_LIST {
|
|
//
|
|
// Array of LONG string ids
|
|
//
|
|
MY_ARRAY Dirs;
|
|
//
|
|
// String block
|
|
//
|
|
STRINGBLOCK StringBlock;
|
|
|
|
} SUBDIR_LIST,*PSUBDIR_LIST;
|
|
|
|
|
|
//
|
|
// Define structure that describes a set of directories that
|
|
// have changed.
|
|
//
|
|
typedef struct _DIR_AND_FILE_DIFF_HEADER {
|
|
//
|
|
// Currently unused.
|
|
//
|
|
DWORD Unused;
|
|
//
|
|
// Total number of directories described in the file diff
|
|
//
|
|
UINT DirCount;
|
|
//
|
|
// Total size of the file diff, including the file data area,
|
|
// if present.
|
|
//
|
|
DWORD TotalSize;
|
|
//
|
|
// If the file data area is present, this is the offset to it,
|
|
// from the start of this structure.
|
|
//
|
|
DWORD FileDataOffset;
|
|
|
|
} DIR_AND_FILE_DIFF_HEADER, *PDIR_AND_FILE_DIFF_HEADER;
|
|
|
|
//
|
|
// Define structure that describes a directory's worth of
|
|
// file differences.
|
|
//
|
|
typedef struct _DIR_DIFF {
|
|
//
|
|
// Flag indicating whether this directory was deleted.
|
|
//
|
|
BOOL Deleted;
|
|
//
|
|
// Win32 path or id of path in the string block
|
|
//
|
|
union {
|
|
PCWSTR Path;
|
|
LONG PathId;
|
|
};
|
|
//
|
|
// Win32 path or id of path (SFN) in the string block
|
|
//
|
|
union {
|
|
PCWSTR PathSFN;
|
|
LONG PathSFNId;
|
|
};
|
|
//
|
|
// List of files in this directory that have changed
|
|
// (includes count). If the Data member is uninitialized,
|
|
// then the directory is to be removed.
|
|
//
|
|
MY_ARRAY Files;
|
|
//
|
|
// String data for this directory and files in it.
|
|
//
|
|
STRINGBLOCK StringBlock;
|
|
|
|
} DIR_DIFF, *PDIR_DIFF;
|
|
|
|
|
|
//
|
|
// Define structure that describes a file that changed.
|
|
//
|
|
typedef struct _FILE_DIFF {
|
|
//
|
|
// Flag indicating whether this file was deleted.
|
|
//
|
|
BOOL Deleted;
|
|
//
|
|
// Attributes this file should have when created.
|
|
//
|
|
DWORD Attributes;
|
|
//
|
|
// File name or id of name in string block
|
|
//
|
|
union {
|
|
PCWSTR Name;
|
|
LONG NameId;
|
|
};
|
|
//
|
|
// Filename (SFN)
|
|
//
|
|
WCHAR ShortName[13];
|
|
//
|
|
// If the file data is in the diff file, these members
|
|
// describe how to get at it.
|
|
//
|
|
DWORD OffsetToData;
|
|
DWORD DataSize;
|
|
|
|
} FILE_DIFF, *PFILE_DIFF;
|
|
|
|
DWORD
|
|
ApplyFileChanges(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN PDIR_DIFF DirDiff,
|
|
IN HANDLE FileDataHandle, OPTIONAL
|
|
IN DWORD FileDataOffset, OPTIONAL
|
|
IN HANDLE Dump, OPTIONAL
|
|
IN PINFFILEGEN InfGenContext OPTIONAL
|
|
);
|
|
|
|
VOID
|
|
FreeDirectoryInfoStruct(
|
|
IN OUT PDIR_INFO DirContents
|
|
);
|
|
|
|
VOID
|
|
FreeSubdirListStruct(
|
|
IN OUT PSUBDIR_LIST SubdirList
|
|
);
|
|
|
|
int
|
|
_CRTAPI2
|
|
CompareFileInfo(
|
|
const void *p1,
|
|
const void *p2
|
|
);
|
|
|
|
BOOL
|
|
RemapToDefaultUser(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN UINT Flags,
|
|
IN OUT PWSTR Path
|
|
);
|
|
|
|
#define REMAP_USESFN 0x00000001
|
|
#define REMAP_USEBAK 0x00000002
|
|
|
|
BOOL
|
|
RemapToCommonUser(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN OUT PWSTR Path
|
|
);
|
|
|
|
DWORD
|
|
ReadDirectoryFromDisk(
|
|
IN HWND StatusLogWindow,
|
|
IN PCWSTR OriginalRoot,
|
|
IN PCWSTR Directory,
|
|
OUT PDIR_INFO *Contents,
|
|
OUT PSUBDIR_LIST *Subdirs
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Build a list of all files in a directory, NOT including
|
|
other directories, and gather information about the files.
|
|
|
|
Arguments:
|
|
|
|
OriginalRoot - supplies the original root directory for the
|
|
current shapsnot. This is used to place the rottect relative path
|
|
in the DIR_INFO structure.
|
|
|
|
Directory - supplies the win32 path of the directory to scan.
|
|
|
|
Contents - receives a pointer to a directory contents structure.
|
|
Use FreeIteratedDirectory to free this structure when done.
|
|
|
|
Subdirs - receives information about subdirectories.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome. If NO_ERROR, then
|
|
Contents is filled in.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDIR_INFO contents;
|
|
DWORD d;
|
|
WIN32_FIND_DATA FindData;
|
|
HANDLE FindHandle;
|
|
WCHAR Pattern[MAX_PATH];
|
|
FILE_INFO FileInfo;
|
|
PSUBDIR_LIST subdirs;
|
|
LONG Id;
|
|
unsigned u;
|
|
|
|
if(Cancel) {
|
|
d = ERROR_CANCELLED;
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Make sure the directory exists.
|
|
// This test fails for the root so special-case that.
|
|
//
|
|
if(!(Directory[0] && (Directory[1] == L':') && (Directory[2] == L'\\'))
|
|
&& !FileExists(Directory,NULL))
|
|
{
|
|
d = ERROR_PATH_NOT_FOUND;
|
|
goto c0;
|
|
}
|
|
|
|
contents = _MyMalloc(sizeof(DIR_INFO));
|
|
if(!contents) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c0;
|
|
}
|
|
ZeroMemory(contents,sizeof(DIR_INFO));
|
|
|
|
//
|
|
// Initialize the string block for this directory
|
|
//
|
|
if(!InitStringBlock(&contents->StringBlock)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Figure out which part of the path gets added to the string block.
|
|
// The path has to be relative to the original root for this snapshot.
|
|
//
|
|
u = lstrlen(OriginalRoot);
|
|
if(!_wcsnicmp(OriginalRoot,Directory,u)) {
|
|
if(Directory[u] == L'\\') {
|
|
u++;
|
|
}
|
|
} else {
|
|
//
|
|
// This shouldn't happen if the caller passed us valid params.
|
|
//
|
|
u = 0;
|
|
}
|
|
|
|
contents->DirectoryPathId = AddToStringBlock(&contents->StringBlock,Directory+u);
|
|
if(contents->DirectoryPathId == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
if(!INIT_ARRAY(contents->Files,FILE_INFO,0,20)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Initialize the string block and array for the subdir list.
|
|
//
|
|
subdirs = _MyMalloc(sizeof(SUBDIR_LIST));
|
|
if(!subdirs) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
if(!InitStringBlock(&subdirs->StringBlock)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
if(!INIT_ARRAY(subdirs->Dirs,LONG,0,20)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
//
|
|
// Start looking for files/directories
|
|
//
|
|
lstrcpyn(Pattern,Directory,MAX_PATH);
|
|
ConcatenatePaths(Pattern,L"*",MAX_PATH,NULL);
|
|
|
|
d = NO_ERROR;
|
|
|
|
FindHandle = FindFirstFile(Pattern,&FindData);
|
|
if(FindHandle == INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Empty directory. This can happen at the root,
|
|
// which doesn't necessarily have even . or ..
|
|
//
|
|
goto c4;
|
|
}
|
|
|
|
do {
|
|
if(Cancel) {
|
|
d = ERROR_CANCELLED;
|
|
goto c3;
|
|
}
|
|
|
|
//
|
|
// Handle directories and files differently
|
|
//
|
|
if(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
//
|
|
// Skip ./..
|
|
//
|
|
if(!(((FindData.cFileName[0] == L'.') && !FindData.cFileName[1])
|
|
|| ((FindData.cFileName[0] == L'.') && (FindData.cFileName[1] == L'.') && !FindData.cFileName[2]))) {
|
|
|
|
//
|
|
// Add this subdir to the subdir list.
|
|
//
|
|
Id = AddToStringBlock(&subdirs->StringBlock,FindData.cFileName);
|
|
if(Id == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
if(!ADD_TO_ARRAY(&subdirs->Dirs,Id)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Form the full filename and see if we are supposed to skip it.
|
|
//
|
|
lstrcpy(Pattern,Directory);
|
|
ConcatenatePaths(Pattern,FindData.cFileName,MAX_PATH,NULL);
|
|
|
|
if(IsDirOrFileExcluded(DirAndFileExcludeFile,Pattern)
|
|
|| IsDirOrFileExcluded(DirAndFileExcludeFile,FindData.cFileName)) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SKIPPED_FILE,Pattern);
|
|
} else {
|
|
//
|
|
// See if this is an ini file.
|
|
//
|
|
if(IsIniFile(Pattern)) {
|
|
|
|
if(!QueueIniFile(Pattern)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Store info about the file.
|
|
//
|
|
FileInfo.CreateTime = FindData.ftCreationTime;
|
|
FileInfo.WriteTime = FindData.ftLastWriteTime;
|
|
FileInfo.Attributes = FindData.dwFileAttributes;
|
|
|
|
FileInfo.FileSize = ((DWORDLONG)FindData.nFileSizeHigh << 32)
|
|
+ FindData.nFileSizeLow;
|
|
|
|
//
|
|
// Try to get version data and language id
|
|
//
|
|
if(!GetVersionInfoFromImage(Pattern,&FileInfo.Version,&FileInfo.Language)) {
|
|
|
|
FileInfo.Version = 0;
|
|
FileInfo.Language = 0;
|
|
}
|
|
|
|
FileInfo.FileNameId = AddToStringBlock(
|
|
&contents->StringBlock,
|
|
FindData.cFileName
|
|
);
|
|
|
|
if(FileInfo.FileNameId == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
//
|
|
// Add fileinfo for this file to the array for this directory.
|
|
//
|
|
if(!ADD_TO_ARRAY(&contents->Files,FileInfo)) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while(FindNextFile(FindHandle,&FindData));
|
|
|
|
c4:
|
|
TRIM_ARRAY(&contents->Files);
|
|
TRIM_ARRAY(&subdirs->Dirs);
|
|
|
|
//
|
|
// Sort the array of files by name.
|
|
//
|
|
SortByStrings(
|
|
&contents->StringBlock,
|
|
&contents->Files,
|
|
offsetof(FILE_INFO,FileNameId),
|
|
CompareFileInfo
|
|
);
|
|
|
|
SortByStrings(
|
|
&subdirs->StringBlock,
|
|
&subdirs->Dirs,
|
|
0,
|
|
CompareStringsRoutine
|
|
);
|
|
|
|
if(Cancel) {
|
|
d = ERROR_CANCELLED;
|
|
goto c3;
|
|
}
|
|
|
|
*Contents = contents;
|
|
*Subdirs = subdirs;
|
|
|
|
c3:
|
|
if(FindHandle != INVALID_HANDLE_VALUE) {
|
|
FindClose(FindHandle);
|
|
}
|
|
|
|
c2:
|
|
if(d != NO_ERROR) {
|
|
FreeSubdirListStruct(subdirs);
|
|
}
|
|
c1:
|
|
if(d != NO_ERROR) {
|
|
FreeDirectoryInfoStruct(contents);
|
|
}
|
|
c0:
|
|
return(d);
|
|
}
|
|
|
|
|
|
VOID
|
|
FreeDirectoryInfoStruct(
|
|
IN OUT PDIR_INFO DirContents
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Free a directory contents structure and all resources
|
|
used by and within it.
|
|
|
|
Arguments:
|
|
|
|
DirContents - supplies a pointer to a directory contents
|
|
structure to be freed
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
FREE_ARRAY(&DirContents->Files);
|
|
FreeStringBlock(&DirContents->StringBlock);
|
|
_MyFree(DirContents);
|
|
}
|
|
|
|
|
|
VOID
|
|
FreeSubdirListStruct(
|
|
IN OUT PSUBDIR_LIST SubdirList
|
|
)
|
|
{
|
|
FREE_ARRAY(&SubdirList->Dirs);
|
|
FreeStringBlock(&SubdirList->StringBlock);
|
|
_MyFree(SubdirList);
|
|
}
|
|
|
|
|
|
int
|
|
_CRTAPI2
|
|
CompareFileInfo(
|
|
const void *p1,
|
|
const void *p2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Callback routine passed to the qsort function, which compares 2
|
|
FILE_INFO structures. The comparison is based on the lexical
|
|
value of the filename field of that structure.
|
|
|
|
The comparison is case sensitive.
|
|
|
|
Arguments:
|
|
|
|
p1,p2 - supply pointers to 2 FILE_INFO structures to be compared.
|
|
|
|
Return Value:
|
|
|
|
<0 element1 < element2
|
|
=0 element1 = element2
|
|
>0 element1 > element2
|
|
|
|
--*/
|
|
|
|
{
|
|
return(lstrcmpi(((PFILE_INFO)p1)->FileName,((PFILE_INFO)p2)->FileName));
|
|
}
|
|
|
|
|
|
DWORD
|
|
WriteDirInfoToDisk(
|
|
IN OUT PDIR_AND_FILE_HEADER DirAndFileHeader,
|
|
IN PDIR_INFO DirInfo,
|
|
IN HANDLE OutputFile
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Save a DIR_INFO structure to disk. The on-disk structure
|
|
consists of the DIR_INFO structure, followed by the FILE_INFO
|
|
array data, followed by the string block.
|
|
|
|
The header is assumed to be at the start of the file; the size
|
|
field in that structure is updated by increasing the total size
|
|
value by the amount of data we write in this routine.
|
|
|
|
Arguments:
|
|
|
|
DirAndFileHeader - supplies the header for the current set of
|
|
directories being snapshotted. The size and directory count
|
|
fields in this structure are updated.
|
|
|
|
DirInfo - supplies the directory info structure to be written
|
|
to disk.
|
|
|
|
OutputFile - supplies an open win32 file handle to write to.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD Written;
|
|
BOOL b;
|
|
DWORD rc;
|
|
DWORD Total;
|
|
|
|
//
|
|
// Store the DIR_INFO structure itself.
|
|
//
|
|
b = WriteFile(OutputFile,DirInfo,sizeof(DIR_INFO),&Written,NULL);
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Store the FILE_INFO array data buffer.
|
|
//
|
|
b = WriteFile(
|
|
OutputFile,
|
|
ARRAY_DATA(&DirInfo->Files),
|
|
ARRAY_USED_BYTES(&DirInfo->Files),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Store the string block for this DIR_INFO.
|
|
//
|
|
b = WriteFile(
|
|
OutputFile,
|
|
DirInfo->StringBlock.Data,
|
|
STRBLK_USED_BYTES(&DirInfo->StringBlock),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
Total = sizeof(DIR_INFO)
|
|
+ ARRAY_USED_BYTES(&DirInfo->Files)
|
|
+ STRBLK_USED_BYTES(&DirInfo->StringBlock);
|
|
|
|
//
|
|
// Update the header.
|
|
//
|
|
DirAndFileHeader->TotalSize += Total;
|
|
DirAndFileHeader->DirCount++;
|
|
|
|
if(SetFilePointer(OutputFile,0,NULL,FILE_BEGIN) == 0xffffffff) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
b = WriteFile(
|
|
OutputFile,
|
|
DirAndFileHeader,
|
|
sizeof(DIR_AND_FILE_HEADER),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
if(SetFilePointer(OutputFile,0,NULL,FILE_END) == 0xffffffff) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
SnapDirTree(
|
|
IN HWND StatusLogWindow,
|
|
IN PCWSTR OriginalRoot,
|
|
IN OUT PDIR_AND_FILE_HEADER DirAndFileHeader,
|
|
IN PCWSTR Root,
|
|
IN HANDLE OutputFile
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Snapshot an entire directory tree, saving the result to
|
|
a given file.
|
|
|
|
Worker routine for SnapshotDirectoryTree.
|
|
|
|
Arguments:
|
|
|
|
DirAndFileHeader - supplies the header for the tree of
|
|
directory structures generated as the tree is snapshotted.
|
|
|
|
Root - supplies the win32 path to the tree to be snapshotted.
|
|
|
|
OutputFile - supplies an open Win32 file handle to which
|
|
snapshot information will be written.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDIR_INFO DirInfo;
|
|
unsigned u;
|
|
WCHAR Subdir[MAX_PATH];
|
|
DWORD rc;
|
|
PSUBDIR_LIST Subdirs;
|
|
|
|
//
|
|
// See if we are supposed to skip this entire tree.
|
|
// If so we're done.
|
|
//
|
|
if(IsDirOrFileExcluded(DirAndFileExcludeDirTree,Root)) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SNAPDIRTREE_EXCLUDED,Root);
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
//
|
|
// Snap the current directory.
|
|
//
|
|
rc = ReadDirectoryFromDisk(StatusLogWindow,OriginalRoot,Root,&DirInfo,&Subdirs);
|
|
if(rc != NO_ERROR) {
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Dump this info structure for this directory out to disk.
|
|
// If we are supposed to exclude this directory, skip this step.
|
|
// We still need the info we read from the disk, however, so we
|
|
// can find any subdirectories contained within it.
|
|
//
|
|
if(IsDirOrFileExcluded(DirAndFileExcludeOneDir,Root)) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SNAPDIR_EXCLUDED,Root);
|
|
} else {
|
|
rc = WriteDirInfoToDisk(DirAndFileHeader,DirInfo,OutputFile);
|
|
if(rc == NO_ERROR) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SNAPPED_DIR,Root);
|
|
} else {
|
|
goto c1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Spin through the directory looking for other directories.
|
|
// In this way we accomplish a breadth-first search.
|
|
//
|
|
for(u=0; (rc == NO_ERROR) && (u<ARRAY_USED(&Subdirs->Dirs)); u++) {
|
|
|
|
lstrcpy(Subdir,Root);
|
|
|
|
ConcatenatePaths(
|
|
Subdir,
|
|
StringBlockIdToPointer(
|
|
&Subdirs->StringBlock,
|
|
ARRAY_ELEMENT(&Subdirs->Dirs,u,LONG)
|
|
),
|
|
MAX_PATH,
|
|
NULL
|
|
);
|
|
|
|
rc = SnapDirTree(StatusLogWindow,OriginalRoot,DirAndFileHeader,Subdir,OutputFile);
|
|
}
|
|
|
|
c1:
|
|
FreeDirectoryInfoStruct(DirInfo);
|
|
FreeSubdirListStruct(Subdirs);
|
|
c0:
|
|
if(rc != NO_ERROR) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SNAPDIR_ERROR,Root,rc);
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
SnapshotDirectoryTree(
|
|
IN HWND StatusLogWindow,
|
|
IN PCWSTR RootPath,
|
|
IN PCWSTR OutputFile
|
|
)
|
|
{
|
|
WCHAR FullPath[MAX_PATH];
|
|
PWSTR FinalComponent;
|
|
HANDLE hFile;
|
|
DWORD rc;
|
|
DIR_AND_FILE_HEADER DirAndFileHeader;
|
|
DWORD StringSize;
|
|
DWORD Written;
|
|
|
|
|
|
//
|
|
// Form the full pathname of the root path.
|
|
//
|
|
if(!GetFullPathName(RootPath,MAX_PATH,FullPath,&FinalComponent)) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Create a file for output.
|
|
//
|
|
hFile = CreateFile(
|
|
OutputFile,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_FLAG_RANDOM_ACCESS,
|
|
NULL
|
|
);
|
|
|
|
if(hFile == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Set up a header for the output and write it out.
|
|
// This effectively reserves space in the file.
|
|
// The header will be updated as we move along.
|
|
//
|
|
DirAndFileHeader.DirCount = 0;
|
|
DirAndFileHeader.OriginalRootPath = FullPath;
|
|
|
|
StringSize = (lstrlen(FullPath)+1)*sizeof(WCHAR);
|
|
|
|
DirAndFileHeader.HeaderSize = sizeof(DIR_AND_FILE_HEADER) + StringSize;
|
|
DirAndFileHeader.TotalSize = DirAndFileHeader.HeaderSize;
|
|
|
|
if(!WriteFile(hFile,&DirAndFileHeader,sizeof(DIR_AND_FILE_HEADER),&Written,NULL)
|
|
|| !WriteFile(hFile,FullPath,StringSize,&Written,NULL)) {
|
|
|
|
rc = GetLastError();
|
|
goto c1;
|
|
}
|
|
|
|
rc = SnapDirTree(StatusLogWindow,FullPath,&DirAndFileHeader,FullPath,hFile);
|
|
|
|
c1:
|
|
CloseHandle(hFile);
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
SnapshotDrives(
|
|
IN PCWSTR OutputFile,
|
|
OUT PDWORD OutputSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Top level routine for snapshotting files on the system.
|
|
Iterates all availavble drive letters and for each that is a hard drive
|
|
volume, makes a snapshot by calling down to a lower-level subroutine.
|
|
|
|
Arguments:
|
|
|
|
OutputFile - supplies name of file in which to produce output.
|
|
|
|
OutputSize - if the function is successful, receives the number
|
|
of bytes written to the output file.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR Drive;
|
|
WCHAR Path[MAX_PATH],TempFile[MAX_PATH];
|
|
PWCHAR p;
|
|
DWORD rc;
|
|
HANDLE hFile;
|
|
DWORD Written;
|
|
PWCHAR DriveList;
|
|
DIR_AND_FILE_SNAP_HEADER Header;
|
|
unsigned i;
|
|
HWND StatusLogWindow;
|
|
|
|
//
|
|
// Create window for status output.
|
|
//
|
|
StatusLogWindow = CreateStatusLogWindow(IDS_DRIVESNAP);
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_STARTING_DRIVE_SNAPSHOT);
|
|
|
|
//
|
|
// Generate a temporary file name to use
|
|
// for intermediate output.
|
|
//
|
|
if(!GetFullPathName(OutputFile,MAX_PATH,Path,&p)) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
if(!AddFileToExclude(Path)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c0;
|
|
}
|
|
|
|
*wcsrchr(Path,L'\\') = 0;
|
|
|
|
if(!GetTempFileName(Path,L"$SD",0,TempFile)) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
if(!AddFileToExclude(TempFile)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
DeleteFile(TempFile);
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Create the master output file.
|
|
//
|
|
hFile = CreateFile(
|
|
OutputFile,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if(hFile == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
DeleteFile(TempFile);
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Write a master drives snapshot header.
|
|
//
|
|
Header.TotalSize = sizeof(DIR_AND_FILE_SNAP_HEADER);
|
|
Header.DriveMap = 0;
|
|
Header.DirTreeCount = 0;
|
|
|
|
if(!WriteFile(hFile,&Header,sizeof(DIR_AND_FILE_SNAP_HEADER),&Written,NULL)) {
|
|
rc = GetLastError();
|
|
DeleteFile(TempFile);
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Want to do this for every locally attached hard drive.
|
|
//
|
|
for(DriveList=ValidHardDriveLetters; Drive=(*DriveList); DriveList++) {
|
|
|
|
//
|
|
// Skip excluded drives.
|
|
//
|
|
if(IsDriveExcluded(Drive)) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SKIPPING_DRIVE,Drive);
|
|
continue;
|
|
}
|
|
|
|
Path[0] = Drive;
|
|
Path[1] = L':';
|
|
Path[2] = L'\\';
|
|
Path[3] = 0;
|
|
|
|
rc = SnapshotDirectoryTree(StatusLogWindow,Path,TempFile);
|
|
if(rc != NO_ERROR) {
|
|
DeleteFile(TempFile);
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Append temp output file to the end of the master file.
|
|
//
|
|
rc = AppendFile(hFile,TempFile,TRUE,&Written);
|
|
if(rc != NO_ERROR) {
|
|
//
|
|
// Don't need the temp file even if not successful.
|
|
//
|
|
DeleteFile(TempFile);
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Update relevent fields in the header.
|
|
//
|
|
Header.TotalSize += Written;
|
|
Header.DriveMap |= (1 << (Drive - L'A'));
|
|
Header.DirTreeCount++;
|
|
|
|
//
|
|
// Update the header on-disk.
|
|
//
|
|
if((SetFilePointer(hFile,0,NULL,FILE_BEGIN) == 0xffffffff)
|
|
|| !WriteFile(hFile,&Header,sizeof(DIR_AND_FILE_SNAP_HEADER),&Written,NULL)) {
|
|
|
|
rc = GetLastError();
|
|
goto c1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Success.
|
|
//
|
|
*OutputSize = Header.TotalSize;
|
|
rc = NO_ERROR;
|
|
|
|
c1:
|
|
CloseHandle(hFile);
|
|
if(rc != NO_ERROR) {
|
|
DeleteFile(OutputFile);
|
|
}
|
|
c0:
|
|
if(rc == NO_ERROR) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_DRIVE_SNAPSHOT_OK);
|
|
} else {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_DRIVE_SNAPSHOT_ERR,rc);
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
PDIR_INFO
|
|
LoadDirInfo(
|
|
IN PDIR_INFO DirInfo,
|
|
OUT PDIR_INFO *NextDirInfo
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read a snahshotted directory image structure out of a snapshot file.
|
|
String ids are converted into pointers in the loaded structure.
|
|
|
|
Arguments:
|
|
|
|
DirInfo - supplies a pointer to a DIR_INFO structure within a memory-mapped
|
|
master snapshot file.
|
|
|
|
NextDirInfo - receives a pointer to where the next DIR_INFO structure
|
|
would begin (within the memory-mapped snapshot file).
|
|
|
|
Return Value:
|
|
|
|
Pointer to a loaded DIR_INFO structure or NULL if OOM
|
|
|
|
--*/
|
|
|
|
{
|
|
PDIR_INFO contents;
|
|
PUCHAR p;
|
|
|
|
//
|
|
// The first thing in there is the dir_info structure itself.
|
|
// Load it first so we don't have to worry about unaligned access
|
|
// within the file (which is not guaranteed to be aligned).
|
|
//
|
|
if(contents = _MyMalloc(sizeof(DIR_INFO))) {
|
|
|
|
CopyMemory(contents,DirInfo,sizeof(DIR_INFO));
|
|
|
|
//
|
|
// Skip past the DIR_INFO struct to the FILE_INFO array.
|
|
//
|
|
p = (PUCHAR)(DirInfo+1);
|
|
|
|
//
|
|
// Load the FILE_INFO array.
|
|
//
|
|
if(CopyDataIntoArray(&contents->Files,p)) {
|
|
|
|
//
|
|
// Skip past the FILE_INFO array to the string block image.
|
|
//
|
|
p += ARRAY_SIZE_BYTES(&contents->Files);
|
|
|
|
//
|
|
// Load the string block.
|
|
//
|
|
if(ReinitStringBlock(&contents->StringBlock,p)) {
|
|
|
|
//
|
|
// Skip past the string block. This points us at the next
|
|
// DIR_INFO in the file image.
|
|
//
|
|
p += STRBLK_USED_BYTES(&contents->StringBlock);
|
|
|
|
//
|
|
// Convert String IDs to pointers.
|
|
//
|
|
StringBlockIdsToPointers(
|
|
&contents->StringBlock,
|
|
ARRAY_DATA(&contents->Files),
|
|
ARRAY_USED(&contents->Files),
|
|
ARRAY_ELEMENT_SIZE(&contents->Files),
|
|
offsetof(FILE_INFO,FileNameId)
|
|
);
|
|
|
|
contents->DirectoryPath = StringBlockIdToPointer(
|
|
&contents->StringBlock,
|
|
contents->DirectoryPathId
|
|
);
|
|
|
|
*NextDirInfo = (PDIR_INFO)p;
|
|
|
|
return(contents);
|
|
}
|
|
|
|
FREE_ARRAY(&contents->Files);
|
|
}
|
|
|
|
_MyFree(contents);
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
PDIR_AND_FILE_HEADER
|
|
LoadDirAndFileHeader(
|
|
IN PDIR_AND_FILE_HEADER DirAndFileHeader,
|
|
OUT PDIR_INFO *FirstDirInfo
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read a snahshotted directory image header structure out of a snapshot file.
|
|
|
|
Arguments:
|
|
|
|
DirAndFileHeader - supplies a pointer to a DIR_AND_FILE_HEADER structure
|
|
within a memory-mapped master snapshot file.
|
|
|
|
NextDirInfo - receives a pointer to where the first DIR_INFO structure
|
|
would begin (within the memory-mapped snapshot file).
|
|
|
|
Return Value:
|
|
|
|
Pointer to a loaded DIR_AND_FILE_HEADER structure or NULL if OOM
|
|
|
|
--*/
|
|
|
|
{
|
|
PDIR_AND_FILE_HEADER header;
|
|
|
|
if(header = _MyMalloc(sizeof(DIR_AND_FILE_HEADER))) {
|
|
|
|
CopyMemory(header,DirAndFileHeader,sizeof(DIR_AND_FILE_HEADER));
|
|
|
|
//
|
|
// Now fetch the string out of the on-disk image. The image is not
|
|
// guaranteed to be aligned.
|
|
//
|
|
if(header->OriginalRootPath = DuplicateUnalignedString((WCHAR UNALIGNED *)(DirAndFileHeader+1))) {
|
|
|
|
*FirstDirInfo = (PDIR_INFO)((PUCHAR)DirAndFileHeader + header->HeaderSize);
|
|
|
|
return(header);
|
|
}
|
|
|
|
_MyFree(header);
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
PDIR_DIFF
|
|
LoadDirDiff(
|
|
IN PDIR_DIFF DirDiff,
|
|
OUT PDIR_DIFF *NextDirDiff
|
|
)
|
|
{
|
|
PDIR_DIFF dirDiff;
|
|
PUCHAR p;
|
|
BOOL SameIds;
|
|
|
|
if(dirDiff = _MyMalloc(sizeof(DIR_DIFF))) {
|
|
|
|
//
|
|
// Transfer the DIR_DIFF structure itself from the memory-mapped
|
|
// image to the in-memory copy.
|
|
//
|
|
CopyMemory(dirDiff,DirDiff,sizeof(DIR_DIFF));
|
|
|
|
//
|
|
// Skip over the DIR_DIFF structure to point at the array of FILE_DIFFs.
|
|
//
|
|
p = (PUCHAR)(DirDiff+1);
|
|
|
|
//
|
|
// Load the FILE_DIFF array.
|
|
//
|
|
if(CopyDataIntoArray(&dirDiff->Files,p)) {
|
|
|
|
//
|
|
// Skip over the FILE_DIFF array to the string block.
|
|
//
|
|
p += ARRAY_SIZE_BYTES(&dirDiff->Files);
|
|
|
|
//
|
|
// Load the string block.
|
|
//
|
|
if(ReinitStringBlock(&dirDiff->StringBlock,p)) {
|
|
|
|
//
|
|
// Skip over the string block to point at the next DIR_DIFF.
|
|
//
|
|
*NextDirDiff = (PDIR_DIFF)(p + STRBLK_USED_BYTES(&dirDiff->StringBlock));
|
|
|
|
//
|
|
// Convert string ids to pointers. Note that this has
|
|
// to be aware of the fact that sometimes the LFN and SFN
|
|
// share an Id.
|
|
//
|
|
SameIds = (dirDiff->PathId == dirDiff->PathSFNId);
|
|
dirDiff->Path = StringBlockIdToPointer(&dirDiff->StringBlock,dirDiff->PathId);
|
|
if(SameIds) {
|
|
dirDiff->PathSFN = dirDiff->Path;
|
|
} else {
|
|
dirDiff->PathSFN = StringBlockIdToPointer(&dirDiff->StringBlock,dirDiff->PathSFNId);
|
|
}
|
|
|
|
StringBlockIdsToPointers(
|
|
&dirDiff->StringBlock,
|
|
ARRAY_DATA(&dirDiff->Files),
|
|
ARRAY_USED(&dirDiff->Files),
|
|
ARRAY_ELEMENT_SIZE(&dirDiff->Files),
|
|
offsetof(FILE_DIFF,Name)
|
|
);
|
|
|
|
return(dirDiff);
|
|
}
|
|
|
|
FREE_ARRAY(&dirDiff->Files);
|
|
}
|
|
|
|
_MyFree(dirDiff);
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
DWORD
|
|
SaveFileData(
|
|
IN HANDLE TargetFileHandle,
|
|
IN PCWSTR SourceFile,
|
|
OUT PDWORD BytesWritten
|
|
)
|
|
{
|
|
HANDLE SourceHandle;
|
|
BOOL b;
|
|
NTSTATUS Status;
|
|
PVOID CompressedBuffer;
|
|
DWORD CompressedSize;
|
|
PVOID UncompressedBuffer;
|
|
DWORD UncompressedSize;
|
|
DWORD Written;
|
|
DWORD TotalWritten;
|
|
PVOID Context;
|
|
DWORD rc;
|
|
DWORD CompressWorkspaceSize;
|
|
DWORD FragmentWorkspaceSize;
|
|
PVOID CompressWorkspace;
|
|
PVOID Buffer;
|
|
DWORD Size;
|
|
|
|
#define READ_CHUNK_SIZE 65536
|
|
#define COMPRESS_ENGINE COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD
|
|
|
|
//
|
|
// Get compression requirements.
|
|
//
|
|
Status = RtlGetCompressionWorkSpaceSize(
|
|
COMPRESS_ENGINE,
|
|
&CompressWorkspaceSize,
|
|
&FragmentWorkspaceSize
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status)) {
|
|
rc = RtlNtStatusToDosError(Status);
|
|
goto c0;
|
|
}
|
|
|
|
EnablePrivilege(SE_BACKUP_NAME,TRUE);
|
|
|
|
//
|
|
// Open the source.
|
|
//
|
|
SourceHandle = CreateFile(
|
|
SourceFile,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL
|
|
);
|
|
|
|
if(SourceHandle == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Allocate buffers: a workspace buffer for the compressor,
|
|
// plus read and compress buffers.
|
|
//
|
|
CompressWorkspace = _MyMalloc(CompressWorkspaceSize);
|
|
if(!CompressWorkspace) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
UncompressedBuffer = _MyMalloc(READ_CHUNK_SIZE);
|
|
if(!UncompressedBuffer) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
CompressedBuffer = _MyMalloc(READ_CHUNK_SIZE);
|
|
if(!CompressedBuffer) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
Context = NULL;
|
|
TotalWritten = 0;
|
|
|
|
do {
|
|
b = BackupRead(
|
|
SourceHandle,
|
|
UncompressedBuffer,
|
|
READ_CHUNK_SIZE,
|
|
&UncompressedSize,
|
|
FALSE, // abort flag
|
|
FALSE, // backup ACLs flag
|
|
&Context
|
|
);
|
|
|
|
if(b) {
|
|
if(UncompressedSize) {
|
|
//
|
|
// Read some more data -- compress and write to target.
|
|
// We write a small header to aid in decompression later.
|
|
//
|
|
Status = RtlCompressBuffer(
|
|
COMPRESS_ENGINE,
|
|
UncompressedBuffer, // input buffer
|
|
UncompressedSize, // number of bytes to compress
|
|
CompressedBuffer, // output buffer
|
|
READ_CHUNK_SIZE, // capacity of output buffer
|
|
4096, // chunk size
|
|
&CompressedSize, // size of compressed data
|
|
CompressWorkspace
|
|
);
|
|
|
|
if((Status == STATUS_SUCCESS)
|
|
&& ((CompressedSize+(2*sizeof(DWORD))) < UncompressedSize)) {
|
|
|
|
//
|
|
// Successful compression, and the compressed size makes it worth it
|
|
// to actually use compression.
|
|
//
|
|
Buffer = CompressedBuffer;
|
|
Size = CompressedSize;
|
|
|
|
} else {
|
|
//
|
|
// Unable to compress. Use the uncompressed data as-is.
|
|
//
|
|
Buffer = UncompressedBuffer;
|
|
Size = UncompressedSize;
|
|
}
|
|
|
|
//
|
|
// Write a DWORD indicating how large the compressed data is
|
|
// and abother DWORD indicating how large the uncompressed data is.
|
|
// If these values are the same then the data is uncompressed.
|
|
//
|
|
if(b = WriteFile(TargetFileHandle,&Size,sizeof(DWORD),&Written,NULL)) {
|
|
|
|
TotalWritten += Written;
|
|
|
|
if(b = WriteFile(TargetFileHandle,&UncompressedSize,sizeof(DWORD),&Written,NULL)) {
|
|
|
|
TotalWritten += Written;
|
|
|
|
//
|
|
// Write the data itself.
|
|
//
|
|
if(b = WriteFile(TargetFileHandle,Buffer,Size,&Written,NULL)) {
|
|
TotalWritten += Written;
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Done.
|
|
//
|
|
b = FALSE;
|
|
rc = NO_ERROR;
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} while(b);
|
|
|
|
//
|
|
// Release context buffer. Set abort flag to TRUE, all other params
|
|
// except Context are ignored.
|
|
//
|
|
BackupRead(NULL,NULL,0,NULL,TRUE,FALSE,&Context);
|
|
|
|
if(rc == NO_ERROR) {
|
|
*BytesWritten = TotalWritten;
|
|
}
|
|
|
|
_MyFree(CompressedBuffer);
|
|
c3:
|
|
_MyFree(UncompressedBuffer);
|
|
c2:
|
|
_MyFree(CompressWorkspace);
|
|
c1:
|
|
CloseHandle(SourceHandle);
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
ExtractFileData(
|
|
IN HANDLE FileDataHandle,
|
|
IN DWORD DataOffset,
|
|
IN DWORD DataSize,
|
|
IN PCWSTR TargetName
|
|
)
|
|
{
|
|
PWCHAR p;
|
|
DWORD rc;
|
|
BOOL b;
|
|
NTSTATUS Status;
|
|
PVOID CompressedBuffer;
|
|
DWORD CompressedSize;
|
|
PVOID UncompressedBuffer;
|
|
DWORD UncompressedSize;
|
|
PVOID Buffer;
|
|
DWORD TempSize;
|
|
DWORD BytesRead;
|
|
DWORD ReadSize;
|
|
HANDLE hFile;
|
|
PVOID Context;
|
|
|
|
//
|
|
// Seek to the relevent part of the file data file.
|
|
//
|
|
if(SetFilePointer(FileDataHandle,DataOffset,NULL,FILE_BEGIN) == 0xffffffff) {
|
|
rc = GetLastError();
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Allocate buffers.
|
|
//
|
|
CompressedBuffer = _MyMalloc(READ_CHUNK_SIZE);
|
|
if(!CompressedBuffer) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
UncompressedBuffer = _MyMalloc(READ_CHUNK_SIZE);
|
|
if(!UncompressedBuffer) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
//
|
|
// Create the target file.
|
|
//
|
|
EnablePrivilege(SE_RESTORE_NAME,TRUE);
|
|
|
|
hFile = CreateFile(
|
|
TargetName,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL
|
|
);
|
|
|
|
if(hFile == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
goto c3;
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
Context = NULL;
|
|
|
|
while((rc == NO_ERROR) && ((LONG)DataSize > 0)) {
|
|
//
|
|
// The file data is divided up into compressed blocks.
|
|
// Each has a 2-DWORD header indicating the size of the compressed
|
|
// data stream in the chunk, and its uncompressed size.
|
|
// Because the file was originally read and compressed in
|
|
// READ_CHUNK_SIZE blocks, no compressed chunk can decompress into
|
|
// more than READ_CHUNK_SIZE bytes.
|
|
//
|
|
// Read the header DWORDs.
|
|
//
|
|
if(b = ReadFile(FileDataHandle,&CompressedSize,sizeof(DWORD),&BytesRead,NULL)) {
|
|
|
|
DataSize -= BytesRead;
|
|
|
|
if(b = ReadFile(FileDataHandle,&UncompressedSize,sizeof(DWORD),&BytesRead,NULL)) {
|
|
|
|
DataSize -= BytesRead;
|
|
|
|
//
|
|
// Now read the file data in this block, whose size
|
|
// is the compressed size we just read.
|
|
//
|
|
if(b = ReadFile(FileDataHandle,CompressedBuffer,CompressedSize,&BytesRead,NULL)) {
|
|
|
|
DataSize -= BytesRead;
|
|
|
|
//
|
|
// Decompress the data if necessary. The decompress routines
|
|
// may round the uncompressed size upwards, which is why we store
|
|
// the uncompressed size as well.
|
|
//
|
|
if(CompressedSize == UncompressedSize) {
|
|
//
|
|
// No need to decompress in this case.
|
|
//
|
|
Status = STATUS_SUCCESS;
|
|
Buffer = CompressedBuffer;
|
|
|
|
} else {
|
|
|
|
MYASSERT(CompressedSize < UncompressedSize);
|
|
|
|
Status = RtlDecompressBuffer(
|
|
COMPRESS_ENGINE,
|
|
UncompressedBuffer,
|
|
READ_CHUNK_SIZE,
|
|
CompressedBuffer,
|
|
BytesRead,
|
|
&TempSize
|
|
);
|
|
|
|
Buffer = UncompressedBuffer;
|
|
}
|
|
|
|
if(Status == STATUS_SUCCESS) {
|
|
|
|
b = BackupWrite(
|
|
hFile,
|
|
Buffer,
|
|
UncompressedSize,
|
|
&TempSize,
|
|
FALSE, // abort flag
|
|
FALSE, // ACLs flag
|
|
&Context
|
|
);
|
|
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
rc = RtlNtStatusToDosError(Status);
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free BackupWrite context. When bAbort param is TRUE all other params
|
|
// except the Context pointer are ignored.
|
|
//
|
|
BackupWrite(NULL,NULL,0,NULL,TRUE,FALSE,&Context);
|
|
|
|
CloseHandle(hFile);
|
|
c3:
|
|
_MyFree(UncompressedBuffer);
|
|
c2:
|
|
_MyFree(CompressedBuffer);
|
|
c1:
|
|
if(rc != NO_ERROR) {
|
|
DeleteFile(TargetName);
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
RecordFileDifference(
|
|
IN HWND StatusLogWindow,
|
|
OUT PDIR_DIFF DirDiff,
|
|
IN PFILE_INFO FileInfo,
|
|
IN BOOL Deleted,
|
|
IN HANDLE DataFileHandle
|
|
)
|
|
{
|
|
WCHAR Path[MAX_PATH];
|
|
FILE_DIFF FileDiff;
|
|
DWORD rc;
|
|
DWORD Written;
|
|
HANDLE FindHandle;
|
|
WIN32_FIND_DATA FindData;
|
|
|
|
FileDiff.Deleted = Deleted;
|
|
FileDiff.Attributes = FileInfo->Attributes;
|
|
FileDiff.ShortName[0] = 0;
|
|
|
|
FileDiff.NameId = AddToStringBlock(&DirDiff->StringBlock,FileInfo->FileName);
|
|
if(FileDiff.NameId == -1) {
|
|
return(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
//
|
|
// Form the full pathname of the file.
|
|
//
|
|
lstrcpy(Path,StringBlockIdToPointer(&DirDiff->StringBlock,DirDiff->PathId));
|
|
ConcatenatePaths(Path,FileInfo->FileName,MAX_PATH,NULL);
|
|
|
|
//
|
|
// Assume no offset data is needed.
|
|
//
|
|
FileDiff.OffsetToData = 0;
|
|
FileDiff.DataSize = 0;
|
|
|
|
//
|
|
// If file is being added, remember file data or file name
|
|
// depending on options in force.
|
|
//
|
|
if(Deleted) {
|
|
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_FILE_WAS_DELETED,Path);
|
|
|
|
} else {
|
|
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_FILE_WAS_CHANGED,Path);
|
|
|
|
//
|
|
// Determine offset from the start of the data area to this file's data.
|
|
// This is equal to the current size of the temporary file data file.
|
|
//
|
|
FileDiff.OffsetToData = GetFileSize(DataFileHandle,NULL);
|
|
|
|
//
|
|
// Get the short filename (we only care about the filename part here)
|
|
//
|
|
FindHandle = FindFirstFile(Path,&FindData);
|
|
if(FindHandle == INVALID_HANDLE_VALUE) {
|
|
return(GetLastError());
|
|
} else {
|
|
//
|
|
// If the cAlternateFileName member is empty then assume
|
|
// the LFN and the SFN are the same.
|
|
//
|
|
lstrcpyn(
|
|
FileDiff.ShortName,
|
|
FindData.cAlternateFileName[0] ? FindData.cAlternateFileName : FindData.cFileName,
|
|
sizeof(FileDiff.ShortName)/sizeof(FileDiff.ShortName[0])
|
|
);
|
|
|
|
FindClose(FindHandle);
|
|
}
|
|
|
|
//
|
|
// Transfer data from the file into the data area.
|
|
//
|
|
rc = SaveFileData(DataFileHandle,Path,&FileDiff.DataSize);
|
|
if(rc != NO_ERROR) {
|
|
return(rc);
|
|
}
|
|
}
|
|
|
|
if(!ADD_TO_ARRAY(&DirDiff->Files,FileDiff)) {
|
|
return(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
|
|
BOOL
|
|
StartRecordDirDifference(
|
|
IN HWND StatusLogWindow,
|
|
IN PCWSTR Root,
|
|
IN PCWSTR Directory,
|
|
IN BOOL Deleted,
|
|
OUT PDIR_DIFF DirDiff
|
|
)
|
|
{
|
|
WCHAR Path[MAX_PATH];
|
|
WCHAR PathSFN[MAX_PATH];
|
|
BOOL SfnSameAsLfn;
|
|
|
|
lstrcpyn(Path,Root,MAX_PATH);
|
|
ConcatenatePaths(Path,Directory,MAX_PATH,NULL);
|
|
|
|
//
|
|
// Get the short path for this path.
|
|
// If this fails assume the SFN is the same as the LFN.
|
|
//
|
|
if(GetShortPathName(Path,PathSFN,MAX_PATH)) {
|
|
SfnSameAsLfn = (lstrcmp(Path,PathSFN) == 0);
|
|
} else {
|
|
lstrcpy(PathSFN,Path);
|
|
SfnSameAsLfn = TRUE;
|
|
}
|
|
|
|
ZeroMemory(DirDiff,sizeof(DIR_DIFF));
|
|
|
|
if(DirDiff->Deleted = Deleted) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_DIR_WAS_DELETED,Path);
|
|
}
|
|
|
|
//
|
|
// Start the stringblock.
|
|
//
|
|
if(InitStringBlock(&DirDiff->StringBlock)) {
|
|
|
|
DirDiff->PathId = AddToStringBlock(&DirDiff->StringBlock,Path);
|
|
if(DirDiff->PathId != -1) {
|
|
|
|
if(SfnSameAsLfn) {
|
|
DirDiff->PathSFNId = DirDiff->PathId;
|
|
} else {
|
|
DirDiff->PathSFNId = AddToStringBlock(&DirDiff->StringBlock,PathSFN);
|
|
}
|
|
|
|
if(DirDiff->PathSFNId != -1) {
|
|
//
|
|
// Initialize an array to hold file differences in this directory.
|
|
//
|
|
if(INIT_ARRAY(DirDiff->Files,FILE_DIFF,0,10)) {
|
|
|
|
return(TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
FreeStringBlock(&DirDiff->StringBlock);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
DWORD
|
|
FlushDirDifference(
|
|
OUT PDIR_AND_FILE_DIFF_HEADER Header,
|
|
IN HANDLE FileHandle,
|
|
IN OUT PDIR_DIFF DirDiff,
|
|
IN OUT PDWORD DiffCount
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD Written;
|
|
|
|
*DiffCount = 0;
|
|
|
|
//
|
|
// Write the directory difference structure.
|
|
//
|
|
b = WriteFile(
|
|
FileHandle,
|
|
DirDiff,
|
|
sizeof(DIR_DIFF),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
return(GetLastError());
|
|
}
|
|
|
|
Header->TotalSize += Written;
|
|
|
|
//
|
|
// Write the file difference array.
|
|
//
|
|
b = WriteFile(
|
|
FileHandle,
|
|
ARRAY_DATA(&DirDiff->Files),
|
|
ARRAY_USED_BYTES(&DirDiff->Files),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
return(GetLastError());
|
|
}
|
|
|
|
Header->TotalSize += Written;
|
|
|
|
//
|
|
// Write the string block for this directory.
|
|
//
|
|
b = WriteFile(
|
|
FileHandle,
|
|
DirDiff->StringBlock.Data,
|
|
STRBLK_USED_BYTES(&DirDiff->StringBlock),
|
|
&Written,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
return(GetLastError());
|
|
}
|
|
|
|
Header->TotalSize += Written;
|
|
Header->DirCount++;
|
|
*DiffCount = ARRAY_USED(&DirDiff->Files) + 1;
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
|
|
VOID
|
|
DeleteDirDifferenceStruct(
|
|
IN OUT PDIR_DIFF DirDiff
|
|
)
|
|
{
|
|
FREE_ARRAY(&DirDiff->Files);
|
|
FreeStringBlock(&DirDiff->StringBlock);
|
|
}
|
|
|
|
|
|
DWORD
|
|
CompareDirs(
|
|
IN HWND StatusLogWindow,
|
|
IN PDIR_AND_FILE_DIFF_HEADER Header,
|
|
IN HANDLE OutputFileHandle,
|
|
IN HANDLE DataFileHandle,
|
|
IN PCWSTR RootPath,
|
|
IN PDIR_INFO OldDir,
|
|
IN PDIR_INFO NewDir,
|
|
IN OUT PDWORD DiffCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compare the contents of two directories to see how they are different.
|
|
|
|
Arguments:
|
|
|
|
OldDir - supplies directory information structure for old version of
|
|
directory, such as might be read out of a master snapshot file.
|
|
|
|
NewDir - supplies directory information structure for new version of
|
|
the directory, such as might exist currently on-disk.
|
|
|
|
DiffCount - supplies a pointer to a variable to receive the number of files
|
|
and dirs changes.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_INFO OldFile,NewFile;
|
|
unsigned OldBase,NewBase;
|
|
unsigned OldCount,NewCount;
|
|
unsigned OldIndex,NewIndex;
|
|
BOOL Found;
|
|
PUCHAR OldProcessed,NewProcessed;
|
|
int i;
|
|
DWORD rc;
|
|
BOOL b;
|
|
DIR_DIFF DirDiff;
|
|
BOOL DirDiffStarted;
|
|
BOOL FilesInDirForciblyIncluded;
|
|
WCHAR Path[MAX_PATH];
|
|
|
|
*DiffCount = 0;
|
|
|
|
//
|
|
// We'll do a kind of brute-force NxM thing, using the sorted order
|
|
// of the lists to help us.
|
|
//
|
|
OldCount = ARRAY_USED(&OldDir->Files);
|
|
NewCount = ARRAY_USED(&NewDir->Files);
|
|
|
|
if(!OldCount && !NewCount) {
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
DirDiffStarted = FALSE;
|
|
|
|
OldProcessed = _MyMalloc(OldCount);
|
|
if(!OldProcessed) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c0;
|
|
}
|
|
NewProcessed = _MyMalloc(NewCount);
|
|
if(!NewProcessed) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
ZeroMemory(OldProcessed,OldCount);
|
|
ZeroMemory(NewProcessed,NewCount);
|
|
|
|
//
|
|
// Find files that are in old but not in new.
|
|
// These files were deleted.
|
|
//
|
|
for(NewBase=0,OldIndex=0; OldIndex<OldCount; OldIndex++) {
|
|
|
|
Found = FALSE;
|
|
OldFile = &ARRAY_ELEMENT(&OldDir->Files,OldIndex,FILE_INFO);
|
|
|
|
for(NewIndex=NewBase; NewIndex<NewCount; NewIndex++) {
|
|
|
|
NewFile = &ARRAY_ELEMENT(&NewDir->Files,NewIndex,FILE_INFO);
|
|
|
|
i = lstrcmpi(OldFile->FileName,NewFile->FileName);
|
|
|
|
if(i == 0) {
|
|
//
|
|
// Note that because the lists are sorted it is not possible
|
|
// for any other item in the old list to match any item before
|
|
// this on the new list.
|
|
//
|
|
NewBase = NewIndex + 1;
|
|
Found = TRUE;
|
|
break;
|
|
} else {
|
|
if(i < 0) {
|
|
//
|
|
// The old filename is less than the new filename.
|
|
// This means the old file cannot possibly appear in
|
|
// the the new list.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!Found) {
|
|
//
|
|
// File was deleted.
|
|
//
|
|
if(!DirDiffStarted) {
|
|
|
|
b = StartRecordDirDifference(
|
|
StatusLogWindow,
|
|
RootPath,
|
|
OldDir->DirectoryPath,
|
|
FALSE,
|
|
&DirDiff
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
DirDiffStarted = TRUE;
|
|
}
|
|
|
|
rc = RecordFileDifference(StatusLogWindow,&DirDiff,OldFile,TRUE,DataFileHandle);
|
|
if(rc != NO_ERROR) {
|
|
goto c2;
|
|
}
|
|
OldProcessed[OldIndex] = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find files that are in new but not in old.
|
|
// These files were added.
|
|
//
|
|
for(OldBase=0,NewIndex=0; NewIndex<NewCount; NewIndex++) {
|
|
|
|
Found = FALSE;
|
|
NewFile = &ARRAY_ELEMENT(&NewDir->Files,NewIndex,FILE_INFO);
|
|
|
|
for(OldIndex=OldBase; OldIndex<OldCount; OldIndex++) {
|
|
//
|
|
// Skip this file if we already know it was deleted.
|
|
//
|
|
if(OldProcessed[OldIndex]) {
|
|
continue;
|
|
}
|
|
|
|
OldFile = &ARRAY_ELEMENT(&OldDir->Files,OldIndex,FILE_INFO);
|
|
|
|
i = lstrcmpi(OldFile->FileName,NewFile->FileName);
|
|
|
|
if(i == 0) {
|
|
//
|
|
// Note that because the lists are sorted it is not possible
|
|
// for any other item in the old list to match any item before
|
|
// this on the new list.
|
|
//
|
|
OldBase = OldIndex + 1;
|
|
Found = TRUE;
|
|
break;
|
|
} else {
|
|
if(i > 0) {
|
|
//
|
|
// The new filename is less than the old filename.
|
|
// This means the new file cannot possibly appear in
|
|
// the the old list.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!Found) {
|
|
//
|
|
// File was added.
|
|
//
|
|
if(!DirDiffStarted) {
|
|
|
|
b = StartRecordDirDifference(
|
|
StatusLogWindow,
|
|
RootPath,
|
|
NewDir->DirectoryPath,
|
|
FALSE,
|
|
&DirDiff
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
DirDiffStarted = TRUE;
|
|
}
|
|
|
|
rc = RecordFileDifference(StatusLogWindow,&DirDiff,NewFile,FALSE,DataFileHandle);
|
|
if(rc != NO_ERROR) {
|
|
goto c2;
|
|
}
|
|
NewProcessed[NewIndex] = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The files we haven't processed yet are present in both lists.
|
|
// Process forcibly included directories here (this is only relevent
|
|
// for files that were not added above, because files that were added
|
|
// since the last snapshot are automatically included).
|
|
//
|
|
lstrcpy(Path,RootPath);
|
|
ConcatenatePaths(Path,NewDir->DirectoryPath,MAX_PATH,NULL);
|
|
FilesInDirForciblyIncluded = IsDirOrFileExcluded(DirAndFileIncludeDirFiles,Path);
|
|
|
|
OldIndex = NewIndex = 0;
|
|
do {
|
|
//
|
|
// Find next unprocessed item in old list and new list.
|
|
// They must match.
|
|
//
|
|
while((OldIndex < OldCount) && OldProcessed[OldIndex]) {
|
|
OldIndex++;
|
|
}
|
|
|
|
while((NewIndex < NewCount) && NewProcessed[NewIndex]) {
|
|
NewIndex++;
|
|
}
|
|
|
|
if((OldIndex < OldCount) && (NewIndex < NewCount)) {
|
|
|
|
OldFile = &ARRAY_ELEMENT(&OldDir->Files,OldIndex,FILE_INFO);
|
|
NewFile = &ARRAY_ELEMENT(&NewDir->Files,NewIndex,FILE_INFO);
|
|
|
|
//
|
|
// See whether this file was changed.
|
|
//
|
|
if(FilesInDirForciblyIncluded
|
|
|| (OldFile->FileSize != NewFile->FileSize)
|
|
|| CompareFileTime(&OldFile->WriteTime,&NewFile->WriteTime)
|
|
|| (OldFile->Version != NewFile->Version)
|
|
|| CompareFileTime(&OldFile->CreateTime,&NewFile->CreateTime)) {
|
|
|
|
//
|
|
// File changed.
|
|
//
|
|
if(!DirDiffStarted) {
|
|
|
|
b = StartRecordDirDifference(
|
|
StatusLogWindow,
|
|
RootPath,
|
|
NewDir->DirectoryPath,
|
|
FALSE,
|
|
&DirDiff
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
DirDiffStarted = TRUE;
|
|
}
|
|
rc = RecordFileDifference(StatusLogWindow,&DirDiff,NewFile,FALSE,DataFileHandle);
|
|
if(rc != NO_ERROR) {
|
|
goto c2;
|
|
}
|
|
}
|
|
|
|
OldIndex++;
|
|
NewIndex++;
|
|
}
|
|
|
|
} while((OldIndex < OldCount) && (NewIndex < NewCount));
|
|
|
|
rc = NO_ERROR;
|
|
|
|
c2:
|
|
if(DirDiffStarted) {
|
|
if(rc == NO_ERROR) {
|
|
rc = FlushDirDifference(Header,OutputFileHandle,&DirDiff, DiffCount);
|
|
}
|
|
DeleteDirDifferenceStruct(&DirDiff);
|
|
}
|
|
|
|
_MyFree(NewProcessed);
|
|
c1:
|
|
_MyFree(OldProcessed);
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
DiffDirAndFileSnapshots(
|
|
IN HWND StatusLogWindow,
|
|
IN OUT PDIR_AND_FILE_DIFF_HEADER Header,
|
|
IN HANDLE OutputFileHandle,
|
|
IN HANDLE DataFileHandle,
|
|
IN PDIR_AND_FILE_HEADER Old,
|
|
IN PDIR_AND_FILE_HEADER New,
|
|
IN OUT PDWORD DiffCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
DiffCount - Supplies a variable to receive number of dir and file changes
|
|
|
|
Return Value:
|
|
|
|
Win32 error code indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDIR_AND_FILE_HEADER old,new;
|
|
PDIR_INFO oldDir,newDir;
|
|
PDIR_INFO OldDir,NewDir;
|
|
BOOL LoadOld,LoadNew;
|
|
unsigned OldIndex,NewIndex;
|
|
DWORD rc;
|
|
int i;
|
|
unsigned x;
|
|
BOOL b;
|
|
DIR_DIFF DirDiff;
|
|
DWORD TotalCount = 0, Count;
|
|
|
|
*DiffCount = 0;
|
|
|
|
//
|
|
// First, load the headers for these guys.
|
|
//
|
|
old = LoadDirAndFileHeader(Old,&oldDir);
|
|
if(!old) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c0;
|
|
}
|
|
new = LoadDirAndFileHeader(New,&newDir);
|
|
if(!new) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
LoadOld = LoadNew = TRUE;
|
|
OldIndex = NewIndex = 0;
|
|
OldDir = NewDir = NULL;
|
|
|
|
//
|
|
// We can use the sorted order of the lists to help us out.
|
|
// We look at the current old and new elements. If they are equal,
|
|
// then examine files in the directory and advance both old and new.
|
|
// If old is less then new, then there are elements in old that are
|
|
// less than new, meaning the directory has been removed.
|
|
// Otherwise old is greater than new, and there are elements in new
|
|
// that are not in old, meaning the directory has been added.
|
|
//
|
|
while((OldIndex < old->DirCount) || (NewIndex < new->DirCount)) {
|
|
|
|
//
|
|
// Load old and new dirs out of the file as needed.
|
|
//
|
|
if(LoadOld) {
|
|
if(OldDir) {
|
|
FreeDirectoryInfoStruct(OldDir);
|
|
OldDir = NULL;
|
|
}
|
|
|
|
if(OldIndex < old->DirCount) {
|
|
//
|
|
// Load from disk
|
|
//
|
|
OldDir = LoadDirInfo(oldDir,&oldDir);
|
|
if(!OldDir) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
}
|
|
LoadOld = FALSE;
|
|
}
|
|
|
|
if(LoadNew) {
|
|
if(NewDir) {
|
|
FreeDirectoryInfoStruct(NewDir);
|
|
NewDir = NULL;
|
|
}
|
|
|
|
if(NewIndex < new->DirCount) {
|
|
//
|
|
// Load from disk
|
|
//
|
|
NewDir = LoadDirInfo(newDir,&newDir);
|
|
if(!NewDir) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
}
|
|
LoadNew = FALSE;
|
|
}
|
|
|
|
if(OldDir && NewDir) {
|
|
i = CompareMultiLevelPath(OldDir->DirectoryPath,NewDir->DirectoryPath);
|
|
} else {
|
|
if(!NewDir) {
|
|
//
|
|
// We've exhausted the supply of new directories.
|
|
//
|
|
i = -1;
|
|
} else {
|
|
//
|
|
// We've exhausted the supply of old directories.
|
|
//
|
|
i = 1;
|
|
}
|
|
}
|
|
|
|
if(!i) {
|
|
//
|
|
// These directories match. Compare files within them.
|
|
//
|
|
rc = CompareDirs(
|
|
StatusLogWindow,
|
|
Header,
|
|
OutputFileHandle,
|
|
DataFileHandle,
|
|
new->OriginalRootPath,
|
|
OldDir,
|
|
NewDir,
|
|
&Count
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
goto c3;
|
|
}
|
|
|
|
TotalCount += Count;
|
|
LoadOld = LoadNew = TRUE;
|
|
OldIndex++;
|
|
NewIndex++;
|
|
|
|
} else {
|
|
//
|
|
// The directories do not match.
|
|
//
|
|
if(i > 0) {
|
|
//
|
|
// The new directory was added.
|
|
// Add it and advance the input from the new list.
|
|
//
|
|
PutTextInStatusLogWindow(
|
|
StatusLogWindow,
|
|
MSG_DIR_WAS_ADDED,
|
|
new->OriginalRootPath,
|
|
NewDir->DirectoryPath
|
|
);
|
|
|
|
b = StartRecordDirDifference(
|
|
StatusLogWindow,
|
|
new->OriginalRootPath,
|
|
NewDir->DirectoryPath,
|
|
FALSE,
|
|
&DirDiff
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
for(x=0; x<ARRAY_USED(&NewDir->Files); x++) {
|
|
|
|
rc = RecordFileDifference(
|
|
StatusLogWindow,
|
|
&DirDiff,
|
|
&ARRAY_ELEMENT(&NewDir->Files,x,FILE_INFO),
|
|
FALSE,
|
|
DataFileHandle
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
DeleteDirDifferenceStruct(&DirDiff);
|
|
goto c3;
|
|
}
|
|
}
|
|
|
|
rc = FlushDirDifference(Header,OutputFileHandle,&DirDiff, &Count);
|
|
DeleteDirDifferenceStruct(&DirDiff);
|
|
if(rc != NO_ERROR) {
|
|
goto c3;
|
|
}
|
|
|
|
LoadNew = TRUE;
|
|
NewIndex++;
|
|
TotalCount += Count;
|
|
|
|
} else {
|
|
//
|
|
// The old directory was deleted.
|
|
// Delete it and advance the input from the old list.
|
|
//
|
|
b = StartRecordDirDifference(
|
|
StatusLogWindow,
|
|
old->OriginalRootPath,
|
|
OldDir->DirectoryPath,
|
|
TRUE,
|
|
&DirDiff
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c3;
|
|
}
|
|
|
|
for(x=0; x<ARRAY_USED(&OldDir->Files); x++) {
|
|
|
|
rc = RecordFileDifference(
|
|
StatusLogWindow,
|
|
&DirDiff,
|
|
&ARRAY_ELEMENT(&OldDir->Files,x,FILE_INFO),
|
|
TRUE,
|
|
DataFileHandle
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
DeleteDirDifferenceStruct(&DirDiff);
|
|
goto c3;
|
|
}
|
|
}
|
|
|
|
rc = FlushDirDifference(Header,OutputFileHandle,&DirDiff, &Count);
|
|
DeleteDirDifferenceStruct(&DirDiff);
|
|
if(rc != NO_ERROR) {
|
|
goto c3;
|
|
}
|
|
|
|
TotalCount += Count;
|
|
LoadOld = TRUE;
|
|
OldIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
*DiffCount = TotalCount;
|
|
|
|
c3:
|
|
if(OldDir) {
|
|
FreeDirectoryInfoStruct(OldDir);
|
|
}
|
|
c2:
|
|
if(NewDir) {
|
|
FreeDirectoryInfoStruct(NewDir);
|
|
}
|
|
|
|
_MyFree(new->OriginalRootPath);
|
|
_MyFree(new);
|
|
c1:
|
|
_MyFree(old->OriginalRootPath);
|
|
_MyFree(old);
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
DiffDrives(
|
|
IN PVOID OriginalSnapshot,
|
|
IN PCWSTR OutputFile,
|
|
OUT PDWORD BytesWritten,
|
|
OUT PDWORD DiffCount
|
|
)
|
|
{
|
|
PDIR_AND_FILE_SNAP_HEADER originalSnapshot;
|
|
WCHAR TempFile[MAX_PATH];
|
|
WCHAR DataFile[MAX_PATH];
|
|
HANDLE OutputFileHandle;
|
|
HANDLE DataFileHandle;
|
|
PWCHAR p;
|
|
WCHAR Path[MAX_PATH];
|
|
DWORD rc;
|
|
DWORD Written;
|
|
DWORD FileDataSize;
|
|
UINT TreeCount;
|
|
UINT u;
|
|
PDIR_AND_FILE_HEADER TreeHeader,treeHeader;
|
|
PDIR_INFO dirInfo;
|
|
DWORD FileSize;
|
|
HANDLE FileHandle;
|
|
HANDLE FileMapping;
|
|
PVOID BaseAddress;
|
|
DIR_AND_FILE_DIFF_HEADER DiffHeader;
|
|
HWND StatusLogWindow;
|
|
|
|
*DiffCount = 0;
|
|
|
|
//
|
|
// Create window for status output.
|
|
//
|
|
StatusLogWindow = CreateStatusLogWindow(IDS_DRIVEDIFF);
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_STARTING_DRIVE_DIFF);
|
|
|
|
originalSnapshot = OriginalSnapshot;
|
|
|
|
//
|
|
// Generate a temporary file name to use for the snapshots.
|
|
//
|
|
if(!GetFullPathName(OutputFile,MAX_PATH,Path,&p)) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
if(!AddFileToExclude(Path)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c0;
|
|
}
|
|
|
|
*(p-1) = 0;
|
|
|
|
if(!GetTempFileName(Path,L"$DD",0,TempFile)) {
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
if(!AddFileToExclude(TempFile)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Create the file that will be used to hold file data.
|
|
//
|
|
if(!GetTempFileName(Path,L"$F",0,DataFile)) {
|
|
rc = GetLastError();
|
|
goto c1;
|
|
}
|
|
if(!AddFileToExclude(DataFile)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
DataFileHandle = CreateFile(
|
|
DataFile,
|
|
GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL
|
|
);
|
|
|
|
if(DataFileHandle == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
goto c2;
|
|
}
|
|
|
|
//
|
|
// Create the output file. This will hold the dir and file descriptors.
|
|
//
|
|
OutputFileHandle = CreateFile(
|
|
OutputFile,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if(OutputFileHandle == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
goto c3;
|
|
}
|
|
|
|
//
|
|
// Set up an initial header and write it out to the output file.
|
|
//
|
|
DiffHeader.Unused = 0;
|
|
DiffHeader.DirCount = 0;
|
|
DiffHeader.TotalSize = sizeof(DIR_AND_FILE_DIFF_HEADER);
|
|
DiffHeader.FileDataOffset = 0;
|
|
|
|
if(!WriteFile(OutputFileHandle,&DiffHeader,sizeof(DIR_AND_FILE_DIFF_HEADER),&Written,NULL)) {
|
|
|
|
rc = GetLastError();
|
|
goto c4;
|
|
}
|
|
|
|
//
|
|
// Pull values out from the snapshot header. The parameter the caller
|
|
// passed us points within a memory-mapped file, and the file format
|
|
// does not guarantee alignment.
|
|
//
|
|
TreeCount = *(UINT UNALIGNED *)(&originalSnapshot->DirTreeCount);
|
|
|
|
treeHeader = (PDIR_AND_FILE_HEADER)(originalSnapshot+1);
|
|
|
|
//
|
|
// We will do a diff for each drive that has a snapshot in the original
|
|
// snapshot file.
|
|
//
|
|
for(rc=NO_ERROR,u=0; (rc==NO_ERROR) && (u<TreeCount); u++) {
|
|
//
|
|
// Load the tree snapshot header for the old snapshot.
|
|
//
|
|
TreeHeader = LoadDirAndFileHeader(treeHeader,&dirInfo);
|
|
if(!TreeHeader) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
} else {
|
|
//
|
|
// Figure out if this is a full drive snapshot, and which drive it is.
|
|
//
|
|
Path[0] = UPPER(TreeHeader->OriginalRootPath[0]);
|
|
|
|
if(((Path[0] >= L'A') && (Path[0] <= L'Z'))
|
|
&& (TreeHeader->OriginalRootPath[1] == L':')
|
|
&& (TreeHeader->OriginalRootPath[2] == L'\\')
|
|
&& !TreeHeader->OriginalRootPath[3]) {
|
|
|
|
if(IsDriveExcluded(Path[0])) {
|
|
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SKIPPING_DRIVE,Path[0]);
|
|
|
|
} else {
|
|
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_SNAPPING_DRIVE_DIFF,Path[0]);
|
|
|
|
Path[1] = L':';
|
|
Path[2] = L'\\';
|
|
Path[3] = 0;
|
|
|
|
rc = SnapshotDirectoryTree(StatusLogWindow,Path,TempFile);
|
|
if(rc == NO_ERROR) {
|
|
|
|
//
|
|
// Open the snapshot file we just created.
|
|
//
|
|
rc = OpenAndMapFileForRead(
|
|
TempFile,
|
|
&FileSize,
|
|
&FileHandle,
|
|
&FileMapping,
|
|
&BaseAddress
|
|
);
|
|
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// Diff the old snapshot with the one we just took.
|
|
//
|
|
rc = DiffDirAndFileSnapshots(
|
|
StatusLogWindow,
|
|
&DiffHeader,
|
|
OutputFileHandle,
|
|
DataFileHandle,
|
|
treeHeader,
|
|
BaseAddress,
|
|
DiffCount
|
|
);
|
|
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// Point to the next tree header in the old snapshot file.
|
|
//
|
|
treeHeader = (PDIR_AND_FILE_HEADER)((PUCHAR)treeHeader
|
|
+ TreeHeader->TotalSize);
|
|
}
|
|
|
|
UnmapAndCloseFile(FileHandle,FileMapping,BaseAddress);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_MyFree(TreeHeader->OriginalRootPath);
|
|
_MyFree(TreeHeader);
|
|
}
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
*BytesWritten = DiffHeader.TotalSize;
|
|
}
|
|
|
|
c4:
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// Append the data file to the output file and update the header.
|
|
//
|
|
FileDataSize = GetFileSize(DataFileHandle,NULL);
|
|
|
|
DiffHeader.FileDataOffset = DiffHeader.TotalSize;
|
|
DiffHeader.TotalSize += FileDataSize;
|
|
|
|
if((SetFilePointer(OutputFileHandle,0,NULL,FILE_BEGIN) == 0xffffffff)
|
|
|| !WriteFile(OutputFileHandle,&DiffHeader,sizeof(DIR_AND_FILE_DIFF_HEADER),&Written,NULL)) {
|
|
|
|
rc = GetLastError();
|
|
}
|
|
}
|
|
|
|
if(rc != NO_ERROR) {
|
|
CloseHandle(OutputFileHandle);
|
|
DeleteFile(OutputFile);
|
|
}
|
|
c3:
|
|
CloseHandle(DataFileHandle);
|
|
if(rc == NO_ERROR) {
|
|
if(FileDataSize) {
|
|
rc = AppendFile(OutputFileHandle,DataFile,FALSE,&Written);
|
|
*BytesWritten += Written;
|
|
}
|
|
CloseHandle(OutputFileHandle);
|
|
}
|
|
c2:
|
|
DeleteFile(DataFile);
|
|
c1:
|
|
DeleteFile(TempFile);
|
|
c0:
|
|
if(rc == NO_ERROR) {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_DRIVE_DIFF_OK);
|
|
} else {
|
|
PutTextInStatusLogWindow(StatusLogWindow,MSG_DRIVE_DIFF_ERR,rc);
|
|
}
|
|
return(rc);
|
|
}
|
|
|
|
|
|
int
|
|
_CRTAPI1
|
|
ReverseCompareStringsRoutine(
|
|
const void *p1,
|
|
const void *p2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Callback routine passed to the qsort function, which compares 2
|
|
strings. The comparison is based on the lexical value of the
|
|
strings.
|
|
|
|
The comparison is not case sensitive.
|
|
|
|
Arguments:
|
|
|
|
p1,p2 - supply pointers to 2 pointers to strings to be compared.
|
|
|
|
Return Value:
|
|
|
|
<0 element1 < element2
|
|
=0 element1 = element2
|
|
>0 element1 > element2
|
|
|
|
--*/
|
|
|
|
{
|
|
return(lstrcmpi(*(PCWSTR *)p2,*(PCWSTR *)p1));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
DWORD
|
|
_ApplyDrives(
|
|
IN HANDLE DiffFileHandle,
|
|
IN HANDLE DiffFileMapping,
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN HANDLE Dump, OPTIONAL
|
|
IN PINFFILEGEN InfGenContext OPTIONAL
|
|
)
|
|
{
|
|
DWORD rc;
|
|
DIR_AND_FILE_DIFF_HEADER DirAndFileDiffHeader;
|
|
DWORD MapSize;
|
|
PVOID BaseAddress;
|
|
PDIR_DIFF DirDiff,NextDirDiff;
|
|
MY_ARRAY DelDirList;
|
|
STRINGBLOCK DelDirStrings;
|
|
UINT u;
|
|
LONG Id;
|
|
BOOL b;
|
|
|
|
//
|
|
// The caller will have read in the file header. The file header
|
|
// contains all the info we need to access the rest of the file.
|
|
//
|
|
// Seek to the dir and file part of the diff file and read in the
|
|
// dir and file diff header. Note that we rely on the caller to have
|
|
// cloned the file handle so we can party using this one without worrying
|
|
// about thread synch on this handle.
|
|
//
|
|
if((SetFilePointer(DiffFileHandle,DiffHeader->u.Diff.DirAndFileDiffOffset,NULL,FILE_BEGIN) == 0xffffffff)
|
|
|| !ReadFile(DiffFileHandle,&DirAndFileDiffHeader,sizeof(DIR_AND_FILE_DIFF_HEADER),&rc,NULL)) {
|
|
|
|
rc = GetLastError();
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// We will map in the dir and file portion of the diff file,
|
|
// exclusive of the file data section.
|
|
//
|
|
MapSize = DirAndFileDiffHeader.FileDataOffset - sizeof(DIR_AND_FILE_DIFF_HEADER);
|
|
|
|
//
|
|
// If there is no data in the dir and file diff section,
|
|
// then we're done, bail out now.
|
|
//
|
|
if(!MapSize) {
|
|
rc = NO_ERROR;
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Map in the main area of the dir and file diff. The first byte in
|
|
// the mapping is the first DIR_DIFF.
|
|
//
|
|
rc = MapPartOfFileForRead(
|
|
DiffFileHandle,
|
|
DiffFileMapping,
|
|
DiffHeader->u.Diff.DirAndFileDiffOffset + sizeof(DIR_AND_FILE_DIFF_HEADER),
|
|
MapSize,
|
|
&BaseAddress,
|
|
&NextDirDiff
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Initialize an array and string block to list directories
|
|
// that need to be deleted.
|
|
//
|
|
if(!INIT_ARRAY(DelDirList,LONG,0,10)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c1;
|
|
}
|
|
|
|
if(!InitStringBlock(&DelDirStrings)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto c2;
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
|
|
//
|
|
// Process each directory with differences.
|
|
//
|
|
for(u=0; (rc==NO_ERROR) && (u<DirAndFileDiffHeader.DirCount); u++) {
|
|
//
|
|
// Load the DIR_DIFF structure.
|
|
//
|
|
if(DirDiff = LoadDirDiff(NextDirDiff,&NextDirDiff)) {
|
|
|
|
if(Dump) {
|
|
WriteText(Dump,MSG_CRLF);
|
|
WriteText(Dump,MSG_DUMP_DIRECTORY,DirDiff->Path);
|
|
if(DirDiff->Path != DirDiff->PathSFN) {
|
|
WriteText(Dump,MSG_DUMP_DIRECTORY_SFN,DirDiff->PathSFN);
|
|
}
|
|
WriteText(Dump,MSG_CRLF);
|
|
}
|
|
|
|
//
|
|
// If the directory was deleted, add to list of dirs to delete.
|
|
// We'll do the actual deletes later.
|
|
//
|
|
if(DirDiff->Deleted) {
|
|
|
|
if(Dump || InfGenContext) {
|
|
if(Dump) {
|
|
WriteText(Dump,MSG_DUMP_DIR_DELETED);
|
|
}
|
|
if(InfGenContext) {
|
|
//
|
|
// We're not actually generating an inf for files,
|
|
// we''re creating a directory tree for oem preinstall.
|
|
// Deleting files is not supported in this mechanism.
|
|
//
|
|
//rc = InfRecordDelFile(InfGenContext,DirDiff->Path,NULL);
|
|
rc = NO_ERROR;
|
|
}
|
|
} else {
|
|
Id = AddToStringBlock(&DelDirStrings,DirDiff->Path);
|
|
if(Id == -1) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
} else {
|
|
if(!ADD_TO_ARRAY(&DelDirList,Id)) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
rc = ApplyFileChanges(
|
|
DiffHeader,
|
|
DirDiff,
|
|
DiffFileHandle,
|
|
DiffHeader->u.Diff.DirAndFileDiffOffset + DirAndFileDiffHeader.FileDataOffset,
|
|
Dump,
|
|
InfGenContext
|
|
);
|
|
}
|
|
|
|
//
|
|
// Free the current in-memory DIR_DIFF structure.
|
|
//
|
|
FREE_ARRAY(&DirDiff->Files);
|
|
FreeStringBlock(&DirDiff->StringBlock);
|
|
_MyFree(DirDiff);
|
|
|
|
} else {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
if(!Dump && !InfGenContext && (rc == NO_ERROR)) {
|
|
//
|
|
// Process the delete-dir list. First sort in reverse order.
|
|
// This guarantees that the lower entries in the tree are first
|
|
// so we don't have to worry about deleting whole trees.
|
|
//
|
|
StringBlockIdsToPointers(
|
|
&DelDirStrings,
|
|
ARRAY_DATA(&DelDirList),
|
|
ARRAY_USED(&DelDirList),
|
|
ARRAY_ELEMENT_SIZE(&DelDirList),
|
|
0
|
|
);
|
|
|
|
qsort(
|
|
ARRAY_DATA(&DelDirList),
|
|
ARRAY_USED(&DelDirList),
|
|
ARRAY_ELEMENT_SIZE(&DelDirList),
|
|
ReverseCompareStringsRoutine
|
|
);
|
|
|
|
for(u=0; u<ARRAY_USED(&DelDirList); u++) {
|
|
//
|
|
// Ignore errors, which may occur if the dir has subdirs, etc.
|
|
// Because the list of dirs is in sorted order, we are guaranteed
|
|
// to do the best we can just by following the list (ie, subdirs
|
|
// always come before their parents in this list).
|
|
//
|
|
RemoveDirectory(ARRAY_ELEMENT(&DelDirList,u,PCWSTR));
|
|
ADVANCE_PROGRESS_BAR;
|
|
}
|
|
}
|
|
|
|
FreeStringBlock(&DelDirStrings);
|
|
c2:
|
|
FREE_ARRAY(&DelDirList);
|
|
c1:
|
|
UnmapViewOfFile(BaseAddress);
|
|
c0:
|
|
return(rc);
|
|
}
|
|
|
|
|
|
DWORD
|
|
ApplyDrives(
|
|
IN HANDLE DiffFileHandle,
|
|
IN HANDLE DiffFileMapping,
|
|
IN PSYSDIFF_FILE DiffHeader
|
|
)
|
|
{
|
|
return(_ApplyDrives(DiffFileHandle,DiffFileMapping,DiffHeader,NULL,NULL));
|
|
}
|
|
|
|
|
|
DWORD
|
|
DumpDrives(
|
|
IN HANDLE DiffFileHandle,
|
|
IN HANDLE DiffFileMapping,
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN HANDLE OutputFile, OPTIONAL
|
|
IN PINFFILEGEN InfGenContext OPTIONAL
|
|
)
|
|
{
|
|
return(_ApplyDrives(DiffFileHandle,DiffFileMapping,DiffHeader,OutputFile,InfGenContext));
|
|
}
|
|
|
|
|
|
VOID
|
|
WhackLinkFile(
|
|
IN PCWSTR TargetName
|
|
)
|
|
{
|
|
unsigned u;
|
|
UCHAR Header[0x4c];
|
|
DWORD Read;
|
|
HANDLE hFile;
|
|
|
|
//
|
|
// Check for .lnk extension. Ignore if not.
|
|
//
|
|
u = lstrlen(TargetName);
|
|
if((u > 4) && !lstrcmpi(TargetName+u-4,L".lnk")) {
|
|
|
|
//
|
|
// Open the file.
|
|
//
|
|
hFile = CreateFile(
|
|
TargetName,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if(hFile != INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Read the header and check signature.
|
|
//
|
|
if(ReadFile(hFile,Header,sizeof(Header),&Read,NULL)
|
|
&& (Read = sizeof(Header))
|
|
&& (*(DWORD *)Header == 0x4c)) {
|
|
|
|
//
|
|
// OK, it looks like a link! Whack the flags.
|
|
// This is really nasty -- what is does is set the
|
|
// force-no-linkinfo flag.
|
|
//
|
|
Header[21] |= 1;
|
|
|
|
//
|
|
// Write it out.
|
|
//
|
|
if(!SetFilePointer(hFile,0,NULL,FILE_BEGIN)) {
|
|
|
|
WriteFile(hFile,Header,sizeof(Header),&Read,NULL);
|
|
}
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ApplyFileChanges(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN PDIR_DIFF DirDiff,
|
|
IN HANDLE FileDataHandle, OPTIONAL
|
|
IN DWORD FileDataOffset, OPTIONAL
|
|
IN HANDLE Dump, OPTIONAL
|
|
IN PINFFILEGEN InfGenContext OPTIONAL
|
|
)
|
|
{
|
|
unsigned u;
|
|
DWORD rc;
|
|
PFILE_DIFF FileDiff;
|
|
WCHAR TempName[MAX_PATH];
|
|
WCHAR TargetName[MAX_PATH],ShortName[MAX_PATH];
|
|
WCHAR RenameListFile[MAX_PATH];
|
|
UINT RootOffset;
|
|
BOOL b;
|
|
BOOL InUse;
|
|
HANDLE hTemp;
|
|
|
|
rc = NO_ERROR;
|
|
|
|
for(u=0; (rc == NO_ERROR) && (u < ARRAY_USED(&DirDiff->Files)); u++) {
|
|
|
|
FileDiff = &ARRAY_ELEMENT(&DirDiff->Files,u,FILE_DIFF);
|
|
|
|
if(FileDiff->Deleted) {
|
|
//
|
|
// Deleting the file.
|
|
//
|
|
if(Dump || InfGenContext) {
|
|
if(Dump) {
|
|
WriteText(Dump,MSG_DUMP_FILE_DELETED,FileDiff->Name);
|
|
}
|
|
if(InfGenContext) {
|
|
//
|
|
// We're not actually generating an inf for files,
|
|
// we''re creating a directory tree for oem preinstall.
|
|
// Deleting files is not supported in this mechanism.
|
|
//
|
|
//rc = InfRecordDelFile(InfGenContext,DirDiff->Path,FileDiff->Name);
|
|
}
|
|
} else {
|
|
lstrcpy(TargetName,DirDiff->Path);
|
|
ConcatenatePaths(TargetName,FileDiff->Name,MAX_PATH,NULL);
|
|
if(RemapProfileChanges) {
|
|
RemapToDefaultUser(DiffHeader,0,TargetName);
|
|
}
|
|
rc = DeleteFile(TargetName);
|
|
|
|
if((rc == ERROR_FILE_NOT_FOUND) || (rc == ERROR_PATH_NOT_FOUND)) {
|
|
rc = NO_ERROR;
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// Adding/overwriting the file. If the file data is contained
|
|
// within the diff file, extract it first. Otherwise just use
|
|
// the source file as-is.
|
|
//
|
|
if(Dump) {
|
|
if(lstrcmp(FileDiff->Name,FileDiff->ShortName)) {
|
|
WriteText(Dump,MSG_DUMP_FILE_ADDCHANGE_SFN,FileDiff->Name,FileDiff->ShortName);
|
|
} else {
|
|
WriteText(Dump,MSG_DUMP_FILE_ADDCHANGE,FileDiff->Name);
|
|
}
|
|
} else {
|
|
//
|
|
// Extract the file, either to a temporary location
|
|
// or to its final location, depending on the operation
|
|
// we're performing.
|
|
//
|
|
if(InfGenContext) {
|
|
|
|
if(DspMode && lstrcpy(TempName,DirDiff->Path)
|
|
&& ( RemapToDefaultUser(DiffHeader,REMAP_USEBAK,TempName)
|
|
|| RemapToCommonUser(DiffHeader,TempName))) {
|
|
|
|
//
|
|
// In this mode we don't generate an oem tree
|
|
// but instead extract files in the user profile tree
|
|
// into the backup profile directory. Other files are ignored.
|
|
//
|
|
// When rollback is used GUI mode setup will wind up
|
|
// delnoding the Profiles directory and restoring profiles.bak
|
|
// to Profiles. Thus we need to put files into the backup dir.
|
|
//
|
|
lstrcpy(ShortName,TempName);
|
|
ConcatenatePaths(ShortName,FileDiff->Name,MAX_PATH,NULL);
|
|
rc = pSetupMakeSurePathExists(ShortName);
|
|
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// Make sure backup profiles directory is valid.
|
|
//
|
|
GetWindowsDirectory(TempName,MAX_PATH);
|
|
ConcatenatePaths(TempName,L"Profiles.bak\\$$VALID",MAX_PATH,NULL);
|
|
pSetupMakeSurePathExists(TempName);
|
|
|
|
hTemp = CreateFile(
|
|
TempName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if(hTemp == INVALID_HANDLE_VALUE) {
|
|
rc = GetLastError();
|
|
} else {
|
|
CloseHandle(hTemp);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Figure out where in the oem dir structure this file goes.
|
|
// Use short filenames, since the OEM is likely going to stash
|
|
// his install source away on some server and then use winnt.exe
|
|
// to do preinstall -- both of which are potential snafus for LFNs.
|
|
// Also get it using the long filenames, because we will need
|
|
// to generate rename entries for directories and we have to
|
|
// be able to correlate LFNs with SFNs along the path.
|
|
//
|
|
lstrcpy(TempName,DirDiff->PathSFN);
|
|
if(RemapProfileChanges) {
|
|
RemapToDefaultUser(DiffHeader,REMAP_USESFN,TempName);
|
|
}
|
|
|
|
FilePathToOemPath(
|
|
DiffHeader,
|
|
InfGenContext->OemRoot,
|
|
TempName,
|
|
FileDiff->ShortName,
|
|
ShortName,
|
|
&RootOffset,
|
|
RenameListFile
|
|
);
|
|
|
|
lstrcpy(TempName,DirDiff->Path);
|
|
if(RemapProfileChanges) {
|
|
RemapToDefaultUser(DiffHeader,0,TempName);
|
|
}
|
|
|
|
FilePathToOemPath(
|
|
DiffHeader,
|
|
InfGenContext->OemRoot,
|
|
TempName,
|
|
FileDiff->Name,
|
|
TargetName,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Now ShortName has the SFN and TargetName has the LFN.
|
|
// Create the path using the SFNs, and store the rename
|
|
// information at each level.
|
|
//
|
|
rc = CreatePathWithSFNs(ShortName,RootOffset,TargetName,RenameListFile);
|
|
}
|
|
|
|
} else {
|
|
lstrcpy(TargetName,DirDiff->Path);
|
|
if(RemapProfileChanges) {
|
|
RemapToDefaultUser(DiffHeader,0,TargetName);
|
|
}
|
|
//
|
|
// Make sure the dir exists before trying to create
|
|
// a temporary file in it! pSetupMakeSurePathExists assumes
|
|
// the last component is the filename so we need to
|
|
// fake it out since TargetName is at this point just a path
|
|
// without a filename on it.
|
|
//
|
|
lstrcpy(TempName,TargetName);
|
|
ConcatenatePaths(TempName,L"Ignored",MAX_PATH,NULL);
|
|
rc = pSetupMakeSurePathExists(TempName);
|
|
|
|
if(rc == NO_ERROR) {
|
|
if(!GetTempFileName(TargetName,L"$FX",0,TempName)) {
|
|
rc = GetLastError();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
|
|
rc = ExtractFileData(
|
|
FileDataHandle,
|
|
FileDataOffset + FileDiff->OffsetToData,
|
|
FileDiff->DataSize,
|
|
InfGenContext ? ShortName : TempName
|
|
);
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
|
|
if(InfGenContext) {
|
|
//
|
|
// HACK: whack link files to remove link tracking
|
|
//
|
|
WhackLinkFile(ShortName);
|
|
|
|
//
|
|
// Now we have the file in the OEM target tree.
|
|
// Set file attributes, which will be preserved
|
|
// during oem preinstall.
|
|
//
|
|
SetFileAttributes(ShortName,FileDiff->Attributes);
|
|
} else {
|
|
//
|
|
// Copy the file into its final location.
|
|
//
|
|
lstrcpyn(TargetName,DirDiff->Path,MAX_PATH);
|
|
ConcatenatePaths(TargetName,FileDiff->Name,MAX_PATH,NULL);
|
|
|
|
if(RemapProfileChanges) {
|
|
RemapToDefaultUser(DiffHeader,0,TargetName);
|
|
}
|
|
|
|
b = SetupInstallFileEx(
|
|
NULL,
|
|
NULL,
|
|
TempName,
|
|
NULL,
|
|
TargetName,
|
|
SP_COPY_NEWER | SP_COPY_SOURCE_ABSOLUTE | SP_COPY_DELETESOURCE,
|
|
NULL,
|
|
NULL,
|
|
&InUse
|
|
);
|
|
|
|
if(b) {
|
|
//
|
|
// HACK: whack link files to remove link tracking
|
|
//
|
|
WhackLinkFile(TargetName);
|
|
|
|
//
|
|
// Apply attributes. Ignore errors.
|
|
//
|
|
if(!InUse) {
|
|
SetFileAttributes(TargetName,FileDiff->Attributes);
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ADVANCE_PROGRESS_BAR;
|
|
}
|
|
ADVANCE_PROGRESS_BAR; // signal the whole directory is done
|
|
|
|
return(rc);
|
|
}
|
|
|
|
|
|
BOOL
|
|
RemapToDefaultUser(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN UINT Flags,
|
|
IN OUT PWSTR Path
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If the prefix of a path matches a given path (which is supposed
|
|
to be the user profile root in a diff), then change it so the file
|
|
is instead in the Default User profile.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE if the path was changed, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR TempPath[MAX_PATH];
|
|
unsigned ProfileRootLength;
|
|
unsigned PathLength;
|
|
PWSTR ProfilePath;
|
|
BOOL UseSFN;
|
|
BOOL UseBak;
|
|
|
|
UseSFN = ((Flags & REMAP_USESFN) != 0);
|
|
UseBak = ((Flags & REMAP_USEBAK) != 0);
|
|
|
|
ProfilePath = UseSFN ? DiffHeader->UserProfileRootSFN : DiffHeader->UserProfileRoot;
|
|
if(!ProfilePath[0]) {
|
|
return(FALSE);
|
|
}
|
|
|
|
ProfileRootLength = lstrlen(ProfilePath);
|
|
|
|
if(ProfilePath[ProfileRootLength-1] == L'\\') {
|
|
ProfileRootLength--;
|
|
}
|
|
|
|
lstrcpyn(TempPath,Path,MAX_PATH);
|
|
PathLength = lstrlen(Path);
|
|
|
|
if((PathLength >= ProfileRootLength)
|
|
&& ((TempPath[ProfileRootLength] == L'\\') || !TempPath[ProfileRootLength])
|
|
&& !_wcsnicmp(TempPath,ProfilePath,ProfileRootLength)) {
|
|
|
|
lstrcpy(Path,DiffHeader->Sysroot);
|
|
|
|
ConcatenatePaths(
|
|
Path,
|
|
UseBak ? (UseSFN ? L"Profiles.bak\\DEFAUL~1" : L"Profiles.bak\\Default User")
|
|
: (UseSFN ? L"Profiles\\DEFAUL~1" : L"Profiles\\Default User"),
|
|
MAX_PATH,
|
|
NULL
|
|
);
|
|
|
|
if(TempPath[ProfileRootLength]) {
|
|
ConcatenatePaths(Path,TempPath+ProfileRootLength,MAX_PATH,NULL);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
RemapToCommonUser(
|
|
IN PSYSDIFF_FILE DiffHeader,
|
|
IN OUT PWSTR Path
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines whether a given path is within the Profiles\All Users
|
|
directory and if so remap to Profiles.Bak\All Users.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating outcome of comparison. If TRUE Path gets
|
|
remapped path.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR ProfilePath[MAX_PATH];
|
|
WCHAR TempPath[MAX_PATH];
|
|
unsigned ProfilePathLen;
|
|
unsigned PathLen;
|
|
BOOL b;
|
|
|
|
//
|
|
// Form path of common user within this tree.
|
|
//
|
|
lstrcpy(ProfilePath,DiffHeader->Sysroot);
|
|
ConcatenatePaths(ProfilePath,L"Profiles\\All Users",MAX_PATH,NULL);
|
|
ProfilePathLen = lstrlen(ProfilePath);
|
|
|
|
lstrcpyn(TempPath,Path,MAX_PATH);
|
|
PathLen = lstrlen(TempPath);
|
|
|
|
b = FALSE;
|
|
|
|
if((PathLen >= ProfilePathLen) && ((TempPath[ProfilePathLen] == L'\\') || !TempPath[ProfilePathLen])) {
|
|
|
|
TempPath[ProfilePathLen] = 0;
|
|
|
|
if(!lstrcmpi(TempPath,ProfilePath)) {
|
|
|
|
lstrcpy(Path,DiffHeader->Sysroot);
|
|
ConcatenatePaths(Path,L"Profiles.bak\\All Users",MAX_PATH,NULL);
|
|
|
|
if((PathLen > ProfilePathLen) && TempPath[ProfilePathLen+1]) {
|
|
ConcatenatePaths(Path,&TempPath[ProfilePathLen+1],MAX_PATH,NULL);
|
|
}
|
|
|
|
b = TRUE;
|
|
}
|
|
}
|
|
|
|
return(b);
|
|
}
|