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.
11142 lines
344 KiB
11142 lines
344 KiB
/*++
|
|
|
|
Copyright (c) 1997-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
replica.c
|
|
|
|
Abstract:
|
|
Replica Control Command Server (Replica).
|
|
|
|
The Ds Poller periodically pulls the configuration from the DS,
|
|
checks it for consistency, and then merges it with the current
|
|
local configuration. The Ds Poller then makes copies of each
|
|
replica for this machine and sends the copy to this replica
|
|
control command server.
|
|
|
|
Author:
|
|
Billy J. Fuller 23-May-1997
|
|
|
|
Revised:
|
|
David A. Orbits 24-Jan-1998, added locking and interfaced with INLOG.
|
|
Restructured Connection JOIN sequence.
|
|
Jul-1999 Registry code rewrite
|
|
|
|
Environment
|
|
User mode winnt
|
|
|
|
--*/
|
|
|
|
#include <ntreppch.h>
|
|
#pragma hdrstop
|
|
|
|
|
|
#include <ntdsapi.h>
|
|
#include <frs.h>
|
|
#include <tablefcn.h>
|
|
#include <ntfrsapi.h>
|
|
#include <perrepsr.h>
|
|
#include <Sddl.h>
|
|
|
|
//
|
|
// Connection flags.
|
|
//
|
|
FLAG_NAME_TABLE CxtionFlagNameTable[] = {
|
|
{CXTION_FLAGS_CONSISTENT , "Consistent " },
|
|
{CXTION_FLAGS_SCHEDULE_OFF , "SchedOff " },
|
|
{CXTION_FLAGS_VOLATILE , "Volatile " },
|
|
{CXTION_FLAGS_DEFERRED_JOIN , "DeferredJoin " },
|
|
|
|
{CXTION_FLAGS_DEFERRED_UNJOIN , "DeferUnjoin " },
|
|
{CXTION_FLAGS_TIMEOUT_SET , "TimeOutSet " },
|
|
{CXTION_FLAGS_JOIN_GUID_VALID , "JoinGuidValid " },
|
|
{CXTION_FLAGS_UNJOIN_GUID_VALID , "UnJoinGuidValid "},
|
|
|
|
{CXTION_FLAGS_PERFORM_VVJOIN , "PerformVVJoin" },
|
|
{CXTION_FLAGS_DEFERRED_DELETE , "DeferredDel " },
|
|
{CXTION_FLAGS_PAUSED , "Paused " },
|
|
{CXTION_FLAGS_HUNG_INIT_SYNC , "HungInitSync " },
|
|
{CXTION_FLAGS_TRIM_OUTLOG , "TrimOutLog " },
|
|
{CXTION_FLAGS_INIT_SYNC , "InitSync " },
|
|
{CXTION_FLAGS_TRIGGER_SCHEDULE , "TriggerSched " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
//
|
|
// Directory and file filter lists from registry.
|
|
//
|
|
extern PWCHAR RegistryFileExclFilterList;
|
|
extern PWCHAR RegistryDirExclFilterList;
|
|
|
|
extern ULONGLONG ActiveChange;
|
|
|
|
BOOL CurrentSysvolReadyIsValid;
|
|
DWORD CurrentSysvolReady;
|
|
|
|
//
|
|
// Replica tombstone in days
|
|
//
|
|
DWORD ReplicaTombstone;
|
|
ULONGLONG ReplicaTombstoneInFileTime;
|
|
|
|
//
|
|
// Retry a join every MinJoinRetry milliseconds, increasing by
|
|
// MinJoinRetry every retry (but no longer than MaxJoinRetry).
|
|
//
|
|
#define JOIN_RETRY_EVENT (5) // record event every 5 join retries
|
|
LONG MinJoinRetry;
|
|
LONG MaxJoinRetry;
|
|
|
|
|
|
extern DWORD CommTimeoutInMilliSeconds;
|
|
|
|
//
|
|
// Start replication even if the DS could not be accessed
|
|
//
|
|
DWORD ReplicaStartTimeout;
|
|
|
|
//
|
|
// Struct for the Replica Control Command Server
|
|
// Contains info about the queues and the threads
|
|
//
|
|
COMMAND_SERVER ReplicaCmdServer;
|
|
|
|
//
|
|
// Table of active replicas
|
|
//
|
|
PGEN_TABLE ReplicasByGuid;
|
|
PGEN_TABLE ReplicasByNumber;
|
|
|
|
//
|
|
// Table of deleted replicas discovered at startup. These replicas
|
|
// never make it into the active tables, ever.
|
|
//
|
|
PGEN_TABLE DeletedReplicas;
|
|
|
|
//
|
|
// Table of cxtions deleted during runtime. They are eventually
|
|
// freed at shutdown.
|
|
//
|
|
PGEN_TABLE DeletedCxtions;
|
|
|
|
PGEN_TABLE ReplicasNotInTheDs;
|
|
|
|
|
|
#define MINUTES_IN_INTERVAL (15)
|
|
|
|
#define CMD_DELETE_RETRY_SHORT_TIMEOUT (10 * 1000)
|
|
#define CMD_DELETE_RETRY_LONG_TIMEOUT (60 * 1000)
|
|
|
|
//
|
|
// Partners are not allowed to join if their clocks are out-of-sync
|
|
//
|
|
ULONGLONG MaxPartnerClockSkew;
|
|
DWORD PartnerClockSkew;
|
|
|
|
#define ReplicaCmdSetInitialTimeOut(_Cmd_, _Init_) \
|
|
if (RsTimeout(Cmd) == 0) { \
|
|
RsTimeout(Cmd) = (_Init_); \
|
|
}
|
|
|
|
#define SET_JOINED(_Replica_, _Cxtion_, _S_) \
|
|
{ \
|
|
SetCxtionState(_Cxtion_, CxtionStateJoined); \
|
|
PM_INC_CTR_REPSET((_Replica_), Joins, 1); \
|
|
PM_INC_CTR_CXTION((_Cxtion_), Joins, 1); \
|
|
\
|
|
DPRINT3(0, ":X: ***** %s CxtG %08x "FORMAT_CXTION_PATH2"\n", \
|
|
_S_, \
|
|
((_Cxtion_) != NULL) ? ((PCXTION)(_Cxtion_))->Name->Guid->Data1 : 0,\
|
|
PRINT_CXTION_PATH2(_Replica_, _Cxtion_)); \
|
|
if (_Cxtion_->JoinCmd && \
|
|
(LONG)RsTimeout(_Cxtion_->JoinCmd) > (JOIN_RETRY_EVENT * MinJoinRetry)) { \
|
|
if (_Cxtion_->Inbound) { \
|
|
EPRINT3(EVENT_FRS_LONG_JOIN_DONE, \
|
|
_Cxtion_->Partner->Name, ComputerName, _Replica_->Root); \
|
|
} else { \
|
|
EPRINT3(EVENT_FRS_LONG_JOIN_DONE, \
|
|
ComputerName, _Cxtion_->Partner->Name, _Replica_->Root); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
#define UNIDLE_CO_PROCESS_QUEUE(_Replica_, _Cxtion_, _CoProcessQueue_) \
|
|
{ \
|
|
if (_CoProcessQueue_ != NULL) { \
|
|
CXTION_STATE_TRACE(3, _Cxtion_, _Replica_, 0, "CO Process Q Unblock"); \
|
|
FrsRtlUnIdledQueue(_CoProcessQueue_); \
|
|
_CoProcessQueue_ = NULL; \
|
|
} \
|
|
}
|
|
|
|
//
|
|
// UNJOIN TRIGGER
|
|
//
|
|
// Trigger an unjoin after N co's on *ONE* cxtion *ONE* time.
|
|
//
|
|
#if DBG
|
|
#define PULL_UNJOIN_TRIGGER(_Cxtion_, _Cmd_) \
|
|
{ \
|
|
if (_Cxtion_->UnjoinTrigger && (--_Cxtion_->UnjoinTrigger == 0)) { \
|
|
DPRINT1(0, ":X: UNJOIN TRIGGER FIRED FOR %ws\n", _Cxtion_->Name->Name); \
|
|
FrsCompleteCommand(_Cmd_, ERROR_OPERATION_ABORTED); \
|
|
return; \
|
|
} \
|
|
}
|
|
|
|
#define SET_UNJOIN_TRIGGER(_Cxtion_) \
|
|
{ \
|
|
if (DebugInfo.UnjoinTrigger) { \
|
|
_Cxtion_->UnjoinReset = DebugInfo.UnjoinTrigger; \
|
|
DebugInfo.UnjoinTrigger = 0; \
|
|
} \
|
|
_Cxtion_->UnjoinTrigger = _Cxtion_->UnjoinReset; \
|
|
_Cxtion_->UnjoinReset <<= 1; \
|
|
_Cxtion_->UnjoinReset = 0; \
|
|
}
|
|
#else DBG
|
|
#define SET_UNJOIN_TRIGGER(_Cxtion_)
|
|
#define PULL_UNJOIN_TRIGGER(_Cxtion_, _Cmd_)
|
|
#endif DBG
|
|
|
|
//
|
|
// Flags for RcsCheckCmd.
|
|
//
|
|
#define CHECK_CMD_PARTNERCOC (0x00000001)
|
|
#define CHECK_CMD_REPLICA (0x00000002)
|
|
#define CHECK_CMD_CXTION (0x00000004)
|
|
#define CHECK_CMD_JOINGUID (0x00000008)
|
|
|
|
#define CHECK_CMD_COE (0x00000010)
|
|
#define CHECK_CMD_COGUID (0x00000020)
|
|
#define CHECK_CMD_NOT_EXPIRED (0x00000040)
|
|
#define CHECK_CMD_JOINTIME (0x00000080)
|
|
|
|
#define CHECK_CMD_REPLICA_VERSION_GUID (0x00000100)
|
|
|
|
#define CHECK_CMD_CXTION_OK (CHECK_CMD_REPLICA | \
|
|
CHECK_CMD_CXTION)
|
|
|
|
#define CHECK_CMD_CXTION_AND_COGUID_OK (CHECK_CMD_CXTION_OK | \
|
|
CHECK_CMD_COGUID)
|
|
|
|
#define CHECK_CMD_CXTION_AND_JOINGUID_OK (CHECK_CMD_CXTION_OK | \
|
|
CHECK_CMD_JOINGUID)
|
|
|
|
|
|
VOID
|
|
ChgOrdStartJoinRequest(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
);
|
|
|
|
ULONG
|
|
OutLogRetireCo(
|
|
PREPLICA Replica,
|
|
ULONG COx,
|
|
PCXTION PartnerCxtion
|
|
);
|
|
|
|
ULONG
|
|
OutLogInitPartner(
|
|
PREPLICA Replica,
|
|
PCXTION PartnerInfo
|
|
);
|
|
|
|
ULONG
|
|
RcsForceUnjoin(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
);
|
|
|
|
VOID
|
|
RcsCreatePerfmonCxtionName(
|
|
PREPLICA Replica,
|
|
PCXTION Cxtion
|
|
);
|
|
|
|
DWORD
|
|
SndCsAssignCommQueue(
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
SndCsCreateCxtion(
|
|
IN OUT PCXTION Cxtion
|
|
);
|
|
|
|
VOID
|
|
SndCsDestroyCxtion(
|
|
IN PCXTION Cxtion,
|
|
IN DWORD CxtionFlags
|
|
);
|
|
|
|
VOID
|
|
SndCsSubmitCommPkt(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN PCHANGE_ORDER_ENTRY Coe,
|
|
IN GUID *JoinGuid,
|
|
IN BOOL SetTimeout,
|
|
IN PCOMM_PACKET CommPkt,
|
|
IN DWORD CommQueueIndex
|
|
);
|
|
|
|
VOID
|
|
SndCsSubmitCommPkt2(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN PCHANGE_ORDER_ENTRY Coe,
|
|
IN BOOL SetTimeout,
|
|
IN PCOMM_PACKET CommPkt
|
|
);
|
|
|
|
VOID
|
|
SndCsSubmitCmd(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN PCOMMAND_SERVER FlushCs,
|
|
IN PCOMMAND_PACKET FlushCmd,
|
|
IN DWORD CommQueueIndex
|
|
);
|
|
|
|
VOID
|
|
ChgOrdInjectControlCo(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN ULONG ContentCmd
|
|
);
|
|
|
|
VOID
|
|
RcsUpdateReplicaSetMember(
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
VOID
|
|
RcsReplicaSetRegistry(
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
VOID
|
|
CfgFilesNotToBackup(
|
|
IN PGEN_TABLE Replicas
|
|
);
|
|
|
|
DWORD
|
|
NtFrsApi_Rpc_BindEx(
|
|
IN PWCHAR MachineName,
|
|
OUT PWCHAR *OutPrincName,
|
|
OUT handle_t *OutHandle,
|
|
OUT ULONG *OutParentAuthLevel
|
|
);
|
|
|
|
PWCHAR
|
|
FrsDsConvertName(
|
|
IN HANDLE Handle,
|
|
IN PWCHAR InputName,
|
|
IN DWORD InputFormat,
|
|
IN PWCHAR DomainDnsName,
|
|
IN DWORD DesiredFormat
|
|
);
|
|
|
|
DWORD
|
|
UtilRpcServerHandleToAuthSidString(
|
|
IN handle_t ServerHandle,
|
|
IN PWCHAR AuthClient,
|
|
OUT PWCHAR *ClientSid
|
|
);
|
|
|
|
|
|
VOID
|
|
RcsCloseReplicaSetmember(
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
VOID
|
|
RcsCloseReplicaCxtions(
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
DbsProcessReplicaFaultList(
|
|
PDWORD pReplicaSetsDeleted
|
|
);
|
|
|
|
ULONG
|
|
DbsCheckForOverlapErrors(
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
PCOMMAND_PACKET
|
|
CommPktToCmd(
|
|
IN PCOMM_PACKET CommPkt
|
|
);
|
|
|
|
PCOMM_PACKET
|
|
CommBuildCommPkt(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN ULONG Command,
|
|
IN PGEN_TABLE VVector,
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCHANGE_ORDER_COMMAND Coc
|
|
);
|
|
|
|
|
|
BOOL
|
|
RcsAreAuthNamesEqual(
|
|
IN PWCHAR AuthName1,
|
|
IN PWCHAR AuthName2
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Are the two auth names equal?
|
|
|
|
The principle name comes from an rpc server handle or
|
|
from DsCrackName(NT4 ACCOUNT NAME). The formats are
|
|
slightly different so this isn't a simple wcsicmp().
|
|
|
|
The principle name from the server handle has the format
|
|
DNS-Domain-Name\ComputerName$.
|
|
|
|
The principle name from DsCrackName has the format
|
|
NetBIOS-Domain-Name\ComputerName$.
|
|
|
|
The principle name from RpcMgmtInqServerPrincName() has the format
|
|
ComputerName$@DNS-Domain-Name
|
|
|
|
The names may be stringized sids.
|
|
|
|
Arguments:
|
|
AuthName1 - from rpc server handle or DsCrackName()
|
|
AuthName2 - from rpc server handle or DsCrackName()
|
|
|
|
Return Value:
|
|
TRUE - the princnames are effectively equal
|
|
FALSE - not
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsAreAuthNamesEqual:"
|
|
BOOL AreEqual = FALSE;
|
|
PWCHAR c;
|
|
PWCHAR Sam1Begin;
|
|
PWCHAR Sam2Begin;
|
|
PWCHAR Sam1End;
|
|
PWCHAR Sam2End;
|
|
PWCHAR Dom1Begin;
|
|
PWCHAR Dom2Begin;
|
|
PWCHAR Dom1End;
|
|
PWCHAR Dom2End;
|
|
|
|
//
|
|
// NULL Param
|
|
//
|
|
if (!AuthName1 || !AuthName2) {
|
|
if (!AuthName1 && !AuthName2) {
|
|
AreEqual = TRUE;
|
|
goto CLEANUP;
|
|
}
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// principal names are usually in the format domain\samaccount
|
|
// or stringized SID
|
|
//
|
|
if (WSTR_EQ(AuthName1, AuthName2)) {
|
|
AreEqual = TRUE;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Find the sam account name and the domain name
|
|
//
|
|
for (c = AuthName1; *c && *c != L'\\' && *c != L'@'; ++c);
|
|
if (*c) {
|
|
//
|
|
// domain\samaccount
|
|
//
|
|
if (*c == L'\\') {
|
|
Dom1Begin = AuthName1;
|
|
Dom1End = c;
|
|
Sam1Begin = c + 1;
|
|
Sam1End = &AuthName1[wcslen(AuthName1)];
|
|
}
|
|
//
|
|
// samaccount@dnsdomain
|
|
//
|
|
else {
|
|
Sam1Begin = AuthName1;
|
|
Sam1End = c;
|
|
Dom1Begin = c + 1;
|
|
for (; *c && *c != L'.'; ++c);
|
|
Dom1End = c;
|
|
}
|
|
}
|
|
//
|
|
// Unknown format
|
|
//
|
|
else {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
for (c = AuthName2; *c && *c != L'\\' && *c != L'@'; ++c);
|
|
if (*c) {
|
|
//
|
|
// domain\samaccount
|
|
//
|
|
if (*c == L'\\') {
|
|
Dom2Begin = AuthName2;
|
|
Dom2End = c;
|
|
Sam2Begin = c + 1;
|
|
Sam2End = &AuthName2[wcslen(AuthName2)];
|
|
}
|
|
//
|
|
// samaccount@dnsdomain
|
|
//
|
|
else {
|
|
Sam2Begin = AuthName2;
|
|
Sam2End = c;
|
|
Dom2Begin = c + 1;
|
|
for (; *c && *c != L'.'; ++c);
|
|
Dom2End = c;
|
|
}
|
|
}
|
|
//
|
|
// Unknown format
|
|
//
|
|
else {
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Compare samaccount
|
|
//
|
|
while (Sam1Begin != Sam1End && Sam2Begin != Sam2End) {
|
|
if (towlower(*Sam1Begin) != towlower(*Sam2Begin)) {
|
|
goto CLEANUP;
|
|
}
|
|
++Sam1Begin;
|
|
++Sam2Begin;
|
|
}
|
|
|
|
//
|
|
// compare domain
|
|
//
|
|
while (Dom1Begin != Dom1End && Dom2Begin != Dom2End) {
|
|
if (towlower(*Dom1Begin) != towlower(*Dom2Begin)) {
|
|
goto CLEANUP;
|
|
}
|
|
++Dom1Begin;
|
|
++Dom2Begin;
|
|
}
|
|
|
|
AreEqual = (Sam1Begin == Sam1End &&
|
|
Sam2Begin == Sam2End &&
|
|
Dom1Begin == Dom1End &&
|
|
Dom2Begin == Dom2End);
|
|
CLEANUP:
|
|
|
|
DPRINT3(4, "Auth names %ws %s %ws\n",
|
|
AuthName1, (AreEqual) ? "==" : "!=", AuthName2);
|
|
|
|
return AreEqual;
|
|
}
|
|
|
|
|
|
PREPLICA
|
|
RcsFindReplicaByNumber(
|
|
IN ULONG ReplicaNumber
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Find the replica by internal number
|
|
|
|
Arguments:
|
|
ReplicaNumber
|
|
|
|
Return Value:
|
|
Address of the replica or NULL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindReplicaByNumber:"
|
|
//
|
|
// Find the replica by internal replica number (used in the Jet Table names)
|
|
//
|
|
return GTabLookup(ReplicasByNumber, &ReplicaNumber, NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
PREPLICA
|
|
RcsFindReplicaByGuid(
|
|
IN GUID *Guid
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Find a replica by Guid
|
|
|
|
Arguments:
|
|
Guid - Replica->ReplicaName->Guid
|
|
|
|
Return Value:
|
|
Address of the replica or NULL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindReplicaByGuid:"
|
|
//
|
|
// Find the replica by guid
|
|
//
|
|
return GTabLookup(ReplicasByGuid, Guid, NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
PREPLICA
|
|
RcsFindReplicaById(
|
|
IN ULONG Id
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Find a replica given the internal ID.
|
|
|
|
Arguments:
|
|
|
|
Id - Internal Replica ID.
|
|
|
|
Return Value:
|
|
Address of the replica or NULL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindReplicaById:"
|
|
PREPLICA RetVal = NULL;
|
|
|
|
ForEachListEntry( &ReplicaListHead, REPLICA, ReplicaList,
|
|
if (pE->ReplicaNumber == Id) {
|
|
RetVal = pE;
|
|
break;
|
|
}
|
|
);
|
|
|
|
return RetVal;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsCheckCmd(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCHAR Debsub,
|
|
IN ULONG Flags
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check the command packet for the specified fields.
|
|
|
|
Arguments:
|
|
Cmd
|
|
Hdr
|
|
Flags
|
|
|
|
Return Value:
|
|
TRUE - packet is ok
|
|
FALSE - not
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckCmd:"
|
|
BOOL Ret = TRUE;
|
|
PREPLICA Replica = RsReplica(Cmd);
|
|
CHAR Tstr1[128];
|
|
|
|
//
|
|
// Replica
|
|
//
|
|
if ((Flags & CHECK_CMD_REPLICA) && !RsReplica(Cmd)) {
|
|
DPRINT(0, "WARN - No replica in command packet\n");
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//
|
|
// Partner change order command
|
|
//
|
|
if ((Flags & CHECK_CMD_PARTNERCOC) && !RsPartnerCoc(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_PARTNERCOC failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Replica has been (or may be) deleted
|
|
//
|
|
if ((Flags & CHECK_CMD_NOT_EXPIRED) &&
|
|
!IS_TIME_ZERO(RsReplica(Cmd)->MembershipExpires)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_NOT_EXPIRED failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Cxtion
|
|
//
|
|
if ((Flags & CHECK_CMD_CXTION) &&
|
|
!(RsCxtion(Cmd) && RsCxtion(Cmd)->Guid)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_CXTION failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Join guid
|
|
//
|
|
if ((Flags & CHECK_CMD_JOINGUID) && !RsJoinGuid(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_JOINGUID failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Replica Version Guid
|
|
//
|
|
if ((Flags & CHECK_CMD_REPLICA_VERSION_GUID) &&
|
|
!RsReplicaVersionGuid(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_REPLICA_VERSION_GUID failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Change order entry
|
|
//
|
|
if ((Flags & CHECK_CMD_COE) && !RsCoe(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_COE failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Change order guid
|
|
//
|
|
if ((Flags & CHECK_CMD_COGUID) && !RsCoGuid(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_COGUID failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
//
|
|
// Join time
|
|
//
|
|
if ((Flags & CHECK_CMD_JOINTIME) && !RsJoinTime(Cmd)) {
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CHECK_CMD_JOINTIME failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
Ret = FALSE;
|
|
}
|
|
|
|
if (!Ret) {
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, ERROR_INVALID_PARAMETER, Tstr1);
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsCheckCxtionCommon(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCXTION Cxtion,
|
|
IN PCHAR Debsub,
|
|
IN ULONG Flags,
|
|
OUT PFRS_QUEUE *CoProcessQueue
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
find the in/outbound cxtion referenced by a command received
|
|
from a remote machine.
|
|
|
|
The caller should have used RcsCheckCmd() with
|
|
CHECK_CMD_REPLICA
|
|
CHECK_CMD_CXTION
|
|
CHECK_CMD_JOINGUID (if CHECK_CXTION_JOINGUID)
|
|
before calling this function.
|
|
|
|
Arguments:
|
|
Cmd -- Command packet
|
|
Cxtion -- connection struct to be checked.
|
|
Debsub
|
|
Flags
|
|
CoProcessQueue -- return the pointer to the process queue to unidle.
|
|
|
|
Return Value:
|
|
Error status code.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckCxtionCommon:"
|
|
|
|
PREPLICA Replica = RsReplica(Cmd);
|
|
|
|
//
|
|
// Cxtion exists
|
|
//
|
|
if ((Flags & CHECK_CXTION_EXISTS) &&
|
|
((Cxtion == NULL) ||
|
|
CxtionStateIs(Cxtion, CxtionStateInit))) {
|
|
DPRINT2(1, "++ WARN - %s no cxtion for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
//
|
|
// Not much purpose in continuing
|
|
//
|
|
if (!Cxtion) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Inbound cxtion
|
|
//
|
|
if ((Flags & CHECK_CXTION_INBOUND) && !Cxtion->Inbound) {
|
|
DPRINT2(1, "++ WARN - %s cxtion is not inbound for %08x\n", Debsub, Cmd);
|
|
//
|
|
// Change order accept better not be waiting for this cxtion.
|
|
//
|
|
FRS_ASSERT(Cxtion->CoProcessQueue == NULL);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Outbound cxtion
|
|
//
|
|
if ((Flags & CHECK_CXTION_OUTBOUND) && Cxtion->Inbound) {
|
|
DPRINT2(1, "++ WARN - %s cxtion is not outbound for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Jrnl cxtion
|
|
//
|
|
if ((Flags & CHECK_CXTION_JRNLCXTION) && !Cxtion->JrnlCxtion) {
|
|
DPRINT2(1, "++ WARN - %s cxtion is not jrnlcxtion for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Version vector
|
|
//
|
|
if ((Flags & CHECK_CXTION_VVECTOR) && !Cxtion->VVector) {
|
|
DPRINT2(1, "++ WARN - %s no version vector for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Authenticate Partner
|
|
//
|
|
if ((Flags & CHECK_CXTION_PARTNER)) {
|
|
|
|
//
|
|
// Increment the Authentications counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, Authentications, 1);
|
|
PM_INC_CTR_REPSET(Replica, Authentications, 1);
|
|
|
|
if (
|
|
#if DBG
|
|
//
|
|
// We don't enable authentication when emulating machines
|
|
//
|
|
!ServerGuid &&
|
|
#endif DBG
|
|
(Cxtion->PartnerAuthLevel == CXTION_AUTH_KERBEROS_FULL) &&
|
|
RunningAsAService &&
|
|
#ifdef DS_FREE
|
|
(NoDs == FALSE) &&
|
|
#endif DS_FREE
|
|
(!RcsAreAuthNamesEqual(Cxtion->PartnerSid, RsAuthSid(Cmd)) &&
|
|
!RcsAreAuthNamesEqual(Cxtion->PartnerPrincName, RsAuthClient(Cmd)))) {
|
|
|
|
DPRINT4(1, "++ WARN - %s %08x: PrincName %ws != %ws\n",
|
|
Debsub, Cmd, RsAuthClient(Cmd), Cxtion->PartnerPrincName);
|
|
|
|
DPRINT4(1, "++ WARN - %s %08x: AuthSid %ws != %ws\n",
|
|
Debsub, Cmd, RsAuthSid(Cmd), Cxtion->PartnerSid);
|
|
|
|
return ERROR_INVALID_PARAMETER;
|
|
} else {
|
|
//
|
|
// Increment the Authentications in error counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, AuthenticationsError, 1);
|
|
PM_INC_CTR_REPSET(Replica, AuthenticationsError, 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Authentication info
|
|
//
|
|
if ((Flags & CHECK_CXTION_AUTH)) {
|
|
|
|
//
|
|
// Increment the Authentications counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, Authentications, 1);
|
|
PM_INC_CTR_REPSET(Replica, Authentications, 1);
|
|
|
|
if (
|
|
#if DBG
|
|
//
|
|
// We don't enable authentication when emulating machines
|
|
//
|
|
!ServerGuid &&
|
|
#endif DBG
|
|
//
|
|
// We don't enable authentication when running in DS_FREE mode.
|
|
//
|
|
#ifdef DS_FREE
|
|
(NoDs == FALSE) &&
|
|
#endif DS_FREE
|
|
(Cxtion->PartnerAuthLevel == CXTION_AUTH_KERBEROS_FULL) &&
|
|
(RsAuthLevel(Cmd) != RPC_C_AUTHN_LEVEL_PKT_PRIVACY ||
|
|
(RsAuthN(Cmd) != RPC_C_AUTHN_GSS_KERBEROS &&
|
|
RsAuthN(Cmd) != RPC_C_AUTHN_GSS_NEGOTIATE))) {
|
|
DPRINT2(1, "++ WARN - %s bad authentication for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
} else {
|
|
//
|
|
// Increment the Authentications in error counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, AuthenticationsError, 1);
|
|
PM_INC_CTR_REPSET(Replica, AuthenticationsError, 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fix Join
|
|
//
|
|
// We may receive change orders after a successful join but
|
|
// before we receive the JOINED packet from our inbound
|
|
// partner. Fix the JOINED flag if so.
|
|
//
|
|
if ((Flags & CHECK_CXTION_FIXJOINED) &&
|
|
CxtionStateIs(Cxtion, CxtionStateWaitJoin) &&
|
|
RsJoinGuid(Cmd) &&
|
|
Replica &&
|
|
GUIDS_EQUAL(&Cxtion->JoinGuid, RsJoinGuid(Cmd)) &&
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_JOIN_GUID_VALID)) {
|
|
SET_JOINED(Replica, Cxtion, "OOJOINED");
|
|
//
|
|
// Return the pointer to the process queue to the caller so that the caller
|
|
// can unidle the queue after releasing the cxtion lock.
|
|
// Never try to lock the process queue when you have the cxtion lock. It will
|
|
// result in a deadlock.
|
|
//
|
|
*CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
SET_UNJOIN_TRIGGER(Cxtion);
|
|
}
|
|
|
|
//
|
|
// Joined
|
|
//
|
|
if ((Flags & CHECK_CXTION_JOINED) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateJoined)) {
|
|
DPRINT2(1, "++ WARN - %s cxtion is not joined for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Join guid
|
|
//
|
|
if ((Flags & CHECK_CXTION_JOINGUID) &&
|
|
(!RsJoinGuid(Cmd) ||
|
|
!GUIDS_EQUAL(&Cxtion->JoinGuid, RsJoinGuid(Cmd)) ||
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_JOIN_GUID_VALID))) {
|
|
DPRINT2(1, "++ WARN - %s wrong join guid for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// UnJoin guid
|
|
//
|
|
if ((Flags & CHECK_CXTION_UNJOINGUID) &&
|
|
(!RsJoinGuid(Cmd) ||
|
|
!GUIDS_EQUAL(&Cxtion->JoinGuid, RsJoinGuid(Cmd)) ||
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_UNJOIN_GUID_VALID))) {
|
|
DPRINT2(1, "++ WARN - %s wrong unjoin guid for %08x\n", Debsub, Cmd);
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
PCXTION
|
|
RcsCheckCxtion(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCHAR Debsub,
|
|
IN ULONG Flags
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
find the in/outbound cxtion referenced by a command received
|
|
from a remote machine.
|
|
|
|
The caller should have used RcsCheckCmd() with
|
|
CHECK_CMD_REPLICA
|
|
CHECK_CMD_CXTION
|
|
CHECK_CMD_JOINGUID (if CHECK_CXTION_JOINGUID)
|
|
before calling this function.
|
|
|
|
Complete the command with an error if a specified check fails.
|
|
|
|
Arguments:
|
|
Cmd
|
|
Debsub
|
|
Flags
|
|
|
|
Return Value:
|
|
Address of the cxtion or NULL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckCxtion:"
|
|
PCXTION Cxtion;
|
|
PREPLICA Replica = RsReplica(Cmd);
|
|
ULONG WStatus;
|
|
CHAR Tstr1[64];
|
|
PFRS_QUEUE CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Lock the connection table to sync with INLOG and OUTLOG access.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
//
|
|
// Find the cxtion and check it.
|
|
//
|
|
Cxtion = GTabLookupNoLock(Replica->Cxtions, RsCxtion(Cmd)->Guid, NULL);
|
|
|
|
WStatus = RcsCheckCxtionCommon(Cmd, Cxtion, Debsub, Flags, &CoProcessQueue);
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
//
|
|
// If RcsCheckCxtionCommon wanted us to unidle the process queue then do it here
|
|
// after releasing the cxtion lock.
|
|
//
|
|
UNIDLE_CO_PROCESS_QUEUE(Replica, Cxtion, CoProcessQueue);
|
|
|
|
//
|
|
// If check not successfull then complete the command with an error.
|
|
//
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// The join command packet is being rejected; Clear the reference.
|
|
//
|
|
_snprintf(Tstr1, sizeof(Tstr1), "W, %s CheckCxtion failed", Debsub);
|
|
Tstr1[sizeof(Tstr1)-1] = '\0';
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, WStatus, Tstr1);
|
|
|
|
if (Cxtion && Cxtion->JoinCmd == Cmd) {
|
|
Cxtion->JoinCmd = NULL;
|
|
}
|
|
//
|
|
// Do not complete the command if the check is being made for the
|
|
// fetch command server.
|
|
//
|
|
if (!BooleanFlagOn(Flags, CHECK_CXTION_FOR_FETCHCS)) {
|
|
FrsCompleteCommand(Cmd, WStatus);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Check is successful. Return the Cxtion.
|
|
//
|
|
return Cxtion;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsJoinCxtionLater(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check on the join status of the cxtion occasionally. Restart the join
|
|
process if needed. The cxtion contains the retry timeout.
|
|
|
|
Cmd contains enough info to get back to the replica\cxtion.
|
|
|
|
Arguments:
|
|
Replica
|
|
Cxtion
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsJoinCxtionLater:"
|
|
ULONG Timeout;
|
|
|
|
//
|
|
// There is already a join command packet in the works; done
|
|
//
|
|
if (Cxtion->JoinCmd) {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
Cxtion->JoinCmd = Cmd;
|
|
Cmd->Command = CMD_JOIN_CXTION;
|
|
|
|
//
|
|
// Stop retrying after a while, but not too long or too short.
|
|
//
|
|
RsTimeout(Cmd) += MinJoinRetry;
|
|
|
|
if ((LONG)RsTimeout(Cmd) < MinJoinRetry) {
|
|
RsTimeout(Cmd) = MinJoinRetry;
|
|
}
|
|
|
|
if ((LONG)RsTimeout(Cmd) > MaxJoinRetry) {
|
|
RsTimeout(Cmd) = MaxJoinRetry;
|
|
}
|
|
|
|
//
|
|
// Add in the penalty from failed rpc calls, but not too long or too short.
|
|
//
|
|
Timeout = RsTimeout(Cmd) + Cxtion->Penalty;
|
|
|
|
if ((LONG)Timeout < MinJoinRetry) {
|
|
Timeout = MinJoinRetry;
|
|
}
|
|
|
|
if ((LONG)Timeout > MaxJoinRetry) {
|
|
Timeout = MaxJoinRetry;
|
|
}
|
|
|
|
//
|
|
// Inform the user that the join is taking a long time.
|
|
// The user receives no notification if a join occurs in
|
|
// a short time.
|
|
//
|
|
// A trigger scheduled cxtion will stop retrying once the
|
|
// trigger interval goes off (usually in 15 minutes).
|
|
//
|
|
if (!(RsTimeout(Cmd) % (JOIN_RETRY_EVENT * MinJoinRetry))) {
|
|
if (Cxtion->Inbound) {
|
|
EPRINT4(EVENT_FRS_LONG_JOIN, Cxtion->Partner->Name, ComputerName,
|
|
Replica->Root, Cxtion->PartnerDnsName);
|
|
} else {
|
|
EPRINT4(EVENT_FRS_LONG_JOIN, ComputerName, Cxtion->Partner->Name,
|
|
Replica->Root, Cxtion->PartnerDnsName);
|
|
}
|
|
}
|
|
|
|
//
|
|
// This command will come back to us in a bit
|
|
//
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, Timeout);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsJoinCxtion(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Kick off the commands needed to join with our in/outbound partners.
|
|
|
|
Joining an inbound partner is a two step process. First, we pass a
|
|
command to the inbound log process to allow it to scan the inbound log
|
|
for any change orders from this connection. These change orders are
|
|
inserted on the CO process queue so we preserve ordering with new change
|
|
orders that arrive after the Join completes. In addtion we extract the
|
|
the sequence number and change order ID of the last change order we have
|
|
pending from this inbound partner. Second, we send the join request to
|
|
the inbound partner. This same path is taken whether this is a clean
|
|
startup or a startup after a crash.
|
|
|
|
JOINING:
|
|
UNJOINED -> UNJOINED ask our partner if it is alive
|
|
UNJOINED -> START our partner responded
|
|
START -> STARTING call ChgOrdStartJoinRequest()
|
|
STARTING -> SCANNING (when chgord starts inlog scan)
|
|
SCANNING -> SENDJOIN (when chgord completes inlog scan)
|
|
SENDJOIN -> WAITJOIN (when join sent to partner)
|
|
WAITJOIN -> JOINED (when partner responds)
|
|
|
|
UNJOINING
|
|
STARTING |
|
|
SCANNING |
|
|
SENDJOIN |
|
|
WAITJOIN -> UNJOINING (wait for remote change orders to retry)
|
|
UNJOINING -> UNJOINED (no more remote change orders)
|
|
UNJOINED -> DELETED (cxtion has been deleted)
|
|
|
|
Arguments:
|
|
|
|
Replica -- ptr to the Replica struct
|
|
Cxtion -- ptr to the connection struct we are talking to.
|
|
|
|
Return Value:
|
|
True if if ReJoin is needed.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsJoinCxtion:"
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
PCOMM_PACKET CPkt;
|
|
ULONG CmdCode;
|
|
DWORD CQIndex;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsJoinCxtion entry1");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
Cxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS);
|
|
if (!Cxtion) {
|
|
return;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, RcsJoinCxtion entry2");
|
|
|
|
|
|
//
|
|
// This is our periodic join command packet
|
|
// - clear the reference
|
|
// - Ignore if the cxtion has successfully joined;
|
|
// our partner will inform us if there is a state change.
|
|
// - ignore if the replica set is in seeding state and this
|
|
// connection has been paused by the initial sync command server.
|
|
//
|
|
if (Cxtion->JoinCmd == Cmd) {
|
|
Cxtion->JoinCmd = NULL;
|
|
if ((CxtionStateIs(Cxtion, CxtionStateJoined) &&
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN)) ||
|
|
(BooleanFlagOn(Replica->CnfFlags,CONFIG_FLAG_SEEDING) &&
|
|
CxtionFlagIs(Cxtion,CXTION_FLAGS_PAUSED))) {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Don't bother joining if the service is shutting down
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get lock to sync with change order accept
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
switch (GetCxtionState(Cxtion)) {
|
|
case CxtionStateInit:
|
|
//
|
|
// Not setup, yet. Ignore
|
|
//
|
|
DPRINT1(4, ":X: Cxtion isn't inited at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
}
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateUnjoined:
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
|
|
//
|
|
// Cxtion is deleted; nevermind
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Schedule is off; nevermind
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_SCHEDULE_OFF)) {
|
|
DPRINT1(4, ":X: Schedule is off at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Replica is deleted; nevermind
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
DPRINT1(4, ":S: Replica deleted at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Send our join info to our inbound partner or request an outbound
|
|
// partner to start a join. Don't send anything if this is the
|
|
// journal cxtion.
|
|
//
|
|
// Do not join with a downstream partner if this replica is still
|
|
// seeding and is not online.
|
|
//
|
|
if (Cxtion->Inbound) {
|
|
if (Cxtion->JrnlCxtion) {
|
|
DPRINT1(4, "DO NOT Send CMD_START_JOIN to jrnl cxtion: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
} else {
|
|
DPRINT1(4, ":X: Send CMD_NEED_JOIN to inbound: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
}
|
|
} else {
|
|
if ((BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) ||
|
|
Replica->IsSeeding) &&
|
|
!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE)) {
|
|
DPRINT1(4, ":X: DO NOT Send CMD_START_JOIN until we are Online: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
} else {
|
|
DPRINT1(4, ":X: Send CMD_START_JOIN to outbound: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Journal cxtions transition from unjoined to joined w/o
|
|
// any intervening states UNLESS someone adds code at
|
|
// this point to activate the journal for this set. But
|
|
// that would require a rewrite of the journal startup
|
|
// subsystem. Which may happen...
|
|
//
|
|
if (Cxtion->JrnlCxtion) {
|
|
DPRINT1(0, ":X: ***** JOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, JOINED");
|
|
SetCxtionState(Cxtion, CxtionStateJoined);
|
|
SndCsCreateCxtion(Cxtion);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
//
|
|
// Do not join with a downstream partner if this replica is still
|
|
// seeding and is not online. Do not send a join if this
|
|
// is a journal cxtion. Do not join if there is already an
|
|
// active join request outstanding.
|
|
//
|
|
// The SndCs and the ReplicaCs cooperate to limit the
|
|
// number of active join "pings" so that the Snd threads
|
|
// are not hung waiting for pings to dead servers to
|
|
// time out.
|
|
//
|
|
if (!Cxtion->ActiveJoinCommPkt &&
|
|
!Cxtion->JrnlCxtion &&
|
|
(Cxtion->Inbound ||
|
|
BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE) ||
|
|
(!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) &&
|
|
!Replica->IsSeeding))) {
|
|
|
|
//
|
|
// Increment the Join Notifications sent counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, JoinNSent, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNSent, 1);
|
|
|
|
//
|
|
// Assign a comm queue if none has been assigned. A cxtion
|
|
// must use the same comm queue for a given session (join guid)
|
|
// to maintain packet order. Old packets have an invalid join
|
|
// guid and are either not sent or ignored on the receiving side.
|
|
//
|
|
if (!Cxtion->CommQueueIndex) {
|
|
Cxtion->CommQueueIndex = SndCsAssignCommQueue();
|
|
}
|
|
//
|
|
// Partner is considered slow-to-respond if the last
|
|
// rpc calls are erroring off and their cumulative
|
|
// timeouts are greater than MinJoinRetry.
|
|
//
|
|
// BUT, Don't reassign the cxtion's queue index because,
|
|
// if this is an outbound cxtion, the flush-at-join
|
|
// logic needs to know the old queue index so that it
|
|
// flushes the correct queue.
|
|
//
|
|
CQIndex = Cxtion->CommQueueIndex;
|
|
if ((LONG)Cxtion->Penalty > MinJoinRetry) {
|
|
CQIndex = 0;
|
|
}
|
|
CmdCode = (Cxtion->Inbound) ? CMD_NEED_JOIN : CMD_START_JOIN;
|
|
CPkt = CommBuildCommPkt(Replica, Cxtion, CmdCode, NULL, NULL, NULL);
|
|
//
|
|
// The SndCs and the ReplicaCs cooperate to limit the
|
|
// number of active join "pings" so that the Snd threads
|
|
// are not hung waiting for pings to dead servers to
|
|
// time out.
|
|
//
|
|
Cxtion->ActiveJoinCommPkt = CPkt;
|
|
SndCsSubmitCommPkt(Replica, Cxtion, NULL, NULL, FALSE, CPkt, CQIndex);
|
|
}
|
|
|
|
//
|
|
// Keep trying an inbound cxtion but try the outbound connection
|
|
// only once. The outbound partner will keep trying the join and
|
|
// the connection will eventually join.
|
|
//
|
|
if (Cxtion->Inbound) {
|
|
RcsJoinCxtionLater(Replica, Cxtion, Cmd);
|
|
} else {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case CxtionStateStart:
|
|
//
|
|
// Unjoined and our partner has responed; start the join
|
|
//
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
|
|
//
|
|
// Cxtion is deleted; nevermind
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Schedule is off; nevermind
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_SCHEDULE_OFF)) {
|
|
DPRINT1(4, ":X: Schedule is off at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Replica is deleted; nevermind
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
DPRINT1(4, ":X: Replica deleted at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
if (Cxtion->Inbound) {
|
|
//
|
|
// Tell the inbound log subsystem to initialize for a Join with
|
|
// an inbound partner. When it begins processing the request it
|
|
// sets the state to STARTING. When it completes it sets the
|
|
// state to SENDJOIN and sends a CMD_JOIN_CXTION command.
|
|
//
|
|
// We need our join guid at this time because the change
|
|
// orders are stamped with the join guid during change order
|
|
// accept. Mismatched joinguids result in a unjoin-with-
|
|
// retry call so that old change orders can drain out
|
|
// of change order accept and into the replica command
|
|
// server after a cxtion is unjoined and joined again.
|
|
//
|
|
SetCxtionState(Cxtion, CxtionStateStarting);
|
|
SndCsCreateCxtion(Cxtion);
|
|
|
|
//
|
|
// ** DEADLOCK WARNING **
|
|
// The connection table lock must be unlocked before the request is
|
|
// put on the change order process queue. This is because the
|
|
// change order accept thread locks the process queue while it is
|
|
// considering the issue state of the head entry. If the CO it is
|
|
// trying to issue is for a connection being restarted it will wait
|
|
// until the cxtion starts otherwise the subsequent fetch request by
|
|
// the CO would just fail. While change_order_accept has the queue
|
|
// lock it then acquires the cxtion table lock. Thus two threads
|
|
// are acquiring two locks in different orders causing deadlock.
|
|
//
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
ChgOrdStartJoinRequest(Replica, Cxtion);
|
|
LOCK_CXTION_TABLE(Replica);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
}
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateStarting:
|
|
case CxtionStateScanning:
|
|
//
|
|
// Join in process; our inbound partner will be informed later
|
|
//
|
|
DPRINT1(4, ":X: Scanning at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateSendJoin:
|
|
case CxtionStateWaitJoin:
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
//
|
|
// Replica is deleted,
|
|
// The cxtion needs to be unjoined, or
|
|
// The cxtion needs to be deleted
|
|
// Unjoin
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Send our join info to our inbound partner
|
|
//
|
|
// This request times out if our partner doesn't answer.
|
|
//
|
|
DPRINT1(4, ":X: Send join info at send/wait join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
SetCxtionState(Cxtion, CxtionStateWaitJoin);
|
|
|
|
//
|
|
// Increment the Join Notifications sent counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, JoinNSent, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNSent, 1);
|
|
|
|
|
|
CPkt = CommBuildCommPkt(Replica, Cxtion, CMD_JOINING, Replica->VVector, NULL, NULL);
|
|
SndCsSubmitCommPkt2(Replica, Cxtion, NULL, TRUE, CPkt);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateJoined:
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
//
|
|
// Replica is deleted,
|
|
// The cxtion needs to be unjoined, or
|
|
// The cxtion needs to be deleted
|
|
// Unjoin
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Refresh our inbound partner's join state (with timeout)
|
|
//
|
|
if (Cxtion->Inbound && !Cxtion->JrnlCxtion) {
|
|
DPRINT1(4, ":X: send join info at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
|
|
//
|
|
// Increment the Join Notifications sent counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, JoinNSent, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNSent, 1);
|
|
|
|
CPkt = CommBuildCommPkt(Replica, Cxtion, CMD_JOINING, Replica->VVector, NULL, NULL);
|
|
SndCsSubmitCommPkt2(Replica, Cxtion, NULL, TRUE, CPkt);
|
|
}
|
|
//
|
|
// Already joined; nothing to do
|
|
//
|
|
DPRINT1(4, ":X: Joined at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateUnjoining:
|
|
//
|
|
// Ignore requests to join while unjoining so that we don't
|
|
// end up with multiple inbound log scans. If this join
|
|
// request originated from our partner then the caller
|
|
// will set the CXTION_FLAGS_DEFERRED_JOIN and the join
|
|
// will start at the transition from UNJOINING to UNJOINED.
|
|
//
|
|
DPRINT1(4, ":X: Unjoining at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
case CxtionStateDeleted:
|
|
//
|
|
// Deleted; nothing to do
|
|
//
|
|
DPRINT1(4, ":X: Cxtion is deleted at join: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
|
|
default:
|
|
//
|
|
// ?
|
|
//
|
|
DPRINT2(0, ":X: ERROR - bad state %d for "FORMAT_CXTION_PATH2"\n",
|
|
GetCxtionState(Cxtion),
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
RcsEmptyPreExistingDir(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Delete empty directories in the preexisting directory, inclusive.
|
|
|
|
WARN: The replica set must be filtering the preexisting directory.
|
|
|
|
Arguments:
|
|
|
|
Replica - The replica is filtering the preexisting directory.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsEmptyPreExistingDir:"
|
|
ULONG WStatus;
|
|
PWCHAR PreExistingPath = NULL;
|
|
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, RcsEmptyPreExistingDir entry");
|
|
|
|
//
|
|
// Not if the set isn't open
|
|
//
|
|
if (!Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, RcsEmptyPreExistingDir: not open");
|
|
return;
|
|
}
|
|
//
|
|
// Not if the set isn't journaling
|
|
//
|
|
if (!Replica->IsJournaling) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, RcsEmptyPreExistingDir: not journaling");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Empty the preexisting directory (continue on error)
|
|
//
|
|
PreExistingPath = FrsWcsPath(Replica->Root, NTFRS_PREEXISTING_DIRECTORY);
|
|
WStatus = FrsDeletePath(PreExistingPath,
|
|
ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE |
|
|
ENUMERATE_DIRECTORY_FLAGS_DIRECTORIES_ONLY);
|
|
DPRINT1_WS(3, "++ ERROR - FrsDeletePath(%ws) (IGNORED);", PreExistingPath, WStatus);
|
|
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, WStatus, "W, RcsEmptyPreExistingDir: done");
|
|
FrsFree(PreExistingPath);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsOpenReplicaSetMember(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Open a replica set.
|
|
|
|
Arguments:
|
|
|
|
Replica -- ptr to a REPLICA struct
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsOpenReplicaSetMember:"
|
|
ULONG WStatus;
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Replica already open");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Submit an open
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_OPEN_REPLICA_SET_MEMBER, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "Submit DB OPEN_REPLICA_SET_MEMBER");
|
|
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->FStatus, "F, OPEN_REPLICA_SET_MEMBER return");
|
|
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Open Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Open Replica DB Command failed", WStatus);
|
|
|
|
goto out;
|
|
}
|
|
|
|
Replica->IsOpen = FRS_SUCCESS(Replica->FStatus);
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsInitOneReplicaSet(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Open a replica set.
|
|
|
|
Arguments:
|
|
Replica -- ptr to a REPLICA struct
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsInitOneReplicaSet:"
|
|
ULONG FStatus;
|
|
|
|
//
|
|
// Already journaling; done
|
|
//
|
|
if (Replica->IsJournaling) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, IsJournaling True");
|
|
return;
|
|
}
|
|
|
|
if (!Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, IsOpen False");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Don't retry if in journal wrap error state error
|
|
//
|
|
if (Replica->ServiceState == REPLICA_STATE_JRNL_WRAP_ERROR) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, In Jrnl Wrap Error State");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Otherwise set it to the initializing state.
|
|
//
|
|
if (REPLICA_IN_ERROR_STATE(Replica->ServiceState)) {
|
|
JrnlSetReplicaState(Replica, REPLICA_STATE_INITIALIZING);
|
|
}
|
|
|
|
//
|
|
// Don't start journaling if the preinstall directory is unavailable
|
|
//
|
|
if (!HANDLE_IS_VALID(Replica->PreInstallHandle)) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, No PreInstallHandle");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Initialize the DB and Journal subsystems for this replica set.
|
|
//
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, DbsInitOneReplicaSet call");
|
|
|
|
FStatus = DbsInitOneReplicaSet(Replica);
|
|
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, FStatus, "F, DbsInitOneReplicaSet return");
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsJoiningAfterFlush(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
A downstream partner (X) sent this JOINING request to us. We are its
|
|
inbound partner. We should have a corresponding outbound connection
|
|
for X. If we do and the replication schedule allows we activate the
|
|
outbound log partner and ack the Joining request with CMD_JOINED.
|
|
|
|
This command packet was first placed on the SndCs's queue by
|
|
RcsJoining() so that all of the old comm packets with the
|
|
old join guid have been discarded. This protocol is needed because
|
|
this function may REJOIN and revalidate the old join guid. Packets
|
|
still on the send queue would then be sent out of order.
|
|
|
|
NOTE:
|
|
|
|
Activating the OUTLOG partner before sending the JOINED cmd to the partner
|
|
can cause COs to be sent to the partner before it sees the JOINED cmd.
|
|
Since the JoinGuid matches in the sent change orders the partner accepts
|
|
them and queues them to the change order process queue. If this CO gets to
|
|
the head of the process queue the issue logic in ChgOrdAccept will
|
|
block the queue waiting for the connection to go to the JOINED state. When
|
|
the JOINED cmd arrives it is processed by RcsInboundJoined() which sets the
|
|
connection state to JOINED and unblocks the change order process queue.
|
|
|
|
We can't send the JOINED cmd first because the partner may then send back
|
|
a fetch request before we think it has joined. The JOINED cmd must always
|
|
be sent so the partner knows it can start sending fetch cmds because we
|
|
may have no outbound COs to send. In addtion the JOINED command contains
|
|
the new value for LastJoinedTime that is saved in the connection record
|
|
for the next join request.
|
|
|
|
NOTE:
|
|
|
|
Cxtion activation should always come from the downstream partner. Only they
|
|
know if a VVJoin is required because of a database re-init or restore.
|
|
Otherwise we will just continue sending from the last unacked CO in the log.
|
|
At best we can notify outbound partners we are now available to provide
|
|
change orders (via CMD_JOIN_RESEND)
|
|
|
|
Arguments:
|
|
|
|
Cmd
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsJoiningAfterFlush:"
|
|
ULONGLONG Delta;
|
|
ULONG FStatus;
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
#define CXTION_STR_MAX 256
|
|
WCHAR CxtionStr[CXTION_STR_MAX];
|
|
WCHAR WSkew[15], WDelta[15];
|
|
CHAR UpstreamTimeStr[TIME_STRING_LENGTH];
|
|
CHAR DownstreamTimeStr[TIME_STRING_LENGTH];
|
|
|
|
PVOID Key;
|
|
PGEN_ENTRY Entry;
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK |
|
|
CHECK_CMD_REPLICA_VERSION_GUID |
|
|
CHECK_CMD_JOINTIME |
|
|
CHECK_CMD_NOT_EXPIRED)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsJoiningAfterFlush entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_OUTBOUND |
|
|
CHECK_CXTION_PARTNER |
|
|
CHECK_CXTION_AUTH);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Shutting down; ignore join request
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, FrsIsShuttingDown");
|
|
FrsCompleteCommand(Cmd, ERROR_OPERATION_ABORTED);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Do not join with a downstream partner if this replica is still
|
|
// seeding and is not online.
|
|
//
|
|
if ((BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) ||
|
|
Replica->IsSeeding) &&
|
|
!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE)) {
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, Seeding and Offline");
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Already joined; should we rejoin?
|
|
//
|
|
if (CxtionStateIs(OutCxtion, CxtionStateJoined)) {
|
|
//
|
|
// Don't rejoin if this is a retry by our outbound partner
|
|
//
|
|
if (GUIDS_EQUAL(&OutCxtion->JoinGuid, RsJoinGuid(Cmd)) &&
|
|
CxtionFlagIs(OutCxtion, CXTION_FLAGS_JOIN_GUID_VALID)) {
|
|
//
|
|
// Tell our outbound partner that we have rejoined successfully
|
|
// Increment the Joins counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(OutCxtion, Joins, 1);
|
|
PM_INC_CTR_REPSET(Replica, Joins, 1);
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, REJOINED");
|
|
DPRINT1(0, ":X: ***** REJOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_JOINED, NULL, NULL, NULL);
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Unjoin and rejoin. Partner may have restarted.
|
|
// WARN: forcing an unjoin of an outbound cxtion works
|
|
// because outbound cxtions transition from Joined to
|
|
// Unjoined with no intervening states.
|
|
//
|
|
FStatus = RcsForceUnjoin(Replica, OutCxtion);
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, FStatus, "F, RcsForceUnjoin return");
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT_FS(0, ":X: ERROR - return from RcsForceUnjoin:", FStatus);
|
|
FrsCompleteCommand(Cmd, ERROR_REQUEST_ABORTED);
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// Machines can't join if their times are badly out of sync
|
|
// UNLESS this is a VOLATILE cxtion (I.e., sysvol seeding).
|
|
//
|
|
// Compare the time when the packet was built by our partner and the time
|
|
// when we received the packet.
|
|
// Time in 100nsec tics since Jan 1, 1601
|
|
//
|
|
if (*RsCommPktRcvTime(Cmd) > *RsJoinTime(Cmd)) {
|
|
Delta = *RsCommPktRcvTime(Cmd) - *RsJoinTime(Cmd);
|
|
} else {
|
|
Delta = *RsJoinTime(Cmd) - *RsCommPktRcvTime(Cmd);
|
|
}
|
|
|
|
//
|
|
// Ignore out-of-sync times if this is a volatile cxtion. Volatile
|
|
// cxtions are only used for sysvol seeding where times don't matter.
|
|
//
|
|
if (!CxtionFlagIs(OutCxtion, CXTION_FLAGS_VOLATILE) &&
|
|
(Delta > MaxPartnerClockSkew)) {
|
|
Delta = Delta / CONVERT_FILETIME_TO_MINUTES;
|
|
DPRINT1(0, ":X: ERROR - Joining CommPkt receive time is %08x %08x\n", PRINTQUAD(*RsCommPktRcvTime(Cmd)));
|
|
DPRINT1(0, ":X: ERROR - Joining CommPkt send time on partner is %08x %08x\n", PRINTQUAD(*RsJoinTime(Cmd)));
|
|
|
|
_snwprintf(CxtionStr, CXTION_STR_MAX, FORMAT_CXTION_PATH2W,
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
CxtionStr[CXTION_STR_MAX-1] = UNICODE_NULL;
|
|
|
|
_itow(PartnerClockSkew, WSkew, 10);
|
|
_itow((LONG)Delta, WDelta, 10);
|
|
|
|
EPRINT3(EVENT_FRS_JOIN_FAIL_TIME_SKEW, WSkew, CxtionStr, WDelta);
|
|
|
|
DPRINT2(0, ":X: ERROR - Cannot join (%ws) clocks are out of sync by %d minutes\n",
|
|
CxtionStr, (LONG)Delta);
|
|
|
|
DPRINT(0, ":X: Note: If this time difference is close to a multiple of 60 minutes then it\n");
|
|
DPRINT(0, ":X: is likely that either this computer or its partner computer was set to the\n");
|
|
DPRINT(0, ":X: incorrect time zone when the computer time was initially set. Check that both \n");
|
|
DPRINT(0, ":X: the time zones and the time is set correctly on both computers.\n");
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_FUNCTION);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Grab the new version vector. Initialize an empty version vector
|
|
// if our outbound partner did not send a version vector. Our partner
|
|
// simply does not yet have any entries in his version vector.
|
|
//
|
|
OutCxtion->VVector = (RsVVector(Cmd) != NULL) ?
|
|
RsVVector(Cmd) : GTabAllocTable();
|
|
RsVVector(Cmd) = NULL;
|
|
|
|
//
|
|
// Grab the compression table from the outbound partner.
|
|
// This table is a list of guids 1 for each compression format that
|
|
// the partner supports.
|
|
//
|
|
|
|
OutCxtion->CompressionTable = (RsCompressionTable(Cmd) != NULL) ?
|
|
RsCompressionTable(Cmd) : GTabAllocTable();
|
|
|
|
DPRINT1(4, "Received following compression table from %ws.\n", OutCxtion->PartnerDnsName);
|
|
|
|
GTabLockTable(OutCxtion->CompressionTable);
|
|
Key = NULL;
|
|
while (Entry = GTabNextEntryNoLock(OutCxtion->CompressionTable, &Key)) {
|
|
|
|
FrsPrintGuid(Entry->Key1);
|
|
}
|
|
GTabUnLockTable(OutCxtion->CompressionTable);
|
|
|
|
RsCompressionTable(Cmd) = NULL;
|
|
|
|
|
|
//
|
|
// Assign the join guid and comm queue to this cxtion.
|
|
//
|
|
DPRINT1(4, ":X: %ws: Assigning join guid.\n", OutCxtion->Name->Name);
|
|
COPY_GUID(&OutCxtion->JoinGuid, RsJoinGuid(Cmd));
|
|
|
|
SetCxtionFlag(OutCxtion, CXTION_FLAGS_JOIN_GUID_VALID |
|
|
CXTION_FLAGS_UNJOIN_GUID_VALID);
|
|
//
|
|
// Assign a comm queue. A cxtion must use the same comm queue
|
|
// for a given session (join guid) to maintain packet order.
|
|
// Old packets have an invalid join guid and are either not
|
|
// sent or ignored on the receiving side.
|
|
//
|
|
OutCxtion->CommQueueIndex = SndCsAssignCommQueue();
|
|
|
|
VV_PRINT_OUTBOUND(4, OutCxtion->Partner->Name, OutCxtion->VVector);
|
|
|
|
//
|
|
// Use the last join time to check if the connection is a new connection.
|
|
// If the connection on upstream or the connection on downstream is new
|
|
// then force a vvjoin. Upstream may have deleted the connection because
|
|
// the downstream did not join for a week (Outlog change history time).
|
|
// When the database on downstream or upstream is restored both will have
|
|
// valid times but we want a vvjoin at that time. We don't support restoring
|
|
// old FRS database so we will not worry about that scenario here.
|
|
// We used to just compare LastJoinTimes and force vvjoin if they didn't
|
|
// match but that can result in unnecessary vvjoins. (Customer reported problem)
|
|
//
|
|
// For E.g. If the upstream crashed when it was in this function at the point
|
|
// where it had initialized a new LastJoinTime but before it sent the CMD_JOINED
|
|
// packet to the downstream then when the downstream joins later LastJoinTime
|
|
// will not match and a vvjoin will be performed unnecessarily.
|
|
//
|
|
|
|
FileTimeToString((PFILETIME) &OutCxtion->LastJoinTime, UpstreamTimeStr);
|
|
FileTimeToString((PFILETIME) &RsLastJoinTime(Cmd), DownstreamTimeStr);
|
|
CXTION_STATE_TRACE(4, OutCxtion, Replica, 0, "LastJoinTime Check");
|
|
DPRINT2(4, ":X: Upstream (This Computer) LastJoinTime = %s ; Downstream LastJoinTime = %s\n", UpstreamTimeStr,DownstreamTimeStr);
|
|
if ((RsLastJoinTime(Cmd) == (ULONGLONG) 1) ||
|
|
(OutCxtion->LastJoinTime == (ULONGLONG) 1)) {
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, LastJoinTime Mismatch, force VVJoin");
|
|
SetCxtionFlag(OutCxtion, CXTION_FLAGS_PERFORM_VVJOIN);
|
|
}
|
|
|
|
//
|
|
// Our partner's replica version guid (aka originator guid) used
|
|
// for dampening requests to our partner.
|
|
//
|
|
COPY_GUID(&OutCxtion->ReplicaVersionGuid, RsReplicaVersionGuid(Cmd));
|
|
|
|
//
|
|
// REPLICA JOINED
|
|
//
|
|
SetCxtionState(OutCxtion, CxtionStateJoined);
|
|
|
|
//
|
|
// Inform the user that a long join has finally completed.
|
|
// The user receives no notification if a join occurs in a short time.
|
|
//
|
|
if (OutCxtion->JoinCmd &&
|
|
(LONG)RsTimeout(OutCxtion->JoinCmd) > (JOIN_RETRY_EVENT * MinJoinRetry)) {
|
|
if (OutCxtion->Inbound) {
|
|
EPRINT3(EVENT_FRS_LONG_JOIN_DONE,
|
|
OutCxtion->Partner->Name, ComputerName, Replica->Root);
|
|
} else {
|
|
EPRINT3(EVENT_FRS_LONG_JOIN_DONE,
|
|
ComputerName, OutCxtion->Partner->Name, Replica->Root);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Tell the Outbound Log that this partner has joined. This call is synchronous.
|
|
//
|
|
#if DBG
|
|
//
|
|
// Always VvJoin
|
|
if (DebugInfo.ForceVvJoin) {
|
|
SetCxtionFlag(OutCxtion, CXTION_FLAGS_PERFORM_VVJOIN);
|
|
}
|
|
#endif DBG
|
|
FStatus = OutLogSubmit(Replica, OutCxtion, CMD_OUTLOG_ACTIVATE_PARTNER);
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, OUTLOG_ACTIVATE_PARTNER return");
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// Could not enable outbound log processing for this cxtion
|
|
//
|
|
DPRINT_FS(0, "++ ERROR - return from CMD_OUTLOG_ACTIVATE_PARTNER:", FStatus);
|
|
RcsForceUnjoin(Replica, OutCxtion);
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_FUNCTION);
|
|
return;
|
|
}
|
|
//
|
|
// OUTBOUND LOG PROCESSING HAS BEEN ENABLED
|
|
//
|
|
|
|
//
|
|
// Tell our outbound partner that we are ready to go.
|
|
//
|
|
// WARN: comm packets may have already been sent to our partner once we
|
|
// activated outlog processing above. In that case, our partner treated
|
|
// the packet as an implicit CMD_JOINED and completed the join. This
|
|
// packet is then ignored as an extraneous join request except that it
|
|
// causes our partner to update the last joined time to match ours.
|
|
//
|
|
// Increment the Joins counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(OutCxtion, Joins, 1);
|
|
PM_INC_CTR_REPSET(Replica, Joins, 1);
|
|
|
|
DPRINT1(0, ":X: ***** JOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, JOINED");
|
|
//
|
|
// Update the DB cxtion table record with the new Join Time.
|
|
//
|
|
// *NOTE* The following call is synchronous. If we have the Cxtion
|
|
// table lock then a hang is possible.
|
|
//
|
|
GetSystemTimeAsFileTime((PFILETIME)&OutCxtion->LastJoinTime);
|
|
InterlockedIncrement(&Replica->OutLogCxtionsJoined);
|
|
DPRINT1(4, "TEMP: OutLogCxtionsJoined %d\n", Replica->OutLogCxtionsJoined);
|
|
|
|
FStatus = OutLogSubmit(Replica, OutCxtion, CMD_OUTLOG_UPDATE_PARTNER);
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, FStatus, "F, OUTLOG_UPDATE_PARTNER return");
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT3(0, "++ WARN changes to cxtion %ws (to %ws, %ws) not updated in database\n",
|
|
OutCxtion->Name->Name, OutCxtion->Partner->Name, Replica->ReplicaName->Name);
|
|
}
|
|
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, Cxtion JOINED, xmit resp");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_JOINED, NULL, NULL, NULL);
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
|
|
//
|
|
// Inject a end-of-opt-vvjoin control co if this cxtion is in the
|
|
// OptVVJoinMode. This co is used to send a fake vvjoindone to downstream
|
|
// so it can transition out of initsync state.
|
|
//
|
|
OutLogAcquireLock(Replica);
|
|
|
|
if ((OutCxtion->OLCtx != NULL) && InOptVVJoinMode(OutCxtion->OLCtx)) {
|
|
ChgOrdInjectControlCo(Replica, OutCxtion, FCN_CO_END_OF_OPTIMIZED_VVJOIN);
|
|
}
|
|
|
|
//
|
|
// Inject a end-of-join control co if this cxtion is using a trigger
|
|
// schedule. Note that the EndOfJoin Co may have been processed by
|
|
// the time this call returns.
|
|
//
|
|
if (CxtionFlagIs(OutCxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|
if (OutCxtion->OLCtx &&
|
|
!InVVJoinMode(OutCxtion->OLCtx) &&
|
|
CxtionStateIs(OutCxtion, CxtionStateJoined)) {
|
|
ChgOrdInjectControlCo(Replica, OutCxtion, FCN_CO_END_OF_JOIN);
|
|
}
|
|
}
|
|
|
|
OutLogReleaseLock(Replica);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsJoining(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
A downstream partner (X) sent this JOINING request to us. We are its
|
|
inbound partner. We should have a corresponding outbound connection
|
|
for X. If we do and the replication schedule allows we activate the
|
|
outbound log partner and ack the Joining request with CMD_JOINED.
|
|
|
|
This command packet is first put on the SndCs's(Send Command Server)
|
|
queue so that all of the old comm packets with the old join guid
|
|
will have been discarded. This protocol is needed because this
|
|
function may REJOIN and revalidate the old join guid. Packets
|
|
still on the send queue would then be sent out of order.
|
|
|
|
The command packet is sent to RcsJoiningAfterFlush() after
|
|
bubbling up to the top of the send queue.
|
|
|
|
Arguments:
|
|
|
|
Cmd
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsJoining:"
|
|
ULONG FStatus;
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK |
|
|
CHECK_CMD_REPLICA_VERSION_GUID |
|
|
CHECK_CMD_JOINTIME |
|
|
CHECK_CMD_NOT_EXPIRED)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsJoining entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_OUTBOUND |
|
|
CHECK_CXTION_PARTNER |
|
|
CHECK_CXTION_AUTH);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Increment the Join Notifications Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(OutCxtion, JoinNRcvd, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNRcvd, 1);
|
|
|
|
//
|
|
// Shutting down; ignore join request
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
FrsCompleteCommand(Cmd, ERROR_OPERATION_ABORTED);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Do not join with a downstream partner if this replica is still
|
|
// seeding and is not online.
|
|
//
|
|
if ((BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) ||
|
|
Replica->IsSeeding) &&
|
|
!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE)) {
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
//
|
|
// Put this command packet at the end of this cxtion's send queue. Once it
|
|
// bubbles up to the top the command packet will be sent back to this
|
|
// command server with the ccommand CMD_JOINING_AFTER_FLUSH and will be
|
|
// given to RcsJoiningAfterFlush() for processing.
|
|
//
|
|
Cmd->Command = CMD_JOINING_AFTER_FLUSH;
|
|
SndCsSubmitCmd(Replica,
|
|
OutCxtion,
|
|
&ReplicaCmdServer,
|
|
Cmd,
|
|
OutCxtion->CommQueueIndex);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsNeedJoin(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Our outbound partner has sent this packet to see if we are alive.
|
|
Respond with a start-your-join packet.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsNeedJoin:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
DWORD CQIndex;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA |
|
|
CHECK_CMD_CXTION |
|
|
CHECK_CMD_NOT_EXPIRED)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsNeedJoin entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_OUTBOUND |
|
|
CHECK_CXTION_PARTNER |
|
|
CHECK_CXTION_AUTH);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Increment the Join Notifications Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(OutCxtion, JoinNRcvd, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNRcvd, 1);
|
|
|
|
//
|
|
// Do not join with a downstream partner if this replica is still
|
|
// seeding and is not online.
|
|
//
|
|
|
|
if ((!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) &&
|
|
!Replica->IsSeeding) ||
|
|
BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE)) {
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, Xmit START_JOIN req");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_START_JOIN, NULL, NULL, NULL);
|
|
CQIndex = OutCxtion->CommQueueIndex;
|
|
SndCsSubmitCommPkt(Replica, OutCxtion, NULL, NULL, FALSE, CPkt, CQIndex);
|
|
}
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsStartJoin(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Our inbound partner has sent this packet to tell us it is alive
|
|
and that the join can be started.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsStartJoin:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA |
|
|
CHECK_CMD_CXTION |
|
|
CHECK_CMD_NOT_EXPIRED)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsStartJoin entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_PARTNER |
|
|
CHECK_CXTION_AUTH);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Increment the Join Notifications Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, JoinNRcvd, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNRcvd, 1);
|
|
|
|
//
|
|
// If this replica set is in seeding state and this connection is in
|
|
// initial sync state then let the initial sync command server decide
|
|
// how to respond to this START_JOIN. It will respond to one START_JOIN
|
|
// at a time.
|
|
//
|
|
if (BooleanFlagOn(Replica->CnfFlags,CONFIG_FLAG_SEEDING) &&
|
|
CxtionFlagIs(InCxtion,CXTION_FLAGS_INIT_SYNC)) {
|
|
|
|
InitSyncCsSubmitTransfer(Cmd, CMD_INITSYNC_START_JOIN);
|
|
return;
|
|
}
|
|
|
|
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
|
|
//
|
|
// If we were waiting for our partner to respond or think we
|
|
// have already joined then either start the join process or
|
|
// resend our join info.
|
|
//
|
|
// Otherwise, let the normal join flow take over. If there are
|
|
// problems then the join will timeout and retry.
|
|
//
|
|
if (CxtionStateIs(InCxtion, CxtionStateUnjoined) ||
|
|
CxtionStateIs(InCxtion, CxtionStateJoined)) {
|
|
if (CxtionStateIs(InCxtion, CxtionStateUnjoined)) {
|
|
SetCxtionState(InCxtion, CxtionStateStart);
|
|
}
|
|
//
|
|
// Start the join process or resend the join info
|
|
//
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, RcsJoinCxtion call");
|
|
RcsJoinCxtion(Cmd);
|
|
} else {
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, Cannot start join");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitReplicaCxtionJoin(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN BOOL Later
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a join command to a replica\cxtion.
|
|
|
|
Build cmd pkt with Cxtion GName and replica ptr. Submit to Replica cmd
|
|
server. Calls dispatch function and translates cxtion GName to cxtion ptr
|
|
|
|
SYNCHRONIZATION -- Only called from the context of the
|
|
Replica Command Server. No locks needed.
|
|
|
|
Arguments:
|
|
Replica - existing replica
|
|
Cxtion - existing cxtion
|
|
Later - Delayed submission
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitReplicaCxtionJoin:"
|
|
PCOMMAND_PACKET Cmd;
|
|
|
|
|
|
|
|
//
|
|
// The cxtion already has a join command outstanding; don't flood the
|
|
// queue with extraneous requests.
|
|
//
|
|
if (Cxtion->JoinCmd) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Not scheduled to run
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_SCHEDULE_OFF)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Already joined; done
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateJoined) &&
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Trigger schedules are managed by RcsCheckSchedules()
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Allocate a command packet
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, CMD_JOIN_CXTION);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of replica set and new replica set
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
RsCxtion(Cmd) = FrsDupGName(Cxtion->Name);
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, RcsSubmitReplicaCxtionJoin entry");
|
|
|
|
//
|
|
// Cxtion should have just one join command outstanding
|
|
//
|
|
if (Later) {
|
|
DPRINT5(4, "++ Submit LATER %08x for Cmd %08x %ws\\%ws\\%ws\n",
|
|
Cmd->Command, Cmd, Replica->SetName->Name,
|
|
Replica->MemberName->Name, Cxtion->Name->Name);
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, RcsJoinCxtionLater call");
|
|
RcsJoinCxtionLater(Replica, Cxtion, Cmd);
|
|
} else {
|
|
Cxtion->JoinCmd = Cmd;
|
|
DPRINT5(4, "++ Submit %08x for Cmd %08x %ws\\%ws\\%ws\n",
|
|
Cmd->Command, Cmd, Replica->SetName->Name,
|
|
Replica->MemberName->Name, Cxtion->Name->Name);
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Submit JOIN_CXTION req");
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsResubmitActiveCOsOnCxtion(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Remove the active change orders from this connection and resubmit them.
|
|
This is done when downstream notices that the upstream has rejoined.
|
|
It may have dropped a fetch request that we send causing us to hang for ever.
|
|
|
|
Assumes: Caller has acquired the CXTION_TABLE lock.
|
|
|
|
Arguments:
|
|
Replica - ptr to replica struct for this replica set.
|
|
Cxtion - ptr to connection struct being unjoined.
|
|
|
|
Return Value:
|
|
FrsErrorStatus
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsResubmitActiveCOsOnCxtion:"
|
|
|
|
PVOID Key;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
|
|
//
|
|
// Take idle change orders through retry
|
|
//
|
|
LOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
Key = NULL;
|
|
while (Coe = GTabNextDatumNoLock(Cxtion->CoeTable, &Key)) {
|
|
|
|
if (CO_FLAG_ON(Coe, CO_FLAG_LOCALCO)) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Do not ReSubmit Local COs");
|
|
} else {
|
|
|
|
FRS_ASSERT(CO_STATE_IS_LE(Coe, IBCO_FETCH_RETRY));
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "ReSubmit CO to fetch");
|
|
Key = NULL;
|
|
GTabDeleteNoLock(Cxtion->CoeTable, &Coe->Cmd.ChangeOrderGuid, NULL, NULL);
|
|
RcsSubmitRemoteCoAccepted(Coe);
|
|
}
|
|
}
|
|
|
|
UNLOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsDrainActiveCOsOnCxtion(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Remove the active change orders from this connection and send them
|
|
thru retry.
|
|
|
|
Assumes: Caller has acquired the CXTION_TABLE lock.
|
|
|
|
Arguments:
|
|
Replica - ptr to replica struct for this replica set.
|
|
Cxtion - ptr to connection struct being unjoined.
|
|
|
|
Return Value:
|
|
FrsErrorStatus
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDrainActiveCOsOnCxtion:"
|
|
|
|
PVOID Key;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
ULONG FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// Take idle change orders through retry
|
|
//
|
|
LOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
Key = NULL;
|
|
while (Coe = GTabNextDatumNoLock(Cxtion->CoeTable, &Key)) {
|
|
Key = NULL;
|
|
GTabDeleteNoLock(Cxtion->CoeTable, &Coe->Cmd.ChangeOrderGuid, NULL, NULL);
|
|
|
|
SET_COE_FLAG(Coe, COE_FLAG_NO_INBOUND);
|
|
|
|
if (Cxtion->JrnlCxtion) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to stage gen retry");
|
|
ChgOrdInboundRetry(Coe, IBCO_STAGING_RETRY);
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to fetch retry");
|
|
ChgOrdInboundRetry(Coe, IBCO_FETCH_RETRY);
|
|
}
|
|
}
|
|
|
|
UNLOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
//
|
|
// If there are no outstanding change orders that need to be taken through the
|
|
// retry path, unjoin complete. Otherwise, the unjoin will complete once
|
|
// the count reaches 0. Until then, further attempts to join will be
|
|
// ignored.
|
|
//
|
|
if (Cxtion->ChangeOrderCount == 0) {
|
|
SndCsDestroyCxtion(Cxtion, CXTION_FLAGS_UNJOIN_GUID_VALID);
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
|
|
//
|
|
// Increment the Unjoins counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, Unjoins, 1);
|
|
PM_INC_CTR_REPSET(Replica, Unjoins, 1);
|
|
|
|
DPRINT1(0, ":X: ***** UNJOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
//
|
|
// Deleted cxtion
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
//
|
|
// Rejoin if requested.
|
|
//
|
|
} else if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_JOIN)) {
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
RcsSubmitReplicaCxtionJoin(Replica, Cxtion, TRUE);
|
|
}
|
|
} else {
|
|
FStatus = FrsErrorUnjoining;
|
|
DPRINT2(0, ":X: ***** UNJOINING "FORMAT_CXTION_PATH2" (%d cos)\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion), Cxtion->ChangeOrderCount);
|
|
}
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsForceUnjoin(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Unjoin this connection.
|
|
|
|
Arguments:
|
|
Replica - ptr to replica struct for this replica set.
|
|
Cxtion - ptr to connection struct being unjoined.
|
|
|
|
Return Value:
|
|
FrsErrorStatus
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsForceUnjoin:"
|
|
ULONG FStatus = FrsErrorSuccess;
|
|
CHAR GuidStr[GUID_CHAR_LEN + 1];
|
|
PFRS_QUEUE CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Get the table lock to sync with change order accept and the
|
|
// inbound log scanner. The outbound log process uses different
|
|
// state to control its own processing.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, Cxtion->Flags, "Flags, RcsForceUnjoin entry");
|
|
|
|
switch (GetCxtionState(Cxtion)) {
|
|
|
|
case CxtionStateInit:
|
|
//
|
|
// ?
|
|
//
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
//
|
|
// Deleted cxtion
|
|
//
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
}
|
|
break;
|
|
|
|
case CxtionStateUnjoined:
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Unjoined; nothing to do
|
|
//
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
|
|
//
|
|
// Deleted cxtion
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
}
|
|
break;
|
|
|
|
case CxtionStateStart:
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Haven't had a chance to start; nothing to do
|
|
//
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
|
|
//
|
|
// Deleted cxtion
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
}
|
|
break;
|
|
|
|
case CxtionStateUnjoining:
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
//
|
|
// Take idle change orders through retry
|
|
//
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, Cxtion->ChangeOrderCount,
|
|
"COs, A-Send Idle COs to retry");
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
|
|
RcsDrainActiveCOsOnCxtion(Replica, Cxtion);
|
|
|
|
break;
|
|
|
|
case CxtionStateStarting:
|
|
case CxtionStateScanning:
|
|
//
|
|
// Wait for the inbound scan to finish. The change order retry
|
|
// thread or the change order accept thread will eventually unjoin us.
|
|
//
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
FStatus = FrsErrorUnjoining;
|
|
break;
|
|
|
|
case CxtionStateSendJoin:
|
|
case CxtionStateWaitJoin:
|
|
case CxtionStateJoined:
|
|
//
|
|
// Once we destroy our join guid, future remote change
|
|
// orders will be discarded by the replica command server
|
|
// before they show up on the change order process queue.
|
|
//
|
|
// This works because the replica command server is
|
|
// single threaded per replica; any packets received during
|
|
// this function have been enqueued for later processing.
|
|
//
|
|
// The outlog process may attempt to send change orders
|
|
// if this is an outbound cxtion but they will bounce
|
|
// because the join guid is incorrect.
|
|
//
|
|
|
|
//
|
|
// Invalidate our join guid
|
|
//
|
|
SndCsDestroyCxtion(Cxtion, 0);
|
|
SetCxtionState(Cxtion, CxtionStateUnjoining);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
|
|
if (Cxtion->Inbound) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, Cxtion->ChangeOrderCount,
|
|
"COs, B-Send Idle COs to retry");
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
|
|
RcsDrainActiveCOsOnCxtion(Replica, Cxtion);
|
|
|
|
} else {
|
|
//
|
|
// Tell the vvjoin command server to stop
|
|
// Tell outlog process to Unjoin from this partner.
|
|
// The call is synchronous so drop the lock around the call.
|
|
//
|
|
if (Cxtion->OLCtx != NULL) {
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
SubmitVvJoinSync(Replica, Cxtion, CMD_VVJOIN_DONE_UNJOIN);
|
|
FStatus = OutLogSubmit(Replica, Cxtion, CMD_OUTLOG_DEACTIVATE_PARTNER);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT1_FS(0, ":X: ERROR - %ws: Can't deactivate at unjoin;",
|
|
Cxtion->Name->Name, FStatus);
|
|
}
|
|
LOCK_CXTION_TABLE(Replica);
|
|
}
|
|
//
|
|
// Free the outbound version vector, if present
|
|
//
|
|
Cxtion->VVector = VVFreeOutbound(Cxtion->VVector);
|
|
|
|
//
|
|
// Invalidate the un/join guid
|
|
//
|
|
SndCsDestroyCxtion(Cxtion, CXTION_FLAGS_UNJOIN_GUID_VALID);
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
|
|
//
|
|
// Increment the Unjoins counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(Cxtion, Unjoins, 1);
|
|
PM_INC_CTR_REPSET(Replica, Unjoins, 1);
|
|
|
|
DPRINT1(0, ":X: ***** UNJOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
//
|
|
// :SP1: Volatile connection cleanup.
|
|
//
|
|
// Deleted cxtion or outbound volatile connection. Volatile
|
|
// connection gets deleted after the first time it UNJOINS.
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE) ||
|
|
VOLATILE_OUTBOUND_CXTION(Cxtion)) {
|
|
SetCxtionState(Cxtion, CxtionStateDeleted);
|
|
//
|
|
// Rejoin
|
|
//
|
|
} else if (CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_JOIN)) {
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
RcsSubmitReplicaCxtionJoin(Replica, Cxtion, TRUE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CxtionStateDeleted:
|
|
//
|
|
// Unidle the change order process queue if it is blocked.
|
|
//
|
|
CoProcessQueue = Cxtion->CoProcessQueue;
|
|
Cxtion->CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Deleted; nothing to do
|
|
//
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// ?
|
|
//
|
|
DPRINT2(0, ":X: ERROR - bad state %d for "FORMAT_CXTION_PATH2"\n",
|
|
GetCxtionState(Cxtion), PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
break;
|
|
}
|
|
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
//
|
|
// The process queue should be unidled here after releasing the cxtion
|
|
// lock to prevent deadlock. Never lock the process queue when you have the
|
|
// cxtion lock.
|
|
//
|
|
UNIDLE_CO_PROCESS_QUEUE(Replica, Cxtion, CoProcessQueue);
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsSetSysvolReady(
|
|
IN DWORD NewSysvolReady
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Set the registry value for ...\netlogon\parameters\SysvolReady to
|
|
the specified value. Do nothing if the SysvolReady value is
|
|
set accordingly.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
TRUE - SysvolReady is set to TRUE
|
|
FALSE - State of SysvolReady is unknown
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSetSysvolReady:"
|
|
DWORD WStatus;
|
|
|
|
DPRINT1(4, ":S: Setting SysvolReady to %d\n", NewSysvolReady);
|
|
|
|
//
|
|
// Restrict values to 0 or 1
|
|
//
|
|
if (NewSysvolReady) {
|
|
NewSysvolReady = 1;
|
|
}
|
|
|
|
if (CurrentSysvolReadyIsValid &&
|
|
CurrentSysvolReady == NewSysvolReady) {
|
|
DPRINT1(4, ":S: SysvolReady is already set to %d\n", NewSysvolReady);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Access the netlogon\parameters key to tell NetLogon to share the sysvol
|
|
//
|
|
WStatus = CfgRegWriteDWord(FKC_SYSVOL_READY, NULL, 0, NewSysvolReady);
|
|
CLEANUP3_WS(0, "++ ERROR - writing %ws\\%ws to %d;",
|
|
NETLOGON_SECTION, SYSVOL_READY, NewSysvolReady, WStatus, RETURN_ERROR);
|
|
|
|
CurrentSysvolReady = NewSysvolReady;
|
|
CurrentSysvolReadyIsValid = TRUE;
|
|
DPRINT1(3, ":S: SysvolReady is set to %d\n", NewSysvolReady);
|
|
|
|
//
|
|
// Report event if transitioning from 0 to 1
|
|
//
|
|
if (NewSysvolReady) {
|
|
EPRINT1(EVENT_FRS_SYSVOL_READY, ComputerName);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
RETURN_ERROR:
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsVvJoinDoneUnJoin(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
From: Myself
|
|
The VvJoin hack has declared victory and told our inbound partner
|
|
to unjoin from us. That was VVJOIN_HACK_TIMEOUT seconds ago. By
|
|
now, our inbound partner should have unjoined from us and we can
|
|
tell dcpromo that the sysvol has been promoted.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsVvJoinDoneUnJoin:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK )) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, ReplicaVvJoinDoneUnjoin entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
Replica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_DONE;
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCheckPromotion(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
From: Delayed command server
|
|
Check on the process of the sysvol promotion. If there has been
|
|
no activity since the last time we checked, set the replica's
|
|
promotion state to "done with error".
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckPromotion:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
ULONG Timeout;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsCheckPromotion entry");
|
|
|
|
//
|
|
// Promotion is done; ignore
|
|
//
|
|
if (Replica->NtFrsApi_ServiceState != NTFRSAPI_SERVICE_PROMOTING) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica not promoting");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// No activity in awhile, declare failure
|
|
//
|
|
if (Replica->NtFrsApi_HackCount == RsTimeout(Cmd)) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, FRS_ERR_SYSVOL_POPULATE_TIMEOUT,
|
|
"W, Replica promotion timed out");
|
|
Replica->NtFrsApi_ServiceWStatus = FRS_ERR_SYSVOL_POPULATE_TIMEOUT;
|
|
Replica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_DONE;
|
|
FrsCompleteCommand(Cmd, ERROR_SERVICE_SPECIFIC_ERROR);
|
|
return;
|
|
}
|
|
//
|
|
// There has been activity. Wait awhile and check again
|
|
//
|
|
CfgRegReadDWord(FKC_PROMOTION_TIMEOUT, NULL, 0, &Timeout);
|
|
|
|
RsTimeout(Cmd) = Replica->NtFrsApi_HackCount;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, Timeout);
|
|
}
|
|
|
|
|
|
#ifndef NOVVJOINHACK
|
|
#define VVJOIN_HACK_TIMEOUT (5 * 1000)
|
|
#endif NOVVJOINHACK
|
|
VOID
|
|
RcsVvJoinDone(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
From: Inbound partner
|
|
The VvJoin thread has initiated all of the change orders. This doesn't
|
|
mean that the change orders have been installed, or even received.
|
|
|
|
At this second, we are trying to get sysvols to work; we
|
|
don't really care if the vvjoin is done or not except in
|
|
the case of sysvols. Hence, the hack below.
|
|
|
|
Use the sysvol seeding hack to initiate the deleting of empty
|
|
directories in the preexisting directory.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsVvJoinDone:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
ULONG FStatus;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsVvJoinDone entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Seeding has pretty much completed. We go ahead and hammer
|
|
// the configrecord in the DB in the hopes we don't lose this
|
|
// once-in-replica-lifetime state transition. The state is lost
|
|
// if this service restarts and none of the upstream partners
|
|
// vvjoins again.
|
|
//
|
|
// Set the incore BOOL that lets this code know the replica
|
|
// set is still seeding even though the CONFIG_FLAG_SEEDING
|
|
// bit is off. And yes, this is by design. The incore flag
|
|
// is enabled iff the DB state was updated successfully.
|
|
//
|
|
if (BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING)) {
|
|
|
|
ClearCxtionFlag(InCxtion, CXTION_FLAGS_INIT_SYNC);
|
|
|
|
FStatus = OutLogSubmit(Replica, InCxtion, CMD_OUTLOG_UPDATE_PARTNER);
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, FStatus, "F, OUTLOG_UPDATE_PARTNER return");
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT3(0, "++ WARN changes to cxtion %ws (to %ws, %ws) not updated in database\n",
|
|
InCxtion->Name->Name, InCxtion->Partner->Name, Replica->ReplicaName->Name);
|
|
}
|
|
}
|
|
|
|
//
|
|
// First time; wait a bit and retry
|
|
//
|
|
#ifndef NOVVJOINHACK
|
|
if (!RsTimeout(Cmd)) {
|
|
Replica->NtFrsApi_HackCount++; // != 0
|
|
RsTimeout(Cmd) = Replica->NtFrsApi_HackCount;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, VVJOIN_HACK_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// There as been no activity on this cxtion for awhile after the
|
|
// vvjoin done was received. Declare success. Tell our upstream
|
|
// partner to discard the volatile cxtion. Update the database
|
|
// if it hasn't already been updated. If updated successfully,
|
|
// inform NetLogon that it is time to share the sysvol.
|
|
//
|
|
if (RsTimeout(Cmd) == Replica->NtFrsApi_HackCount) {
|
|
//
|
|
// VVJOIN DONE
|
|
//
|
|
//
|
|
// Send this command to the initial sync command server for further
|
|
// processing if we are in seeding state.
|
|
//
|
|
if (BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING)) {
|
|
|
|
InitSyncCsSubmitTransfer(Cmd, CMD_INITSYNC_VVJOIN_DONE);
|
|
} else {
|
|
Cmd->Command = CMD_VVJOIN_DONE_UNJOIN;
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
} else {
|
|
//
|
|
// VVJOIN IN PROGRESS
|
|
//
|
|
RsTimeout(Cmd) = Replica->NtFrsApi_HackCount;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, VVJOIN_HACK_TIMEOUT);
|
|
}
|
|
|
|
#endif NOVVJOINHACK
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsVvJoinSuccess(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Type: Local
|
|
From: VvJoin Thread
|
|
Change the state of the oubound cxtion from VVJOINING to JOINED and
|
|
tell the outbound partner that the vvjoin thread is done.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsVvJoinSuccess:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsVvJoinSuccess entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_VVJOIN_DONE, NULL, NULL, NULL);
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsHungCxtion(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
From: Local OutLog Command Server
|
|
|
|
The outlog command server has detected a wrapped ack vector. This
|
|
may have been caused by a lost ack. In any case, check the progress
|
|
of the change orders by examining the the ack vector, the trailing
|
|
index, and the count of comm packets.
|
|
|
|
A cxtion is hung if it is wrapped and both the trailing index and
|
|
the number of comm packets received remains unchanged for
|
|
CommTimeoutInMilliSeconds (~5 minutes). In that case, the cxtion
|
|
is unjoined.
|
|
|
|
If the trailing index remains unchanged but comm packets are
|
|
being received then the timeout is reset to another
|
|
CommTimeoutInMilliSeconds interval.
|
|
|
|
If the cxtion appears un-hung, the command packet is completed.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsHungCxtion:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsHungCxtion entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
DPRINT1(4, ":X: Check Hung Cxtion for "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
|
|
//
|
|
// No out log context; can't be hung
|
|
//
|
|
if (!OutCxtion->OLCtx) {
|
|
DPRINT1(4, "++ No partner context for "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_PARAMETER);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// No longer wrapped; not hung
|
|
//
|
|
if (!AVWrapped(OutCxtion->OLCtx)) {
|
|
DPRINT1(4, "++ No longer wrapped for "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// AV is not wrapped at the same trailing index; not hung
|
|
//
|
|
if (OutCxtion->OLCtx->COTx != RsCOTx(Cmd)) {
|
|
DPRINT3(4, "++ COTx is %d; not %d for "FORMAT_CXTION_PATH2"\n",
|
|
OutCxtion->OLCtx->COTx,
|
|
RsCOTx(Cmd), PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Some comm pkts have shown up from the downstream partner,
|
|
// may not be hung. Check again later.
|
|
//
|
|
if (OutCxtion->CommPkts != RsCommPkts(Cmd)) {
|
|
DPRINT3(4, "++ CommPkts is %d; not %d for "FORMAT_CXTION_PATH2"\n",
|
|
OutCxtion->CommPkts,
|
|
RsCommPkts(Cmd), PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
RsCommPkts(Cmd) = OutCxtion->CommPkts;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, CommTimeoutInMilliSeconds);
|
|
} else {
|
|
DPRINT1(4, "++ WARN - Unjoin; cxtion is hung "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, OutCxtion));
|
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsDeleteCxtionFromReplica(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Common subroutine that synchronously deletes the cxtion from the
|
|
database and then removes it from the replica's table of cxtions.
|
|
|
|
The caller is responsible for insuring that this operation is
|
|
appropriate (cxtion exists, appropriate locks are held, ...)
|
|
|
|
Arguments:
|
|
Replica
|
|
Cxtion
|
|
|
|
Return Value:
|
|
TRUE - replica set was altered
|
|
FALSE - replica is unaltered
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDeleteCxtionFromReplica:"
|
|
ULONG FStatus;
|
|
|
|
//
|
|
// Delete the cxtion from the database and then remove it from
|
|
// the replica's table of cxtions. Keep it in the table of
|
|
// deleted cxtions because change orders may contain the address
|
|
// of this cxtion.
|
|
//
|
|
DPRINT1(4, ":X: Deleting cxtion from Db: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Deleting cxtion from Db");
|
|
|
|
//
|
|
// Must be in the deleted state to be deleted
|
|
//
|
|
if (!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, ERROR Cxtion state not deleted");
|
|
return;
|
|
}
|
|
|
|
FStatus = OutLogSubmit(Replica, Cxtion, CMD_OUTLOG_REMOVE_PARTNER);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
CXTION_STATE_TRACE(0, Cxtion, Replica, FStatus, "F, Warn: Del cxtion failed");
|
|
DPRINT1(0, "++ WARN Could not delete cxtion: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
}
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DPRINT1(4, "++ Deleting cxtion from table: "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Deleting cxtion GenTab");
|
|
GTabDelete(Replica->Cxtions, Cxtion->Name->Guid, NULL, NULL);
|
|
|
|
//
|
|
// Remove the connection from the perfmon tables so we stop returning
|
|
// data and allow a new connection with the same name to be established.
|
|
//
|
|
DeletePerfmonInstance(REPLICACONN, Cxtion->PerfRepConnData);
|
|
|
|
GTabInsertEntry(DeletedCxtions, Cxtion, Cxtion->Name->Guid, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsUnJoinCxtion(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN BOOL RemoteUnJoin
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
A comm pkt could not be sent to a cxtion's partner. Unjoin the
|
|
cxtion and periodically retry the join unless the cxtion is
|
|
joined and volatile. In that case, delete the cxtion because
|
|
our partner is unlikely to be able to rejoin with us (volatile
|
|
cxtions are lost at restart).
|
|
|
|
Arguments:
|
|
Cmd
|
|
RemoteUnJoin - Check authentication if remote
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsUnJoinCxtion:"
|
|
ULONG FStatus;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
BOOL CxtionWasJoined;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsUnJoinCxtion entry");
|
|
|
|
//
|
|
// Abort promotion; if any
|
|
//
|
|
// We no longer seed during dcpromo so a unjoin during seeding means that the
|
|
// volatile connection has timed out (on upstream) because of no activity for
|
|
// some time (default 30 minutes). The vvjoin will finish over the new connection
|
|
// or the volatile connection will be re-established when the seeding downstream
|
|
// partner comes back up.
|
|
//
|
|
if (Replica->NtFrsApi_ServiceState == NTFRSAPI_SERVICE_PROMOTING) {
|
|
DPRINT1(4, ":X: Promotion aborted: unjoin for %ws.\n", Replica->SetName->Name);
|
|
Replica->NtFrsApi_ServiceWStatus = FRS_ERR_SYSVOL_POPULATE;
|
|
Replica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_DONE;
|
|
}
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
Cxtion = RcsCheckCxtion(Cmd, DEBSUB,
|
|
CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_UNJOINGUID |
|
|
((RemoteUnJoin) ? CHECK_CXTION_AUTH : 0) |
|
|
((RemoteUnJoin) ? CHECK_CXTION_PARTNER : 0));
|
|
if (!Cxtion) {
|
|
return;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, Cxtion->Flags, "Flags, RcsUnJoinCxtion entry");
|
|
//
|
|
// The call to RcsForceUnjoin may alter the cxtion state.
|
|
// Retry the join later. Don't retry if the cxtion isn't
|
|
// joined because there are other retry mechanisms covering
|
|
// that case.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
CxtionWasJoined = CxtionStateIs(Cxtion, CxtionStateJoined);
|
|
if (!FrsIsShuttingDown &&
|
|
CxtionWasJoined &&
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_VOLATILE) &&
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE) &&
|
|
!RemoteUnJoin &&
|
|
IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Cxtion is DEFERRED_JOIN");
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
FStatus = RcsForceUnjoin(Replica, Cxtion);
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, FStatus, "F, RcsForceUnjoin return");
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
FrsCompleteCommand(Cmd, ERROR_REQUEST_ABORTED);
|
|
return;
|
|
}
|
|
//
|
|
// Delete the volatile cxtion if it was previously joined because
|
|
// there is no recovery for a failed sysvol seeding operation. Or
|
|
// if our downstream partner requested the unjoin.
|
|
//
|
|
if (!FrsIsShuttingDown &&
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_VOLATILE) &&
|
|
(CxtionWasJoined || RemoteUnJoin)) {
|
|
RcsDeleteCxtionFromReplica(Replica, Cxtion);
|
|
}
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
VOID
|
|
RcsInboundJoined(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
An inbound partner sent a JOINED command as a response to our join request.
|
|
|
|
JoinGuid may not match because we are seeing an old response after a timeout
|
|
on our end caused us to retry the join request (so the Guid changed).
|
|
|
|
The cxtion state should be JOINING but it could be JOINED if this is a
|
|
duplicate response. If we timed out and gave up and/or restarted the
|
|
entire Join sequence from the REQUEST_START state when a join response
|
|
finally arrives the JoinGuid won't match and the response is ignored.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsInboundJoined:"
|
|
PCXTION InCxtion;
|
|
ULONG FStatus;
|
|
PREPLICA Replica;
|
|
PFRS_QUEUE CoProcessQueue = NULL;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_JOINGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsInboundJoined entry");
|
|
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_AUTH |
|
|
CHECK_CXTION_PARTNER |
|
|
CHECK_CXTION_JOINGUID);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Shutting down; ignore join request
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, FrsIsShuttingDown");
|
|
FrsCompleteCommand(Cmd, ERROR_OPERATION_ABORTED);
|
|
return;
|
|
}
|
|
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, RcsInboundJoined entry");
|
|
|
|
//
|
|
// Increment the Join Notifications Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, JoinNRcvd, 1);
|
|
PM_INC_CTR_REPSET(Replica, JoinNRcvd, 1);
|
|
|
|
//
|
|
// Update the LastJoinedTime in our connection record for use in the next
|
|
// Join request. The following call is synchronous. Can't hold the
|
|
// cxtion table lock across this call.
|
|
//
|
|
if (RsLastJoinTime(Cmd) != InCxtion->LastJoinTime) {
|
|
InCxtion->LastJoinTime = RsLastJoinTime(Cmd);
|
|
FStatus = OutLogSubmit(Replica, InCxtion, CMD_OUTLOG_UPDATE_PARTNER);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT3(0, ":X: WARN changes to cxtion %ws (to %ws, %ws) not updated in database\n",
|
|
InCxtion->Name->Name,
|
|
InCxtion->Partner->Name, Replica->ReplicaName->Name);
|
|
}
|
|
|
|
//
|
|
// If the last join time is changing and we are not in the waitjoin state
|
|
// then it means that the upstream is transitioning from join-unjoin-join.
|
|
// We might still have outstanding fetch requests on this connection that
|
|
// the upstream has dropped. Resubmit all fetch requests at this time.
|
|
//
|
|
|
|
LOCK_CXTION_TABLE(Replica);
|
|
RcsResubmitActiveCOsOnCxtion(Replica, InCxtion);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
}
|
|
|
|
//
|
|
// Join complete; unidle the change order process queue
|
|
//
|
|
if (CxtionStateIs(InCxtion, CxtionStateWaitJoin)) {
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, JOINED");
|
|
LOCK_CXTION_TABLE(Replica);
|
|
SET_JOINED(Replica, InCxtion, "JOINED ");
|
|
CoProcessQueue = InCxtion->CoProcessQueue;
|
|
InCxtion->CoProcessQueue = NULL;
|
|
SET_UNJOIN_TRIGGER(InCxtion);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
//
|
|
// The process queue should be unidled here after releasing the cxtion
|
|
// lock to prevent deadlock. Never lock the process queue when you have the
|
|
// cxtion lock.
|
|
//
|
|
UNIDLE_CO_PROCESS_QUEUE(Replica, InCxtion, CoProcessQueue);
|
|
} else {
|
|
//
|
|
// Increment the Joins counter for both the replica set and cxtion objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, Joins, 1);
|
|
PM_INC_CTR_REPSET(Replica, Joins, 1);
|
|
|
|
DPRINT1(0, ":X: ***** REJOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, InCxtion));
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, REJOINED");
|
|
}
|
|
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsRemoteCoDoneRvcd(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Our outbound partner has completed processing this change order.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsRemoteCoDoneRvcd:"
|
|
|
|
ULONGLONG AckVersion;
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
POUT_LOG_PARTNER OutLogPartner;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK | CHECK_CMD_PARTNERCOC)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsRemoteCoDoneRvcd entry");
|
|
CHANGE_ORDER_COMMAND_TRACE(3, RsPartnerCoc(Cmd), "Command Remote CO Done");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_OUTBOUND |
|
|
CHECK_CXTION_JOINED |
|
|
CHECK_CXTION_JOINGUID |
|
|
CHECK_CXTION_AUTH);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
|
|
FRS_CO_COMM_PROGRESS(3, RsPartnerCoc(Cmd), RsCoSn(Cmd),
|
|
OutCxtion->PartSrvName, "Remote Co Done");
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, Remote Co Done");
|
|
|
|
//
|
|
// Update the outbound cxtion's version vector with the
|
|
// version (guid, vsn) supplied by the outbound partner
|
|
//
|
|
VVUpdateOutbound(OutCxtion->VVector, RsGVsn(Cmd));
|
|
RsGVsn(Cmd) = NULL;
|
|
|
|
//
|
|
// Check if this Ack is for a previous generation of the the Ack Vector
|
|
// and if it is just ignore it. Test for zero means that this is an old
|
|
// rev CO that was not sent with an AckVersion so skip the test.
|
|
// If we accept an ACK for a CO sent out with an old version of the Ack
|
|
// vector the effect will be to either accept an Ack for the wrong CO or
|
|
// mark the ack vector for a CO that has not yet been sent. The latter
|
|
// can later lead to an assert if the connection is now out of the
|
|
// VVJoin state.
|
|
//
|
|
OutLogPartner = OutCxtion->OLCtx;
|
|
|
|
AckVersion = RsPartnerCoc(Cmd)->AckVersion;
|
|
if ((AckVersion != 0) && (AckVersion != OutLogPartner->AckVersion)) {
|
|
|
|
CHANGE_ORDER_COMMAND_TRACE(3, RsPartnerCoc(Cmd), "Stale AckVersion - ignore");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Tell Outbound Log this CO for this connection is retired.
|
|
//
|
|
OutLogRetireCo(Replica, RsCoSn(Cmd), OutCxtion);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendRemoteCoDone(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_COMMAND Coc
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Tell our inbound partner that the change order is done
|
|
|
|
Arguments:
|
|
Replica
|
|
Coc
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendRemoteCoDone:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PCXTION InCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
#ifndef NOVVJOINHACK
|
|
Replica->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
//
|
|
// Return the (guid,vsn) and the co guid for this change order
|
|
//
|
|
Cmd = FrsAllocCommand(NULL, CMD_REMOTE_CO_DONE);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
RsGVsn(Cmd) = VVGetGVsn(Replica->VVector, &Coc->OriginatorGuid);
|
|
RsCoGuid(Cmd) = FrsDupGuid(&Coc->ChangeOrderGuid);
|
|
RsCoSn(Cmd) = Coc->PartnerAckSeqNumber;
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
RsCxtion(Cmd) = FrsBuildGName(FrsDupGuid(&Coc->CxtionGuid), NULL);
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
//
|
|
// Only needed for the call to RcsCheckCxtion above.
|
|
//
|
|
RsCxtion(Cmd) = FrsFreeGName(RsCxtion(Cmd));
|
|
RsReplica(Cmd) = NULL;
|
|
|
|
//
|
|
// Tell our inbound partner that the remote CO is done
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, Send REMOTE_CO_DONE");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, InCxtion, CMD_REMOTE_CO_DONE, NULL, Cmd, Coc);
|
|
|
|
SndCsSubmitCommPkt2(Replica, InCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsInboundCommitOk(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
The change order has been retired; inform our inbound partner
|
|
|
|
Arguments:
|
|
Replica
|
|
Coe
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsInboundCommitOk:"
|
|
PCHANGE_ORDER_COMMAND Coc = &Coe->Cmd;
|
|
|
|
#ifndef NOVVJOINHACK
|
|
Replica->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
DPRINT2(4, "++ Commit the retire on %s co for %ws\n",
|
|
CO_FLAG_ON(Coe, CO_FLAG_LOCALCO) ? "Local" : "Remote", Coc->FileName);
|
|
|
|
//
|
|
// Tell our inbound partner that we are done with the change order
|
|
//
|
|
if (!CO_FLAG_ON(Coe, CO_FLAG_LOCALCO)) {
|
|
RcsSendRemoteCoDone(Replica, Coc);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsSendCoToOneOutbound(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN PCHANGE_ORDER_COMMAND Coc
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Send the change order to one outbound cxtion
|
|
|
|
Arguments:
|
|
Replica
|
|
Cxtion
|
|
Coc
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendCoToOneOutbound:"
|
|
|
|
ULONG OrigFlags;
|
|
PCOMM_PACKET CPkt;
|
|
BOOL RestoreOldParent = FALSE;
|
|
BOOL RestoreNewParent = FALSE;
|
|
|
|
//
|
|
// Must be a joined, outbound cxtion
|
|
//
|
|
FRS_ASSERT(!Cxtion->Inbound);
|
|
|
|
//
|
|
// Don't send a change order our outbound partner has seen
|
|
//
|
|
if (VVHasVsn(Cxtion->VVector, Coc)) {
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Dampen Send VV");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Don't send a change order to its originator unless
|
|
// this is a vvjoin change order that isn't in the
|
|
// originator's version vector.
|
|
//
|
|
if ((!COC_FLAG_ON(Coc, CO_FLAG_VVJOIN_TO_ORIG)) &&
|
|
GUIDS_EQUAL(&Cxtion->ReplicaVersionGuid, &Coc->OriginatorGuid)) {
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Dampen Send Originator");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Send the change order to our outbound partner
|
|
//
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Sending");
|
|
|
|
//
|
|
// We don't know the value of our partner's root guid. Instead,
|
|
// we substitute the value of our partner's guid. Our partner
|
|
// will substitute the correct value for its root guid when
|
|
// our partner detects our substitution.
|
|
//
|
|
if (GUIDS_EQUAL(&Coc->OldParentGuid, Replica->ReplicaRootGuid)) {
|
|
RestoreOldParent = TRUE;
|
|
COPY_GUID(&Coc->OldParentGuid, Cxtion->Partner->Guid);
|
|
}
|
|
if (GUIDS_EQUAL(&Coc->NewParentGuid, Replica->ReplicaRootGuid)) {
|
|
RestoreNewParent = TRUE;
|
|
COPY_GUID(&Coc->NewParentGuid, Cxtion->Partner->Guid);
|
|
}
|
|
|
|
//
|
|
// A change order can be directed to only one outbound cxtion. Once
|
|
// the change order goes across the wire it is no longer "directed"
|
|
// to an outbound cxtion. So, turn off the flag before it is sent.
|
|
//
|
|
OrigFlags = Coc->Flags;
|
|
CLEAR_COC_FLAG(Coc, CO_FLAG_DIRECTED_CO);
|
|
|
|
CPkt = CommBuildCommPkt(Replica, Cxtion, CMD_REMOTE_CO, NULL, NULL, Coc);
|
|
|
|
//
|
|
// Restore the root guids substituted above
|
|
//
|
|
if (RestoreOldParent) {
|
|
COPY_GUID(&Coc->OldParentGuid, Replica->ReplicaRootGuid);
|
|
}
|
|
if (RestoreNewParent) {
|
|
COPY_GUID(&Coc->NewParentGuid, Replica->ReplicaRootGuid);
|
|
}
|
|
//
|
|
// Restore the flags
|
|
//
|
|
SET_COC_FLAG(Coc, OrigFlags);
|
|
|
|
SndCsSubmitCommPkt2(Replica, Cxtion, NULL, FALSE, CPkt);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsReceivedStageFile(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN ULONG AdditionalCxtionChecks
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
An outbound partner is sending a staging file to this inbound cxtion.
|
|
Request more if needed.
|
|
|
|
Arguments:
|
|
Cmd
|
|
AdditionalReplicaChecks
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReceivedStageFile:"
|
|
PCXTION InCxtion;
|
|
PREPLICA Replica;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
PCOMM_PACKET CPkt;
|
|
ULONG Flags;
|
|
ULONG WStatus;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
Coe = RsCoe(Cmd);
|
|
Coc = RsCoc(Cmd);
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsReceivedStageFile entry");
|
|
CHANGE_ORDER_TRACEXP(3, Coe, "Replica received stage", Cmd);
|
|
|
|
#ifndef NOVVJOINHACK
|
|
Replica->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
//
|
|
// *Note*
|
|
// Perf: Clean this up when the whole RCS, FETCS, STAGE_GEN_CS design is redone - Davidor.
|
|
//
|
|
// This bypass is needed because some of the callers want to make an
|
|
// authentication check on the received data fetch packet. In the case of an
|
|
// MD5 match where the MD5 checksum is supplied with the Change Order
|
|
// there is no authentication info to check so this check fails and causes
|
|
// the connection to unjoin and sends the CO thru retry. This makes the
|
|
// VVJoin real slow. This also skips over the perfmon fetch counters which
|
|
// is good since there was no fetch.
|
|
//
|
|
if (COE_FLAG_ON(Coe, COE_FLAG_PRE_EXIST_MD5_MATCH)) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "MD5 Match Bypass");
|
|
//
|
|
// We can't skip the below because we need to check the staging
|
|
// file flags for STAGE_FLAG_DATA_PRESENT. If that is clear we need
|
|
// to pass this friggen cmd pkt to the FetchCs to do the final stage file
|
|
// reanme. So instead clear the CHECK_CXTION_AUTH flag to avoid bogus
|
|
// cxtion unjoins. This stinks.
|
|
//
|
|
ClearFlag(AdditionalCxtionChecks, CHECK_CXTION_AUTH);
|
|
// goto FETCH_IS_DONE;
|
|
}
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND |
|
|
AdditionalCxtionChecks);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, RcsReceivedStageFile entry");
|
|
|
|
//
|
|
// If there is more staging file to fetch, go fetch it!
|
|
//
|
|
if (RsFileOffset(Cmd).QuadPart < RsFileSize(Cmd).QuadPart) {
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, Send more stage");
|
|
|
|
//
|
|
// Remember what offset of the staging file we are requesting.
|
|
//
|
|
//Coe->FileOffset.QuadPart = RsFileOffset(Cmd).QuadPart;
|
|
|
|
CPkt = CommBuildCommPkt(Replica, InCxtion, CMD_SEND_STAGE, NULL, Cmd, Coc);
|
|
SndCsSubmitCommPkt2(Replica, InCxtion, Coe, TRUE, CPkt);
|
|
RsCoe(Cmd) = NULL;
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
|
|
} else {
|
|
|
|
|
|
if (FrsDoesCoNeedStage(Coc)) {
|
|
//
|
|
// Even though all the data has been delivered the staging file rename
|
|
// may not have completed. E.g. a sharing violation on final rename.
|
|
// If so then send the cmd back to the Fetch CS to finish up.
|
|
//
|
|
Flags = STAGE_FLAG_EXCLUSIVE;
|
|
WStatus = StageAcquire(&Coc->ChangeOrderGuid,
|
|
Coc->FileName,
|
|
Coc->FileSize,
|
|
&Flags,
|
|
Replica->ReplicaNumber,
|
|
NULL);
|
|
|
|
if (WIN_RETRY_FETCH(WStatus)) {
|
|
//
|
|
// Retriable problem
|
|
//
|
|
CHANGE_ORDER_TRACEW(3, Coe, "Send to Fetch Retry", WStatus);
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_RETRY_FETCH);
|
|
return;
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Unrecoverable error; abort
|
|
//
|
|
CHANGE_ORDER_TRACEW(0, Coe, "Send to fetch abort", WStatus);
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_ABORT_FETCH);
|
|
return;
|
|
}
|
|
|
|
StageRelease(&Coc->ChangeOrderGuid, Coc->FileName, 0, NULL, NULL, NULL);
|
|
|
|
//
|
|
// Now check if we still need to finish the rename.
|
|
//
|
|
if (!(Flags & STAGE_FLAG_DATA_PRESENT)) {
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_RECEIVING_STAGE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Increment the staging files fetched counter for the replica set.
|
|
//
|
|
PM_INC_CTR_REPSET(Replica, SFFetched, 1);
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, Stage fetch complete");
|
|
}
|
|
|
|
//
|
|
// Increment the Fetch Requests Files Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, FetRReceived, 1);
|
|
PM_INC_CTR_REPSET(Replica, FetRReceived, 1);
|
|
|
|
//
|
|
// Display info for dcpromo during sysvol seeding.
|
|
// We don't seed during dcpromo anymore so we don't need to
|
|
// display any progress information.
|
|
//
|
|
// if (!Replica->NtFrsApi_ServiceDisplay) {
|
|
// Replica->NtFrsApi_ServiceDisplay = FrsWcsDup(Coc->FileName);
|
|
// }
|
|
|
|
// FETCH_IS_DONE:
|
|
|
|
//
|
|
// Installing the fetched staging file
|
|
//
|
|
SET_CHANGE_ORDER_STATE(Coe, IBCO_FETCH_COMPLETE);
|
|
SET_CHANGE_ORDER_STATE(Coe, IBCO_INSTALL_INITIATED);
|
|
|
|
//
|
|
// Install the staging file.
|
|
//
|
|
FrsInstallCsSubmitTransfer(Cmd, CMD_INSTALL_STAGE);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsRetryFetch(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Our inbound partner has requested that we retry fetching the staging
|
|
file at a later time.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsRetryFetch:"
|
|
PCXTION InCxtion;
|
|
PREPLICA Replica;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsRetryFetch entry");
|
|
CHANGE_ORDER_TRACEXP(3, RsCoe(Cmd), "Replica retry fetch", Cmd);
|
|
|
|
#ifndef NOVVJOINHACK
|
|
RsReplica(Cmd)->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_AUTH);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_RETRY_FETCH");
|
|
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_RETRY_FETCH);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsAbortFetch(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Our inbound partner has requested that we abort the fetch.
|
|
It could be out of disk space, out of disk quota or it may
|
|
not have the original file to create the staging file.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsAbortFetch:"
|
|
PCXTION InCxtion;
|
|
PREPLICA Replica;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
Coe = RsCoe(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsAbortFetch entry");
|
|
CHANGE_ORDER_TRACEXP(3, Coe, "Replica abort fetch", Cmd);
|
|
|
|
//
|
|
// Abort promotion; if any
|
|
//
|
|
//
|
|
// We no longer seed during dcpromo so aborting a fetch during seeding is same as
|
|
// aborting it at any other time. If we abort a dir create then it will trigger a unjoin
|
|
// of the volatile connection. The vvjoin will finish over the new connection
|
|
// or the volatile connection will be re-established when the seeding downstream
|
|
// partner comes back up.
|
|
//
|
|
if (Replica->NtFrsApi_ServiceState == NTFRSAPI_SERVICE_PROMOTING) {
|
|
DPRINT1(4, "++ Promotion aborted: abort fetch for %ws.\n",
|
|
Replica->SetName->Name);
|
|
Replica->NtFrsApi_ServiceWStatus = FRS_ERR_SYSVOL_POPULATE;
|
|
Replica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_DONE;
|
|
}
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_AUTH);
|
|
if (InCxtion == NULL) {
|
|
return;
|
|
}
|
|
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_ABORT_FETCH);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsReceivingStageFile(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Received this data from our inbound partner. Give it to the fetcher
|
|
so he can put it into the staging file.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReceivingStageFile:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsReceivingStageFile entry");
|
|
CHANGE_ORDER_TRACEXP(3, RsCoe(Cmd), "Replica receiving stage", Cmd);
|
|
|
|
#ifndef NOVVJOINHACK
|
|
RsReplica(Cmd)->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_AUTH);
|
|
if (!InCxtion) {
|
|
//
|
|
// Note: If auth check fails we might consider aborting the CO instead.
|
|
// Need to first try it and see if the CO would be aborted
|
|
// elsewhere first.
|
|
//
|
|
return;
|
|
}
|
|
//
|
|
// Display info for dcpromo during sysvol seeding
|
|
// We don't seed during dcpromo anymore so we don't need to
|
|
// display any progress information.
|
|
//
|
|
// if (!Replica->NtFrsApi_ServiceDisplay) {
|
|
// Replica->NtFrsApi_ServiceDisplay = FrsWcsDup(RsCoc(Cmd)->FileName);
|
|
// }
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_RECEIVING_STAGE");
|
|
|
|
//
|
|
// Increment the Fetch Requests Bytes Received counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, FetBRcvd, 1);
|
|
PM_INC_CTR_REPSET(Replica, FetBRcvd, 1);
|
|
PM_INC_CTR_CXTION(InCxtion, FetBRcvdBytes, RsBlockSize(Cmd));
|
|
PM_INC_CTR_REPSET(Replica, FetBRcvdBytes, RsBlockSize(Cmd));
|
|
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_RECEIVING_STAGE);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendRetryFetch(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Received from the local staging file fetcher. Tell our
|
|
outbound partner to retry the fetch at a later time.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendRetryFetch:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSendRetryFetch entry");
|
|
|
|
#ifndef NOVVJOINHACK
|
|
RsReplica(Cmd)->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, submit cmd CMD_RETRY_FETCH");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_RETRY_FETCH, NULL, Cmd, NULL);
|
|
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendAbortFetch(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Received from the local staging file fetcher. Tell our
|
|
outbound partner to abort the fetch.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendAbortFetch:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSendAbortFetch entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Tell our outbound partner that the file has been sent
|
|
//
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, submit cmd CMD_ABORT_FETCH");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_ABORT_FETCH, NULL, Cmd, NULL);
|
|
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendingStageFile(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Received from the local staging file fetcher. Push this data to
|
|
our outbound partner.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendingStageFile:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSendingStageFile entry");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Increment the Fetch Blocks sent and Fetch Bytes sent counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(OutCxtion, FetBSent, 1);
|
|
PM_INC_CTR_REPSET(Replica, FetBSent, 1);
|
|
PM_INC_CTR_CXTION(OutCxtion, FetBSentBytes, RsBlockSize(Cmd));
|
|
PM_INC_CTR_REPSET(Replica, FetBSentBytes, RsBlockSize(Cmd));
|
|
|
|
//
|
|
// Send the next block of the file to the outbound partner.
|
|
//
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, submit cmd CMD_RECEIVING_STAGE");
|
|
|
|
CPkt = CommBuildCommPkt(Replica, OutCxtion, CMD_RECEIVING_STAGE, NULL, Cmd, NULL);
|
|
|
|
SndCsSubmitCommPkt2(Replica, OutCxtion, NULL, FALSE, CPkt);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendStageFile(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Received a request to send the staging control file to our
|
|
outbound partner. Tell the staging fetcher to send it on.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendStageFile:"
|
|
PCXTION OutCxtion;
|
|
PREPLICA Replica;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_AND_COGUID_OK |
|
|
CHECK_CMD_PARTNERCOC )) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSendStageFile entry");
|
|
CHANGE_ORDER_COMMAND_TRACE(3, RsPartnerCoc(Cmd), "Command send stage");
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
OutCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_OUTBOUND);
|
|
if (!OutCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Tell the staging file generator to send the file
|
|
//
|
|
|
|
CXTION_STATE_TRACE(3, OutCxtion, Replica, 0, "F, submit cmd CMD_SEND_STAGE");
|
|
FrsFetchCsSubmitTransfer(Cmd, CMD_SEND_STAGE);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsRemoteCoAccepted(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
We have accepted the remote change order, fetch the staging file
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsRemoteCoAccepted:"
|
|
DWORD WStatus;
|
|
DWORD Flags;
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
PCOMM_PACKET CPkt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
Coe = RsCoe(Cmd);
|
|
Coc = RsCoc(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsRemoteCoAccepted entry");
|
|
CHANGE_ORDER_TRACEXP(3, Coe, "Replica remote co accepted", Cmd);
|
|
|
|
#ifndef NOVVJOINHACK
|
|
Replica->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
// We don't need auth check here because this cmd hasn't arrived
|
|
// from a partner. It is submitted by RcsSubmitRemoteCoAccepted()
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
FRS_CO_COMM_PROGRESS(3, Coc, (ULONG)(Coc->SequenceNumber),
|
|
InCxtion->PartSrvName, "Remote Co Accepted");
|
|
|
|
//
|
|
// MACRO MAY RETURN!!!
|
|
//
|
|
PULL_UNJOIN_TRIGGER(InCxtion, Cmd);
|
|
|
|
if (VVHasVsn(Replica->VVector, Coc)) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Dampen Accepted Remote Co");
|
|
}
|
|
|
|
//
|
|
// Don't fetch a non-existent staging file
|
|
//
|
|
if (!FrsDoesCoNeedStage(Coc)) {
|
|
|
|
//
|
|
// Allocate or update the Join Guid.
|
|
//
|
|
if (RsJoinGuid(Cmd) == NULL) {
|
|
RsJoinGuid(Cmd) = FrsDupGuid(&InCxtion->JoinGuid);
|
|
} else {
|
|
COPY_GUID(RsJoinGuid(Cmd), &InCxtion->JoinGuid);
|
|
}
|
|
|
|
RcsReceivedStageFile(Cmd, 0);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Don't refetch a recovered staging file
|
|
//
|
|
Flags = 0;
|
|
WStatus = StageAcquire(&Coc->ChangeOrderGuid, Coc->FileName, QUADZERO, &Flags, Replica->ReplicaNumber, NULL);
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
StageRelease(&Coc->ChangeOrderGuid, Coc->FileName, Flags, NULL, NULL, NULL);
|
|
if (Flags & STAGE_FLAG_CREATED) {
|
|
//
|
|
// File has been fetched
|
|
//
|
|
RsFileOffset(Cmd).QuadPart = RsFileSize(Cmd).QuadPart;
|
|
|
|
//
|
|
// Allocate or update the Join Guid.
|
|
//
|
|
if (RsJoinGuid(Cmd) == NULL) {
|
|
RsJoinGuid(Cmd) = FrsDupGuid(&InCxtion->JoinGuid);
|
|
} else {
|
|
COPY_GUID(RsJoinGuid(Cmd), &InCxtion->JoinGuid);
|
|
}
|
|
|
|
RcsReceivedStageFile(Cmd, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Attempt to use a preexisting file if this is the first block of the
|
|
// staging file, is a vvjoin co, and there is a preinstall file.
|
|
//
|
|
//
|
|
// 04/24/2002: We only do this for VVJoin COs. The change to look for a preexisting
|
|
// for non-vvjoin COs was removed as it was at the expense of a huge
|
|
// perf hit. Bug # 493700
|
|
//
|
|
if (InCxtion->PartnerMinor >= NTFRS_COMM_MINOR_1 &&
|
|
(RsFileOffset(Cmd).QuadPart == QUADZERO) &&
|
|
CO_FLAG_ON(Coe, CO_FLAG_VVJOIN_TO_ORIG) &&
|
|
COE_FLAG_ON(Coe, COE_FLAG_PREINSTALL_CRE)) {
|
|
|
|
//
|
|
// Removed the check for a VVJoin Co above. If this is a new file
|
|
// (i.e. a pe-install file was created) then check for a local file
|
|
// with a matching OID and MD5. Put out a trace record so we can
|
|
// tell when this case occurs.
|
|
//
|
|
if (!CO_FLAG_ON(Coe, CO_FLAG_VVJOIN_TO_ORIG)) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "**** Chk Existing - non-VVJOIN Co");
|
|
}
|
|
|
|
FrsStageCsSubmitTransfer(Cmd, CMD_CREATE_EXISTING);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// fetching the staging file
|
|
//
|
|
SET_CHANGE_ORDER_STATE(RsCoe(Cmd), IBCO_FETCH_INITIATED);
|
|
|
|
//
|
|
// Increment the Fetch Requests Files Requested counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, FetRSent, 1);
|
|
PM_INC_CTR_REPSET(Replica, FetRSent, 1);
|
|
|
|
//
|
|
// Tell our inbound partner to send the staging file
|
|
//
|
|
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_SEND_STAGE");
|
|
|
|
//
|
|
// Remember what offset of the staging file we are requesting.
|
|
//
|
|
//Coe->FileOffset.QuadPart = RsFileOffset(Cmd).QuadPart;
|
|
|
|
CPkt = CommBuildCommPkt(Replica, InCxtion, CMD_SEND_STAGE, NULL, Cmd, Coc);
|
|
SndCsSubmitCommPkt2(Replica, InCxtion, RsCoe(Cmd), TRUE, CPkt);
|
|
|
|
RsCoe(Cmd) = NULL;
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSendStageFileRequest(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Generated staging file (maybe) from preexisting file.
|
|
Request stage file from upstream partner. If MD5Digest matches
|
|
on Upstream partner's stage file then our pre-existing stage file (if any)
|
|
is good.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSendStageFileRequest:"
|
|
PREPLICA Replica;
|
|
PCXTION InCxtion;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
PCOMM_PACKET CPkt;
|
|
ULONG CocAttrs;
|
|
ULONG CoeAttrs;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
|
|
Replica = RsReplica(Cmd);
|
|
Coe = RsCoe(Cmd);
|
|
Coc = RsCoc(Cmd);
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSendStageFileRequest entry");
|
|
CHANGE_ORDER_TRACEXP(3, Coe, "Replica created existing", Cmd);
|
|
|
|
#ifndef NOVVJOINHACK
|
|
Replica->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
|
|
CocAttrs = Coc->FileAttributes &
|
|
~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NORMAL);
|
|
CoeAttrs = Coe->FileAttributes &
|
|
~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NORMAL);
|
|
|
|
//
|
|
// If the attribs don't match then we don't want to pass along or check the
|
|
// MD5 checksum. Otherwise we might wind up sending the local checksum
|
|
// upstream for our partner to do the check. He won't have the Attribs and
|
|
// would think the file was the same if only the attribs had changed.
|
|
//
|
|
|
|
if (CocAttrs != CoeAttrs) {
|
|
DPRINT2(3, "Attribute miss-match, zeroing MD5. CocAttrs: 0x%08x CoeAttrs: 0x%08x\n", CocAttrs, CoeAttrs);
|
|
RsMd5Digest(Cmd) = FrsFree(RsMd5Digest(Cmd));
|
|
}
|
|
|
|
if (RsMd5Digest(Cmd)) {
|
|
PDATA_EXTENSION_CHECKSUM CocDataChkSum;
|
|
|
|
//
|
|
// We have an existing file and the checksum has been created.
|
|
// See if we have a checksum in the changeorder from our inbound
|
|
// partner. If so and they match then we can move on to the install.
|
|
//
|
|
//
|
|
// We also need to check the attributes and the times, since they
|
|
// are not part of the checksum. Right now we do not have the times
|
|
// from the upstream partner so we can't check them.
|
|
//
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "Created Existing");
|
|
CocDataChkSum = DbsDataExtensionFind(Coc->Extension, DataExtend_MD5_CheckSum);
|
|
|
|
if ((CocDataChkSum != NULL) &&
|
|
!IS_MD5_CHKSUM_ZERO(CocDataChkSum->Data) &&
|
|
MD5_EQUAL(CocDataChkSum->Data, RsMd5Digest(Cmd))) {
|
|
|
|
//
|
|
// MD5 digest from CO matches so our file is good and we can
|
|
// avoid having the inbound partner recompute the checksum.
|
|
//
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Md5, Attribs match preexisting, no fetch");
|
|
SET_COE_FLAG(Coe, COE_FLAG_PRE_EXIST_MD5_MATCH);
|
|
|
|
DPRINT1(4, "++ RsFileSize(Cmd).QuadPart: %08x %08x\n",
|
|
PRINTQUAD(RsFileSize(Cmd).QuadPart));
|
|
DPRINT1(4, "++ Coc->FileSize: %08x %08x\n",
|
|
PRINTQUAD(Coc->FileSize));
|
|
|
|
//
|
|
// Even if the file is 0 bytes in length, the staging file will
|
|
// always have at least the header. There are some retry paths
|
|
// that will incorrectly think the staging file has been fetched
|
|
// if RsFileSize(Cmd) is 0. So make sure it isn't.
|
|
//
|
|
if (RsFileSize(Cmd).QuadPart == QUADZERO) {
|
|
RsFileSize(Cmd).QuadPart = Coc->FileSize;
|
|
|
|
if (RsFileSize(Cmd).QuadPart == QUADZERO) {
|
|
RsFileSize(Cmd).QuadPart = sizeof(STAGE_HEADER);
|
|
}
|
|
}
|
|
|
|
DPRINT1(4, "++ RsFileSize(Cmd).QuadPart: %08x %08x\n",
|
|
PRINTQUAD(RsFileSize(Cmd).QuadPart));
|
|
|
|
//
|
|
// Set the offset to the size of the stage file so we don't request
|
|
// any data.
|
|
//
|
|
RsFileOffset(Cmd).QuadPart = RsFileSize(Cmd).QuadPart;
|
|
RsBlockSize(Cmd) = QUADZERO;
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Allocate or update the Join Guid.
|
|
//
|
|
if (RsJoinGuid(Cmd) == NULL) {
|
|
RsJoinGuid(Cmd) = FrsDupGuid(&InCxtion->JoinGuid);
|
|
} else {
|
|
COPY_GUID(RsJoinGuid(Cmd), &InCxtion->JoinGuid);
|
|
}
|
|
|
|
RcsReceivedStageFile(Cmd, 0);
|
|
|
|
//RcsSubmitTransferToRcs(Cmd, CMD_RECEIVED_STAGE);
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Could not create existing");
|
|
}
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Tell our inbound partner to send the staging file
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_SEND_STAGE");
|
|
|
|
//
|
|
// Remember what offset of the staging file we are requesting.
|
|
//
|
|
//Coe->FileOffset.QuadPart = RsFileOffset(Cmd).QuadPart;
|
|
|
|
CPkt = CommBuildCommPkt(Replica, InCxtion, CMD_SEND_STAGE, NULL, Cmd, Coc);
|
|
SndCsSubmitCommPkt2(Replica, InCxtion, RsCoe(Cmd), TRUE, CPkt);
|
|
|
|
RsCoe(Cmd) = NULL;
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsRemoteCoReceived(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Remote CO has arrived.
|
|
|
|
Translate guid of our root dir if necc.
|
|
|
|
Attach the Change Order extension if provided.
|
|
|
|
Check version vector dampening and provide immediate Ack if we have
|
|
already seen this CO.
|
|
|
|
Finally, pass the change order on to the change order subsystem.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsRemoteCoReceived:"
|
|
|
|
PCXTION InCxtion;
|
|
PREPLICA Replica;
|
|
PULONG pULong;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
PCHANGE_ORDER_RECORD_EXTENSION CocExt;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_CXTION_OK | CHECK_CMD_PARTNERCOC )) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
Coc = RsPartnerCoc(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsRemoteCoReceived entry");
|
|
|
|
#ifndef NOVVJOINHACK
|
|
RsReplica(Cmd)->NtFrsApi_HackCount++;
|
|
#endif NOVVJOINHACK
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_JOIN_OK |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_AUTH |
|
|
CHECK_CXTION_FIXJOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Remember which cxtion this change order was directed at
|
|
//
|
|
COPY_GUID(&Coc->CxtionGuid, RsCxtion(Cmd)->Guid);
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Replica Received Remote Co");
|
|
|
|
//
|
|
// Our partner doesn't know the correct value for our root guid and
|
|
// so substituted our ReplicaName->Guid (its Cxtion->Partner->Guid)
|
|
// for its own root guids when sending the change order to us.
|
|
// We substitute our own root guids once we receive the change order
|
|
// and detect the substitution.
|
|
//
|
|
if (GUIDS_EQUAL(&Coc->OldParentGuid, RsReplica(Cmd)->ReplicaName->Guid)) {
|
|
COPY_GUID(&Coc->OldParentGuid, RsReplica(Cmd)->ReplicaRootGuid);
|
|
}
|
|
if (GUIDS_EQUAL(&Coc->NewParentGuid, RsReplica(Cmd)->ReplicaName->Guid)) {
|
|
COPY_GUID(&Coc->NewParentGuid, RsReplica(Cmd)->ReplicaRootGuid);
|
|
}
|
|
|
|
//
|
|
// Init the Coc pointer to the CO Data Extension. Down Rev partners
|
|
// won't have one so supply an empty field. Do this here in case VV
|
|
// Dampening short circuits the CO and sends back the RemoteCoDone ACK here.
|
|
//
|
|
CocExt = RsPartnerCocExt(Cmd);
|
|
|
|
if (CocExt == NULL) {
|
|
CocExt = FrsAlloc(sizeof(CHANGE_ORDER_RECORD_EXTENSION));
|
|
DbsDataInitCocExtension(CocExt);
|
|
DPRINT(4, "Allocating initial Coc Extension\n");
|
|
}
|
|
|
|
Coc->Extension = CocExt;
|
|
|
|
pULong = (PULONG) CocExt;
|
|
DPRINT5(5, "Extension Buffer: (%08x) %08x %08x %08x %08x\n",
|
|
pULong, *(pULong+0), *(pULong+1), *(pULong+2), *(pULong+3));
|
|
DPRINT5(5, "Extension Buffer: (%08x) %08x %08x %08x %08x\n",
|
|
(PCHAR)pULong+16, *(pULong+4), *(pULong+5), *(pULong+6), *(pULong+7));
|
|
|
|
//
|
|
// Don't redo a change order
|
|
//
|
|
if (VVHasVsn(RsReplica(Cmd)->VVector, Coc)) {
|
|
//
|
|
// Increment the Inbound CO dampned counter for
|
|
// both the replica set and connection objects
|
|
//
|
|
PM_INC_CTR_CXTION(InCxtion, InCODampned, 1);
|
|
PM_INC_CTR_REPSET(RsReplica(Cmd), InCODampned, 1);
|
|
CHANGE_ORDER_COMMAND_TRACE(3, Coc, "Dampen Received Remote Co");
|
|
|
|
RcsSendRemoteCoDone(RsReplica(Cmd), Coc);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Put the change order on the inbound queue for this replica.
|
|
//
|
|
ChgOrdInsertRemoteCo(Cmd, InCxtion);
|
|
|
|
//
|
|
// Done
|
|
//
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsRetryStageFileCreate(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Retry generating the staging file.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsRetryStageFileCreate:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCXTION InCxtion;
|
|
PVOID Key;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsRetryStageFileCreate entry");
|
|
CHANGE_ORDER_TRACEXP(3, RsCoe(Cmd), "Replica retry stage", Cmd);
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JRNLCXTION |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Ignore local change order if there are no outbound cxtions
|
|
//
|
|
Key = NULL;
|
|
while (OutCxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
if (!OutCxtion->Inbound) {
|
|
break;
|
|
}
|
|
}
|
|
if (OutCxtion == NULL) {
|
|
//
|
|
// Make sure a user hasn't altered our object id on the file
|
|
// and then retire the change order without propagating to the
|
|
// outbound log. The stager is responsible for hammering the
|
|
// object id because it knows how to handle sharing violations
|
|
// and file-not-found errors.
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_CHECK_OID");
|
|
FrsStageCsSubmitTransfer(Cmd, CMD_CHECK_OID);
|
|
} else {
|
|
//
|
|
// Generate the staging file
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_CREATE_STAGE");
|
|
FrsStageCsSubmitTransfer(Cmd, CMD_CREATE_STAGE);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsLocalCoAccepted(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Process the accepted, local change order. Either retire it if there
|
|
are no outbound cxtions or send it own to the staging file generator.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsLocalCoAccepted:"
|
|
PREPLICA Replica;
|
|
PCXTION OutCxtion;
|
|
PCXTION InCxtion;
|
|
PVOID Key;
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA | CHECK_CMD_COE)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
Coe = RsCoe(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsLocalCoAccepted entry");
|
|
CHANGE_ORDER_TRACEXP(3, Coe, "Replica local co accepted", Cmd);
|
|
|
|
//
|
|
// Find and check the cxtion
|
|
//
|
|
InCxtion = RcsCheckCxtion(Cmd, DEBSUB, CHECK_CXTION_EXISTS |
|
|
CHECK_CXTION_INBOUND |
|
|
CHECK_CXTION_JRNLCXTION |
|
|
CHECK_CXTION_JOINED);
|
|
if (!InCxtion) {
|
|
return;
|
|
}
|
|
|
|
FRS_CO_COMM_PROGRESS(3, &Coe->Cmd, Coe->Cmd.SequenceNumber,
|
|
InCxtion->PartSrvName, "Local Co Accepted");
|
|
|
|
//
|
|
// Ignore local change order if there are no outbound cxtions
|
|
//
|
|
Key = NULL;
|
|
while (OutCxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
if (!OutCxtion->Inbound) {
|
|
break;
|
|
}
|
|
}
|
|
if (OutCxtion == NULL) {
|
|
//
|
|
// Make sure a user hasn't altered our object id on the file
|
|
// and then retire the change order without propagating to the
|
|
// outbound log. The stager is responsible for hammering the
|
|
// object id because it knows how to handle sharing violations
|
|
// and file-not-found errors.
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_CHECK_OID");
|
|
FrsStageCsSubmitTransfer(Cmd, CMD_CHECK_OID);
|
|
} else {
|
|
//
|
|
// Generate the staging file
|
|
//
|
|
CXTION_STATE_TRACE(3, InCxtion, Replica, 0, "F, submit cmd CMD_CREATE_STAGE");
|
|
FrsStageCsSubmitTransfer(Cmd, CMD_CREATE_STAGE);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsBeginMergeWithDs(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
The DS has been polled and now has replicas to merge into the
|
|
active replicas initially retrieved from the database or merged
|
|
into the active replicas by a previous poll.
|
|
|
|
Each active replica is marked as "not merged with ds". Any
|
|
replica that remains in this state after the merge is done
|
|
is a deleted replica. See RcsEndMergeWithDs().
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
TRUE - Continue with merge
|
|
FALSE - Abort merge
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsBeginMergeWithDs:"
|
|
PVOID Key;
|
|
PREPLICA Replica;
|
|
extern CRITICAL_SECTION MergingReplicasWithDs;
|
|
|
|
//
|
|
// Wait for the replica command server to start up. The shutdown
|
|
// code will set this event so we don't sleep forever.
|
|
//
|
|
WaitForSingleObject(ReplicaEvent, INFINITE);
|
|
|
|
//
|
|
// Synchronize with sysvol seeding
|
|
//
|
|
EnterCriticalSection(&MergingReplicasWithDs);
|
|
|
|
//
|
|
// Snapshot a copy of the replica table. Anything left in the table
|
|
// after the merge should be deleted because a corresponding entry
|
|
// no longer exists in the DS. This code is not multithread!
|
|
//
|
|
FRS_ASSERT(ReplicasNotInTheDs == NULL);
|
|
ReplicasNotInTheDs = GTabAllocTable();
|
|
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Insert ReplicasNotInTheDs");
|
|
GTabInsertEntry(ReplicasNotInTheDs, Replica, Replica->ReplicaName->Guid, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitReplica(
|
|
IN PREPLICA Replica,
|
|
IN PREPLICA NewReplica, OPTIONAL
|
|
IN USHORT Command
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a command to a replica.
|
|
|
|
Arguments:
|
|
Replica - existing replica
|
|
NewReplica - Changes to Replica (may be NULL)
|
|
Command
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitReplica:"
|
|
PCOMMAND_PACKET Cmd;
|
|
|
|
//
|
|
// Allocate a command packet
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, Command);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of replica set and new replica set
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
RsNewReplica(Cmd) = NewReplica;
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsSubmitReplica cmd");
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitReplicaCxtion(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN USHORT Command
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a command to a replica\cxtion.
|
|
|
|
Build cmd pkt with Cxtion GName, replica ptr, Join Guid and supplied command.
|
|
Submit to Replica cmd server.
|
|
Calls dispatch function and translates cxtion GName to cxtion ptr
|
|
Builds Comm pkt for cxtion and calls SndCsSubmit() to send it.
|
|
|
|
Arguments:
|
|
Replica - existing replica
|
|
Cxtion - existing cxtion
|
|
Command
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitReplicaCxtion:"
|
|
PCOMMAND_PACKET Cmd;
|
|
|
|
//
|
|
// Allocate a command packet
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, Command);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of replica set and new replica set
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
RsCxtion(Cmd) = FrsDupGName(Cxtion->Name);
|
|
RsJoinGuid(Cmd) = FrsDupGuid(&Cxtion->JoinGuid);
|
|
//
|
|
// OLCtx and CommPkts are used for CMD_HUNG_CXTION.
|
|
// They are used to detect a hung outbound cxtion that
|
|
// is probably hung because of a dropped ack.
|
|
//
|
|
if (Cxtion->OLCtx) {
|
|
RsCOTx(Cmd) = Cxtion->OLCtx->COTx;
|
|
}
|
|
RsCommPkts(Cmd) = Cxtion->CommPkts - 1;
|
|
|
|
DPRINT5(5, "Submit %08x for Cmd %08x %ws\\%ws\\%ws\n",
|
|
Cmd->Command, Cmd, Replica->SetName->Name, Replica->MemberName->Name,
|
|
Cxtion->Name->Name);
|
|
|
|
CXTION_STATE_TRACE(5, Cxtion, Replica, 0, "F, RcsSubmitReplicaCxtion cmd");
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
|
|
|
|
DWORD
|
|
RcsSubmitReplicaSync(
|
|
IN PREPLICA Replica,
|
|
IN PREPLICA NewReplica,
|
|
IN PCXTION VolatileCxtion,
|
|
IN USHORT Command
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a command to a replica and wait for it to finish.
|
|
|
|
Arguments:
|
|
Replica - existing replica
|
|
NewReplica - Changes to Replica (may be NULL)
|
|
VolatileCxtion - New cxtion (currently used for seeding sysvols)
|
|
Command
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitReplicaSync:"
|
|
DWORD WStatus;
|
|
PCOMMAND_PACKET Cmd;
|
|
|
|
//
|
|
// Allocate a command packet
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, Command);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of replica set and new replica set
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
RsNewReplica(Cmd) = NewReplica;
|
|
RsNewCxtion(Cmd) = VolatileCxtion;
|
|
RsCompletionEvent(Cmd) = FrsCreateEvent(TRUE, FALSE);
|
|
|
|
DPRINT3(5, "Submit Sync %08x for Cmd %08x %ws\n",
|
|
Cmd->Command, Cmd, RsReplica(Cmd)->ReplicaName->Name);
|
|
|
|
|
|
CXTION_STATE_TRACE(5, VolatileCxtion, Replica, 0, "F, RcsSubmitReplicaSync cmd");
|
|
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
|
|
//
|
|
// Wait for the command to finish
|
|
//
|
|
WaitForSingleObject(RsCompletionEvent(Cmd), INFINITE);
|
|
FRS_CLOSE(RsCompletionEvent(Cmd));
|
|
|
|
WStatus = Cmd->ErrorStatus;
|
|
FrsCompleteCommand(Cmd, Cmd->ErrorStatus);
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsEndMergeWithDs(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
The DS has been polled and the replicas have been merged into the
|
|
active replicas.
|
|
|
|
Each active replica was initially included in a temporary table.
|
|
Any replica still left in the table is a deleted replica because
|
|
its corresponding entry in the DS was not found.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsEndMergeWithDs:"
|
|
PVOID Key;
|
|
PREPLICA Replica;
|
|
DWORD ReplicaSetsDeleted;
|
|
extern BOOL DsIsShuttingDown;
|
|
extern CRITICAL_SECTION MergingReplicasWithDs;
|
|
extern ULONG DsPollingInterval;
|
|
extern ULONG DsPollingShortInterval;
|
|
|
|
//
|
|
// Any replica that is in the state "not merged" should be deleted
|
|
// unless the Ds is shutting down; in which case do not process
|
|
// deletes because a sysvol seeding operation may be in progress.
|
|
// We do not want to delete the sysvol we are currently trying to
|
|
// create.
|
|
//
|
|
Key = NULL;
|
|
while (!DsIsShuttingDown &&
|
|
(Replica = GTabNextDatum(ReplicasNotInTheDs, &Key))) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, submit replica CMD_DELETE");
|
|
RcsSubmitReplica(Replica, NULL, CMD_DELETE);
|
|
}
|
|
|
|
DbsProcessReplicaFaultList(&ReplicaSetsDeleted);
|
|
|
|
//
|
|
// If any replica sets were deleted while processing the fault list then we should
|
|
// trigger the next poll sooner so we can start the non-auth restore on the
|
|
// deleted replica sets.
|
|
//
|
|
if (ReplicaSetsDeleted) {
|
|
DsPollingInterval = DsPollingShortInterval;
|
|
}
|
|
|
|
GTabFreeTable(ReplicasNotInTheDs, NULL);
|
|
ReplicasNotInTheDs = NULL;
|
|
|
|
//
|
|
// Synchronize with sysvol seeding
|
|
//
|
|
LeaveCriticalSection(&MergingReplicasWithDs);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsReplicaSetRegistry(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function stores information about the replica set
|
|
into the registry for use by ntfrsupg /restore
|
|
(non-authoritative restore).
|
|
|
|
Arguments:
|
|
|
|
Replica
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReplicaSetRegistry:"
|
|
DWORD WStatus;
|
|
PWCHAR ReplicaSetTypeW;
|
|
DWORD NumberOfPartners;
|
|
DWORD BurFlags;
|
|
DWORD ReplicaSetTombstoned;
|
|
HKEY HSeedingsKey = INVALID_HANDLE_VALUE;
|
|
WCHAR GuidW[GUID_CHAR_LEN + 1];
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
if (!Replica ||
|
|
!Replica->SetName || !Replica->SetName->Name ||
|
|
!Replica->MemberName || !Replica->MemberName->Guid) {
|
|
DPRINT(4, ":S: WARN - Partial replica set ignored\n");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Working path to jet Database dir.
|
|
//
|
|
CfgRegWriteString(FKC_SETS_JET_PATH, NULL, FRS_RKF_CREATE_KEY, JetPath);
|
|
|
|
//
|
|
// Create the subkey for this set
|
|
//
|
|
GuidToStrW(Replica->MemberName->Guid, GuidW);
|
|
|
|
//
|
|
// Replica set name
|
|
// Replica set root path
|
|
// Replica set stage path
|
|
//
|
|
CfgRegWriteString(FKC_SET_N_REPLICA_SET_NAME,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
Replica->SetName->Name);
|
|
|
|
CfgRegWriteString(FKC_SET_N_REPLICA_SET_ROOT,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
Replica->Root);
|
|
|
|
CfgRegWriteString(FKC_SET_N_REPLICA_SET_STAGE,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
Replica->Stage);
|
|
|
|
//
|
|
// Replica set type
|
|
//
|
|
switch (Replica->ReplicaSetType) {
|
|
case FRS_RSTYPE_ENTERPRISE_SYSVOL:
|
|
ReplicaSetTypeW = NTFRSAPI_REPLICA_SET_TYPE_ENTERPRISE;
|
|
break;
|
|
case FRS_RSTYPE_DOMAIN_SYSVOL:
|
|
ReplicaSetTypeW = NTFRSAPI_REPLICA_SET_TYPE_DOMAIN;
|
|
break;
|
|
case FRS_RSTYPE_DFS:
|
|
ReplicaSetTypeW = NTFRSAPI_REPLICA_SET_TYPE_DFS;
|
|
break;
|
|
default:
|
|
ReplicaSetTypeW = NTFRSAPI_REPLICA_SET_TYPE_OTHER;
|
|
break;
|
|
}
|
|
|
|
CfgRegWriteString(FKC_SET_N_REPLICA_SET_TYPE,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
ReplicaSetTypeW);
|
|
|
|
//
|
|
// Replica Set Tombstoned
|
|
//
|
|
ReplicaSetTombstoned = (!IS_TIME_ZERO(Replica->MembershipExpires)) ? 1 : 0;
|
|
|
|
CfgRegWriteDWord(FKC_SET_N_REPLICA_SET_TOMBSTONED,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
ReplicaSetTombstoned);
|
|
|
|
|
|
//
|
|
// Update the registry state under Cumulative Replica Sets for this set.
|
|
//
|
|
|
|
NumberOfPartners = GTabNumberInTable(Replica->Cxtions);
|
|
//
|
|
// If NumberOfPartners is non zero, subtract the Journal
|
|
// Cxtion entry since its not a real connection
|
|
//
|
|
if (NumberOfPartners > 0) {
|
|
NumberOfPartners -= 1;
|
|
}
|
|
|
|
CfgRegWriteDWord(FKC_CUMSET_N_NUMBER_OF_PARTNERS,
|
|
GuidW,
|
|
FRS_RKF_CREATE_KEY,
|
|
NumberOfPartners);
|
|
|
|
|
|
//
|
|
// Init Backup / Restore flags.
|
|
//
|
|
BurFlags = NTFRSAPI_BUR_FLAGS_NONE;
|
|
CfgRegWriteDWord(FKC_CUMSET_N_BURFLAGS, GuidW, FRS_RKF_CREATE_KEY, BurFlags);
|
|
|
|
|
|
//
|
|
// If done seeding then cleanup the sysvol seeding key.
|
|
//
|
|
if (!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING)) {
|
|
|
|
WStatus = CfgRegOpenKey(FKC_SYSVOL_SEEDING_SECTION_KEY,
|
|
NULL,
|
|
FRS_RKF_CREATE_KEY,
|
|
&HSeedingsKey);
|
|
CLEANUP1_WS(4, ":S: WARN - Cannot create sysvol seedings key for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Seeding is over so delete sysvol seeding key using replica set Name.
|
|
//
|
|
WStatus = RegDeleteKey(HSeedingsKey, Replica->ReplicaName->Name);
|
|
DPRINT1_WS(4, ":S: WARN - Cannot delete seeding key for %ws;",
|
|
Replica->SetName->Name, WStatus);
|
|
}
|
|
|
|
CLEANUP:
|
|
|
|
FRS_REG_CLOSE(HSeedingsKey);
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsReplicaIsRestored(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check if the replica set should be deleted because it has been
|
|
restored. Only called from RcsInitKnownReplicaSetMembers() at startup. The
|
|
BurFlags will be wiped after the replica set is recreated (if ever).
|
|
|
|
Arguments:
|
|
|
|
Replica
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReplicaIsRestored:"
|
|
DWORD WStatus;
|
|
DWORD BurFlags;
|
|
BOOL IsRestored = FALSE;
|
|
WCHAR GuidW[GUID_CHAR_LEN + 1];
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
if (!Replica ||
|
|
!Replica->MemberName || !Replica->MemberName->Guid) {
|
|
DPRINT(0, ":S: WARN - Partial replica set ignored\n");
|
|
return IsRestored;
|
|
}
|
|
|
|
//
|
|
// Get BurFlags
|
|
// FRS_CONFIG_SECTION\Cumulative Replica Sets\Replica Sets\<guid>\BurFlags
|
|
//
|
|
GuidToStrW(Replica->MemberName->Guid, GuidW);
|
|
|
|
WStatus = CfgRegReadDWord(FKC_CUMSET_N_BURFLAGS, GuidW, FRS_RKF_CREATE_KEY, &BurFlags);
|
|
CLEANUP_WS(0, ":S: ERROR - Can't read FKC_CUMSET_N_BURFLAGS;", WStatus, CLEANUP);
|
|
|
|
if ((BurFlags & NTFRSAPI_BUR_FLAGS_RESTORE) &&
|
|
(BurFlags & NTFRSAPI_BUR_FLAGS_ACTIVE_DIRECTORY) &&
|
|
(BurFlags & (NTFRSAPI_BUR_FLAGS_PRIMARY |
|
|
NTFRSAPI_BUR_FLAGS_NON_AUTHORITATIVE))) {
|
|
//
|
|
// SUCCESS
|
|
//
|
|
IsRestored = TRUE;
|
|
DPRINT1(4, ":S: %ws has been restored\n", Replica->SetName->Name);
|
|
} else {
|
|
//
|
|
// FAILURE
|
|
//
|
|
DPRINT1(4, ":S: %ws has not been restored\n", Replica->SetName->Name);
|
|
}
|
|
|
|
CLEANUP:
|
|
|
|
return IsRestored;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsReplicaDeleteRegistry (
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes the information about the replica set
|
|
from the registry.
|
|
|
|
Arguments:
|
|
|
|
Replica
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReplicaDeleteRegistry:"
|
|
DWORD WStatus;
|
|
HKEY HKey = INVALID_HANDLE_VALUE;
|
|
HKEY HAllSetsKey = INVALID_HANDLE_VALUE;
|
|
HKEY HCumusKey = INVALID_HANDLE_VALUE;
|
|
WCHAR GuidW[GUID_CHAR_LEN + 1];
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
if (!Replica ||
|
|
!Replica->SetName || !Replica->SetName->Name ||
|
|
!Replica->MemberName || !Replica->MemberName->Guid) {
|
|
DPRINT(0, ":S: WARN - Partial replica set ignored\n");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Delete old state from the registry
|
|
//
|
|
WStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
FRS_CONFIG_SECTION,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&HKey);
|
|
CLEANUP1_WS(0, ":S: WARN - Cannot open parameters for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Create the subkey for all sets
|
|
//
|
|
WStatus = RegCreateKey(HKey, FRS_SETS_KEY, &HAllSetsKey);
|
|
CLEANUP1_WS(0, ":S: WARN - Cannot create sets key for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Delete the subkey for this set
|
|
//
|
|
GuidToStrW(Replica->MemberName->Guid, GuidW);
|
|
WStatus = RegDeleteKey(HAllSetsKey, GuidW);
|
|
CLEANUP1_WS(0, ":S: WARN - Cannot delete set key for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Cumulative Replica Sets
|
|
//
|
|
//
|
|
// Create the subkey for all sets
|
|
//
|
|
WStatus = RegCreateKey(HKey, FRS_CUMULATIVE_SETS_KEY, &HCumusKey);
|
|
CLEANUP1_WS(0, ":S: WARN - Cannot create cumulative sets key for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Delete the subkey for this set
|
|
//
|
|
WStatus = RegDeleteKey(HCumusKey, GuidW);
|
|
CLEANUP1_WS(0, ":S: WARN - Cannot delete cumulative key for %ws;",
|
|
Replica->SetName->Name, WStatus, CLEANUP);
|
|
|
|
CLEANUP:
|
|
FRS_REG_CLOSE(HKey);
|
|
FRS_REG_CLOSE(HAllSetsKey);
|
|
FRS_REG_CLOSE(HCumusKey);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsReplicaClearRegistry(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes all of the replica set information in the registry.
|
|
This function should only be called after enumerating the configrecords.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReplicaClearRegistry:"
|
|
DWORD WStatus;
|
|
HKEY HKey = INVALID_HANDLE_VALUE;
|
|
HKEY HAllSetsKey = INVALID_HANDLE_VALUE;
|
|
WCHAR KeyBuf[MAX_PATH + 1];
|
|
|
|
//
|
|
// Empty the replica set info out of the registry
|
|
//
|
|
|
|
//
|
|
// Set new state in the registry
|
|
//
|
|
WStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
FRS_CONFIG_SECTION,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&HKey);
|
|
CLEANUP_WS(0, "WARN - Cannot open parameters for delete sets", WStatus, CLEANUP);
|
|
|
|
//
|
|
// Create the subkey for all sets
|
|
//
|
|
WStatus = RegCreateKey(HKey, FRS_SETS_KEY, &HAllSetsKey);
|
|
CLEANUP_WS(0, "WARN - Cannot create sets key for delete sets", WStatus, CLEANUP);
|
|
|
|
//
|
|
// Delete the subkeys
|
|
//
|
|
do {
|
|
WStatus = RegEnumKey(HAllSetsKey, 0, KeyBuf, MAX_PATH + 1);
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
WStatus = RegDeleteKey(HAllSetsKey, KeyBuf);
|
|
}
|
|
} while (WIN_SUCCESS(WStatus));
|
|
|
|
if (WStatus != ERROR_NO_MORE_ITEMS) {
|
|
CLEANUP_WS(0, "WARN - Cannot delete all keys", WStatus, CLEANUP);
|
|
}
|
|
|
|
CLEANUP:
|
|
FRS_REG_CLOSE(HKey);
|
|
FRS_REG_CLOSE(HAllSetsKey);
|
|
}
|
|
|
|
|
|
DWORD
|
|
RcsCreateReplicaSetMember(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Create a database record for Replica.
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
An Frs Error status
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCreateReplicaSetMember:"
|
|
ULONG Status;
|
|
ULONG FStatus;
|
|
ULONG WStatus;
|
|
ULONG RootLen;
|
|
ULONG i, Rest;
|
|
|
|
PVOID Key;
|
|
PCXTION Cxtion;
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
PTABLE_CTX TableCtx = NULL;
|
|
PWCHAR WStatusUStr, FStatusUStr;
|
|
|
|
|
|
#define CXTION_EVENT_RPT_MAX 8
|
|
PWCHAR InWStr, OutWStr, WStrArray[CXTION_EVENT_RPT_MAX];
|
|
|
|
#define CXTION_STR_MAX 256
|
|
WCHAR CxtionStr[CXTION_STR_MAX];
|
|
|
|
|
|
extern ULONGLONG ActiveChange;
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// We are creating a new replica set member. Set the Cnf flag to CONFIG_FLAG_SEEDING.
|
|
// This will trigger a serialvvjoin for this replica set.
|
|
if (!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_PRIMARY)) {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_SEEDING);
|
|
}
|
|
|
|
|
|
//
|
|
// Table context?
|
|
//
|
|
TableCtx = DbsCreateTableContext(ConfigTablex);
|
|
|
|
//
|
|
// Submitted to the db command server
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_CREATE_REPLICA_SET_MEMBER, // CmdRequest,
|
|
TableCtx, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Submit DB CMD_CREATE_REPLICA_SET_MEMBER");
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Create Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Create Replica DB Command failed", WStatus);
|
|
|
|
//
|
|
// Post the failure in the event log.
|
|
//
|
|
WStatusUStr = FrsAtoW(ErrLabelW32(WStatus));
|
|
FStatusUStr = FrsAtoW(ErrLabelFrs(Replica->FStatus));
|
|
|
|
EPRINT8(EVENT_FRS_REPLICA_SET_CREATE_FAIL,
|
|
Replica->SetName->Name,
|
|
ComputerDnsName,
|
|
Replica->MemberName->Name,
|
|
Replica->Root,
|
|
Replica->Stage,
|
|
JetPath,
|
|
WStatusUStr,
|
|
FStatusUStr);
|
|
|
|
FrsFree(WStatusUStr);
|
|
FrsFree(FStatusUStr);
|
|
|
|
goto out;
|
|
}
|
|
|
|
//
|
|
// Post the success in the event log.
|
|
//
|
|
EPRINT6(EVENT_FRS_REPLICA_SET_CREATE_OK,
|
|
Replica->SetName->Name,
|
|
ComputerDnsName,
|
|
Replica->MemberName->Name,
|
|
Replica->Root,
|
|
Replica->Stage,
|
|
JetPath);
|
|
|
|
//
|
|
// Increment the Replica Sets Created Counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, RSCreated, 1);
|
|
|
|
|
|
InWStr = FrsGetResourceStr(IDS_INBOUND);
|
|
OutWStr = FrsGetResourceStr(IDS_OUTBOUND);
|
|
i = 0;
|
|
|
|
//
|
|
// Create the cxtions
|
|
//
|
|
Key = NULL;
|
|
while (Cxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
Key = NULL;
|
|
|
|
//
|
|
// Skip inconsistent cxtions and journal cxtions
|
|
//
|
|
if ((!Cxtion->JrnlCxtion) &&
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_CONSISTENT)) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, submit cmd CMD_OUTLOG_ADD_NEW_PARTNER");
|
|
FStatus = OutLogSubmit(Replica, Cxtion, CMD_OUTLOG_ADD_NEW_PARTNER);
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, FStatus, "F, CMD_OUTLOG_ADD_NEW_PARTNER return");
|
|
//
|
|
// Build a string for the event log.
|
|
//
|
|
if (Cxtion->PartnerDnsName != NULL) {
|
|
_snwprintf(CxtionStr, CXTION_STR_MAX, L"%ws \"%ws\"",
|
|
Cxtion->Inbound ? InWStr : OutWStr,
|
|
Cxtion->PartnerDnsName);
|
|
|
|
CxtionStr[CXTION_STR_MAX-1] = UNICODE_NULL;
|
|
|
|
WStrArray[i++] = FrsWcsDup(CxtionStr);
|
|
|
|
if (i == CXTION_EVENT_RPT_MAX) {
|
|
|
|
EPRINT9(EVENT_FRS_REPLICA_SET_CXTIONS, Replica->SetName->Name,
|
|
WStrArray[0], WStrArray[1], WStrArray[2], WStrArray[3],
|
|
WStrArray[4], WStrArray[5], WStrArray[6], WStrArray[7]);
|
|
|
|
for (i = 0; i < CXTION_EVENT_RPT_MAX; i++) {
|
|
WStrArray[i] = FrsFree(WStrArray[i]);
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Done with this cxtion
|
|
//
|
|
GTabDelete(Replica->Cxtions, Cxtion->Name->Guid, NULL, FrsFreeType);
|
|
}
|
|
|
|
if (i > 0) {
|
|
//
|
|
// print any left over.
|
|
//
|
|
Rest = i;
|
|
for (i = Rest; i < CXTION_EVENT_RPT_MAX; i++) {
|
|
WStrArray[i] = L" ";
|
|
}
|
|
|
|
EPRINT9(EVENT_FRS_REPLICA_SET_CXTIONS, Replica->SetName->Name,
|
|
WStrArray[0], WStrArray[1], WStrArray[2], WStrArray[3],
|
|
WStrArray[4], WStrArray[5], WStrArray[6], WStrArray[7]);
|
|
|
|
for (i = 0; i < Rest; i++) {
|
|
WStrArray[i] = FrsFree(WStrArray[i]);
|
|
}
|
|
}
|
|
|
|
FrsFree(InWStr);
|
|
FrsFree(OutWStr);
|
|
|
|
//
|
|
// Set the OID data structure which is a part of the
|
|
// counter data structure stored in the hash table
|
|
// Add ReplicaSet Instance to the registry
|
|
//
|
|
if (Replica->Root != NULL) {
|
|
DPRINT(5, "PERFMON:Adding Set:REPLICA.C:1\n");
|
|
AddPerfmonInstance(REPLICASET, Replica->PerfRepSetData, Replica->Root);
|
|
}
|
|
|
|
//
|
|
// Add to the replica tables (by guid and by number)
|
|
//
|
|
Replica->Queue = FrsAlloc(sizeof(FRS_QUEUE));
|
|
FrsInitializeQueue(Replica->Queue, &ReplicaCmdServer.Control);
|
|
|
|
GTabInsertEntry(ReplicasByGuid, Replica, Replica->ReplicaName->Guid, NULL);
|
|
GTabInsertEntry(ReplicasByNumber, Replica, &Replica->ReplicaNumber, NULL);
|
|
|
|
//
|
|
// WARNING: NO Failure return is allowed after this point because the
|
|
// Replica struct is pointed at by a number of other data structs like the
|
|
// Three above. A Failure return here causes our caller to free the replica
|
|
// struct but of course it does that without first pulling it out of any of
|
|
// the above or calling the DB Service to tell it that the replica struct is
|
|
// going away This is unfortunate but its 7/8/99 and too late to clean this up.
|
|
//
|
|
|
|
//
|
|
// Set registry value "FilesNotToBackup"
|
|
//
|
|
CfgFilesNotToBackup(ReplicasByGuid);
|
|
|
|
//
|
|
// Open the replica set
|
|
//
|
|
RcsOpenReplicaSetMember(Replica);
|
|
|
|
//
|
|
// See comment above.
|
|
//
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
|
|
//if (Replica->FStatus != FrsErrorSuccess) {
|
|
// goto out;
|
|
//}
|
|
|
|
|
|
//
|
|
// Insert replica information into the registry
|
|
//
|
|
RcsReplicaSetRegistry(Replica);
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
if (TableCtx) {
|
|
DbsFreeTableContext(TableCtx, 0);
|
|
}
|
|
if (!FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
|
|
return Replica->FStatus;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsReplicaHasExpired(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Has this replica's tombstone expired?
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
TRUE - Tombstone has expired.
|
|
FALSE - Not tombstoned or tombstone has not expired.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsReplicaHasExpired:"
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
|
|
//
|
|
// Is it tombstoned?
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
GetSystemTimeAsFileTime(&FileTime);
|
|
COPY_TIME(&Now, &FileTime);
|
|
//
|
|
// Has it expired?
|
|
//
|
|
if (Now > Replica->MembershipExpires) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Replica has expired");
|
|
return TRUE;
|
|
}
|
|
}
|
|
//
|
|
// Not expired
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
PREPLICA
|
|
RcsFindSysVolByType(
|
|
IN DWORD ReplicaSetType
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Find the sysvol with the indicated type.
|
|
|
|
Arguments:
|
|
ReplicaSetType
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindSysVolByType:"
|
|
PREPLICA Replica;
|
|
PVOID Key;
|
|
|
|
//
|
|
// Find a match for the sysvol by name
|
|
//
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
if (!FRS_RSTYPE_IS_SYSVOL(Replica->ReplicaSetType)) {
|
|
continue;
|
|
}
|
|
//
|
|
// SysVol types match
|
|
//
|
|
if (Replica->ReplicaSetType == ReplicaSetType) {
|
|
//
|
|
// Don't return expired replicas
|
|
//
|
|
if (RcsReplicaHasExpired(Replica)) {
|
|
DPRINT2(4, ":S: %ws\\%ws: IGNORING, tombstoned expired.\n",
|
|
Replica->SetName->Name, Replica->MemberName->Name);
|
|
continue;
|
|
}
|
|
return Replica;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
PREPLICA
|
|
RcsFindSysVolByName(
|
|
IN PWCHAR ReplicaSetName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
The replica set from the Ds could not be located by its Ds-object guid.
|
|
This may be a sysvol that has been seeded but hasn't picked up its
|
|
guids from its Ds objects. Try to find the sysvol by name.
|
|
|
|
Arguments:
|
|
ReplicaSetName
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindSysVolByName:"
|
|
PREPLICA Replica;
|
|
PVOID Key;
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
|
|
//
|
|
// Find a match for the sysvol by name
|
|
//
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
if (!FRS_RSTYPE_IS_SYSVOL(Replica->ReplicaSetType)) {
|
|
continue;
|
|
}
|
|
if (WSTR_EQ(ReplicaSetName, Replica->ReplicaName->Name)) {
|
|
//
|
|
// Don't return expired replicas
|
|
//
|
|
if (RcsReplicaHasExpired(Replica)) {
|
|
DPRINT2(4, ":S: %ws\\%ws: IGNORING, tombstoned expired.\n",
|
|
Replica->SetName->Name, Replica->MemberName->Name);
|
|
continue;
|
|
}
|
|
return Replica;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PREPLICA
|
|
RcsFindNextReplica(
|
|
IN PVOID *Key
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Return the next replica in the active replication subsystem.
|
|
|
|
Arguments:
|
|
Key
|
|
|
|
Return Value:
|
|
Replica or NULL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFindNextReplica:"
|
|
//
|
|
// Next replica
|
|
//
|
|
return GTabNextDatum(ReplicasByGuid, Key);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsMergeReplicaFromDs(
|
|
IN PREPLICA DsReplica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Merge this replica from the DS with the active replicas.
|
|
|
|
Arguments:
|
|
DsReplica
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsMergeReplicaFromDs:"
|
|
PVOID Key;
|
|
PREPLICA Replica;
|
|
|
|
DPRINT(4, ":S: Merge Replica from the DS\n");
|
|
FRS_PRINT_TYPE(4, DsReplica);
|
|
|
|
//
|
|
// If the replica is not in the table, create it
|
|
//
|
|
Replica = GTabLookup(ReplicasByGuid, DsReplica->ReplicaName->Guid, NULL);
|
|
|
|
if (Replica && REPLICA_STATE_NEEDS_RESTORE(Replica->ServiceState)) {
|
|
//
|
|
// This replica is on the fault list and will be deleted at the end
|
|
// of poll when we process the fault list. We should not try to
|
|
// do anything with this replica. Just remove it from ReplicasNotInTheDs
|
|
// table and free the DsReplica structure.
|
|
//
|
|
GTabDelete(ReplicasNotInTheDs, Replica->ReplicaName->Guid, NULL, NULL);
|
|
FrsFreeType(DsReplica);
|
|
return;
|
|
}
|
|
//
|
|
// Might be a sysvol that has been seeded but hasn't picked up
|
|
// its final guids from its Ds objects. Try to find it by name.
|
|
//
|
|
if (!Replica &&
|
|
FRS_RSTYPE_IS_SYSVOL(DsReplica->ReplicaSetType)) {
|
|
//
|
|
// Pretend there isn't a match if the replica has already
|
|
// been merged with the info in the DS. We only want to merge once.
|
|
// We deduce that the merge hasn't occured if the replica set
|
|
// guid and the root guid are the same. They should be different
|
|
// since the replica set guid wasn't available when the root
|
|
// guid was created.
|
|
//
|
|
// Otherwise, we could end up with a sysvol replica that
|
|
// claims its root path is an old sysvol root instead of a new
|
|
// sysvol root (assuming ntfrsupg was run twice).
|
|
//
|
|
// The old sysvol will be tombstoned and will eventually
|
|
// be deleted.
|
|
//
|
|
// Note: seeding during dcpromo is now broken since root guid
|
|
// is created when set is created in the db and so
|
|
// never matches the set guid. fix if seeding during
|
|
// dcpromo is resurrected (with CnfFlag)
|
|
//
|
|
Replica = RcsFindSysVolByName(DsReplica->ReplicaName->Name);
|
|
if (Replica && !GUIDS_EQUAL(Replica->SetName->Guid,
|
|
Replica->ReplicaRootGuid)) {
|
|
Replica = NULL;
|
|
}
|
|
}
|
|
if (Replica) {
|
|
//
|
|
// Replica still exists in the DS; don't delete it
|
|
//
|
|
GTabDelete(ReplicasNotInTheDs, Replica->ReplicaName->Guid, NULL, NULL);
|
|
//
|
|
// Tell the replica to merge with the information from the DS
|
|
// and to retry any failed or new startup operations like
|
|
// starting the journal and joining new connections.
|
|
//
|
|
if (DsReplica->Consistent) {
|
|
(VOID) RcsSubmitReplicaSync(Replica, DsReplica, NULL, CMD_START);
|
|
} else {
|
|
//
|
|
// WARN: it looks like it gets freed here but still lives in the
|
|
// ReplicasByGuid table --- AV later if table enumed.
|
|
// RcsBeginMergeWithDs() does an enum of this table.
|
|
//
|
|
FrsFreeType(DsReplica);
|
|
}
|
|
} else {
|
|
//
|
|
// Insert the replica into the database and add it to the table
|
|
// of active replicas. Comm packets will continue to be discarded
|
|
// because the replica is not yet "accepting" remote change orders
|
|
//
|
|
// Replica sets for sysvols must exist in the database prior to
|
|
// the entries in the Ds. If the opposite is true then the entry
|
|
// in the Ds is bogus; probably the result of the Ds polling thread
|
|
// being unabled to delete the Ds objects after a dcdemote. In
|
|
// any case, ignore the Ds.
|
|
//
|
|
if (DsReplica->Consistent &&
|
|
FRS_SUCCESS(RcsCreateReplicaSetMember(DsReplica))) {
|
|
RcsSubmitReplicaSync(DsReplica, NULL, NULL, CMD_START);
|
|
} else {
|
|
//
|
|
// WARN: The above could happen here too if Consistent is false
|
|
// and DsReplica is in a table.
|
|
// Also happens if RcsCreateReplicaSetMember() fails.
|
|
// since it can fail after DsReplica is added to.
|
|
// the ReplicasByGuid and ReplicasByNumber tables. Sigh.
|
|
//
|
|
FrsFreeType(DsReplica);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsMergeReplicaGName(
|
|
IN PREPLICA Replica,
|
|
IN PWCHAR Tag,
|
|
IN PGNAME GName,
|
|
IN PGNAME NewGName
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Update the Replica with new information from NewReplica.
|
|
|
|
Arguments:
|
|
Replica
|
|
GName
|
|
NewGName
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsMergeReplicaGName:"
|
|
|
|
//
|
|
// Name
|
|
//
|
|
if (WSTR_NE(GName->Name, NewGName->Name)) {
|
|
DPRINT5(0, "++ %ws\\%ws - Changing %ws name from %ws to %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name, Tag,
|
|
GName->Name, NewGName->Name);
|
|
|
|
FrsDsSwapPtrs(&GName->Name, &NewGName->Name);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
//
|
|
// Guid
|
|
//
|
|
if (!GUIDS_EQUAL(GName->Guid, NewGName->Guid)) {
|
|
DPRINT3(0, "++ %ws\\%ws - Changing guid for %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name, Tag);
|
|
|
|
FrsDsSwapPtrs(&GName->Guid, &NewGName->Guid);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsMergeReplicaFields(
|
|
IN PREPLICA Replica,
|
|
IN PREPLICA NewReplica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Update the Replica with new information from NewReplica.
|
|
|
|
Arguments:
|
|
Replica - active replica
|
|
NewReplica
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsMergeReplicaFields:"
|
|
BOOL IsSysVol;
|
|
PWCHAR DirFilterList;
|
|
PWCHAR TmpList;
|
|
UNICODE_STRING TempUStr;
|
|
|
|
if (NewReplica == NULL) {
|
|
return;
|
|
}
|
|
//
|
|
// CHECK FIELDS THAT CAN'T CHANGE
|
|
//
|
|
|
|
//
|
|
// Replica type
|
|
//
|
|
if (Replica->ReplicaSetType != NewReplica->ReplicaSetType) {
|
|
DPRINT4(0, "++ ERROR - %ws\\%ws - Changing replica type from %d to %d is not allowed.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name,
|
|
Replica->ReplicaSetType, NewReplica->ReplicaSetType);
|
|
NewReplica->Consistent = FALSE;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// ReplicaName Guid
|
|
//
|
|
IsSysVol = FRS_RSTYPE_IS_SYSVOL(Replica->ReplicaSetType);
|
|
if (!GUIDS_EQUAL(Replica->ReplicaName->Guid, NewReplica->ReplicaName->Guid)) {
|
|
if (!IsSysVol) {
|
|
DPRINT2(0, "++ ERROR - %ws\\%ws - Changing replica guid is not allowed.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set Guid
|
|
//
|
|
if (!GUIDS_EQUAL(Replica->SetName->Guid, NewReplica->SetName->Guid)) {
|
|
if (!IsSysVol) {
|
|
DPRINT2(0, "++ ERROR - %ws\\%ws - Changing set guid is not allowed.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Root Guid
|
|
// Cannot be changed because it is created whenever the set
|
|
// is created in the DB. The root guid from the DS is always
|
|
// ignored.
|
|
//
|
|
// if (!GUIDS_EQUAL(Replica->ReplicaRootGuid, NewReplica->ReplicaRootGuid)) {
|
|
// if (!IsSysVol) {
|
|
// DPRINT2(0, "++ ERROR - %ws\\%ws - Changing root guid "
|
|
// "is not allowed.\n",
|
|
// Replica->ReplicaName->Name,
|
|
// Replica->MemberName->Name);
|
|
// return;
|
|
// }
|
|
// }
|
|
|
|
//
|
|
// Stage Path.
|
|
//
|
|
if (WSTR_NE(Replica->Stage, NewReplica->Stage)) {
|
|
|
|
DPRINT3(3, "The staging path for the replica set (%ws) has changed from (%ws) to (%ws).\n",
|
|
Replica->SetName->Name, Replica->Stage, NewReplica->Stage);
|
|
|
|
EPRINT3(EVENT_FRS_STAGE_HAS_CHANGED, Replica->SetName->Name, Replica->Stage,
|
|
NewReplica->Stage);
|
|
|
|
FrsFree(Replica->NewStage);
|
|
Replica->NewStage = FrsWcsDup(NewReplica->Stage);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
|
|
//
|
|
// Updating the replica's guid is tricky since the guid is
|
|
// used by the other subsystems, like RPC, to find a replica
|
|
// set. Both the old and new guid are temporarily in the
|
|
// lookup table. Once the guid is updated, the old entry is
|
|
// deleted.
|
|
//
|
|
if (!GUIDS_EQUAL(Replica->ReplicaName->Guid, NewReplica->ReplicaName->Guid)) {
|
|
DPRINT2(0, "++ %ws\\%ws - Changing guid for Replica.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name);
|
|
|
|
GTabInsertEntry(ReplicasByGuid, Replica, NewReplica->ReplicaName->Guid, NULL);
|
|
FrsDsSwapPtrs(&Replica->ReplicaName->Guid, &NewReplica->ReplicaName->Guid);
|
|
|
|
GTabDelete(ReplicasByGuid, NewReplica->ReplicaName->Guid, NULL, NULL);
|
|
COPY_GUID(NewReplica->ReplicaName->Guid, Replica->ReplicaName->Guid);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
|
|
//
|
|
// FIELDS THAT CAN CHANGE
|
|
//
|
|
|
|
//
|
|
// FrsRsoFlags
|
|
//
|
|
Replica->FrsRsoFlags = NewReplica->FrsRsoFlags;
|
|
|
|
//
|
|
// ReplicaName (note that the guid was handled above)
|
|
//
|
|
RcsMergeReplicaGName(Replica, L"Replica", Replica->ReplicaName, NewReplica->ReplicaName);
|
|
|
|
//
|
|
// MemberName
|
|
//
|
|
RcsMergeReplicaGName(Replica, L"Member", Replica->MemberName, NewReplica->MemberName);
|
|
//
|
|
// SetName
|
|
//
|
|
RcsMergeReplicaGName(Replica, L"Set", Replica->SetName, NewReplica->SetName);
|
|
//
|
|
// Schedule
|
|
//
|
|
if (Replica->Schedule || NewReplica->Schedule) {
|
|
if ((Replica->Schedule && !NewReplica->Schedule) ||
|
|
(!Replica->Schedule && NewReplica->Schedule) ||
|
|
(Replica->Schedule->Size != NewReplica->Schedule->Size) ||
|
|
(memcmp(Replica->Schedule,
|
|
NewReplica->Schedule,
|
|
Replica->Schedule->Size))) {
|
|
DPRINT2(0, "++ %ws\\%ws - Changing replica schedule.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name);
|
|
|
|
FrsDsSwapPtrs(&Replica->Schedule, &NewReplica->Schedule);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// File Exclusion Filter
|
|
//
|
|
if (Replica->FileFilterList || NewReplica->FileFilterList) {
|
|
if ((Replica->FileFilterList && !NewReplica->FileFilterList) ||
|
|
(!Replica->FileFilterList && NewReplica->FileFilterList) ||
|
|
WSTR_NE(Replica->FileFilterList, NewReplica->FileFilterList)) {
|
|
|
|
DPRINT4(0, "++ %ws\\%ws - Changing file filter from %ws to %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name,
|
|
Replica->FileFilterList, NewReplica->FileFilterList);
|
|
|
|
FrsDsSwapPtrs(&Replica->FileFilterList, &NewReplica->FileFilterList);
|
|
if (!Replica->FileFilterList) {
|
|
Replica->FileFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
NULL,
|
|
RegistryFileExclFilterList,
|
|
DEFAULT_FILE_FILTER_LIST);
|
|
}
|
|
RtlInitUnicodeString(&TempUStr, Replica->FileFilterList);
|
|
LOCK_REPLICA(Replica);
|
|
FrsLoadNameFilter(&TempUStr, &Replica->FileNameFilterHead);
|
|
UNLOCK_REPLICA(Replica);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// File Inclusion Filter (Registry only)
|
|
//
|
|
if (Replica->FileInclFilterList || NewReplica->FileInclFilterList) {
|
|
if ((Replica->FileInclFilterList && !NewReplica->FileInclFilterList) ||
|
|
(!Replica->FileInclFilterList && NewReplica->FileInclFilterList) ||
|
|
WSTR_NE(Replica->FileInclFilterList, NewReplica->FileInclFilterList)) {
|
|
|
|
DPRINT4(0, "++ %ws\\%ws - Changing file Inclusion filter from %ws to %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name,
|
|
Replica->FileInclFilterList, NewReplica->FileInclFilterList);
|
|
|
|
FrsDsSwapPtrs(&Replica->FileInclFilterList, &NewReplica->FileInclFilterList);
|
|
|
|
RtlInitUnicodeString(&TempUStr, Replica->FileInclFilterList);
|
|
LOCK_REPLICA(Replica);
|
|
FrsLoadNameFilter(&TempUStr, &Replica->FileNameInclFilterHead);
|
|
UNLOCK_REPLICA(Replica);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Directory Filter
|
|
//
|
|
if (Replica->DirFilterList || NewReplica->DirFilterList) {
|
|
if ((Replica->DirFilterList && !NewReplica->DirFilterList) ||
|
|
(!Replica->DirFilterList && NewReplica->DirFilterList) ||
|
|
WSTR_NE(Replica->DirFilterList, NewReplica->DirFilterList)) {
|
|
|
|
DPRINT4(0, "++ %ws\\%ws - Changing dir filter from %ws to %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name,
|
|
Replica->DirFilterList, NewReplica->DirFilterList);
|
|
|
|
FrsDsSwapPtrs(&Replica->DirFilterList, &NewReplica->DirFilterList);
|
|
|
|
if (!Replica->DirFilterList) {
|
|
Replica->DirFilterList = FRS_DS_COMPOSE_FILTER_LIST(
|
|
NULL,
|
|
RegistryDirExclFilterList,
|
|
DEFAULT_DIR_FILTER_LIST);
|
|
}
|
|
|
|
//
|
|
// Add the pre-install dir, the pre-existing dir and the
|
|
// replication suppression prefix name to the dir filter list.
|
|
//
|
|
DirFilterList = FrsWcsCat3(NTFRS_PREINSTALL_DIRECTORY,
|
|
L",",
|
|
Replica->DirFilterList);
|
|
|
|
TmpList = FrsWcsCat3(NTFRS_PREEXISTING_DIRECTORY, L",", DirFilterList);
|
|
FrsFree(DirFilterList);
|
|
DirFilterList = TmpList;
|
|
|
|
#if 0
|
|
//
|
|
// This workaround did not solve the DFS dir create problem because the
|
|
// later rename of the dir to the final target name is treated like
|
|
// a movein operation so the dir replicates which was what we were trying
|
|
// to avoid since that led to name morph collisions on other DFS alternates
|
|
// which were doing the same thing.
|
|
//
|
|
TmpList = FrsWcsCat3(NTFRS_REPL_SUPPRESS_PREFIX, L"*,", DirFilterList);
|
|
FrsFree(DirFilterList);
|
|
DirFilterList = TmpList;
|
|
#endif
|
|
|
|
DPRINT3(0, "++ %ws\\%ws - New dir filter: %ws\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name, DirFilterList);
|
|
|
|
RtlInitUnicodeString(&TempUStr, DirFilterList);
|
|
|
|
LOCK_REPLICA(Replica);
|
|
FrsLoadNameFilter(&TempUStr, &Replica->DirNameFilterHead);
|
|
UNLOCK_REPLICA(Replica);
|
|
|
|
FrsFree(DirFilterList);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Directory Inclusion Filter (Registry only)
|
|
//
|
|
if (Replica->DirInclFilterList || NewReplica->DirInclFilterList) {
|
|
if ((Replica->DirInclFilterList && !NewReplica->DirInclFilterList) ||
|
|
(!Replica->DirInclFilterList && NewReplica->DirInclFilterList) ||
|
|
WSTR_NE(Replica->DirInclFilterList, NewReplica->DirInclFilterList)) {
|
|
|
|
DPRINT4(0, "++ %ws\\%ws - Changing dir inclusion filter from %ws to %ws.\n",
|
|
Replica->ReplicaName->Name, Replica->MemberName->Name,
|
|
Replica->DirInclFilterList, NewReplica->DirInclFilterList);
|
|
|
|
FrsDsSwapPtrs(&Replica->DirInclFilterList, &NewReplica->DirInclFilterList);
|
|
|
|
RtlInitUnicodeString(&TempUStr, Replica->DirInclFilterList);
|
|
LOCK_REPLICA(Replica);
|
|
FrsLoadNameFilter(&TempUStr, &Replica->DirNameInclFilterHead);
|
|
UNLOCK_REPLICA(Replica);
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The Replica->CnfFlags are only valid when the replica is created.
|
|
// They are ignored thereafter.
|
|
//
|
|
}
|
|
|
|
|
|
PCXTION
|
|
RcsCreateSeedingCxtion(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION SeedingCxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Create a seeding cxtion if needed.
|
|
|
|
This function may open and read the registry.
|
|
This function may RPC to another computer.
|
|
|
|
Arguments:
|
|
Replica - active replica
|
|
SeedingCxtion - seeding cxtion
|
|
|
|
Return Value:
|
|
NULL - no seeding cxtion needed (or possible)
|
|
Otherwise, a seeding cxtion with a matching cxtion on
|
|
another computer (specified by the registry).
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCreateSeedingCxtion:"
|
|
DWORD WStatus;
|
|
PVOID Key;
|
|
ULONG ParentAuthLevel;
|
|
PWCHAR KeyName = NULL;
|
|
PWCHAR ParentComputer = NULL;
|
|
PWCHAR ParentPrincName = NULL;
|
|
PWCHAR ParentNT4Name = NULL;
|
|
handle_t ParentHandle = NULL;
|
|
PWCHAR LocalServerName = NULL;
|
|
GUID SeedingGuid;
|
|
GUID ParentGuid;
|
|
PSID pSid = NULL;
|
|
PWCHAR SidString = NULL;
|
|
PWCHAR DomainName = NULL;
|
|
DWORD ccDomainName = 0;
|
|
DWORD cbSid = 0;
|
|
SID_NAME_USE SidUse = SidTypeInvalid;
|
|
extern ULONGLONG ActiveChange;
|
|
|
|
|
|
//
|
|
// This operation is non-critical. The sysvol will eventually seed
|
|
// after the FRS and KCC information converges on the appropriate
|
|
// DCs. Hence, trap exceptions and ignore them.
|
|
//
|
|
try {
|
|
|
|
//
|
|
// Already created. Seeding during dcpromo.
|
|
//
|
|
if (SeedingCxtion) {
|
|
goto CLEANUP_OK;
|
|
}
|
|
|
|
//
|
|
// No seeding cxtion is needed
|
|
//
|
|
if (!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING)) {
|
|
goto CLEANUP_OK;
|
|
}
|
|
|
|
//
|
|
// Do not create another seeding cxtion. The sysvol will eventually
|
|
// be seeded from a KCC generated cxtion (from the DS). But it may
|
|
// take awhile because both the FRS objects and the KCC must converge
|
|
// after the KCC has run. And then the FRS service has to notice the
|
|
// convergence.
|
|
//
|
|
Key = NULL;
|
|
while (SeedingCxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
if (CxtionFlagIs(SeedingCxtion, CXTION_FLAGS_VOLATILE)) {
|
|
break;
|
|
}
|
|
}
|
|
if (SeedingCxtion) {
|
|
SeedingCxtion = NULL;
|
|
goto CLEANUP_OK;
|
|
}
|
|
//
|
|
// Retrieve the parent computer's name
|
|
//
|
|
WStatus = CfgRegReadString(FKC_SYSVOL_SEEDING_N_PARENT,
|
|
Replica->ReplicaName->Name,
|
|
0,
|
|
&ParentComputer);
|
|
|
|
CLEANUP1_WS(0, ":X: ERROR - no parent computer for %ws :",
|
|
Replica->ReplicaName->Name, WStatus, CLEANUP_OK);
|
|
|
|
//
|
|
// Bind to the parent
|
|
//
|
|
WStatus = NtFrsApi_Rpc_BindEx(ParentComputer,
|
|
&ParentPrincName,
|
|
&ParentHandle,
|
|
&ParentAuthLevel);
|
|
CLEANUP_WS(0, "ERROR - binding", WStatus, CLEANUP);
|
|
|
|
DPRINT3(4, ":X: Seeding cxtion has bound to %ws (princ name is %ws) auth level %d\n",
|
|
ParentComputer, ParentPrincName, ParentAuthLevel);
|
|
|
|
//
|
|
// Placeholder guid for the cxtion
|
|
// Updated once the Ds objects are created
|
|
//
|
|
FrsUuidCreate(&SeedingGuid);
|
|
|
|
//
|
|
// Get local computer's NT4 style name. Send ServerPrincName if
|
|
// conversion fails.
|
|
//
|
|
|
|
LocalServerName = FrsDsConvertName(DsHandle, ServerPrincName, DS_USER_PRINCIPAL_NAME, NULL, DS_NT4_ACCOUNT_NAME);
|
|
|
|
if (LocalServerName == NULL) {
|
|
LocalServerName = FrsWcsDup(ServerPrincName);
|
|
}
|
|
|
|
//
|
|
// Create volatile cxtion on the parent
|
|
//
|
|
try {
|
|
WStatus = FrsRpcStartPromotionParent(ParentHandle,
|
|
NULL,
|
|
NULL,
|
|
Replica->SetName->Name,
|
|
NTFRSAPI_REPLICA_SET_TYPE_DOMAIN,
|
|
ParentComputer,
|
|
// Change the following to ComputerName for old DNS behavior
|
|
ComputerDnsName,
|
|
LocalServerName, // NT$ style name or user principal name.
|
|
ParentAuthLevel,
|
|
sizeof(GUID),
|
|
(PUCHAR)&SeedingGuid,
|
|
(PUCHAR)Replica->MemberName->Guid,
|
|
(PUCHAR)&ParentGuid);
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
GET_EXCEPTION_CODE(WStatus);
|
|
}
|
|
CLEANUP1_WS(0, ":X: ERROR - Can't create seeding cxtion on parent %ws;",
|
|
ParentComputer, WStatus, CLEANUP);
|
|
|
|
DPRINT3(4, ":X: Seeding cxtion has been created on %ws (princ name is %ws) auth level %d\n",
|
|
ParentComputer, ParentPrincName, ParentAuthLevel);
|
|
|
|
|
|
ParentNT4Name = FrsDsConvertName(DsHandle, ParentPrincName, DS_USER_PRINCIPAL_NAME, NULL, DS_NT4_ACCOUNT_NAME);
|
|
|
|
if (ParentNT4Name == NULL) {
|
|
ParentNT4Name = FrsWcsDup(ParentPrincName);
|
|
}
|
|
|
|
if(!LookupAccountName(NULL,
|
|
ParentNT4Name,
|
|
NULL, // NULL means the function will give us the required size
|
|
&cbSid,
|
|
NULL, // NULL means the function will give us the required size
|
|
&ccDomainName,
|
|
&SidUse
|
|
)) {
|
|
//
|
|
// Couldn't find parent's sid!!
|
|
//
|
|
|
|
WStatus = GetLastError();
|
|
|
|
if(WStatus != ERROR_INSUFFICIENT_BUFFER) {
|
|
DPRINT2(0, "++ ERROR - Unable to get SID for %ws. WStatus = 0x%08x\n", ParentNT4Name, WStatus);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
WStatus = ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Allocate the needed memory and make the call again.
|
|
//
|
|
|
|
DomainName = FrsAlloc(sizeof(WCHAR) * (ccDomainName + 1));
|
|
pSid = FrsAlloc(cbSid);
|
|
|
|
if(!LookupAccountName(NULL,
|
|
ParentNT4Name,
|
|
pSid,
|
|
&cbSid,
|
|
DomainName,
|
|
&ccDomainName,
|
|
&SidUse
|
|
)) {
|
|
//
|
|
// Couldn't find parent's sid!!
|
|
//
|
|
|
|
WStatus = GetLastError();
|
|
DPRINT2(0, "++ ERROR - Unable to get SID for %ws. WStatus = 0x%08x\n", ParentNT4Name, WStatus);
|
|
goto CLEANUP;
|
|
}
|
|
/*
|
|
if(SidUse != SidTypeComputer) {
|
|
//
|
|
// This Sid is the wrong type.
|
|
//
|
|
DPRINT2(4, "++ WARNING - SID for %ws is not SidTypeComputer. SidUse = %d\n", ParentNT4Name, SidUse);
|
|
|
|
}
|
|
*/
|
|
if(!ConvertSidToStringSid(pSid, &SidString)) {
|
|
|
|
WStatus = GetLastError();
|
|
DPRINT1(0, "++ ERROR - Unable to convert SID to string. WStatus = 0x%08x\n", WStatus);
|
|
goto CLEANUP;
|
|
|
|
}
|
|
|
|
//
|
|
// Create local seeding cxtion
|
|
//
|
|
SeedingCxtion = FrsAllocType(CXTION_TYPE);
|
|
SeedingCxtion->PartnerSid = FrsWcsDup(SidString);
|
|
LocalFree(SidString);
|
|
SeedingCxtion->Inbound = TRUE;
|
|
SetCxtionFlag(SeedingCxtion, CXTION_FLAGS_CONSISTENT |
|
|
CXTION_FLAGS_VOLATILE);
|
|
|
|
SeedingCxtion->Name = FrsBuildGName(FrsDupGuid(&SeedingGuid),
|
|
FrsWcsDup(ParentComputer));
|
|
|
|
SeedingCxtion->Partner = FrsBuildGName(FrsDupGuid(&ParentGuid),
|
|
FrsWcsDup(ParentComputer));
|
|
|
|
SeedingCxtion->PartnerDnsName = FrsWcsDup(ParentComputer);
|
|
SeedingCxtion->PartnerPrincName = FrsWcsDup(ParentNT4Name);
|
|
SeedingCxtion->PartSrvName = FrsWcsDup(ParentComputer);
|
|
SeedingCxtion->PartnerAuthLevel = ParentAuthLevel;
|
|
SetCxtionState(SeedingCxtion, CxtionStateUnjoined);
|
|
|
|
CLEANUP_OK:
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
CLEANUP:;
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
GET_EXCEPTION_CODE(WStatus);
|
|
DPRINT_WS(0, "ERROR - Exception", WStatus);
|
|
}
|
|
try {
|
|
if (ParentHandle) {
|
|
RpcBindingFree(&ParentHandle);
|
|
}
|
|
FrsFree(KeyName);
|
|
FrsFree(ParentComputer);
|
|
FrsFree(ParentPrincName);
|
|
FrsFree(ParentNT4Name);
|
|
FrsFree(LocalServerName);
|
|
FrsFree(pSid);
|
|
FrsFree(DomainName);
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
GET_EXCEPTION_CODE(WStatus);
|
|
DPRINT_WS(0, "ERROR - Cleanup Exception", WStatus);
|
|
}
|
|
//
|
|
// Retry later. ERROR_SUCCESS means don't retry; there may have
|
|
// been errors that retrying is unlikely to fix.
|
|
//
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
return SeedingCxtion;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSetCxtionSchedule(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN DWORD ScheduleIndex,
|
|
IN DWORD ScheduleShift
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Set or clear the CXTION_FLAGS_SCHEDULE_OFF bit in the cxtion
|
|
depending on whether the 15min interval identified by
|
|
(ScheduleIndex, ScheduleShift) is set.
|
|
|
|
Trigger schedules use the bit differently. A triggered cxtion
|
|
may actually be joined and running even if the schedule is OFF
|
|
because the current set of cos haven't been received/sent, yet.
|
|
|
|
So, be careful. The interpretation of CXTION_FLAGS_SCHEDULE_OFF
|
|
varies with the type of schedule.
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSetCxtionSchedule:"
|
|
ULONG i;
|
|
BOOL On;
|
|
PSCHEDULE Schedule;
|
|
|
|
//
|
|
// Set or clear CXTION_FLAGS_SCHEDULE_OFF
|
|
//
|
|
|
|
//
|
|
// Inbound connection AND In seeding state AND option is set to
|
|
// ignore schedule.
|
|
//
|
|
if (Cxtion->Inbound &&
|
|
((BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) ||
|
|
Replica->IsSeeding) &&
|
|
NTDSCONN_IGNORE_SCHEDULE(Cxtion->Options))
|
|
) {
|
|
|
|
Schedule = NULL;
|
|
|
|
//
|
|
// Outbound connection AND (In vvjoin mode OR never joined) AND option is set to
|
|
// ignore schedule.
|
|
//
|
|
} else if (!Cxtion->Inbound &&
|
|
((Cxtion->OLCtx == NULL) ||
|
|
WaitingToVVJoin(Cxtion->OLCtx) ||
|
|
InVVJoinMode(Cxtion->OLCtx)) &&
|
|
NTDSCONN_IGNORE_SCHEDULE(Cxtion->Options)
|
|
) {
|
|
|
|
Schedule = NULL;
|
|
|
|
} else {
|
|
//
|
|
// No schedule == always on
|
|
//
|
|
Schedule = Cxtion->Schedule;
|
|
if (!Schedule) {
|
|
Schedule = Replica->Schedule;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Is this 15minute interval ON or OFF?
|
|
//
|
|
On = TRUE;
|
|
if (Schedule) {
|
|
//
|
|
// Find the interval schedule
|
|
//
|
|
for (i = 0; i < Schedule->NumberOfSchedules; ++i) {
|
|
if (Schedule->Schedules[i].Type == SCHEDULE_INTERVAL) {
|
|
On = ((*(((PUCHAR)Schedule) +
|
|
Schedule->Schedules[i].Offset +
|
|
ScheduleIndex)) >> ScheduleShift) & 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (On) {
|
|
DPRINT1(0, ":X: %08x, schedule is on\n",
|
|
(Cxtion->Name && Cxtion->Name->Guid) ? Cxtion->Name->Guid->Data1 : 0);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_SCHEDULE_OFF);
|
|
|
|
} else {
|
|
DPRINT1(0, ":X: %08x, schedule is off.\n",
|
|
(Cxtion->Name && Cxtion->Name->Guid) ? Cxtion->Name->Guid->Data1 : 0);
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_SCHEDULE_OFF);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCheckCxtionSchedule(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Call the function to set or clear CXTION_FLAGS_SCHEDULE_OFF once
|
|
the current interval has been determined and the appropriate locks
|
|
acquired.
|
|
|
|
Called when a replica is "opened" (read from the DB) and when a
|
|
cxtion is created or its schedule altered in RcsMergeReplicaCxtions().
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckCxtionSchedule:"
|
|
DWORD ScheduleIndex;
|
|
DWORD ScheduleShift;
|
|
SYSTEMTIME SystemTime;
|
|
DWORD ScheduleHour;
|
|
BOOL On;
|
|
|
|
//
|
|
// Find the current interval
|
|
//
|
|
GetSystemTime(&SystemTime);
|
|
ScheduleHour = (SystemTime.wDayOfWeek * 24) + SystemTime.wHour;
|
|
ScheduleShift = SystemTime.wMinute / MINUTES_IN_INTERVAL;
|
|
ScheduleIndex = ScheduleHour;
|
|
FRS_ASSERT(ScheduleIndex < SCHEDULE_DATA_BYTES);
|
|
|
|
//
|
|
// Set or clear CXTION_FLAGS_SCHEDULE_OFF
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
RcsSetCxtionSchedule(Replica, Cxtion, ScheduleIndex, ScheduleShift);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCheckSchedules(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check all schedules every so often.
|
|
|
|
There are three schedule protocols:
|
|
Sysvol cxtion within a site
|
|
---------------------------
|
|
The cxtion is always on; any schedule is ignored. Normal join
|
|
retries apply. The normal join retry is to increase the timeout
|
|
by MinJoinTimeout every retry until the join succeeds. A
|
|
successful unjoin is followed by an immediate join.
|
|
Normal cxtion
|
|
-------------
|
|
The schedule is treated as a 15 minute interval stop/start
|
|
schedule. Normal join retries apply.
|
|
|
|
Sysvol cxtion between sites
|
|
---------------------------
|
|
The cxtion is treated as a 15-minute interval trigger schedule.
|
|
The downstream partner initiates the join. The upstream partner
|
|
ignores its schedule and responds to any request by the downstream
|
|
partner. The upstream partner unjoins the cxtion when the current
|
|
contents of the outlog at the time of join have been sent and
|
|
acked.
|
|
|
|
For interoperability, the minor comm version was bumped and the
|
|
outbound trigger cxtion is not unjoined when the end-of-join
|
|
control co is encountered if the partner is downrev.
|
|
|
|
I.e., B2 <-> B3 systems behave like B2 systems wrt trigger
|
|
scheduled cxtions.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCheckSchedules:"
|
|
PVOID ReplicaKey;
|
|
PVOID CxtionKey;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
DWORD ScheduleIndex;
|
|
DWORD ScheduleShift;
|
|
SYSTEMTIME BeginSystemTime;
|
|
SYSTEMTIME EndSystemTime;
|
|
DWORD ScheduleHour;
|
|
DWORD MilliSeconds;
|
|
BOOL On;
|
|
|
|
DPRINT1(5, ":X: Command check schedules %08x\n", Cmd);
|
|
|
|
//
|
|
// Loop if the interval changes during processing
|
|
//
|
|
AGAIN:
|
|
|
|
//
|
|
// Current 15min interval
|
|
//
|
|
GetSystemTime(&BeginSystemTime);
|
|
ScheduleHour = (BeginSystemTime.wDayOfWeek * 24) + BeginSystemTime.wHour;
|
|
ScheduleShift = BeginSystemTime.wMinute / MINUTES_IN_INTERVAL;
|
|
ScheduleIndex = ScheduleHour;
|
|
FRS_ASSERT(ScheduleIndex < SCHEDULE_DATA_BYTES);
|
|
|
|
//
|
|
// For each replica (while not shutting down)
|
|
//
|
|
LOCK_REPLICA_TABLE(ReplicasByGuid);
|
|
ReplicaKey = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Replica = GTabNextDatumNoLock(ReplicasByGuid, &ReplicaKey))) {
|
|
//
|
|
// Replica hasn't been started or is deleted; ignore for now
|
|
//
|
|
if (!Replica->IsAccepting) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Don't bother; replica has been deleted
|
|
//
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// For each cxtion (while not shutting down)
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
CxtionKey = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatumNoLock(Replica->Cxtions, &CxtionKey))) {
|
|
//
|
|
// Ignore the (local) journal connection.
|
|
//
|
|
if (Cxtion->JrnlCxtion) {
|
|
continue;
|
|
}
|
|
//
|
|
// Set the cxtion flags related to schedules
|
|
//
|
|
RcsSetCxtionSchedule(Replica, Cxtion, ScheduleIndex, ScheduleShift);
|
|
//
|
|
// Schedule is off. Unjoin the cxtion UNLESS this is
|
|
// a trigger schedule, in which case the cxtion will
|
|
// turn itself off once the current set of changes
|
|
// have been sent.
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_SCHEDULE_OFF)) {
|
|
if (!CxtionStateIs(Cxtion, CxtionStateUnjoined) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateDeleted) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateStart) &&
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|
RcsSubmitReplicaCxtion(Replica, Cxtion, CMD_UNJOIN);
|
|
}
|
|
//
|
|
// Schedule is on. Join the cxtion unless it is already
|
|
// joined or is controlled by a trigger schedule. The
|
|
// join is needed for a trigger scheduled cxtion in case
|
|
// the upstream partner has sent its cos and is now
|
|
// unjoined.
|
|
//
|
|
// Do not send a CMD_JOIN if this replica set is still seeding and
|
|
// connection is in INIT_SYNC state and marked paused.
|
|
// Initial sync command server will unpause it when it is ready to join.
|
|
//
|
|
} else {
|
|
if ((!CxtionStateIs(Cxtion, CxtionStateJoined) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) &&
|
|
(!CxtionFlagIs(Cxtion, CXTION_FLAGS_PAUSED) ||
|
|
(!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) ||
|
|
!CxtionFlagIs(Cxtion, CXTION_FLAGS_INIT_SYNC)))
|
|
) {
|
|
|
|
RcsSubmitReplicaCxtion(Replica, Cxtion, CMD_JOIN_CXTION);
|
|
}
|
|
}
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
UNLOCK_REPLICA_TABLE(ReplicasByGuid);
|
|
//
|
|
// Has the clock ticked into the next interval? If so, check again
|
|
//
|
|
GetSystemTime(&EndSystemTime);
|
|
if (EndSystemTime.wDayOfWeek != BeginSystemTime.wDayOfWeek ||
|
|
EndSystemTime.wHour != BeginSystemTime.wHour ||
|
|
(EndSystemTime.wMinute / MINUTES_IN_INTERVAL) != (BeginSystemTime.wMinute / MINUTES_IN_INTERVAL)) {
|
|
goto AGAIN;
|
|
}
|
|
//
|
|
// Time until the beginning of the next 15 minute interval
|
|
//
|
|
MilliSeconds = ((((EndSystemTime.wMinute + MINUTES_IN_INTERVAL)
|
|
/ MINUTES_IN_INTERVAL)
|
|
* MINUTES_IN_INTERVAL)
|
|
- EndSystemTime.wMinute);
|
|
DPRINT1(5, ":X: Check schedules in %d minutes\n", MilliSeconds);
|
|
MilliSeconds *= (60 * 1000);
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, MilliSeconds);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsMergeReplicaCxtions(
|
|
IN PREPLICA Replica,
|
|
IN PREPLICA NewReplica,
|
|
IN PCXTION VolatileCxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Update the Replica's cxtions with information from NewReplica.
|
|
|
|
Arguments:
|
|
Replica - active replica
|
|
NewReplica
|
|
VolatileCxtion -- Ptr to a cxtion struct created for temporary connections.
|
|
Hence the name Volatile. Currently used for Sysvol Seeding.
|
|
|
|
Return Value:
|
|
TRUE - replica set was altered
|
|
FALSE - replica is unaltered
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsMergeReplicaCxtions:"
|
|
ULONG FStatus;
|
|
PVOID Key;
|
|
PCXTION Cxtion;
|
|
PCXTION NextCxtion;
|
|
BOOL UpdateNeeded;
|
|
PCXTION NewCxtion;
|
|
|
|
//
|
|
// Note: Review - There needs to be a lock on the table or the cxtion
|
|
// since the outlog, chgorder, and replica code may
|
|
// reference the cxtion struct in parallel.
|
|
//
|
|
|
|
//
|
|
// The cxtions are enumerated when the replica is opened. If the
|
|
// replica isn't opened then we don't know the state of the existing
|
|
// cxtions.
|
|
//
|
|
if (!Replica->IsOpen) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The replica's config record can't be updated; ignore cxtion changes
|
|
//
|
|
if (Replica->NeedsUpdate) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Add the seeding cxtion
|
|
// The seeding cxtion is created by the caller when the
|
|
// sysvol is seeded after dcpromo. Otherwise, it is created
|
|
// at this time.
|
|
//
|
|
// WARN - The function may open and read the registry and may
|
|
// RPC to another computer.
|
|
//
|
|
VolatileCxtion = RcsCreateSeedingCxtion(Replica, VolatileCxtion);
|
|
if (VolatileCxtion) {
|
|
//
|
|
// Update the cxtion table
|
|
//
|
|
// *NOTE* The following call is synchronous. If we have the Cxtion
|
|
// table lock then a hang is possible.
|
|
//
|
|
FStatus = OutLogSubmit(Replica, VolatileCxtion, CMD_OUTLOG_ADD_NEW_PARTNER);
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// Update the incore cxtion table
|
|
//
|
|
GTabInsertEntry(Replica->Cxtions, VolatileCxtion, VolatileCxtion->Name->Guid, NULL);
|
|
OutLogInitPartner(Replica, VolatileCxtion);
|
|
} else {
|
|
//
|
|
// Chuck the cxtion. We will retry the create during the
|
|
// next ds polling cycle.
|
|
//
|
|
FrsFreeType(VolatileCxtion);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Done
|
|
//
|
|
if (!NewReplica) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check for altered or deleted cxtions
|
|
//
|
|
Key = NULL;
|
|
for (Cxtion = GTabNextDatum(Replica->Cxtions, &Key); Cxtion; Cxtion = NextCxtion) {
|
|
NextCxtion = GTabNextDatum(Replica->Cxtions, &Key);
|
|
//
|
|
// Ignore the (local) journal connection.
|
|
//
|
|
if (Cxtion->JrnlCxtion) {
|
|
continue;
|
|
}
|
|
//
|
|
// This cxtion is volatile, it will disappear after the sysvol is seeded.
|
|
// NOTE: the cxtion flags are not stored in the database so, at
|
|
// restart, this cxtion will not have the flag, CXTION_FLAGS_VOLATILE,
|
|
// set. The cxtion is deleted at restart because there isn't a
|
|
// corresponding DS entry.
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_VOLATILE)) {
|
|
continue;
|
|
}
|
|
|
|
NewCxtion = GTabLookup(NewReplica->Cxtions, Cxtion->Name->Guid, NULL);
|
|
|
|
//
|
|
// Delete the cxtion after unjoining. A Cxtion is deleted if it is not
|
|
// found in the DS. A connection can also be deleted if the outbound log cleanup
|
|
// decides that a particular outbound connection is holding up COs longer than
|
|
// the specified history time. (Outlog Change History In Minutes)
|
|
//
|
|
if (NewCxtion == NULL || (!Cxtion->Inbound && CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIM_OUTLOG))) {
|
|
//
|
|
// Unjoin the cxtion after setting the deferred delete bit.
|
|
// The deferred delete bit will keep the cxtion from re-joining
|
|
// before it can be deleted during the next pass of the ds
|
|
// polling thread. Warning - cxtion may reappear at any time!
|
|
//
|
|
if (!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
LOCK_CXTION_TABLE(Replica);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_DELETE);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
RcsForceUnjoin(Replica, Cxtion);
|
|
}
|
|
//
|
|
// If successfully unjoined; move to the deleted-cxtion table
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
RcsDeleteCxtionFromReplica(Replica, Cxtion);
|
|
}
|
|
continue;
|
|
//
|
|
// Deleted cxtion reappeared; reanimate it
|
|
//
|
|
// if the cxtion is deleted or marked for delete
|
|
// and new cxtion is consistent
|
|
// and partner's guid's match
|
|
// then reanimate
|
|
//
|
|
// Don't attempt to reanimate a cxtion if the info in the DS
|
|
// is inconsistent or the cxtion's partner has changed. The
|
|
// cxtion is deleted when its partner changes because the
|
|
// state kept per cxtion is partner specific.
|
|
//
|
|
} else if ((CxtionStateIs(Cxtion, CxtionStateDeleted) ||
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_DEFERRED_DELETE)) &&
|
|
CxtionFlagIs(NewCxtion, CXTION_FLAGS_CONSISTENT) &&
|
|
GUIDS_EQUAL(Cxtion->Partner->Guid, NewCxtion->Partner->Guid)) {
|
|
LOCK_CXTION_TABLE(Replica);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_DELETE);
|
|
if (CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
SetCxtionState(Cxtion, CxtionStateUnjoined);
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
|
|
//
|
|
// Check for altered fields; Ignore if not consistent
|
|
//
|
|
if (CxtionFlagIs(NewCxtion, CXTION_FLAGS_CONSISTENT)) {
|
|
//
|
|
// No changes, yet
|
|
//
|
|
UpdateNeeded = FALSE;
|
|
|
|
//
|
|
// Ignore all change is a field changed that isn't allowed
|
|
// to change.
|
|
//
|
|
|
|
//
|
|
// Cxtion Guid
|
|
//
|
|
if (!GUIDS_EQUAL(Cxtion->Name->Guid, NewCxtion->Name->Guid)) {
|
|
DPRINT2(0, ":X: ERROR - %ws\\%ws - Changing cxtion guid is not allowed.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name);
|
|
goto DELETE_AND_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Partner Guid
|
|
//
|
|
// The cxtion is deleted when its partner changes because the
|
|
// state kept per cxtion is partner specific. If the cxtion
|
|
// has been newly altered so that its previous mismatched
|
|
// partner guid now matches then the check in the previous
|
|
// basic block would have reanimated the cxtion before getting
|
|
// to this code. Hence, the lack of an "else" clause.
|
|
//
|
|
if (!GUIDS_EQUAL(Cxtion->Partner->Guid, NewCxtion->Partner->Guid)) {
|
|
DPRINT2(0, ":X: ERROR - %ws\\%ws - Changing cxtion's partner guid "
|
|
"is not allowed. DELETING CURRENT CXTION!\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name);
|
|
//
|
|
// Unjoin the cxtion after setting the deferred delete bit.
|
|
// The deferred delete bit will keep the cxtion from re-joining
|
|
// before it can be deleted during the next pass of the ds
|
|
// polling thread. Warning - cxtion may reappear at any time!
|
|
//
|
|
if (!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
LOCK_CXTION_TABLE(Replica);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_JOIN);
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_UNJOIN);
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_DEFERRED_DELETE);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
RcsForceUnjoin(Replica, Cxtion);
|
|
}
|
|
//
|
|
// If successfully unjoined; move to the deleted-cxtion table
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
RcsDeleteCxtionFromReplica(Replica, Cxtion);
|
|
}
|
|
goto DELETE_AND_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Partner Name
|
|
//
|
|
if (WSTR_NE(Cxtion->Partner->Name, NewCxtion->Partner->Name)) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing cxtion's partner name from %ws to %ws.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->Partner->Name, NewCxtion->Partner->Name);
|
|
FrsDsSwapPtrs(&Cxtion->Partner->Name, &NewCxtion->Partner->Name);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Partner PrincName
|
|
//
|
|
if (WSTR_NE(Cxtion->PartnerPrincName, NewCxtion->PartnerPrincName)) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing cxtion's partner princname from %ws to %ws.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->PartnerPrincName, NewCxtion->PartnerPrincName);
|
|
FrsDsSwapPtrs(&Cxtion->PartnerPrincName, &NewCxtion->PartnerPrincName);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Partner Dns Name
|
|
//
|
|
if (WSTR_NE(Cxtion->PartnerDnsName, NewCxtion->PartnerDnsName)) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing cxtion's partner DNS name from %ws to %ws.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->PartnerDnsName, NewCxtion->PartnerDnsName);
|
|
FrsDsSwapPtrs(&Cxtion->PartnerDnsName, &NewCxtion->PartnerDnsName);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Partner SID
|
|
//
|
|
if (WSTR_NE(Cxtion->PartnerSid, NewCxtion->PartnerSid)) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing cxtion's partner SID from %ws to %ws.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->PartnerSid, NewCxtion->PartnerSid);
|
|
FrsDsSwapPtrs(&Cxtion->PartnerSid, &NewCxtion->PartnerSid);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Cxtion Schedule
|
|
//
|
|
if (Cxtion->Schedule || NewCxtion->Schedule) {
|
|
if ((Cxtion->Schedule && !NewCxtion->Schedule) ||
|
|
(!Cxtion->Schedule && NewCxtion->Schedule) ||
|
|
(Cxtion->Schedule->Size != NewCxtion->Schedule->Size) ||
|
|
(memcmp(Cxtion->Schedule,
|
|
NewCxtion->Schedule,
|
|
Cxtion->Schedule->Size))) {
|
|
DPRINT2(4, ":X: %ws\\%ws - Changing cxtion schedule.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name);
|
|
FrsDsSwapPtrs(&Cxtion->Schedule, &NewCxtion->Schedule);
|
|
RcsCheckCxtionSchedule(Replica, Cxtion);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Partner Server Name
|
|
//
|
|
if (WSTR_NE(Cxtion->PartSrvName, NewCxtion->PartSrvName)) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing partner's server name from %ws to %ws.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->PartSrvName, NewCxtion->PartSrvName);
|
|
FrsDsSwapPtrs(&Cxtion->PartSrvName, &NewCxtion->PartSrvName);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Cxtion options.
|
|
//
|
|
if (Cxtion->Options != NewCxtion->Options) {
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing Cxtion's options from 0x%08x to 0x%08x.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
Cxtion->Options, NewCxtion->Options);
|
|
Cxtion->Options = NewCxtion->Options;
|
|
Cxtion->Priority = FRSCONN_GET_PRIORITY(Cxtion->Options);
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// Schedule type
|
|
//
|
|
if (CxtionFlagIs(NewCxtion, CXTION_FLAGS_TRIGGER_SCHEDULE) !=
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|
|
|
DPRINT4(4, ":X: %ws\\%ws - Changing schedule type from %s to %s.\n",
|
|
Replica->MemberName->Name, Cxtion->Name->Name,
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE) ?
|
|
"Trigger" : "Stop/Start",
|
|
CxtionFlagIs(NewCxtion, CXTION_FLAGS_TRIGGER_SCHEDULE) ?
|
|
"Trigger" : "Stop/Start");
|
|
|
|
if (CxtionFlagIs(NewCxtion, CXTION_FLAGS_TRIGGER_SCHEDULE)) {
|
|
SetCxtionFlag(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE);
|
|
} else {
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_TRIGGER_SCHEDULE);
|
|
}
|
|
UpdateNeeded = TRUE;
|
|
}
|
|
|
|
//
|
|
// No changes, done
|
|
//
|
|
if (!UpdateNeeded) {
|
|
goto DELETE_AND_CONTINUE;
|
|
}
|
|
//
|
|
// Update the cxtion table in the DB
|
|
//
|
|
// *NOTE* The following call is synchronous. If we have the Cxtion
|
|
// table lock then a hang is possible.
|
|
//
|
|
FStatus = OutLogSubmit(Replica, Cxtion, CMD_OUTLOG_UPDATE_PARTNER);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT3(0, ":X: WARN changes to cxtion %ws (to %ws, %ws) not updated in database\n",
|
|
Cxtion->Name->Name, Cxtion->Partner->Name,
|
|
Replica->ReplicaName->Name);
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
}
|
|
DELETE_AND_CONTINUE:
|
|
//
|
|
// Remove the cxtion from the new replica set. Anything left
|
|
// after this loop must be a new cxtion. Free the partner
|
|
// so that FrsFreeType() will not attempt to unbind our
|
|
// partner's handles.
|
|
//
|
|
NewCxtion->Partner = FrsFreeGName(NewCxtion->Partner);
|
|
GTabDelete(NewReplica->Cxtions, Cxtion->Name->Guid, NULL, FrsFreeType);
|
|
}
|
|
|
|
//
|
|
// New cxtions
|
|
//
|
|
for (Key = NULL;
|
|
NewCxtion = GTabNextDatum(NewReplica->Cxtions, &Key);
|
|
Key = NULL) {
|
|
|
|
//
|
|
// Remove the cxtion from the new replica
|
|
//
|
|
GTabDelete(NewReplica->Cxtions, NewCxtion->Name->Guid, NULL, NULL);
|
|
|
|
//
|
|
// Inconsistent cxtion; ignore
|
|
//
|
|
if (NewCxtion->JrnlCxtion ||
|
|
!CxtionFlagIs(NewCxtion, CXTION_FLAGS_CONSISTENT)) {
|
|
FrsFreeType(NewCxtion);
|
|
continue;
|
|
}
|
|
RcsCheckCxtionSchedule(Replica, NewCxtion);
|
|
|
|
//
|
|
// Update the cxtion table
|
|
//
|
|
// *NOTE* The following call is synchronous. If we have the Cxtion
|
|
// table lock then a hang is possible.
|
|
//
|
|
FStatus = OutLogSubmit(Replica, NewCxtion, CMD_OUTLOG_ADD_NEW_PARTNER);
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DPRINT2(4, ":X: %ws\\%ws - Created cxtion.\n",
|
|
Replica->MemberName->Name, NewCxtion->Name->Name);
|
|
//
|
|
// Update the incore cxtion table
|
|
//
|
|
GTabInsertEntry(Replica->Cxtions, NewCxtion, NewCxtion->Name->Guid, NULL);
|
|
|
|
DPRINT(5, ":X: PERFMON:Adding Connection:REPLICA.C:2\n");
|
|
RcsCreatePerfmonCxtionName(Replica, NewCxtion);
|
|
|
|
OutLogInitPartner(Replica, NewCxtion);
|
|
|
|
} else {
|
|
DPRINT2_FS(0, ":X: %ws\\%ws - ERROR creating cxtion;",
|
|
Replica->MemberName->Name, NewCxtion->Name->Name, FStatus);
|
|
//
|
|
// Chuck the cxtion. We will retry the create during the
|
|
// next ds polling cycle.
|
|
//
|
|
FrsFreeType(NewCxtion);
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCreatePerfmonCxtionName(
|
|
PREPLICA Replica,
|
|
PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Set the OID data structure which is a part of the counter data structure
|
|
stored in the hash table.
|
|
|
|
Add ReplicaConn Instance to the registry
|
|
|
|
|
|
Arguments:
|
|
Replica - active replica
|
|
Cxtion - Connection being added.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCreatePerfmonCxtionName:"
|
|
|
|
PWCHAR ToFrom;
|
|
ULONG NameLen;
|
|
PWCHAR CxNamePtr;
|
|
|
|
if ((Cxtion->PartSrvName != NULL) && (Replica->Root != NULL)) {
|
|
//
|
|
// The oid name used in the HT_REPLICA_CONN_DATA is of the form
|
|
// Replica->Root::FROM:Cxtion->PartSrvName or
|
|
// Replica->Root::TO:Cxtion->PartSrvName
|
|
//
|
|
|
|
WCHAR Tstr[256];
|
|
_snwprintf(Tstr, ARRAY_SZ(Tstr), L"%ws%ws%ws",
|
|
Replica->Root,
|
|
(Cxtion->Inbound) ? (L"::FROM:") : (L"::TO:"),
|
|
Cxtion->PartSrvName);
|
|
Tstr[ARRAY_SZ(Tstr)-1] = L'\0';
|
|
//
|
|
// Finally, add the REPLICACONN instance to the Registry
|
|
//
|
|
AddPerfmonInstance(REPLICACONN, Cxtion->PerfRepConnData, Tstr);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitStopReplicaToDb(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Release the replica's journal state and outbound log state.
|
|
This will also close the replica set if it was open. The close happens
|
|
after the Journaling on the replica has stopped so we save the correct
|
|
Journal restart USN.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitStopReplicaToDb:"
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
ULONG WStatus;
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// Abort promotion; if any
|
|
//
|
|
if (Replica->NtFrsApi_ServiceState == NTFRSAPI_SERVICE_PROMOTING) {
|
|
DPRINT1(4, ":S: Promotion aborted: stop for %ws.\n", Replica->SetName->Name);
|
|
Replica->NtFrsApi_ServiceWStatus = FRS_ERR_SYSVOL_POPULATE;
|
|
Replica->NtFrsApi_ServiceState = NTFRSAPI_SERVICE_DONE;
|
|
}
|
|
|
|
//
|
|
// Attempt to stop journaling even if "IsJournaling" is false because
|
|
// may have journal state even if it isn't journaling. The journal
|
|
// is kept in pVme.
|
|
//
|
|
if (Replica->pVme == NULL) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica->pVme == NULL");
|
|
//
|
|
// Close replica is OK here as long as the Journal has not
|
|
// been started on this replica set.
|
|
//
|
|
RcsCloseReplicaSetmember(Replica);
|
|
RcsCloseReplicaCxtions(Replica);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Submitted to the db command server
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_STOP_REPLICATION_SINGLE_REPLICA, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Submit DB CMD_STOP_REPLICATION_SINGLE_REPLICA");
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Stop Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Stop Replica DB Command failed", WStatus);
|
|
goto out;
|
|
}
|
|
|
|
Replica->IsOpen = FALSE;
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCloseReplicaCxtions(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
"Delete" the cxtions from active processing. RcsOpenReplicaSetMember() will
|
|
generate new cxtion structs before active processing (aka joining)
|
|
occurs. The cxtions remain in the DeletedCxtions table so that
|
|
other threads that may have been time sliced just after acquiring
|
|
the cxtion pointer but before making use of it will not AV. We
|
|
could have put in locks but then there would be deadlock and
|
|
perf considerations; all for the sake of this seldom occuring
|
|
operation.
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCloseReplicaCxtions:"
|
|
PVOID Key;
|
|
PCXTION Cxtion;
|
|
|
|
//
|
|
// Open; ignore request
|
|
//
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Replica open at close cxtions");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// "Delete" the cxtions from active processing. RcsOpenReplicaSetMember() will
|
|
// generate new cxtion structs before active processing (aka joining)
|
|
// occurs. The cxtions remain in the DeletedCxtions table so that
|
|
// other threads that may have been time sliced just after acquiring
|
|
// the cxtion pointer but before making use of it will not AV. We
|
|
// could have put in locks but then there would be deadlock and
|
|
// perf considerations; all for the sake of this seldom occuring
|
|
// operation.
|
|
//
|
|
if (Replica->Cxtions) {
|
|
Key = NULL;
|
|
while (Cxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
Key = NULL;
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, GenTable Cxtion deleted");
|
|
GTabDelete(Replica->Cxtions, Cxtion->Name->Guid, NULL, NULL);
|
|
|
|
//
|
|
// Remove the connection from the perfmon tables so we stop returning
|
|
// data and allow a new connection with the same name to be established.
|
|
//
|
|
// Journal cxtion does not have a perfmon entry
|
|
//
|
|
if (!Cxtion->JrnlCxtion) {
|
|
DeletePerfmonInstance(REPLICACONN, Cxtion->PerfRepConnData);
|
|
}
|
|
GTabInsertEntry(DeletedCxtions, Cxtion, Cxtion->Name->Guid, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCloseReplicaSetmember(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Close an open replica
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCloseReplicaSetmember:"
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
ULONG WStatus;
|
|
PVOID Key;
|
|
PCXTION Cxtion;
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// Not open or still journaling; ignore request
|
|
//
|
|
if (!Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica not open");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Submitted to the db command server
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_CLOSE_REPLICA_SET_MEMBER, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Submit DB CMD_CLOSE_REPLICA_SET_MEMBER");
|
|
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Close Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Close Replica DB Command failed", WStatus);
|
|
goto out;
|
|
}
|
|
|
|
Replica->IsOpen = FALSE;
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsDeleteReplicaFromDb(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Delete the replica from the database
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
winerror
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDeleteReplicaFromDb:"
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
ULONG WStatus;
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// Replica must be closed to delete
|
|
//
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica not closed.");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Submitted to the db command server
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_DELETE_REPLICA_SET_MEMBER, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Submit DB CMD_DELETE_REPLICA_SET_MEMBER");
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Delete Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Delete Replica DB Command failed", WStatus);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsUpdateReplicaSetMember(
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Update the database record for Replica.
|
|
|
|
*Note*
|
|
Perf: RcsUpdateReplicaSetMember() should return status as part
|
|
of the function call not use some side-effect flag(NeedsUpdate)
|
|
in the Replica struct.
|
|
|
|
Arguments:
|
|
Replica
|
|
|
|
Return Value:
|
|
winerror
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsUpdateReplicaSetMember:"
|
|
PCOMMAND_PACKET Cmd = NULL;
|
|
ULONG WStatus;
|
|
|
|
Replica->FStatus = FrsErrorSuccess;
|
|
|
|
//
|
|
// Replica is not dirty
|
|
//
|
|
if (!Replica->NeedsUpdate || !Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica not open or not dirty");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Command packet
|
|
//
|
|
Cmd = DbsPrepareCmdPkt(NULL, // Cmd,
|
|
Replica, // Replica,
|
|
CMD_UPDATE_REPLICA_SET_MEMBER, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
NULL, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
FALSE); // Submit
|
|
|
|
//
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
//
|
|
// SUBMIT DB Cmd and wait for completion.
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Submit DB CMD_UPDATE_REPLICA_SET_MEMBER");
|
|
WStatus = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, Cmd, INFINITE);
|
|
Replica->FStatus = Cmd->Parameters.DbsRequest.FStatus;
|
|
//
|
|
// If wait or database op failed
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) || !FRS_SUCCESS(Replica->FStatus)) {
|
|
//
|
|
// If wait / submit failed then let caller know cmd srv submit failed.
|
|
//
|
|
if (FRS_SUCCESS(Replica->FStatus)) {
|
|
Replica->FStatus = FrsErrorCmdSrvFailed;
|
|
}
|
|
|
|
DPRINT2_FS(0, ":S: ERROR - %ws\\%ws: Update Replica failed;",
|
|
Replica->SetName->Name, Replica->MemberName->Name, Replica->FStatus);
|
|
|
|
DPRINT_WS(0, "ERROR: Update Replica DB Command failed", WStatus);
|
|
goto out;
|
|
}
|
|
|
|
Replica->NeedsUpdate = !FRS_SUCCESS(Replica->FStatus);
|
|
|
|
out:
|
|
if (Cmd) {
|
|
FrsFreeCommand(Cmd, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
RcsHasReplicaRootPathMoved(
|
|
IN PREPLICA Replica,
|
|
IN PREPLICA NewReplica,
|
|
OUT PDWORD ReplicaState
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Check if the replica root path has moved. Called at each poll.
|
|
|
|
The following checks are made to make sure that the volume and journal
|
|
info is not changed while the service was not running.
|
|
|
|
VOLUME SERIAL NUMBER MISMATCH CHECK:
|
|
In case of a mismatch the replica set is marked to be deleted.
|
|
|
|
REPLICA ROOT DIR OBJECTID MISMATCH CHECK:
|
|
In case of a mismatch the replica set is marked to be deleted.
|
|
|
|
REPLICA ROOT DIR FID MISMATCH CHECK:
|
|
In case of a mismatch the replica set is marked to be deleted.
|
|
|
|
|
|
Arguments:
|
|
Replica - Existing replica structure.
|
|
NewReplica - New replica structure from DS.
|
|
ReplicaState - Error state to move to if root has moved.
|
|
|
|
Return Value:
|
|
BOOL.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsHasReplicaRootPathMoved:"
|
|
|
|
HANDLE NewRootHandle = INVALID_HANDLE_VALUE;
|
|
DWORD WStatus;
|
|
DWORD VolumeInfoLength;
|
|
PFILE_FS_VOLUME_INFORMATION VolumeInfo = NULL;
|
|
IO_STATUS_BLOCK Iosb;
|
|
NTSTATUS Status;
|
|
PCONFIG_TABLE_RECORD ConfigRecord = NULL;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
BOOL Result = FALSE;
|
|
PCOMMAND_PACKET CmdPkt = NULL;
|
|
ULONG AccessRequest;
|
|
PDB_SERVICE_REQUEST DbsRequest = NULL;
|
|
FILE_OBJECTID_BUFFER ObjectIdBuffer;
|
|
FILE_INTERNAL_INFORMATION InternalFileInfo;
|
|
PREPLICA_THREAD_CTX RtCtx = NULL;
|
|
PTABLE_CTX IDTableCtx = NULL;
|
|
PVOID pKey = NULL;
|
|
PIDTABLE_RECORD IDTableRec = NULL;
|
|
|
|
//
|
|
// If this is the first time we are starting this replica set.
|
|
// Nothing to compare with.
|
|
//
|
|
if (NewReplica == NULL) {
|
|
goto RETURN;
|
|
}
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD)Replica->ConfigTable.pDataRecord;
|
|
|
|
//
|
|
// Always open the path by masking off the FILE_OPEN_REPARSE_POINT flag
|
|
// because we want to open the destination dir not the junction if the root
|
|
// happens to be a mount point.
|
|
//
|
|
WStatus = FrsOpenSourceFileW(&NewRootHandle,
|
|
NewReplica->Root,
|
|
GENERIC_READ,
|
|
FILE_OPEN_FOR_BACKUP_INTENT);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, "ERROR - Unable to open root path %ws. Retry as next poll.",
|
|
NewReplica->Root, WStatus);
|
|
goto RETURN;
|
|
}
|
|
|
|
//
|
|
// Get the volume information.
|
|
//
|
|
VolumeInfoLength = sizeof(FILE_FS_VOLUME_INFORMATION) +
|
|
MAXIMUM_VOLUME_LABEL_LENGTH;
|
|
|
|
VolumeInfo = FrsAlloc(VolumeInfoLength);
|
|
|
|
Status = NtQueryVolumeInformationFile(NewRootHandle,
|
|
&Iosb,
|
|
VolumeInfo,
|
|
VolumeInfoLength,
|
|
FileFsVolumeInformation);
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
DPRINT2(0,"ERROR - Getting NtQueryVolumeInformationFile for %ws. NtStatus = %08x\n",
|
|
NewReplica->Root, Status);
|
|
|
|
goto RETURN;
|
|
}
|
|
|
|
//
|
|
// VOLUME SERIAL NUMBER MISMATCH CHECK:
|
|
//
|
|
// If LastShutdown is 0 then this is the very first time we have started
|
|
// replication on this replica set so save the volumeinformation in
|
|
// the config record. Even if Lastshutdown is not 0 CnfUsnJournalID could
|
|
// be 0 because it was not getting correctly updated in Win2K.. For sysvol replica sets the
|
|
// JournalID was getting updated correctly but not the volume information.
|
|
//
|
|
if ((ConfigRecord->LastShutdown == (ULONGLONG)0) ||
|
|
(ConfigRecord->ServiceState == CNF_SERVICE_STATE_CREATING) ||
|
|
(ConfigRecord->CnfUsnJournalID == (ULONGLONG)0) ||
|
|
(
|
|
(ConfigRecord->FSVolInfo != NULL) &&
|
|
(ConfigRecord->FSVolInfo->VolumeSerialNumber == 0)
|
|
)) {
|
|
|
|
CopyMemory(ConfigRecord->FSVolInfo,VolumeInfo,sizeof(FILE_FS_VOLUME_INFORMATION) + MAXIMUM_VOLUME_LABEL_LENGTH);
|
|
Replica->NeedsUpdate = TRUE;
|
|
|
|
} else
|
|
//
|
|
// Check if the VolumeSerialNumber for New Replica matches with the
|
|
// VolumeSerialNumber from the config record for this replica set. If
|
|
// it does not then it means that this replica set has been moved.
|
|
// Returning error here will trigger a deletion of the replica set. The set will
|
|
// be recreated at the next poll cycle and it will either be primary or non-auth
|
|
// depending on the case.
|
|
//
|
|
|
|
if (VolumeInfo->VolumeSerialNumber != ConfigRecord->FSVolInfo->VolumeSerialNumber) {
|
|
DPRINT1(0,"ERROR - VolumeSerialNumber mismatch for Replica Set (%ws)\n",Replica->ReplicaName->Name);
|
|
DPRINT2(0,"ERROR - VolumeSerialNumber %x(FS) != %x(DB)\n",
|
|
VolumeInfo->VolumeSerialNumber,ConfigRecord->FSVolInfo->VolumeSerialNumber);
|
|
DPRINT1(0,"ERROR - Replica Set (%ws) is marked to be deleted\n",Replica->ReplicaName->Name);
|
|
Replica->FStatus = FrsErrorMismatchedVolumeSerialNumber;
|
|
*ReplicaState = REPLICA_STATE_MISMATCHED_VOLUME_SERIAL_NO;
|
|
Result = TRUE;
|
|
goto RETURN;
|
|
}
|
|
|
|
VolumeInfo = FrsFree(VolumeInfo);
|
|
|
|
//
|
|
// Get the FID for the replica root.
|
|
//
|
|
//
|
|
// zero the buffer in case the data that comes back is short.
|
|
//
|
|
ZeroMemory(&InternalFileInfo, sizeof(FILE_INTERNAL_INFORMATION));
|
|
|
|
Status = NtQueryInformationFile(NewRootHandle,
|
|
&Iosb,
|
|
&InternalFileInfo,
|
|
sizeof(FILE_INTERNAL_INFORMATION),
|
|
FileInternalInformation);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
DPRINT1(0, "ERROR getting FID for %ws\n", NewReplica->Root);
|
|
goto RETURN;
|
|
}
|
|
|
|
//
|
|
// zero the buffer in case the data that comes back is short.
|
|
//
|
|
ZeroMemory(&ObjectIdBuffer, sizeof(FILE_OBJECTID_BUFFER));
|
|
|
|
//
|
|
// Get the Object ID from the replica root.
|
|
//
|
|
Status = NtFsControlFile(
|
|
NewRootHandle, // file handle
|
|
NULL, // event
|
|
NULL, // apc routine
|
|
NULL, // apc context
|
|
&Iosb, // iosb
|
|
FSCTL_GET_OBJECT_ID, // FsControlCode
|
|
&NewRootHandle, // input buffer
|
|
sizeof(HANDLE), // input buffer length
|
|
&ObjectIdBuffer, // OutputBuffer for data from the FS
|
|
sizeof(FILE_OBJECTID_BUFFER)); // OutputBuffer Length
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
GuidToStr((GUID *)ObjectIdBuffer.ObjectId, GuidStr);
|
|
DPRINT1(4, ":S: Oid for replica root is %s\n", GuidStr );
|
|
} else
|
|
if (Status == STATUS_NOT_IMPLEMENTED) {
|
|
DPRINT1(0, ":S: ERROR - FSCTL_GET_OBJECT_ID failed on file %ws. Object IDs are not enabled on the volume.\n",
|
|
NewReplica->Root);
|
|
|
|
DisplayNTStatus(Status);
|
|
}
|
|
FRS_CLOSE(NewRootHandle);
|
|
|
|
//
|
|
// REPLICA ROOT DIR OBJECTID MISMATCH CHECK:
|
|
//
|
|
// If LastShutdown is 0 then this is the very first time we have started replication on this replica set
|
|
// in that case skip this check.
|
|
//
|
|
if (ConfigRecord->LastShutdown != (ULONGLONG)0 &&
|
|
ConfigRecord->ServiceState != CNF_SERVICE_STATE_CREATING &&
|
|
ConfigRecord->CnfUsnJournalID != (ULONGLONG)0) {
|
|
|
|
//
|
|
// The replica root might have been recreated in which case it will might
|
|
// have a object ID. In that vase we want to recreate the replica set.
|
|
// Check if the ReplicaRootGuid from config record matches with the ReplicaRootGuid
|
|
// from the FileSystem. If it does not then it means that
|
|
// this replica set has been moved. Returning error here will trigger
|
|
// a deletion of the replica set. The set will be recreated at the next
|
|
// poll cycle and it will either be primary or non-auth depending on the
|
|
// case.
|
|
//
|
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND ||
|
|
Status == STATUS_OBJECTID_NOT_FOUND ||
|
|
!GUIDS_EQUAL(&(ObjectIdBuffer.ObjectId), &(ConfigRecord->ReplicaRootGuid))) {
|
|
|
|
DPRINT1(0,"ERROR - Replica root guid mismatch for Replica Set (%ws)\n",Replica->ReplicaName->Name);
|
|
|
|
GuidToStr((GUID *)ObjectIdBuffer.ObjectId, GuidStr);
|
|
DPRINT1(0,"ERROR - Replica root guid (FS) (%s)\n",GuidStr);
|
|
|
|
GuidToStr((GUID *)&(ConfigRecord->ReplicaRootGuid), GuidStr);
|
|
DPRINT1(0,"ERROR - Replica root guid (DB) (%s)\n",GuidStr);
|
|
|
|
DPRINT1(0,"ERROR - Replica Set (%ws) is marked to be deleted\n",Replica->ReplicaName->Name);
|
|
|
|
Replica->FStatus = FrsErrorMismatchedReplicaRootObjectId;
|
|
*ReplicaState = REPLICA_STATE_MISMATCHED_REPLICA_ROOT_OBJECT_ID;
|
|
Result = TRUE;
|
|
goto RETURN;
|
|
}
|
|
}
|
|
|
|
//
|
|
// REPLICA ROOT DIR FID MISMATCH CHECK:
|
|
//
|
|
// If LastShutdown is 0 then this is the very first time we have started replication on this replica set
|
|
// in that case skip this check.
|
|
//
|
|
if (ConfigRecord->LastShutdown != (ULONGLONG)0 &&
|
|
ConfigRecord->ServiceState != CNF_SERVICE_STATE_CREATING &&
|
|
ConfigRecord->CnfUsnJournalID != (ULONGLONG)0) {
|
|
//
|
|
// Open the ID table.
|
|
//
|
|
RtCtx = FrsAllocType(REPLICA_THREAD_TYPE);
|
|
IDTableCtx = &RtCtx->IDTable;
|
|
|
|
AccessRequest = DBS_ACCESS_BYKEY | DBS_ACCESS_CLOSE;
|
|
pKey = (PVOID)&(ConfigRecord->ReplicaRootGuid);
|
|
|
|
CmdPkt = DbsPrepareCmdPkt(CmdPkt, // CmdPkt,
|
|
Replica, // Replica,
|
|
CMD_READ_TABLE_RECORD, // CmdRequest,
|
|
IDTableCtx, // TableCtx,
|
|
NULL, // CallContext,
|
|
IDTablex, // TableType,
|
|
AccessRequest, // AccessRequest,
|
|
GuidIndexx, // IndexType,
|
|
pKey, // KeyValue,
|
|
sizeof(GUID), // KeyValueLength,
|
|
FALSE); // Submit
|
|
|
|
if (CmdPkt == NULL) {
|
|
DPRINT(0, "ERROR - Failed to init the cmd pkt\n");
|
|
RtCtx = FrsFreeType(RtCtx);
|
|
goto RETURN;;
|
|
}
|
|
FrsSetCompletionRoutine(CmdPkt, FrsCompleteKeepPkt, NULL);
|
|
|
|
Status = FrsSubmitCommandServerAndWait(&DBServiceCmdServer, CmdPkt, INFINITE);
|
|
|
|
DbsRequest = &CmdPkt->Parameters.DbsRequest;
|
|
IDTableCtx = DBS_GET_TABLECTX(DbsRequest);
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
if (DbsRequest == NULL || DbsRequest->FStatus != FrsErrorSuccess) {
|
|
|
|
RtCtx = FrsFreeType(RtCtx);
|
|
FrsFreeCommand(CmdPkt, NULL);
|
|
goto RETURN;
|
|
}
|
|
|
|
//
|
|
// The replica root might have been recreated by a restore operation in which case
|
|
// it will have the same object ID but a different FID. In that case we want
|
|
// to recreate the replica set. Check if the FID from IDTable matches with the FID
|
|
// from the FileSystem. If it does not then it means that this replica set has been
|
|
// moved. Returning error here will trigger a deletion of the replica set. The set
|
|
// will be recreated at the next poll cycle and it will either be primary or non-auth
|
|
// depending on the case.
|
|
// This can also happen when a junction point is present in the initial path to the replica
|
|
// root. At a later time the junction points destination is changed and the replica rpot
|
|
// has been moved.
|
|
//
|
|
if (IDTableRec->FileID != InternalFileInfo.IndexNumber.QuadPart) {
|
|
|
|
DPRINT1(0,"ERROR - Replica root fid mismatch for Replica Set (%ws)\n",Replica->ReplicaName->Name);
|
|
|
|
DPRINT1(0,"ERROR - Replica root fid (FS) (%08x %08x)\n",PRINTQUAD(InternalFileInfo.IndexNumber.QuadPart));
|
|
|
|
DPRINT1(0,"ERROR - Replica root fid (DB) (%08x %08x)\n",PRINTQUAD(IDTableRec->FileID));
|
|
|
|
DPRINT1(0,"ERROR - Replica Set (%ws) is marked to be deleted\n",Replica->ReplicaName->Name);
|
|
|
|
RtCtx = FrsFreeType(RtCtx);
|
|
FrsFreeCommand(CmdPkt, NULL);
|
|
Replica->FStatus = FrsErrorMismatchedReplicaRootFileId;
|
|
*ReplicaState = REPLICA_STATE_MISMATCHED_REPLICA_ROOT_FILE_ID;
|
|
Result = TRUE;
|
|
goto RETURN;
|
|
}
|
|
RtCtx = FrsFreeType(RtCtx);
|
|
FrsFreeCommand(CmdPkt, NULL);
|
|
}
|
|
|
|
RETURN:
|
|
|
|
VolumeInfo = FrsFree(VolumeInfo);
|
|
FRS_CLOSE(NewRootHandle);
|
|
return Result;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsStartReplicaSetMember(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Bring the replica up to joined, active status.
|
|
|
|
Arguments:
|
|
Cmd.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsStartReplicaSetMember:"
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
ULONGLONG MembershipExpires;
|
|
PVOID Key;
|
|
DWORD NumberOfCxtions;
|
|
DWORD ReplicaState;
|
|
ULONG FStatus;
|
|
PWCHAR CmdFile = NULL;
|
|
ULONG FileAttributes;
|
|
extern ULONGLONG ActiveChange;
|
|
extern ULONG DsPollingInterval;
|
|
WCHAR DsPollingIntervalStr[7]; // Max interval is NTFRSAPI_MAX_INTERVAL.
|
|
FRS_ERROR_CODE SavedFStatus;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsStartReplicaSetMember entry");
|
|
FRS_PRINT_TYPE(4, Replica);
|
|
|
|
//
|
|
// Increment the Replica Sets started counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, RSStarted, 1);
|
|
|
|
//
|
|
// Someone is trying to start a deleted replica. This is either part
|
|
// of startup and we are simply trying to start replication on all
|
|
// replicas or a deleted replica has actually reappeared.
|
|
//
|
|
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica Tombstoned");
|
|
|
|
//
|
|
// The deletion hasn't completed processing. The jrnlcxtion cannot
|
|
// be restarted without restarting the entire replica set. The
|
|
// set needs to be "closed" and then "opened". Let the tombstoning
|
|
// finish before attempting to re-open. A completely closed
|
|
// replica set has IsOpen set to FALSE and no cxtions.
|
|
//
|
|
if (Replica->IsOpen || GTabNumberInTable(Replica->Cxtions)) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica tombstone in progress");
|
|
RcsSubmitTransferToRcs(Cmd, CMD_DELETE);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Don't start a deleted replica unless it has magically reappeared.
|
|
//
|
|
if (RsNewReplica(Cmd) == NULL) {
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
//
|
|
// Don't reanimate if the tombstone has expired
|
|
//
|
|
if (RcsReplicaHasExpired(Replica)) {
|
|
//
|
|
// Remove registry entry
|
|
//
|
|
RcsReplicaDeleteRegistry(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica Expired");
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// A deleted replica has reappeared; undelete the replica
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica Reanimating");
|
|
RcsOpenReplicaSetMember(Replica);
|
|
MembershipExpires = Replica->MembershipExpires;
|
|
Replica->MembershipExpires = 0;
|
|
Replica->NeedsUpdate = TRUE;
|
|
RcsUpdateReplicaSetMember(Replica);
|
|
//
|
|
// The above friggen call set NeedsUpdate to false if the update succeeded.
|
|
//
|
|
|
|
//
|
|
// Replica cannot be marked as "undeleted" in the DB; retry later
|
|
// and *don't* start. We wouldn't want to start replication only
|
|
// to have the replica disappear from the database at the next
|
|
// reboot or service startup because its timeout expired.
|
|
//
|
|
|
|
FStatus = DbsCheckForOverlapErrors(Replica);
|
|
|
|
if (Replica->NeedsUpdate || !FRS_SUCCESS(FStatus)) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica Reanimation failed");
|
|
Replica->MembershipExpires = MembershipExpires;
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
//
|
|
// Note: Does there need to be a replica set close here?
|
|
// Need to check all use of Replica->IsOpen to figure this out.
|
|
//
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The open above returned success so Replica->IsOpen would be set
|
|
// as expected but since the replica set was marked expired the actual
|
|
// service state for the replica set is STOPPED and the handles to
|
|
// The pre-install dir was never opened. The call to reopen later
|
|
// will fail if Replica->IsOpen is still TRUE. So close it for now.
|
|
//
|
|
// *Note*
|
|
// Perf: Get rid of the friggen Replica->IsOpen flag and use the service
|
|
// state as originally intended.
|
|
//
|
|
|
|
RcsCloseReplicaSetmember(Replica);
|
|
|
|
//
|
|
// No longer tombstoned; update the registry
|
|
//
|
|
RcsReplicaSetRegistry(Replica);
|
|
|
|
//
|
|
// Set registry value "FilesNotToBackup"
|
|
//
|
|
CfgFilesNotToBackup(ReplicasByGuid);
|
|
}
|
|
|
|
//
|
|
// If this replica is in the error state then first make sure it is closed.
|
|
// and reset its state to initializing.
|
|
//
|
|
if (REPLICA_IN_ERROR_STATE(Replica->ServiceState)) {
|
|
RcsCloseReplicaSetmember(Replica);
|
|
JrnlSetReplicaState(Replica, REPLICA_STATE_INITIALIZING);
|
|
}
|
|
|
|
//
|
|
// Check if the replica root path has moved between two polls.
|
|
//
|
|
if (RcsHasReplicaRootPathMoved(Replica, RsNewReplica(Cmd), &ReplicaState)) {
|
|
//
|
|
// Save the FStatus. We need the correct FStatus to report in the
|
|
// eventlog written in JrnlSetReplicaState(). FStatus set by
|
|
// RcsHasReplicaRootPathMoved() is overwritten when we stop and
|
|
// close the replica set.
|
|
//
|
|
//
|
|
SavedFStatus = Replica->FStatus;
|
|
|
|
//
|
|
// The replica root has moved. This could be a intentional move or
|
|
// it could be caused by change of drive letter. In any case we need a
|
|
// confirmation from the user to go ahead and trigger a non-auth restore
|
|
// of the replica set. We will look for a special file "NTFRS_CMD_FILE_MOVE_ROOT"
|
|
// under the new replica root. If this file is present we will go ahead and delete
|
|
// the replica set which will trigger a non-auth restore at the next poll.
|
|
// The command file is deleted when the replica set is initialized.
|
|
//
|
|
|
|
CmdFile = FrsWcsCat3((RsNewReplica(Cmd))->Root, L"\\", NTFRS_CMD_FILE_MOVE_ROOT);
|
|
|
|
FileAttributes = GetFileAttributes(CmdFile);
|
|
|
|
if ((FileAttributes == 0xffffffff) ||
|
|
(FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
//
|
|
// Get the DsPollingInteval in minutes.
|
|
//
|
|
_itow(DsPollingInterval / (60 * 1000), DsPollingIntervalStr, 10);
|
|
|
|
EPRINT4(EVENT_FRS_ROOT_HAS_MOVED, Replica->SetName->Name, Replica->Root,
|
|
(RsNewReplica(Cmd))->Root, DsPollingIntervalStr);
|
|
|
|
DPRINT1(0,"ERROR Command file not found at the new root %ws. Can not move root.\n",CmdFile);
|
|
//
|
|
// This replica state will not trigger a delete but it will still put it on
|
|
// the fault list.
|
|
//
|
|
ReplicaState = REPLICA_STATE_ERROR;
|
|
}
|
|
|
|
CmdFile = FrsFree(CmdFile);
|
|
RcsSubmitStopReplicaToDb(Replica);
|
|
RcsCloseReplicaSetmember(Replica);
|
|
RcsCloseReplicaCxtions(Replica);
|
|
|
|
//
|
|
// Write the saved FStatus back so we can write it to the eventlog.
|
|
//
|
|
Replica->FStatus = SavedFStatus;
|
|
JrnlSetReplicaState(Replica, ReplicaState);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
ActiveChange = 0;
|
|
return;
|
|
}
|
|
//
|
|
// Retry the open
|
|
//
|
|
RcsOpenReplicaSetMember(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsOpen, "B, Replica opened");
|
|
|
|
//
|
|
// Pre-Install Dir should now be open.
|
|
//
|
|
if (!HANDLE_IS_VALID(Replica->PreInstallHandle) ||
|
|
!FRS_SUCCESS(Replica->FStatus)) {
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
ActiveChange = 0;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Retry the journal
|
|
//
|
|
RcsInitOneReplicaSet(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsJournaling, "B, Journal opened");
|
|
|
|
//
|
|
// Update the database iff fields have changed
|
|
//
|
|
RcsMergeReplicaFields(Replica, RsNewReplica(Cmd));
|
|
|
|
//
|
|
// If needed, update the replica in the database
|
|
//
|
|
RcsUpdateReplicaSetMember(Replica);
|
|
//
|
|
// The above friggen call set Replica->NeedsUpdate to false if the update succeeded.
|
|
//
|
|
|
|
//
|
|
// Update the database iff cxtions have changed
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Merge Replica Cxtions");
|
|
NumberOfCxtions = GTabNumberInTable(Replica->Cxtions);
|
|
RcsMergeReplicaCxtions(Replica, RsNewReplica(Cmd), RsNewCxtion(Cmd));
|
|
RsNewCxtion(Cmd) = NULL;
|
|
if (NumberOfCxtions != GTabNumberInTable(Replica->Cxtions)) {
|
|
RcsReplicaSetRegistry(Replica);
|
|
}
|
|
|
|
//
|
|
// Accept remote change orders and join outbound connections.
|
|
//
|
|
if (Replica->IsOpen && Replica->IsJournaling) {
|
|
Replica->IsAccepting = TRUE;
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsAccepting, "B, Is Accepting");
|
|
}
|
|
|
|
|
|
//
|
|
// Retry the join
|
|
//
|
|
if (Replica->IsOpen && Replica->IsJournaling) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica accepting, Starting cxtions");
|
|
|
|
//
|
|
// If we are in the seeding state then let the Initial Sync command server
|
|
// control the joining.
|
|
//
|
|
if (BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING)) {
|
|
|
|
if (Replica->InitSyncQueue == NULL) {
|
|
//
|
|
// Initialize the queue for the InitSync Command server.
|
|
//
|
|
|
|
Replica->InitSyncQueue = FrsAlloc(sizeof(FRS_QUEUE));
|
|
FrsInitializeQueue(Replica->InitSyncQueue, &InitSyncCs.Control);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, submit CMD_INITSYNC_START_SYNC");
|
|
InitSyncSubmitToInitSyncCs(Replica, CMD_INITSYNC_START_SYNC);
|
|
} else {
|
|
//
|
|
// Initial Sync command server is already working on this replica set.
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica in initial sync state");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process all the connections in the table by putting a command
|
|
// packet for each cxtion on this replica's command queue.
|
|
//
|
|
Key = NULL;
|
|
while (Cxtion = GTabNextDatum(Replica->Cxtions, &Key)) {
|
|
|
|
//
|
|
// If replica is in seeding state then skip the connections that have
|
|
// not completed their initial join. (CXTION_FLAGS_INIT_SYNC)
|
|
// These connections will be joined by the initial sync command server.
|
|
//
|
|
if (BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) &&
|
|
CxtionFlagIs(Cxtion, CXTION_FLAGS_INIT_SYNC)) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, skip cxtion join");
|
|
continue;
|
|
}
|
|
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, submit cxtion join");
|
|
RcsSubmitReplicaCxtionJoin(Replica, Cxtion, FALSE);
|
|
}
|
|
|
|
} else {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica is NOT accepting");
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Check if this replica set is journaling and is not seeding or is online.
|
|
//
|
|
if (Replica->IsJournaling &&
|
|
((!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_SEEDING) &&
|
|
!Replica->IsSeeding) ||
|
|
BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE))){
|
|
|
|
//
|
|
// If this is a sysvol replica set then set sysvol ready if not already set.
|
|
//
|
|
if (FRS_RSTYPE_IS_SYSVOL(Replica->ReplicaSetType) &&
|
|
!Replica->IsSysvolReady) {
|
|
|
|
Replica->IsSysvolReady = RcsSetSysvolReady(1);
|
|
if (!Replica->IsSysvolReady) {
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica sysvol not ready");
|
|
} else {
|
|
RcsReplicaSetRegistry(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica sysvol is ready");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Also make this set online if not already set.
|
|
//
|
|
if (!BooleanFlagOn(Replica->CnfFlags, CONFIG_FLAG_ONLINE)) {
|
|
SetFlag(Replica->CnfFlags, CONFIG_FLAG_ONLINE);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica is Online");
|
|
Replica->NeedsUpdate = TRUE;
|
|
}
|
|
}
|
|
|
|
if (Replica->NeedsUpdate) {
|
|
//
|
|
// Ds poll thread will restart the replica during the next
|
|
// polling cycle if ActiveChange is set to 0.
|
|
//
|
|
ActiveChange = 0;
|
|
}
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsDeleteReplicaRetry(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Retry bringing the replica down to an unjoined, idle status.
|
|
|
|
Arguments:
|
|
Cmd.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDeleteReplicaRetry:"
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
PREPLICA Replica;
|
|
PVOID CxtionKey;
|
|
PCXTION Cxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsDeleteReplicaRetry entry");
|
|
|
|
//
|
|
// No longer tombstoned; done
|
|
//
|
|
if (IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Start the unjoin process on all cxtions
|
|
//
|
|
CxtionKey = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Force unjoin cxtion");
|
|
RcsForceUnjoin(Replica, Cxtion);
|
|
}
|
|
|
|
//
|
|
// Are all cxtions unjoined?
|
|
//
|
|
CxtionKey = NULL;
|
|
Cxtion = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
if (!CxtionStateIs(Cxtion, CxtionStateUnjoined) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Not all cxtions are unjoined (or deleted). Retry this command
|
|
// in a bit if the replica set ramains tombstoned.
|
|
//
|
|
if (Cxtion) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Retry delete later, again");
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, CMD_DELETE_RETRY_LONG_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// All of the cxtions were successfully unjoined. Shut down the replica.
|
|
//
|
|
|
|
//
|
|
// Stop accepting comm packets. Errors are returned to our partner.
|
|
//
|
|
// IsAccepting will become TRUE again in RcsStartReplicaSetMember() before
|
|
// any cxtion rejoins.
|
|
//
|
|
Replica->IsAccepting = FALSE;
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica is NOT Accepting");
|
|
|
|
//
|
|
// Stop journal processing and close the replica.
|
|
//
|
|
RcsSubmitStopReplicaToDb(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsOpen, "IsOpen, Replica closed");
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->FStatus, "F, Replica still open");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
} else {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica closed");
|
|
}
|
|
|
|
//
|
|
// "Delete" the cxtions from active processing. RcsOpenReplicaSetMember() will
|
|
// generate new cxtion structs before active processing (aka joining)
|
|
// occurs. The cxtions remain in the DeletedCxtions table so that
|
|
// other threads that may have been time sliced just after acquiring
|
|
// the cxtion pointer but before making use of it will not AV. We
|
|
// could have put in locks but then there would be deadlock and
|
|
// perf considerations; all for the sake of this seldom occuring
|
|
// operation.
|
|
//
|
|
RcsCloseReplicaCxtions(Replica);
|
|
|
|
//
|
|
// Increment the Replica Sets deleted counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, RSDeleted, 1);
|
|
|
|
//
|
|
// Has the tombstone expired?
|
|
//
|
|
if (RcsReplicaHasExpired(Replica)) {
|
|
RcsSubmitTransferToRcs(Cmd, CMD_DELETE_NOW);
|
|
} else {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsDeleteReplicaSetMember(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Bring the replica down to an unjoined, idle status.
|
|
|
|
Arguments:
|
|
Cmd.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDeleteReplicaSetMember:"
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
PREPLICA Replica;
|
|
PVOID CxtionKey;
|
|
PCXTION Cxtion;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsDeleteReplicaSetMember entry");
|
|
|
|
//
|
|
// Time in 100nsec tics since Jan 1, 1601
|
|
//
|
|
GetSystemTimeAsFileTime(&FileTime);
|
|
COPY_TIME(&Now, &FileTime);
|
|
|
|
//
|
|
// SET THE TOMBSTONE
|
|
//
|
|
if (IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
|
|
//
|
|
// Set the timeout
|
|
//
|
|
Replica->MembershipExpires = Now + ReplicaTombstoneInFileTime;
|
|
|
|
//
|
|
// Update the database record
|
|
//
|
|
Replica->NeedsUpdate = TRUE;
|
|
RcsUpdateReplicaSetMember(Replica);
|
|
//
|
|
// The above friggen call set NeedsUpdate to false if the update succeeded.
|
|
//
|
|
|
|
//
|
|
// Couldn't update; reset the timeout and retry at the next ds poll
|
|
//
|
|
if (Replica->NeedsUpdate) {
|
|
Replica->MembershipExpires = 0;
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set registry value "FilesNotToBackup"
|
|
//
|
|
CfgFilesNotToBackup(ReplicasByGuid);
|
|
|
|
//
|
|
// Tombstoned; update the registry
|
|
//
|
|
RcsReplicaSetRegistry(Replica);
|
|
|
|
//
|
|
// Start the unjoin process on all cxtions
|
|
//
|
|
CxtionKey = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Force unjoin cxtion");
|
|
RcsForceUnjoin(Replica, Cxtion);
|
|
}
|
|
|
|
//
|
|
// Are all cxtions unjoined?
|
|
//
|
|
CxtionKey = NULL;
|
|
Cxtion = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
if (!CxtionStateIs(Cxtion, CxtionStateUnjoined) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Not all cxtions are unjoined (or deleted). Try again at the
|
|
// next ds polling cycle IFF the set is still deleted. Return
|
|
// success since the set has been successfully marked as
|
|
// tombstoned.
|
|
if (Cxtion) {
|
|
//
|
|
// Complete the old command packet in case another thread
|
|
// is waiting on it (like FrsDsStartDemotion()).
|
|
//
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Retry delete later");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
|
|
//
|
|
// Allocate a new command packet that will retry the delete
|
|
// as long as the replica set remains tombstoned.
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, CMD_DELETE_RETRY);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
RsReplica(Cmd) = Replica;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, CMD_DELETE_RETRY_SHORT_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// All of the cxtions were successfully unjoined. Shut down the replica.
|
|
//
|
|
|
|
//
|
|
// Stop accepting comm packets. Errors are returned to our partner.
|
|
//
|
|
// IsAccepting will become TRUE again in RcsStartReplicaSetMember() before
|
|
// any cxtion rejoins.
|
|
//
|
|
Replica->IsAccepting = FALSE;
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica is NOT Accepting");
|
|
|
|
//
|
|
// Stop journal processing and close the replica.
|
|
//
|
|
RcsSubmitStopReplicaToDb(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsOpen, "IsOpen, Replica closed");
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->FStatus, "F, Replica still open");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
} else {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica closed");
|
|
}
|
|
|
|
//
|
|
// "Delete" the cxtions from active processing. RcsOpenReplicaSetMember() will
|
|
// generate new cxtion structs before active processing (aka joining)
|
|
// occurs. The cxtions remain in the DeletedCxtions table so that
|
|
// other threads that may have been time sliced just after acquiring
|
|
// the cxtion pointer but before making use of it will not AV. We
|
|
// could have put in locks but then there would be deadlock and
|
|
// perf considerations; all for the sake of this seldom occuring
|
|
// operation.
|
|
//
|
|
RcsCloseReplicaCxtions(Replica);
|
|
|
|
//
|
|
// Increment the Replica Sets deleted counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, RSDeleted, 1);
|
|
|
|
//
|
|
// Has the tombstone expired?
|
|
//
|
|
if (RcsReplicaHasExpired(Replica)) {
|
|
RcsSubmitTransferToRcs(Cmd, CMD_DELETE_NOW);
|
|
} else {
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsDeleteReplicaSetMemberNow(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Bring the replica down to an unjoined, idle status. Don't reanimate.
|
|
|
|
Arguments:
|
|
Cmd.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsDeleteReplicaSetMemberNow:"
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
PREPLICA Replica;
|
|
PVOID CxtionKey;
|
|
PCXTION Cxtion;
|
|
ULONGLONG OldMembershipExpires;
|
|
|
|
//
|
|
// Check the command packet
|
|
//
|
|
if (!RcsCheckCmd(Cmd, DEBSUB, CHECK_CMD_REPLICA)) {
|
|
return;
|
|
}
|
|
Replica = RsReplica(Cmd);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, RcsDeleteReplicaSetMemberNow entry");
|
|
|
|
//
|
|
// Time in 100nsec tics since Jan 1, 1601
|
|
//
|
|
GetSystemTimeAsFileTime(&FileTime);
|
|
COPY_TIME(&Now, &FileTime);
|
|
|
|
if (IS_TIME_ZERO(Replica->MembershipExpires) || Replica->MembershipExpires >= Now) {
|
|
|
|
//
|
|
// Never reanimate this replica.
|
|
//
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Marking Replica expired");
|
|
RcsOpenReplicaSetMember(Replica);
|
|
OldMembershipExpires = Replica->MembershipExpires;
|
|
Replica->MembershipExpires = Now - ONEDAY;
|
|
Replica->NeedsUpdate = TRUE;
|
|
RcsUpdateReplicaSetMember(Replica);
|
|
//
|
|
// The above friggen call set NeedsUpdate to false if the update succeeded.
|
|
//
|
|
|
|
//
|
|
// Replica cannot be updated in the DB. Give up.
|
|
//
|
|
if (Replica->NeedsUpdate) {
|
|
Replica->MembershipExpires = OldMembershipExpires;
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// Set registry value "FilesNotToBackup"
|
|
//
|
|
CfgFilesNotToBackup(ReplicasByGuid);
|
|
|
|
//
|
|
// Remove registry entry
|
|
//
|
|
RcsReplicaDeleteRegistry(Replica);
|
|
|
|
//
|
|
// Start the unjoin process on all cxtions
|
|
//
|
|
CxtionKey = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Force unjoin cxtion");
|
|
RcsForceUnjoin(Replica, Cxtion);
|
|
}
|
|
|
|
//
|
|
// Are all cxtions unjoined?
|
|
//
|
|
CxtionKey = NULL;
|
|
Cxtion = NULL;
|
|
while ((!FrsIsShuttingDown) &&
|
|
(Cxtion = GTabNextDatum(Replica->Cxtions, &CxtionKey))) {
|
|
if (!CxtionStateIs(Cxtion, CxtionStateUnjoined) &&
|
|
!CxtionStateIs(Cxtion, CxtionStateDeleted)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Not all cxtions are unjoined (or deleted). Try again at the
|
|
// next ds polling cycle IFF the set is still deleted. Return
|
|
// success since the set has been successfully marked as
|
|
// do-not-animate.
|
|
//
|
|
if (Cxtion) {
|
|
//
|
|
// Complete the old command packet in case another thread
|
|
// is waiting on it (like FrsDsStartDemotion()).
|
|
//
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Retry delete later");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
|
|
//
|
|
// Allocate a new command packet that will retry the delete
|
|
// as long as the replica set remains tombstoned.
|
|
//
|
|
Cmd = FrsAllocCommand(Replica->Queue, CMD_DELETE_RETRY);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
RsReplica(Cmd) = Replica;
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, CMD_DELETE_RETRY_SHORT_TIMEOUT);
|
|
return;
|
|
}
|
|
//
|
|
// All of the cxtions were successfully unjoined. Shut down the replica.
|
|
//
|
|
|
|
//
|
|
// Stop accepting comm packets. Errors are returned to our partner.
|
|
//
|
|
Replica->IsAccepting = FALSE;
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica is NOT Accepting");
|
|
|
|
//
|
|
// Stop journal processing and close the replica.
|
|
//
|
|
RcsSubmitStopReplicaToDb(Replica);
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->IsOpen, "IsOpen, Replica closed");
|
|
if (Replica->IsOpen) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, Replica->FStatus, "F, Replica still open");
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
} else {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Replica closed");
|
|
}
|
|
|
|
//
|
|
// "Delete" the cxtions from active processing. RcsOpenReplicaSetMember() will
|
|
// generate new cxtion structs before active processing (aka joining)
|
|
// occurs. The cxtions remain in the DeletedCxtions table so that
|
|
// other threads that may have been time sliced just after acquiring
|
|
// the cxtion pointer but before making use of it will not AV. We
|
|
// could have put in locks but then there would be deadlock and
|
|
// perf considerations; all for the sake of this seldom occuring
|
|
// operation.
|
|
//
|
|
RcsCloseReplicaCxtions(Replica);
|
|
|
|
//
|
|
// Delete the replica set tables in the db.
|
|
//
|
|
RcsDeleteReplicaFromDb(Replica);
|
|
|
|
//
|
|
// Remove the replica from any in-memory tables that it might be in.
|
|
//
|
|
if (RcsFindReplicaByGuid(Replica->ReplicaName->Guid) != NULL) {
|
|
GTabDelete(ReplicasByGuid, Replica->ReplicaName->Guid, NULL, NULL);
|
|
}
|
|
|
|
if (RcsFindReplicaByNumber(Replica->ReplicaNumber) != NULL) {
|
|
GTabDelete(ReplicasByNumber, &Replica->ReplicaNumber, NULL, NULL);
|
|
}
|
|
|
|
//
|
|
// Increment the Replica Sets removed counter
|
|
//
|
|
PM_INC_CTR_SERVICE(PMTotalInst, RSRemoved, 1);
|
|
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsStartValidReplicaSetMembers(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Tell the replicas to bring themselves up to joined, active status.
|
|
This includes the journal and joining with inbound partners.
|
|
|
|
This routine is run once at service startup and then once
|
|
an hour to check the schedule.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsStartValidReplicaSetMembers:"
|
|
PVOID Key;
|
|
PREPLICA Replica;
|
|
ULONG TimeOut;
|
|
SYSTEMTIME SystemTime;
|
|
|
|
DPRINT1(4, ":S: Command start replicas waiting %08x\n", Cmd);
|
|
WaitForSingleObject(ReplicaEvent, INFINITE);
|
|
DPRINT1(4, ":S: Command start replicas %08x\n", Cmd);
|
|
|
|
//
|
|
// Send a start command to each replica. Replicas that are already
|
|
// started or starting will ignore the command. The replicas will
|
|
// check their join status and rejoin if needed.
|
|
//
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Submit Start Replica");
|
|
RcsSubmitReplica(Replica, NULL, CMD_START);
|
|
}
|
|
|
|
//
|
|
// Milliseconds till the next hour
|
|
//
|
|
GetSystemTime(&SystemTime);
|
|
TimeOut = ((60 - SystemTime.wMinute) * 60000) +
|
|
((60 - SystemTime.wSecond) * 1000) +
|
|
(1000 - SystemTime.wMilliseconds);
|
|
DPRINT1(4, "Check schedules in %d seconds\n", TimeOut / 1000);
|
|
#if DBG
|
|
if (DebugInfo.Interval) {
|
|
TimeOut = DebugInfo.Interval * 1000;
|
|
DPRINT1(0, ":X: DEBUG - toggle schedules in %d seconds\n", TimeOut / 1000);
|
|
}
|
|
#endif DBG
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, TimeOut);
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsSubmitCmdPktToRcsQueue(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCOMM_PACKET CommPkt,
|
|
IN PWCHAR AuthClient,
|
|
IN PWCHAR AuthName,
|
|
IN PWCHAR AuthSid,
|
|
IN DWORD AuthLevel,
|
|
IN DWORD AuthN,
|
|
IN DWORD AuthZ
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Convert a comm packet into a command packet and send
|
|
it to the correct replica set. NOTE - RPC owns the comm
|
|
packet.
|
|
|
|
Arguments:
|
|
CommPkt
|
|
AuthClient
|
|
AuthName
|
|
AuthSid
|
|
AuthLevel
|
|
AuthN
|
|
AuthZ
|
|
|
|
Return Value:
|
|
Error status to be propagated to the sender
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitCmdPktToRcsQueue:"
|
|
DWORD WStatus;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion = NULL;
|
|
ULONG WaitTime;
|
|
ULONG NumWaitingCOs;
|
|
ULONGLONG *pFileTime = NULL;
|
|
|
|
|
|
//
|
|
// Must have the replica's name in order to queue the command
|
|
//
|
|
if (RsReplicaName(Cmd) == NULL || RsReplicaName(Cmd)->Name == NULL ) {
|
|
COMMAND_RCV_TRACE(3, Cmd, Cxtion, ERROR_INVALID_NAME, "RcvFail - no replica name");
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_NAME);
|
|
return ERROR_INVALID_NAME;
|
|
}
|
|
//
|
|
// Find the target replica
|
|
//
|
|
Replica = GTabLookup(ReplicasByGuid, RsReplicaName(Cmd)->Guid, NULL);
|
|
if (Replica == NULL) {
|
|
COMMAND_RCV_TRACE(4, Cmd, Cxtion, ERROR_FILE_NOT_FOUND, "RcvFail - replica not found");
|
|
FrsCompleteCommand(Cmd, ERROR_FILE_NOT_FOUND);
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// The target replica may not be accepting comm packets
|
|
//
|
|
if (!Replica->IsAccepting) {
|
|
COMMAND_RCV_TRACE(4, Cmd, Cxtion, ERROR_RETRY, "RcvFail - not accepting");
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return ERROR_RETRY;
|
|
}
|
|
|
|
//
|
|
// Find the cxtion
|
|
//
|
|
if (RsCxtion(Cmd)) {
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
Cxtion = GTabLookupNoLock(Replica->Cxtions, RsCxtion(Cmd)->Guid, NULL);
|
|
if (Cxtion != NULL) {
|
|
Cxtion->PartnerMajor = CommPkt->Major;
|
|
Cxtion->PartnerMinor = CommPkt->Minor;
|
|
|
|
//
|
|
// The count of comm packets is used to detect a hung outbound
|
|
// cxtion (See CMD_HUNG_CXTION). The hang is most likely caused by
|
|
// a dropped ack.
|
|
//
|
|
Cxtion->CommPkts++;
|
|
|
|
//
|
|
// This change order may already exist on this machine. If so,
|
|
// use its change order entry because we will need its database
|
|
// context for updates.
|
|
//
|
|
LOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
if (RsCoGuid(Cmd)) {
|
|
RsCoe(Cmd) = GTabLookupNoLock(Cxtion->CoeTable, RsCoGuid(Cmd), NULL);
|
|
//
|
|
// Note this command should be a response to a stage file
|
|
// fetch request. As such the Command code should be one of
|
|
// the following:
|
|
// CMD_RECEIVING_STAGE, CMD_RETRY_FETCH, or CMD_ABORT_FETCH.
|
|
//
|
|
// Also this response should have the same data that we are expecting.
|
|
// Consider a case of a outstanding request for staging data. Now we
|
|
// rejoin and send the request again (Win2K SP3 and greater), now
|
|
// we can get responses to both the requests. We do not want to start
|
|
// two parallel chain of requests for subsequent data in the staging file.
|
|
//
|
|
// If it is anything else then don't pull it out of the table.
|
|
//
|
|
// This check is needed because if we have just restarted this
|
|
// connection then this CO could be a resend of a CO that was
|
|
// already restarted from the Inbound log. Without the Command
|
|
// check we would incorrectly attach this Coe state to the wrong
|
|
// command packet and possibly cancel the timeout check.
|
|
//
|
|
if (RsCoe(Cmd) && ((Cmd->Command == CMD_RECEIVING_STAGE) ||
|
|
(Cmd->Command == CMD_RETRY_FETCH) ||
|
|
(Cmd->Command == CMD_ABORT_FETCH))) {
|
|
GTabDeleteNoLock(Cxtion->CoeTable, RsCoGuid(Cmd), NULL, NULL);
|
|
} else {
|
|
RsCoe(Cmd) = NULL;
|
|
}
|
|
}
|
|
|
|
NumWaitingCOs = GTabNumberInTable(Cxtion->CoeTable);
|
|
UNLOCK_CXTION_COE_TABLE(Replica, Cxtion);
|
|
|
|
//
|
|
// There is no need to keep a timeout pending if there are no
|
|
// idle change orders. Otherwise, bump the timeout since we
|
|
// have received something from our partner.
|
|
//
|
|
// Note: Need better filter for commands that aren't ACKs.
|
|
//
|
|
if (CxtionFlagIs(Cxtion, CXTION_FLAGS_TIMEOUT_SET)) {
|
|
|
|
//
|
|
// :SP1: Volatile connection cleanup.
|
|
//
|
|
// A volatile connection is used to seed sysvols after dcpromo.
|
|
// If there is inactivity on a volatile outbound connection for
|
|
// more than FRS_VOLATILE_CONNECTION_MAX_IDLE_TIME then this
|
|
// connection is unjoined. An unjoin on a volatile outbound
|
|
// connection triggers a delete on that connection. This is to
|
|
// prevent the case where staging files are kept for ever on the
|
|
// parent for a volatile connection.
|
|
//
|
|
WaitTime = (VOLATILE_OUTBOUND_CXTION(Cxtion) ?
|
|
FRS_VOLATILE_CONNECTION_MAX_IDLE_TIME :
|
|
CommTimeoutInMilliSeconds);
|
|
|
|
if (VOLATILE_OUTBOUND_CXTION(Cxtion)) {
|
|
GetSystemTimeAsFileTime((PFILETIME)&SRTimeoutSetTime(Cxtion->CommTimeoutCmd));
|
|
SRLastJoinTime(Cxtion->CommTimeoutCmd) = Cxtion->LastJoinTime;
|
|
WaitSubmit(Cxtion->CommTimeoutCmd, WaitTime, CMD_DELAYED_COMPLETE);
|
|
} else
|
|
|
|
if (NumWaitingCOs > 0) {
|
|
if (RsCoe(Cmd) != NULL) {
|
|
//
|
|
// Extend the timer since we still have outstanding
|
|
// change orders that need a response from our partner
|
|
// and the current comm packet is responding to one of
|
|
// those change orders.
|
|
//
|
|
GetSystemTimeAsFileTime((PFILETIME)&SRTimeoutSetTime(Cxtion->CommTimeoutCmd));
|
|
SRLastJoinTime(Cxtion->CommTimeoutCmd) = Cxtion->LastJoinTime;
|
|
|
|
WaitSubmit(Cxtion->CommTimeoutCmd, WaitTime, CMD_DELAYED_COMPLETE);
|
|
}
|
|
} else
|
|
|
|
if (Cmd->Command != CMD_START_JOIN) {
|
|
//
|
|
// Not volatile outbound and no COs in CoeTable waiting for
|
|
// a response so disable the cxtion timer... but
|
|
// CMD_START_JOIN's do not disable the join timer because
|
|
// they aren't sent in response to a request by this
|
|
// service. Disabling the timer might hang the service as
|
|
// it waits for a response that never comes and a timeout
|
|
// that never hits.
|
|
//
|
|
ClearCxtionFlag(Cxtion, CXTION_FLAGS_TIMEOUT_SET);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
COMMAND_RCV_TRACE(4, Cmd, Cxtion, ERROR_SUCCESS, "RcvFail - cxtion not found");
|
|
}
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
if (Cxtion != NULL) {
|
|
COMMAND_RCV_TRACE(4, Cmd, Cxtion, ERROR_SUCCESS, "RcvSuccess");
|
|
}
|
|
|
|
} else {
|
|
COMMAND_RCV_TRACE(4, Cmd, Cxtion, ERROR_SUCCESS, "RcvFail - no cxtion");
|
|
}
|
|
|
|
//
|
|
// Update the command with the replica pointer and the change order
|
|
// command with the local the replica number.
|
|
//
|
|
if (RsPartnerCoc(Cmd)) {
|
|
RsPartnerCoc(Cmd)->NewReplicaNum = ReplicaAddrToId(Replica);
|
|
//
|
|
// We will never see a remotely generated MOVERS. We always
|
|
// see a delete to the old RS followed by a create in the
|
|
// new RS. So set both replica ptrs to our Replica struct.
|
|
//
|
|
RsPartnerCoc(Cmd)->OriginalReplicaNum = ReplicaAddrToId(Replica);
|
|
}
|
|
RsReplica(Cmd) = Replica;
|
|
|
|
//
|
|
// Authentication info
|
|
//
|
|
RsAuthClient(Cmd) = FrsWcsDup(AuthClient);
|
|
RsAuthName(Cmd) = FrsWcsDup(AuthName);
|
|
RsAuthLevel(Cmd) = AuthLevel;
|
|
RsAuthN(Cmd) = AuthN;
|
|
RsAuthZ(Cmd) = AuthZ;
|
|
|
|
switch(Cmd->Command) {
|
|
|
|
case CMD_JOINING:
|
|
//
|
|
// This is a Joining packet so save the current time
|
|
// in the Cmd as the receive time. This time is used to make
|
|
// the time skew check.
|
|
//
|
|
pFileTime = FrsAlloc(sizeof(FILETIME));
|
|
GetSystemTimeAsFileTime((FILETIME *)pFileTime);
|
|
RsCommPktRcvTime(Cmd) = pFileTime;
|
|
|
|
// Intentional fall through.
|
|
|
|
case CMD_NEED_JOIN:
|
|
case CMD_START_JOIN:
|
|
case CMD_JOINED:
|
|
|
|
case CMD_UNJOIN_REMOTE:
|
|
//
|
|
// Set user sid
|
|
//
|
|
RsAuthSid(Cmd) = FrsWcsDup(AuthSid);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Put the command on the replica's queue
|
|
//
|
|
Cmd->TargetQueue = Replica->Queue;
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsInitKnownReplicaSetMembers(
|
|
IN PCOMMAND_PACKET Cmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Wait for the database to be initialized and then take the
|
|
replicas that were retrieved from the database and put them
|
|
into the table that the replica control command server
|
|
uses. Open the replicas. The journal will be started when
|
|
a replica successfully joins with an outbound partner.
|
|
|
|
Arguments:
|
|
Cmd
|
|
|
|
Return Value:
|
|
winerror
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsInitKnownReplicaSetMembers:"
|
|
FILETIME FileTime;
|
|
ULONGLONG Now;
|
|
GUID Record0Guid;
|
|
PREPLICA Replica, NextReplica;
|
|
PVOID Key;
|
|
ULONG RootLen;
|
|
|
|
//
|
|
// Time in 100nsec tics since Jan 1, 1601
|
|
//
|
|
GetSystemTimeAsFileTime(&FileTime);
|
|
COPY_TIME(&Now, &FileTime);
|
|
|
|
//
|
|
// A non-empty table implies multiple calls to this
|
|
// routine our an out-of-sequence call to this routine.
|
|
//
|
|
FRS_ASSERT(GTabNumberInTable(ReplicasByGuid) == 0);
|
|
FRS_ASSERT(GTabNumberInTable(ReplicasByNumber) == 0);
|
|
|
|
//
|
|
// Wait for the database, journal, and comm subsystem to start up
|
|
//
|
|
WaitForSingleObject(CommEvent, INFINITE);
|
|
WaitForSingleObject(DataBaseEvent, INFINITE);
|
|
WaitForSingleObject(JournalEvent, INFINITE);
|
|
WaitForSingleObject(ChgOrdEvent, INFINITE);
|
|
|
|
if (FrsIsShuttingDown) {
|
|
FrsCompleteCommand(Cmd, ERROR_OPERATION_ABORTED);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Delete the contents of the Replica Set key in the registry
|
|
// and refresh with the current info in the database.
|
|
//
|
|
RcsReplicaClearRegistry();
|
|
|
|
//
|
|
// The database built a list of replicas. Insert them into the tables
|
|
//
|
|
ForEachListEntry(&ReplicaListHead, REPLICA, ReplicaList,
|
|
// loop iterator pE is of type REPLICA
|
|
|
|
//
|
|
// Replica was opened as part of the database initialization
|
|
//
|
|
pE->IsOpen = TRUE;
|
|
REPLICA_STATE_TRACE(3, Cmd, pE, pE->IsOpen, "B, Replica opened");
|
|
|
|
//
|
|
// The timeout for the deleted replica has expired; attempt to
|
|
// delete the replica from the database. The delete will be
|
|
// retried at the next startup if this delete fails. In any case,
|
|
// the replica does not show up in the active set of replicas
|
|
// and so is ignored by all further processing except for shutdown.
|
|
//
|
|
if (RcsReplicaIsRestored(pE) ||
|
|
(!IS_TIME_ZERO(pE->MembershipExpires) && pE->MembershipExpires < Now)) {
|
|
GTabInsertEntry(DeletedReplicas, pE, pE->ReplicaName->Guid, NULL);
|
|
continue;
|
|
}
|
|
//
|
|
// Insert replica information into the registry
|
|
//
|
|
RcsReplicaSetRegistry(pE);
|
|
|
|
//
|
|
// Create a queue
|
|
//
|
|
pE->Queue = FrsAlloc(sizeof(FRS_QUEUE));
|
|
FrsInitializeQueue(pE->Queue, &ReplicaCmdServer.Control);
|
|
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, pE, 0, "F, Replica added to GUID table");
|
|
//
|
|
// Table by guid
|
|
//
|
|
GTabInsertEntry(ReplicasByGuid, pE, pE->ReplicaName->Guid, NULL);
|
|
//
|
|
// Add ReplicaSet Instance to the registry
|
|
//
|
|
if (pE->Root != NULL) {
|
|
DPRINT(5, ":S: PERFMON:Adding Set:REPLICA.C:2\n");
|
|
AddPerfmonInstance(REPLICASET, pE->PerfRepSetData, pE->Root);
|
|
}
|
|
|
|
//
|
|
// Table by number
|
|
//
|
|
GTabInsertEntry(ReplicasByNumber, pE, &pE->ReplicaNumber, NULL);
|
|
);
|
|
|
|
//
|
|
// Now account for the replica sets that are on the fault list.
|
|
//
|
|
ForEachListEntry(&ReplicaFaultListHead, REPLICA, ReplicaList,
|
|
// loop iterator pE is of type REPLICA
|
|
|
|
//
|
|
// Replica was opened as part of the database initialization
|
|
//
|
|
pE->IsOpen = FALSE;
|
|
REPLICA_STATE_TRACE(3, Cmd, pE, pE->IsOpen, "B, Replica not opened");
|
|
|
|
//
|
|
// The timeout for the deleted replica has expired; attempt to
|
|
// delete the replica from the database. The delete will be
|
|
// retried at the next startup if this delete fails. In any case,
|
|
// the replica does not show up in the active set of replicas
|
|
// and so is ignored by all further processing except for shutdown.
|
|
//
|
|
if (RcsReplicaIsRestored(pE) ||
|
|
(!IS_TIME_ZERO(pE->MembershipExpires) && pE->MembershipExpires < Now)) {
|
|
GTabInsertEntry(DeletedReplicas, pE, pE->ReplicaName->Guid, NULL);
|
|
continue;
|
|
}
|
|
//
|
|
// Insert replica information into the registry
|
|
//
|
|
RcsReplicaSetRegistry(pE);
|
|
|
|
//
|
|
// Create a queue
|
|
//
|
|
pE->Queue = FrsAlloc(sizeof(FRS_QUEUE));
|
|
FrsInitializeQueue(pE->Queue, &ReplicaCmdServer.Control);
|
|
|
|
|
|
REPLICA_STATE_TRACE(3, Cmd, pE, 0, "F, Replica added to GUID table");
|
|
//
|
|
// Table by guid
|
|
//
|
|
GTabInsertEntry(ReplicasByGuid, pE, pE->ReplicaName->Guid, NULL);
|
|
|
|
//
|
|
// Table by number
|
|
//
|
|
GTabInsertEntry(ReplicasByNumber, pE, &pE->ReplicaNumber, NULL);
|
|
);
|
|
|
|
|
|
|
|
//
|
|
// Set the registry value "FilesNotToBackup"
|
|
//
|
|
CfgFilesNotToBackup(ReplicasByGuid);
|
|
|
|
//
|
|
// Close the expired replica sets
|
|
//
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
if (!IS_TIME_ZERO(Replica->MembershipExpires)) {
|
|
//
|
|
// Close replica is OK here as long as the Journal has not been started
|
|
// on this replica set.
|
|
//
|
|
RcsCloseReplicaSetmember(Replica);
|
|
RcsCloseReplicaCxtions(Replica);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Delete the replica sets with expired tombstones
|
|
//
|
|
Key = NULL;
|
|
for (Replica = GTabNextDatum(DeletedReplicas, &Key);
|
|
Replica;
|
|
Replica = NextReplica) {
|
|
|
|
NextReplica = GTabNextDatum(DeletedReplicas, &Key);
|
|
|
|
//
|
|
// Replica number 0 is reserved for the template tables in post
|
|
// WIN2K but Databases built with Win2K can still use replica
|
|
// number 0.
|
|
//
|
|
// Record 0 contains the DB templates. Don't delete it but
|
|
// change its fields so that it won't interfere with the
|
|
// creation of other replica set.
|
|
// Note: New databases will not use replica number zero but old
|
|
// databases will. To avoid name conflicts with replica sets
|
|
// created in the future we still need to overwrite a few
|
|
// fields in config record zero.
|
|
//
|
|
if (Replica->ReplicaNumber == DBS_TEMPLATE_TABLE_NUMBER) {
|
|
|
|
if (WSTR_NE(Replica->ReplicaName->Name, NTFRS_RECORD_0)) {
|
|
//
|
|
// Pull the entry out of the table since we are changing the
|
|
// replica guid below. (makes the saved guid ptr in the entry
|
|
// invalid and can lead to access violation).
|
|
//
|
|
GTabDelete(DeletedReplicas, Replica->ReplicaName->Guid, NULL, NULL);
|
|
|
|
FrsUuidCreate(&Record0Guid);
|
|
//
|
|
// ReplicaName
|
|
//
|
|
FrsFreeGName(Replica->ReplicaName);
|
|
Replica->ReplicaName = FrsBuildGName(FrsDupGuid(&Record0Guid),
|
|
FrsWcsDup(NTFRS_RECORD_0));
|
|
//
|
|
// MemberName
|
|
//
|
|
FrsFreeGName(Replica->MemberName);
|
|
Replica->MemberName = FrsBuildGName(FrsDupGuid(&Record0Guid),
|
|
FrsWcsDup(NTFRS_RECORD_0));
|
|
//
|
|
// SetName
|
|
//
|
|
FrsFreeGName(Replica->SetName);
|
|
Replica->SetName = FrsBuildGName(FrsDupGuid(&Record0Guid),
|
|
FrsWcsDup(NTFRS_RECORD_0));
|
|
//
|
|
// SetType
|
|
//
|
|
Replica->ReplicaSetType = FRS_RSTYPE_OTHER;
|
|
|
|
//
|
|
// Root (to avoid failing a replica create because of
|
|
// overlapping roots)
|
|
//
|
|
FrsFree(Replica->Root);
|
|
Replica->Root = FrsWcsDup(NTFRS_RECORD_0_ROOT);
|
|
|
|
//
|
|
// Stage (to avoid failing a replica create because of
|
|
// overlapping Stages)
|
|
//
|
|
FrsFree(Replica->Stage);
|
|
Replica->Stage = FrsWcsDup(NTFRS_RECORD_0_STAGE);
|
|
|
|
//
|
|
// Update
|
|
//
|
|
Replica->NeedsUpdate = TRUE;
|
|
RcsUpdateReplicaSetMember(Replica);
|
|
//
|
|
// The above friggen call set NeedsUpdate to false if the update succeeded.
|
|
//
|
|
if (Replica->NeedsUpdate) {
|
|
DPRINT(0, ":S: ERROR - Can't update record 0.\n");
|
|
}
|
|
|
|
//
|
|
// Insert the entry back into the table with the new Guid index.
|
|
//
|
|
GTabInsertEntry(DeletedReplicas,
|
|
Replica,
|
|
Replica->ReplicaName->Guid,
|
|
NULL);
|
|
|
|
}
|
|
RcsCloseReplicaSetmember(Replica);
|
|
RcsCloseReplicaCxtions(Replica);
|
|
} else {
|
|
REPLICA_STATE_TRACE(3, Cmd, Replica, 0, "F, Deleting Tombstoned replica");
|
|
|
|
//
|
|
// Close replica is OK here as long as the Journal has not
|
|
// been started on this replica set.
|
|
//
|
|
RcsCloseReplicaSetmember(Replica);
|
|
RcsCloseReplicaCxtions(Replica);
|
|
RcsDeleteReplicaFromDb(Replica);
|
|
}
|
|
}
|
|
|
|
SetEvent(ReplicaEvent);
|
|
|
|
//
|
|
// Check the schedules every so often
|
|
//
|
|
Cmd->Command = CMD_CHECK_SCHEDULES;
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
|
|
//
|
|
// Free up memory by reducing our working set size
|
|
//
|
|
SetProcessWorkingSetSize(ProcessHandle, (SIZE_T)-1, (SIZE_T)-1);
|
|
}
|
|
|
|
|
|
DWORD
|
|
RcsExitThread(
|
|
PFRS_THREAD FrsThread
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Immediate cancel of all outstanding RPC calls for the thread
|
|
identified by FrsThread. Set the tombstone to 5 seconds from
|
|
now. If this thread does not exit within that time, any calls
|
|
to ThSupWaitThread() will return a timeout error.
|
|
|
|
Arguments:
|
|
FrsThread
|
|
|
|
Return Value:
|
|
ERROR_SUCCESS
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsExitThread:"
|
|
|
|
ULONG WStatus;
|
|
|
|
if (HANDLE_IS_VALID(FrsThread->Handle)) {
|
|
DPRINT1(4, ":S: Canceling RPC requests for thread %ws\n", FrsThread->Name);
|
|
WStatus = RpcCancelThread(FrsThread->Handle);
|
|
if (!RPC_SUCCESS(WStatus)) {
|
|
DPRINT_WS(0, ":S: RpcCancelThread failed.", WStatus);
|
|
}
|
|
}
|
|
|
|
return ThSupExitWithTombstone(FrsThread);
|
|
}
|
|
|
|
|
|
#if _MSC_FULL_VER >= 13008827
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
|
|
#endif
|
|
|
|
DWORD
|
|
RcsMain(
|
|
PVOID Arg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Entry point for replica control command server thread
|
|
|
|
Arguments:
|
|
Arg - thread
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsMain:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PCOMMAND_SERVER Cs;
|
|
PFRS_QUEUE IdledQueue;
|
|
ULONG Status;
|
|
PFRS_THREAD FrsThread = (PFRS_THREAD)Arg;
|
|
DWORD WStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Try-Finally
|
|
//
|
|
try {
|
|
|
|
//
|
|
// Capture exception.
|
|
//
|
|
try {
|
|
|
|
FRS_ASSERT(FrsThread->Data == &ReplicaCmdServer);
|
|
//
|
|
// Immediate cancel of outstanding RPC calls during shutdown
|
|
//
|
|
WStatus = RpcMgmtSetCancelTimeout(0);
|
|
DPRINT_WS(0, "Timeout cancel failed.", WStatus);
|
|
|
|
FrsThread->Exit = RcsExitThread;
|
|
|
|
cant_exit_yet:
|
|
//
|
|
// Replica Control Command Server
|
|
// Controls access to the database entries
|
|
//
|
|
while (Cmd = FrsGetCommandServerIdled(&ReplicaCmdServer, &IdledQueue)) {
|
|
switch (Cmd->Command) {
|
|
//
|
|
// INITIALIZATION, STARTUP, AND CONFIGURATION
|
|
//
|
|
case CMD_INIT_SUBSYSTEM:
|
|
DPRINT(0, ":S: Replica subsystem is starting.\n");
|
|
DPRINT1(4, ":S: Command init subsystem %08x\n", Cmd);
|
|
RcsInitKnownReplicaSetMembers(Cmd);
|
|
if (!FrsIsShuttingDown) {
|
|
DPRINT(0, ":S: Replica subsystem has started.\n");
|
|
}
|
|
break;
|
|
|
|
case CMD_CHECK_SCHEDULES:
|
|
RcsCheckSchedules(Cmd);
|
|
break;
|
|
|
|
case CMD_START_REPLICAS:
|
|
RcsStartValidReplicaSetMembers(Cmd);
|
|
break;
|
|
|
|
case CMD_START:
|
|
RcsStartReplicaSetMember(Cmd);
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
RcsDeleteReplicaSetMember(Cmd);
|
|
break;
|
|
|
|
case CMD_DELETE_RETRY:
|
|
RcsDeleteReplicaRetry(Cmd);
|
|
break;
|
|
|
|
case CMD_DELETE_NOW:
|
|
RcsDeleteReplicaSetMemberNow(Cmd);
|
|
break;
|
|
|
|
//
|
|
// CHANGE ORDERS
|
|
//
|
|
|
|
case CMD_LOCAL_CO_ACCEPTED:
|
|
RcsLocalCoAccepted(Cmd);
|
|
break;
|
|
|
|
case CMD_REMOTE_CO:
|
|
RcsRemoteCoReceived(Cmd);
|
|
break;
|
|
|
|
case CMD_REMOTE_CO_ACCEPTED:
|
|
RcsRemoteCoAccepted(Cmd);
|
|
break;
|
|
|
|
case CMD_SEND_STAGE:
|
|
RcsSendStageFile(Cmd);
|
|
break;
|
|
|
|
case CMD_SENDING_STAGE:
|
|
RcsSendingStageFile(Cmd);
|
|
break;
|
|
|
|
case CMD_RECEIVING_STAGE:
|
|
RcsReceivingStageFile(Cmd);
|
|
break;
|
|
|
|
case CMD_CREATED_EXISTING:
|
|
RcsSendStageFileRequest(Cmd);
|
|
break;
|
|
|
|
case CMD_RECEIVED_STAGE:
|
|
RcsReceivedStageFile(Cmd, CHECK_CXTION_AUTH);
|
|
break;
|
|
|
|
case CMD_REMOTE_CO_DONE:
|
|
RcsRemoteCoDoneRvcd(Cmd);
|
|
break;
|
|
|
|
case CMD_SEND_ABORT_FETCH:
|
|
RcsSendAbortFetch(Cmd);
|
|
break;
|
|
|
|
case CMD_ABORT_FETCH:
|
|
RcsAbortFetch(Cmd);
|
|
break;
|
|
|
|
case CMD_SEND_RETRY_FETCH:
|
|
RcsSendRetryFetch(Cmd);
|
|
break;
|
|
|
|
case CMD_RETRY_STAGE:
|
|
RcsRetryStageFileCreate(Cmd);
|
|
break;
|
|
|
|
case CMD_RETRY_FETCH:
|
|
RcsRetryFetch(Cmd);
|
|
break;
|
|
|
|
//
|
|
// JOINING
|
|
//
|
|
case CMD_NEED_JOIN:
|
|
RcsNeedJoin(Cmd);
|
|
break;
|
|
|
|
case CMD_START_JOIN:
|
|
RcsStartJoin(Cmd);
|
|
break;
|
|
|
|
case CMD_JOIN_CXTION:
|
|
RcsJoinCxtion(Cmd);
|
|
break;
|
|
|
|
case CMD_JOINING_AFTER_FLUSH:
|
|
RcsJoiningAfterFlush(Cmd);
|
|
break;
|
|
|
|
case CMD_JOINING:
|
|
RcsJoining(Cmd);
|
|
break;
|
|
|
|
case CMD_JOINED:
|
|
RcsInboundJoined(Cmd);
|
|
break;
|
|
|
|
case CMD_UNJOIN:
|
|
RcsUnJoinCxtion(Cmd, FALSE);
|
|
break;
|
|
|
|
case CMD_UNJOIN_REMOTE:
|
|
RcsUnJoinCxtion(Cmd, TRUE);
|
|
break;
|
|
|
|
case CMD_HUNG_CXTION:
|
|
RcsHungCxtion(Cmd);
|
|
break;
|
|
|
|
//
|
|
// VVJOIN
|
|
//
|
|
case CMD_VVJOIN_SUCCESS:
|
|
RcsVvJoinSuccess(Cmd);
|
|
break;
|
|
|
|
case CMD_VVJOIN_DONE:
|
|
RcsVvJoinDone(Cmd);
|
|
break;
|
|
|
|
case CMD_VVJOIN_DONE_UNJOIN:
|
|
RcsVvJoinDoneUnJoin(Cmd);
|
|
break;
|
|
|
|
case CMD_CHECK_PROMOTION:
|
|
RcsCheckPromotion(Cmd);
|
|
break;
|
|
|
|
default:
|
|
DPRINT1(0, "Replica Control: unknown command 0x%x\n", Cmd->Command);
|
|
FrsCompleteCommand(Cmd, ERROR_INVALID_FUNCTION);
|
|
break;
|
|
}
|
|
FrsRtlUnIdledQueue(IdledQueue);
|
|
}
|
|
//
|
|
// Exit
|
|
//
|
|
FrsExitCommandServer(&ReplicaCmdServer, FrsThread);
|
|
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, "RcsMain finally.", WStatus);
|
|
|
|
//
|
|
// Trigger FRS shutdown if we terminated abnormally.
|
|
//
|
|
if (!WIN_SUCCESS(WStatus) && (WStatus != ERROR_PROCESS_ABORTED)) {
|
|
DPRINT(0, "RcsMain terminated abnormally, forcing service shutdown.\n");
|
|
FrsIsShuttingDown = TRUE;
|
|
SetEvent(ShutDownEvent);
|
|
} else {
|
|
WStatus = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return WStatus;
|
|
|
|
}
|
|
#if _MSC_FULL_VER >= 13008827
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
|
|
|
|
VOID
|
|
RcsInitializeReplicaCmdServer(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Initialize the replica set command server and idle it until the
|
|
database is initialized.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsInitializeReplicaCmdServer:"
|
|
ULONG Status;
|
|
PCOMMAND_PACKET Cmd;
|
|
DWORD MaxRepThreads;
|
|
|
|
//
|
|
// Retry a join every MinJoinRetry milliseconds, doubling the interval
|
|
// every retry. Stop retrying when the interval is greater than
|
|
// MaxJoinRetry.
|
|
//
|
|
CfgRegReadDWord(FKC_MIN_JOIN_RETRY, NULL, 0, &MinJoinRetry);
|
|
DPRINT1(0, ":S: Min Join Retry : %d\n", MinJoinRetry);
|
|
|
|
CfgRegReadDWord(FKC_MAX_JOIN_RETRY, NULL, 0, &MaxJoinRetry);
|
|
DPRINT1(0, ":S: Max Join Retry : %d\n", MaxJoinRetry);
|
|
|
|
|
|
//
|
|
// The replica command server services commands for configuration changes
|
|
// and REPLICATION.
|
|
//
|
|
CfgRegReadDWord(FKC_MAX_REPLICA_THREADS, NULL, 0, &MaxRepThreads);
|
|
DPRINT1(0, ":S: Max Replica Threads : %d\n", MaxRepThreads);
|
|
|
|
//
|
|
// Start replication even if the DS could not be accessed
|
|
//
|
|
CfgRegReadDWord(FKC_REPLICA_START_TIMEOUT, NULL, 0, &ReplicaStartTimeout);
|
|
DPRINT1(0, ":S: Replica Start Timeout: %d\n", ReplicaStartTimeout);
|
|
|
|
//
|
|
// Partners are not allowed to join if their clocks are out-of-sync
|
|
//
|
|
CfgRegReadDWord(FKC_PARTNER_CLOCK_SKEW, NULL, 0, &PartnerClockSkew);
|
|
DPRINT1(0, ":S: Partner Clock Skew : %d\n", PartnerClockSkew);
|
|
|
|
MaxPartnerClockSkew = (ULONGLONG)PartnerClockSkew *
|
|
CONVERT_FILETIME_TO_MINUTES;
|
|
//
|
|
// Replica tombstone in days
|
|
//
|
|
CfgRegReadDWord(FKC_REPLICA_TOMBSTONE, NULL, 0, &ReplicaTombstone);
|
|
DPRINT1(0, ":S: Replica Tombstone : %d\n", ReplicaTombstone);
|
|
|
|
ReplicaTombstoneInFileTime = (ULONGLONG)ReplicaTombstone *
|
|
CONVERT_FILETIME_TO_DAYS;
|
|
|
|
//
|
|
// Start the Replica command server
|
|
//
|
|
FrsInitializeCommandServer(&ReplicaCmdServer, MaxRepThreads, L"ReplicaCs", RcsMain);
|
|
|
|
//
|
|
// Empty table of replicas. Existing replicas will be filled in after
|
|
// the database has started up.
|
|
//
|
|
DeletedReplicas = GTabAllocTable();
|
|
DeletedCxtions = GTabAllocTable();
|
|
ReplicasByNumber = GTabAllocNumberTable();
|
|
|
|
//
|
|
// Tell the replica command server to init.
|
|
//
|
|
Cmd = FrsAllocCommand(&ReplicaCmdServer.Queue, CMD_INIT_SUBSYSTEM);
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
|
|
//
|
|
// The DS may have changed while the service was down. In fact, the
|
|
// service may have been down because the DS was being changed. The
|
|
// current state of the configuration in the DS should be merged
|
|
// with the state in the database before replication begins. But
|
|
// the DS may not be reachable. We don't want to delay replication
|
|
// in the off chance that the DS changed, but we do want to pick
|
|
// up any changes before replication begins. Our compromise is to
|
|
// allow a few minutes for the DS to come online and then start
|
|
// replication anyway.
|
|
//
|
|
if (ReplicaStartTimeout) {
|
|
Cmd = FrsAllocCommand(&ReplicaCmdServer.Queue, CMD_START_REPLICAS);
|
|
FrsDelCsSubmitSubmit(&ReplicaCmdServer, Cmd, ReplicaStartTimeout);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsFrsUnInitializeReplicaCmdServer(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Free up the RCS memory.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsFrsUnInitializeReplicaCmdServer:"
|
|
GTabFreeTable(ReplicasByNumber, NULL);
|
|
GTabFreeTable(ReplicasByGuid, FrsFreeType);
|
|
GTabFreeTable(DeletedReplicas, FrsFreeType);
|
|
GTabFreeTable(DeletedCxtions, FrsFreeType);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsShutDownReplicaCmdServer(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Abort the replica control command server
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsShutDownReplicaCmdServer:"
|
|
PVOID Key;
|
|
PVOID SubKey;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
|
|
//
|
|
// Rundown all known queues. New queue entries will bounce.
|
|
//
|
|
Key = NULL;
|
|
while (Replica = GTabNextDatum(ReplicasByGuid, &Key)) {
|
|
REPLICA_STATE_TRACE(3, NULL, Replica, 0, "F, Rundown replica cmd srv");
|
|
FrsRunDownCommandServer(&ReplicaCmdServer, Replica->Queue);
|
|
|
|
SubKey = NULL;
|
|
while (Cxtion = GTabNextDatum(Replica->Cxtions, &SubKey)) {
|
|
if (Cxtion->VvJoinCs) {
|
|
CXTION_STATE_TRACE(3, Cxtion, Replica, 0, "F, Rundown replica VVJoin srv");
|
|
FrsRunDownCommandServer(Cxtion->VvJoinCs, &Cxtion->VvJoinCs->Queue);
|
|
}
|
|
}
|
|
}
|
|
|
|
FrsRunDownCommandServer(&ReplicaCmdServer, &ReplicaCmdServer.Queue);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsCmdPktCompletionRoutine(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PVOID Arg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Completion routine for Replica. Free the replica set info
|
|
and send the command on to the generic command packet
|
|
completion routine for freeing.
|
|
|
|
Arguments:
|
|
Cmd
|
|
Arg - Cmd->CompletionArg
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsCmdPktCompletionRoutine:"
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
|
|
DPRINT1(5, "Replica completion %08x\n", Cmd);
|
|
|
|
//
|
|
// To preserve order among change orders all subsequent change orders
|
|
// from this connection are going to the Inbound log (as a consequence
|
|
// of the Unjoin below). We will retry them later when the connection
|
|
// is restarted.
|
|
//
|
|
if (RsCoe(Cmd)) {
|
|
Coe = RsCoe(Cmd);
|
|
RsCoe(Cmd) = NULL;
|
|
//
|
|
// Unjoin if possible
|
|
// The unjoin will fail if there isn't a cxtion or
|
|
// the join guid is out-of-date. Which is what we
|
|
// want -- we don't want a lot of unjoins flooding
|
|
// the replica queue and wrecking havoc with future
|
|
// joins.
|
|
//
|
|
// WARN: Must happen before the call to ChgOrdInboundRetry below.
|
|
//
|
|
if (RsReplica(Cmd)) {
|
|
CHANGE_ORDER_TRACE(3, Coe, "Retry/Unjoin");
|
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|
Cmd = NULL;
|
|
}
|
|
//
|
|
// Retry the change order. This must happen *AFTER* issueing
|
|
// the unjoin above so that any change orders kicked loose by
|
|
// the following retry will be pushed into the retry path because
|
|
// their join guid will be invalidated during the unjoin.
|
|
//
|
|
SET_COE_FLAG(Coe, COE_FLAG_NO_INBOUND);
|
|
|
|
if (CO_STATE_IS_LE(Coe, IBCO_STAGING_RETRY)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to staging retry");
|
|
ChgOrdInboundRetry(Coe, IBCO_STAGING_RETRY);
|
|
} else
|
|
if (CO_STATE_IS_LE(Coe, IBCO_FETCH_RETRY)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to fetch retry");
|
|
ChgOrdInboundRetry(Coe, IBCO_FETCH_RETRY);
|
|
} else
|
|
if (CO_STATE_IS_LE(Coe, IBCO_INSTALL_RETRY)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to install retry");
|
|
ChgOrdInboundRetry(Coe, IBCO_INSTALL_RETRY);
|
|
} else {
|
|
|
|
CHANGE_ORDER_TRACE(3, Coe, "Submit CO to retry");
|
|
ChgOrdInboundRetry(Coe, CO_STATE(Coe));
|
|
}
|
|
|
|
|
|
//
|
|
// Command was transfered to the replica command server for unjoin
|
|
//
|
|
if (!Cmd) {
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// The originator owns the disposition of this command packet
|
|
//
|
|
if (HANDLE_IS_VALID(RsCompletionEvent(Cmd))) {
|
|
SetEvent(RsCompletionEvent(Cmd));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Free up the "address" portion of the command
|
|
//
|
|
FrsFreeGName(RsTo(Cmd));
|
|
FrsFreeGName(RsFrom(Cmd));
|
|
FrsFreeGName(RsReplicaName(Cmd));
|
|
FrsFreeGName(RsCxtion(Cmd));
|
|
|
|
FrsFree(RsBlock(Cmd));
|
|
FrsFree(RsGVsn(Cmd));
|
|
FrsFree(RsCoGuid(Cmd));
|
|
FrsFree(RsJoinGuid(Cmd));
|
|
FrsFree(RsJoinTime(Cmd));
|
|
FrsFree(RsCommPktRcvTime(Cmd));
|
|
FrsFree(RsReplicaVersionGuid(Cmd));
|
|
//
|
|
// Free the copy of our partner's change order command and the data extension.
|
|
//
|
|
if (RsPartnerCoc(Cmd) != NULL) {
|
|
FrsFree(RsPartnerCocExt(Cmd));
|
|
FrsFree(RsPartnerCoc(Cmd));
|
|
}
|
|
|
|
//
|
|
// a replica (never free the RsReplica(Cmd) field; it addresses
|
|
// an active replica in the Replicas table).
|
|
//
|
|
FrsFreeType(RsNewReplica(Cmd));
|
|
|
|
//
|
|
// Seeding cxtion
|
|
// Delete the connection from the perfmon tables.
|
|
//
|
|
if (RsNewCxtion(Cmd) != NULL) {
|
|
FrsFreeType(RsNewCxtion(Cmd));
|
|
}
|
|
|
|
//
|
|
// Free the compression table, if any.
|
|
//
|
|
if (RsCompressionTable(Cmd)) {
|
|
GTabFreeTable(RsCompressionTable(Cmd), FrsFree);
|
|
}
|
|
//
|
|
// Free the version vector, if any
|
|
//
|
|
RsVVector(Cmd) = VVFreeOutbound(RsVVector(Cmd));
|
|
RsReplicaVv(Cmd) = VVFreeOutbound(RsReplicaVv(Cmd));
|
|
|
|
//
|
|
// Authentication info
|
|
//
|
|
FrsFree(RsAuthClient(Cmd));
|
|
FrsFree(RsAuthName(Cmd));
|
|
FrsFree(RsAuthSid(Cmd));
|
|
|
|
//
|
|
// Md5 Digest
|
|
//
|
|
FrsFree(RsMd5Digest(Cmd));
|
|
|
|
//
|
|
// Send the packet on to the generic completion routine for freeing
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsFreeCommand, NULL);
|
|
FrsCompleteCommand(Cmd, Cmd->ErrorStatus);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitTransferToRcs(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN USHORT Command
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Transfer a request to the replica command server
|
|
|
|
Arguments:
|
|
Cmd
|
|
Command
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitTransferToRcs:"
|
|
|
|
Cmd->TargetQueue = RsReplica(Cmd)->Queue;
|
|
Cmd->Command = Command;
|
|
RsTimeout(Cmd) = 0;
|
|
|
|
DPRINT3(5, "Transfer %08x (%08x) to %ws\n",
|
|
Command, Cmd, RsReplica(Cmd)->SetName->Name);
|
|
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
RcsSubmitRemoteCoInstallRetry(
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a remote change order to retry the file install.
|
|
|
|
Arguments:
|
|
Coe - Change order entry.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitRemoteCoInstallRetry:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PREPLICA Replica;
|
|
|
|
Replica = Coe->NewReplica;
|
|
Cmd = FrsAllocCommand(Replica->Queue, 0);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of change order entry
|
|
//
|
|
RsCoe(Cmd) = Coe;
|
|
|
|
//
|
|
// Mask out the irrelevant usn reasons
|
|
//
|
|
RsCoc(Cmd)->ContentCmd &= CO_CONTENT_MASK;
|
|
|
|
//
|
|
// Guid of the change order entry
|
|
//
|
|
RsCoGuid(Cmd) = FrsDupGuid(&RsCoc(Cmd)->ChangeOrderGuid);
|
|
|
|
//
|
|
// Initialize the command packet for eventual transfer to
|
|
// the replica set command server
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
|
|
//
|
|
// Cxtion's guid (note - we lose the printable name for now)
|
|
//
|
|
RsCxtion(Cmd) = FrsBuildGName(FrsDupGuid(&Coe->Cmd.CxtionGuid), NULL);
|
|
|
|
DPRINT3(5, "Submit %08x (%08x) to %ws\n",
|
|
CMD_REMOTE_CO_ACCEPTED, Cmd, Replica->SetName->Name);
|
|
FrsInstallCsSubmitTransfer(Cmd, CMD_INSTALL_STAGE);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitRemoteCoAccepted(
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a remote change order to the staging file generator.
|
|
|
|
Arguments:
|
|
Co
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitRemoteCoAccepted:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PREPLICA Replica;
|
|
|
|
Replica = Coe->NewReplica;
|
|
Cmd = FrsAllocCommand(Replica->Queue, CMD_REMOTE_CO_ACCEPTED);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of change order entry
|
|
//
|
|
RsCoe(Cmd) = Coe;
|
|
|
|
//
|
|
// Mask out the irrelevant usn reasons
|
|
//
|
|
RsCoc(Cmd)->ContentCmd &= CO_CONTENT_MASK;
|
|
|
|
//
|
|
// Guid of the change order entry
|
|
//
|
|
RsCoGuid(Cmd) = FrsDupGuid(&RsCoc(Cmd)->ChangeOrderGuid);
|
|
|
|
//
|
|
// Initialize the command packet for eventual transfer to
|
|
// the replica set command server
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
|
|
//
|
|
// Cxtion's guid (note - we lose the printable name for now)
|
|
//
|
|
RsCxtion(Cmd) = FrsBuildGName(FrsDupGuid(&Coe->Cmd.CxtionGuid), NULL);
|
|
|
|
//
|
|
// Join guid
|
|
//
|
|
RsJoinGuid(Cmd) = FrsDupGuid(&Coe->JoinGuid);
|
|
|
|
DPRINT1(5, "Submit %08x\n", Cmd);
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
|
|
|
|
VOID
|
|
RcsSubmitLocalCoAccepted(
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit a local change order to the staging file generator.
|
|
|
|
Arguments:
|
|
Coe
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitLocalCoAccepted:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PREPLICA Replica;
|
|
|
|
//
|
|
// NewReplica?
|
|
//
|
|
Replica = Coe->NewReplica;
|
|
Cmd = FrsAllocCommand(Replica->Queue, CMD_LOCAL_CO_ACCEPTED);
|
|
FrsSetCompletionRoutine(Cmd, RcsCmdPktCompletionRoutine, NULL);
|
|
|
|
//
|
|
// Address of the change order entry
|
|
//
|
|
RsCoe(Cmd) = Coe;
|
|
|
|
//
|
|
// Mask out the irrelevant usn reasons
|
|
//
|
|
RsCoc(Cmd)->ContentCmd &= CO_CONTENT_MASK;
|
|
|
|
//
|
|
// Guid of the change order entry
|
|
//
|
|
RsCoGuid(Cmd) = FrsDupGuid(&RsCoc(Cmd)->ChangeOrderGuid);
|
|
|
|
//
|
|
// Initialize the command packet for eventual transfer to
|
|
// the replica set command server
|
|
//
|
|
RsReplica(Cmd) = Replica;
|
|
|
|
//
|
|
// Cxtion's guid (note - we lose the printable name for now)
|
|
//
|
|
RsCxtion(Cmd) = FrsBuildGName(FrsDupGuid(&Coe->Cmd.CxtionGuid), NULL);
|
|
|
|
DPRINT1(5, "Submit %08x\n", Cmd);
|
|
FrsSubmitCommandServer(&ReplicaCmdServer, Cmd);
|
|
}
|
|
|
|
|
|
ULONG
|
|
RcsSubmitCommPktWithErrorToRcs(
|
|
IN PCOMM_PACKET CommPkt
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
A comm packet could not be sent because of an error. If the
|
|
comm packet was for a joined cxtion then the affected
|
|
replica\cxtion is unjoined.
|
|
|
|
Arguments:
|
|
CommPkt - Comm packet that couldn't be sent
|
|
|
|
Return Value:
|
|
Error status to be propagated to the sender
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "RcsSubmitCommPktWithErrorToRcs:"
|
|
PCOMMAND_PACKET Cmd;
|
|
PREPLICA Replica;
|
|
PGNAME TmpGName;
|
|
|
|
//
|
|
// Convert the comm packet into a command packet
|
|
//
|
|
Cmd = CommPktToCmd(CommPkt);
|
|
|
|
FRS_ASSERT(Cmd != NULL);
|
|
FRS_ASSERT(RsTo(Cmd));
|
|
FRS_ASSERT(RsFrom(Cmd));
|
|
FRS_ASSERT(RsReplicaName(Cmd));
|
|
|
|
//
|
|
// Rebuild the replica name to address the originating, or local, replica
|
|
//
|
|
TmpGName = RsReplicaName(Cmd);
|
|
RsReplicaName(Cmd) = FrsBuildGName(FrsDupGuid(RsFrom(Cmd)->Guid),
|
|
FrsWcsDup(RsReplicaName(Cmd)->Name));
|
|
FrsFreeGName(TmpGName);
|
|
|
|
//
|
|
// Adjust the "to" and "from" addresses
|
|
//
|
|
TmpGName = RsTo(Cmd);
|
|
RsTo(Cmd) = RsFrom(Cmd);
|
|
RsFrom(Cmd) = TmpGName;
|
|
|
|
//
|
|
// Find the target replica
|
|
//
|
|
Replica = GTabLookup(ReplicasByGuid, RsReplicaName(Cmd)->Guid, NULL);
|
|
if (Replica == NULL) {
|
|
DPRINT1(4, ":S: WARN - Submit comm pkt w/error: Replica not found: %ws\n",
|
|
RsReplicaName(Cmd)->Name);
|
|
FrsCompleteCommand(Cmd, ERROR_FILE_NOT_FOUND);
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
RsReplica(Cmd) = Replica;
|
|
|
|
//
|
|
// The target replica may not be accepting comm packets. The
|
|
// target replica will reset itself before accepting
|
|
// commpkts again.
|
|
//
|
|
if (!Replica->IsAccepting) {
|
|
DPRINT1(4, ":S: WARN - Submit comm pkt w/error: Replica is not accepting: %ws\n",
|
|
Replica->ReplicaName->Name);
|
|
FrsCompleteCommand(Cmd, ERROR_RETRY);
|
|
return ERROR_RETRY;
|
|
}
|
|
switch (Cmd->Command) {
|
|
//
|
|
// NEVER SENT VIA A COMM PKT
|
|
//
|
|
case CMD_INIT_SUBSYSTEM:
|
|
case CMD_CHECK_SCHEDULES:
|
|
case CMD_START_REPLICAS:
|
|
// case CMD_STOP:
|
|
case CMD_START:
|
|
case CMD_DELETE:
|
|
case CMD_DELETE_NOW:
|
|
case CMD_LOCAL_CO_ACCEPTED:
|
|
case CMD_REMOTE_CO_ACCEPTED:
|
|
case CMD_SENDING_STAGE:
|
|
case CMD_RECEIVED_STAGE:
|
|
case CMD_CREATED_EXISTING:
|
|
case CMD_SEND_ABORT_FETCH:
|
|
case CMD_SEND_RETRY_FETCH:
|
|
case CMD_JOIN_CXTION:
|
|
case CMD_UNJOIN:
|
|
case CMD_VVJOIN_START:
|
|
case CMD_VVJOIN_SUCCESS:
|
|
case CMD_HUNG_CXTION:
|
|
case CMD_JOINING_AFTER_FLUSH:
|
|
FRS_ASSERT(!"RcsSubmitCommPktWithErrorToRcs: invalid cmd for comm pkt");
|
|
break;
|
|
|
|
//
|
|
// There is other retry code in replica.c that will take care of these
|
|
//
|
|
case CMD_UNJOIN_REMOTE:
|
|
case CMD_JOINED:
|
|
case CMD_NEED_JOIN:
|
|
case CMD_START_JOIN:
|
|
DPRINT3(5, ":X: Ignore commpkt with error Command:%08x Cmd:%08x CommPkt:%08x\n",
|
|
Cmd->Command, Cmd, CommPkt);
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
break;
|
|
|
|
//
|
|
// SENT VIA COMM PKT; UNJOIN
|
|
//
|
|
case CMD_JOINING:
|
|
case CMD_REMOTE_CO:
|
|
case CMD_SEND_STAGE:
|
|
case CMD_RECEIVING_STAGE:
|
|
case CMD_REMOTE_CO_DONE:
|
|
case CMD_ABORT_FETCH:
|
|
case CMD_RETRY_FETCH:
|
|
case CMD_VVJOIN_DONE:
|
|
//
|
|
// Put the unjoin command on the replica's queue
|
|
//
|
|
DPRINT3(5, ":X: Submit commpkt with error Command:%08x Cmd:%08x CommPkt:%08x\n",
|
|
Cmd->Command, Cmd, CommPkt);
|
|
RcsSubmitTransferToRcs(Cmd, CMD_UNJOIN);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|