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.
2842 lines
78 KiB
2842 lines
78 KiB
/*++
|
|
|
|
Copyright (c) 1987-1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
synctree.c
|
|
|
|
Abstract:
|
|
|
|
Contains the functions which actually sync with the master.
|
|
|
|
Author:
|
|
|
|
Ported from Lan Man 2.1
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
|
|
09-May-1989 (yuv)
|
|
Initial Coding.
|
|
|
|
17-Oct-1991 (cliffv)
|
|
Ported to NT. Converted to NT style.
|
|
|
|
11-Dec-1991 JohnRo
|
|
Avoid unnamed structure fields to allow MIPS builds.
|
|
|
|
13-Dec-1991 JohnRo
|
|
Avoid nonstandard dollar sign in C source code.
|
|
|
|
18-Dec-1991 JohnRo
|
|
Propagate LM2.1 bug fixes for LM bugs 3505 and 3423.
|
|
|
|
08-Jan-1992 JohnRo
|
|
Use REPL_STATE_ equates for client_list_rec.state values.
|
|
21-Jan-1992 JohnRo
|
|
Changed tree_depth_exception to a real BOOL.
|
|
Made other changes suggested by PC-LINT.
|
|
24-Jan-1992 JohnRo
|
|
P_ globals are now called ReplGlobal variables in replgbl.h.
|
|
Changed to use LPTSTR etc.
|
|
26-Feb-1992 JohnRo
|
|
Renamed tree depth fields to avoid conflicts between syncer and pulser.
|
|
05-Mar-1992 JohnRo
|
|
Changed interface to match new service controller.
|
|
24-Mar-1992 JohnRo
|
|
Added more debug output.
|
|
Use integrity and extent equates in <lmrepl.h>.
|
|
Modify REPL$ share handling.
|
|
24-Mar-1992 JohnRo
|
|
Renamed many ReplGlobal vars to ReplConfig vars.
|
|
Added/corrected some lock handling.
|
|
Still more debug output.
|
|
25-Mar-1992 JohnRo
|
|
New ReplFind routines interface.
|
|
Fixed bug where wrong master path name was being built in ReplSyncTree.
|
|
Fixed a max depth problem in ReplFileIntegritySync().
|
|
17-Jul-1992 JohnRo
|
|
RAID 10503: srv mgr: repl dialog doesn't come up.
|
|
Use PREFIX_ equates.
|
|
29-Jul-1992 JohnRo
|
|
RAID 2650: repl svc should handle new subdirs. Corrected ReplAllocBuf()
|
|
arg types.
|
|
11-Aug-1992 JohnRo
|
|
RAID 2115: repl svc should wait while stopping or changing roles.
|
|
17-Aug-1992 JohnRo
|
|
RAID 3607: REPLLOCK.RP$ is being created during tree copy.
|
|
10-Sep-1992 JohnRo
|
|
RAID 3608: repl time stamp not updated.
|
|
04-Nov-1992 JohnRo
|
|
RAID 10615: fix rare memory leak in ReplSyncTree.
|
|
Also added some debug output when setting no sync state.
|
|
18-Nov-1992 JohnRo
|
|
RAID 3638: repl cannot get more than 64K for buffer space.
|
|
Avoid compiler warnings (const vs. volatile).
|
|
17-Dec-1992 JohnRo
|
|
RAID 1513: Repl does not maintain ACLs. (Also fix HPFS->FAT timestamp.)
|
|
06-Jan-1993 JohnRo
|
|
RAID 6727: some "only at client" files are never deleted.
|
|
Made some changes suggested by PC-LINT 5.0
|
|
11-Jan-1993 JohnRo
|
|
RAID 6710: repl cannot manage dir with 2048 files.
|
|
17-Jan-1993 JohnRo
|
|
RAID 7053: locked trees added to pulse msg. (Actually fix all
|
|
kinds of remote lock handling.)
|
|
03-Mar-1993 JohnRo
|
|
RAID 12392: if dir timestamp changes, don't delnode entire tree.
|
|
04-Mar-1993 JohnRo
|
|
RAID 12237: replicator tree depth exceeded.
|
|
RAID 8355: Downlevel lock file check causes assert in repl importer.
|
|
PC-LINT found a bug in ReplTreeIntegritySync.
|
|
Added debug output to ChecksumEqual().
|
|
11-Mar-1993 JohnRo
|
|
RAID 14144: avoid very long hourglass in repl UI.
|
|
26-Mar-1993 JohnRo
|
|
RAID 4267: Replicator has problems when work queue gets large.
|
|
Prepare for >32 bits someday.
|
|
31-Mar-1993 JohnRo
|
|
Repl svc stop should be quicker.
|
|
Also fix some HANDLE vs. LPREPL_FIND_HANDLE problems.
|
|
Also various debug output changes.
|
|
13-Apr-1993 JohnRo
|
|
RAID 3107: locking directory over the net gives network path not found.
|
|
21-Apr-1993 JohnRo
|
|
RAID 7313: repl needs change permission to work on NTFS,
|
|
or we need to delete files differently.
|
|
26-Apr-1993 JohnRo
|
|
RAID 7157: fix setting ACLs, etc., on dirs themselves.
|
|
28-Apr-1993 JohnRo
|
|
RAID 7984: repl sometimes forgets to update empty dir timestamp, etc.
|
|
Use NetpKdPrint() where possible.
|
|
27-May-1993 JimKel and JohnRo
|
|
RAID 11682: Fixed tree integrity first level dir (ACLs, etc) after copy.
|
|
Made changes suggested by PC-LINT 5.0.
|
|
10-Jun-1993 JohnRo
|
|
RAID 13080: Allow repl between different timezones.
|
|
Updated some comments.
|
|
More changes suggested by PC-LINT 5.0
|
|
|
|
--*/
|
|
|
|
|
|
// These must be included first:
|
|
|
|
#include <windows.h> // IN, DWORD, etc.
|
|
#include <lmcons.h>
|
|
|
|
// These may be included in any order:
|
|
|
|
#include <alertmsg.h> // ALERT_* defines
|
|
#include <align.h> // ALIGN_* defines
|
|
#include <checksum.h> // FORMAT_CHECKSUM.
|
|
#include <client.h>
|
|
#include <config.h> // NetpOpenConfigData(), LPNET_CONFIG_HANDLE, etc.
|
|
#include <confname.h> // SECT_ and REPL_KEYWORD_ equates.
|
|
#include <dirname.h> // ReplIsDirNameValid().
|
|
#include <filefind.h> // REPL_WIN32_FIND_DATA, ReplCountDirectoryEntries, etc.
|
|
#include <impdir.h> // ImportDirUnlockInRegistry().
|
|
#include <lmapibuf.h> // NetApiBufferFree().
|
|
#include <lmerr.h> // NO_ERROR, ERROR_ and NERR_ equates.
|
|
#include <lmerrlog.h> // NELOG_* defines
|
|
#include <lmrepl.h> // REPL_STATE_ equates, etc.
|
|
#include <masproto.h> // ReplCheckExportLocks().
|
|
#if DBG
|
|
#include <names.h> // NetpIsUncComputerNameValid().
|
|
#endif
|
|
#include <netdebug.h> // DBGSTATIC, NetKdPrint(), etc.
|
|
#include <netlib.h> // NetpMemoryAllocate
|
|
#include <prefix.h> // PREFIX_ equates.
|
|
#include <repldefs.h> // REPL_SHARE, etc.
|
|
#include <replgbl.h> // ReplGlobal and ReplConfig variables.
|
|
#include <replp.h>
|
|
#include <tstr.h> // STRLEN(), etc.
|
|
|
|
|
|
//
|
|
// Return values for ReplFileCompare
|
|
//
|
|
#define COPY_FROM_MASTER 1
|
|
#define COPY_FROM_CLIENT 2
|
|
|
|
//
|
|
// Locally used definitions
|
|
//
|
|
#define ONLY_AT_CLIENT 1
|
|
#define ONLY_AT_MASTER -1
|
|
#define AT_BOTH 0
|
|
|
|
|
|
//
|
|
// The VAR_BUF structure defines a buffer to contain several FIND_DATA
|
|
// structures and an array of pointers to the structure. The buffer is
|
|
// allocated, enlarged and deallocated by the ReplAllocBuf routine.
|
|
//
|
|
typedef struct _VAR_BUF {
|
|
DWORD size; // Numbers of bytes alloced for *buf
|
|
PUCHAR buf;
|
|
DWORD ArraySize; // Number of pointers alloced for *array
|
|
LPREPL_WIN32_FIND_DATA *array;
|
|
} VAR_BUF, *PVAR_BUF;
|
|
|
|
|
|
//
|
|
// Allocation flags to ReplAllocBuf
|
|
//
|
|
#define ALLOC_BUF 0
|
|
#define REALLOC_BUF 1
|
|
#define FREE_BUF 2
|
|
|
|
|
|
DBGSTATIC VOID
|
|
ReplDisplayFileFindArray(
|
|
IN LPTSTR Tag,
|
|
IN LPREPL_WIN32_FIND_DATA Array,
|
|
IN DWORD EntryCount
|
|
)
|
|
{
|
|
#if 0
|
|
LPREPL_WIN32_FIND_DATA Entry = Array;
|
|
DWORD Index = 0;
|
|
|
|
NetpKdPrint(( FORMAT_LPTSTR ":\n", Tag ));
|
|
// BUGBUG: This seems to be broken (only displays first entry right).
|
|
while (Index < EntryCount) {
|
|
if (Entry->fdFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
LPTSTR Name = Entry->fdFound.cFileName;
|
|
NetpKdPrint(( " " FORMAT_DWORD ": " FORMAT_LPTSTR "\n",
|
|
Index, Name ));
|
|
}
|
|
++Index;
|
|
++Entry;
|
|
}
|
|
#else
|
|
UNREFERENCED_PARAMETER( Tag );
|
|
UNREFERENCED_PARAMETER( Array );
|
|
UNREFERENCED_PARAMETER( EntryCount );
|
|
#endif
|
|
|
|
} // ReplDisplayFileFindArray
|
|
|
|
|
|
BOOL
|
|
ReplFileOrDirExists(
|
|
IN LPCTSTR FileName
|
|
)
|
|
{
|
|
if ( (FileName==NULL) || ( (*FileName) == TCHAR_EOS ) ) {
|
|
return (FALSE); // no, it does not exist.
|
|
} else if ( GetFileAttributes( (LPTSTR) FileName ) != ((DWORD)-1) ) {
|
|
return (TRUE); // yes, it exists.
|
|
} else {
|
|
// GetFileAttributes returned -1. Does not exist or some other error.
|
|
return (FALSE); // no, it does not exist.
|
|
}
|
|
|
|
/*NOTREACHED*/
|
|
|
|
} // ReplFileOrDirExists
|
|
|
|
|
|
|
|
DBGSTATIC NET_API_STATUS
|
|
ReplAllocBuf(
|
|
IN OUT PCLIENT_LIST_REC tree_rec,
|
|
IN OUT PVAR_BUF alloc,
|
|
IN DWORD alloc_flag
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ReplAllocBuf handles file find buffer alloc/realloc/free operations.
|
|
This routine allocates a buffer for the structures plus another array
|
|
of pointers to those structures. The VAR_BUF structure contains the
|
|
data which is controlled by this routine. The est_max_dir_entry_count
|
|
field in the client record is used to tell this routine how large to
|
|
allocate things.
|
|
|
|
Note that the old data is NOT copied if a reallocation is done.
|
|
|
|
Also note that this is a recursive routine. Be careful.
|
|
|
|
Arguments:
|
|
|
|
tree_rec - identifies the directory this buffer is being allocated for.
|
|
This may have alert bits changed in it if ReplAllocBuf fails.
|
|
|
|
alloc - specifies the buffer descriptor (VAR_BUF) for this buffer.
|
|
|
|
alloc_flag - ALLOC_BUF if this is an allocation.
|
|
REALLOC_BUF if this is a reallocation.
|
|
FREE_BUF if this is to free allocated buffer.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
DWORD EntrySize;
|
|
NET_API_STATUS NetStatus;
|
|
DWORD NewEntryCount;
|
|
|
|
EntrySize = ROUND_UP_COUNT(
|
|
sizeof(REPL_WIN32_FIND_DATA),
|
|
ALIGN_WORST );
|
|
NetpAssert( EntrySize > 0 );
|
|
|
|
//
|
|
// Check for caller errors.
|
|
//
|
|
NetpAssert(
|
|
(alloc_flag==ALLOC_BUF)
|
|
|| (alloc_flag==REALLOC_BUF)
|
|
|| (alloc_flag==FREE_BUF) );
|
|
|
|
NetpAssert( alloc != NULL );
|
|
NetpAssert( tree_rec != NULL );
|
|
NewEntryCount = tree_rec->est_max_dir_entry_count;
|
|
|
|
//
|
|
// Make sure we have room for at least one entry. We'll always
|
|
// find "." and "..", so we need a buffer. But we'll skip leaving those
|
|
// entries in the buffer.
|
|
//
|
|
// Also, make sure that there is space for an entire extra entry at the end
|
|
// of the array. That way, if ReplFindNext tries to write something while
|
|
// there being called at end of file, we won't trash the heap.
|
|
//
|
|
++NewEntryCount;
|
|
NetpAssert( NewEntryCount > 0 );
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplAllocBuf: operation " FORMAT_DWORD " for VAR_BUF at "
|
|
FORMAT_LPVOID ", changing to " FORMAT_DWORD " entries.\n",
|
|
alloc_flag, (LPVOID) alloc, NewEntryCount ));
|
|
}
|
|
|
|
//
|
|
// Free the old buffer if necessary.
|
|
//
|
|
if ( alloc_flag != ALLOC_BUF ) {
|
|
if ( alloc->buf != NULL ) {
|
|
NetpMemoryFree( alloc->buf );
|
|
alloc->buf = NULL;
|
|
}
|
|
if ( alloc->array != NULL ) {
|
|
NetpMemoryFree( alloc->array );
|
|
alloc->array = NULL;
|
|
}
|
|
if (alloc_flag==FREE_BUF) {
|
|
return NO_ERROR;
|
|
}
|
|
// Fall through to alloc code for realloc case.
|
|
}
|
|
|
|
//
|
|
// Avoid confusing callers if we can only allocate one of these things...
|
|
//
|
|
alloc->array = NULL;
|
|
alloc->buf = NULL;
|
|
|
|
//
|
|
// Compute size of array of pointers and allocate it.
|
|
//
|
|
alloc->ArraySize = NewEntryCount;
|
|
|
|
alloc->array = NetpMemoryAllocate(
|
|
NewEntryCount * sizeof(LPREPL_WIN32_FIND_DATA) );
|
|
|
|
if (alloc->array == NULL) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
if ((tree_rec->alerts & UPDATE_ERROR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUpdateError,
|
|
NELOG_ReplUpdateError,
|
|
NetStatus,
|
|
tree_rec->dir_name,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= UPDATE_ERROR_ALERT;
|
|
}
|
|
return NetStatus;
|
|
}
|
|
|
|
//
|
|
// Compute size of find buffer and allocate it.
|
|
//
|
|
alloc->size = NewEntryCount * EntrySize;
|
|
|
|
alloc->buf = NetpMemoryAllocate( alloc->size );
|
|
|
|
if ( alloc->buf == NULL || alloc->array == NULL ) {
|
|
|
|
(VOID) ReplAllocBuf( tree_rec, alloc, FREE_BUF );
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
if ((tree_rec->alerts & UPDATE_ERROR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUpdateError,
|
|
NELOG_ReplUpdateError,
|
|
NetStatus,
|
|
tree_rec->dir_name,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= UPDATE_ERROR_ALERT;
|
|
}
|
|
return NetStatus;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DBGSTATIC VOID
|
|
ReplSort(
|
|
LPREPL_WIN32_FIND_DATA base[],
|
|
DWORD num
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Shell sort to FIND_DATA structures by file name.
|
|
|
|
Arguments:
|
|
|
|
base - Array of pointer to FIND_DATA structures to sort.
|
|
|
|
num - Number of entries in the array.
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
--*/
|
|
{
|
|
INT inc; // diminishing increment.
|
|
|
|
//
|
|
// use Knuth formula h(k-1) = 2*h(k) +1
|
|
//
|
|
|
|
LPREPL_WIN32_FIND_DATA temp;
|
|
INT i, j;
|
|
|
|
//
|
|
// compute starting inc.
|
|
//
|
|
|
|
inc = 1;
|
|
while (inc < (INT)num)
|
|
inc = 2 * inc + 1;
|
|
inc = (inc - 1) / 2;
|
|
|
|
for (; inc != 0 ; inc = (inc - 1) / 2) {
|
|
for (i = inc; i < (INT)num; i++) {
|
|
temp = base[i];
|
|
j = i - inc;
|
|
while ((j >= 0) &&
|
|
(STRICMP(temp->fdFound.cFileName, base[j]->fdFound.cFileName ) < 0 )) {
|
|
base[j+inc] = base[j];
|
|
j -= inc;
|
|
}
|
|
base[j+inc] = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
DBGSTATIC NET_API_STATUS
|
|
ReplReadSortDir2(
|
|
IN LPTSTR path,
|
|
OUT LPDWORD dir_count,
|
|
IN OUT PVAR_BUF out,
|
|
IN OUT PCLIENT_LIST_REC tree_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads all entries (up to dir_count) in the dir specified by dir, and sorts
|
|
acording to file name. The sort is not directly on buffer, but rather
|
|
the offsets into the buffer are arranged in array.
|
|
|
|
Arguments:
|
|
|
|
path - Specifies UNC pathname of the directory. This buffer will be
|
|
modified and returned to its original value.
|
|
|
|
dir_count - Returns the number of directory entries used.
|
|
|
|
out - Specifies the size and address of the buffer to return the FIND_DATA
|
|
in.
|
|
|
|
tree_rec - Pointer to dir's CLIENT_LIST_REC.
|
|
|
|
Return Value:
|
|
|
|
Status code.
|
|
|
|
ERROR_BUFFER_OVERFLOW : means that the buffer size needs to be extended.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
LPREPL_WIN32_FIND_DATA buf_p;
|
|
LPREPL_FIND_HANDLE FindHandle = INVALID_REPL_HANDLE;
|
|
|
|
DWORD path_index;
|
|
DWORD count = 0;
|
|
|
|
//
|
|
// Read the first file into the front of the buffer.
|
|
//
|
|
|
|
buf_p = (LPREPL_WIN32_FIND_DATA) out->buf;
|
|
if ( out->size < sizeof( *buf_p ) ) {
|
|
return ERROR_BUFFER_OVERFLOW ;
|
|
}
|
|
|
|
//
|
|
// Append \*.* to the path
|
|
//
|
|
|
|
path_index = STRLEN(path);
|
|
(void) STRCPY(path + path_index, SLASH);
|
|
(void) STRCPY(path + path_index + 1, STAR_DOT_STAR);
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplReadSortDir2: searching '" FORMAT_LPTSTR "'.\n",
|
|
path ));
|
|
}
|
|
|
|
//
|
|
// Find the first file.
|
|
//
|
|
|
|
FindHandle = ReplFindFirstFile( path, buf_p );
|
|
|
|
if ( FindHandle == INVALID_REPL_HANDLE ) {
|
|
|
|
NetStatus = GetLastError();
|
|
|
|
|
|
//
|
|
// This error means there was a connection to the REPL$ share
|
|
// on the srever that went away (the server went down and has
|
|
// come up again, or more likely the REPL on the server was stopped
|
|
// and restarted) so we must retry to establish the connection again.
|
|
//
|
|
|
|
if ( NetStatus == ERROR_NETNAME_DELETED ) {
|
|
FindHandle = ReplFindFirstFile( path, buf_p );
|
|
|
|
if ( FindHandle == INVALID_REPL_HANDLE ) {
|
|
NetStatus = GetLastError();
|
|
} else {
|
|
NetStatus = NO_ERROR;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now a Hack - the server maps access denied to one of the
|
|
// following error codes, to verify whether it is really an access
|
|
// denied error we need a different api.
|
|
//
|
|
|
|
if ( NetStatus == ERROR_NO_MORE_FILES ||
|
|
NetStatus == ERROR_PATH_NOT_FOUND ) {
|
|
|
|
*(path + path_index) = L'\0';
|
|
|
|
//
|
|
// If there really are no files in the directory,
|
|
// just indicate so.
|
|
//
|
|
|
|
if ( ReplFileOrDirExists( path ) ) {
|
|
*dir_count = 0;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
//
|
|
// The LM2.1 code didn't generate an alert for ERROR_PATH_NOT_FOUND.
|
|
// I'll just join the common error code.
|
|
//
|
|
|
|
NetStatus = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// do FindNext until exhausted or count reached.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// Handle subdirectories.
|
|
//
|
|
|
|
if (buf_p->fdFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
|
|
//
|
|
// If only first level files should be replicated,
|
|
// ignore directories.
|
|
//
|
|
|
|
if (tree_rec->extent != REPL_EXTENT_TREE ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Skip over "." and ".." once and for all.
|
|
//
|
|
|
|
if ( STRCMP( buf_p->fdFound.cFileName, DOT) == 0 ||
|
|
STRCMP( buf_p->fdFound.cFileName, DOT_DOT) == 0 ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Check for end of array. Note that we can't fill in the last entry,
|
|
// as we need to leave space for the last find next call.
|
|
//
|
|
|
|
if ( count >= ((out->ArraySize)-1) ) {
|
|
NetStatus = ERROR_BUFFER_OVERFLOW;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Keep a pointer to this entry.
|
|
//
|
|
out->array[count] = buf_p;
|
|
count++;
|
|
|
|
//
|
|
// Find out where next entry should go.
|
|
//
|
|
|
|
++buf_p;
|
|
|
|
buf_p = ROUND_UP_POINTER( buf_p, ALIGN_WORST );
|
|
|
|
#if 0
|
|
// BUGBUG: we now allocate entire entries only, so this check is
|
|
// useless.
|
|
if ( out->size - ((PUCHAR)buf_p - (PUCHAR)out->buf) < sizeof( *buf_p )){
|
|
NetStatus = ERROR_BUFFER_OVERFLOW;
|
|
goto Cleanup;
|
|
}
|
|
#endif
|
|
|
|
} while ( ReplFindNextFile( FindHandle, buf_p ));
|
|
|
|
//
|
|
// FindNext failed (perhaps with ERROR_NO_MORE_FILES).
|
|
//
|
|
|
|
NetStatus = GetLastError();
|
|
|
|
|
|
//
|
|
// Clean up
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Free any locally used resources.
|
|
//
|
|
|
|
if ( FindHandle != INVALID_REPL_HANDLE ) {
|
|
(void) ReplFindClose( FindHandle );
|
|
}
|
|
*(path + path_index) = '\0';
|
|
|
|
|
|
|
|
//
|
|
// If we simply enumerated all the files,
|
|
// sort the array and return them.
|
|
//
|
|
|
|
if ( NetStatus == ERROR_NO_MORE_FILES ) {
|
|
ReplSort( out->array, count );
|
|
*dir_count = count;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DBGSTATIC NET_API_STATUS
|
|
ReplReadSortDir(
|
|
IN LPTSTR path,
|
|
OUT LPDWORD dir_count,
|
|
IN OUT PVAR_BUF out,
|
|
IN OUT PCLIENT_LIST_REC tree_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Just a wrapper for ReplReadSortDir2 which does the actual work.
|
|
|
|
Reads all entries (up to out->ArraySize) in the dir specified by dir,
|
|
and sorts acording to file name. The sort is not directly on buffer,
|
|
but rather the pointers into the buffer are arranged in out->array.
|
|
|
|
Arguments:
|
|
|
|
path - Specifies UNC pathname of the directory. This buffer will be
|
|
modified and returned to its original value.
|
|
|
|
dir_count - Returns the number of directory entries used.
|
|
|
|
out - Specifies the size and address of the buffer to return the find data
|
|
in.
|
|
|
|
tree_rec - Pointer to dir's CLIENT_LIST_REC.
|
|
|
|
Return Value:
|
|
|
|
Status code.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
|
|
//
|
|
// Loop calling ReplReadSortDir2 growing the buffer until it is big enough.
|
|
//
|
|
|
|
for (;;) {
|
|
|
|
NetStatus = ReplReadSortDir2(path, dir_count, out, tree_rec);
|
|
|
|
if ( NetStatus != ERROR_BUFFER_OVERFLOW ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Need to grow buffer. Figure out how big it should be.
|
|
//
|
|
NetStatus = ReplCountDirectoryEntries(
|
|
path,
|
|
& (tree_rec->est_max_dir_entry_count) );
|
|
if (NetStatus != NO_ERROR) {
|
|
NetpAssert( NetStatus != ERROR_BUFFER_OVERFLOW );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Expand the buffer.
|
|
//
|
|
if ((NetStatus = ReplAllocBuf(tree_rec, out, REALLOC_BUF)) != NO_ERROR){
|
|
|
|
//
|
|
// ReplAllocBuf already reported the error.
|
|
//
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( NetStatus == NO_ERROR ) {
|
|
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
//
|
|
// Report any errors.
|
|
//
|
|
|
|
if ( NetStatus == ERROR_BUFFER_OVERFLOW ) {
|
|
|
|
//
|
|
// max_file limit has been exceeded.
|
|
//
|
|
|
|
if ((tree_rec->alerts & MAX_FILES_ALERT) == 0) {
|
|
|
|
//
|
|
// BUGBUG Shouldn't path be reported
|
|
//
|
|
|
|
AlertLogExit( ALERT_ReplMaxFiles,
|
|
NELOG_ReplMaxFiles,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= MAX_FILES_ALERT;
|
|
}
|
|
|
|
} else if ( NetStatus == ERROR_NETWORK_ACCESS_DENIED ||
|
|
NetStatus == ERROR_ACCESS_DENIED ) {
|
|
|
|
if ((tree_rec->alerts & ACCESS_DENIED_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplAccessDenied,
|
|
NELOG_ReplAccessDenied,
|
|
NetStatus,
|
|
path,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= ACCESS_DENIED_ALERT;
|
|
}
|
|
|
|
} else {
|
|
|
|
if ((tree_rec->alerts & UPDATE_ERROR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUpdateError,
|
|
NELOG_ReplUpdateError,
|
|
NetStatus,
|
|
path,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= UPDATE_ERROR_ALERT;
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
|
|
|
|
DBGSTATIC DWORD
|
|
ReplFileCompare(
|
|
LPREPL_WIN32_FIND_DATA buf1,
|
|
LPREPL_WIN32_FIND_DATA buf2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compares the attributes of both entries and returns as follows:
|
|
|
|
Only the following attributes are compared (NOTE: assumes filenames are
|
|
identical).: LastWrittenTime, FileSize, FileAttributes, EA size.
|
|
These are the same fields used to compute the checksum.
|
|
|
|
Arguments:
|
|
|
|
buf1 - the FIND_DATA structure for the first file.
|
|
|
|
buf_2 - the FIND_DATA structure for the second file.
|
|
|
|
Return Value:
|
|
|
|
COPY_FROM_CLIENT: All attributes are the same.
|
|
|
|
COPY_FROM_MASTER: At least one attribute is different.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
|
|
#define PRINT_COMPARE( msgText ) \
|
|
{ \
|
|
IF_DEBUG( SYNC ) { \
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT \
|
|
"ReplFindCompare: comparing " FORMAT_LPTSTR " to " \
|
|
FORMAT_LPTSTR ": " msgText ".\n", \
|
|
buf1->fdFound.cFileName, \
|
|
buf2->fdFound.cFileName )); \
|
|
} \
|
|
}
|
|
|
|
NetpAssert(STRICMP( buf1->fdFound.cFileName, buf2->fdFound.cFileName)==0);
|
|
|
|
// Does file time match (within resolution of file system)?
|
|
if ( !ReplIsFileTimeCloseEnough( &buf1->fdFound.ftLastWriteTime, &buf2->fdFound.ftLastWriteTime) ) {
|
|
|
|
PRINT_COMPARE( "different write times (beyond resolution)" );
|
|
return COPY_FROM_MASTER;
|
|
}
|
|
|
|
if (buf1->fdFound.nFileSizeHigh != buf2->fdFound.nFileSizeHigh) {
|
|
PRINT_COMPARE( "different file size (high)" );
|
|
return COPY_FROM_MASTER;
|
|
}
|
|
|
|
if (buf1->fdFound.nFileSizeLow != buf2->fdFound.nFileSizeLow) {
|
|
PRINT_COMPARE( "different file size (low)" );
|
|
return COPY_FROM_MASTER;
|
|
}
|
|
|
|
if (buf1->fdFound.dwFileAttributes != buf2->fdFound.dwFileAttributes) {
|
|
PRINT_COMPARE( "different attributes" );
|
|
return COPY_FROM_MASTER;
|
|
}
|
|
|
|
if (buf1->nEaSize != buf2->nEaSize) {
|
|
PRINT_COMPARE( "different EA sizes" );
|
|
return COPY_FROM_MASTER;
|
|
}
|
|
|
|
//
|
|
// BUGBUG: We should compare other times here, like modification ("change")
|
|
// time and create time. Comparing the access time would probably cause
|
|
// other problems. Perhaps the change time will reflect an alternate-data-
|
|
// stream update, which is currently now reflected in the file's write time.
|
|
// Don't ask me why, I only work here.
|
|
//
|
|
|
|
//
|
|
// files are identical.
|
|
//
|
|
|
|
PRINT_COMPARE( "identical" );
|
|
return COPY_FROM_CLIENT;
|
|
|
|
} // ReplFileCompare
|
|
|
|
|
|
|
|
DBGSTATIC NET_API_STATUS
|
|
ReplTreeIntegritySync(
|
|
IN OUT PVAR_BUF MasterBuffer,
|
|
IN OUT PVAR_BUF ClientBuffer,
|
|
IN LPTSTR master_path,
|
|
IN LPTSTR client_path OPTIONAL,
|
|
IN LPTSTR tmp_path,
|
|
IN PCLIENT_LIST_REC tree_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a complete tree integrity sync for 1 dir, recursively.
|
|
|
|
Called by ReplSyncTree and by itself recursively.
|
|
|
|
Arguments:
|
|
|
|
MasterBuffer - Buffer to be used to contain FIND_DATA structures of
|
|
the master directory.
|
|
|
|
ClientBuffer - Buffer to be used to contain FIND_DATA structures of
|
|
the client directory.
|
|
|
|
client_path - UNC to client's dir. This buffer is modified during
|
|
the execution of this routine but is returned to its original
|
|
value on completion. This path can be NULL in the case that this
|
|
subdirectory doesn't currently exist on the client.
|
|
|
|
master_path - UNC to master's dir. This buffer is modified during
|
|
the execution of this routine but is returned to its original
|
|
value on completion.
|
|
|
|
tmp_path - UNC to import\TMPTREE.RP$ temporary tree.
|
|
|
|
tree_rec - pointer to dir's CLIENT_LIST_REC.
|
|
|
|
Return Value:
|
|
|
|
NET_API_STATUS.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
LPREPL_FIND_HANDLE FindHandle = INVALID_REPL_HANDLE;
|
|
NET_API_STATUS NetStatus;
|
|
|
|
DWORD cli_dir_cnt;
|
|
DWORD mas_dir_cnt;
|
|
DWORD master_index;
|
|
DWORD client_index = 0;
|
|
DWORD tmp_index;
|
|
DWORD i, j;
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplTreeIntegritySync: master path='" FORMAT_LPTSTR
|
|
"', client path=" FORMAT_LPTSTR "'.\n", master_path,
|
|
client_path ? client_path : (LPTSTR) TEXT("<NONE>") ));
|
|
}
|
|
|
|
//
|
|
// Get the file names of the master and client directories.
|
|
//
|
|
|
|
master_index = STRLEN(master_path);
|
|
tmp_index = STRLEN(tmp_path);
|
|
if ( client_path != NULL ) {
|
|
client_index = STRLEN(client_path);
|
|
}
|
|
|
|
NetStatus = ReplReadSortDir( master_path, &mas_dir_cnt, MasterBuffer, tree_rec);
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( client_path != NULL ) {
|
|
NetStatus = ReplReadSortDir( client_path,
|
|
&cli_dir_cnt,
|
|
ClientBuffer,
|
|
tree_rec);
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
} else {
|
|
client_index = 0;
|
|
cli_dir_cnt = 0;
|
|
}
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplTreeIntegritySync: " FORMAT_DWORD
|
|
" master dirs/files and " FORMAT_DWORD " on client.\n",
|
|
mas_dir_cnt, cli_dir_cnt ));
|
|
}
|
|
|
|
|
|
//
|
|
// see if dirs are empty.
|
|
//
|
|
|
|
if ((mas_dir_cnt == 0) && (cli_dir_cnt == 0)) {
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup; // Make sure we update timestamp for empty dir.
|
|
}
|
|
|
|
//
|
|
// now the works.
|
|
//
|
|
|
|
i = j = 0;
|
|
|
|
while ((i < mas_dir_cnt) || (j < cli_dir_cnt)) {
|
|
INT State;
|
|
|
|
//
|
|
// Determine if the file/dir exists as master/client/both.
|
|
//
|
|
|
|
if ((i < mas_dir_cnt) && (j < cli_dir_cnt)) {
|
|
State = STRICMP( MasterBuffer->array[i]->fdFound.cFileName,
|
|
ClientBuffer->array[j]->fdFound.cFileName );
|
|
|
|
if ( State < 0 ) {
|
|
State = ONLY_AT_MASTER;
|
|
} else if ( State > 0 ) {
|
|
State = ONLY_AT_CLIENT;
|
|
} else {
|
|
State = AT_BOTH;
|
|
}
|
|
|
|
//
|
|
// either i or j is exhausted.
|
|
//
|
|
|
|
} else {
|
|
if (i < mas_dir_cnt) {
|
|
State = ONLY_AT_MASTER;
|
|
} else {
|
|
State = ONLY_AT_CLIENT;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// File not present at Master ==> deleted - so don't copy to temp.
|
|
//
|
|
|
|
switch (State) {
|
|
case ONLY_AT_CLIENT:
|
|
|
|
j++; // advance client index.
|
|
break;
|
|
|
|
//
|
|
// Files present only at Master ==> added - must copy to tmp dir.
|
|
//
|
|
|
|
case ONLY_AT_MASTER:
|
|
|
|
|
|
//
|
|
// Don't copy REPL.INI or userlock files.
|
|
//
|
|
|
|
if ( !ReplIgnoreDirOrFileName(
|
|
MasterBuffer->array[i]->fdFound.cFileName ) ) {
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
(void) STRCPY(tmp_path + tmp_index, SLASH);
|
|
(void) STRCPY(tmp_path + tmp_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
NetStatus = ReplSyncCopy( master_path, tmp_path, tree_rec );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
*(master_path + master_index) = '\0';
|
|
*(tmp_path + tmp_index) = '\0';
|
|
}
|
|
|
|
i++; // advance master index.
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// File exists at both master and client.
|
|
//
|
|
|
|
case AT_BOTH:
|
|
|
|
(void) STRCPY(tmp_path + tmp_index, SLASH);
|
|
(void) STRCPY(tmp_path + tmp_index + 1, MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
//
|
|
// If the file at the master is different than at the client,
|
|
// copy the file from the master into the temp directory.
|
|
//
|
|
|
|
if(ReplFileCompare(MasterBuffer->array[i],
|
|
ClientBuffer->array[j])==COPY_FROM_MASTER){
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
NetStatus = ReplSyncCopy( master_path, tmp_path, tree_rec );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
*(master_path + master_index) = '\0';
|
|
*(tmp_path + tmp_index) = '\0';
|
|
|
|
//
|
|
// If the file at the master is same as that at the client,
|
|
// copy the file from the client into the temp directory to
|
|
// avoid network traffic.
|
|
//
|
|
|
|
} else {
|
|
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1,
|
|
ClientBuffer->array[j]->fdFound.cFileName);
|
|
|
|
//
|
|
// make shure that the client_path does not equal the
|
|
// tmp_path, if equal, a sharing violation occurs.
|
|
//
|
|
if (STRICMP( client_path, tmp_path ))
|
|
{
|
|
NetStatus = ReplSyncCopy( client_path,
|
|
tmp_path,
|
|
tree_rec );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
*(client_path + client_index) = '\0';
|
|
*(tmp_path + tmp_index) = '\0';
|
|
}
|
|
i++;
|
|
j++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NetpAssert( FALSE ); // invalid state!
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
} // switch
|
|
|
|
//
|
|
// Quit if service is stopping.
|
|
//
|
|
if (ReplGlobalIsServiceStopping) {
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
} // big while loop
|
|
|
|
//
|
|
// NOTE!!!!
|
|
//
|
|
//
|
|
// This gets complicated in the case of a new dir at master: the dir has already
|
|
// been created under tmp_path, but does NOT appear under client_path.
|
|
// Work around:
|
|
//
|
|
// Go thru master sub-dirs
|
|
// if same sub-dir exists under client_path
|
|
// recurse normally
|
|
// else
|
|
// recurse with client_path == tmp_path!!!!
|
|
//
|
|
|
|
|
|
//
|
|
// Now we are left with the sub-directories that are present at both sides
|
|
// we have taken care of the cases where a dir is missing at either side.
|
|
//
|
|
|
|
if (tree_rec->extent == REPL_EXTENT_TREE) {
|
|
|
|
REPL_WIN32_FIND_DATA FindData;
|
|
|
|
//
|
|
// Walk the temp tree finding sub-directories to copy from the master.
|
|
// We don't walk the MasterBuffer (or ClientBuffer) list since we
|
|
// want to re-use that same buffer for the recursion.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + tmp_index, SLASH);
|
|
(void) STRCPY(tmp_path + tmp_index + 1, STAR_DOT_STAR);
|
|
|
|
FindHandle = ReplFindFirstFile( tmp_path, &FindData );
|
|
|
|
if ( FindHandle != INVALID_REPL_HANDLE ) {
|
|
|
|
do {
|
|
if (FindData.fdFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
//
|
|
// make sure it's not '.' or '..'.
|
|
//
|
|
|
|
if (STRCMP(FindData.fdFound.cFileName, DOT) == 0) {
|
|
continue;
|
|
}
|
|
if (STRCMP(FindData.fdFound.cFileName, DOT_DOT) == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Build the path names.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + tmp_index + 1, FindData.fdFound.cFileName);
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1, FindData.fdFound.cFileName);
|
|
|
|
if ( client_path != NULL ) {
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1,
|
|
FindData.fdFound.cFileName);
|
|
}
|
|
|
|
//
|
|
// If the client path exists for this subdirectory,
|
|
// use it.
|
|
//
|
|
|
|
if ( ReplFileOrDirExists( client_path ) ) {
|
|
|
|
//
|
|
// RECURSIVE CALL HERE.
|
|
//
|
|
|
|
NetStatus = ReplTreeIntegritySync( MasterBuffer,
|
|
ClientBuffer,
|
|
master_path,
|
|
client_path,
|
|
tmp_path,
|
|
tree_rec);
|
|
|
|
//
|
|
// If the client path doesn't exist,
|
|
// just use null.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// RECURSIVE CALL HERE.
|
|
//
|
|
|
|
NetStatus = ReplTreeIntegritySync( MasterBuffer,
|
|
ClientBuffer,
|
|
master_path,
|
|
tmp_path, // BUGBUG?
|
|
tmp_path,
|
|
tree_rec);
|
|
}
|
|
|
|
//
|
|
// Quit immediately on any error.
|
|
//
|
|
|
|
if (NetStatus) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Also quit if service is stopping.
|
|
//
|
|
if (ReplGlobalIsServiceStopping) {
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
} while ( ReplFindNextFile( FindHandle, &FindData ));
|
|
|
|
}
|
|
|
|
} // end TREE extent.
|
|
|
|
NetStatus = NO_ERROR;
|
|
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
|
|
Cleanup:
|
|
*(master_path + master_index) = '\0';
|
|
if ( client_path != NULL ) {
|
|
*(client_path + client_index) = '\0';
|
|
}
|
|
*(tmp_path + tmp_index) = '\0';
|
|
|
|
if (FindHandle != INVALID_REPL_HANDLE) {
|
|
(VOID) ReplFindClose( FindHandle );
|
|
}
|
|
|
|
if ( NetStatus == NO_ERROR ) {
|
|
|
|
//
|
|
// Update attributes, times, ACL, etc.
|
|
//
|
|
|
|
NetStatus = ReplCopyDirectoryItself(
|
|
master_path, // src
|
|
tmp_path, // dest (temp tree, which caller will rename)
|
|
FALSE ); // don't fail if already exists.
|
|
|
|
if (NetStatus != NO_ERROR) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplTreeIntegritySync: got status " FORMAT_API_STATUS
|
|
" from ReplCopyDirectoryItself\n", NetStatus ));
|
|
}
|
|
}
|
|
|
|
if (NetStatus != NO_ERROR) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplTreeIntegritySync: returning status " FORMAT_API_STATUS
|
|
".\n", NetStatus ));
|
|
|
|
if ((tree_rec->alerts & UPDATE_ERROR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUpdateError,
|
|
NELOG_ReplUpdateError,
|
|
NetStatus,
|
|
tree_rec->dir_name,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= UPDATE_ERROR_ALERT;
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBGSTATIC VOID
|
|
ReplFileIntegritySync(
|
|
IN OUT PVAR_BUF MasterBuffer,
|
|
IN OUT PVAR_BUF ClientBuffer,
|
|
IN LPTSTR master_path,
|
|
IN LPTSTR client_path,
|
|
IN LPTSTR tmp_path,
|
|
IN PCLIENT_LIST_REC tree_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does a complete file integrity sync for 1 dir, recursively.
|
|
|
|
Called by ReplSyncTree and by itself recursively.
|
|
|
|
Arguments:
|
|
|
|
MasterBuffer - Buffer to be used to contain FIND_DATA structures of
|
|
the master directory.
|
|
|
|
ClientBuffer - Buffer to be used to contain FIND_DATA structures of
|
|
the client directory.
|
|
|
|
client_path - UNC to client's dir. This buffer is modified during
|
|
the execution of this routine but is returned to its original
|
|
value on completion.
|
|
|
|
master_path - UNC to master's dir. This buffer is modified during
|
|
the execution of this routine but is returned to its original
|
|
value on completion.
|
|
|
|
tmp_path - UNC to import\TMPFILE.RP$
|
|
|
|
tree_rec - pointer to dir's CLIENT_LIST_REC.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
DWORD cli_dir_cnt;
|
|
LPREPL_FIND_HANDLE FindHandle = INVALID_REPL_HANDLE;
|
|
DWORD mas_dir_cnt;
|
|
DWORD master_index;
|
|
DWORD client_index;
|
|
DWORD i, j;
|
|
|
|
|
|
IF_DEBUG( MAJOR ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"scanning '" FORMAT_LPTSTR "'...\n", client_path ));
|
|
}
|
|
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplFileIntegritySync: master path='" FORMAT_LPTSTR
|
|
"', client path=" FORMAT_LPTSTR "'.\n", master_path,
|
|
client_path ));
|
|
}
|
|
|
|
//
|
|
// Get the file names of the master and client directories.
|
|
//
|
|
|
|
master_index = STRLEN(master_path);
|
|
client_index = STRLEN(client_path);
|
|
|
|
if ( ReplReadSortDir( master_path, &mas_dir_cnt, MasterBuffer, tree_rec) !=
|
|
NO_ERROR ) {
|
|
return;
|
|
}
|
|
|
|
if ( ReplReadSortDir( client_path, &cli_dir_cnt, ClientBuffer, tree_rec) !=
|
|
NO_ERROR ) {
|
|
return;
|
|
}
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplFileIntegritySync: " FORMAT_DWORD
|
|
" master dirs/files and " FORMAT_DWORD " on client.\n",
|
|
mas_dir_cnt, cli_dir_cnt ));
|
|
ReplDisplayFileFindArray(
|
|
(LPTSTR) TEXT("***master***"),
|
|
(LPVOID) MasterBuffer->buf,
|
|
mas_dir_cnt );
|
|
ReplDisplayFileFindArray(
|
|
(LPTSTR) TEXT("***client***"),
|
|
(LPVOID) ClientBuffer->buf,
|
|
cli_dir_cnt );
|
|
|
|
}
|
|
|
|
//
|
|
// see if dirs are empty.
|
|
//
|
|
|
|
if ((mas_dir_cnt == 0) && (cli_dir_cnt == 0)) {
|
|
ApiStatus = NO_ERROR;
|
|
goto Cleanup; // Make sure we update timestamp for empty dir.
|
|
}
|
|
|
|
//
|
|
// now the works.
|
|
//
|
|
|
|
i = j = 0;
|
|
|
|
while ((i < mas_dir_cnt) || (j < cli_dir_cnt)) {
|
|
INT State;
|
|
|
|
//
|
|
// Determine if the file/dir exists as master/client/both.
|
|
//
|
|
|
|
if ((i < mas_dir_cnt) && (j < cli_dir_cnt)) {
|
|
State = STRICMP( MasterBuffer->array[i]->fdFound.cFileName,
|
|
ClientBuffer->array[j]->fdFound.cFileName );
|
|
|
|
if ( State < 0 ) {
|
|
State = ONLY_AT_MASTER;
|
|
} else if ( State > 0 ) {
|
|
State = ONLY_AT_CLIENT;
|
|
} else {
|
|
State = AT_BOTH;
|
|
}
|
|
|
|
//
|
|
// either i or j is exhausted.
|
|
//
|
|
|
|
} else {
|
|
if (i < mas_dir_cnt) {
|
|
State = ONLY_AT_MASTER;
|
|
} else {
|
|
State = ONLY_AT_CLIENT;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// File not present at Master ==> so delete at client.
|
|
//
|
|
|
|
switch (State) {
|
|
|
|
case ONLY_AT_CLIENT: {
|
|
|
|
//
|
|
// Don't delete any signal files.
|
|
//
|
|
|
|
if ( !ReplIgnoreDirOrFileName(
|
|
ClientBuffer->array[j]->fdFound.cFileName ) ) {
|
|
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1,
|
|
ClientBuffer->array[j]->fdFound.cFileName);
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplFileIntegritySync: deleting "
|
|
FORMAT_LPTSTR " (only at client).\n",
|
|
client_path ));
|
|
}
|
|
|
|
(VOID) ReplFileIntegrityDel(
|
|
client_path,
|
|
ClientBuffer->array[j]->fdFound.dwFileAttributes,
|
|
tree_rec);
|
|
|
|
*(client_path + client_index) = '\0';
|
|
}
|
|
|
|
j++; // advance client index.
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Files present only at Master ==> added - must copy to client.
|
|
//
|
|
|
|
case ONLY_AT_MASTER:
|
|
|
|
//
|
|
// Don't copy REPL.INI or userlock files from master.
|
|
//
|
|
|
|
if ( !ReplIgnoreDirOrFileName(
|
|
MasterBuffer->array[i]->fdFound.cFileName ) ) {
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
|
|
(VOID) ReplSyncCopy( master_path, client_path, tree_rec );
|
|
|
|
*(master_path + master_index) = '\0';
|
|
*(client_path + client_index) = '\0';
|
|
}
|
|
|
|
i++; // advance master index.
|
|
|
|
break;
|
|
|
|
|
|
|
|
//
|
|
// File exists at both master and client.
|
|
//
|
|
|
|
case AT_BOTH:
|
|
|
|
//
|
|
// Only copy the file if it is actually different at the master.
|
|
//
|
|
|
|
if(ReplFileCompare(MasterBuffer->array[i],
|
|
ClientBuffer->array[j]) ==COPY_FROM_MASTER){
|
|
|
|
DWORD MasterAttributes
|
|
= MasterBuffer->array[i]->fdFound.dwFileAttributes;
|
|
|
|
//
|
|
// If it is a file, nuke it and copy the new one.
|
|
//
|
|
if ( (MasterAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ) {
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1,
|
|
MasterBuffer->array[i]->fdFound.cFileName);
|
|
|
|
// Copy this file. (Dir will be done below.)
|
|
(VOID) ReplFileIntegrityCopy(
|
|
master_path,
|
|
client_path,
|
|
tmp_path,
|
|
tree_rec,
|
|
MasterAttributes,
|
|
ClientBuffer->array[j]->fdFound.dwFileAttributes);
|
|
// Errors ignored? Yes. The checksum later will find
|
|
// the different, and the error has already been logged.
|
|
|
|
*(master_path + master_index) = '\0';
|
|
*(client_path + client_index) = '\0';
|
|
|
|
} else {
|
|
|
|
//
|
|
// Directory timestamp will be fixed below.
|
|
// EAs, attributes, ACL, etc., are also fixed up below.
|
|
//
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"MISMATCH(master, entry " FORMAT_DWORD ") '"
|
|
FORMAT_LPTSTR "':",
|
|
i,
|
|
MasterBuffer->array[i]->fdFound.cFileName ));
|
|
NetpDbgDisplayFileTime(
|
|
&(MasterBuffer->array[i]->fdFound.ftLastWriteTime));
|
|
NetpKdPrint(( "\n" ));
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"MISMATCH(client, entry " FORMAT_DWORD ") '"
|
|
FORMAT_LPTSTR "':",
|
|
j,
|
|
ClientBuffer->array[j]->fdFound.cFileName ));
|
|
NetpDbgDisplayFileTime(
|
|
&(ClientBuffer->array[j]->fdFound.ftLastWriteTime));
|
|
NetpKdPrint(( "\n" ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
i++;
|
|
j++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NetpAssert( FALSE ); // invalid state!
|
|
ApiStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
} // switch
|
|
|
|
//
|
|
// Quit if service is stopping.
|
|
//
|
|
if (ReplGlobalIsServiceStopping) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
} // big while loop
|
|
|
|
|
|
//
|
|
// Now we are left with the sub-directories that are present at both sides
|
|
// we have taken care of the cases where a dir is missing at either side.
|
|
//
|
|
|
|
if (tree_rec->extent == REPL_EXTENT_TREE) {
|
|
|
|
REPL_WIN32_FIND_DATA FindData;
|
|
|
|
//
|
|
// Walk the client tree finding sub-directories to copy from the master.
|
|
// We don't walk the MasterBuffer (or ClientBuffer) list since we
|
|
// want to re-use that same buffer for the recursion.
|
|
//
|
|
|
|
(void) STRCPY(client_path + client_index, SLASH);
|
|
(void) STRCPY(client_path + client_index + 1, STAR_DOT_STAR);
|
|
|
|
FindHandle = ReplFindFirstFile( client_path, &FindData );
|
|
|
|
if ( FindHandle != INVALID_REPL_HANDLE ) {
|
|
|
|
do {
|
|
if (FindData.fdFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
//
|
|
// make sure it's not '.' or '..'.
|
|
//
|
|
|
|
if (STRCMP(FindData.fdFound.cFileName, DOT) == 0) {
|
|
continue;
|
|
}
|
|
if (STRCMP(FindData.fdFound.cFileName, DOT_DOT) == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Only recurse if this directory exists at the master.
|
|
//
|
|
|
|
(void) STRCPY(master_path + master_index, SLASH);
|
|
(void) STRCPY(master_path + master_index + 1, FindData.fdFound.cFileName);
|
|
|
|
if ( ReplFileOrDirExists( master_path )) {
|
|
|
|
//
|
|
// RECURSIVE CALL HERE.
|
|
//
|
|
|
|
(void) STRCPY( client_path + client_index + 1,
|
|
FindData.fdFound.cFileName);
|
|
|
|
ReplFileIntegritySync( MasterBuffer,
|
|
ClientBuffer,
|
|
master_path,
|
|
client_path,
|
|
tmp_path,
|
|
tree_rec);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Also quit if service is stopping.
|
|
//
|
|
if (ReplGlobalIsServiceStopping) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
} while ( ReplFindNextFile( FindHandle, &FindData ));
|
|
|
|
}
|
|
|
|
} // end TREE extent
|
|
|
|
Cleanup:
|
|
|
|
*(master_path + master_index) = '\0';
|
|
*(client_path + client_index) = '\0';
|
|
|
|
if (FindHandle != INVALID_REPL_HANDLE) {
|
|
(VOID) ReplFindClose( FindHandle );
|
|
}
|
|
|
|
//
|
|
// Update times, ACL, etc.
|
|
//
|
|
|
|
ApiStatus = ReplCopyDirectoryItself(
|
|
master_path, // src
|
|
client_path, // dest
|
|
FALSE ); // don't fail if already exists.
|
|
|
|
if (ApiStatus != NO_ERROR) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplFileIntegritySync: got status " FORMAT_API_STATUS
|
|
" from ReplCopyDirectoryItself\n", ApiStatus ));
|
|
|
|
if ((tree_rec->alerts & UPDATE_ERROR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUpdateError,
|
|
NELOG_ReplUpdateError,
|
|
ApiStatus,
|
|
tree_rec->dir_name,
|
|
tree_rec->master,
|
|
NO_EXIT);
|
|
tree_rec->alerts |= UPDATE_ERROR_ALERT;
|
|
}
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ReplSyncTree(
|
|
IN PMSG_STATUS_REC upd_rec,
|
|
IN OUT PCLIENT_LIST_REC tree_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ReplSyncTree will sync the specified directory, do another checksum,
|
|
and update state and checksum in client record.
|
|
|
|
Assumes that caller has a lock (any kind) on RCGlobalClientListLock.
|
|
|
|
Arguments:
|
|
|
|
upd_rec - Specifies the desired checksum and file count from the master.
|
|
|
|
tree_rec - points to client_rec struct for updated dir/tree.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
BOOL ExportLocked;
|
|
TCHAR UncMasterName[UNCLEN+1];
|
|
|
|
TCHAR master_path[MAX_PATH];
|
|
TCHAR client_path[MAX_PATH];
|
|
TCHAR tmp_path[MAX_PATH];
|
|
|
|
VAR_BUF MasterBuffer, ClientBuffer;
|
|
DWORD import_index;
|
|
|
|
CHECKSUM_REC ck_rec;
|
|
|
|
#define WARN_ABOUT_DELETE( fileName ) \
|
|
{ \
|
|
IF_DEBUG( SYNC ) { \
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT \
|
|
"ReplSyncTree: ***DELETING*** " FORMAT_LPTSTR ".\n", \
|
|
fileName )); \
|
|
} \
|
|
}
|
|
#define WARN_ABOUT_MOVEFILE( oldName, newName ) \
|
|
{ \
|
|
IF_DEBUG( MAJOR ) { \
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT \
|
|
"ReplSyncTree: ***MOVEFILE*** " FORMAT_LPTSTR " to " \
|
|
FORMAT_LPTSTR ".\n", oldName, newName )); \
|
|
} \
|
|
}
|
|
|
|
NetpAssert( tree_rec != NULL );
|
|
NetpAssert( upd_rec != NULL );
|
|
|
|
//
|
|
// form master's path UNC.
|
|
//
|
|
|
|
(void) STRCPY(master_path, SLASH_SLASH);
|
|
(void) STRCAT(master_path, tree_rec->master);
|
|
(void) STRCAT(master_path, SLASH);
|
|
(void) STRCAT(master_path, REPL_SHARE);
|
|
(void) STRCAT(master_path, SLASH);
|
|
(void) STRCAT(master_path, tree_rec->dir_name);
|
|
|
|
//
|
|
// form client's path UNC.
|
|
//
|
|
|
|
ACQUIRE_LOCK_SHARED( ReplConfigLock );
|
|
import_index = STRLEN(ReplConfigImportPath);
|
|
(void) STRCPY(client_path, ReplConfigImportPath);
|
|
(void) STRCAT(client_path, SLASH);
|
|
(void) STRCAT(client_path, tree_rec->dir_name);
|
|
RELEASE_LOCK( ReplConfigLock );
|
|
|
|
//
|
|
// Form master's server name UNC.
|
|
//
|
|
(VOID) STRCPY( UncMasterName, SLASH_SLASH );
|
|
(VOID) STRCAT( UncMasterName, tree_rec->master );
|
|
NetpAssert( NetpIsUncComputerNameValid( UncMasterName ) );
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: processing opcode " FORMAT_DWORD
|
|
" for master '" FORMAT_LPTSTR "'"
|
|
", dir '" FORMAT_LPTSTR "',\n"
|
|
" master path '" FORMAT_LPTSTR "', client_path '"
|
|
FORMAT_LPTSTR "'.\n",
|
|
upd_rec->opcode,
|
|
UncMasterName,
|
|
tree_rec->dir_name, master_path, client_path ));
|
|
}
|
|
|
|
//
|
|
// Allocate 2 buffers to compare 2 dirs.
|
|
//
|
|
|
|
if ( ReplAllocBuf(tree_rec, &MasterBuffer, ALLOC_BUF) != NO_ERROR ) {
|
|
return;
|
|
}
|
|
if ( ReplAllocBuf(tree_rec, &ClientBuffer, ALLOC_BUF) != NO_ERROR ) {
|
|
(VOID) ReplAllocBuf(tree_rec, &MasterBuffer, FREE_BUF);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Handle Tree Integrity
|
|
//
|
|
|
|
if (tree_rec->integrity == REPL_INTEGRITY_TREE) {
|
|
|
|
//
|
|
// Form tmp path UNC.
|
|
//
|
|
|
|
ACQUIRE_LOCK_SHARED( ReplConfigLock );
|
|
(void) STRCPY(tmp_path, ReplConfigImportPath);
|
|
(void) STRCAT(tmp_path, SLASH);
|
|
(void) STRCAT(tmp_path, TMP_TREE);
|
|
RELEASE_LOCK( ReplConfigLock );
|
|
|
|
|
|
//
|
|
// If the user has locked us from replication,
|
|
// don't sync now.
|
|
//
|
|
|
|
if (tree_rec->lockcount > 0) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: import locked)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto Cleanup;
|
|
}
|
|
|
|
ApiStatus = ReplCheckExportLocks(
|
|
UncMasterName, // server name to check locks on
|
|
(LPCTSTR) tree_rec->dir_name,
|
|
&ExportLocked );
|
|
if ( ExportLocked || (ApiStatus!=NO_ERROR) ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: export locked or down)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If we can't create the temporary directory,
|
|
// don't sync now.
|
|
//
|
|
|
|
if (ReplCreateTempDir(tmp_path, tree_rec)) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: can't create temp dir)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Sync master tree into temp tree.
|
|
//
|
|
|
|
ApiStatus = ReplTreeIntegritySync( &MasterBuffer,
|
|
&ClientBuffer,
|
|
master_path,
|
|
client_path,
|
|
tmp_path,
|
|
tree_rec);
|
|
|
|
|
|
if ( ApiStatus != NO_ERROR ) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: sync failed)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_1;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If the new directory doesn't match the master copy,
|
|
// try again after the guard time has elapsed.
|
|
//
|
|
// Notice that the master only sends us a pulse for a guarded
|
|
// tree integrity directory if and only if it thinks the tree
|
|
// has been stable for the guard time. Therefore, if we're
|
|
// able to duplicate a tree that the master has told us to
|
|
// duplicate, we know a priori that the guard time has already been
|
|
// statisfied.
|
|
//
|
|
|
|
if (!ChecksumEqual(UncMasterName, tmp_path, upd_rec, &ck_rec)) {
|
|
|
|
//
|
|
// If there is no guard time,
|
|
// don't do anything here.
|
|
//
|
|
|
|
if (tree_rec->guard_time != 0) {
|
|
|
|
//
|
|
// If this is the guard check itself,
|
|
// don't both doing another guard check.
|
|
//
|
|
|
|
if (tree_rec->timer.grd_count != (DWORD) -1) {
|
|
tree_rec->timer.grd_count = (DWORD) -1;
|
|
|
|
//
|
|
// If this call was the result of the initial pulse from
|
|
// the master, set up a guard request to try again later.
|
|
//
|
|
|
|
} else {
|
|
tree_rec->timer.grd_checksum = ck_rec.checksum;
|
|
tree_rec->timer.grd_count = ck_rec.count;
|
|
|
|
// Note: ReplSetTimeOut needs shared RCGlobalClientListLock.
|
|
ReplSetTimeOut(GUARD_TIMEOUT, tree_rec);
|
|
}
|
|
}
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: checksum mismatch)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_1;
|
|
}
|
|
|
|
//
|
|
// On success, always clear the guard request.
|
|
//
|
|
|
|
tree_rec->timer.grd_count = (DWORD) -1;
|
|
|
|
//
|
|
// Check for user locks. If none...
|
|
// Create our own lock, so we can lock out apps.
|
|
// ReplCreateReplLock will also put dir name in CrashDir in the
|
|
// registry, so we can recover if we die while copying this stuff.
|
|
//
|
|
|
|
// Note: ReplCreateReplLock any lock on RCGlobalClientListLock.
|
|
ApiStatus = ReplCreateReplLock( tree_rec );
|
|
if (ApiStatus != NO_ERROR) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: ReplCreateReplLock failed).\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_1;
|
|
}
|
|
|
|
//
|
|
// Check for any open files.
|
|
//
|
|
|
|
if ( ReplAnyRemoteFilesOpen(client_path)) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: remote files open)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_2;
|
|
}
|
|
|
|
//
|
|
// rename old client's dir to TMPTREEX.RP$.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + import_index + 1, TMP_TREEX);
|
|
|
|
WARN_ABOUT_MOVEFILE( client_path, tmp_path );
|
|
if ( !MoveFile( client_path, tmp_path ) ) {
|
|
|
|
ApiStatus = GetLastError();
|
|
|
|
//
|
|
// Might fail if it is current dir for some user process.
|
|
//
|
|
|
|
if (ApiStatus == ERROR_SHARING_VIOLATION) {
|
|
if ((tree_rec->alerts & USER_CURDIR_ALERT) == 0) {
|
|
AlertLogExit( ALERT_ReplUserCurDir,
|
|
NELOG_ReplUserCurDir,
|
|
0,
|
|
client_path,
|
|
NULL,
|
|
NO_EXIT);
|
|
|
|
tree_rec->alerts |= USER_CURDIR_ALERT;
|
|
}
|
|
}
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: "
|
|
"move file to TMPTREEX.RP$ failed)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_3;
|
|
}
|
|
|
|
//
|
|
// Rename new synced TMPTREE.RP$ dir to client's dir.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + import_index + 1, TMP_TREE);
|
|
|
|
WARN_ABOUT_MOVEFILE( tmp_path, client_path );
|
|
if ( !MoveFile( tmp_path, client_path )) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (tree integrity: "
|
|
"move file TMPTREE.RP$ to destination failed)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto CLEANUP_3;
|
|
}
|
|
|
|
//
|
|
// That's it sync done - now update all status variables.
|
|
//
|
|
|
|
tree_rec->checksum = ck_rec.checksum;
|
|
tree_rec->count = ck_rec.count;
|
|
tree_rec->timer.grd_timeout = 0;
|
|
tree_rec->timestamp = NetpReplTimeNow();
|
|
tree_rec->alerts = 0;
|
|
|
|
// Note: ReplSetTimeOut and ReplSetSignalFile need lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetTimeOut(PULSE_1_TIMEOUT, tree_rec);
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting state OK (tree integrity).\n" ));
|
|
}
|
|
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_OK);
|
|
|
|
AlertLogExit( ALERT_ReplDataChanged,
|
|
0,
|
|
0,
|
|
tree_rec->dir_name,
|
|
NULL,
|
|
NO_EXIT);
|
|
|
|
//
|
|
// Delete the old client tree.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + import_index + 1, TMP_TREEX);
|
|
(VOID) ReplTreeDelete(tmp_path);
|
|
goto CLEANUP_2;
|
|
|
|
|
|
//
|
|
// Cleanup to restore the old client tree and the correct tree.
|
|
//
|
|
|
|
CLEANUP_3:
|
|
(void) STRCPY(tmp_path + import_index + 1, TMP_TREEX);
|
|
WARN_ABOUT_MOVEFILE( tmp_path + import_index + 1, TMP_TREEX );
|
|
(VOID) MoveFile(tmp_path, client_path);
|
|
|
|
//
|
|
// Cleanup to remove the lock (if any).
|
|
//
|
|
|
|
CLEANUP_2:
|
|
|
|
//
|
|
// Undo client list update.
|
|
//
|
|
NetpAssert( tree_rec->lockcount == 1 );
|
|
ApiStatus = ReplDecrLockFields(
|
|
& (tree_rec->lockcount),
|
|
& (tree_rec->time_of_first_lock),
|
|
REPL_UNLOCK_NOFORCE );
|
|
NetpAssert( ApiStatus == NO_ERROR );
|
|
NetpAssert( tree_rec->lockcount == 0 );
|
|
|
|
//
|
|
// Update registry import entry (decr lock count).
|
|
//
|
|
ApiStatus = ImportDirUnlockInRegistry(
|
|
NULL, // no server name.
|
|
tree_rec->dir_name,
|
|
REPL_UNLOCK_NOFORCE );
|
|
NetpAssert( ApiStatus == NO_ERROR );
|
|
|
|
//
|
|
// Delete CrashDir from registry.
|
|
//
|
|
ApiStatus = ReplDeleteCrashDirRecord();
|
|
NetpAssert( ApiStatus == NO_ERROR );
|
|
|
|
//
|
|
// Just delete the new temporary tree (if any).
|
|
//
|
|
|
|
CLEANUP_1:
|
|
(void) STRCPY( tmp_path + import_index, SLASH);
|
|
(void) STRCPY( tmp_path + import_index + 1, TMP_TREE);
|
|
if (ReplFileOrDirExists(tmp_path)) { // Avoid warning if not needed.
|
|
(VOID) ReplTreeDelete(tmp_path);
|
|
}
|
|
|
|
//
|
|
// Handle file integrity.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Form tmp file UNC.
|
|
//
|
|
|
|
ACQUIRE_LOCK_SHARED( ReplConfigLock );
|
|
(void) STRCPY(tmp_path, ReplConfigImportPath);
|
|
(void) STRCAT(tmp_path, SLASH);
|
|
(void) STRCAT(tmp_path, TMP_FILE);
|
|
RELEASE_LOCK( ReplConfigLock );
|
|
|
|
//
|
|
// If the user has locked us from replication,
|
|
// don't sync now.
|
|
//
|
|
|
|
if (tree_rec->lockcount > 0) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (file integrity: import locked)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto Cleanup;
|
|
}
|
|
|
|
ApiStatus = ReplCheckExportLocks(
|
|
UncMasterName, // server name to check locks on
|
|
(LPCTSTR) tree_rec->dir_name,
|
|
&ExportLocked );
|
|
if ( ExportLocked || (ApiStatus!=NO_ERROR) ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (file integrity: export locked or down)\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
// Sync master tree into Client tree.
|
|
//
|
|
|
|
ReplFileIntegritySync( &MasterBuffer,
|
|
&ClientBuffer,
|
|
master_path,
|
|
client_path,
|
|
tmp_path,
|
|
tree_rec);
|
|
|
|
//
|
|
// Note we could do just a partial update, so in any case we need
|
|
// to update dir's checksum and count to actual value. If it was
|
|
// not complete - I.E ChecksumEqual will fail we do not update
|
|
// any other status variables because we know we are in some way
|
|
// out of date
|
|
//
|
|
|
|
|
|
//
|
|
// If our checksum doesn't match the master's,
|
|
// just remember our checksum,
|
|
// and mark the directory as needing syncing.
|
|
//
|
|
|
|
if (!ChecksumEqual(UncMasterName, client_path, upd_rec, &ck_rec)) {
|
|
tree_rec->checksum = ck_rec.checksum;
|
|
tree_rec->count = ck_rec.count;
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting no sync"
|
|
" (file integrity: checksum mismatch).\n" ));
|
|
|
|
// Note: ReplSetSignalFile needs lock (any kind) on
|
|
// RCGlobalClientListLock.
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_NO_SYNC);
|
|
|
|
//
|
|
// If our checksum does match the master,
|
|
// Indicate so everywhere.
|
|
//
|
|
|
|
} else {
|
|
|
|
tree_rec->checksum = ck_rec.checksum;
|
|
tree_rec->count = ck_rec.count;
|
|
tree_rec->timer.grd_timeout = 0;
|
|
tree_rec->timestamp = NetpReplTimeNow();
|
|
tree_rec->alerts = 0;
|
|
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplSyncTree: setting state OK (file integrity).\n" ));
|
|
}
|
|
|
|
// Note: ReplSetTimeOut and ReplSetSignalFile need lock (any kind)
|
|
// on RCGlobalClientListLock.
|
|
ReplSetTimeOut(PULSE_1_TIMEOUT, tree_rec);
|
|
|
|
ReplSetSignalFile(tree_rec, REPL_STATE_OK);
|
|
|
|
AlertLogExit( ALERT_ReplDataChanged,
|
|
0,
|
|
0,
|
|
tree_rec->dir_name,
|
|
NULL,
|
|
NO_EXIT);
|
|
|
|
}
|
|
|
|
//
|
|
// Delete our temporary file.
|
|
//
|
|
|
|
WARN_ABOUT_DELETE( tmp_path );
|
|
(VOID) ReplDeleteFile( tmp_path );
|
|
}
|
|
|
|
//
|
|
// Free the two allocated buffers
|
|
//
|
|
|
|
Cleanup:
|
|
(VOID) ReplAllocBuf(tree_rec, &MasterBuffer, FREE_BUF);
|
|
(VOID) ReplAllocBuf(tree_rec, &ClientBuffer, FREE_BUF);
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
ChecksumEqual(
|
|
IN LPCTSTR UncMasterName,
|
|
IN LPTSTR tmp_path,
|
|
IN PMSG_STATUS_REC upd_rec,
|
|
OUT PCHECKSUM_REC check_rec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compute the checksum on the tree / dir (according to extent) under
|
|
tmp_path and compare to those stored in upd_rec.
|
|
|
|
Arguments:
|
|
|
|
tmp_path - Path to combine the checksum on.
|
|
|
|
upd_rec - the desired checksum to compare with.
|
|
|
|
check_rec - Returns the computed checksum.
|
|
|
|
Return Value:
|
|
|
|
TRUE: The checksum for tmp_path and upd_rec are the same.
|
|
FALSE: Otherwise
|
|
|
|
Threads:
|
|
|
|
Only called by syncer thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
LONG MasterTimeZoneOffsetSecs; // exporter offset from GMT
|
|
|
|
IF_DEBUG( MAJOR ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"Doing checksum for tree '" FORMAT_LPTSTR "', master '"
|
|
FORMAT_LPTSTR "'\n",
|
|
tmp_path, UncMasterName ));
|
|
}
|
|
|
|
ApiStatus = ReplGetTimeCacheValue(
|
|
UncMasterName,
|
|
&MasterTimeZoneOffsetSecs ); // offset (+ for West of GMT, etc).
|
|
if (ApiStatus != NO_ERROR) {
|
|
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"CompareChecksum FAILED CALLING ReplGetTimeCacheValue, "
|
|
"status is " FORMAT_API_STATUS ".\n",
|
|
ApiStatus ));
|
|
|
|
AlertLogExit(
|
|
ALERT_ReplSysErr,
|
|
NELOG_ReplSysErr,
|
|
ApiStatus,
|
|
NULL,
|
|
NULL,
|
|
NO_EXIT);
|
|
|
|
return (FALSE); // closest we can get to telling caller.
|
|
}
|
|
|
|
//
|
|
// Compute the checksum for the passed-in tree.
|
|
//
|
|
|
|
if (upd_rec->extent == REPL_EXTENT_FILE) {
|
|
ScanDir(MasterTimeZoneOffsetSecs, tmp_path, check_rec, 0);
|
|
} else {
|
|
ScanTree(MasterTimeZoneOffsetSecs, tmp_path, check_rec);
|
|
}
|
|
|
|
//
|
|
// see if scan tree completed ok.
|
|
//
|
|
|
|
if (check_rec->count == (DWORD)-1) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ChecksumEqual: ERROR! COUNT IS -1!!!\n" ));
|
|
// BUGBUG: log this?
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// see if checksum and count are same as those in update.
|
|
//
|
|
|
|
if ((upd_rec->checksum == check_rec->checksum)
|
|
&& (((DWORD)upd_rec->count) == check_rec->count)) {
|
|
return TRUE;
|
|
|
|
} else {
|
|
IF_DEBUG( SYNC ) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ChecksumEqual: DID NOT MATCH...\n" ));
|
|
#define SHOW_ONE( name, rec_ptr ) \
|
|
NetpKdPrint(( \
|
|
" " name " checksum=" FORMAT_CHECKSUM ", count=" \
|
|
FORMAT_DWORD ",\n", rec_ptr->checksum, rec_ptr->count ))
|
|
|
|
SHOW_ONE( "master", upd_rec );
|
|
SHOW_ONE( "client actual", check_rec );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
ReplCrashRecovery(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks for any crashed dir - i.e. crashed while syncing it.
|
|
|
|
if (dir TMPTREE.RP$ exists) and (dir TMPTREEX.RP$ exists)
|
|
assume sync was complete and all that was missing was to copy it
|
|
TMPTREEX.RP$ dir back into client's dir under IMPORT. Retrieves
|
|
the client's dir name from the CrashDir field in the registry and
|
|
copies the dir there.
|
|
|
|
otherwise if either TMP dirs exist simply removes them.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
NET_API_STATUS
|
|
|
|
Threads:
|
|
|
|
Only called by client thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
LPNET_CONFIG_HANDLE ConfigHandle = NULL;
|
|
LPTSTR CrashDir = NULL;
|
|
|
|
TCHAR client_path[MAX_PATH];
|
|
TCHAR tmp_path[MAX_PATH];
|
|
|
|
BOOL tmptree_exists = FALSE;
|
|
BOOL tmptreex_exists = FALSE;
|
|
DWORD tmp_index;
|
|
#if DBG
|
|
DWORD tmptreex_index;
|
|
#endif
|
|
DWORD Attributes;
|
|
|
|
|
|
//
|
|
// Determine if TMPTREE exists.
|
|
//
|
|
|
|
ACQUIRE_LOCK_SHARED( ReplConfigLock );
|
|
(void) STRCPY(tmp_path, ReplConfigImportPath);
|
|
RELEASE_LOCK( ReplConfigLock );
|
|
|
|
tmp_index = STRLEN(tmp_path);
|
|
(void) STRCPY(tmp_path + tmp_index++, SLASH);
|
|
(void) STRCPY(tmp_path + tmp_index, TMP_TREE);
|
|
|
|
Attributes = GetFileAttributes( tmp_path );
|
|
|
|
if ( Attributes != (DWORD) -1 && (Attributes & FILE_ATTRIBUTE_DIRECTORY) ) {
|
|
tmptree_exists = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine if TMPTREEX exists.
|
|
//
|
|
|
|
(void) STRCPY(tmp_path + tmp_index, TMP_TREEX);
|
|
#if DBG
|
|
tmptreex_index = STRLEN(tmp_path);
|
|
#endif
|
|
|
|
Attributes = GetFileAttributes( tmp_path );
|
|
|
|
if ( Attributes != (DWORD) -1 && (Attributes & FILE_ATTRIBUTE_DIRECTORY) ) {
|
|
tmptreex_exists = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// If both TMPTREE.RP$ and TMPTREEX.RP$ exists,
|
|
// assume sync was complete and all that was missing was to copy it
|
|
// TMPTREE.RP$ dir back into client's dir under IMPORT. Retrieves
|
|
// the client's dir name from the CrashDir field in the registry and
|
|
// copies the dir there. Deletes CrashDir field so we don't get confused.
|
|
//
|
|
|
|
if ( tmptree_exists && tmptreex_exists ) {
|
|
|
|
//
|
|
// Open the right section of the config file/whatever.
|
|
//
|
|
ApiStatus = NetpOpenConfigData(
|
|
& ConfigHandle,
|
|
NULL, // local (no server name)
|
|
(LPTSTR) SECT_NT_REPLICATOR,
|
|
TRUE); // read-only.
|
|
if (ApiStatus != NO_ERROR) {
|
|
// BUGBUG: Alert/event for this!
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCrashRecovery: UNEXPECTED ERROR " FORMAT_API_STATUS
|
|
" from NetpOpenConfigData.\n", ApiStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Read the pathname from the registry.
|
|
// Assume the pathname is correct.
|
|
//
|
|
|
|
ApiStatus = NetpGetConfigValue(
|
|
ConfigHandle,
|
|
(LPTSTR) REPL_KEYWORD_CRASHDIR,
|
|
& CrashDir ); // alloc and set pointer.
|
|
if (ApiStatus != NO_ERROR) {
|
|
// BUGBUG: Alert/event for this!
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCrashRecovery: UNEXPECTED ERROR " FORMAT_API_STATUS
|
|
" from NetpGetConfigValue.\n", ApiStatus ));
|
|
goto Cleanup;
|
|
}
|
|
NetpAssert( CrashDir != NULL );
|
|
|
|
//
|
|
// Make sure CrashDir syntax is valid.
|
|
//
|
|
if ( !ReplIsDirNameValid( CrashDir ) ) {
|
|
// BUGBUG: Alert/event for this!
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCrashRecovery: BAD CRASH DIR '" FORMAT_LPTSTR
|
|
"' in registry.\n", CrashDir ));
|
|
ApiStatus = ERROR_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// tmp path should be just tmptreex.
|
|
//
|
|
|
|
#if DBG
|
|
NetpAssert( *(tmp_path + tmptreex_index - 1) == TCHAR_EOS );
|
|
#endif
|
|
|
|
//
|
|
// Build path name of final client tree.
|
|
//
|
|
ACQUIRE_LOCK_SHARED( ReplConfigLock );
|
|
(VOID) STRCPY( client_path, ReplConfigImportPath );
|
|
RELEASE_LOCK( ReplConfigLock );
|
|
(VOID) STRCAT( client_path, SLASH );
|
|
(VOID) STRCAT( client_path, CrashDir );
|
|
|
|
//
|
|
// Fail if the original name already exists.
|
|
//
|
|
if (ReplFileOrDirExists( client_path ) ) {
|
|
ApiStatus = NERR_InternalError; // BUGBUG: better error code?
|
|
// BUGBUG: Alert/event for this!
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCrashRecovery: UNEXPECTED ERROR: '" FORMAT_LPTSTR
|
|
" exists but should not!\n", client_path ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the TMPTREEX directory to the original name.
|
|
//
|
|
|
|
|
|
ApiStatus = ReplCopyTree( tmp_path, client_path );
|
|
if (ApiStatus != NO_ERROR) {
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplCrashRecovery: UNEXPECTED ERROR " FORMAT_API_STATUS
|
|
" from ReplCopyTree.\n", ApiStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Decrement lock count for client dir.
|
|
//
|
|
ApiStatus = ImportDirUnlockInRegistry(
|
|
NULL,
|
|
CrashDir,
|
|
REPL_UNLOCK_NOFORCE );
|
|
if (ApiStatus != NO_ERROR) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
ApiStatus = NO_ERROR;
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Delete CrashDir so we don't do this again next time.
|
|
//
|
|
(VOID) ReplDeleteCrashDirRecord();
|
|
|
|
//
|
|
// Clean up the temp directories.
|
|
//
|
|
|
|
if (tmptreex_exists) {
|
|
(void) STRCPY(tmp_path + tmp_index, TMP_TREEX);
|
|
(VOID) ReplTreeDelete(tmp_path);
|
|
}
|
|
|
|
if (tmptree_exists) {
|
|
(void) STRCPY(tmp_path + tmp_index, TMP_TREE);
|
|
(VOID) ReplTreeDelete(tmp_path);
|
|
}
|
|
|
|
if (ApiStatus != NO_ERROR) {
|
|
ReplFinish( ApiStatus );
|
|
}
|
|
if (CrashDir != NULL) {
|
|
(VOID) NetApiBufferFree( CrashDir );
|
|
}
|
|
if (ConfigHandle != NULL) {
|
|
(VOID) NetpCloseConfigData( ConfigHandle );
|
|
}
|
|
|
|
return (ApiStatus);
|
|
|
|
} // ReplCrashRecovery
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
ReplDeleteCrashDirRecord(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Deletes registry entry for CrashDir.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
NET_API_STATUS
|
|
|
|
Threads:
|
|
|
|
Only called by client thread.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS ApiStatus;
|
|
LPNET_CONFIG_HANDLE ConfigHandle = NULL;
|
|
|
|
//
|
|
// Open the right section of the config file/whatever.
|
|
//
|
|
ApiStatus = NetpOpenConfigData(
|
|
& ConfigHandle,
|
|
NULL, // local (no server name)
|
|
(LPTSTR) SECT_NT_REPLICATOR,
|
|
FALSE); // not read-only
|
|
if (ApiStatus != NO_ERROR) {
|
|
// BUGBUG: Alert/event for this!
|
|
NetpKdPrint(( PREFIX_REPL_CLIENT
|
|
"ReplDeleteCrashDirRecord: UNEXPECTED ERROR " FORMAT_API_STATUS
|
|
" from NetpOpenConfigData.\n", ApiStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
ApiStatus = NetpDeleteConfigKeyword(
|
|
ConfigHandle,
|
|
(LPTSTR) REPL_KEYWORD_CRASHDIR );
|
|
|
|
Cleanup:
|
|
|
|
if (ConfigHandle != NULL) {
|
|
(VOID) NetpCloseConfigData( ConfigHandle );
|
|
}
|
|
|
|
return (ApiStatus);
|
|
|
|
} // ReplDeleteCrashDirRecord
|