2516 lines
75 KiB
C
2516 lines
75 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1997-1999 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
vvjoin.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
Our outbound partner requested a join but the outbound log does not
|
|||
|
contain the change orders needed to bring our outbound partner up-to-
|
|||
|
date. Instead, this thread will scan our idtable and generate
|
|||
|
refresh change orders for our outbound partner. This process is
|
|||
|
termed a vvjoin because the outbound partner's version vector is
|
|||
|
used as a filter when deciding which files and directories in our
|
|||
|
idtable will be sent.
|
|||
|
|
|||
|
Once the vvjoin is completed, our outbound partner will regenerate
|
|||
|
his version vector and attempt the join again with the outbound log
|
|||
|
sequence number saved at the beginning of the vvjoin. If that sequence
|
|||
|
number is no longer available in the oubound log, the vvjoin is
|
|||
|
repeated with, hopefully, fewer files being sent to our outbound
|
|||
|
partner.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Billy J Fuller 27-Jan-1998
|
|||
|
|
|||
|
Environment
|
|||
|
|
|||
|
User mode, winnt32
|
|||
|
|
|||
|
--*/
|
|||
|
#include <ntreppch.h>
|
|||
|
#pragma hdrstop
|
|||
|
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "vvjoin:"
|
|||
|
|
|||
|
#include <frs.h>
|
|||
|
#include <tablefcn.h>
|
|||
|
#include <perrepsr.h>
|
|||
|
|
|||
|
#if DBG
|
|||
|
DWORD VvJoinTrigger = 0;
|
|||
|
DWORD VvJoinReset = 0;
|
|||
|
DWORD VvJoinInc = 0;
|
|||
|
#define VV_JOIN_TRIGGER(_VvJoinContext_) \
|
|||
|
{ \
|
|||
|
if (VvJoinTrigger && !--VvJoinTrigger) { \
|
|||
|
VvJoinReset += VvJoinInc; \
|
|||
|
VvJoinTrigger = VvJoinReset; \
|
|||
|
DPRINT1(0, ":V: DEBUG -- VvJoin Trigger HIT; reset to %d\n", VvJoinTrigger); \
|
|||
|
*((DWORD *)&_VvJoinContext_->JoinGuid) += 1; \
|
|||
|
} \
|
|||
|
}
|
|||
|
#else DBG
|
|||
|
#define VV_JOIN_TRIGGER(_VvJoinContext_)
|
|||
|
#endif DBG
|
|||
|
|
|||
|
|
|||
|
#if DBG
|
|||
|
#define VVJOIN_TEST() VvJoinTest()
|
|||
|
#define VVJOIN_PRINT(_S_, _VC_) VvJoinPrint(_S_, _VC_)
|
|||
|
#define VVJOIN_PRINT_NODE(_S_, _I_, _N_) VvJoinPrintNode(_S_, _I_, _N_)
|
|||
|
#define VVJOIN_TEST_SKIP_BEGIN(_X_, _C_) VvJoinTestSkipBegin(_X_, _C_)
|
|||
|
#define VVJOIN_TEST_SKIP_CHECK(_X, _F, _B_) VvJoinTestSkipCheck(_X, _F, _B_)
|
|||
|
#define VVJOIN_TEST_SKIP_END(_X_) VvJoinTestSkipEnd(_X_)
|
|||
|
#define VVJOIN_TEST_SKIP(_C_) VvJoinTestSkip(_C_)
|
|||
|
#else DBG
|
|||
|
#define VVJOIN_TEST()
|
|||
|
#define VVJOIN_PRINT(_S_, _VC_)
|
|||
|
#define VVJOIN_PRINT_NODE(_S_, _I_, _N_)
|
|||
|
#define VVJOIN_TEST_SKIP_BEGIN(_X_, _C_)
|
|||
|
#define VVJOIN_TEST_SKIP_CHECK(_X_, _F_, _B_)
|
|||
|
#define VVJOIN_TEST_SKIP_END(_X_)
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Context global to a vvjoin thread
|
|||
|
//
|
|||
|
typedef struct _VVJOIN_CONTEXT {
|
|||
|
PRTL_GENERIC_TABLE Guids; // all nodes by guid
|
|||
|
PRTL_GENERIC_TABLE Originators; // originator nodes by originator
|
|||
|
DWORD (*Send)();
|
|||
|
// IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
// IN PVVJOIN_NODE VvJoinNode);
|
|||
|
DWORD NumberSent;
|
|||
|
PREPLICA Replica;
|
|||
|
PCXTION Cxtion;
|
|||
|
PGEN_TABLE CxtionVv;
|
|||
|
PGEN_TABLE ReplicaVv;
|
|||
|
PTHREAD_CTX ThreadCtx;
|
|||
|
PTABLE_CTX TableCtx;
|
|||
|
GUID JoinGuid;
|
|||
|
LONG MaxOutstandingCos;
|
|||
|
LONG OutstandingCos;
|
|||
|
LONG OlTimeout;
|
|||
|
LONG OlTimeoutMax;
|
|||
|
PWCHAR SkipDir;
|
|||
|
PWCHAR SkipFile1;
|
|||
|
PWCHAR SkipFile2;
|
|||
|
BOOL SkippedDir;
|
|||
|
BOOL SkippedFile1;
|
|||
|
BOOL SkippedFile2;
|
|||
|
} VVJOIN_CONTEXT, *PVVJOIN_CONTEXT;
|
|||
|
|
|||
|
//
|
|||
|
// One node per file from the idtable
|
|||
|
//
|
|||
|
typedef struct _VVJOIN_NODE {
|
|||
|
ULONG Flags;
|
|||
|
GUID FileGuid;
|
|||
|
GUID ParentGuid;
|
|||
|
GUID Originator;
|
|||
|
ULONGLONG Vsn;
|
|||
|
PRTL_GENERIC_TABLE Vsns;
|
|||
|
} VVJOIN_NODE, *PVVJOIN_NODE;
|
|||
|
|
|||
|
//
|
|||
|
// Maximum timeout (unless overridden by registry)
|
|||
|
//
|
|||
|
#define VVJOIN_TIMEOUT_MAX (180 * 1000)
|
|||
|
|
|||
|
//
|
|||
|
// Flags for VVJOIN_NODE
|
|||
|
//
|
|||
|
#define VVJOIN_FLAGS_ISDIR 0x00000001
|
|||
|
#define VVJOIN_FLAGS_SENT 0x00000002
|
|||
|
#define VVJOIN_FLAGS_ROOT 0x00000004
|
|||
|
#define VVJOIN_FLAGS_PROCESSING 0x00000008
|
|||
|
#define VVJOIN_FLAGS_OUT_OF_ORDER 0x00000010
|
|||
|
#define VVJOIN_FLAGS_DELETED 0x00000020
|
|||
|
|
|||
|
//
|
|||
|
// Maximum number of vvjoin threads PER CXTION
|
|||
|
//
|
|||
|
#define VVJOIN_MAXTHREADS_PER_CXTION (1)
|
|||
|
|
|||
|
//
|
|||
|
// Next entry in any gen table (don't splay)
|
|||
|
//
|
|||
|
#define VVJOIN_NEXT_ENTRY(_T_, _K_) \
|
|||
|
(PVOID)RtlEnumerateGenericTableWithoutSplaying(_T_, _K_)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
PCHANGE_ORDER_ENTRY
|
|||
|
ChgOrdMakeFromIDRecord(
|
|||
|
IN PIDTABLE_RECORD IDTableRec,
|
|||
|
IN PREPLICA Replica,
|
|||
|
IN ULONG LocationCmd,
|
|||
|
IN ULONG CoFlags,
|
|||
|
IN GUID *CxtionGuid
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
VvJoinAlloc(
|
|||
|
IN PRTL_GENERIC_TABLE Table,
|
|||
|
IN DWORD NodeSize
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Allocate space for a table entry. The entry includes the user-defined
|
|||
|
struct and some overhead used by the generic table routines. The
|
|||
|
generic table routines call this function when they need memory.
|
|||
|
|
|||
|
Arguments:
|
|||
|
Table - Address of the table (not used).
|
|||
|
NodeSize - Bytes to allocate
|
|||
|
|
|||
|
Return Value:
|
|||
|
Address of newly allocated memory.
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinAlloc:"
|
|||
|
|
|||
|
//
|
|||
|
// Need to check if NodeSize == 0 as FrsAlloc asserts if called with 0 as the first parameter (prefix fix).
|
|||
|
//
|
|||
|
if (NodeSize == 0) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
return FrsAlloc(NodeSize);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinFree(
|
|||
|
IN PRTL_GENERIC_TABLE Table,
|
|||
|
IN PVOID Buffer
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Free the space allocated by VvJoinAlloc(). The generic table
|
|||
|
routines call this function to free memory.
|
|||
|
|
|||
|
Arguments:
|
|||
|
Table - Address of the table (not used).
|
|||
|
Buffer - Address of previously allocated memory
|
|||
|
|
|||
|
Return Value:
|
|||
|
None.
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinFree:"
|
|||
|
FrsFree(Buffer);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
VvJoinFreeContext(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Free the context and its contents
|
|||
|
|
|||
|
Arguments:
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Return Value:
|
|||
|
None.
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinFreeContext:"
|
|||
|
JET_ERR jerr;
|
|||
|
PVVJOIN_NODE VvJoinNode;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
PVVJOIN_NODE *SubEntry;
|
|||
|
|
|||
|
//
|
|||
|
// Done
|
|||
|
//
|
|||
|
if (!VvJoinContext) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the entries in the generic table of originators. The nodes
|
|||
|
// addressed by the entries are freed below.
|
|||
|
//
|
|||
|
if (VvJoinContext->Originators) {
|
|||
|
while (Entry = RtlEnumerateGenericTable(VvJoinContext->Originators, TRUE)) {
|
|||
|
VvJoinNode = *Entry;
|
|||
|
if (VvJoinNode->Vsns) {
|
|||
|
while (SubEntry = RtlEnumerateGenericTable(VvJoinNode->Vsns, TRUE)) {
|
|||
|
RtlDeleteElementGenericTable(VvJoinNode->Vsns, SubEntry);
|
|||
|
}
|
|||
|
VvJoinNode->Vsns = FrsFree(VvJoinNode->Vsns);
|
|||
|
}
|
|||
|
RtlDeleteElementGenericTable(VvJoinContext->Originators, Entry);
|
|||
|
}
|
|||
|
VvJoinContext->Originators = FrsFree(VvJoinContext->Originators);
|
|||
|
}
|
|||
|
//
|
|||
|
// Free the entries in the generic table of files. The nodes are freed,
|
|||
|
// too, because no other table addresses them.
|
|||
|
//
|
|||
|
if (VvJoinContext->Guids) {
|
|||
|
while (Entry = RtlEnumerateGenericTable(VvJoinContext->Guids, TRUE)) {
|
|||
|
VvJoinNode = *Entry;
|
|||
|
RtlDeleteElementGenericTable(VvJoinContext->Guids, Entry);
|
|||
|
FrsFree(VvJoinNode);
|
|||
|
}
|
|||
|
VvJoinContext->Guids = FrsFree(VvJoinContext->Guids);
|
|||
|
}
|
|||
|
//
|
|||
|
// Free the version vector and the cxtion guid.
|
|||
|
//
|
|||
|
VVFreeOutbound(VvJoinContext->CxtionVv);
|
|||
|
VVFreeOutbound(VvJoinContext->ReplicaVv);
|
|||
|
|
|||
|
//
|
|||
|
// Jet table context
|
|||
|
//
|
|||
|
if (VvJoinContext->TableCtx) {
|
|||
|
DbsFreeTableContext(VvJoinContext->TableCtx,
|
|||
|
VvJoinContext->ThreadCtx->JSesid);
|
|||
|
}
|
|||
|
//
|
|||
|
// Now close the jet session and free the Jet ThreadCtx.
|
|||
|
//
|
|||
|
if (VvJoinContext->ThreadCtx) {
|
|||
|
jerr = DbsCloseJetSession(VvJoinContext->ThreadCtx);
|
|||
|
if (!JET_SUCCESS(jerr)) {
|
|||
|
DPRINT_JS(1,":V: DbsCloseJetSession : ", jerr);
|
|||
|
} else {
|
|||
|
DPRINT(4, ":V: DbsCloseJetSession complete\n");
|
|||
|
}
|
|||
|
VvJoinContext->ThreadCtx = FrsFreeType(VvJoinContext->ThreadCtx);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the context
|
|||
|
//
|
|||
|
FrsFree(VvJoinContext);
|
|||
|
|
|||
|
//
|
|||
|
// DONE
|
|||
|
//
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
RTL_GENERIC_COMPARE_RESULTS
|
|||
|
VvJoinCmpGuids(
|
|||
|
IN PRTL_GENERIC_TABLE Guids,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode1,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode2
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Compare the Guids of the two entries.
|
|||
|
|
|||
|
This function is used by the gentable runtime to compare entries.
|
|||
|
In this case, each entry in the table addresses a node.
|
|||
|
|
|||
|
Arguments:
|
|||
|
Guids - Sorted by Guid
|
|||
|
VvJoinNode1 - PVVJOIN_NODE
|
|||
|
VvJoinNode2 - PVVJOIN_NODE
|
|||
|
|
|||
|
Return Value:
|
|||
|
<0 First < Second
|
|||
|
=0 First == Second
|
|||
|
>0 First > Second
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinCmpGuids:"
|
|||
|
INT Cmp;
|
|||
|
|
|||
|
FRS_ASSERT(VvJoinNode1 && VvJoinNode2 && *VvJoinNode1 && *VvJoinNode2);
|
|||
|
|
|||
|
Cmp = memcmp(&(*VvJoinNode1)->FileGuid,
|
|||
|
&(*VvJoinNode2)->FileGuid,
|
|||
|
sizeof(GUID));
|
|||
|
if (Cmp < 0) {
|
|||
|
return GenericLessThan;
|
|||
|
} else if (Cmp > 0) {
|
|||
|
return GenericGreaterThan;
|
|||
|
}
|
|||
|
return GenericEqual;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
RTL_GENERIC_COMPARE_RESULTS
|
|||
|
VvJoinCmpVsns(
|
|||
|
IN PRTL_GENERIC_TABLE Vsns,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode1,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode2
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Compare the vsns of the two entries as unsigned values.
|
|||
|
|
|||
|
This function is used by the gentable runtime to compare entries.
|
|||
|
In this case, each entry in the table addresses a node.
|
|||
|
|
|||
|
Arguments:
|
|||
|
Vsns - Sorted by Vsn
|
|||
|
VvJoinNode1 - PVVJOIN_NODE
|
|||
|
VvJoinNode2 - PVVJOIN_NODE
|
|||
|
|
|||
|
Return Value:
|
|||
|
<0 First < Second
|
|||
|
=0 First == Second
|
|||
|
>0 First > Second
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinCmpVsns:"
|
|||
|
INT Cmp;
|
|||
|
|
|||
|
FRS_ASSERT(VvJoinNode1 && VvJoinNode2 && *VvJoinNode1 && *VvJoinNode2);
|
|||
|
|
|||
|
if ((ULONGLONG)(*VvJoinNode1)->Vsn > (ULONGLONG)(*VvJoinNode2)->Vsn) {
|
|||
|
return GenericGreaterThan;
|
|||
|
} else if ((ULONGLONG)(*VvJoinNode1)->Vsn < (ULONGLONG)(*VvJoinNode2)->Vsn) {
|
|||
|
return GenericLessThan;
|
|||
|
}
|
|||
|
return GenericEqual;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
RTL_GENERIC_COMPARE_RESULTS
|
|||
|
VvJoinCmpOriginators(
|
|||
|
IN PRTL_GENERIC_TABLE Originators,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode1,
|
|||
|
IN PVVJOIN_NODE *VvJoinNode2
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Compare the originators ID of the two entries.
|
|||
|
|
|||
|
This function is used by the gentable runtime to compare entries.
|
|||
|
In this case, each entry in the table addresses a node.
|
|||
|
|
|||
|
Arguments:
|
|||
|
Originators - Sorted by Guid
|
|||
|
VvJoinNode1 - PVVJOIN_NODE
|
|||
|
VvJoinNode2 - PVVJOIN_NODE
|
|||
|
|
|||
|
Return Value:
|
|||
|
<0 First < Second
|
|||
|
=0 First == Second
|
|||
|
>0 First > Second
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinCmpOriginators:"
|
|||
|
INT Cmp;
|
|||
|
|
|||
|
FRS_ASSERT(VvJoinNode1 && VvJoinNode2 && *VvJoinNode1 && *VvJoinNode2);
|
|||
|
|
|||
|
Cmp = memcmp(&(*VvJoinNode1)->Originator, &(*VvJoinNode2)->Originator, sizeof(GUID));
|
|||
|
|
|||
|
if (Cmp < 0) {
|
|||
|
return GenericLessThan;
|
|||
|
} else if (Cmp > 0) {
|
|||
|
return GenericGreaterThan;
|
|||
|
}
|
|||
|
return GenericEqual;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinInsertEntry(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
IN DWORD Flags,
|
|||
|
IN GUID *FileGuid,
|
|||
|
IN GUID *ParentGuid,
|
|||
|
IN GUID *Originator,
|
|||
|
IN PULONGLONG Vsn
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Insert the entry into the table of files (Guids) and the
|
|||
|
table of originators (Originators). This function is called
|
|||
|
during the IDTable scan.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
Flags
|
|||
|
FileGuid
|
|||
|
ParentGuid
|
|||
|
Originator
|
|||
|
Vsn
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinInsertEntry:"
|
|||
|
PVVJOIN_NODE VvJoinNode;
|
|||
|
PVVJOIN_NODE VvJoinOriginators;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
BOOLEAN IsNew;
|
|||
|
|
|||
|
//
|
|||
|
// First call, allocate the table of files
|
|||
|
//
|
|||
|
if (!VvJoinContext->Guids) {
|
|||
|
VvJoinContext->Guids = FrsAlloc(sizeof(RTL_GENERIC_TABLE));
|
|||
|
RtlInitializeGenericTable(VvJoinContext->Guids,
|
|||
|
VvJoinCmpGuids,
|
|||
|
VvJoinAlloc,
|
|||
|
VvJoinFree,
|
|||
|
NULL);
|
|||
|
}
|
|||
|
//
|
|||
|
// First call, allocate the table of originators
|
|||
|
//
|
|||
|
if (!VvJoinContext->Originators) {
|
|||
|
VvJoinContext->Originators = FrsAlloc(sizeof(RTL_GENERIC_TABLE));
|
|||
|
RtlInitializeGenericTable(VvJoinContext->Originators,
|
|||
|
VvJoinCmpOriginators,
|
|||
|
VvJoinAlloc,
|
|||
|
VvJoinFree,
|
|||
|
NULL);
|
|||
|
}
|
|||
|
//
|
|||
|
// One node per file
|
|||
|
//
|
|||
|
VvJoinNode = FrsAlloc(sizeof(VVJOIN_NODE));
|
|||
|
VvJoinNode->FileGuid = *FileGuid;
|
|||
|
VvJoinNode->ParentGuid = *ParentGuid;
|
|||
|
VvJoinNode->Originator = *Originator;
|
|||
|
VvJoinNode->Vsn = *Vsn;
|
|||
|
VvJoinNode->Flags = Flags;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Insert into the table of files
|
|||
|
//
|
|||
|
RtlInsertElementGenericTable(VvJoinContext->Guids,
|
|||
|
&VvJoinNode,
|
|||
|
sizeof(PVVJOIN_NODE),
|
|||
|
&IsNew);
|
|||
|
//
|
|||
|
// Duplicate Guids! The IDTable must be corrupt. Give up.
|
|||
|
//
|
|||
|
if (!IsNew) {
|
|||
|
return ERROR_DUP_NAME;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Must be the root of the replicated tree. The root directory
|
|||
|
// isn't replicated but is included in the table to make the
|
|||
|
// code for "directory scans" cleaner.
|
|||
|
//
|
|||
|
if (Flags & VVJOIN_FLAGS_SENT) {
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The table of originators is used to order the files when
|
|||
|
// sending them to our outbound partner.
|
|||
|
//
|
|||
|
// The table is really a table of tables. The first table
|
|||
|
// is indexed by originator, and the second by vsn.
|
|||
|
//
|
|||
|
Entry = RtlInsertElementGenericTable(VvJoinContext->Originators,
|
|||
|
&VvJoinNode,
|
|||
|
sizeof(PVVJOIN_NODE),
|
|||
|
&IsNew);
|
|||
|
VvJoinOriginators = *Entry;
|
|||
|
FRS_ASSERT((IsNew && !VvJoinOriginators->Vsns) ||
|
|||
|
(!IsNew && VvJoinOriginators->Vsns));
|
|||
|
|
|||
|
if (!VvJoinOriginators->Vsns) {
|
|||
|
VvJoinOriginators->Vsns = FrsAlloc(sizeof(RTL_GENERIC_TABLE));
|
|||
|
RtlInitializeGenericTable(VvJoinOriginators->Vsns,
|
|||
|
VvJoinCmpVsns,
|
|||
|
VvJoinAlloc,
|
|||
|
VvJoinFree,
|
|||
|
NULL);
|
|||
|
}
|
|||
|
|
|||
|
RtlInsertElementGenericTable(VvJoinOriginators->Vsns,
|
|||
|
&VvJoinNode,
|
|||
|
sizeof(PVVJOIN_NODE),
|
|||
|
&IsNew);
|
|||
|
//
|
|||
|
// Every vsn should be unique. IDTable must be corrupt. give up
|
|||
|
//
|
|||
|
if (!IsNew) {
|
|||
|
return ERROR_DUP_NAME;
|
|||
|
}
|
|||
|
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#if DBG
|
|||
|
VOID
|
|||
|
VvJoinPrintNode(
|
|||
|
ULONG Sev,
|
|||
|
PWCHAR Indent,
|
|||
|
PVVJOIN_NODE VvJoinNode
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Print a node
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Indent
|
|||
|
VvJoinNode
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinPrintNode:"
|
|||
|
CHAR Originator[GUID_CHAR_LEN];
|
|||
|
CHAR FileGuidA[GUID_CHAR_LEN];
|
|||
|
CHAR ParentGuidA[GUID_CHAR_LEN];
|
|||
|
|
|||
|
GuidToStr(&VvJoinNode->Originator, Originator);
|
|||
|
GuidToStr(&VvJoinNode->FileGuid, FileGuidA);
|
|||
|
GuidToStr(&VvJoinNode->ParentGuid, ParentGuidA);
|
|||
|
DPRINT2(Sev, ":V: %wsNode: %08x\n", Indent, VvJoinNode);
|
|||
|
DPRINT2(Sev, ":V: %ws\tFlags : %08x\n", Indent, VvJoinNode->Flags);
|
|||
|
DPRINT2(Sev, ":V: %ws\tFileGuid : %s\n", Indent, FileGuidA);
|
|||
|
DPRINT2(Sev, ":V: %ws\tParentGuid: %s\n", Indent, ParentGuidA);
|
|||
|
DPRINT2(Sev, ":V: %ws\tOriginator: %s\n", Indent, Originator);
|
|||
|
DPRINT2(Sev, ":V: %ws\tVsn : %08x %08x\n", Indent, PRINTQUAD(VvJoinNode->Vsn));
|
|||
|
DPRINT2(Sev, ":V: %ws\tVsns : %08x\n", Indent, VvJoinNode->Vsns);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinPrint(
|
|||
|
ULONG Sev,
|
|||
|
PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Print the tables
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinPrint:"
|
|||
|
PVOID Key;
|
|||
|
PVOID SubKey;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
PVVJOIN_NODE VvJoinNode;
|
|||
|
|
|||
|
DPRINT1(Sev, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
DPRINT(Sev, "\n");
|
|||
|
DPRINT(Sev, ":V: GUIDS\n");
|
|||
|
if (VvJoinContext->Guids) {
|
|||
|
Key = NULL;
|
|||
|
while (Entry = VVJOIN_NEXT_ENTRY(VvJoinContext->Guids, &Key)) {
|
|||
|
VvJoinPrintNode(Sev, L"", *Entry);
|
|||
|
}
|
|||
|
}
|
|||
|
DPRINT(Sev, "\n");
|
|||
|
DPRINT(Sev, ":V: ORIGINATORS\n");
|
|||
|
if (VvJoinContext->Originators) {
|
|||
|
Key = NULL;
|
|||
|
while (Entry = VVJOIN_NEXT_ENTRY(VvJoinContext->Originators, &Key)) {
|
|||
|
VvJoinPrintNode(Sev, L"", *Entry);
|
|||
|
VvJoinNode = *Entry;
|
|||
|
DPRINT(Sev, "\n");
|
|||
|
DPRINT(Sev, ":V: \tVSNS\n");
|
|||
|
if (VvJoinNode->Vsns) {
|
|||
|
SubKey = NULL;
|
|||
|
while (Entry = VVJOIN_NEXT_ENTRY(VvJoinNode->Vsns, &SubKey)) {
|
|||
|
VvJoinPrintNode(Sev, L"\t", *Entry);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Used to test the code that sends files
|
|||
|
//
|
|||
|
VVJOIN_NODE TestNodes[] = {
|
|||
|
// Flags FileGuid ParentGuid Originator Vsn Vsns
|
|||
|
7, 1,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0, 0,0,0,1,2,3,4,5,6,7,8, 9, NULL,
|
|||
|
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,2, 1,0,0,0,0,0,0,0,0,0,0, 0,0,0,1,2,3,4,5,6,7,8, 39, NULL,
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,0,0,0,2, 0,0,0,1,2,3,4,5,6,7,8, 29, NULL,
|
|||
|
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,12, 1,0,0,0,0,0,0,0,0,0,0, 0,0,0,1,2,3,4,5,6,7,9, 39, NULL,
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,11, 0,0,0,0,0,0,0,0,0,0,12, 0,0,0,1,2,3,4,5,6,7,9, 29, NULL,
|
|||
|
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,0,0,0,0,0,0,0,96, 0,0,0,1,2,3,4,5,6,7,7, 39, NULL,
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,21, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,7, 29, NULL,
|
|||
|
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,31, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,7, 49, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,41, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,8, 49, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,51, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 49, NULL,
|
|||
|
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,61, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,7, 9, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,71, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,8, 9, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,81, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 9, NULL,
|
|||
|
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,91, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 10, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,92, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 11, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,93, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 12, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,94, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 13, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,95, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,1,2,3,4,5,6,7,9, 14, NULL,
|
|||
|
1, 0,0,0,0,0,0,0,0,0,0,96, 1,0,0,0,0,0,0,0,0,0,0, 0,0,0,1,2,3,4,5,6,7,9, 99, NULL,
|
|||
|
};
|
|||
|
//
|
|||
|
// Expected send order of the above array
|
|||
|
//
|
|||
|
VVJOIN_NODE TestNodesExpected[] = {
|
|||
|
// Flags FileGuid ParentGuid Originator Vsn Vsns
|
|||
|
0x19, 0,0,0,0,0,0,0,0,0,0,96, 1,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 99, NULL,
|
|||
|
0x19, 0,0,0,0,0,0,0,0,0,0,22, 0,0,0,0,0,0,0,0,0,0,96, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 39, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,61, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, NULL,
|
|||
|
0x01, 0,0,0,0,0,0,0,0,0,0,21, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 29, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,31, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 49, NULL,
|
|||
|
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,71, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,81, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 9, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,91, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,92, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,93, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 12, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,94, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 13, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,95, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 14, NULL,
|
|||
|
|
|||
|
0x19, 0,0,0,0,0,0,0,0,0,0,2, 1,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 39, NULL,
|
|||
|
0x01, 0,0,0,0,0,0,0,0,0,0,1, 0,0,0,0,0,0,0,0,0,0,2, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 29, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,41, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 49, NULL,
|
|||
|
|
|||
|
0x19, 0,0,0,0,0,0,0,0,0,0,12, 1,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 39, NULL,
|
|||
|
0x01, 0,0,0,0,0,0,0,0,0,0,11, 0,0,0,0,0,0,0,0,0,0,12, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 29, NULL,
|
|||
|
0, 0,0,0,0,0,0,0,0,0,0,51, 0,0,0,0,0,0,0,0,0,0,22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 9, 49, NULL,
|
|||
|
};
|
|||
|
DWORD NumberOfTestNodes = ARRAY_SZ(TestNodes);
|
|||
|
DWORD NumberOfExpected = ARRAY_SZ(TestNodesExpected);
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinTestSend(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
IN PVVJOIN_NODE VvJoinNode
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Pretend to send a test node. Compare the node with the expected
|
|||
|
results.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
VvJoinNode
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinTestSend:"
|
|||
|
CHAR VGuid[GUID_CHAR_LEN];
|
|||
|
CHAR EGuid[GUID_CHAR_LEN];
|
|||
|
PVVJOIN_NODE Expected;
|
|||
|
|
|||
|
VVJOIN_PRINT_NODE(5, L"TestSend ", VvJoinNode);
|
|||
|
|
|||
|
//
|
|||
|
// Sending too many!
|
|||
|
//
|
|||
|
if (VvJoinContext->NumberSent >= NumberOfTestNodes) {
|
|||
|
DPRINT2(0, ":V: ERROR - TOO MANY (%d > %d)\n",
|
|||
|
VvJoinContext->NumberSent, NumberOfTestNodes);
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Compare this node with the node we expected to send
|
|||
|
//
|
|||
|
Expected = &TestNodesExpected[VvJoinContext->NumberSent];
|
|||
|
VvJoinContext->NumberSent++;
|
|||
|
|
|||
|
if (!GUIDS_EQUAL(&VvJoinNode->FileGuid, &Expected->FileGuid)) {
|
|||
|
GuidToStr(&VvJoinNode->FileGuid, VGuid);
|
|||
|
GuidToStr(&Expected->FileGuid, EGuid);
|
|||
|
DPRINT2(0, ":V: ERROR - UNEXPECTED ORDER (FileGuid %s != %s)\n", VGuid, EGuid);
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
if (VvJoinNode->Flags != Expected->Flags) {
|
|||
|
DPRINT2(0, ":V: ERROR - UNEXPECTED ORDER (Flags %08x != %08x)\n",
|
|||
|
VvJoinNode->Flags, Expected->Flags);
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
if (!GUIDS_EQUAL(&VvJoinNode->ParentGuid, &Expected->ParentGuid)) {
|
|||
|
GuidToStr(&VvJoinNode->ParentGuid, VGuid);
|
|||
|
GuidToStr(&Expected->ParentGuid, EGuid);
|
|||
|
DPRINT2(0, ":V: ERROR - UNEXPECTED ORDER (ParentGuid %s != %s)\n", VGuid, EGuid);
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
if (VvJoinNode->Vsn != Expected->Vsn) {
|
|||
|
DPRINT(0, ":V: ERROR - UNEXPECTED ORDER (Vsn)\n");
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
if (!GUIDS_EQUAL(&VvJoinNode->Originator, &Expected->Originator)) {
|
|||
|
GuidToStr(&VvJoinNode->Originator, VGuid);
|
|||
|
GuidToStr(&Expected->Originator, EGuid);
|
|||
|
DPRINT2(0, ":V: ERROR - UNEXPECTED ORDER (Originator %s != %s)\n", VGuid, EGuid);
|
|||
|
return ERROR_GEN_FAILURE;
|
|||
|
}
|
|||
|
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinTest(
|
|||
|
VOID
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Test ordering by filling the tables from a hardwired array and then
|
|||
|
calling the ordering code to send them. Check that the ordering
|
|||
|
code sends the nodes in the correct order.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinTest:"
|
|||
|
DWORD WStatus;
|
|||
|
DWORD i;
|
|||
|
DWORD NumberSent;
|
|||
|
PVVJOIN_CONTEXT VvJoinContext;
|
|||
|
DWORD VvJoinSendInOrder(IN PVVJOIN_CONTEXT VvJoinContext);
|
|||
|
DWORD VvJoinSendOutOfOrder(IN PVVJOIN_CONTEXT VvJoinContext);
|
|||
|
|
|||
|
if (!DebugInfo.VvJoinTests) {
|
|||
|
DPRINT(4, ":V: VvJoin tests disabled\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Pretend that the hardwired array is an IDTable and fill up the tables
|
|||
|
//
|
|||
|
DPRINT1(0, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
DPRINT2(0, ":V: >>>>> %s Starting (%d entries)\n", DEBSUB, NumberOfTestNodes);
|
|||
|
|
|||
|
VvJoinContext = FrsAlloc(sizeof(VVJOIN_CONTEXT));
|
|||
|
VvJoinContext->Send = VvJoinTestSend;
|
|||
|
|
|||
|
for (i = 0; i < NumberOfTestNodes; ++i) {
|
|||
|
WStatus = VvJoinInsertEntry(VvJoinContext,
|
|||
|
TestNodes[i].Flags,
|
|||
|
&TestNodes[i].FileGuid,
|
|||
|
&TestNodes[i].ParentGuid,
|
|||
|
&TestNodes[i].Originator,
|
|||
|
&TestNodes[i].Vsn);
|
|||
|
if (!WIN_SUCCESS(WStatus)) {
|
|||
|
DPRINT_WS(0, ":V: ERROR - inserting test nodes;", WStatus);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
VVJOIN_PRINT(5, VvJoinContext);
|
|||
|
|
|||
|
//
|
|||
|
// Send the "files" through our send routine
|
|||
|
//
|
|||
|
do {
|
|||
|
NumberSent = VvJoinContext->NumberSent;
|
|||
|
WStatus = VvJoinSendInOrder(VvJoinContext);
|
|||
|
if (WIN_SUCCESS(WStatus) &&
|
|||
|
NumberSent == VvJoinContext->NumberSent) {
|
|||
|
WStatus = VvJoinSendOutOfOrder(VvJoinContext);
|
|||
|
}
|
|||
|
} while (WIN_SUCCESS(WStatus) &&
|
|||
|
NumberSent != VvJoinContext->NumberSent);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// DONE
|
|||
|
//
|
|||
|
if (!WIN_SUCCESS(WStatus)) {
|
|||
|
DPRINT_WS(0, ":V: ERROR - TEST FAILED;", WStatus);
|
|||
|
} else if (VvJoinContext->NumberSent != NumberOfExpected) {
|
|||
|
DPRINT2(0, ":V: ERROR - TEST FAILED; Expected to send %d; not %d\n",
|
|||
|
NumberOfExpected, VvJoinContext->NumberSent);
|
|||
|
} else {
|
|||
|
DPRINT(0, ":V: TEST PASSED\n");
|
|||
|
}
|
|||
|
VvJoinContext = VvJoinFreeContext(VvJoinContext);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinTestSkipBegin(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
IN PCOMMAND_PACKET Cmd
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Create a directory and files that will be skipped.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
Cmd
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinTestSkipBegin:"
|
|||
|
HANDLE Handle;
|
|||
|
DWORD WStatus;
|
|||
|
|
|||
|
if (!DebugInfo.VvJoinTests) {
|
|||
|
DPRINT(4, ":V: VvJoin tests disabled\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
VvJoinContext->SkipDir = FrsWcsPath(RsReplica(Cmd)->Root, L"SkipDir");
|
|||
|
VvJoinContext->SkipFile1 = FrsWcsPath(VvJoinContext->SkipDir, L"SkipFile1");
|
|||
|
VvJoinContext->SkipFile2 = FrsWcsPath(RsReplica(Cmd)->Root, L"SkipFile2");
|
|||
|
|
|||
|
if (!WIN_SUCCESS(FrsCreateDirectory(VvJoinContext->SkipDir))) {
|
|||
|
DPRINT1(0, ":V: ERROR - Can't create %ws\n", VvJoinContext->SkipDir);
|
|||
|
}
|
|||
|
WStatus = StuCreateFile(VvJoinContext->SkipFile1, &Handle);
|
|||
|
if (!HANDLE_IS_VALID(Handle) || !WIN_SUCCESS(WStatus)) {
|
|||
|
DPRINT1(0, ":V: ERROR - Can't create %ws\n", VvJoinContext->SkipFile1);
|
|||
|
} else {
|
|||
|
CloseHandle(Handle);
|
|||
|
}
|
|||
|
WStatus = StuCreateFile(VvJoinContext->SkipFile2, &Handle);
|
|||
|
if (!HANDLE_IS_VALID(Handle) || !WIN_SUCCESS(WStatus)) {
|
|||
|
DPRINT1(0, ":V: ERROR - Can't create %ws\n", VvJoinContext->SkipFile2);
|
|||
|
} else {
|
|||
|
CloseHandle(Handle);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Wait for the local change orders to propagate
|
|||
|
//
|
|||
|
Sleep(10 * 1000);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinTestSkipCheck(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
IN PWCHAR FileName,
|
|||
|
IN BOOL IsDir
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Did we skip the correct files/dirs?
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
FileName
|
|||
|
IsDir
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinTestSkipCheck:"
|
|||
|
|
|||
|
if (!DebugInfo.VvJoinTests) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (IsDir && WSTR_EQ(FileName, L"SkipDir")) {
|
|||
|
VvJoinContext->SkippedDir = TRUE;
|
|||
|
} else if (!IsDir && WSTR_EQ(FileName, L"SkipFile1")) {
|
|||
|
VvJoinContext->SkippedFile1 = TRUE;
|
|||
|
} else if (!IsDir && WSTR_EQ(FileName, L"SkipFile2")) {
|
|||
|
VvJoinContext->SkippedFile2 = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
VvJoinTestSkipEnd(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Did we skip the correct files/dirs?
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinTestSkipEnd:"
|
|||
|
|
|||
|
if (!DebugInfo.VvJoinTests) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (VvJoinContext->SkippedDir &&
|
|||
|
VvJoinContext->SkippedFile1 &&
|
|||
|
VvJoinContext->SkippedFile2) {
|
|||
|
DPRINT(0, ":V: Skip Test Passed\n");
|
|||
|
} else {
|
|||
|
DPRINT(0, ":V: ERROR - Skip Test failed\n");
|
|||
|
}
|
|||
|
FrsDeleteFile(VvJoinContext->SkipFile1);
|
|||
|
FrsDeleteFile(VvJoinContext->SkipFile2);
|
|||
|
FrsDeleteFile(VvJoinContext->SkipDir);
|
|||
|
VvJoinContext->SkipDir = FrsFree(VvJoinContext->SkipDir);
|
|||
|
VvJoinContext->SkipFile1 = FrsFree(VvJoinContext->SkipFile1);
|
|||
|
VvJoinContext->SkipFile2 = FrsFree(VvJoinContext->SkipFile2);
|
|||
|
}
|
|||
|
#endif DBG
|
|||
|
|
|||
|
|
|||
|
PVVJOIN_NODE
|
|||
|
VvJoinFindNode(
|
|||
|
PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
GUID *FileGuid
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Find the node by Guid in the file table.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
FileGuid
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Node or NULL
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinFindNode:"
|
|||
|
VVJOIN_NODE Node;
|
|||
|
VVJOIN_NODE *pNode;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
|
|||
|
//
|
|||
|
// No file table? Let the caller deal with it
|
|||
|
//
|
|||
|
if (!VvJoinContext->Guids) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Build a phoney node to use as a key
|
|||
|
//
|
|||
|
Node.FileGuid = *FileGuid;
|
|||
|
pNode = &Node;
|
|||
|
Entry = RtlLookupElementGenericTable(VvJoinContext->Guids, &pNode);
|
|||
|
if (Entry) {
|
|||
|
return *Entry;
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinSendInOrder(
|
|||
|
PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Look through the originator tables and find a node at the
|
|||
|
head of the list that can be sent in the proper VSN order.
|
|||
|
Stop looping when none of the nodes can be sent in order.
|
|||
|
|
|||
|
Even if a node is in order, its parent may not have been sent,
|
|||
|
and so the file represented by the node cannot be sent.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinSendInOrder:"
|
|||
|
DWORD WStatus;
|
|||
|
BOOL SentOne;
|
|||
|
PVOID Key;
|
|||
|
PVOID SubKey;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
PVVJOIN_NODE *SubEntry;
|
|||
|
PVVJOIN_NODE VvJoinNode;
|
|||
|
PVVJOIN_NODE VsnNode;
|
|||
|
PVVJOIN_NODE ParentNode;
|
|||
|
CHAR ParentGuidA[GUID_CHAR_LEN];
|
|||
|
CHAR FileGuidA[GUID_CHAR_LEN];
|
|||
|
|
|||
|
//
|
|||
|
// Continue to scan the originator tables until nothing can be sent
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
do {
|
|||
|
WStatus = ERROR_SUCCESS;
|
|||
|
SentOne = FALSE;
|
|||
|
//
|
|||
|
// No tables or no entries; nothing to do
|
|||
|
//
|
|||
|
if (!VvJoinContext->Originators) {
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
if (!RtlNumberGenericTableElements(VvJoinContext->Originators)) {
|
|||
|
VvJoinContext->Originators = FrsFree(VvJoinContext->Originators);
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
//
|
|||
|
// Examine the head of each originator table. If the entry can
|
|||
|
// be sent in order, do so.
|
|||
|
Key = NULL;
|
|||
|
while (Entry = VVJOIN_NEXT_ENTRY(VvJoinContext->Originators, &Key)) {
|
|||
|
VvJoinNode = *Entry;
|
|||
|
//
|
|||
|
// No entries; done
|
|||
|
//
|
|||
|
if (!RtlNumberGenericTableElements(VvJoinNode->Vsns)) {
|
|||
|
RtlDeleteElementGenericTable(VvJoinContext->Originators, Entry);
|
|||
|
VvJoinNode->Vsns = FrsFree(VvJoinNode->Vsns);
|
|||
|
Key = NULL;
|
|||
|
continue;
|
|||
|
}
|
|||
|
//
|
|||
|
// Scan for an unsent entry
|
|||
|
//
|
|||
|
SubKey = NULL;
|
|||
|
while (SubEntry = VVJOIN_NEXT_ENTRY(VvJoinNode->Vsns, &SubKey)) {
|
|||
|
VsnNode = *SubEntry;
|
|||
|
VVJOIN_PRINT_NODE(5, L"CHECKING ", VsnNode);
|
|||
|
//
|
|||
|
// Entry was previously sent; remove it and continue
|
|||
|
//
|
|||
|
if (VsnNode->Flags & VVJOIN_FLAGS_SENT) {
|
|||
|
DPRINT(5, ":V: ALREADY SENT\n");
|
|||
|
RtlDeleteElementGenericTable(VvJoinNode->Vsns, SubEntry);
|
|||
|
SubKey = NULL;
|
|||
|
continue;
|
|||
|
}
|
|||
|
//
|
|||
|
// VsnNode is the head of this list and can be sent in
|
|||
|
// order iff its parent has been sent. Find its parent
|
|||
|
// and check it.
|
|||
|
//
|
|||
|
ParentNode = VvJoinFindNode(VvJoinContext, &VsnNode->ParentGuid);
|
|||
|
|
|||
|
//
|
|||
|
// Something is really wrong; everyone's parent should
|
|||
|
// be in the file table UNLESS we picked up a file whose
|
|||
|
// parent was created after we began the IDTable scan.
|
|||
|
//
|
|||
|
// But this is okay if the idtable entry is a tombstoned
|
|||
|
// delete. I am not sure why but I think it has to do
|
|||
|
// with name morphing, reanimation, and out-of-order
|
|||
|
// directory tree deletes.
|
|||
|
//
|
|||
|
if (!ParentNode) {
|
|||
|
if (VsnNode->Flags & VVJOIN_FLAGS_DELETED) {
|
|||
|
//
|
|||
|
// Let the reconcile code decide to accept
|
|||
|
// this change order. Don't let it be dampened.
|
|||
|
//
|
|||
|
VsnNode->Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
} else {
|
|||
|
GuidToStr(&VsnNode->ParentGuid, ParentGuidA);
|
|||
|
GuidToStr(&VsnNode->FileGuid, FileGuidA);
|
|||
|
DPRINT2(0, ":V: ERROR - Can't find parent node %s for %s\n",
|
|||
|
ParentGuidA, FileGuidA);
|
|||
|
WStatus = ERROR_NOT_FOUND;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
// Parent hasn't been sent; we can't send this node
|
|||
|
// Unless it is a tombstoned delete that lacks a parent
|
|||
|
// node. See above.
|
|||
|
//
|
|||
|
if (ParentNode && !(ParentNode->Flags & VVJOIN_FLAGS_SENT)) {
|
|||
|
break;
|
|||
|
}
|
|||
|
//
|
|||
|
// Parent has been sent; send this node to the outbound
|
|||
|
// log to be sent on to our outbound partner. If we
|
|||
|
// cannot create this change order give up and return
|
|||
|
// to our caller.
|
|||
|
//
|
|||
|
WStatus = (VvJoinContext->Send)(VvJoinContext, VsnNode);
|
|||
|
if (!WIN_SUCCESS(WStatus)) {
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
//
|
|||
|
// Remove it from the originator table
|
|||
|
//
|
|||
|
VsnNode->Flags |= VVJOIN_FLAGS_SENT;
|
|||
|
SentOne = TRUE;
|
|||
|
RtlDeleteElementGenericTable(VvJoinNode->Vsns, SubEntry);
|
|||
|
SubKey = NULL;
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
} while (WIN_SUCCESS(WStatus) && SentOne);
|
|||
|
CLEANUP:
|
|||
|
return WStatus;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
VvJoinInOrder(
|
|||
|
PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
PVVJOIN_NODE VvJoinNode
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Is this node at the head of an originator list? In other words,
|
|||
|
can this node be sent in order?
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
VvJoinNode
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
TRUE - head of list
|
|||
|
FALSE - NOT
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinInOrder:"
|
|||
|
PVOID Key;
|
|||
|
PVVJOIN_NODE *Entry;
|
|||
|
PVVJOIN_NODE OriginatorNode;
|
|||
|
CHAR FileGuidA[GUID_CHAR_LEN];
|
|||
|
|
|||
|
//
|
|||
|
// No originators or no entries. In either case, this node
|
|||
|
// cannot possibly be on the head of a list.
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
if (!VvJoinContext->Originators) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
if (!RtlNumberGenericTableElements(VvJoinContext->Originators)) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// Find the originator table.
|
|||
|
//
|
|||
|
Entry = RtlLookupElementGenericTable(VvJoinContext->Originators,
|
|||
|
&VvJoinNode);
|
|||
|
if (!Entry || !*Entry) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// No entries; this node cannot possibly be on the head of a list.
|
|||
|
//
|
|||
|
OriginatorNode = *Entry;
|
|||
|
if (!OriginatorNode->Vsns) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
if (!RtlNumberGenericTableElements(OriginatorNode->Vsns)) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// Head of this list of nodes sorted by vsn
|
|||
|
//
|
|||
|
Key = NULL;
|
|||
|
Entry = VVJOIN_NEXT_ENTRY(OriginatorNode->Vsns, &Key);
|
|||
|
if (!Entry || !*Entry) {
|
|||
|
goto NOTFOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// This node is at the head of the list if the entry at the head
|
|||
|
// of the list points to it.
|
|||
|
//
|
|||
|
return (*Entry == VvJoinNode);
|
|||
|
|
|||
|
NOTFOUND:
|
|||
|
GuidToStr(&VvJoinNode->FileGuid, FileGuidA);
|
|||
|
DPRINT1(0, ":V: ERROR - node %s is not in a list\n", FileGuidA);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinSendIfParentSent(
|
|||
|
PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
PVVJOIN_NODE VvJoinNode
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Send this node if its parent has been sent. Otherwise, try to
|
|||
|
send its parent.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
VvJoinNode
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinSendIfParentSent:"
|
|||
|
DWORD WStatus;
|
|||
|
PVVJOIN_NODE ParentNode;
|
|||
|
CHAR ParentGuidA[GUID_CHAR_LEN];
|
|||
|
CHAR FileGuidA[GUID_CHAR_LEN];
|
|||
|
|
|||
|
DPRINT1(4, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
|
|||
|
//
|
|||
|
// Find this node's parent
|
|||
|
//
|
|||
|
ParentNode = VvJoinFindNode(VvJoinContext, &VvJoinNode->ParentGuid);
|
|||
|
//
|
|||
|
// Something is really wrong. Every node should have a parent UNLESS
|
|||
|
// its parent was created after the IDTable scan began. In either case,
|
|||
|
// give up.
|
|||
|
//
|
|||
|
//
|
|||
|
// But this is okay if the idtable entry is a tombstoned
|
|||
|
// delete. I am not sure why but I think it has to do
|
|||
|
// with name morphing, reanimation, and out-of-order
|
|||
|
// directory tree deletes.
|
|||
|
//
|
|||
|
if (!ParentNode) {
|
|||
|
if (VvJoinNode->Flags & VVJOIN_FLAGS_DELETED) {
|
|||
|
//
|
|||
|
// Let the reconcile code decide to accept
|
|||
|
// this change order. Don't let it be dampened.
|
|||
|
//
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
} else {
|
|||
|
GuidToStr(&VvJoinNode->ParentGuid, ParentGuidA);
|
|||
|
GuidToStr(&VvJoinNode->FileGuid, FileGuidA);
|
|||
|
DPRINT2(0, ":V: ERROR - Can't find parent node %s for %s\n",
|
|||
|
ParentGuidA, FileGuidA);
|
|||
|
WStatus = ERROR_NOT_FOUND;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
// A loop in the directory hierarchy!? Give up.
|
|||
|
//
|
|||
|
if (ParentNode && (ParentNode->Flags & VVJOIN_FLAGS_PROCESSING)) {
|
|||
|
GuidToStr(&VvJoinNode->ParentGuid, ParentGuidA);
|
|||
|
GuidToStr(&VvJoinNode->FileGuid, FileGuidA);
|
|||
|
DPRINT2(0, ":V: ERROR - LOOP parent node %s for %s\n", ParentGuidA, FileGuidA);
|
|||
|
WStatus = ERROR_INVALID_DATA;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
//
|
|||
|
// Has this node's parent been sent?
|
|||
|
//
|
|||
|
if (!ParentNode || (ParentNode->Flags & VVJOIN_FLAGS_SENT)) {
|
|||
|
//
|
|||
|
// Sending out of order; don't update the version vector
|
|||
|
//
|
|||
|
if (ParentNode && !VvJoinInOrder(VvJoinContext, VvJoinNode)) {
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
}
|
|||
|
//
|
|||
|
// Send this node (its parent has been sent)
|
|||
|
//
|
|||
|
WStatus = (VvJoinContext->Send)(VvJoinContext, VvJoinNode);
|
|||
|
if (!WIN_SUCCESS(WStatus)) {
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_SENT;
|
|||
|
} else {
|
|||
|
//
|
|||
|
// Recurse to see if we can send the parent
|
|||
|
//
|
|||
|
ParentNode->Flags |= VVJOIN_FLAGS_PROCESSING;
|
|||
|
WStatus = VvJoinSendIfParentSent(VvJoinContext, ParentNode);
|
|||
|
ParentNode->Flags &= ~VVJOIN_FLAGS_PROCESSING;
|
|||
|
}
|
|||
|
|
|||
|
CLEANUP:
|
|||
|
return WStatus;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinSendOutOfOrder(
|
|||
|
PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is called after VvJoinSendInOrder(). All of the nodes
|
|||
|
that could be sent in order have been sent. At this time, we take
|
|||
|
the first entry of the first originator table and send it or, if
|
|||
|
its parent hasn't been sent, its parent. The search for the parent
|
|||
|
recurses until we have a node whose parent has been sent. The
|
|||
|
recursion will stop because we will either hit the root or we will
|
|||
|
loop.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinSendOutOfOrder:"
|
|||
|
DWORD WStatus = ERROR_SUCCESS;
|
|||
|
PVOID Key;
|
|||
|
PVOID SubKey;
|
|||
|
PVVJOIN_NODE *Entry = NULL;
|
|||
|
PVVJOIN_NODE *SubEntry = NULL;
|
|||
|
PVVJOIN_NODE VvJoinNode = NULL;
|
|||
|
PVVJOIN_NODE VsnNode = NULL;
|
|||
|
PVVJOIN_NODE ParentNode = NULL;
|
|||
|
|
|||
|
DPRINT1(4, ":V: >>>>> %s\n", DEBSUB);
|
|||
|
//
|
|||
|
// No table or no entries; nothing to do
|
|||
|
//
|
|||
|
if (!VvJoinContext->Originators) {
|
|||
|
WStatus = ERROR_SUCCESS;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
if (!RtlNumberGenericTableElements(VvJoinContext->Originators)) {
|
|||
|
VvJoinContext->Originators = FrsFree(VvJoinContext->Originators);
|
|||
|
WStatus = ERROR_SUCCESS;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
//
|
|||
|
// Find the first originator table with entries
|
|||
|
//
|
|||
|
Key = NULL;
|
|||
|
while (Entry = VVJOIN_NEXT_ENTRY(VvJoinContext->Originators, &Key)) {
|
|||
|
VvJoinNode = *Entry;
|
|||
|
if (!RtlNumberGenericTableElements(VvJoinNode->Vsns)) {
|
|||
|
RtlDeleteElementGenericTable(VvJoinContext->Originators, Entry);
|
|||
|
VvJoinNode->Vsns = FrsFree(VvJoinNode->Vsns);
|
|||
|
Key = NULL;
|
|||
|
continue;
|
|||
|
}
|
|||
|
//
|
|||
|
// Scan for an unsent entry
|
|||
|
//
|
|||
|
SubKey = NULL;
|
|||
|
while (SubEntry = VVJOIN_NEXT_ENTRY(VvJoinNode->Vsns, &SubKey)) {
|
|||
|
VsnNode = *SubEntry;
|
|||
|
VVJOIN_PRINT_NODE(5, L"CHECKING ", VsnNode);
|
|||
|
//
|
|||
|
// Entry was previously sent; remove it and continue
|
|||
|
//
|
|||
|
if (VsnNode->Flags & VVJOIN_FLAGS_SENT) {
|
|||
|
DPRINT(5, ":V: ALREADY SENT\n");
|
|||
|
RtlDeleteElementGenericTable(VvJoinNode->Vsns, SubEntry);
|
|||
|
SubKey = NULL;
|
|||
|
continue;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
//
|
|||
|
// No unsent entries; reset the loop indicator back to the first
|
|||
|
// entry so that the code at the top of the loop will delete
|
|||
|
// this originator and then look at other originators.
|
|||
|
//
|
|||
|
if (!SubEntry) {
|
|||
|
Key = NULL;
|
|||
|
} else {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
// No more entries; done
|
|||
|
//
|
|||
|
if (!SubEntry) {
|
|||
|
WStatus = ERROR_SUCCESS;
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
//
|
|||
|
// Send this entry if its parent has been sent. Otherwise, recurse.
|
|||
|
// The VVJOIN_FLAGS_PROCESSING detects loops.
|
|||
|
//
|
|||
|
VsnNode = *SubEntry;
|
|||
|
VsnNode->Flags |= VVJOIN_FLAGS_PROCESSING;
|
|||
|
WStatus = VvJoinSendIfParentSent(VvJoinContext, VsnNode);
|
|||
|
VsnNode->Flags &= ~VVJOIN_FLAGS_PROCESSING;
|
|||
|
|
|||
|
CLEANUP:
|
|||
|
return WStatus;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
JET_ERR
|
|||
|
VvJoinBuildTables(
|
|||
|
IN PTHREAD_CTX ThreadCtx,
|
|||
|
IN PTABLE_CTX TableCtx,
|
|||
|
IN PIDTABLE_RECORD IDTableRec,
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is a worker function passed to FrsEnumerateTable(). Each time
|
|||
|
it is called it inserts an entry into the vvjoin tables. These
|
|||
|
tables will be used later to send the appropriate files and directories
|
|||
|
to our outbound partner.
|
|||
|
|
|||
|
Files that would have been dampened are not included.
|
|||
|
|
|||
|
All directories are included but directories that would have been
|
|||
|
dampened are marked as "SENT". Keeping the complete directory
|
|||
|
hierarchy makes other code in this subsystem easier.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ThreadCtx - Needed to access Jet.
|
|||
|
TableCtx - A ptr to an IDTable context struct.
|
|||
|
IDTableRec - A ptr to a IDTable record.
|
|||
|
VvJoinContext
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
A Jet error status. Success means call us with the next record.
|
|||
|
Failure means don't call again and pass our status back to the
|
|||
|
caller of FrsEnumerateTable().
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinBuildTables:"
|
|||
|
|
|||
|
|
|||
|
ULONGLONG SystemTime;
|
|||
|
ULONGLONG ExpireTime;
|
|||
|
|
|||
|
ULONGLONG OriginatorVSN = IDTableRec->OriginatorVSN;
|
|||
|
GUID *OriginatorGuid = &IDTableRec->OriginatorGuid;
|
|||
|
PGEN_TABLE ReplicaVv = VvJoinContext->ReplicaVv;
|
|||
|
|
|||
|
DWORD WStatus;
|
|||
|
DWORD Flags = 0;
|
|||
|
|
|||
|
//
|
|||
|
// First check to see if our partner already has this file by comparing
|
|||
|
// the OriginatorVSN in our IDTable record with the corresponding value
|
|||
|
// in the version vector we got from the partner.
|
|||
|
//
|
|||
|
if (VVHasVsnNoLock(VvJoinContext->CxtionVv, OriginatorGuid, OriginatorVSN)) {
|
|||
|
//
|
|||
|
// Yes, buts its a dir; include in the tables but mark it as sent. We
|
|||
|
// include it in the tables because the code that checks for an
|
|||
|
// existing parent requires the entire directory tree.
|
|||
|
//
|
|||
|
if (IDTableRec->FileIsDir) {
|
|||
|
DPRINT1(4, ":V: Dir %ws is in the Vv; INSERT SENT.\n", IDTableRec->FileName);
|
|||
|
Flags |= VVJOIN_FLAGS_SENT;
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Dampened file, done
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: File %ws is in the Vv; SKIP.\n", IDTableRec->FileName);
|
|||
|
return JET_errSuccess;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Ignore files that have changed since the beginning of the IDTable scan.
|
|||
|
// Keep directories but mark them as sent out-of-order. We must still
|
|||
|
// send the directories because a file with a lower VSN may depend on this
|
|||
|
// directory's existance. We mark the directory out-of-order because we
|
|||
|
// can't be certain that we have seen all of the directories and files
|
|||
|
// with VSNs lower than this.
|
|||
|
//
|
|||
|
// Note: If the orginator guid is not found in our Replica VV then we have
|
|||
|
// to send the file and let the partner accept/reject. This can happen
|
|||
|
// if one or more COs arrived here with a new originator guid and were
|
|||
|
// all marked out-of-order. Because the VVRetire code will not update the
|
|||
|
// VV for out-of-order COs the new originator guid will not get added to
|
|||
|
// our Replica VV so VVHasVsn will return FALSE making us think that a new
|
|||
|
// CO has arrived since the scan started (and get sent out) which is
|
|||
|
// not the case. The net effect is that the file won't get sent.
|
|||
|
// (Yes this actually happened.)
|
|||
|
//
|
|||
|
if (!VVHasVsnNoLock(ReplicaVv, OriginatorGuid, OriginatorVSN) &&
|
|||
|
VVHasOriginatorNoLock(ReplicaVv, OriginatorGuid)) {
|
|||
|
|
|||
|
if (IDTableRec->FileIsDir) {
|
|||
|
DPRINT1(4, ":V: Dir %ws is not in the ReplicaVv; Mark out-of-order.\n",
|
|||
|
IDTableRec->FileName);
|
|||
|
|
|||
|
Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
VVJOIN_TEST_SKIP_CHECK(VvJoinContext, IDTableRec->FileName, TRUE);
|
|||
|
} else {
|
|||
|
//
|
|||
|
// Ignored file, done
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: File %ws is not in the ReplicaVv; Skip.\n",
|
|||
|
IDTableRec->FileName);
|
|||
|
|
|||
|
VVJOIN_TEST_SKIP_CHECK(VvJoinContext, IDTableRec->FileName, FALSE);
|
|||
|
return JET_errSuccess;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Deleted
|
|||
|
//
|
|||
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED)) {
|
|||
|
Flags |= VVJOIN_FLAGS_DELETED;
|
|||
|
|
|||
|
//
|
|||
|
// Check for expired tombstones and don't send them.
|
|||
|
//
|
|||
|
GetSystemTimeAsFileTime((PFILETIME)&SystemTime);
|
|||
|
COPY_TIME(&ExpireTime, &IDTableRec->TombStoneGC);
|
|||
|
|
|||
|
if ((ExpireTime < SystemTime) && (ExpireTime != QUADZERO)) {
|
|||
|
|
|||
|
//
|
|||
|
// IDTable record has expired. Delete it.
|
|||
|
//
|
|||
|
if (IDTableRec->FileIsDir) {
|
|||
|
DPRINT1(4, ":V: Dir %ws IDtable record has expired. Don't send.\n",
|
|||
|
IDTableRec->FileName);
|
|||
|
Flags |= VVJOIN_FLAGS_SENT;
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Expired and not a dir so don't even insert in tables.
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: File %ws is expired; SKIP.\n", IDTableRec->FileName);
|
|||
|
return JET_errSuccess;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Ignore incomplete entries. Check for the IDREC_FLAGS_NEW_FILE_IN_PROGRESS flag.
|
|||
|
//
|
|||
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS)) {
|
|||
|
DPRINT1(4, ":V: %ws is new file in progress; SKIP.\n", IDTableRec->FileName);
|
|||
|
return JET_errSuccess;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The root node is never sent.
|
|||
|
//
|
|||
|
if (GUIDS_EQUAL(&IDTableRec->FileGuid,
|
|||
|
VvJoinContext->Replica->ReplicaRootGuid)) {
|
|||
|
DPRINT1(4, ":V: %ws is root\n", IDTableRec->FileName);
|
|||
|
Flags |= VVJOIN_FLAGS_ROOT | VVJOIN_FLAGS_SENT;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Is a directory
|
|||
|
//
|
|||
|
if (IDTableRec->FileIsDir) {
|
|||
|
DPRINT1(4, ":V: %ws is directory\n", IDTableRec->FileName);
|
|||
|
Flags |= VVJOIN_FLAGS_ISDIR;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Include in the vvjoin tables.
|
|||
|
//
|
|||
|
WStatus = VvJoinInsertEntry(VvJoinContext,
|
|||
|
Flags,
|
|||
|
&IDTableRec->FileGuid,
|
|||
|
&IDTableRec->ParentGuid,
|
|||
|
OriginatorGuid,
|
|||
|
&OriginatorVSN);
|
|||
|
CLEANUP3_WS(4, ":V: ERROR - inserting %ws for %ws\\%ws;",
|
|||
|
IDTableRec->FileName, VvJoinContext->Replica->SetName->Name,
|
|||
|
VvJoinContext->Replica->MemberName->Name, WStatus, CLEANUP);
|
|||
|
|
|||
|
//
|
|||
|
// Stop the VvJoin if the cxtion is no longer joined
|
|||
|
//
|
|||
|
VV_JOIN_TRIGGER(VvJoinContext);
|
|||
|
if (!CxtionFlagIs(VvJoinContext->Cxtion, CXTION_FLAGS_JOIN_GUID_VALID) ||
|
|||
|
!GUIDS_EQUAL(&VvJoinContext->JoinGuid, &VvJoinContext->Cxtion->JoinGuid)) {
|
|||
|
DPRINT(0, ":V: VVJOIN ABORTED; MISMATCHED JOIN GUIDS\n");
|
|||
|
goto CLEANUP;
|
|||
|
}
|
|||
|
|
|||
|
return JET_errSuccess;
|
|||
|
|
|||
|
CLEANUP:
|
|||
|
|
|||
|
return JET_errKeyDuplicate;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
VvJoinSend(
|
|||
|
IN PVVJOIN_CONTEXT VvJoinContext,
|
|||
|
IN PVVJOIN_NODE VvJoinNode
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Generate a refresh change order and inject it into the outbound
|
|||
|
log. The staging file will be generated on demand. See the
|
|||
|
outlog.c code for more information.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
VvJoinContext
|
|||
|
VvJoinNode
|
|||
|
|
|||
|
Thread Return Value:
|
|||
|
|
|||
|
Win32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "VvJoinSend:"
|
|||
|
JET_ERR jerr;
|
|||
|
PCOMMAND_PACKET Cmd;
|
|||
|
PIDTABLE_RECORD IDTableRec;
|
|||
|
PCHANGE_ORDER_ENTRY Coe;
|
|||
|
ULONG CoFlags;
|
|||
|
LONG OlTimeout = VvJoinContext->OlTimeout;
|
|||
|
ULONG LocationCmd = CO_LOCATION_CREATE;
|
|||
|
|
|||
|
VVJOIN_PRINT_NODE(5, L"Sending ", VvJoinNode);
|
|||
|
|
|||
|
//
|
|||
|
// Read the IDTable entry for this file
|
|||
|
//
|
|||
|
jerr = DbsReadRecord(VvJoinContext->ThreadCtx,
|
|||
|
&VvJoinNode->FileGuid,
|
|||
|
GuidIndexx,
|
|||
|
VvJoinContext->TableCtx);
|
|||
|
if (!JET_SUCCESS(jerr)) {
|
|||
|
DPRINT_JS(0, "DbsReadRecord:", jerr);
|
|||
|
return ERROR_NOT_FOUND;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Deleted
|
|||
|
//
|
|||
|
IDTableRec = VvJoinContext->TableCtx->pDataRecord;
|
|||
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED)) {
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_DELETED;
|
|||
|
LocationCmd = CO_LOCATION_DELETE;
|
|||
|
}
|
|||
|
//
|
|||
|
// How could this happen?!
|
|||
|
//
|
|||
|
if (!GUIDS_EQUAL(&VvJoinNode->FileGuid, &IDTableRec->FileGuid)) {
|
|||
|
return ERROR_OPERATION_ABORTED;
|
|||
|
} else {
|
|||
|
DPRINT1(4, ":V: Read IDTable entry for %ws\n", IDTableRec->FileName);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// File or dir changed after the IDTable scan. Ignore files
|
|||
|
// but send directories marked as out-of-order. We do this
|
|||
|
// because a file with a lower VSN may require this directory.
|
|||
|
// Note that a directory's VSN may be higher because it was
|
|||
|
// newly created or simply altered by, say, changing its times
|
|||
|
// or adding an alternate data stream.
|
|||
|
//
|
|||
|
if (!GUIDS_EQUAL(&IDTableRec->OriginatorGuid, &VvJoinNode->Originator) ||
|
|||
|
IDTableRec->OriginatorVSN != VvJoinNode->Vsn) {
|
|||
|
DPRINT3(4, ":V: WARN: VSN/ORIGINATOR Mismatch for %ws\\%ws %ws\n",
|
|||
|
VvJoinContext->Replica->SetName->Name,
|
|||
|
VvJoinContext->Replica->MemberName->Name,
|
|||
|
IDTableRec->FileName);
|
|||
|
|
|||
|
if (VvJoinNode->Flags & VVJOIN_FLAGS_DELETED) {
|
|||
|
//
|
|||
|
// If this entry was marked deleted it is possible that it was
|
|||
|
// reamimated with a demand refresh CO. If that happened then
|
|||
|
// the VSN in the IDTable would have changed (getting us here)
|
|||
|
// but no CO would get placed in the Outbound log (since demand
|
|||
|
// refresh COs don't propagate). So let the reconcile code
|
|||
|
// decide to accept/reject this change order. Don't let it be dampened.
|
|||
|
//
|
|||
|
DPRINT1(4, ":V: Sending delete tombstone for %ws out of order\n", IDTableRec->FileName);
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
} else
|
|||
|
|
|||
|
if (VvJoinNode->Flags & VVJOIN_FLAGS_ISDIR) {
|
|||
|
DPRINT1(4, ":V: Sending directory %ws out of order\n", IDTableRec->FileName);
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_OUT_OF_ORDER;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
DPRINT1(4, ":V: Skipping file %ws\n", IDTableRec->FileName);
|
|||
|
VvJoinNode->Flags |= VVJOIN_FLAGS_SENT;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!(VvJoinNode->Flags & VVJOIN_FLAGS_SENT)) {
|
|||
|
if (VvJoinNode->Flags & VVJOIN_FLAGS_DELETED) {
|
|||
|
FRS_CO_FILE_PROGRESS(IDTableRec->FileName,
|
|||
|
IDTableRec->OriginatorVSN,
|
|||
|
"VVjoin sending delete");
|
|||
|
} else {
|
|||
|
FRS_CO_FILE_PROGRESS(IDTableRec->FileName,
|
|||
|
IDTableRec->OriginatorVSN,
|
|||
|
"VVjoin sending create");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
CoFlags = 0;
|
|||
|
//
|
|||
|
// Change order has a Location command
|
|||
|
//
|
|||
|
SetFlag(CoFlags, CO_FLAG_LOCATION_CMD);
|
|||
|
//
|
|||
|
// Mascarade as a local change order since the staging file
|
|||
|
// is created from the local version of the file and isn't
|
|||
|
// from a inbound partner.
|
|||
|
//
|
|||
|
SetFlag(CoFlags, CO_FLAG_LOCALCO);
|
|||
|
|
|||
|
//
|
|||
|
// Refresh change orders will not be propagated by our outbound
|
|||
|
// partner to its outbound partners.
|
|||
|
//
|
|||
|
// Change of plans; allow the propagation to occur so that
|
|||
|
// parallel vvjoins and vvjoinings work (A is vvjoining B is
|
|||
|
// vvjoining C).
|
|||
|
//
|
|||
|
// SetFlag(CoFlags, CO_FLAG_REFRESH);
|
|||
|
|
|||
|
//
|
|||
|
// Directed at one cxtion
|
|||
|
//
|
|||
|
SetFlag(CoFlags, CO_FLAG_DIRECTED_CO);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// This vvjoin requested change order may end up going back to its
|
|||
|
// originator because the originator's version vector doesn't seem to
|
|||
|
// have this file/dir. This could happen if the database was deleted on
|
|||
|
// the originator and is being re-synced. Since the originator could be
|
|||
|
// several hops away the bit stays set to suppress dampening back to the
|
|||
|
// originator. If it gets to the originator then reconcile logic there
|
|||
|
// decides to accept or reject.
|
|||
|
//
|
|||
|
SetFlag(CoFlags, CO_FLAG_VVJOIN_TO_ORIG);
|
|||
|
|
|||
|
//
|
|||
|
// Increment the LCO Sent At Join counters
|
|||
|
// for both the Replic set and connection objects.
|
|||
|
//
|
|||
|
PM_INC_CTR_REPSET(VvJoinContext->Replica, LCOSentAtJoin, 1);
|
|||
|
PM_INC_CTR_CXTION(VvJoinContext->Cxtion, LCOSentAtJoin, 1);
|
|||
|
|
|||
|
//
|
|||
|
// By "out of order" we mean that the VSN on this change order
|
|||
|
// should not be used to update the version vector because there
|
|||
|
// may be other files or dirs with lower VSNs that will be sent
|
|||
|
// later. We wouldn't want our partner to dampen them.
|
|||
|
//
|
|||
|
if (VvJoinNode->Flags & VVJOIN_FLAGS_OUT_OF_ORDER) {
|
|||
|
SetFlag(CoFlags, CO_FLAG_OUT_OF_ORDER);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Build a change order entry from the IDtable Record.
|
|||
|
//
|
|||
|
Coe = ChgOrdMakeFromIDRecord(IDTableRec,
|
|||
|
VvJoinContext->Replica,
|
|||
|
LocationCmd,
|
|||
|
CoFlags,
|
|||
|
VvJoinContext->Cxtion->Name->Guid);
|
|||
|
|
|||
|
//
|
|||
|
// Set CO state
|
|||
|
//
|
|||
|
SET_CHANGE_ORDER_STATE(Coe, IBCO_OUTBOUND_REQUEST);
|
|||
|
|
|||
|
//
|
|||
|
// The DB server is responsible for updating the outbound log.
|
|||
|
// WARNING -- The operation is synchronous so that an command
|
|||
|
// packets will not be sitting on the DB queue once an unjoin
|
|||
|
// command has completed. If this call is made async; then
|
|||
|
// some thread must wait for the DB queue to drain before
|
|||
|
// completing the unjoin operation. E.g., use the inbound
|
|||
|
// changeorder count for these outbound change orders and don't
|
|||
|
// unjoin until the count hits 0.
|
|||
|
//
|
|||
|
Cmd = DbsPrepareCmdPkt(NULL, // CmdPkt,
|
|||
|
VvJoinContext->Replica, // Replica,
|
|||
|
CMD_DBS_INJECT_OUTBOUND_CO, // CmdRequest,
|
|||
|
NULL, // TableCtx,
|
|||
|
Coe, // CallContext,
|
|||
|
0, // TableType,
|
|||
|
0, // AccessRequest,
|
|||
|
0, // IndexType,
|
|||
|
NULL, // KeyValue,
|
|||
|
0, // KeyValueLength,
|
|||
|
FALSE); // Submit
|
|||
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|||
|
FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|||
|
FrsFreeCommand(Cmd, NULL);
|
|||
|
//
|
|||
|
// Stats
|
|||
|
//
|
|||
|
VvJoinContext->NumberSent++;
|
|||
|
VvJoinContext->OutstandingCos++;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Stop the vvjoin thread
|
|||
|
//
|
|||
|
if (FrsIsShuttingDown) {
|
|||
|
return ERROR_PROCESS_ABORTED;
|
|||
|
}
|
|||
|
//
|
|||
|
// Stop the VvJoin if the cxtion is no longer joined
|
|||
|
//
|
|||
|
VV_JOIN_TRIGGER(VvJoinContext);
|
|||
|
|
|||
|
RETEST:
|
|||
|
if (!CxtionFlagIs(VvJoinContext->Cxtion, CXTION_FLAGS_JOIN_GUID_VALID) ||
|
|||
|
!GUIDS_EQUAL(&VvJoinContext->JoinGuid, &VvJoinContext->Cxtion->JoinGuid)) {
|
|||
|
DPRINT(0, ":V: VVJOIN ABORTED; MISMATCHED JOIN GUIDS\n");
|
|||
|
return ERROR_OPERATION_ABORTED;
|
|||
|
}
|
|||
|
//
|
|||
|
// Throttle the number of vvjoin change orders outstanding so
|
|||
|
// that we don't fill up the staging area or the database.
|
|||
|
//
|
|||
|
if (VvJoinContext->OutstandingCos >= VvJoinContext->MaxOutstandingCos) {
|
|||
|
if (VvJoinContext->Cxtion->OLCtx->OutstandingCos) {
|
|||
|
DPRINT2(0, ":V: Throttling for %d ms; %d OutstandingCos\n",
|
|||
|
OlTimeout, VvJoinContext->Cxtion->OLCtx->OutstandingCos);
|
|||
|
|
|||
|
Sleep(OlTimeout);
|
|||
|
|
|||
|
OlTimeout <<= 1;
|
|||
|
//
|
|||
|
// Too small
|
|||
|
//
|
|||
|
if (OlTimeout < VvJoinContext->OlTimeout) {
|
|||
|
OlTimeout = VvJoinContext->OlTimeout;
|
|||
|
}
|
|||
|
//
|
|||
|
// Too large
|
|||
|
//
|
|||
|
if (OlTimeout > VvJoinContext->OlTimeoutMax) {
|
|||
|
OlTimeout = VvJoinContext->OlTimeoutMax;
|
|||
|
}
|
|||
|
goto RETEST;
|
|||
|
}
|
|||
|
//
|
|||
|
// The number of outstanding cos went to 0; send another slug
|
|||
|
// of change orders to the outbound log process.
|
|||
|
//
|
|||
|
VvJoinContext->OutstandingCos = 0;
|
|||
|
}
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
ChgOrdInjectControlCo(
|
|||
|
IN PREPLICA Replica,
|
|||
|
IN PCXTION Cxtion,
|
|||
|
IN ULONG ContentCmd
|
|||
|
);
|
|||
|
ULONG
|
|||
|
MainVvJoin(
|
|||
|
PVOID FrsThreadCtxArg
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Entry point for processing vvjoins. This thread scans the IDTable
|
|||
|
and generates change orders for files and dirs that our outbound
|
|||
|
partner lacks. The outbound partner's version vector is used
|
|||
|
to select the files and dirs. This thread is invoked during
|
|||
|
join iff the change orders needed by our outbound partner have
|
|||
|
been deleted from the outbound log. See outlog.c for more info
|
|||
|
about this decision.
|
|||
|
|
|||
|
This process is termed a vvjoin to distinguish it from a normal
|
|||
|
join. A normal join sends the change orders in the outbound
|
|||
|
log to our outbound partner without invoking this thread.
|
|||
|
|
|||
|
This thread is a command server and is associated with a
|
|||
|
cxtion by a PCOMMAND_SERVER field (VvJoinCs) in the cxtion.
|
|||
|
Like all command servers, this thread will exit after a
|
|||
|
few minutes if there is no work and will be spawned when
|
|||
|
work shows up on its queue.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
FrsThreadCtxArg - Frs thread context
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
WIN32 Status
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "MainVvJoin:"
|
|||
|
|
|||
|
JET_ERR jerr;
|
|||
|
ULONG WStatus = ERROR_SUCCESS;
|
|||
|
ULONG FStatus;
|
|||
|
DWORD NumberSent;
|
|||
|
PCOMMAND_PACKET Cmd;
|
|||
|
PFRS_THREAD FrsThread = (PFRS_THREAD)FrsThreadCtxArg;
|
|||
|
PCOMMAND_SERVER VvJoinCs = FrsThread->Data;
|
|||
|
PVVJOIN_CONTEXT VvJoinContext = NULL;
|
|||
|
|
|||
|
DPRINT(1, ":S: VvJoin Thread is starting.\n");
|
|||
|
FrsThread->Exit = ThSupExitWithTombstone;
|
|||
|
|
|||
|
//
|
|||
|
// Quick verification test
|
|||
|
//
|
|||
|
VVJOIN_TEST();
|
|||
|
|
|||
|
//
|
|||
|
// Try-Finally
|
|||
|
//
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// Capture exception.
|
|||
|
//
|
|||
|
try {
|
|||
|
|
|||
|
CANT_EXIT_YET:
|
|||
|
|
|||
|
DPRINT(1, ":S: VvJoin Thread has started.\n");
|
|||
|
while((Cmd = FrsGetCommandServer(VvJoinCs)) != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Shutting down; stop accepting command packets
|
|||
|
//
|
|||
|
if (FrsIsShuttingDown) {
|
|||
|
FrsRunDownCommandServer(VvJoinCs, &VvJoinCs->Queue);
|
|||
|
}
|
|||
|
switch (Cmd->Command) {
|
|||
|
case CMD_VVJOIN_START: {
|
|||
|
DPRINT3(1, ":V: Start vvjoin for %ws\\%ws\\%ws\n",
|
|||
|
RsReplica(Cmd)->SetName->Name, RsReplica(Cmd)->MemberName->Name,
|
|||
|
RsCxtion(Cmd)->Name);
|
|||
|
//
|
|||
|
// The database must be started before we create a jet session
|
|||
|
// WARN: The database event may be set by the shutdown
|
|||
|
// code in order to force threads to exit.
|
|||
|
//
|
|||
|
WaitForSingleObject(DataBaseEvent, INFINITE);
|
|||
|
if (FrsIsShuttingDown) {
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
WStatus = ERROR_PROCESS_ABORTED;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Global info
|
|||
|
//
|
|||
|
VvJoinContext = FrsAlloc(sizeof(VVJOIN_CONTEXT));
|
|||
|
VvJoinContext->Send = VvJoinSend;
|
|||
|
|
|||
|
//
|
|||
|
// Outstanding change orders
|
|||
|
//
|
|||
|
CfgRegReadDWord(FKC_VVJOIN_LIMIT, NULL, 0, &VvJoinContext->MaxOutstandingCos);
|
|||
|
|
|||
|
DPRINT1(4, ":V: VvJoin Max OutstandingCos is %d\n",
|
|||
|
VvJoinContext->MaxOutstandingCos);
|
|||
|
|
|||
|
//
|
|||
|
// Outbound Log Throttle Timeout
|
|||
|
//
|
|||
|
CfgRegReadDWord(FKC_VVJOIN_TIMEOUT, NULL, 0, &VvJoinContext->OlTimeout);
|
|||
|
|
|||
|
if (VvJoinContext->OlTimeout < VVJOIN_TIMEOUT_MAX) {
|
|||
|
VvJoinContext->OlTimeoutMax = VVJOIN_TIMEOUT_MAX;
|
|||
|
} else {
|
|||
|
VvJoinContext->OlTimeoutMax = VvJoinContext->OlTimeout;
|
|||
|
}
|
|||
|
|
|||
|
DPRINT2(4, ":V: VvJoin Outbound Log Throttle Timeout is %d (%d max)\n",
|
|||
|
VvJoinContext->OlTimeout, VvJoinContext->OlTimeoutMax);
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a context for Jet to run in this thread.
|
|||
|
//
|
|||
|
VvJoinContext->ThreadCtx = FrsAllocType(THREAD_CONTEXT_TYPE);
|
|||
|
VvJoinContext->TableCtx = DbsCreateTableContext(IDTablex);
|
|||
|
|
|||
|
//
|
|||
|
// Setup a Jet Session (returning the session ID in ThreadCtx).
|
|||
|
//
|
|||
|
jerr = DbsCreateJetSession(VvJoinContext->ThreadCtx);
|
|||
|
if (JET_SUCCESS(jerr)) {
|
|||
|
DPRINT(4,":V: JetOpenDatabase complete\n");
|
|||
|
} else {
|
|||
|
DPRINT_JS(0,":V: ERROR - OpenDatabase failed.", jerr);
|
|||
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Pull over the params from the command packet into our context
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Replica
|
|||
|
//
|
|||
|
VvJoinContext->Replica = RsReplica(Cmd);
|
|||
|
//
|
|||
|
// Outbound version vector
|
|||
|
//
|
|||
|
VvJoinContext->CxtionVv = RsVVector(Cmd);
|
|||
|
RsVVector(Cmd) = NULL;
|
|||
|
//
|
|||
|
// Replica's version vector
|
|||
|
//
|
|||
|
VvJoinContext->ReplicaVv = RsReplicaVv(Cmd);
|
|||
|
RsReplicaVv(Cmd) = NULL;
|
|||
|
//
|
|||
|
// Join Guid
|
|||
|
//
|
|||
|
COPY_GUID(&VvJoinContext->JoinGuid, RsJoinGuid(Cmd));
|
|||
|
//
|
|||
|
// Cxtion
|
|||
|
//
|
|||
|
VvJoinContext->Cxtion = GTabLookup(VvJoinContext->Replica->Cxtions,
|
|||
|
RsCxtion(Cmd)->Guid,
|
|||
|
NULL);
|
|||
|
if (!VvJoinContext->Cxtion) {
|
|||
|
DPRINT2(4, ":V: No Cxtion for %ws\\%ws; unjoining\n",
|
|||
|
VvJoinContext->Replica->SetName->Name,
|
|||
|
VvJoinContext->Replica->MemberName->Name);
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
DPRINT2(4, ":V: VvJoining %ws\\%ws\n",
|
|||
|
VvJoinContext->Replica->SetName->Name,
|
|||
|
VvJoinContext->Replica->MemberName->Name);
|
|||
|
|
|||
|
VV_PRINT_OUTBOUND(4, L"Cxtion ", VvJoinContext->CxtionVv);
|
|||
|
VV_PRINT_OUTBOUND(4, L"Replica ", VvJoinContext->ReplicaVv);
|
|||
|
|
|||
|
VVJOIN_TEST_SKIP_BEGIN(VvJoinContext, Cmd);
|
|||
|
|
|||
|
//
|
|||
|
// Init the table context and open the ID table.
|
|||
|
//
|
|||
|
jerr = DbsOpenTable(VvJoinContext->ThreadCtx,
|
|||
|
VvJoinContext->TableCtx,
|
|||
|
VvJoinContext->Replica->ReplicaNumber,
|
|||
|
IDTablex,
|
|||
|
NULL);
|
|||
|
if (!JET_SUCCESS(jerr)) {
|
|||
|
DPRINT_JS(0,":V: ERROR - DbsOpenTable failed.", jerr);
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Scan thru the IDTable by the FileGuidIndex calling
|
|||
|
// VvJoinBuildTables() for each record to make entires
|
|||
|
// in the vvjoin tables.
|
|||
|
//
|
|||
|
jerr = FrsEnumerateTable(VvJoinContext->ThreadCtx,
|
|||
|
VvJoinContext->TableCtx,
|
|||
|
GuidIndexx,
|
|||
|
VvJoinBuildTables,
|
|||
|
VvJoinContext);
|
|||
|
|
|||
|
//
|
|||
|
// We're done. Return success if we made it to the end
|
|||
|
// of the ID Table.
|
|||
|
//
|
|||
|
if (jerr != JET_errNoCurrentRecord ) {
|
|||
|
DPRINT_JS(0,":V: ERROR - FrsEnumerateTable failed.", jerr);
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
break;
|
|||
|
}
|
|||
|
VVJOIN_PRINT(5, VvJoinContext);
|
|||
|
|
|||
|
//
|
|||
|
// Send the files and dirs to our outbound partner in order,
|
|||
|
// if possible. Otherwise send them on out-of-order. Stop
|
|||
|
// on error or shutdown.
|
|||
|
//
|
|||
|
do {
|
|||
|
//
|
|||
|
// Send in order
|
|||
|
//
|
|||
|
NumberSent = VvJoinContext->NumberSent;
|
|||
|
WStatus = VvJoinSendInOrder(VvJoinContext);
|
|||
|
//
|
|||
|
// Send out of order
|
|||
|
//
|
|||
|
// If none could be sent in order, send one out-of-order
|
|||
|
// and then try to send in-order again.
|
|||
|
//
|
|||
|
if (WIN_SUCCESS(WStatus) &&
|
|||
|
!FrsIsShuttingDown &&
|
|||
|
NumberSent == VvJoinContext->NumberSent) {
|
|||
|
WStatus = VvJoinSendOutOfOrder(VvJoinContext);
|
|||
|
}
|
|||
|
} while (WIN_SUCCESS(WStatus) &&
|
|||
|
!FrsIsShuttingDown &&
|
|||
|
NumberSent != VvJoinContext->NumberSent);
|
|||
|
|
|||
|
//
|
|||
|
// Shutting down; abort
|
|||
|
//
|
|||
|
if (FrsIsShuttingDown) {
|
|||
|
WStatus = ERROR_PROCESS_ABORTED;
|
|||
|
}
|
|||
|
|
|||
|
DPRINT5(1, ":V: vvjoin %s for %ws\\%ws\\%ws (%d sent)\n",
|
|||
|
(WIN_SUCCESS(WStatus)) ? "succeeded" : "failed",
|
|||
|
RsReplica(Cmd)->SetName->Name, RsReplica(Cmd)->MemberName->Name,
|
|||
|
RsCxtion(Cmd)->Name, VvJoinContext->NumberSent);
|
|||
|
|
|||
|
VVJOIN_TEST_SKIP_END(VvJoinContext);
|
|||
|
|
|||
|
//
|
|||
|
// We either finished without problems or we force an unjoin
|
|||
|
//
|
|||
|
if (WIN_SUCCESS(WStatus)) {
|
|||
|
ChgOrdInjectControlCo(VvJoinContext->Replica,
|
|||
|
VvJoinContext->Cxtion,
|
|||
|
FCN_CO_NORMAL_VVJOIN_TERM);
|
|||
|
VvJoinContext->NumberSent++;
|
|||
|
if (CxtionFlagIs(VvJoinContext->Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|||
|
ChgOrdInjectControlCo(VvJoinContext->Replica,
|
|||
|
VvJoinContext->Cxtion,
|
|||
|
FCN_CO_END_OF_JOIN);
|
|||
|
VvJoinContext->NumberSent++;
|
|||
|
}
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_VVJOIN_SUCCESS);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ChgOrdInjectControlCo(VvJoinContext->Replica,
|
|||
|
VvJoinContext->Cxtion,
|
|||
|
FCN_CO_ABNORMAL_VVJOIN_TERM);
|
|||
|
VvJoinContext->NumberSent++;
|
|||
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case CMD_VVJOIN_DONE: {
|
|||
|
DPRINT3(1, ":V: Stop vvjoin for %ws\\%ws\\%ws\n",
|
|||
|
RsReplica(Cmd)->SetName->Name, RsReplica(Cmd)->MemberName->Name,
|
|||
|
RsCxtion(Cmd)->Name);
|
|||
|
FrsRunDownCommandServer(VvJoinCs, &VvJoinCs->Queue);
|
|||
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case CMD_VVJOIN_DONE_UNJOIN: {
|
|||
|
DPRINT3(1, ":V: Stop vvjoin for unjoining %ws\\%ws\\%ws\n",
|
|||
|
RsReplica(Cmd)->SetName->Name, RsReplica(Cmd)->MemberName->Name,
|
|||
|
RsCxtion(Cmd)->Name);
|
|||
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
DPRINT1(0, ":V: ERROR - Unknown command %08x\n", Cmd->Command);
|
|||
|
FrsCompleteCommand(Cmd, ERROR_INVALID_PARAMETER);
|
|||
|
break;
|
|||
|
}
|
|||
|
} // end of switch
|
|||
|
//
|
|||
|
// Clean up our context
|
|||
|
//
|
|||
|
VvJoinContext = VvJoinFreeContext(VvJoinContext);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VvJoinContext = VvJoinFreeContext(VvJoinContext);
|
|||
|
DPRINT(1, ":S: Vv Join Thread is exiting.\n");
|
|||
|
FrsExitCommandServer(VvJoinCs, FrsThread);
|
|||
|
DPRINT(1, ":S: CAN'T EXIT, YET; Vv Join Thread is still running.\n");
|
|||
|
goto CANT_EXIT_YET;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get exception status.
|
|||
|
//
|
|||
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|||
|
GET_EXCEPTION_CODE(WStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
if (WIN_SUCCESS(WStatus)) {
|
|||
|
if (AbnormalTermination()) {
|
|||
|
WStatus = ERROR_OPERATION_ABORTED;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
DPRINT_WS(0, "VvJoinCs finally.", WStatus);
|
|||
|
|
|||
|
//
|
|||
|
// Trigger FRS shutdown if we terminated abnormally.
|
|||
|
//
|
|||
|
if (!WIN_SUCCESS(WStatus) && (WStatus != ERROR_PROCESS_ABORTED)) {
|
|||
|
DPRINT(0, "VvJoinCs terminated abnormally, forcing service shutdown.\n");
|
|||
|
FrsIsShuttingDown = TRUE;
|
|||
|
SetEvent(ShutDownEvent);
|
|||
|
} else {
|
|||
|
WStatus = ERROR_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return WStatus;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SubmitVvJoin(
|
|||
|
IN PREPLICA Replica,
|
|||
|
IN PCXTION Cxtion,
|
|||
|
IN USHORT Command
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Submit a command to a vvjoin command server.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Replica
|
|||
|
Cxtion
|
|||
|
Command
|
|||
|
|
|||
|
Return Value:
|
|||
|
None.
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "SubmitVvJoin:"
|
|||
|
PCOMMAND_PACKET Cmd;
|
|||
|
|
|||
|
//
|
|||
|
// Don't create command servers during shutdown
|
|||
|
// Obviously, the check isn't protected and so
|
|||
|
// a command server may get kicked off and never
|
|||
|
// rundown if the replica subsystem shutdown
|
|||
|
// function has already been called BUT the
|
|||
|
// vvjoin threads use the exittombstone so
|
|||
|
// the shutdown thread won't wait too long for the
|
|||
|
// vvjoin threads to exit.
|
|||
|
//
|
|||
|
if (FrsIsShuttingDown) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// First submission; create the command server
|
|||
|
//
|
|||
|
if (!Cxtion->VvJoinCs) {
|
|||
|
Cxtion->VvJoinCs = FrsAlloc(sizeof(COMMAND_SERVER));
|
|||
|
FrsInitializeCommandServer(Cxtion->VvJoinCs,
|
|||
|
VVJOIN_MAXTHREADS_PER_CXTION,
|
|||
|
L"VvJoinCs",
|
|||
|
MainVvJoin);
|
|||
|
}
|
|||
|
Cmd = FrsAllocCommand(&Cxtion->VvJoinCs->Queue, Command);
|
|||
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|||
|
|
|||
|
//
|
|||
|
// Outbound version vector
|
|||
|
//
|
|||
|
RsReplica(Cmd) = Replica;
|
|||
|
RsCxtion(Cmd) = FrsDupGName(Cxtion->Name);
|
|||
|
RsJoinGuid(Cmd) = FrsDupGuid(&Cxtion->JoinGuid);
|
|||
|
RsVVector(Cmd) = VVDupOutbound(Cxtion->VVector);
|
|||
|
RsReplicaVv(Cmd) = VVDupOutbound(Replica->VVector);
|
|||
|
|
|||
|
//
|
|||
|
// And away we go
|
|||
|
//
|
|||
|
DPRINT5(4, ":V: Submit %08x for Cmd %08x %ws\\%ws\\%ws\n",
|
|||
|
Cmd->Command, Cmd, RsReplica(Cmd)->SetName->Name,
|
|||
|
RsReplica(Cmd)->MemberName->Name, RsCxtion(Cmd)->Name);
|
|||
|
|
|||
|
FrsSubmitCommandServer(Cxtion->VvJoinCs, Cmd);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
SubmitVvJoinSync(
|
|||
|
IN PREPLICA Replica,
|
|||
|
IN PCXTION Cxtion,
|
|||
|
IN USHORT Command
|
|||
|
)
|
|||
|
/*++
|
|||
|
Routine Description:
|
|||
|
Submit a command to a vvjoin command server.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Replica
|
|||
|
Cxtion
|
|||
|
Command
|
|||
|
|
|||
|
Return Value:
|
|||
|
None.
|
|||
|
--*/
|
|||
|
{
|
|||
|
#undef DEBSUB
|
|||
|
#define DEBSUB "SubmitVvJoinSync:"
|
|||
|
PCOMMAND_PACKET Cmd;
|
|||
|
DWORD WStatus;
|
|||
|
|
|||
|
//
|
|||
|
// First submission; done
|
|||
|
//
|
|||
|
if (!Cxtion->VvJoinCs) {
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
Cmd = FrsAllocCommand(&Cxtion->VvJoinCs->Queue, Command);
|
|||
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|||
|
|
|||
|
//
|
|||
|
// Outbound version vector
|
|||
|
//
|
|||
|
RsReplica(Cmd) = Replica;
|
|||
|
RsCxtion(Cmd) = FrsDupGName(Cxtion->Name);
|
|||
|
RsCompletionEvent(Cmd) = FrsCreateEvent(TRUE, FALSE);
|
|||
|
|
|||
|
//
|
|||
|
// And away we go
|
|||
|
//
|
|||
|
DPRINT5(4, ":V: Submit Sync %08x for Cmd %08x %ws\\%ws\\%ws\n",
|
|||
|
Cmd->Command, Cmd, RsReplica(Cmd)->SetName->Name,
|
|||
|
RsReplica(Cmd)->MemberName->Name, RsCxtion(Cmd)->Name);
|
|||
|
|
|||
|
FrsSubmitCommandServer(Cxtion->VvJoinCs, Cmd);
|
|||
|
|
|||
|
//
|
|||
|
// Wait for the command to finish
|
|||
|
//
|
|||
|
WaitForSingleObject(RsCompletionEvent(Cmd), INFINITE);
|
|||
|
FRS_CLOSE(RsCompletionEvent(Cmd));
|
|||
|
|
|||
|
WStatus = Cmd->ErrorStatus;
|
|||
|
FrsCompleteCommand(Cmd, Cmd->ErrorStatus);
|
|||
|
return WStatus;
|
|||
|
}
|