Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

2515 lines
75 KiB

/*++
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;
}