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.
14718 lines
561 KiB
14718 lines
561 KiB
#define SERIALIZE_CO 0
|
|
/*++
|
|
|
|
Copyright (c) 1997-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
chgorder.c
|
|
|
|
Abstract:
|
|
|
|
This module processes change orders from either the local
|
|
journal or the inbound partners. It makes the accept/reject
|
|
decision, putting the change order into the correct inbound log
|
|
if accepted.
|
|
|
|
Author:
|
|
|
|
David A. Orbits 23-June-1997
|
|
|
|
Environment
|
|
|
|
User mode, winnt32
|
|
|
|
--*/
|
|
#include <ntreppch.h>
|
|
#pragma hdrstop
|
|
|
|
|
|
#include <frs.h>
|
|
#include <tablefcn.h>
|
|
#include <test.h>
|
|
|
|
#include <perrepsr.h>
|
|
|
|
|
|
//
|
|
// Change order entry Flags.
|
|
//
|
|
FLAG_NAME_TABLE CoeFlagNameTable[] = {
|
|
{COE_FLAG_IN_AGING_CACHE , "InAgingCache " },
|
|
{COE_FLAG_RECOVERY_CO , "RecoveryCo " },
|
|
{COE_FLAG_JUST_TOMBSTONE , "JustTombstone " },
|
|
|
|
{COE_FLAG_VOL_COLIST_BLOCKED , "CoListBlked " },
|
|
{COE_FLAG_MOVEOUT_ENUM_DONE , "MovOutEnumDone "},
|
|
{COE_FLAG_NO_INBOUND , "NoInbound " },
|
|
|
|
{COE_FLAG_STAGE_ABORTED , "StageAbort " },
|
|
{COE_FLAG_STAGE_DELETED , "StageDeleted " },
|
|
|
|
{COE_FLAG_REJECT_AT_RECONCILE , "RejectAtRecon " },
|
|
{COE_FLAG_DELETE_GEN_CO , "DeleteGenCo " },
|
|
|
|
{COE_FLAG_REANIMATION , "Reanimation " },
|
|
{COE_FLAG_PARENT_REANIMATION , "ParReanimation "},
|
|
{COE_FLAG_PARENT_RISE_REQ , "ParRiseRequest "},
|
|
|
|
{COE_FLAG_MORPH_GEN_FOLLOWER , "MGFollower " },
|
|
{COE_FLAG_MG_FOLLOWER_MADE , "MGFollowerMade "},
|
|
|
|
{COE_FLAG_NEED_RENAME , "NeedRename " },
|
|
{COE_FLAG_NEED_DELETE , "NeedDelete " },
|
|
{COE_FLAG_PREINSTALL_CRE , "PreInstallCre " },
|
|
{COE_FLAG_TRY_OVRIDE_INSTALL , "TryOvrRideInst "},
|
|
|
|
{COE_FLAG_IDT_ORIG_PARENT_DEL , "IdtOrigParDel " },
|
|
{COE_FLAG_IDT_ORIG_PARENT_ABS , "IdtOrigParAbs " },
|
|
{COE_FLAG_IDT_NEW_PARENT_DEL , "IdtNewParDel " },
|
|
{COE_FLAG_IDT_NEW_PARENT_ABS , "IdtNewParAbs " },
|
|
{COE_FLAG_IDT_TARGET_DEL , "IdtTargetDel " },
|
|
{COE_FLAG_IDT_TARGET_ABS , "IdtTargetAbs " },
|
|
|
|
{COE_FLAG_OID_FROM_FILE , "OidFmFile " },
|
|
{COE_FLAG_IDT_NEW_PARENT_DEL_DEF , "IdtNewParDelDef " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
//
|
|
// Issue Cleanup Flags.
|
|
//
|
|
FLAG_NAME_TABLE IscuFlagNameTable[] = {
|
|
{ISCU_CO_ABORT , "AbortCo " },
|
|
{ISCU_UPDATE_IDT_VVFLAGS , "UpdIdtVVFlags " },
|
|
|
|
{ISCU_ACK_INBOUND , "AckInb " },
|
|
{ISCU_UPDATEVV_DB , "UpdateVVDb " },
|
|
{ISCU_ACTIVATE_VV , "ActivateVV " },
|
|
{ISCU_ACTIVATE_VV_DISCARD , "ActVVDiscard " },
|
|
|
|
{ISCU_INS_OUTLOG , "InsOutlog " },
|
|
{ISCU_INS_OUTLOG_NEW_GUID , "InsOLogNewGuid "},
|
|
|
|
{ISCU_DEL_IDT_ENTRY , "DelIdt " },
|
|
{ISCU_UPDATE_IDT_ENTRY , "UpdIdt " },
|
|
{ISCU_UPDATE_IDT_FLAGS , "UpdIDTFlags " },
|
|
{ISCU_UPDATE_IDT_FILEUSN , "UpdIdtFileUsn " },
|
|
{ISCU_UPDATE_IDT_VERSION , "UpdIDTVersion " },
|
|
|
|
{ISCU_DEL_PREINSTALL , "DelPreInstall " },
|
|
{ISCU_DEL_STAGE_FILE , "DelStageF " },
|
|
{ISCU_DEL_STAGE_FILE_IF , "DelStageFIf " },
|
|
|
|
{ISCU_AIBCO , "AIBCO " },
|
|
{ISCU_ACTIVE_CHILD , "ActChild " },
|
|
{ISCU_NC_TABLE , "NamConfTbl " },
|
|
{ISCU_CHECK_ISSUE_BLOCK , "ChkIsBlk " },
|
|
|
|
{ISCU_DEC_CO_REF , "DecCoRef " },
|
|
{ISCU_DEL_RTCTX , "DelRtCtx " },
|
|
{ISCU_DEL_INLOG , "DelInlog " },
|
|
{ISCU_UPDATE_INLOG , "UpdInlog " },
|
|
{ISCU_FREE_CO , "FreeCo " },
|
|
|
|
{ISCU_NO_CLEANUP_MERGE , "NoFlagMrg " },
|
|
|
|
{0, NULL}
|
|
};
|
|
|
|
|
|
|
|
//
|
|
// Set to TRUE in ChgOrdAcceptShutdown() to shutdown the change order accept
|
|
// thread. Errors and other problems may prevent the journal thread from
|
|
// running down all of the change order accept queues. For now, use this
|
|
// boolean to help shutdown the change order accept thread.
|
|
//
|
|
BOOL ChangeOrderAcceptIsShuttingDown;
|
|
|
|
ULONG ChgOrdNextRetryTime;
|
|
PCHAR IbcoStateNames[IBCO_MAX_STATE+1];
|
|
|
|
|
|
ULONG UpdateInlogState[] = {COStatex, COFlagsx, COIFlagsx, COExtensionx
|
|
/*, COContentCmdx, COLcmdx */};
|
|
|
|
|
|
|
|
//
|
|
// The following IDTable record fields may be updated when a change order goes
|
|
// through the retry path.
|
|
//
|
|
ULONG IdtCoRetryFieldList[] = {Flagsx, CurrentFileUsnx};
|
|
#define IdtCoRetryFieldCount (sizeof(IdtCoRetryFieldList) / sizeof(ULONG))
|
|
|
|
//
|
|
// The following IDTable record are updated for a CO that reanimates a
|
|
// deleted file/dir.
|
|
//
|
|
ULONG IdtFieldReanimateList[] = {FileIDx, FileNamex, ParentGuidx, ParentFileIDx};
|
|
#define IdtCoReanimateFieldCount (sizeof(IdtFieldReanimateList) / sizeof(ULONG))
|
|
|
|
|
|
//
|
|
// The max number of IDTable fields that might be updated in the retire path.
|
|
//
|
|
#define CO_ACCEPT_IDT_FIELD_UPDATE_MAX 13
|
|
|
|
|
|
// Note: Change this to init from either the DS or the registry.
|
|
#if SERIALIZE_CO
|
|
BOOL SerializeAllChangeOrders = TRUE;
|
|
#else
|
|
BOOL SerializeAllChangeOrders = FALSE;
|
|
#endif
|
|
|
|
|
|
//
|
|
// 64 bit Global sequence number and related lock.
|
|
//
|
|
ULONGLONG GlobSeqNum = QUADZERO;
|
|
CRITICAL_SECTION GlobSeqNumLock;
|
|
|
|
|
|
extern FRS_QUEUE FrsVolumeLayerCOList;
|
|
extern FRS_QUEUE FrsVolumeLayerCOQueue;
|
|
|
|
extern FRS_QUEUE VolumeMonitorQueue; // for test
|
|
extern HANDLE JournalCompletionPort; // for test
|
|
|
|
extern PCHAR CoLocationNames[];
|
|
|
|
//
|
|
// To test for change orders with bogus event times.
|
|
//
|
|
extern ULONGLONG MaxPartnerClockSkew; // 100 ns units.
|
|
extern DWORD PartnerClockSkew; // minutes
|
|
|
|
extern ULONG ChangeOrderAgingDelay;
|
|
|
|
//
|
|
// Fixed guid for the dummy cxtion (aka GhostCxtion) assigned to orphan remote
|
|
// change orders whose inbound cxtion has been deleted from the DS but they
|
|
// have already past the fetching state and can finish without the real cxtion
|
|
// coming back. No authentication checks are made for this dummy cxtion.
|
|
//
|
|
extern GUID FrsGuidGhostCxtion;
|
|
extern GUID FrsGhostJoinGuid;
|
|
extern PCXTION FrsGhostCxtion;
|
|
|
|
//
|
|
// The following quadword is stored in a QHash table to track the number of
|
|
// active change orders with a given parent directory. A change order that
|
|
// arrives on the parent is blocked until all the changeorders on the children
|
|
// have finished.
|
|
//
|
|
typedef struct _CHILD_ACTIVE_ {
|
|
union {
|
|
struct {
|
|
ULONG Count; // Number of active children
|
|
PCHANGE_ORDER_ENTRY ChangeOrder; //
|
|
};
|
|
ULONGLONG Data;
|
|
};
|
|
} CHILD_ACTIVE, *PCHILD_ACTIVE;
|
|
|
|
|
|
|
|
typedef struct _MOVEIN_CONTEXT {
|
|
ULONG NumFiles;
|
|
ULONG NumDirs;
|
|
ULONG NumSkipped;
|
|
ULONG NumFiltered;
|
|
LONGLONG ParentFileID;
|
|
PREPLICA Replica;
|
|
} MOVEIN_CONTEXT, *PMOVEIN_CONTEXT;
|
|
|
|
|
|
//
|
|
// Event time window default is 30 min (in 100 nanosec units). Inited
|
|
// from registry.
|
|
//
|
|
LONGLONG RecEventTimeWindow;
|
|
|
|
//
|
|
// Limits on how many time and for how long we will continue to retry a
|
|
// change order when the parent is missing.
|
|
//
|
|
extern ULONG MaxCoRetryTimeoutMinutes;
|
|
extern ULONG MaxCoRetryTimeoutCount;
|
|
|
|
|
|
//
|
|
// Change order retry command server.
|
|
//
|
|
// ** WARNING ** There is currently only one thread to service the change order
|
|
// retry scans for all replica sets. The ActiveInlogRetry table associated with each
|
|
// replica set tracks the sequence numbers of the change orders that have been
|
|
// resubmitted. This should also guard against the simultaneous submitting of
|
|
// the same change order by one thread doing a single connection retry and
|
|
// another thread doing an all connection retry for the same replica set.
|
|
// However, this has not been tested.
|
|
//
|
|
#define CHGORD_RETRY_MAXTHREADS (1)
|
|
COMMAND_SERVER ChgOrdRetryCS;
|
|
|
|
//
|
|
// The time in ms between checks for retries in the inbound log.
|
|
// (FKC_INLOG_RETRY_TIME from registry)
|
|
//
|
|
ULONG InlogRetryInterval;
|
|
|
|
//
|
|
// The time to let a ChangeORder block a process queue waiting for a Join to
|
|
// complete.
|
|
//
|
|
#define CHG_ORD_HOLD_ISSUE_LIMIT (ULONG)(2 * 60 * 1000) // 2 min
|
|
|
|
//
|
|
// Struct for the change order accept server
|
|
// Contains info about the queues and the threads
|
|
// Warning: Check for needed locks if we allow multiple threads.
|
|
//
|
|
#define CHGORDER_MAXTHREADS (1)
|
|
|
|
|
|
#define CHGORDER_RETRY_MIN (1 * 1000) // 1 second
|
|
#define CHGORDER_RETRY_MAX (5 * 1000) // 5 seconds
|
|
|
|
|
|
|
|
#define ISSUE_OK 0
|
|
#define ISSUE_CONFLICT 1
|
|
#define ISSUE_DUPLICATE_REMOTE_CO 2
|
|
#define ISSUE_REJECT_CO 4
|
|
#define ISSUE_RETRY_LATER 5
|
|
|
|
|
|
#define REANIMATE_SUCCESS 0
|
|
#define REAMIMATE_RETRY_LATER 1
|
|
#define REANIMATE_CO_REJECTED 2
|
|
#define REANIMATE_FAILED 3
|
|
#define REANIMATE_NO_NEED 4
|
|
|
|
//
|
|
// ChgOrdInsertProcQ Flag defs.
|
|
//
|
|
#define IPQ_HEAD 0x00000000
|
|
#define IPQ_TAIL 0x00000001
|
|
#define IPQ_TAKE_COREF 0x00000002
|
|
#define IPQ_ASSERT_IF_ERR 0x00000004
|
|
#define IPQ_DEREF_CXTION_IF_ERR 0x00000008
|
|
#define IPQ_MG_LOOP_CHK 0x00000010
|
|
|
|
|
|
#define SIZEOF_RENAME_SUFFIX (7 + 8) // _NTFRS_<tic count in hex>
|
|
|
|
#define MAX_RETRY_SET_OBJECT_ID 10 // retry 10 times
|
|
|
|
|
|
SubmitReplicaJoinWait(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN ULONG TimeOut
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdFetchCmd(
|
|
PFRS_QUEUE *pIdledQueue,
|
|
PCHANGE_ORDER_ENTRY *pChangeOrder,
|
|
PVOLUME_MONITOR_ENTRY *ppVme
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdHoldIssue(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PFRS_QUEUE CoProcessQueue
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdReconcile(
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PIDTABLE_RECORD IDTableRec
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdGenMorphCo(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX TmpIDTableCtxNC,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
);
|
|
|
|
|
|
BOOL
|
|
ChgOrdApplyMorphCo(
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdReanimate(
|
|
IN PTABLE_CTX IDTableCtx,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TmpIDTableCtx
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdMakeDeleteCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PIDTABLE_RECORD IDTableRec
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdMakeRenameCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PIDTABLE_RECORD IDTableRec
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdConvertCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN ULONG LocationCmd
|
|
);
|
|
|
|
BOOL
|
|
ChgOrdReserve(
|
|
IN PIDTABLE_RECORD IDTableRec,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdDispatch(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PIDTABLE_RECORD IDTableRec,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
);
|
|
|
|
VOID
|
|
ChgOrdReject(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdIssueCleanup(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
ULONG CleanUpFlags
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdUpdateIDTableRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdReadIDRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PTABLE_CTX IDTableCtxNC,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL *IDTableRecExists
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdInsertIDRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PTABLE_CTX DIRTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdInsertDirRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX DIRTableCtx,
|
|
PIDTABLE_RECORD IDTableRec,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL InsertFlag
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdCheckAndFixTunnelConflict(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtxNew,
|
|
PTABLE_CTX IDTableCtxExist,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
BOOL *IDTableRecExists
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdCheckNameMorphConflict(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtxNC,
|
|
ULONG ReplicaNumber,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder
|
|
);
|
|
|
|
VOID
|
|
ChgOrdProcessControlCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
);
|
|
|
|
VOID
|
|
ChgOrdTranslateGuidFid(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdInsertInlogRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX InLogTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL RetryOutOfOrder
|
|
);
|
|
|
|
typedef struct _MOVEOUT_CONTEXT {
|
|
ULONG NumFiles;
|
|
LONGLONG ParentFileID;
|
|
PREPLICA Replica;
|
|
} MOVEOUT_CONTEXT, *PMOVEOUT_CONTEXT;
|
|
|
|
JET_ERR
|
|
ChgOrdMoveoutWorker(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Record,
|
|
IN PVOID Context
|
|
);
|
|
|
|
DWORD
|
|
ChgOrdStealObjectId(
|
|
IN PWCHAR Name,
|
|
IN PVOID Fid,
|
|
IN PVOLUME_MONITOR_ENTRY pVme,
|
|
OUT USN *Usn,
|
|
IN OUT PFILE_OBJECTID_BUFFER FileObjID
|
|
);
|
|
|
|
DWORD
|
|
ChgOrdSkipBasicInfoChange(
|
|
IN PCHANGE_ORDER_ENTRY Coe,
|
|
IN PBOOL SkipCo
|
|
);
|
|
|
|
JET_ERR
|
|
ChgOrdRetryWorker(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Record,
|
|
IN PVOID Context
|
|
);
|
|
|
|
JET_ERR
|
|
ChgOrdRetryWorkerPreRead(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Context
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdMoveInDirTree(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdRetryThread(
|
|
PVOID FrsThreadCtxArg
|
|
);
|
|
|
|
|
|
PCHANGE_ORDER_ENTRY
|
|
ChgOrdMakeFromFile(
|
|
IN PREPLICA Replica,
|
|
IN HANDLE FileHandle,
|
|
IN PULONGLONG ParentFid,
|
|
IN ULONG LocationCmd,
|
|
IN ULONG CoFlags,
|
|
IN PWCHAR FileName,
|
|
IN USHORT Length
|
|
);
|
|
|
|
PCHANGE_ORDER_ENTRY
|
|
ChgOrdMakeFromIDRecord(
|
|
IN PIDTABLE_RECORD IDTableRec,
|
|
IN PREPLICA Replica,
|
|
IN ULONG LocationCmd,
|
|
IN ULONG CoFlags,
|
|
IN GUID *CxtionGuid
|
|
);
|
|
|
|
ULONG
|
|
ChgOrdInsertProcessQueue(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_COMMAND ChangeOrder,
|
|
IN ULONG CoeFlags,
|
|
IN PCXTION Cxtion
|
|
);
|
|
|
|
VOID
|
|
ChgOrdRetrySubmit(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN USHORT Command,
|
|
IN BOOL Wait
|
|
);
|
|
|
|
PCHANGE_ORDER_RECORD_EXTENSION
|
|
DbsDataConvertCocExtensionFromWin2K(
|
|
IN PCO_RECORD_EXTENSION_WIN2K CocExtW2K
|
|
);
|
|
|
|
BOOL
|
|
JrnlDoesChangeOrderHaveChildren(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TmpIDTableCtx,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder
|
|
);
|
|
|
|
ULONG
|
|
JrnlGetPathAndLevel(
|
|
IN PGENERIC_HASH_TABLE FilterTable,
|
|
IN PLONGLONG StartDirFileID,
|
|
OUT PULONG Level
|
|
);
|
|
|
|
ULONG
|
|
JrnlAddFilterEntryFromCo(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
OUT PFILTER_TABLE_ENTRY *RetFilterEntry
|
|
);
|
|
|
|
ULONG
|
|
JrnlDeleteDirFilterEntry(
|
|
IN PGENERIC_HASH_TABLE FilterTable,
|
|
IN PULONGLONG DFileID,
|
|
IN PFILTER_TABLE_ENTRY ArgFilterEntry
|
|
);
|
|
|
|
ULONG
|
|
JrnlHashCalcGuid (
|
|
PVOID Buf,
|
|
ULONG Length
|
|
);
|
|
|
|
ULONG
|
|
OutLogInsertCo(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PREPLICA Replica,
|
|
PTABLE_CTX OutLogTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder
|
|
);
|
|
|
|
VOID
|
|
ChgOrdCalcHashGuidAndName (
|
|
IN PUNICODE_STRING Name,
|
|
IN GUID *Guid,
|
|
OUT PULONGLONG HashValue
|
|
);
|
|
|
|
LONG
|
|
FrsGuidCompare (
|
|
IN GUID *Guid1,
|
|
IN GUID *Guid2
|
|
);
|
|
|
|
DWORD
|
|
FrsGetFileInternalInfoByHandle(
|
|
IN HANDLE Handle,
|
|
OUT PFILE_INTERNAL_INFORMATION InternalFileInfo
|
|
);
|
|
|
|
ULONG
|
|
DbsUpdateIDTableFields(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PULONG RecordFieldx,
|
|
IN ULONG FieldCount
|
|
);
|
|
|
|
DWORD
|
|
StuDelete(
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
);
|
|
|
|
DWORD
|
|
StuCreatePreInstallFile(
|
|
IN PCHANGE_ORDER_ENTRY Coe
|
|
);
|
|
|
|
|
|
VOID
|
|
ChgOrdDelayCommand(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PFRS_QUEUE IdledQueue,
|
|
IN PCOMMAND_SERVER CmdServer,
|
|
IN OUT PULONG TimeOut
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Put the command back on the head of the queue and submit a
|
|
delayed command to unidle the queue. When the "unidle" occurs,
|
|
Cmd will be reissued. Retry forever.
|
|
|
|
Arguments:
|
|
|
|
Cmd -- The command packet to delay
|
|
IdledQueue --
|
|
CmdServer -- The command server to reactivate
|
|
TimeOut -- ptr to the time out value.
|
|
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
//
|
|
// Put the command back at the head of the queue
|
|
//
|
|
FrsUnSubmitCommand(Cmd);
|
|
|
|
//
|
|
// Extend the retry time (but not too long)
|
|
//
|
|
|
|
*TimeOut <<= 1;
|
|
if (*TimeOut > CHGORDER_RETRY_MAX)
|
|
*TimeOut = CHGORDER_RETRY_MAX;
|
|
//
|
|
// or too short
|
|
//
|
|
if (*TimeOut < CHGORDER_RETRY_MIN)
|
|
*TimeOut = CHGORDER_RETRY_MIN;
|
|
//
|
|
// This queue will be unidled later. At that time, a thread will
|
|
// retry the command at the head of the queue.
|
|
//
|
|
FrsDelCsSubmitUnIdled(CmdServer, IdledQueue, *TimeOut);
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdRetryPerfmonCount(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Tally the perfmon counters for retry change orders.
|
|
|
|
Arguments:
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdRetryPerfmonCount:"
|
|
|
|
BOOL LocalCo, ControlCo;
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
ControlCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_CONTROL);
|
|
|
|
//
|
|
// Increment the Change orders (Local and Remote) retried at
|
|
// (Generate, Fetch, Install, Rename)
|
|
//
|
|
if (LocalCo) {
|
|
//
|
|
// Retried at Generate
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_STAGING_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, LCORetriedGen, 1);
|
|
}
|
|
//
|
|
// Retried at Fetch
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_FETCH_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, LCORetriedFet, 1);
|
|
}
|
|
//
|
|
// Retried at Install
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, LCORetriedIns, 1);
|
|
}
|
|
//
|
|
// Retried at Rename or Delete
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_REN_RETRY) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, LCORetriedRen, 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If it's not Local and Control CO it must be a Remote CO
|
|
//
|
|
else if (!ControlCo) {
|
|
//
|
|
// Retried at Generate
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_STAGING_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, RCORetriedGen, 1);
|
|
}
|
|
//
|
|
// Retried at Fetch
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_FETCH_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, RCORetriedFet, 1);
|
|
}
|
|
//
|
|
// Retried at Install
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, RCORetriedIns, 1);
|
|
}
|
|
//
|
|
// Retried at Rename or Delete
|
|
//
|
|
else if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_REN_RETRY) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY)) {
|
|
PM_INC_CTR_REPSET(Replica, RCORetriedRen, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ChgOrdInsertProcQ(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY Coe,
|
|
IN DWORD Flags
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert the change order (Coe) onto the process queue associated with this
|
|
Replica's VME process queue.
|
|
|
|
IPQ_HEAD - Insert on head of queue
|
|
IPQ_TAIL - Insert on tail of queue
|
|
IPQ_TAKE_COREF - Increment the ref count on the change order.
|
|
IPQ_ASSERT_IF_ERR - If insert fails then ASSERT.
|
|
IPQ_DEREF_CXTION_IF_ERR - If insert fails then drop the changeorder count
|
|
for the connection assiociated with the CO.
|
|
IPQ_MG_LOOP_CHK - Check if this CO is in a MorphGen Loop.
|
|
|
|
Arguments:
|
|
Replica -- The Replica struct.
|
|
Coe -- The change order.
|
|
Flags -- see above.
|
|
|
|
Return Value:
|
|
|
|
Win32 Status.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertProcQ:"
|
|
|
|
DWORD WStatus;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_TAKE_COREF) && (++Coe->CoMorphGenCount > 50)) {
|
|
DPRINT(0, "++ ERROR: ChangeOrder caught in morph gen loop\n");
|
|
FRS_PRINT_TYPE(0, Coe);
|
|
FRS_ASSERT(!"ChangeOrder caught in morph gen loop");
|
|
return ERROR_OPERATION_ABORTED;
|
|
}
|
|
|
|
//
|
|
// Init the cleanup flags, set the ONLIST CO flag, set entry create time
|
|
// for no aging delay.
|
|
//
|
|
ZERO_ISSUE_CLEANUP(Coe);
|
|
SET_CO_FLAG(Coe, CO_FLAG_ONLIST);
|
|
Coe->EntryCreateTime = Coe->TimeToRun = CO_TIME_NOW(pVme);
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_TAKE_COREF)) {
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(Coe);
|
|
}
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_TAIL)) {
|
|
WStatus = FrsRtlInsertTailQueue(&pVme->ChangeOrderList, &Coe->ProcessList);
|
|
} else {
|
|
WStatus = FrsRtlInsertHeadQueue(&pVme->ChangeOrderList, &Coe->ProcessList);
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT_WS(0, "++ ERROR - ChangeOrder insert failed.", WStatus);
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_DEREF_CXTION_IF_ERR)) {
|
|
DROP_CO_CXTION_COUNT(Replica, Coe, WStatus);
|
|
}
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_TAKE_COREF)) {
|
|
DECREMENT_CHANGE_ORDER_REF_COUNT(Coe);
|
|
}
|
|
|
|
if (BooleanFlagOn(Flags, IPQ_ASSERT_IF_ERR)) {
|
|
FRS_ASSERT(!"ChgOrdInsertProcQ: queue insert failed.");
|
|
}
|
|
}
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
ChgOrdAccept(
|
|
PVOID FrsThreadCtxArg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Entry point for processing change orders from the volume change order lists.
|
|
|
|
|
|
Pull entries off the change order queue and:
|
|
1. See if they have expired. (They stay on the list for a short time to
|
|
accumulate multiple changes.)
|
|
|
|
2. If there is a change order active on this file then idle the queue
|
|
until the active change order completes.
|
|
|
|
3. Process expired CO by first deciding acceptance. This is
|
|
provisional for early abort. The final decision occurs just prior
|
|
to installing the staged file locally.
|
|
|
|
4. Make entry in the inbound hash table
|
|
|
|
5. Make entry in the inbound log for this replica set and get the
|
|
CO sequence number. State is IBCO_INITIALIZING.
|
|
|
|
6. Send request to staging, setting state to IBCO_STAGING_REQUESTED.
|
|
|
|
If a change order is already pending for a file we stop processing
|
|
further change orders until the pending request completes. If the
|
|
pending request ultimately fails (say because we can't stage the files)
|
|
then we abort the change order. The DB version info on the file is not
|
|
updated until after the file has been installed.
|
|
|
|
As the change order progresses we update the current status in the
|
|
change order entry in Jet at points where we need to preserve persistence.
|
|
|
|
Inbound Change Order States:
|
|
|
|
IBCO_INITIALIZING Initial state when CO is first put in log.
|
|
IBCO_STAGING_REQUESTED Alloc staging file space for local CO
|
|
IBCO_STAGING_INITIATED LocalCO Staging file copy has started
|
|
IBCO_STAGING_COMPLETE LocalCO Staging file complete
|
|
At this point prelim accept rechecked and
|
|
becomes either final accept of abort.
|
|
Abort is caused by more recent local change.
|
|
IBCO_STAGING_RETRY Waiting to retry local CO stage file generation.
|
|
|
|
IBCO_FETCH_REQUESTED Alloc staging file space for remote co
|
|
IBCO_FETCH_INITIATED RemoteCO staging file fetch has started
|
|
IBCO_FETCH_COMPLETE RemoteCO Staging file fetch complete
|
|
IBCO_FETCH_RETRY Waiting to retry remote CO stage file fetch
|
|
|
|
IBCO_INSTALL_REQUESTED File install requested
|
|
IBCO_INSTALL_INITIATED File install has started
|
|
IBCO_INSTALL_COMPLETE File install is complete
|
|
IBCO_INSTALL_WAIT File install is waiting to try again.
|
|
IBCO_INSTALL_RETRY File install is retrying.
|
|
IBCO_INSTALL_REN_RETRY File install rename is retrying.
|
|
IBCO_INSTALL_DEL_RETRY Delete or Moveout CO finished except for final on-disk delete.
|
|
|
|
IBCO_OUTBOUND_REQUEST Request outbound propagaion
|
|
IBCO_OUTBOUND_ACCEPTED Request accepted and now in Outbound log
|
|
|
|
IBCO_COMMIT_STARTED DB state update started.
|
|
IBCO_RETIRE_STARTED DB state update done, freeing change order.
|
|
IBCO_ENUM_REQUESTED CO is being recycled to do a dir enum.
|
|
|
|
IBCO_ABORTING CO is being aborted.
|
|
|
|
Arguments:
|
|
|
|
FrsThreadCtxArg - thread
|
|
|
|
Return Value:
|
|
|
|
WIN32 status
|
|
|
|
--*/
|
|
// Note: Need to Handle MOVERS.
|
|
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdAccept:"
|
|
|
|
JET_ERR jerr, jerr1;
|
|
ULONG FStatus, RStatus, WStatus;
|
|
ULONG GStatus, GStatusAC;
|
|
PFRS_QUEUE IdledQueue;
|
|
PFRS_THREAD FrsThread = (PFRS_THREAD)FrsThreadCtxArg;
|
|
ULONG TimeOut, ULong;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
|
|
PIDTABLE_RECORD IDTableRec;
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd;
|
|
PCHANGE_ORDER_RECORD DupCoCmd;
|
|
PVOID KeyArray[2];
|
|
|
|
PCHANGE_ORDER_ENTRY ChangeOrder;
|
|
ULONG Decision;
|
|
|
|
PTHREAD_CTX ThreadCtx;
|
|
|
|
PTABLE_CTX IDTableCtx, InLogTableCtx, DIRTableCtx;
|
|
PTABLE_CTX TmpIDTableCtx, TmpIDTableCtxNC, TmpINLOGTableCtx;
|
|
PREPLICA Replica;
|
|
BOOL MorphOK;
|
|
BOOL IDTableRecExists;
|
|
|
|
BOOL NewFile, LocalCo, RetryCo, ControlCo, RecoveryCo;
|
|
BOOL MorphGenCo;
|
|
ULONG LocationCmd;
|
|
PREPLICA_THREAD_CTX RtCtx = NULL;
|
|
ULONG i;
|
|
BOOL SkipCo, DuplicateCo, ReAnimate, UnIdleProcessQueue;
|
|
BOOL RetryPreinstall, FilePresent;
|
|
BOOL RaiseTheDead, DemandRefreshCo, NameMorphConflict;
|
|
BOOL JustTombstone, ParentReanimation;
|
|
MOVEOUT_CONTEXT MoveOutContext;
|
|
CHAR FetchTag[32] = "CO Fetch **** ";
|
|
|
|
DPRINT(0, "ChangeOrder Thread is starting.\n");
|
|
|
|
TimeOut = 100;
|
|
|
|
ChgOrdNextRetryTime = GetTickCount();
|
|
|
|
IbcoStateNames[IBCO_INITIALIZING ] = "IBCO_INITIALIZING ";
|
|
IbcoStateNames[IBCO_STAGING_REQUESTED] = "IBCO_STAGING_REQUESTED";
|
|
IbcoStateNames[IBCO_STAGING_INITIATED] = "IBCO_STAGING_INITIATED";
|
|
IbcoStateNames[IBCO_STAGING_COMPLETE ] = "IBCO_STAGING_COMPLETE ";
|
|
IbcoStateNames[IBCO_STAGING_RETRY ] = "IBCO_STAGING_RETRY ";
|
|
IbcoStateNames[IBCO_FETCH_REQUESTED ] = "IBCO_FETCH_REQUESTED ";
|
|
IbcoStateNames[IBCO_FETCH_INITIATED ] = "IBCO_FETCH_INITIATED ";
|
|
IbcoStateNames[IBCO_FETCH_COMPLETE ] = "IBCO_FETCH_COMPLETE ";
|
|
IbcoStateNames[IBCO_FETCH_RETRY ] = "IBCO_FETCH_RETRY ";
|
|
IbcoStateNames[IBCO_INSTALL_REQUESTED] = "IBCO_INSTALL_REQUESTED";
|
|
IbcoStateNames[IBCO_INSTALL_INITIATED] = "IBCO_INSTALL_INITIATED";
|
|
IbcoStateNames[IBCO_INSTALL_COMPLETE ] = "IBCO_INSTALL_COMPLETE ";
|
|
IbcoStateNames[IBCO_INSTALL_WAIT ] = "IBCO_INSTALL_WAIT ";
|
|
IbcoStateNames[IBCO_INSTALL_RETRY ] = "IBCO_INSTALL_RETRY ";
|
|
IbcoStateNames[IBCO_INSTALL_REN_RETRY] = "IBCO_INSTALL_REN_RETRY";
|
|
IbcoStateNames[IBCO_INSTALL_DEL_RETRY] = "IBCO_INSTALL_DEL_RETRY";
|
|
IbcoStateNames[IBCO_UNUSED_16 ] = "IBCO_UNUSED_16 ";
|
|
IbcoStateNames[IBCO_UNUSED_17 ] = "IBCO_UNUSED_17 ";
|
|
IbcoStateNames[IBCO_UNUSED_18 ] = "IBCO_UNUSED_18 ";
|
|
IbcoStateNames[IBCO_OUTBOUND_REQUEST ] = "IBCO_OUTBOUND_REQUEST ";
|
|
IbcoStateNames[IBCO_OUTBOUND_ACCEPTED] = "IBCO_OUTBOUND_ACCEPTED";
|
|
IbcoStateNames[IBCO_COMMIT_STARTED ] = "IBCO_COMMIT_STARTED ";
|
|
IbcoStateNames[IBCO_RETIRE_STARTED ] = "IBCO_RETIRE_STARTED ";
|
|
IbcoStateNames[IBCO_ENUM_REQUESTED ] = "IBCO_ENUM_REQUESTED ";
|
|
IbcoStateNames[IBCO_ABORTING ] = "IBCO_ABORTING ";
|
|
|
|
//
|
|
// Get width of reconcilliation window from registry.
|
|
//
|
|
CfgRegReadDWord(FKC_RECONCILE_WINDOW, NULL, 0, &ULong);
|
|
RecEventTimeWindow = (LONGLONG)ULong * (LONGLONG)CONVERT_FILETIME_TO_MINUTES;
|
|
|
|
//
|
|
// Get Inlog retry interval.
|
|
//
|
|
CfgRegReadDWord(FKC_INLOG_RETRY_TIME, NULL, 0, &ULong);
|
|
InlogRetryInterval = ULong * 1000;
|
|
|
|
//
|
|
// Init Change order lock table.
|
|
//
|
|
for (i=0; i<NUMBER_CHANGE_ORDER_LOCKS; i++) {
|
|
INITIALIZE_CRITICAL_SECTION(&ChangeOrderLockTable[i]);
|
|
}
|
|
|
|
INITIALIZE_CRITICAL_SECTION(&GlobSeqNumLock);
|
|
|
|
//
|
|
// Initialize the retry thread
|
|
//
|
|
FrsInitializeCommandServer(&ChgOrdRetryCS,
|
|
CHGORD_RETRY_MAXTHREADS,
|
|
L"ChgOrdRetryCS",
|
|
ChgOrdRetryThread);
|
|
|
|
//
|
|
// Allocate a context for Jet to run in this thread.
|
|
//
|
|
ThreadCtx = FrsAllocType(THREAD_CONTEXT_TYPE);
|
|
|
|
//
|
|
// Allocate Temp table contexts once for the ID table and the INLOG Table.
|
|
// Don't free them until we exit.
|
|
//
|
|
TmpIDTableCtx = FrsAlloc(sizeof(TABLE_CTX));
|
|
TmpIDTableCtx->TableType = TABLE_TYPE_INVALID;
|
|
TmpIDTableCtx->Tid = JET_tableidNil;
|
|
|
|
TmpIDTableCtxNC = FrsAlloc(sizeof(TABLE_CTX));
|
|
TmpIDTableCtxNC->TableType = TABLE_TYPE_INVALID;
|
|
TmpIDTableCtxNC->Tid = JET_tableidNil;
|
|
|
|
TmpINLOGTableCtx = FrsAlloc(sizeof(TABLE_CTX));
|
|
TmpINLOGTableCtx->TableType = TABLE_TYPE_INVALID;
|
|
TmpINLOGTableCtx->Tid = JET_tableidNil;
|
|
|
|
//
|
|
// Setup a Jet Session returning the session ID in ThreadCtx.
|
|
//
|
|
jerr = DbsCreateJetSession(ThreadCtx);
|
|
if (JET_SUCCESS(jerr)) {
|
|
DPRINT(5,"JetOpenDatabase complete\n");
|
|
} else {
|
|
DPRINT_JS(0,"ERROR - OpenDatabase failed. Thread exiting.", jerr);
|
|
jerr = DbsCloseJetSession(ThreadCtx);
|
|
ThreadCtx = FrsFreeType(ThreadCtx);
|
|
return ERROR_GEN_FAILURE;
|
|
}
|
|
|
|
|
|
DPRINT(0, "ChangeOrder Thread has started.\n");
|
|
DEBUG_FLUSH();
|
|
SetEvent(ChgOrdEvent);
|
|
//
|
|
// Free up memory by reducing our working set size
|
|
//
|
|
SetProcessWorkingSetSize(ProcessHandle, (SIZE_T)-1, (SIZE_T)-1);
|
|
|
|
|
|
//
|
|
// Try-Finally so we shutdown Jet cleanly.
|
|
//
|
|
try {
|
|
|
|
//
|
|
// Capture exception.
|
|
//
|
|
try {
|
|
|
|
while (TRUE) {
|
|
//
|
|
// Fetch the next change order command. If successfull this returns
|
|
// with the FrsVolumeLayerCOList Lock held and a reference on the Vme.
|
|
//
|
|
FStatus = ChgOrdFetchCmd(&IdledQueue, &ChangeOrder, &pVme);
|
|
if ((FStatus == FrsErrorQueueIsRundown) || !FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// Treat non-success status from fetch as a normal termination
|
|
// in the Try-Finally clause.
|
|
//
|
|
WStatus = ERROR_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
CoCmd = &ChangeOrder->Cmd;
|
|
|
|
strcpy(&FetchTag[14], CoLocationNames[LocationCmd]);
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, FetchTag, CoCmd->Flags);
|
|
|
|
ParentReanimation = COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION);
|
|
RecoveryCo = COE_FLAG_ON(ChangeOrder, COE_FLAG_RECOVERY_CO);
|
|
|
|
DemandRefreshCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH);
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
|
|
ControlCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_CONTROL);
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
RetryCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY);
|
|
|
|
UnIdleProcessQueue = TRUE;
|
|
NameMorphConflict = FALSE;
|
|
IDTableRecExists = FALSE;
|
|
RetryPreinstall = FALSE;
|
|
FilePresent = FALSE;
|
|
DuplicateCo = FALSE;
|
|
NewFile = FALSE;
|
|
|
|
Decision = ISSUE_OK;
|
|
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
//
|
|
// We did not decrement the ref count in ChgOrdFetchCmd since the
|
|
// CO was not taken off the process queue.
|
|
// Keep it until processing is done.
|
|
// Another reference on the CO is taken by the VV code when the retire
|
|
// slot is activated. Releasing the reference is done by a request to
|
|
// the issue cleanup logic. But set up to decrement the ref if the
|
|
// CO is rejected.
|
|
//
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO | ISCU_DEC_CO_REF);
|
|
|
|
//
|
|
// The change order goes to the target in
|
|
// NewReplica unless it's null then use the original Replica.
|
|
//
|
|
Replica = CO_REPLICA(ChangeOrder);
|
|
|
|
FRS_ASSERT(Replica != NULL);
|
|
|
|
if (RetryCo) {
|
|
//
|
|
// Update perfmon counts for retry CO's
|
|
//
|
|
ChgOrdRetryPerfmonCount(ChangeOrder, Replica);
|
|
}
|
|
|
|
//
|
|
// For local and remote CO's, check if the connection is no longer
|
|
// joined. If this is true then we bail out here because it is
|
|
// possible that a previously issued CO could be creating the parent
|
|
// dir for this CO and if that CO didn't finish then we would be
|
|
// propagating this one out of order.
|
|
//
|
|
if (!ControlCo) {
|
|
|
|
FRS_ASSERT(LocalCo || ChangeOrder->Cxtion != NULL);
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
if ((ChangeOrder->Cxtion == NULL) ||
|
|
!CxtionFlagIs(ChangeOrder->Cxtion, CXTION_FLAGS_JOIN_GUID_VALID) ||
|
|
!GUIDS_EQUAL(&ChangeOrder->JoinGuid,
|
|
&ChangeOrder->Cxtion->JoinGuid)) {
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
FrsRtlRemoveEntryQueueLock(IdledQueue, &ChangeOrder->ProcessList);
|
|
//
|
|
// The queue was never idled so don't try to unidle it.
|
|
//
|
|
UnIdleProcessQueue = FALSE;
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Invalid Join Guid");
|
|
goto CO_CLEANUP;
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
|
|
|
|
//
|
|
// Check for a Control CO request. This is a change order entry
|
|
// with a special flag set. It used for things that have to be
|
|
// serialized with respect to other change orders already in the
|
|
// process queue.
|
|
//
|
|
if (ControlCo) {
|
|
|
|
//
|
|
// Remove the entry from the queue and idle the queue.
|
|
// Process the control change order, drop the queue lock and clean up.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(IdledQueue, &ChangeOrder->ProcessList);
|
|
FrsRtlIdleQueueLock(IdledQueue);
|
|
|
|
ChgOrdProcessControlCo(ChangeOrder, Replica);
|
|
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Translate the Fids to Guids for Local COs and vice versa for remote COs.
|
|
// Get the IDTable delete status for the target and the original and
|
|
// new parent dirs.
|
|
//
|
|
ChgOrdTranslateGuidFid(ThreadCtx, TmpIDTableCtx, ChangeOrder, Replica);
|
|
|
|
//
|
|
// Make sure we still have the FID in the IDTable for a Local
|
|
// change order if we are recovering after a crash.
|
|
//
|
|
if (LocalCo && RecoveryCo &&
|
|
(ChangeOrder->FileReferenceNumber == ZERO_FID)) {
|
|
//
|
|
// A local recovery CO can have a zero fid if there is no
|
|
// idtable record for the CO. This can happen if we crashed
|
|
// after deleting the idtable record but before deleting the
|
|
// inlog record for the CO. Reject the CO so that the inlog
|
|
// record can be cleaned up.
|
|
//
|
|
DPRINT(0, "++ ERROR - local recovery change order has 0 fid; reject\n");
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected; zero fid");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
|
|
// FRS_ASSERT(!"local recovery change order has 0 fid");
|
|
|
|
FrsRtlRemoveEntryQueueLock(IdledQueue, &ChangeOrder->ProcessList);
|
|
//
|
|
// The queue was never idled so don't try to unidle it.
|
|
//
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
UnIdleProcessQueue = FALSE;
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Check for a dependency between this change order and the currently
|
|
// active change orders. If there is one then we idle the process
|
|
// queue and the active state is marked to unidle it when the conflicting
|
|
// CO is done. Since the queue is now idled we won't pick the CO up the
|
|
// next time around the loop.
|
|
//
|
|
Decision = (SerializeAllChangeOrders) ?
|
|
ISSUE_OK : ChgOrdHoldIssue(Replica, ChangeOrder, IdledQueue);
|
|
if (Decision == ISSUE_CONFLICT) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Issue Blocked");
|
|
DPRINT(4, "++ GAC: Conflict with active change order or Join wait.\n");
|
|
DPRINT1(4, "++ GAC: CO process queue: %ws is blocked.\n",
|
|
Replica->pVme->FSVolInfo.VolumeLabel);
|
|
//
|
|
// Drop the process queue lock.
|
|
//
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
UnIdleProcessQueue = FALSE;
|
|
goto CO_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Check to see if this CO is a duplicate of a currently active CO.
|
|
// If it is then ChgOrdHoldIssue() has removed the duplicate entry
|
|
// from the queue. We insert it into the inbound log in the event
|
|
// that the currently executing CO fails. Even if it succeeds we will
|
|
// reject this CO later and send an Ack to the inbound partner at that
|
|
// time.
|
|
//
|
|
DuplicateCo = (Decision == ISSUE_DUPLICATE_REMOTE_CO);
|
|
if (DuplicateCo) {
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Dup Remote Co");
|
|
UnIdleProcessQueue = FALSE;
|
|
goto CO_DEFER_DUP_CO;
|
|
}
|
|
|
|
//
|
|
// Before we try to reserve a retire slot, check to see if CO needs
|
|
// to be retried later. Occurs when parent dir not yet created.
|
|
//
|
|
if (Decision == ISSUE_RETRY_LATER) {
|
|
//
|
|
// Retry this CO later. Set DuplicateCO to alert retry thread.
|
|
// Note: This should only happen from remote COs. If this happened
|
|
// on a first-time-issue Local Co there would be no IDTable entry
|
|
// present for xlate the Guid to a Fid since we haven't inserted
|
|
// one yet.
|
|
//
|
|
FRS_ASSERT(!(LocalCo && !(RetryCo || RecoveryCo)));
|
|
DuplicateCo = TRUE;
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
UnIdleProcessQueue = FALSE;
|
|
|
|
if (ParentReanimation) {
|
|
//
|
|
// Don't retry parent reanimation COs. The base CO will get
|
|
// retried and regenerate the reanimation request. In this
|
|
// case the parent's parent was absent from the IDTable. This
|
|
// could happen if we got a tombstone for the parent and have
|
|
// not yet seen anything for its parent. Then a child create
|
|
// for the tombstoned parent shows up.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Parent Reanimate. Absent Parent");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "ISSUE_RETRY_LATER");
|
|
//
|
|
// How long are we going to wait for the parent to show up?
|
|
// Need to run a cleanup thread periodically to look for
|
|
// stranded COs and force them to cleanup and abort.
|
|
// See bug 70953 for a description of the problem.
|
|
//
|
|
goto CO_RETRY_LATER;
|
|
}
|
|
|
|
|
|
//
|
|
// Always Reserve a VV retire slot for remote COs since even if
|
|
// it's rejected a VV update and partner ACK are still needed.
|
|
// But, if this is a demand refresh CO (i.e. we demanded a file
|
|
// refresh from our inbound partner) then there is no Version Vector
|
|
// update and no CO in our inbound partner's outbound log to Ack.
|
|
//
|
|
// Do it here so we filter out the duplicate remote CO's first.
|
|
//
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
|
|
FStatus = VVReserveRetireSlot(Replica, ChangeOrder);
|
|
//
|
|
// Check if a prior CO left an activated slot on VV retire list.
|
|
// If it did and this CO is marked activated then this slot is
|
|
// for our CO and we are doing a retry. Otherwise the slot is for
|
|
// a different CO and so we either insert it in the INLOG or just
|
|
// skip it for now if it's a retry. Note: If we crashed then we
|
|
// lost the VV retire list so we don't get a duplicate key return.
|
|
// The CO is marked as a recovery CO when it is reprocessed so as
|
|
// to bypass some error checks that would apply to non-recovery COs.
|
|
//
|
|
if (FStatus == FrsErrorKeyDuplicate) {
|
|
//
|
|
// If this CO has VV_ACTIVATE set then this VV entry is ours.
|
|
// We are now a retry CO since we couldn't quite finish before.
|
|
// If this CO does not have VV_ACTIVATE set then it must be a dup.
|
|
// Reject the dup CO and ACk the partner. The VV entry is activated
|
|
// so it means that it is safe to ack the CO. The original CO
|
|
// that activated the entry may or may not be in the inlog.
|
|
//
|
|
DuplicateCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_VV_ACTIVATED);
|
|
if (DuplicateCo) {
|
|
FrsRtlRemoveEntryQueueLock(IdledQueue, &ChangeOrder->ProcessList);
|
|
FrsRtlIdleQueueLock(IdledQueue);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV);
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
goto CO_CLEANUP;
|
|
|
|
} else {
|
|
//
|
|
// This is a remote CO with VV_ACTIVATED flag set so
|
|
// it should be a retry change order.
|
|
// The state should be greater than IBCO_FETCH_RETRY or
|
|
// it wouldn't be activated. It could be in an install or
|
|
// a rename retry state.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Retry remote CO");
|
|
FRS_ASSERT(RetryCo);
|
|
FRS_ASSERT(!CO_STATE_IS_LE(ChangeOrder, IBCO_FETCH_RETRY));
|
|
//ChgOrdReject(ChangeOrder, Replica);
|
|
//goto CO_CLEANUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this Remote CO should be rejected then do it now.
|
|
// ChgOrdHoldIssue() has removed the CO from the queue.
|
|
//
|
|
if (Decision == ISSUE_REJECT_CO) {
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
UnIdleProcessQueue = FALSE;
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
//
|
|
// No conflict we can issue the Change Order.
|
|
// Remove the entry from the queue and idle the queue.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(IdledQueue, &ChangeOrder->ProcessList);
|
|
FrsRtlIdleQueueLock(IdledQueue);
|
|
|
|
|
|
//
|
|
// We now have the change order off the queue. Drop the locks blocking
|
|
// the journal lookup. If the Journal was about to update this change
|
|
// order it will now not find it in the volume ChangeOrderTable so it
|
|
// creates a new one.
|
|
//
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
//
|
|
// Not on the process list now.
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
FRS_ASSERT(pVme == Replica->pVme);
|
|
|
|
//
|
|
// Allocate the replica thread context (RtCtx) for the change
|
|
// order's DB operations. Link it to the Replica context list head.
|
|
// We then use this to carry the DB related state of the CO thru the
|
|
// system. It also allows us to queue the CO retire operation to
|
|
// the DB system where it will have all the data to perform the
|
|
// changeorder entry update/delete and the IDTable update.
|
|
//
|
|
// To make the above work we can't return any state relative to
|
|
// specific table transactions thru the replica struct.
|
|
//
|
|
// Check on Replica->FStatus.
|
|
//
|
|
// Use RtCtx or cmdpkt instead. The ChangeOrder needs a ptr to the
|
|
// RtCtx. When the ChangeOrder is done all RtCtx tables are closed,
|
|
// The RtCtx is removed from the replica and thread ctx lists and the
|
|
// memory is freed. Also check that mult concurrent transactions on a
|
|
// replica set sync access to the config record in the replica struct.
|
|
// Before a thread passes the changeorder (and hence the RtCtx) on to
|
|
// another thread it will have to make sure any open tables are closed
|
|
// because the table opens are ThreadCtx specific.
|
|
//
|
|
// Note: If this is a Retry CO it will already have a RtCtx if we
|
|
// got it through VVReferenceRetireSlot(). So don't alloc another.
|
|
//
|
|
RtCtx = ChangeOrder->RtCtx;
|
|
if (RtCtx == NULL) {
|
|
RtCtx = FrsAllocTypeSize(REPLICA_THREAD_TYPE,
|
|
FLAG_FRSALLOC_NO_ALLOC_TBL_CTX);
|
|
FrsRtlInsertTailList(&Replica->ReplicaCtxListHead,
|
|
&RtCtx->ReplicaCtxList);
|
|
ChangeOrder->RtCtx = RtCtx;
|
|
} else {
|
|
//
|
|
// Add LocalCo because a localco winner of a name morph conflict
|
|
// will have generated a loser-rename-co before running. The act
|
|
// of generating the loser-rename-co leaves the original localco
|
|
// with an RtCtx.
|
|
//
|
|
FRS_ASSERT(RetryCo || ParentReanimation || MorphGenCo || LocalCo);
|
|
}
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_RTCTX);
|
|
|
|
IDTableCtx = &RtCtx->IDTable;
|
|
DIRTableCtx = &RtCtx->DIRTable;
|
|
InLogTableCtx = &RtCtx->INLOGTable;
|
|
|
|
//
|
|
// Read or initialize an IDTable Record for this file.
|
|
// Insert the GUIDs for originator, File, Old and new parent.
|
|
// FrsErrorNotFound indicates a new record has been initialized.
|
|
//
|
|
FStatus = ChgOrdReadIDRecord(ThreadCtx,
|
|
IDTableCtx,
|
|
TmpIDTableCtxNC,
|
|
ChangeOrder,
|
|
Replica,
|
|
&IDTableRecExists);
|
|
|
|
//
|
|
// If we have detected and fixed a tunnel conflict that remapped
|
|
// the File Guid to a new file instance (File ID).
|
|
// Need to restart processing of this CO as we would for a
|
|
// reanimated parent dir. This ensures that the CO issue interlock
|
|
// checks made in ChgOrdHoldIssue get rechecked.
|
|
//
|
|
if (FStatus == FrsErrorTunnelConflict) {
|
|
//
|
|
// Do the change order cleanup but don't free the CO.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
}
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push Tunnel Retry to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD | IPQ_TAKE_COREF |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Insert CO onto process queue failed probably beccause process
|
|
// queue has been run down.
|
|
//
|
|
ReleaseVmeRef(pVme);
|
|
WStatus = ERROR_OPERATION_ABORTED;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The above put the CO back on the list so set the MorphGenCo flag so
|
|
// we don't decrement the LocalCoQueueCount at the bottom of the loop.
|
|
//
|
|
MorphGenCo = TRUE;
|
|
|
|
//
|
|
// CO ready to restart.
|
|
//
|
|
ChangeOrder = NULL;
|
|
IDTableRec = NULL;
|
|
goto CO_CONTINUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Get ptr to ID Table record.
|
|
//
|
|
IDTableRec = (PIDTABLE_RECORD) (IDTableCtx->pDataRecord);
|
|
|
|
NameMorphConflict = (FStatus == FrsErrorNameMorphConflict);
|
|
|
|
NewFile = (FStatus == FrsErrorNotFound) ||
|
|
IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS);
|
|
|
|
if ((FStatus == FrsErrorTunnelConflictRejectCO) ||
|
|
(!FRS_SUCCESS(FStatus) && !(NewFile || NameMorphConflict))) {
|
|
CHANGE_ORDER_TRACEF(3, ChangeOrder, "Co Rejected", FStatus);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// If this CO is on a new file and it's a Morph generated Co then
|
|
// the morph conflict may no longer exist because of the outcome of
|
|
// a prior change order (e.g. it got aborted).
|
|
// There are two case to consider: A MORPH_GEN_LEADER or a
|
|
// MORPH_GEN_FOLLOWER.
|
|
//
|
|
if (NewFile && MorphGenCo) {
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER)) {
|
|
|
|
// MorphGenFollower: This is the rename MorphGenCo produced
|
|
// as part of a Dir/Dir Name Morph Conflict (DLD Case). The
|
|
// fact that Newfile is TRUE means that the base create CO
|
|
// (the leader) failed and is in retry so reject this MorphGenCo.
|
|
// It gets refabricated later when the Leader is re-issued.
|
|
// ***** Note -- THere is similar code in ChgOrdHoldIssue() *****
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - MORPH_GEN_FOLLOWER");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
} else {
|
|
//
|
|
// Do not pitch MorphGen Delete Cos. An incoming Create Co that
|
|
// looses the name conflict produces a MorphGenLeader DelCo
|
|
// with no IDTable Entry.
|
|
// ***** Note -- THere is similar code in ChgOrdHoldIssue() *****
|
|
//
|
|
if ((LocationCmd != CO_LOCATION_DELETE) &&
|
|
BooleanFlagOn(ChangeOrder->Cmd.ContentCmd, USN_REASON_RENAME_NEW_NAME)) {
|
|
//
|
|
// MorphGenLeader: This is the rename MorphGenCo produced as
|
|
// part of a Dir/Dir Name
|
|
// Morph Conflict where ID table loses name (DDL Case).
|
|
// This CO is intended to free up the name so that the base CO can
|
|
// issue. The fact that Newfile is TRUE means that the conflicting
|
|
// ID table entry either does not exist any more or is still in
|
|
// NewFileInProgress state. Either way the conflicting dir is not
|
|
// on disk so the base CO will be able to proceed.
|
|
// Reject the Morph CO at this point.
|
|
//
|
|
if (!IDTableRecExists) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - MORPH_GEN_LEADER");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "**** WARN - MORPH_GEN_LEADER but IDT entry exists");
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, TRUE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
//
|
|
// IDTable record exists - Implies NewFileInProgress so
|
|
// there is at least one CO that is working on the ID table
|
|
// entry (probably in retry). We end up deleting the ID
|
|
// table entry as part of reject here.
|
|
// (ISCU_DEL_IDT_ENTRY). That is not a problem as the CO
|
|
// that is working on it will recreate it when it gets
|
|
// reissued again and will notice the name morph with our
|
|
// base CO and will do the right thing.
|
|
//
|
|
// WARN:
|
|
// This may cause problems so for now don't do it and
|
|
// wait for better data on this case.
|
|
//
|
|
|
|
// SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_IDT_ENTRY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (LocalCo && !NewFile) {
|
|
ChgOrdSkipBasicInfoChange(ChangeOrder, &SkipCo);
|
|
if (SkipCo) {
|
|
//
|
|
// Skip some basic info changes on local change orders
|
|
// but update the last USN field in the IDTable record so
|
|
// we don't get confused later in staging and think another
|
|
// change order will be coming with a more recent USN.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Basicinfo");
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_UPDATE_IDT_FILEUSN);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Now decide if we accept this change order. This deals with the
|
|
// problem of multiple updates to the same file at different
|
|
// replica set members. In most cases the decision to accept the
|
|
// change is final. However it is possible for the target file on
|
|
// this machine to be modified directly between now and the time
|
|
// when we actually install the file. As an example a slow or
|
|
// faulty network could delay the fetch of the staging file from
|
|
// the inbound partner (perhaps for hours or days). To deal with this
|
|
// case we capture the file's USN now and then we check it again
|
|
// just before the final stage of the install.
|
|
//
|
|
// If this is a new file (not in our IDTable) we accept unless this
|
|
// is a retry of a previous delete that has been superceded by
|
|
// a reanimation. In the latter case, let reconcile have a look.
|
|
//
|
|
// If the Re-animate flag is set then we have already accepted this CO.
|
|
// Otherwise use reconcilliation algorithm to decide accept or reject.
|
|
//
|
|
// What if the reanimation co is a recoveryco or a retryco? Shouldn't
|
|
// reconcile be called? Should REANIMATION be cleared when a co
|
|
// is taken through retry?
|
|
//
|
|
|
|
if (NewFile && !CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - NewFile");
|
|
goto CO_ACCEPT;
|
|
}
|
|
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION) &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - Reanimated");
|
|
goto CO_ACCEPT;
|
|
}
|
|
|
|
if (ChgOrdReconcile(Replica,
|
|
ChangeOrder,
|
|
(PIDTABLE_RECORD) IDTableCtx->pDataRecord)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - Reconcile");
|
|
goto CO_ACCEPT;
|
|
}
|
|
|
|
//
|
|
// This CO is rejected. Set necessary cleanup flags and go clean it up.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Reconcile");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
|
|
|
|
CO_ACCEPT:
|
|
|
|
|
|
//
|
|
// This Change Order is being accepted. Now check for a Name Conflict
|
|
// in the parent dir. This may have been detected above when we searched
|
|
// for the IDTable record or it may have been detected in the past and
|
|
// this CO was marked as a MorphGenLeader and it needs to refabricate
|
|
// the MorphGenFollower rename CO because the leader got sent thru
|
|
// the retry path.
|
|
//
|
|
if (NameMorphConflict ||
|
|
(CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER) &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_MG_FOLLOWER_MADE))) {
|
|
|
|
//
|
|
// Increment the Local and Remote CO Morphed counter
|
|
//
|
|
if (LocalCo) {
|
|
PM_INC_CTR_REPSET(Replica, LCOMorphed, 1);
|
|
}
|
|
else {
|
|
PM_INC_CTR_REPSET(Replica, RCOMorphed, 1);
|
|
}
|
|
|
|
MorphOK = ChgOrdGenMorphCo(ThreadCtx,
|
|
TmpIDTableCtxNC,
|
|
ChangeOrder,
|
|
Replica);
|
|
//
|
|
// If the code above put the change order back on the list then
|
|
// set the MorphGenCo flag so we don't decrement the
|
|
// LocalCoQueueCount at the bottom of the loop.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_ONLIST)) {
|
|
MorphGenCo = TRUE;
|
|
}
|
|
|
|
if (!MorphOK) {
|
|
//
|
|
// Local Co's that fail to generate a Morph Co on the first time
|
|
// through (i.e. not a Retry or Recovery CO) must be rejected
|
|
// since there is no IDTable entry made that will translate the
|
|
// GUID to the FID when the CO is later retried.
|
|
//
|
|
if (LocalCo && !(RetryCo || RecoveryCo)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Morph failure");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
//
|
|
// If this is a parent reanimation CO that has encountered a
|
|
// name morph conflict for the second time then reject it and
|
|
// let the base CO go thru retry. A case where this can happen
|
|
// is when a reanimated parent causes a name morph conflict
|
|
// in which it wins the name from the current owner in the
|
|
// IDTable (the DDL case.) But the rename MorphGenCo that was
|
|
// going to free the name failed because the underlying file
|
|
// was deleted (by a local file op). Now the reanimation CO
|
|
// for the winner of the name re-issues and hits the name morph
|
|
// conflict again. This time it has COE_FLAG_MORPH_GEN_FOLLOWER
|
|
// set so ChgOrdGenMorphCo() returns failure. Since this is
|
|
// a parent reanimation CO it should not go thru retry, instead
|
|
// the base CO will go thru retry. This lets the local CO that
|
|
// deleted the parent get processed so when the base CO is later
|
|
// retried and reanimates the parent, there won't be a name morph
|
|
// conflict.
|
|
//
|
|
if (ParentReanimation) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Parent Reanimate Morph failure");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Retry this CO later. Set DuplicateCO to alert retry thread.
|
|
//
|
|
DuplicateCo = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Retry Later");
|
|
goto CO_RETRY_LATER;
|
|
}
|
|
|
|
//
|
|
// Loop back to the top and process the new CO.
|
|
//
|
|
ChangeOrder = NULL;
|
|
IDTableRec = NULL;
|
|
goto CO_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// The new change order has been accepted.
|
|
//
|
|
DBS_DISPLAY_RECORD_SEV(4, IDTableCtx, FALSE);
|
|
|
|
//
|
|
// Check for accepted Delete Co that missed in the IDTable.
|
|
// This is a remote CO delete that arrived before the create.
|
|
// Create a tombstone in the IDTable.
|
|
// A second case is a delete Co that arrives after the file has been
|
|
// deleted but has more recent event time info. We need to process
|
|
// this and update the IDTable to ensure a correct outcome if an update
|
|
// CO were to arrive from a third originator. See comment in
|
|
// ChgOrdReadIDRecord() re: "Del Co rcvd on deleted file".
|
|
//
|
|
JustTombstone = (//!LocalCo &&
|
|
(NewFile || IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED)) &&
|
|
(CO_LOCN_CMD_IS(ChangeOrder, CO_LOCATION_DELETE) ||
|
|
CO_LOCN_CMD_IS(ChangeOrder, CO_LOCATION_MOVEOUT)));
|
|
|
|
if (JustTombstone) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_JUST_TOMBSTONE);
|
|
ReAnimate = FALSE;
|
|
goto CO_SKIP_REANIMATE;
|
|
}
|
|
|
|
//
|
|
// If this is a MOVEOUT Local CO on a directory then enumerate the
|
|
// IDTable and generate delete COs for all the children. The Journal
|
|
// sends the MOVEOUT to us in a bottom up order using the directory
|
|
// filter table.
|
|
//
|
|
if (LocalCo &&
|
|
CoIsDirectory(ChangeOrder) &&
|
|
CO_LOCN_CMD_IS(ChangeOrder, CO_LOCATION_MOVEOUT) &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_MOVEOUT_ENUM_DONE)) {
|
|
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_MOVEOUT_ENUM_DONE);
|
|
|
|
//
|
|
// Push this CO back onto the front of the CO Process queue
|
|
// and then generate Delete COs for each child.
|
|
// Do the change order cleanup but don't free the CO.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
//
|
|
// Initialize context block before CO is pushed back to queue.
|
|
//
|
|
MoveOutContext.NumFiles = 0;
|
|
MoveOutContext.ParentFileID = ChangeOrder->FileReferenceNumber;
|
|
MoveOutContext.Replica = Replica;
|
|
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push MOVEOUT DIR CO to QHead");
|
|
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD |
|
|
IPQ_TAKE_COREF | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
ChangeOrder = NULL;
|
|
|
|
//
|
|
// The above put the change order back on the list so
|
|
// set the MorphGenCo flag so we don't decrement the
|
|
// LocalCoQueueCount at the bottom of the loop.
|
|
//
|
|
MorphGenCo = TRUE;
|
|
|
|
//
|
|
// Walk through the IDTable and Generate delete COs for each child
|
|
// by calling ChgOrdMoveoutWorker() for this Replica.
|
|
//
|
|
jerr = DbsOpenTable(ThreadCtx, TmpIDTableCtx, Replica->ReplicaNumber, IDTablex, NULL);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT_JS(0, "++ ERROR - IDTable Open Failed:", jerr);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
jerr = FrsEnumerateTable(ThreadCtx,
|
|
TmpIDTableCtx,
|
|
GuidIndexx,
|
|
ChgOrdMoveoutWorker,
|
|
&MoveOutContext);
|
|
if (jerr != JET_errNoCurrentRecord) {
|
|
DPRINT_JS(0, "++ FrsEnumerateTable error:", jerr);
|
|
}
|
|
|
|
DPRINT1(4, "++ MoveOut dir done: %d files\n", MoveOutContext.NumFiles);
|
|
|
|
//
|
|
// Close the IDTable, reset TableCtx Tid and Sesid. Macro writes 1st arg.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, TmpIDTableCtx);
|
|
|
|
//
|
|
// Loop back to the top and process the new delete COs.
|
|
//
|
|
IDTableRec = NULL;
|
|
goto CO_CONTINUE;
|
|
}
|
|
|
|
//
|
|
// Does this CO need reanimation?
|
|
// Do not treat MorphGenCos for local deletes as reanimate cos.
|
|
//
|
|
// 1. A remote co for an update comes in for a tombstoned
|
|
// idtable entry.
|
|
//
|
|
// 2. The remote co becomes a reanimate co.
|
|
//
|
|
// 3. The reanimate remote co loses a name conflict to
|
|
// another idtable entry.
|
|
//
|
|
// 4. A MorphGenCo for a local delete is generated for the
|
|
// reanimate remote co that lost the name conflict.
|
|
//
|
|
// 5. The MorphGenCo is treated as a reanimate co because the
|
|
// idtable entry is deleted. This MorphGenCo for a local
|
|
// delete becomes a MorphGenCo for a local *CREATE* later in
|
|
// this function. Things go downhill from there.
|
|
//
|
|
//
|
|
ReAnimate = (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED) &&
|
|
(!DOES_CO_DELETE_FILE_NAME(CoCmd)) &&
|
|
!(MorphGenCo && (LocationCmd == CO_LOCATION_DELETE)));
|
|
|
|
//
|
|
// Change order is accepted. Check if the parent dir is deleted.
|
|
// This could happen if one member deletes the parent dir while another
|
|
// member creates a new file or updates what had been a deleted file
|
|
// underneath the parent. In this situation the parent dir is recreated
|
|
// by fetching it from the inbound parent that sent us this CO.
|
|
//
|
|
// Do we need to check that the LocationCmd is not a Moveout,
|
|
// movedir, movers, or a delete? Delete and moveout were filtered
|
|
// when we read the IDTable record above. The test below will
|
|
// select the IDT_NEW_PARENT_DEL test in the case of a movedir or
|
|
// movers so an additional test of the location cmd should not be necc.
|
|
//
|
|
|
|
if (ChangeOrder->NewReplica != NULL) {
|
|
RaiseTheDead = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
} else {
|
|
RaiseTheDead = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_DEL);
|
|
}
|
|
|
|
if (RaiseTheDead) {
|
|
RStatus = ChgOrdReanimate(IDTableCtx,
|
|
ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
TmpIDTableCtx);
|
|
|
|
switch (RStatus) {
|
|
|
|
case REANIMATE_SUCCESS:
|
|
//
|
|
// Parent reanimated. Go Issue the CO for it.
|
|
//
|
|
ChangeOrder = NULL;
|
|
IDTableRec = NULL;
|
|
goto CO_CONTINUE;
|
|
|
|
case REANIMATE_CO_REJECTED:
|
|
//
|
|
// Can't do the reanimation. Could be a local co or a nested
|
|
// reanimation failed and now we are failing all nested parents
|
|
// back to the base CO.
|
|
//
|
|
IDTableRec = NULL;
|
|
goto CO_CLEANUP;
|
|
|
|
case REANIMATE_FAILED:
|
|
//
|
|
// This is a system failure. Can't insert CO on process queue
|
|
// probably beccause process queue has been run down.
|
|
//
|
|
ReleaseVmeRef(pVme);
|
|
break;
|
|
|
|
case REAMIMATE_RETRY_LATER:
|
|
//
|
|
// Local Co's that fail to reanimate the parent could occur if
|
|
// both the file and the parent have been deleted out from under
|
|
// us. This might happen in an obscure case where the local
|
|
// child create ends up in a race with a remote co file create
|
|
// that is followed immediately by a remote co file delete and a
|
|
// parent dir delete. The remote COs would have to arrive just
|
|
// before the local CO create was inserted into the process
|
|
// queue. There would be no IDTable name conflict when the
|
|
// remote CO is processed so the remote CO create would just
|
|
// take over the file name. Later when the local CO create is
|
|
// processed our attempt to reanimate the parent will fail. I'm
|
|
// not even sure this can really happen but if it did and this
|
|
// is the first time through (i.e. not a Retry or Recovery CO)
|
|
// the CO must be rejected since there is no IDTable entry made
|
|
// that will translate the GUID to the FID when the CO is later
|
|
// retried.
|
|
//
|
|
//
|
|
// It doesn't seem like the above scenario actually happens.
|
|
// There is however a problem when we fail a reanimation
|
|
// triggered by a local Co and then abort the CO. (bug 464476)
|
|
//
|
|
/*
|
|
|
|
if (LocalCo && !(RetryCo || RecoveryCo)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Parent Reanimate failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Retry this CO later. Set DuplicateCO to alert retry thread.
|
|
// This can happen if it's the base CO and we failed to
|
|
// reanimate one of the parents.
|
|
//
|
|
DuplicateCo = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Reanimate Retry Later");
|
|
goto CO_RETRY_LATER;
|
|
|
|
case REANIMATE_NO_NEED:
|
|
//
|
|
// No need to raise the co's parent because this is a
|
|
// delete change order. This can happen if the file
|
|
// is stuck in the preinstall or staging areas and isn't
|
|
// really under its parent.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Reanimate Parent Not Needed");
|
|
break;
|
|
|
|
default:
|
|
|
|
DPRINT1(0, "++ ERROR: Invalid status return from ChgOrdReanimate: %d\n",
|
|
RStatus);
|
|
FRS_ASSERT(!"Invalid status return from ChgOrdReanimate");
|
|
|
|
}
|
|
|
|
if (RStatus == REANIMATE_FAILED) {
|
|
//
|
|
// This is a system failure. See above.
|
|
//
|
|
WStatus = ERROR_OPERATION_ABORTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
CO_SKIP_REANIMATE:
|
|
//
|
|
// Is a CO reanimation (but not a collateral parent reanimation)?
|
|
// location cmd could have changed in some function calls above.
|
|
//
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
if (ReAnimate && !ParentReanimation) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Issue Reanimated Target");
|
|
|
|
//
|
|
// Clear the IDTable Flags and transform the CO into a create.
|
|
// If the location cmd was a movein then leave it alone since
|
|
// code elsewhere needs to know.
|
|
//
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_ALL);
|
|
|
|
if (!CO_NEW_FILE(LocationCmd)) {
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_CREATE);
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
LocationCmd = CO_LOCATION_CREATE;
|
|
}
|
|
|
|
//
|
|
// Set the IDREC_FLAGS_NEW_FILE_IN_PROGRESS flag so that the
|
|
// CO gets renamed from preinstall file to final name even
|
|
// after it goes through retry. (Bug # 138742)
|
|
//
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS);
|
|
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_REANIMATION);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
} else
|
|
if (ParentReanimation) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Issue Reanimated Parent");
|
|
} else {
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_GROUP_REANIMATE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "GO Issue");
|
|
}
|
|
|
|
|
|
if (LocalCo) {
|
|
//
|
|
// Local Co -- Create the change order guid and update filesize.
|
|
//
|
|
// Morph generated Cos got their COGuid when they were originally
|
|
// created (for tracking purposes). If it's a retry or recovery
|
|
// CO then it came from the inlog and already has a COGuid.
|
|
//
|
|
if (!MorphGenCo && !RetryCo && !RecoveryCo) {
|
|
FrsUuidCreate(&CoCmd->ChangeOrderGuid);
|
|
}
|
|
|
|
CoCmd->FileSize = IDTableRec->FileSize;
|
|
|
|
//
|
|
// If it is a morph generated Co then perform the file op that
|
|
// frees the name. Have to wait until at least this point
|
|
// because otherwise there could be an outstanding CO working
|
|
// on the file. Only Morph generated COs that matter here are
|
|
// those that already have an ID table entry.
|
|
// If this is a MOVEOUT generated Delete Co then NO local op is done.
|
|
//
|
|
if (!NewFile && MorphGenCo &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_DELETE_GEN_CO)) {
|
|
|
|
if (!ChgOrdApplyMorphCo(ChangeOrder, Replica)) {
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Remote Co -- Check if this is a new file or an accepted
|
|
// CO on a file that was previously deleted.
|
|
//
|
|
// A reanimation change order may be kicked through retry.
|
|
// If so the FID may still be valid. Code in ChgOrdReadIDRecord
|
|
// has already checked this. Another case is when the preinstall
|
|
// file for a reanimated co could not be renamed into place
|
|
// because of a sharing violation on the target parent dir so the
|
|
// CO was kicked through retry in state IBCO_INSTALL_REN_RETRY.
|
|
//
|
|
// Note: File is already present if COE_FLAG_NEED_DELETE is set
|
|
// because it is in the deferred delete state.
|
|
// Unless the FID is zero in wich case ChgOrdReadIDRecord()
|
|
// has determined that the FID was not valid.
|
|
//
|
|
if ((NewFile || ReAnimate) &&
|
|
!JustTombstone &&
|
|
(!COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE) ||
|
|
(IDTableRec->FileID == ZERO_FID)) ) {
|
|
|
|
if (IDTableRec->FileID == ZERO_FID) {
|
|
//
|
|
// This is a remote change order with a new or reanimated
|
|
// file or Dir. Create the target file so we can get its
|
|
// FID for the ID and DIR Table Entries. ChangeOrder's
|
|
// FileReferenceNumber field and the FileUsn field are
|
|
// filled in if the call succeeds.
|
|
//
|
|
WStatus = StuCreatePreInstallFile(ChangeOrder);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT1_WS(0, "++ WARN - StuCreatePreInstallFile(%ws) :",
|
|
CoCmd->FileName, WStatus);
|
|
|
|
//
|
|
// NOTE - All failures to create a preinstall file should
|
|
// be retried, even hardware failures. Once the hardware
|
|
// is fixed the retry should succeed. The CO timeout
|
|
// code will pitch a problem CO after a week.
|
|
//
|
|
//if (!WIN_RETRY_PREINSTALL(WStatus)) {
|
|
//
|
|
// Not retriable; Give up!
|
|
//
|
|
// CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Preinstall");
|
|
// ChgOrdReject(ChangeOrder, Replica);
|
|
// goto CO_CLEANUP;
|
|
//}
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Retry - Preinstall");
|
|
RetryPreinstall = TRUE;
|
|
goto CO_RETRY_PREINSTALL;
|
|
}
|
|
|
|
IDTableRec->FileID = ChangeOrder->FileReferenceNumber;
|
|
//
|
|
// Clear COE_FLAG_NEED_DELETE so we don't assume we have
|
|
// the file/dir below and skip the refetch.
|
|
//
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
}
|
|
//
|
|
// Set flag to cleanup preinstall file if CO later aborts.
|
|
//
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_PREINSTALL);
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_PREINSTALL_CRE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// LOCAL or REMOTE CO:
|
|
//
|
|
// If this is a file create, insert the new entries in the ID Table.
|
|
// If this is an update to an existing file then we don't update the
|
|
// entry until the change order is retired. If it aborted we would
|
|
// have to roll it back and I'd rather not. In addition if an outbound
|
|
// partner needs to do a DB resync and we haven't finished a remote
|
|
// CO install then we would be in the state of having new filter
|
|
// info in the IDTable but old data in the disk file.
|
|
//
|
|
if (NewFile) {
|
|
|
|
//
|
|
// Write the new IDTable record into the database.
|
|
//
|
|
if (IDTableRecExists) {
|
|
//
|
|
// Info in the IDTable record inited by a prior CO that failed
|
|
// to complete (e.g. a FID) may be stale. The prior CO is most
|
|
// likely in the retry state so update the IDTable record with
|
|
// the state from the current CO.
|
|
//
|
|
FStatus = DbsUpdateTableRecordByIndex(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&IDTableRec->FileGuid,
|
|
GuidIndexx,
|
|
IDTablex);
|
|
//
|
|
// DirTable
|
|
//
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
FStatus = ChgOrdInsertDirRecord(ThreadCtx,
|
|
DIRTableCtx,
|
|
IDTableRec,
|
|
ChangeOrder,
|
|
Replica,
|
|
FALSE);
|
|
//
|
|
// DirTable entry may not exist; create it
|
|
//
|
|
if (FStatus == FrsErrorNotFound) {
|
|
FStatus = ChgOrdInsertDirRecord(ThreadCtx,
|
|
DIRTableCtx,
|
|
IDTableRec,
|
|
ChangeOrder,
|
|
Replica,
|
|
TRUE);
|
|
}
|
|
}
|
|
} else if (JustTombstone) {
|
|
FStatus = DbsInsertTable(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
IDTablex,
|
|
NULL);
|
|
} else {
|
|
FStatus = ChgOrdInsertIDRecord(ThreadCtx,
|
|
IDTableCtx,
|
|
DIRTableCtx,
|
|
ChangeOrder,
|
|
Replica);
|
|
}
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT_FS(0, "++ ERROR - ChgOrdInsertIDRecord failed.", FStatus);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - IDT Insert failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_IDT_ENTRY);
|
|
//
|
|
// Remember this is a new file so we can delete the IDTable entry
|
|
// if the CO fails.
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_NEW_FILE);
|
|
|
|
} else if (ReAnimate) {
|
|
//
|
|
// Update the ID table record now to reflect the new fid and the
|
|
// new name / parent. to avoid problems if this change order is
|
|
// kicked into retry. One such problem occurs when the retry path
|
|
// resurrects the id table entry with its old name by marking the
|
|
// id table entry as "not deleted". This could cause false name
|
|
// morph conflicts later
|
|
// if the old name is in conflict with another id table entry and
|
|
// could potentially break the name morph logic or cause collisions
|
|
// in the name conflict table.
|
|
//
|
|
CopyMemory(IDTableRec->FileName, CoCmd->FileName, CoCmd->FileNameLength);
|
|
IDTableRec->FileName[CoCmd->FileNameLength/sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
IDTableRec->ParentFileID = ChangeOrder->NewParentFid;
|
|
IDTableRec->ParentGuid = CoCmd->NewParentGuid;
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IDT Reanimate name field Update");
|
|
|
|
// SUDARC-DEV
|
|
|
|
// FRS_ASSERT(IDTableRec->ParentFileID != ZERO_FID);
|
|
|
|
if (IDTableRec->ParentFileID == ZERO_FID) {
|
|
DPRINT(4, "WARN - Parent fid is zero.\n");
|
|
}
|
|
|
|
// SUDARC-DEV
|
|
|
|
|
|
FRS_ASSERT(IDTableRec->FileID != ZERO_FID);
|
|
|
|
FStatus = DbsUpdateIDTableFields(ThreadCtx,
|
|
Replica,
|
|
ChangeOrder,
|
|
IdtFieldReanimateList,
|
|
IdtCoReanimateFieldCount);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT_FS(0, "++ ERROR - DbsUpdateIDTableFields();", FStatus);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - IDT Update FID/Name failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is not a retry of a prior CO or a recovery CO or a parent
|
|
// reanimation CO or a MorphGenCo or Move out generated CO
|
|
// then insert the change order into the Inbound Log.
|
|
//
|
|
if (!RetryCo &&
|
|
!RecoveryCo &&
|
|
!MorphGenCo &&
|
|
!ParentReanimation &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_DELETE_GEN_CO)) {
|
|
|
|
FStatus = ChgOrdInsertInlogRecord(ThreadCtx,
|
|
InLogTableCtx,
|
|
ChangeOrder,
|
|
Replica,
|
|
DuplicateCo || RetryPreinstall);
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(ChangeOrder->pParentGuid));
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT_FS(0, "++ ERROR - ChgOrdInsertInlogRecord failed.", FStatus);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - INLOG Insert Failed");
|
|
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
|
|
//
|
|
// Check if this is a duplicate remote CO. In that case do not delete
|
|
// the idtable entry or the preinstall file for this CO as there is
|
|
// another CO in the inbound log that is still processing this file.
|
|
// Check the state of the CO in the inbound log. If it has passed
|
|
// the IBCO_FETCH_RETRY state then send the ack for this CO. If it
|
|
// has not completed staging yet then do not send the ack for this CO.
|
|
//
|
|
|
|
|
|
if (FStatus == FrsErrorKeyDuplicate) {
|
|
|
|
//
|
|
// This is a duplicate CO so don't delete the preinstall file
|
|
// and the idtable record for it.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_PREINSTALL);
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_IDT_ENTRY);
|
|
|
|
//
|
|
// Send an ack to the upstream member only if there is another CO in
|
|
// the inbound log and if its state is passed IBCO_FETCH_RETRY
|
|
// which we determine below by reading the record.
|
|
//
|
|
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND);
|
|
|
|
KeyArray[0] = (PVOID)&CoCmd->CxtionGuid;
|
|
KeyArray[1] = (PVOID)&CoCmd->ChangeOrderGuid;
|
|
|
|
jerr = DbsOpenTable(ThreadCtx, TmpINLOGTableCtx, Replica->ReplicaNumber, INLOGTablex, NULL);
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT1_JS(0, "DbsOpenTable (INLOG) on replica number %d failed.",
|
|
Replica->ReplicaNumber, jerr);
|
|
DbsCloseTable(jerr1, ThreadCtx->JSesid, TmpINLOGTableCtx);
|
|
DbsFreeTableCtx(TmpINLOGTableCtx, 1);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
FStatus = DbsRecordOperationMKey(ThreadCtx,
|
|
ROP_READ,
|
|
KeyArray,
|
|
ILCxtionGuidCoGuidIndexx,
|
|
TmpINLOGTableCtx);
|
|
|
|
|
|
DPRINT_FS(0,"++ ERROR - DbsRecordOperationMKey failed.", FStatus);
|
|
|
|
//
|
|
// Send an ack to the upstream member only if there is another CO in
|
|
// the inbound log and if its state is passed IBCO_FETCH_RETRY
|
|
//
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DupCoCmd = (PCHANGE_ORDER_RECORD)TmpINLOGTableCtx->pDataRecord;
|
|
|
|
if (DupCoCmd->State > IBCO_FETCH_RETRY) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Dup Co setting ISCU_ACK_INBOUND");
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND);
|
|
}
|
|
|
|
DupCoCmd = NULL;
|
|
}
|
|
|
|
//
|
|
// Close the table and free the storage.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, TmpINLOGTableCtx);
|
|
DPRINT_JS(0,"ERROR - JetCloseTable on TmpINLOGTableCtx failed :", jerr);
|
|
|
|
DbsFreeTableCtx(TmpINLOGTableCtx, 1);
|
|
}
|
|
|
|
goto CO_CLEANUP;
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co INLOG Insert");
|
|
}
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_INLOG);
|
|
} else {
|
|
if (MorphGenCo) {
|
|
//
|
|
// MorphGenCos are local Cos and start out in Staging Requested.
|
|
//
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, IBCO_STAGING_REQUESTED);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// A file can end up with different names on different computers //
|
|
// if an update wins out over a rename. The update will flow back //
|
|
// to the computers that have already executed the rename. The //
|
|
// update must rename the file back to its original position. //
|
|
// In some cases a movedir may get converted into a create when //
|
|
// the original parent dir has been deleted. So these creates //
|
|
// could turn out to be implicit movedirs too. //
|
|
// //
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Not local change order
|
|
// Not a control change order
|
|
// Not a delete, movedir, moveout or movers
|
|
// unless it is a vvjoin CO (which could be a create or a delete)
|
|
// BUT the parent or the file name are different than those in the ID
|
|
// table. This must be an update that won out over a previously
|
|
// applied rename. Rename the file back to the name and parent at
|
|
// the time of the update.
|
|
//
|
|
// VVJOIN COs are special cased to cover the following scenario,
|
|
// A Dir move CO is still in the outbound log of upstream when the
|
|
// connection vvjoins (because it was deleted and created again or
|
|
// the outbound log has been trimmed) in this case we will get a vvjoin
|
|
// create CO for a file at its new location. This vvjoin CO should
|
|
// implicitly move the file or dir as the original CO for move is lost.
|
|
//
|
|
// However, in the case of delete change orders coming from a VVJoin
|
|
// we just want to create the tombstone even if the file had moved
|
|
// to a different parent before it was deleted on the upstream member.
|
|
//
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
if (!LocalCo && !ControlCo &&
|
|
( CO_NEW_FILE(LocationCmd) ||
|
|
!CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCATION_CMD) ||
|
|
CO_FLAG_ON(ChangeOrder, CO_FLAG_VVJOIN_TO_ORIG)
|
|
) &&
|
|
( !GUIDS_EQUAL(&CoCmd->NewParentGuid, &IDTableRec->ParentGuid) ||
|
|
WSTR_NE(IDTableRec->FileName, CoCmd->FileName)
|
|
)
|
|
) {
|
|
|
|
//
|
|
// Put out a trace record so we know when the new MOVEDIR cases hit.
|
|
//
|
|
if (FrsDoesCoAlterNameSpace(CoCmd) &&
|
|
!CO_FLAG_ON(ChangeOrder, CO_FLAG_VVJOIN_TO_ORIG)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "**** NEW Implicit MOVEDIR case");
|
|
}
|
|
|
|
ChangeOrder->OriginalParentFid = IDTableRec->ParentFileID;
|
|
CoCmd->OldParentGuid = IDTableRec->ParentGuid;
|
|
//
|
|
// Make it a MOVEDIR if the parent changed
|
|
//
|
|
if (!GUIDS_EQUAL(&CoCmd->NewParentGuid, &IDTableRec->ParentGuid)) {
|
|
//
|
|
// VVJOIN Cos do not have content command flag set so we should
|
|
// set it here so that StuInstallStage installs the file after
|
|
// moving it to the correct location.
|
|
//
|
|
// see above about no Implicit Movedir for delete Cos.
|
|
// this was 256531 hit by Cox.
|
|
//
|
|
if (!DOES_CO_DELETE_FILE_NAME(CoCmd)) {
|
|
CoCmd->ContentCmd |= USN_REASON_DATA_EXTEND;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_CONTENT_CMD);
|
|
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_MOVEDIR);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Implicit MOVEDIR");
|
|
LocationCmd = CO_LOCATION_MOVEDIR;
|
|
}
|
|
|
|
//
|
|
// Make it a simple rename if the parent did not change
|
|
//
|
|
} else {
|
|
//
|
|
// A simple rename does not have a location command and it
|
|
// has a content command with the value USN_REASON_NEW_NAME.
|
|
// The location command needs to be cleared so that StuInstallStage
|
|
// correctly does the rename of the file to the correct location.
|
|
// see above about no Implicit rename for delete Cos.
|
|
//
|
|
|
|
if (!DOES_CO_DELETE_FILE_NAME(CoCmd)) {
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_NUM_CMD);
|
|
LocationCmd = CO_LOCATION_NUM_CMD;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_CONTENT_CMD);
|
|
CoCmd->ContentCmd |= USN_REASON_RENAME_NEW_NAME;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Implicit RENAME");
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check for a Directory Enumerate pending on the IDTable entry for
|
|
// this file. If found, walk through and touch all the children.
|
|
// Note that this could be triggered by some other CO that is
|
|
// operating on this directory, not just the MOVEIN CO that put the
|
|
// dir into the ENUM_PENDING state. This solves the problem where
|
|
// one CO does a MOVEIN of a subdir and a subsequent CO does a MOVEDIR
|
|
// on a child dir before the parent of the child dir has the ENUM performed
|
|
// on it. (Actually not sure this can really happen because the
|
|
// child dir would still not be in the replica set so the journal should
|
|
// treat it as a MOVEIN.)
|
|
//
|
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_ENUM_PENDING)) {
|
|
//
|
|
// Do the enumeration.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Enum Request Started");
|
|
|
|
WStatus = ChgOrdMoveInDirTree(Replica, ChangeOrder);
|
|
DPRINT1_WS(0, "++ ERROR - Failed to enum dir: %ws;",CoCmd->FileName, WStatus);
|
|
|
|
//
|
|
// Clear the flag bit in the IDTable record.
|
|
//
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_ENUM_PENDING);
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx,
|
|
Replica,
|
|
ChangeOrder,
|
|
ISCU_NO_CLEANUP_MERGE |
|
|
ISCU_UPDATE_IDT_FLAGS
|
|
);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
}
|
|
|
|
//
|
|
// If this CO is a Directory Enum request then we are now done so
|
|
// clean it up.
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_ENUM_REQUESTED)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Enum Request Completed");
|
|
|
|
CLEAR_CO_IFLAG(ChangeOrder, CO_IFLAG_DIR_ENUM_PENDING);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Reserve resources for this CO in the hold issue interlock tables.
|
|
//
|
|
if (!ChgOrdReserve(IDTableRec, ChangeOrder, Replica)) {
|
|
goto CO_CLEANUP;
|
|
}
|
|
//
|
|
// PERF: At this point we should be able to unidle the queue
|
|
// to let other threads grab a change order since the entry in
|
|
// the hash table provides the necessary interlock.
|
|
// TRY THIS LATER ONCE A SINGLE THREAD WORKS
|
|
//
|
|
if (SerializeAllChangeOrders) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
}
|
|
|
|
//
|
|
// Close the replica tables. The RtCtx struct continues on with
|
|
// the change order until it retires.
|
|
//
|
|
jerr = DbsCloseReplicaTables(ThreadCtx, Replica, RtCtx, TRUE);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT_JS(0,"++ ERROR - DbsCloseReplicaTables failed:", jerr);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Close Tbl Failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
|
|
//
|
|
// If COE_FLAG_NEED_DELETE is set then the file is in the deferred
|
|
// delete state which means the delete was never successfull
|
|
// (say because of a sharing violation). So we have the file.
|
|
// We still need to fetch the file from upstream because we may
|
|
// have a older copy of the file/dir. The only case when we
|
|
// don't want to fetch the file from upstream is when a local
|
|
// child create/update triggerred the reanimation of parent.
|
|
// The upstream may or may not have the parent in this case.
|
|
// As a future work we should send this parent reanimation CO
|
|
// to our outbound partners so that all the members have the same
|
|
// version of the parent.
|
|
//
|
|
// e.g. Everyone has V1 of parent.
|
|
// Member 2 locks V1.
|
|
// Everyone goes to V2 of parent.
|
|
// Member 1 locks V2
|
|
// Everyone deletes parent.
|
|
// Child created on M1 and M2.
|
|
// Both reanimate and keep their copy of parent.
|
|
// If parent reanimation is not sent out then both with have a different
|
|
// copy of parent.
|
|
//
|
|
FilePresent = COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
if (FilePresent && LocalCo && !ControlCo && ParentReanimation) {
|
|
//
|
|
// Retire the CO here if we already have the file and this is
|
|
// a reanimation. We don't need to have it deleted.
|
|
//
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "File Present");
|
|
ChgOrdInboundRetired(ChangeOrder);
|
|
} else {
|
|
//
|
|
// Dispatch the change order to the appropriate next stage.
|
|
//
|
|
FStatus = ChgOrdDispatch(ThreadCtx, IDTableRec, ChangeOrder, Replica);
|
|
if (FStatus == FrsErrorCantMoveOut) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Retry - FrsErrorCantMoveOut");
|
|
// Following is commented because we want to retry the delete of parent dir.
|
|
// ChgOrdDispatch fails with FrsErrorCantMoveOut if there are
|
|
// valid children under the parent dir.
|
|
// ChgOrdReject(ChangeOrder, Replica);
|
|
// goto CO_CLEANUP;
|
|
goto CO_RETRY_LATER;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
ChangeOrder = NULL;
|
|
//
|
|
// The CO was issued but if we are serializing all change orders
|
|
// on this volume then set the flag to leave the process queue idle.
|
|
//
|
|
UnIdleProcessQueue = !SerializeAllChangeOrders;
|
|
goto CO_CONTINUE;
|
|
|
|
|
|
|
|
CO_RETRY_LATER:
|
|
CO_RETRY_PREINSTALL:
|
|
CO_DEFER_DUP_CO:
|
|
//
|
|
// This is a duplicate CO which cannot be processed right now.
|
|
// If it's not a retry then insert CO into the inbound log.
|
|
// Either way, skip it for now. We should never be here with a
|
|
// deamnd refresh flag set in the CO because we should detect
|
|
// the case of a dup CO before we know the file is deleted.
|
|
//
|
|
if (!RetryCo && !RecoveryCo && !MorphGenCo) {
|
|
FRS_ASSERT(!CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH));
|
|
|
|
FStatus = ChgOrdInsertInlogRecord(ThreadCtx,
|
|
TmpINLOGTableCtx,
|
|
ChangeOrder,
|
|
Replica,
|
|
DuplicateCo || RetryPreinstall);
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(ChangeOrder->pParentGuid));
|
|
|
|
//
|
|
// The pDataRecord points to the change order command which will
|
|
// be freed with the change order. Also clear the Jet Set/Ret Col
|
|
// address fields for the Change Order Extension buffer to
|
|
// prevent reuse since that buffer also goes with the change order.
|
|
//
|
|
TmpINLOGTableCtx->pDataRecord = NULL;
|
|
DBS_SET_FIELD_ADDRESS(TmpINLOGTableCtx, COExtensionx, NULL);
|
|
|
|
if (FStatus == FrsErrorKeyDuplicate) {
|
|
//
|
|
// Got a new dup remote co before we could finish and ack the
|
|
// previous one. Probably a result of restart. Discard this
|
|
// one. Send an ack to the upstream member only if there the
|
|
// CO in the inbound log has passed IBCO_FETCH_RETRY state
|
|
// (which means it has already sent its Ack, which was
|
|
// probably dropped, and won't send another) which we
|
|
// determine below by reading the record.
|
|
//
|
|
FRS_ASSERT(!LocalCo);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
|
|
//
|
|
// Do not discard the vv slot as it is being used by the duplicate
|
|
// Change order.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND |
|
|
ISCU_ACTIVATE_VV |
|
|
ISCU_ACTIVATE_VV_DISCARD);
|
|
|
|
KeyArray[0] = (PVOID)&CoCmd->CxtionGuid;
|
|
KeyArray[1] = (PVOID)&CoCmd->ChangeOrderGuid;
|
|
|
|
jerr = DbsOpenTable(ThreadCtx, TmpINLOGTableCtx, Replica->ReplicaNumber, INLOGTablex, NULL);
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT1_JS(0, "DbsOpenTable (INLOG) on replica number %d failed.",
|
|
Replica->ReplicaNumber, jerr);
|
|
DbsCloseTable(jerr1, ThreadCtx->JSesid, TmpINLOGTableCtx);
|
|
DbsFreeTableCtx(TmpINLOGTableCtx, 1);
|
|
goto CO_CLEANUP;
|
|
}
|
|
|
|
FStatus = DbsRecordOperationMKey(ThreadCtx,
|
|
ROP_READ,
|
|
KeyArray,
|
|
ILCxtionGuidCoGuidIndexx,
|
|
TmpINLOGTableCtx);
|
|
|
|
|
|
DPRINT_FS(0,"++ ERROR - DbsRecordOperationMKey failed.", FStatus);
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DupCoCmd = (PCHANGE_ORDER_RECORD)TmpINLOGTableCtx->pDataRecord;
|
|
|
|
if (DupCoCmd->State > IBCO_FETCH_RETRY) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Dup Co setting ISCU_ACK_INBOUND");
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND);
|
|
}
|
|
|
|
DupCoCmd = NULL;
|
|
}
|
|
|
|
//
|
|
// Close the table and free the storage.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, TmpINLOGTableCtx);
|
|
DPRINT_JS(0,"ERROR - JetCloseTable on TmpINLOGTableCtx failed :", jerr);
|
|
|
|
DbsFreeTableCtx(TmpINLOGTableCtx, 1);
|
|
|
|
} else
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - INLOG Insert Failed");
|
|
DPRINT_FS(0, "++ ERROR - ChgOrdInsertInlogRecord failed.", FStatus);
|
|
FRS_ASSERT(!"INLOG Insert Failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
}
|
|
} else {
|
|
//
|
|
// Set the retry flag so we will try again later.
|
|
//
|
|
InterlockedIncrement(&Replica->InLogRetryCount);
|
|
}
|
|
|
|
|
|
CO_CLEANUP:
|
|
//
|
|
// In general a branch to this point indicates that the entry in the
|
|
// InLog did not get done. If the CO was rejected for other reasons
|
|
// we flow through here to cleanup state. If it was a remote CO then
|
|
// update the entry in the Version vector for dampening and Ack the
|
|
// Co to our inbound partner. We also get here if we are draining
|
|
// the COs for this cxtion from the process queue.
|
|
//
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
CO_CONTINUE:
|
|
//
|
|
// All done. Decrement local change order queue count for this
|
|
// replica at the end so there is no window during which a save mark
|
|
// could occur and see a zero count even though we have yet to
|
|
// actually insert the CO into the Inlog.
|
|
// The LocalCoQueueCount only tracks local COs that came from
|
|
// the NTFS journal. i.e.,
|
|
// LocalCo, Not a Retry Co, Not a recovery Co, Not a block issue
|
|
// case, Not a control CO, and not a parent reanimation.
|
|
// Note: A Local Co should not have the CO_FLAG_PARENT_REANIMATION
|
|
// flag set.
|
|
//
|
|
// CO_FLAG_MOVEIN_GEN NOTE: This flag does not currently appear to be
|
|
// set anywhere (6/2002). It is tested in several places to bypass
|
|
// an INC_LOCAL_CO_QUEUE_COUNT . It appears that if this flag does
|
|
// get set then the below may NOT bypass the DECREMENT so the count will
|
|
// go negative. This would suppress updates of the journal read point
|
|
// during times of volume activity outside the replica tree and could
|
|
// result in a journal wrap. So if this flag does get set, consider
|
|
// revamping this code to use a COE flag set by journal code when
|
|
// a new local co is created and queued to the process queue. Then
|
|
// just test the flag to control the decrement.
|
|
//
|
|
if (LocalCo &&
|
|
!RetryCo &&
|
|
!RecoveryCo &&
|
|
(Decision != ISSUE_CONFLICT) &&
|
|
!ControlCo &&
|
|
!MorphGenCo) {
|
|
|
|
// FRS_ASSERT(!ParentReanimation);
|
|
|
|
if (!ParentReanimation) {
|
|
DEC_LOCAL_CO_QUEUE_COUNT(Replica);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unidle the queue, reset the timeout.
|
|
//
|
|
if (UnIdleProcessQueue) {
|
|
FrsRtlUnIdledQueue(IdledQueue);
|
|
}
|
|
|
|
TimeOut = 100;
|
|
ReleaseVmeRef(pVme);
|
|
} // End of while(TRUE)
|
|
|
|
//
|
|
// Get exception status.
|
|
//
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
GET_EXCEPTION_CODE(WStatus);
|
|
}
|
|
|
|
|
|
} finally {
|
|
//
|
|
// Shutdown change order accept.
|
|
//
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
if (AbnormalTermination()) {
|
|
WStatus = ERROR_OPERATION_ABORTED;
|
|
}
|
|
}
|
|
|
|
DPRINT_WS(0, "Chg Order Accept finally.", WStatus);
|
|
//
|
|
// Stop the Retry thread.
|
|
//
|
|
FrsRunDownCommandServer(&ChgOrdRetryCS, &ChgOrdRetryCS.Queue);
|
|
|
|
//
|
|
// Close any open tables and terminate the Jet Session.
|
|
//
|
|
|
|
//
|
|
// Close the tables and free the table ctx structs and our thread ctx.
|
|
//
|
|
DbsFreeTableContext(TmpIDTableCtx, ThreadCtx->JSesid);
|
|
TmpIDTableCtx = NULL;
|
|
|
|
DbsFreeTableContext(TmpIDTableCtxNC, ThreadCtx->JSesid);
|
|
TmpIDTableCtxNC = NULL;
|
|
|
|
DbsFreeTableContext(TmpINLOGTableCtx, ThreadCtx->JSesid);
|
|
TmpINLOGTableCtx = NULL;
|
|
|
|
//
|
|
// Now close the jet session and free the Jet ThreadCtx.
|
|
//
|
|
jerr = DbsCloseJetSession(ThreadCtx);
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT_JS(0,"++ DbsCloseJetSession :", jerr);
|
|
} else {
|
|
DPRINT(4,"++ DbsCloseJetSession complete\n");
|
|
}
|
|
|
|
ThreadCtx = FrsFreeType(ThreadCtx);
|
|
|
|
//
|
|
// Trigger FRS shutdown if we terminated abnormally.
|
|
//
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT(0, "Changeorder accept terminated abnormally, forcing service shutdown.\n");
|
|
FrsIsShuttingDown = TRUE;
|
|
SetEvent(ShutDownEvent);
|
|
}
|
|
|
|
DPRINT(0, "ChangeOrder Thread is exiting\n");
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdFetchCmd(
|
|
PFRS_QUEUE *pIdledQueue,
|
|
PCHANGE_ORDER_ENTRY *pChangeOrder,
|
|
PVOLUME_MONITOR_ENTRY *ppVme
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Fetch the next change order command from FrsVolumeLayerCOList.
|
|
FrsVolumeLayerCOList is a striped queue that combines the change order
|
|
process queues from each active volume. We return with the queue lock
|
|
and a pointer to the head entry on the queue. If the caller can
|
|
process the change order the caller removes the entry and drops the
|
|
queue lock. If the caller can't process the entry due to a conflict with
|
|
a change order currently in process then the caller leaves the entry
|
|
on the queue, idles the queue and calls us again. We can then pick up
|
|
work from another queue or wait until the conflict clears and the queue
|
|
is un-blocked.
|
|
|
|
Arguments:
|
|
|
|
pIdledQueue - Returns the ptr to the Change Order Process queue to idle.
|
|
pChangeOrder - Returns the ptr to the change order entry from the queue.
|
|
ppVme - Returns the ptr to the Volume Monitor entry associated with the queue.
|
|
|
|
Return Value:
|
|
|
|
FrsError status
|
|
|
|
If successful this returns with the FrsVolumeLayerCOList Lock held and a
|
|
reference taken on the Volume Monitor Entry (Vme).
|
|
|
|
The caller must release the lock and drop the reference.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdFetchCmd:"
|
|
|
|
ULONG WStatus;
|
|
PFRS_QUEUE Queue;
|
|
PLIST_ENTRY Entry;
|
|
PCHANGE_ORDER_ENTRY ChangeOrder;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
PREPLICA Replica;
|
|
ULONG TimeNow;
|
|
ULONG WaitTime;
|
|
BOOL LocalCo;
|
|
|
|
//
|
|
// We stay in the loop until we either get a change order to process or
|
|
// the queue is rundown.
|
|
//
|
|
while(TRUE) {
|
|
|
|
//
|
|
// Check if it's time to retry any inbound COs. If so then
|
|
// submit retry cmds to the CO Retry thread.
|
|
//
|
|
TimeNow = GetTickCount();
|
|
|
|
WaitTime = ChgOrdNextRetryTime - TimeNow;
|
|
if ((WaitTime > InlogRetryInterval) || (WaitTime == 0)) {
|
|
DPRINT3(4, "NextRetryTime: %08x Time: %08x Diff: %08x\n",
|
|
ChgOrdNextRetryTime, TimeNow, WaitTime);
|
|
ChgOrdNextRetryTime += InlogRetryInterval;
|
|
//
|
|
// Check for any CO retries needed on this replica set.
|
|
//
|
|
ForEachListEntry( &ReplicaListHead, REPLICA, ReplicaList,
|
|
// Loop iterator pE is type PREPLICA.
|
|
if ((pE->ServiceState == REPLICA_STATE_ACTIVE) &&
|
|
(pE->InLogRetryCount > 0)) {
|
|
//
|
|
// Allocate command packet and submit to the CO Retry thread.
|
|
//
|
|
ChgOrdRetrySubmit(pE, NULL, FCN_CORETRY_ALL_CXTIONS, FALSE);
|
|
}
|
|
);
|
|
}
|
|
|
|
//
|
|
// Wait on the queue so that rundowns can be detected. Waiting
|
|
// on the control queue (FrsVolumeLayerCOList) can hang shutdown
|
|
// because not all pVme->ChangeOrderLists are rundown; so the
|
|
// control queue remains "active".
|
|
//
|
|
WStatus = FrsRtlWaitForQueueFull(&FrsVolumeLayerCOList, 10000);
|
|
|
|
if (WStatus == ERROR_INVALID_HANDLE ||
|
|
ChangeOrderAcceptIsShuttingDown) {
|
|
DPRINT(1, "Shutting down ChgOrdAccept...\n");
|
|
//
|
|
// Queue was run down. Time to exit.
|
|
//
|
|
return FrsErrorQueueIsRundown;
|
|
}
|
|
|
|
if (WStatus == WAIT_TIMEOUT) {
|
|
continue;
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Some other error.
|
|
//
|
|
DPRINT_WS(0, "Wait for queue error", WStatus);
|
|
return FrsErrorQueueIsRundown;
|
|
}
|
|
|
|
//
|
|
// Perf: With multiple queues we aren't guaranteed to get the one
|
|
// nearest expiration. Replace below with a loop over each list on the
|
|
// full queue so we can compute the wait times for the head item on
|
|
// each queue then build an ordered list to tell us which entry to
|
|
// run next.
|
|
//
|
|
// Now peek at the first entry on the queue and see if time is up.
|
|
// This is ugly and it is the reason this isn't a command server.
|
|
//
|
|
FrsRtlAcquireListLock(&FrsVolumeLayerCOList);
|
|
|
|
if (FrsVolumeLayerCOList.ControlCount == 0) {
|
|
//
|
|
// Somebody got here before we did, drop lock and go wait on the queue.
|
|
//
|
|
DPRINT(4, "++ Control Count is zero\n");
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
continue;
|
|
}
|
|
//
|
|
// Get next queue on the FULL list & get the pVme containing the queue.
|
|
//
|
|
Entry = GetListHead(&FrsVolumeLayerCOList.Full);
|
|
Queue = CONTAINING_RECORD(Entry, FRS_QUEUE, Full);
|
|
pVme = CONTAINING_RECORD(Queue, VOLUME_MONITOR_ENTRY, ChangeOrderList);
|
|
|
|
//
|
|
// Get the reference on the Vme. If the return is zero the Vme is gone.
|
|
// This could happen since there is a window between getting the queue
|
|
// and getting the entry off the queue.
|
|
//
|
|
if (AcquireVmeRef(pVme) == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Get a pointer to the first change order entry on the queue.
|
|
//
|
|
Entry = GetListHead(&Queue->ListHead);
|
|
|
|
ChangeOrder = CONTAINING_RECORD(Entry, CHANGE_ORDER_ENTRY, ProcessList);
|
|
FRS_PRINT_TYPE(5, ChangeOrder);
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
//
|
|
// If this is a Local Change order then Check the Jrnl Cxtion for this
|
|
// Replica Set. If it is unjoined then don't wait for Aging cache
|
|
// timeout. Fill in the Cxtion ptr in the Change order.
|
|
//
|
|
if (LocalCo && (ChangeOrder->Cxtion == NULL)) {
|
|
|
|
Replica = CO_REPLICA(ChangeOrder);
|
|
FRS_ASSERT(Replica != NULL);
|
|
|
|
ACQUIRE_CXTION_CO_REFERENCE(Replica, ChangeOrder);
|
|
}
|
|
|
|
//
|
|
// If the wait time has not expired then sleep for the time left.
|
|
// The local change order entry stays on the queue. It can accumulate
|
|
// additional changes or evaporate because it is still in the aging
|
|
// cache. Note that when remote change orders are inserted on the
|
|
// queue TimeNow and TimeToRun are set to the current time so they
|
|
// don't wait. If Cxtion is unjoined then don't wait.
|
|
//
|
|
if (ChangeOrder->Cxtion != NULL) {
|
|
|
|
TimeNow = GetTickCount();
|
|
WaitTime = ChangeOrder->TimeToRun - TimeNow;
|
|
if ((WaitTime <= ChangeOrderAgingDelay) && (WaitTime != 0)) {
|
|
DPRINT3(4, "TimeToRun: %08x Time: %08x Diff: %08x\n",
|
|
ChangeOrder->TimeToRun, TimeNow, WaitTime);
|
|
//
|
|
// PERF: Instead of sleeping post a queue wakeup (unidle) so
|
|
// we can continue to process other volume queues.
|
|
//
|
|
|
|
//
|
|
// Drop the lock and reference for the wait.
|
|
//
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
ReleaseVmeRef(pVme);
|
|
//Sleep(WaitTime * 100);
|
|
Sleep(WaitTime);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Pull the local change order out of the hash table. We have the
|
|
// change order process list lock, preventing the journal from
|
|
// updating it. Recovery COs and Retry COs come from the
|
|
// Inbound log and aren't in the aging cache.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_IN_AGING_CACHE)) {
|
|
|
|
BOOL RetryCo, RecoveryCo, MorphGenCo;
|
|
ULONG GStatus;
|
|
|
|
RetryCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY);
|
|
RecoveryCo = RecoveryCo(ChangeOrder);
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
|
|
FRS_ASSERT(LocalCo && !RecoveryCo && !RetryCo && !MorphGenCo);
|
|
|
|
GStatus = GhtRemoveEntryByAddress(pVme->ChangeOrderTable,
|
|
ChangeOrder,
|
|
TRUE);
|
|
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_IN_AGING_CACHE);
|
|
|
|
if (GStatus != GHT_STATUS_SUCCESS) {
|
|
DPRINT(0, "++ ERROR - GhtRemoveEntryByAddress failed.\n");
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"GhtRemoveEntryByAddress failed.");
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
//ChgOrdReject(ChangeOrder, Replica);
|
|
return FrsErrorQueueIsRundown;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Delay has elapsed.
|
|
// Return the change order and its associated queue and Vme.
|
|
// If the caller can process the change order the caller removes it
|
|
// from the queue.
|
|
//
|
|
*pIdledQueue = Queue;
|
|
*pChangeOrder = ChangeOrder;
|
|
*ppVme = pVme;
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdHoldIssue(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PFRS_QUEUE CoProcessQueue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine ensures that a new change order does on conflict with a
|
|
change order already in progress. If it does then we set a flag in the
|
|
active state indicating the change order process queue is blocked and
|
|
and return status. The process queue is blocked while holding the change
|
|
order lock (using the FID or parent FID) to synchronize with the active
|
|
change order retire operation.
|
|
|
|
There are four file dependency cases to consider plus the case of duplicates.
|
|
|
|
1. File update/create on file X must preceed any subsequent file
|
|
update/delete on file X. (X could be a file or a Dir)
|
|
|
|
2. Parent dir create/update (e.g. ACL, RO) must preceed any
|
|
subsequent child file or dir operations.
|
|
|
|
3. Child File or Dir operations must preceed any subsequent parent dir
|
|
update/delete.
|
|
|
|
4. Any rename or delete operation that releases a filename must preceed
|
|
any rename or create operation that will reuse the filename.
|
|
|
|
5. Duplicate change orders can arrive from multiple inbound partners.
|
|
If they arrive at the same time then we could have one in progress while
|
|
the second tries to issue. We can't immediately dampen the second CO because
|
|
the first may fail (E.G. can't complete the fetch) so we mark the duplicate
|
|
CO for retry and stick it in the Inbound log incase the currently active
|
|
CO fails.
|
|
|
|
Three hash tables are used to keep the active state:
|
|
|
|
- The ActiveInboundChangeOrderTable keeps an entry for the change order
|
|
when it is issued. The CO stays in the table until it retires or
|
|
a retry later is requested. This table is indexed by the FileGuid
|
|
so consecutive changes to the same file are performed in order.
|
|
|
|
- The ActiveChildren hash table tracks the Parent File IDs of all active
|
|
change orders. Each time a change order issues, the entry for its
|
|
parent is found (or created) and incremented. When the count goes
|
|
to zero the entry is removed. If a change order was waiting for the
|
|
parent count to go to zero it is unblocked.
|
|
|
|
- The Name Conflict table is indexed by a hash of the file name and
|
|
the parent directory object ID. If a conflict occurs with an
|
|
outstanding opertion the issuing change order must block until
|
|
the current change order completes. A deleted file and the source
|
|
file of a rename reserve an entry in the table so they can block issue
|
|
of a subsequent create or a rename with a target name that matches.
|
|
The reverse check is not needed since that is handled by the
|
|
ActiveInboundChangeOrderTable.
|
|
|
|
Assumptions:
|
|
|
|
1. There are no direct interdependencies between Child File or Dir operations
|
|
and ancestor directories beyond the immediate parent.
|
|
2. The caller has acquired the FrsVolumeLayerCOList lock.
|
|
|
|
Arguments:
|
|
|
|
Replica - The Replica set for the CO (also get us to the volume monitor
|
|
entry associated with the replica set).
|
|
|
|
ChangeOrder -- The new change order to check ordering conflicts.
|
|
|
|
CoProcessQueue -- The process queue this change order is on.
|
|
If the change order can't issue we idle the queue.
|
|
The caller has acquired the queue lock.
|
|
|
|
Return Value:
|
|
|
|
ISSUE_OK means no conflict so the change order can be issued.
|
|
|
|
ISSUE_DUPLICATE_REMOTE_CO means this is a duplicate change order.
|
|
|
|
ISSUE_CONFLICT means a dependency condition exists so this change order
|
|
can't issue.
|
|
|
|
ISSUE_RETRY_LATER means that the parent dir is not present so this CO
|
|
will have to be retried later.
|
|
|
|
ISSUE_REJECT_CO means that we have determined that this CO can be safely
|
|
rejected. This can happen:
|
|
-- if the inbound connection has been deleted so we
|
|
can't fetch the staging file.
|
|
-- if the CO event time is too far into the future so
|
|
it is considered bogus.
|
|
-- if the CO is a morph gen follower and it's apparent
|
|
that the leader didn't get its job done.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdHoldIssue:"
|
|
|
|
ULONGLONG QuadHashValue;
|
|
LONGLONG CurrentTime;
|
|
LONGLONG EventTimeDelta;
|
|
|
|
ULONG GStatus;
|
|
PQHASH_ENTRY QHashEntry;
|
|
ULONG LocationCmd;
|
|
BOOL RemoteCo;
|
|
BOOL DropRef = FALSE;
|
|
BOOL ForceNameConflictCheck = FALSE;
|
|
BOOL OrigParentPresent;
|
|
BOOL SelectedParentPresent, EitherParentAbsent;
|
|
BOOL TargetPresent;
|
|
BOOL DestParentAbsentOrDel, OrigParentAbsentOrDel;
|
|
BOOL DestParentDelDeferred;
|
|
BOOL MorphGenCo, CoFromInlog, DeleteCo;
|
|
|
|
PCHANGE_ORDER_ENTRY ActiveChangeOrder;
|
|
PCHANGE_ORDER_COMMAND CoCmd;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
PCXTION Cxtion;
|
|
ULONG GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
|
|
#define CXTION_STR_MAX 256
|
|
WCHAR CxtionStr[CXTION_STR_MAX], WDelta[15];
|
|
|
|
pVme = Replica->pVme;
|
|
CoCmd = &ChangeOrder->Cmd;
|
|
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
TargetPresent = !COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_TARGET_ABS);
|
|
|
|
OrigParentPresent = !COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
|
|
EitherParentAbsent = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS |
|
|
COE_FLAG_IDT_NEW_PARENT_ABS);
|
|
|
|
SelectedParentPresent = !COE_FLAG_ON(ChangeOrder,
|
|
(ChangeOrder->NewReplica != NULL) ?
|
|
COE_FLAG_IDT_NEW_PARENT_ABS :
|
|
COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
//
|
|
// Synchronize with the Replica command server thread that may be changing
|
|
// the cxtion's state, joinguid, or checking Cxtion->CoProcessQueue.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
Cxtion = ChangeOrder->Cxtion;
|
|
if (Cxtion == NULL) {
|
|
DPRINT(0, "++ ERROR - No connection struct for inbound CO\n");
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
//
|
|
// Every remote change order was received from a cxtion. The change
|
|
// order must be "sent" to that cxtion again for processing. Find it.
|
|
// If it no longer exists then discard the change order.
|
|
//
|
|
// The cxtion can be in any of several states that will end up sending
|
|
// this change order through the retry path. Those states are checked
|
|
// later since they could change between now and issueing the change
|
|
// order to the replica command server.
|
|
//
|
|
if (RemoteCo) {
|
|
//
|
|
// Check if the Event Time on this CO is too far in the future by
|
|
// twice the MaxPartnerClockSkew.
|
|
// This could happen if a member disconnected and its time was
|
|
// set into the future while file operations were performed.
|
|
// Then when the time is set back it reconnects and Joins successfully
|
|
// but is now sending change orders with bogus times.
|
|
//
|
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
|
EventTimeDelta = CoCmd->EventTime.QuadPart - CurrentTime;
|
|
|
|
if (EventTimeDelta > (LONGLONG)(MaxPartnerClockSkew<<1)) {
|
|
EventTimeDelta = EventTimeDelta / CONVERT_FILETIME_TO_MINUTES;
|
|
DPRINT1(0, "++ ERROR: ChangeOrder rejected based on Bogus Event Time > MaxPartnerClockSkew by %08x %08x minutes.\n",
|
|
PRINTQUAD(EventTimeDelta));
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Bogus Event Time - Rejected");
|
|
|
|
_snwprintf(CxtionStr, CXTION_STR_MAX, FORMAT_CXTION_PATH2W,
|
|
PRINT_CXTION_PATH2(Replica, Cxtion));
|
|
CxtionStr[CXTION_STR_MAX-1] = UNICODE_NULL;
|
|
|
|
_itow(PartnerClockSkew, WDelta, 10);
|
|
EPRINT3(EVENT_FRS_RMTCO_TIME_SKEW, WDelta, CoCmd->FileName, CxtionStr);
|
|
|
|
DPRINT1(0, "++ ERROR: Bogus Event Time on CO via connection: %ws\n", CxtionStr);
|
|
//
|
|
// Should we force an unjoin on this connection? Probably not
|
|
// because if the time on the upstream partner was out of whack
|
|
// and it originated a bunch of COs with the bad time but the
|
|
// time is now ok we would just end up unjoining and rejoining
|
|
// on every bum CO until we get them out of the system.
|
|
//
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
//
|
|
// Check if this is a "Recovery CO" from an inbound partner.
|
|
// If it is and the partner connection is not yet joined then we
|
|
// hold issue until the join completes.
|
|
//
|
|
if (RecoveryCo(ChangeOrder)) {
|
|
//
|
|
// If the connection is trying to join then hold issue on this CO
|
|
// until it either succeeds or fails.
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateStarting) ||
|
|
CxtionStateIs(Cxtion, CxtionStateScanning) ||
|
|
CxtionStateIs(Cxtion, CxtionStateWaitJoin) ||
|
|
CxtionStateIs(Cxtion, CxtionStateSendJoin)) {
|
|
//
|
|
// Save the queue address we are blocked on.
|
|
// When JOIN finally succeeds or fails we get unblocked with
|
|
// the appropriate connection state change by a replica
|
|
// command server thread.
|
|
//
|
|
Cxtion->CoProcessQueue = CoProcessQueue;
|
|
|
|
GuidToStr(&CoCmd->ChangeOrderGuid, GuidStr);
|
|
DPRINT3(3, "++ CO hold issue on pending JOIN, vsn %08x %08x, CoGuid: %s for %ws\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
//
|
|
// Idle the queue and drop the connection table lock.
|
|
//
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Cxtion Join");
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
}
|
|
}
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
//
|
|
// If this is a remote CO then it came with a Guid. If this is a local CO
|
|
// then ChgOrdTranslateGuidFid() told us if there is a record in the IDTable.
|
|
//
|
|
// If the Guid is zero then this is a new file from a local change order
|
|
// that was not in the IDTable. So it can't be in the
|
|
// ActiveInboundChangeOrderTable.
|
|
//
|
|
GStatus = GHT_STATUS_NOT_FOUND;
|
|
|
|
if (RemoteCo || TargetPresent) {
|
|
/* !IS_GUID_ZERO(&CoCmd->FileGuid) */
|
|
FRS_ASSERT(!IS_GUID_ZERO(&CoCmd->FileGuid));
|
|
|
|
//
|
|
// Get the ChangeOrder lock from a lock table using a hash of the
|
|
// change order FileGuid. This resolves the race idleing the queue
|
|
// here and unidleing it in ChgOrdIssueCleanup().
|
|
//
|
|
GuidLockSlot = ChgOrdGuidLock(&CoCmd->FileGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
|
|
//
|
|
// Check if the file has an active CO on it. If it does then
|
|
// we can't issue this CO because the active CO could be changing
|
|
// the ACL or the RO bit or it could just be a prior update to the
|
|
// file so ordering has to be maintained.
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&CoCmd->FileGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
DropRef = (GStatus == GHT_STATUS_SUCCESS);
|
|
|
|
//
|
|
// Check if we have seen this change order before. This could happen if
|
|
// we got the same change order from two different inbound partners. We
|
|
// can't dampen the second one until the first one completes sucessfully.
|
|
// If for some reason the first one fails during fetch or install (say
|
|
// the install file is corrupt or a sharing violation on the target
|
|
// causes a retry) then we may need to get the change from the other
|
|
// partner. When the first change order completes successfully all the
|
|
// duplicates are rejected by reconcile when they are retried later.
|
|
// At that point we can Ack the inbound partner.
|
|
//
|
|
if (RemoteCo &&
|
|
(GStatus == GHT_STATUS_SUCCESS) &&
|
|
GUIDS_EQUAL(&CoCmd->ChangeOrderGuid,
|
|
&ActiveChangeOrder->Cmd.ChangeOrderGuid)) {
|
|
|
|
//DPRINT(5, "++ Hit a case of duplicate change orders.\n");
|
|
//DPRINT(5, "++ Incoming CO\n");
|
|
//FRS_PRINT_TYPE(5, ChangeOrder);
|
|
//DPRINT(5, "++ Active CO\n");
|
|
//FRS_PRINT_TYPE(5, ActiveChangeOrder);
|
|
|
|
//
|
|
// Remove the duplicate entry from the queue, drop the locks and
|
|
// return with duplicate remote co status.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
DropRef = FALSE;
|
|
//FrsRtlIdleQueueLock(CoProcessQueue);
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
return ISSUE_DUPLICATE_REMOTE_CO;
|
|
}
|
|
|
|
//
|
|
// If the conflict is with an active change order on the same file
|
|
// then set the flag in the active change order to unidle the queue
|
|
// when the active change order completes.
|
|
//
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - File Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
if (DropRef) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
DropRef = FALSE;
|
|
}
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
} else {
|
|
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
|
|
//
|
|
// Target absent and a Local CO.
|
|
//
|
|
//
|
|
// If this CO is a MorphGenFollower then it is the rename MorphGenCo
|
|
// produced as part of a Dir/Dir Name Morph Conflict (DLD Case). The
|
|
// fact that the target is still absent means that the base create CO
|
|
// (the leader) failed and is in retry so reject this MorphGenCo. It
|
|
// gets refabricated later when the Leader is re-issued.
|
|
// ***** Note -- THere is similar code in ChgOrdAccept() *****
|
|
//
|
|
if (MorphGenCo && COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER)) {
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - MORPH_GEN_FOLLOWER and leader failed");
|
|
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
// If this CO is a MorphGenleader then it is the rename MorphGenCo
|
|
// produced as part of a Dir/Dir Name Morph Conflict (DDL Case). The
|
|
// fact that the target is absent means that there isn't a conflict .
|
|
// anymore. Reject this CO as there is no need for a rename anymore.
|
|
//
|
|
// The following if makes sure that this is a morph gen rename CO generated
|
|
// to rename the idt entry.
|
|
//
|
|
// Do not pitch MorphGen Delete Cos. An incoming Create Co that
|
|
// looses the name conflict produces a MorphGenLeader DelCo with
|
|
// no IDTable Entry.
|
|
// ***** Note -- THere is similar code in ChgOrdAccept() *****
|
|
//
|
|
if (MorphGenCo &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER) &&
|
|
(GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command) != CO_LOCATION_DELETE) &&
|
|
BooleanFlagOn(ChangeOrder->Cmd.ContentCmd, USN_REASON_RENAME_NEW_NAME)) {
|
|
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - IDT deleted. No rename needed.");
|
|
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
//
|
|
// The FileGuid should be zero (i.e. The file wasn't found in the IDTable).
|
|
// Except for the case where this is a MorphGenCo (e.g. a delete) where the
|
|
// IDTable loses the name. It could also be a recovery local CO that
|
|
// came from the INlog. It could also be a parent reanimation CO that was
|
|
// triggerred by a local child update CO.
|
|
//
|
|
CoFromInlog = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY) ||
|
|
RecoveryCo(ChangeOrder);
|
|
|
|
FRS_ASSERT(IS_GUID_ZERO(&CoCmd->FileGuid) || MorphGenCo || CoFromInlog ||
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION));
|
|
|
|
if (!CO_NEW_FILE(LocationCmd)) {
|
|
|
|
if (IS_GUID_ZERO(&CoCmd->FileGuid)) {
|
|
DPRINT1(0, "++ WARN - FileGuid is zero, Location cmd is not a create: %d\n",
|
|
LocationCmd);
|
|
}
|
|
//
|
|
// An update CO could arrive out of order relative to its create
|
|
// if the create got delayed by a sharing violation. We don't
|
|
// want to lose the update so we make it look like a create.
|
|
// This also handles the case of delete change orders generated
|
|
// by name morph conflicts in which a rename arrives for a
|
|
// nonexistent file. We need to force a Name table conflict
|
|
// check below.
|
|
//
|
|
ForceNameConflictCheck = TRUE;
|
|
}
|
|
//
|
|
// We can't return yet. Still need to check Tunnelled OID and
|
|
// the parent dir.
|
|
//
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If no conflict with an active change order using Cmd.ChangeOrderGuid
|
|
// then recheck with the OID we read from the file that could have
|
|
// been tunnelled.
|
|
//
|
|
if (!RemoteCo &&
|
|
(COE_FLAG_ON(ChangeOrder, COE_FLAG_OID_FROM_FILE)) &&
|
|
!GUIDS_EQUAL(&CoCmd->FileGuid, &ChangeOrder->FileObjectId)) {
|
|
|
|
//
|
|
// Get the ChangeOrder lock from a lock table using a hash of the
|
|
// change order FileGuid. This resolves the race idleing the queue
|
|
// here and unidleing it in ChgOrdIssueCleanup().
|
|
//
|
|
GuidLockSlot = ChgOrdGuidLock(&ChangeOrder->FileObjectId);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&ChangeOrder->FileObjectId,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
DropRef = (GStatus == GHT_STATUS_SUCCESS);
|
|
|
|
//
|
|
// If the conflict is with an active change order on the same file
|
|
// then set the flag in the active change order to unidle the queue
|
|
// when the active change order completes.
|
|
//
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict Tunnel - File Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
if (DropRef) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
DropRef = FALSE;
|
|
}
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Check on the condition of the Parent Dirs.
|
|
//
|
|
|
|
//
|
|
// If the parent of a localCo is absent then reject the CO. This can happen
|
|
// if the child is renamed to a new dir (MOVEDIR) and then the old parent
|
|
// is deleted. e.g.
|
|
// Variation 1 Variation 2
|
|
//
|
|
// Cre parent1 cre parent1
|
|
// cre parent1\child move pre-existing-child to parent1
|
|
// rename child to another dir rename child to another dir
|
|
// del parent1 del parent1
|
|
//
|
|
// re: Variation 1-
|
|
// When processed the cre parent1 will not be on the disk since the delete
|
|
// has already been executed so the cre parent1 is rejected.
|
|
// When the cre parent1\child is processed there is no IDTable entry
|
|
// for parent1. So reject this Co.
|
|
// When the rename (MOVEDIR) is processed we treat it as a MOVEIN since
|
|
// there is no IDTable entry for the child but now we do have an IDTable
|
|
// entry for the parent. Del Parent1 is processed as a delete on a deleted
|
|
// file and is rejected.
|
|
//
|
|
// Variation 2 is similiar to 1 but by the time we process the move of
|
|
// a pre-existing child to parent1 (MOVEDIR) we see parent1 has been
|
|
// deleted (and there is no IDTable entry for it just like variation 1).
|
|
// In this case, parent1 is the new parent (or dest dir). The next CO
|
|
// then renames the child to some other dir and again there is no IDTable
|
|
// entry for parent1, the source dir in this case.
|
|
//
|
|
// The following sequence shows a case where both parents of a movedir
|
|
// are absent (movedir child to par2).
|
|
// cre par1 ; cre par1\child ; cre par2 ; movedir child to par2 ;
|
|
// cre par3 ; movedir child to par3 ; del par1 ; del par2
|
|
//
|
|
// Rule: If dest parent is absent or deleted then reject the CO BUT if the
|
|
// CO is a delete (or MOVEOUT) then this is the end of the line for
|
|
// this file and there won't be any follow-on CO.
|
|
//
|
|
// Note: If the dest parent is marked deleted in IDTable but the target file
|
|
// is present then there must be another operation coming up that does a
|
|
// MOVEDIR onthe file. We will treat that as a MOVEIN when it comes.
|
|
//
|
|
// Note: if the dest parent delete occured because of a remote CO delete
|
|
// that was processed before this CO then either the target file will have
|
|
// been deleted along with the parent (the NO ACTIVE CHILD case) or some
|
|
// MOVEDIR has placed it under a different parent.
|
|
//
|
|
// Either way reject this CO and let the file be handled by a subsequent CO.
|
|
//
|
|
// We can not reject the local CO if the parent is marked delete deferred.
|
|
// Instead we want this CO to reanimate the parent.
|
|
//
|
|
|
|
if (!RemoteCo) {
|
|
DestParentAbsentOrDel = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_ABS |
|
|
COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
|
|
DestParentDelDeferred = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL_DEF);
|
|
|
|
DeleteCo = (LocationCmd == CO_LOCATION_DELETE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT);
|
|
|
|
if (DestParentAbsentOrDel && !DestParentDelDeferred && !DeleteCo) {
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - Dest Par Abs or Del");
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
|
|
if (LocationCmd == CO_LOCATION_MOVEDIR) {
|
|
|
|
OrigParentAbsentOrDel = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS |
|
|
COE_FLAG_IDT_ORIG_PARENT_DEL);
|
|
if (OrigParentAbsentOrDel) {
|
|
//
|
|
// If the source parent dir is absent or deleted then let the
|
|
// CO continue as a simple rename.
|
|
//
|
|
// A simple rename does not have a location command and it
|
|
// has a content command with the value USN_REASON_NEW_NAME.
|
|
// The location command needs to be cleared so that StuInstallStage
|
|
// correctly does the rename of the file to the correct location.
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_NUM_CMD);
|
|
LocationCmd = CO_LOCATION_NUM_CMD;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_CONTENT_CMD);
|
|
CoCmd->ContentCmd |= USN_REASON_RENAME_NEW_NAME;
|
|
//
|
|
// If the original parent does not exist because of variation 1
|
|
// above then the FidToGuid translation will leave the
|
|
// oldparentguid zero. So copy the NewParentGuid to the OldParentGuid.
|
|
// Leaving the oldparentguid zero hits asserts related to hash
|
|
// table inserts.
|
|
//
|
|
if (IS_GUID_ZERO(&CoCmd->OldParentGuid)) {
|
|
COPY_GUID(&CoCmd->OldParentGuid, &CoCmd->NewParentGuid);
|
|
}
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Src Abs/Del cvt to simple RENAME");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check for CO Timeout --
|
|
//
|
|
// It is possible for us to get into a state where there is a CO on a child
|
|
// but the parent does not exist on any machine in the replica set. For
|
|
// example,the create CO for the parent may have been held up on the
|
|
// original machine andthe machine was removed form the replica set before
|
|
// sending the CO.
|
|
//
|
|
// Whatever the cause, we are not able to detect this situation. If the
|
|
// parent does exist, it can take an arbitrarily long amount of time to
|
|
// reach us. Since we cannot detect the total absense of the parent, all
|
|
// that we can do is wait a sufficiently long amount of time before giving
|
|
// up all hope of ever seeing it.
|
|
//
|
|
// We give up waiting for the parent once two conditions have been met.
|
|
// 1) We have retried this change order at least certain number of times.
|
|
// 2) A minimum amount of time has passed since we first tried this CO.
|
|
//
|
|
|
|
if (EitherParentAbsent && CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY)) {
|
|
PDATA_EXTENSION_RETRY_TIMEOUT CoCmdRetryTimeout;
|
|
|
|
CoCmdRetryTimeout = DbsDataExtensionFind(CoCmd->Extension, DataExtend_Retry_Timeout);
|
|
|
|
if(CoCmdRetryTimeout != NULL) {
|
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
|
|
|
// increase retry count
|
|
CoCmdRetryTimeout->Count++;
|
|
|
|
//
|
|
// we must update the inlog table because we have
|
|
// modified the RetryTimeout counts.
|
|
//
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_UPDATE_INLOG);
|
|
|
|
// get the time difference in minutes.
|
|
EventTimeDelta = CurrentTime - CoCmdRetryTimeout->FirstTryTime;
|
|
EventTimeDelta = EventTimeDelta / CONVERT_FILETIME_TO_MINUTES;
|
|
|
|
// if the count is too high and enough time has passed, trash this CO
|
|
if((CoCmdRetryTimeout->Count > MaxCoRetryTimeoutCount)
|
|
&&(EventTimeDelta > MaxCoRetryTimeoutMinutes)) {
|
|
|
|
//
|
|
// Remove the CO entry from the queue and remove
|
|
// the preinstall file and the ID Table entry.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_PREINSTALL);
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_IDT_ENTRY);
|
|
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Check if the parent DIR has an active CO on it. If it does then
|
|
// we can't issue this change order because the change on the parent
|
|
// could affect the ACL or the RO bit or ...
|
|
// Also check if there is a name conflict if this is a create or a rename.
|
|
//
|
|
if (SelectedParentPresent) {
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(ChangeOrder->pParentGuid));
|
|
|
|
GuidLockSlot = ChgOrdGuidLock(ChangeOrder->pParentGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
|
|
//
|
|
// Check for name conflict on a create or a rename target.
|
|
// Aliasing is possible in the hash table since we don't have the
|
|
// full key saved. So check for COs that remove a name since a
|
|
// second insert with the same hash value would cause a conflict.
|
|
//
|
|
// If there is a conflict then set the "queue blocked" flag in
|
|
// the name conflict table entry. The flag will be picked up by
|
|
// the co that decs the entry's reference count to 0.
|
|
//
|
|
// NameConflictTable Entry: An entry in the NameConflictTable
|
|
// contains a reference count of the number of active change
|
|
// orders that hash to that entry and a Flags word that is set
|
|
// to COE_FLAG_VOL_COLIST_BLOCKED if the process queue for this
|
|
// volume is idled while waiting on the active change orders for
|
|
// this entry to retire. The queue is idled when the entry at
|
|
// the head of the queue hashes to this entry and so may
|
|
// have a conflicting name (hence the name, "name conflict table").
|
|
//
|
|
// The NameConflictTable can give false positives. But this is okay
|
|
// because a false positive is rare and will only idle the process
|
|
// queue until the active change order retires. Getting rid
|
|
// of the rare false positives would degrade performance. The
|
|
// false positives that happen when inserting an entry into the
|
|
// NameConflictTable are handled by using the QData field in
|
|
// the QHashEntry as a the reference count.
|
|
//
|
|
// But, you ask, how can there be multiple active cos hashing to this
|
|
// entry if the process queue is idled when a conflict is detected?
|
|
// Easy, I say, because the filename in the co is used to detect
|
|
// collisions while the filename in the idtable is used to reserve the
|
|
// qhash entry. Why? Well, the name morph code handles the case of a
|
|
// co's name colliding with an idtable entry. But that code wouldn't
|
|
// work if active change orders were constantly changing the idtable.
|
|
// So, the NameConflictTable synchronizes the namespace amoung active
|
|
// change orders so that the name morph code can work against a static
|
|
// namespace.
|
|
//
|
|
// If this CO is a MORPH_GEN_FOLLOWER make sure we interlock with the
|
|
// MORPH_GEN_LEADER. The leader is removing the name so it makes an
|
|
// entry in the name conflict table. If the follower is a create it
|
|
// will check against the name with one of the DOES_CO predicates
|
|
// below, but if it is a file reanimation it may just look like an
|
|
// update so always make the interlock check if this CO is a follower.
|
|
//
|
|
if (ForceNameConflictCheck ||
|
|
DOES_CO_CREATE_FILE_NAME(CoCmd) ||
|
|
DOES_CO_REMOVE_FILE_NAME(CoCmd) ||
|
|
DOES_CO_DO_SIMPLE_RENAME(CoCmd) ||
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER)) {
|
|
|
|
ChgOrdCalcHashGuidAndName(&ChangeOrder->UFileName,
|
|
ChangeOrder->pParentGuid,
|
|
&QuadHashValue);
|
|
//
|
|
// There should be no ref that needs to be dropped here.
|
|
//
|
|
FRS_ASSERT(!DropRef);
|
|
|
|
QHashAcquireLock(Replica->NameConflictTable);
|
|
//
|
|
// MOVERS: check if we are looking at correct replica if this is a movers
|
|
//
|
|
QHashEntry = QHashLookupLock(Replica->NameConflictTable,
|
|
&QuadHashValue);
|
|
if (QHashEntry != NULL) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Name Table");
|
|
GuidToStr(ChangeOrder->pParentGuid, GuidStr);
|
|
DPRINT4(4, "++ NameConflictTable hit on file %ws, ParentGuid %s, "
|
|
"Key %08x %08x, RefCount %08x %08x\n",
|
|
ChangeOrder->UFileName.Buffer, GuidStr,
|
|
PRINTQUAD(QuadHashValue), PRINTQUAD(QHashEntry->QData));
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
//
|
|
// This flag will be picked up by the change order that
|
|
// decs this entry's count to 0.
|
|
//
|
|
QHashEntry->Flags |= (ULONG_PTR)COE_FLAG_VOL_COLIST_BLOCKED;
|
|
QHashReleaseLock(Replica->NameConflictTable);
|
|
goto NAME_CONFLICT;
|
|
}
|
|
QHashReleaseLock(Replica->NameConflictTable);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Check for active change order on the parent Dir.
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
ChangeOrder->pParentGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
DropRef = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Parent Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
FRS_ASSERT(!DropRef);
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
} else {
|
|
DPRINT(0, "++ ERROR - Neither parent is present.\n");
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
}
|
|
|
|
//
|
|
// Check for a conflict with some operation on the Original parent FID if
|
|
// this is a MOVEOUT, MOVERS or a MOVEDIR.
|
|
//
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
if (CO_MOVE_OUT_RS_OR_DIR(LocationCmd)) {
|
|
|
|
if (OrigParentPresent) {
|
|
FRS_ASSERT(!IS_GUID_ZERO(&CoCmd->OldParentGuid));
|
|
|
|
GuidLockSlot = ChgOrdGuidLock(&CoCmd->OldParentGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&CoCmd->OldParentGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
DropRef = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Orig Parent Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
FRS_ASSERT(!DropRef);
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
} else {
|
|
DPRINT(0, "++ ERROR - Orig Parent not present.\n");
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if this is an operation on a directory with change orders still
|
|
// active on any children. As an example there could be an active CO that
|
|
// deletes a file and this change order wants to delete the parent directory.
|
|
//
|
|
// But.. A new dir can't have any change orders active beneath it.
|
|
// Neither can a delete co generated to produce a tombstone.
|
|
//
|
|
if (!TargetPresent ||
|
|
(!RemoteCo && (LocationCmd == CO_LOCATION_DELETE) &&
|
|
CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN))) {
|
|
|
|
return ISSUE_OK;
|
|
}
|
|
|
|
QHashAcquireLock(pVme->ActiveChildren);
|
|
QHashEntry = QHashLookupLock(pVme->ActiveChildren,
|
|
&ChangeOrder->Cmd.FileGuid);
|
|
|
|
//
|
|
// If the conflict was on a dir that has change orders outstanding on one
|
|
// or more children then set the flag bit to unblock the queue when the
|
|
// count goes to zero.
|
|
//
|
|
if (QHashEntry != NULL) {
|
|
CHAR FileGuidString[GUID_CHAR_LEN];
|
|
GuidToStr(&ChangeOrder->Cmd.FileGuid, FileGuidString);
|
|
DPRINT2(4, "++ GAC: Interlock - Active children (%d) under GUID %s\n",
|
|
QHashEntry->QData>>1, FileGuidString);
|
|
|
|
if (!CoCmdIsDirectory(CoCmd)) {
|
|
DPRINT(0, "++ ERROR - Non Dir entry in pVme->ActiveChildren hash table.\n");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
}
|
|
|
|
//
|
|
// Set the flag and idle the queue and drop the table lock.
|
|
//
|
|
QHashEntry->QData |= 1;
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
QHashReleaseLock(pVme->ActiveChildren);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Child Busy");
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
|
|
QHashReleaseLock(pVme->ActiveChildren);
|
|
|
|
//
|
|
// If there are any remote change orders pending for retry of the install then
|
|
// check to see if this change order guid matches. If it does then we
|
|
// can retire this change order since it is a duplicate and we already
|
|
// have the staging file.
|
|
//
|
|
if (0 && RemoteCo &&
|
|
!CoCmdIsDirectory(CoCmd) &&
|
|
(ChangeOrder->NewReplica->InLogRetryCount > 0)) {
|
|
|
|
//
|
|
// PERF: add code to lookup CO by GUID and check for match.
|
|
//
|
|
// return with a change order reject error code
|
|
//
|
|
// Remove the duplicate entry from the queue.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
|
|
return ISSUE_OK;
|
|
|
|
|
|
//
|
|
// Issue conflict on the file or on the parent dir. Set flag in Active
|
|
// Change order to unblock the queue on retire and then drop the change
|
|
// order GUID lock.
|
|
//
|
|
CONFLICT:
|
|
|
|
SET_COE_FLAG(ActiveChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
|
|
DPRINT1(4, "++ GAC Setting COE_FLAG_VOL_COLIST_BLOCKED, block on: %08x %08x\n",
|
|
PRINTQUAD(ActiveChangeOrder->FileReferenceNumber));
|
|
|
|
//
|
|
// Conflict detected in name conflict table
|
|
//
|
|
NAME_CONFLICT:
|
|
|
|
if (DropRef) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// Idle the queue and drop the change order lock.
|
|
//
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
|
|
FRS_ASSERT(GuidLockSlot != UNDEFINED_LOCK_SLOT);
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
|
|
|
|
#define HI_AIB_T 0x00000001 // AIBCO on Target
|
|
#define HI_AIB_D 0x00000002 // AIBCO plus Dup Rmt Co check
|
|
#define HI_EPA 0x00000004 // Either parent absent
|
|
#define HI_NC 0x00000008 // name Conflict check
|
|
#define HI_ASP 0x00000010 // AIBCO on Selected Parent
|
|
#define HI_AOP 0x00000020 // AIBCO on Original Parent
|
|
#define HI_AC 0x00000040 // Check for active child under dir
|
|
#define HI_ISSUE_OK 0x10000000
|
|
#define HI_ISSUE_LATER 0x20000000 // return issue-retry-later status.
|
|
#define HI_ASSERT 0x40000000
|
|
#define HI_INVALID 0x80000000
|
|
|
|
|
|
// CoType -NewFile - (Create, Update before Cre, MovIn)
|
|
// DelFile - Delete file, MovOut, MovRS
|
|
// UpdFile - Update existing file, could be MovDir)
|
|
//SelParValid - Is selected parent (either NewPar or OrigPar) have a GUID or FID?
|
|
|
|
|
|
ULONG HoldIssueDecodeTable[32] = {
|
|
// AIBCO-Targ NameConf AIBCO-OldPar IssueOK // Rmt/ File/ CO Sel
|
|
// DupRmtCo? if movxxx // Lcl Dir Type Par
|
|
// EitherParAbs AIBCO on Active // Valid? -- Comment
|
|
// IsRetryLater Selected Par Child
|
|
|
|
HI_NC |HI_ASP |HI_ISSUE_OK, // Lcl File New y
|
|
HI_ASSERT, // Lcl File New n -- For local Cre, how come no parent?
|
|
|
|
HI_AIB_T |HI_NC |HI_ASP |HI_AOP |HI_ISSUE_OK, // Lcl File Del y -- If no target then we missed create. For local del, how come?
|
|
HI_ASSERT, // Lcl File Del n -- If either Parent missing then we missed ParCreate, For local Del, how come no parent?
|
|
|
|
HI_AIB_T |HI_NC |HI_ASP |HI_AOP |HI_ISSUE_OK, // Lcl File Upd y
|
|
HI_ASSERT, // Lcl File Upd n -- For local Upd, how come no parent?
|
|
|
|
HI_INVALID, // x
|
|
HI_INVALID, // x
|
|
|
|
HI_NC |HI_ASP |HI_ISSUE_OK, // Lcl Dir New y
|
|
HI_ASSERT, // Lcl Dir New n -- For local Cre, how come no parent?
|
|
|
|
HI_AIB_T |HI_NC |HI_ASP |HI_AOP |HI_AC |HI_ISSUE_OK, // Lcl Dir Del y -- If no target then we missed create. For local del, how come?
|
|
HI_ASSERT, // Lcl Dir Del n -- If either ParGuid is zero then we missed ParCreate, For local Del, how come no parent?
|
|
|
|
HI_AIB_T |HI_NC |HI_ASP |HI_AOP |HI_AC |HI_ISSUE_OK, // Lcl Dir Upd y
|
|
HI_ASSERT, // Lcl Dir Upd n -- For local Upd, how come no parent?
|
|
|
|
HI_INVALID, // x
|
|
HI_INVALID, // x
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_ISSUE_OK, // Rmt File New y -- Why retry later? Cre file in preinstall.
|
|
HI_ISSUE_LATER, // Rmt File New n -- IsRetryLater No parent means file lives in pre-install dir. Do This?
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_AOP |HI_ISSUE_OK, // Rmt File Del y -- If Target IDT Absent then we missed create. Do JustTombStone?
|
|
HI_ISSUE_LATER, // Rmt File Del n -- IsRetryLater If either Par IDT Absent then we missed ParCreate. Do JustTombStone?
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_AOP |HI_ISSUE_OK, // Rmt File Upd y
|
|
HI_ISSUE_LATER, // Rmt File Upd n -- IsRetryLater Why retry later? Update file in preinstall.
|
|
|
|
HI_INVALID, // x
|
|
HI_INVALID, // x
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_ISSUE_OK, // Rmt Dir New y -- Why retry later? Cre dir in preinstall.
|
|
HI_ISSUE_LATER, // Rmt Dir New n -- IsRetryLater No parent means dir lives in pre-install dir. Do This?
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_AOP |HI_AC |HI_ISSUE_OK, // Rmt Dir Del y -- If Target IDT Absent then we missed create. Do JustTombStone?
|
|
HI_ISSUE_LATER, // Rmt Dir Del n -- IsRetryLater If either Par IDT Absent then we missed ParCreate. Do JustTombStone?
|
|
|
|
HI_AIB_D | HI_EPA |HI_NC |HI_ASP |HI_AOP |HI_AC |HI_ISSUE_OK, // Rmt Dir Upd y -- Why retry later? Cre dir in preinstall.
|
|
HI_ISSUE_LATER, // Rmt Dir Upd n -- IsRetryLater No parent means dir lives in pre-install dir. Do This?
|
|
|
|
HI_INVALID, // x
|
|
HI_INVALID // x
|
|
};
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdHoldIssue2(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PFRS_QUEUE CoProcessQueue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine ensures that a new change order does on conflict with a
|
|
change order already in progress. If it does then we set a flag in the
|
|
active state indicating the change order process queue is blocked and
|
|
and return status. The process queue is blocked while holding the change
|
|
order lock (using the FID or parent FID) to synchronize with the active
|
|
change order retire operation.
|
|
|
|
There are four file dependency cases to consider plus the case of duplicates.
|
|
|
|
1. File update/create on file X must preceed any subsequent file
|
|
update/delete on file X. (X could be a file or a Dir)
|
|
|
|
2. Parent dir create/update (e.g. ACL, RO) must preceed any
|
|
subsequent child file or dir operations.
|
|
|
|
3. Child File or Dir operations must preceed any subsequent parent dir
|
|
update/delete.
|
|
|
|
4. Any rename or delete operation that releases a filename must preceed
|
|
any rename or create operation that will reuse the filename.
|
|
|
|
5. Duplicate change orders can arrive from multiple inbound partners.
|
|
If they arrive at the same time then we could have one in progress while
|
|
the second tries to issue. We can't immediately dampen the second CO because
|
|
the first may fail (E.G. can't complete the fetch) so we mark the duplicate
|
|
CO for retry and stick it in the Inbound log incase the currently active
|
|
CO fails.
|
|
|
|
Three hash tables are used to keep the active state:
|
|
|
|
- The ActiveInboundChangeOrderTable keeps an entry for the change order
|
|
when it is issued. The CO stays in the table until it retires or
|
|
a retry later is requested. This table is indexed by the FileGuid
|
|
so consecutive changes to the same file are performed in order.
|
|
|
|
- The ActiveChildren hash table tracks the Parent File IDs of all active
|
|
change orders. Each time a change order issues, the entry for its
|
|
parent is found (or created) and incremented. When the count goes
|
|
to zero the entry is removed. If a change order was waiting for the
|
|
parent count to go to zero it is unblocked.
|
|
|
|
- The Name Conflict table is indexed by a hash of the file name and
|
|
the parent directory object ID. If a conflict occurs with an
|
|
outstanding opertion the issuing change order must block until
|
|
the current change order completes. A deleted file and the source
|
|
file of a rename reserve an entry in the table so they can block issue
|
|
of a subsequent create or a rename with a target name that matches.
|
|
The reverse check is not needed since that is handled by the
|
|
ActiveInboundChangeOrderTable.
|
|
|
|
Assumptions:
|
|
|
|
1. There are no direct interdependencies between Child File or Dir operations
|
|
and ancestor directories beyond the immediate parent.
|
|
2. The caller has acquired the FrsVolumeLayerCOList lock.
|
|
|
|
Arguments:
|
|
|
|
Replica - The Replica set for the CO (also get us to the volume monitor
|
|
entry associated with the replica set).
|
|
|
|
ChangeOrder -- The new change order to check ordering conflicts.
|
|
|
|
CoProcessQueue -- The process queue this change order is on.
|
|
If the change order can't issue we idle the queue.
|
|
The caller has acquired the queue lock.
|
|
|
|
Return Value:
|
|
|
|
ISSUE_OK means no conflict so the change order can be issued.
|
|
|
|
ISSUE_DUPLICATE_REMOTE_CO means this is a duplicate change order.
|
|
|
|
ISSUE_CONFLICT means a dependency condition exists so this change order
|
|
can't issue.
|
|
|
|
ISSUE_RETRY_LATER means that the parent dir is not present so this CO
|
|
will have to be retried later.
|
|
|
|
--*/
|
|
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdHoldIssue2:"
|
|
|
|
ULONG GStatus;
|
|
PQHASH_ENTRY QHashEntry;
|
|
ULONG LocationCmd;
|
|
ULONG Decodex, DecodeMask;
|
|
BOOL RemoteCo;
|
|
BOOL DropRef = FALSE;
|
|
BOOL ForceNameConflictCheck = FALSE;
|
|
BOOL OrigParentPresent;
|
|
BOOL SelectedParentPresent, EitherParentAbsent;
|
|
BOOL TargetPresent;
|
|
|
|
PCHANGE_ORDER_ENTRY ActiveChangeOrder;
|
|
PCHANGE_ORDER_COMMAND CoCmd;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
PCXTION Cxtion;
|
|
ULONGLONG QuadHashValue;
|
|
ULONG GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
|
|
|
|
#undef FlagChk
|
|
#define FlagChk(_flag_) BooleanFlagOn(DecodeMask, _flag_)
|
|
|
|
pVme = Replica->pVme;
|
|
CoCmd = &ChangeOrder->Cmd;
|
|
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
TargetPresent = !COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_TARGET_ABS);
|
|
|
|
OrigParentPresent = !COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
|
|
EitherParentAbsent = COE_FLAG_ON(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS |
|
|
COE_FLAG_IDT_NEW_PARENT_ABS);
|
|
|
|
SelectedParentPresent = !COE_FLAG_ON(ChangeOrder,
|
|
(ChangeOrder->NewReplica != NULL) ?
|
|
COE_FLAG_IDT_NEW_PARENT_ABS :
|
|
COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
|
|
//
|
|
// Construct the dispatch index into the HoldIssueDecodeTable.
|
|
//
|
|
// 10 08 06 01
|
|
// Rmt/ File/ CO Sel
|
|
// Lcl Dir Type Par
|
|
// Valid?
|
|
//
|
|
Decodex = 0;
|
|
if (!RemoteCo) {
|
|
Decodex |= 0x10;
|
|
}
|
|
|
|
if (CoCmdIsDirectory(CoCmd)) {
|
|
Decodex |= 0x08;
|
|
}
|
|
|
|
if (!TargetPresent) {
|
|
// New Co
|
|
Decodex |= 0x00;
|
|
} else
|
|
if (DOES_CO_DELETE_FILE_NAME(CoCmd)) {
|
|
// Del Co (or MoveOut)
|
|
Decodex |= 0x02;
|
|
} else
|
|
if (TargetPresent) {
|
|
// Update existing file Co
|
|
Decodex |= 0x04;
|
|
} else
|
|
// Invalid Co
|
|
{ Decodex |= 0x06; };
|
|
|
|
if (SelectedParentPresent) {
|
|
Decodex |= 0x01;
|
|
}
|
|
|
|
DecodeMask = HoldIssueDecodeTable[Decodex];
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "DecodeIndex=", Decodex);
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "DecodeMask= ", DecodeMask);
|
|
|
|
FRS_ASSERT(!FlagChk(HI_INVALID));
|
|
|
|
//
|
|
// Synchronize with the Replica command server thread that may be changing
|
|
// the cxtion's state, joinguid, or checking Cxtion->CoProcessQueue.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
Cxtion = ChangeOrder->Cxtion;
|
|
if (Cxtion == NULL) {
|
|
DPRINT(0, "++ ERROR - No connection struct for inbound CO\n");
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
//
|
|
// Every remote change order was received from a cxtion. The change
|
|
// order must be "sent" to that cxtion again for processing. Find it.
|
|
// If it no longer exists then discard the change order.
|
|
//
|
|
// The cxtion can be in any of several states that will end up sending
|
|
// this change order through the retry path. Those states are checked
|
|
// later since they could change between now and issueing the change
|
|
// order to the replica command server.
|
|
//
|
|
if (RemoteCo) {
|
|
//
|
|
// Check if this is a "Recovery CO" from an inbound partner.
|
|
// If it is and the partner connection is not yet joined then we
|
|
// hold issue until the join completes.
|
|
//
|
|
if (RecoveryCo(ChangeOrder)) {
|
|
//
|
|
// If the connection is trying to join then hold issue on this CO
|
|
// until it either succeeds or fails.
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateStarting) ||
|
|
CxtionStateIs(Cxtion, CxtionStateScanning) ||
|
|
CxtionStateIs(Cxtion, CxtionStateWaitJoin) ||
|
|
CxtionStateIs(Cxtion, CxtionStateSendJoin)) {
|
|
//
|
|
// Save the queue address we are blocked on.
|
|
// When JOIN finally succeeds or fails we get unblocked with
|
|
// the appropriate connection state change by a replica
|
|
// command server thread.
|
|
//
|
|
Cxtion->CoProcessQueue = CoProcessQueue;
|
|
|
|
GuidToStr(&CoCmd->ChangeOrderGuid, GuidStr);
|
|
DPRINT3(3, "++ CO hold issue on pending JOIN, vsn %08x %08x, CoGuid: %s for %ws\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
//
|
|
// Idle the queue and drop the connection table lock.
|
|
//
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Cxtion Join");
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
}
|
|
}
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
|
|
//
|
|
// If this is a remote CO then it came with a Guid. If this is a local CO
|
|
// then ChgOrdTranslateGuidFid() told us if there is a record in the IDTable.
|
|
//
|
|
// If the Guid is zero then this is a new file from a local change order
|
|
// that was not in the IDTable. So it can't be in the
|
|
// ActiveInboundChangeOrderTable.
|
|
//
|
|
if (FlagChk(HI_AIB_T | HI_AIB_D)) {
|
|
GStatus = GHT_STATUS_NOT_FOUND;
|
|
|
|
if (RemoteCo || TargetPresent) {
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(&CoCmd->FileGuid));
|
|
|
|
//
|
|
// Get the ChangeOrder lock from a lock table using a hash of the
|
|
// change order FileGuid. This resolves the race idleing the queue
|
|
// here and unidleing it in ChgOrdIssueCleanup().
|
|
//
|
|
GuidLockSlot = ChgOrdGuidLock(&CoCmd->FileGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
|
|
//
|
|
// Check if the file has an active CO on it. If it does then
|
|
// we can't issue this CO because the active CO could be changing
|
|
// the ACL or the RO bit or it could just be a prior update to the
|
|
// file so ordering has to be maintained.
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&CoCmd->FileGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
DropRef = (GStatus == GHT_STATUS_SUCCESS);
|
|
|
|
//
|
|
// Check if we have seen this change order before. This could happen if
|
|
// we got the same change order from two different inbound partners. We
|
|
// can't dampen the second one until the first one completes sucessfully.
|
|
// If for some reason the first one fails during fetch or install (say
|
|
// the install file is corrupt or a sharing violation on the target
|
|
// causes a retry) then we may need to get the change from the other
|
|
// partner. When the first change order completes successfully all the
|
|
// duplicates are rejected by reconcile when they are retried later.
|
|
// At that point we can Ack the inbound partner.
|
|
//
|
|
if (FlagChk(HI_AIB_D)) {
|
|
if ((GStatus == GHT_STATUS_SUCCESS) &&
|
|
GUIDS_EQUAL(&CoCmd->ChangeOrderGuid,
|
|
&ActiveChangeOrder->Cmd.ChangeOrderGuid)) {
|
|
|
|
//DPRINT(5, "++ Hit a case of duplicate change orders.\n");
|
|
//DPRINT(5, "++ Incoming CO\n");
|
|
//FRS_PRINT_TYPE(5, ChangeOrder);
|
|
//DPRINT(5, "++ Active CO\n");
|
|
//FRS_PRINT_TYPE(5, ActiveChangeOrder);
|
|
|
|
//
|
|
// Remove the duplicate entry from the queue, drop the locks and
|
|
// return with duplicate remote co status.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
DropRef = FALSE;
|
|
//FrsRtlIdleQueueLock(CoProcessQueue);
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
return ISSUE_DUPLICATE_REMOTE_CO;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the conflict is with an active change order on the same file
|
|
// then set the flag in the active change order to unidle the queue
|
|
// when the active change order completes.
|
|
//
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - File Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
if (DropRef) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
DropRef = FALSE;
|
|
}
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
}
|
|
} // end of HI_AIB_T | HI_AIB_D
|
|
|
|
#if 0
|
|
else {
|
|
// Do we still need this?
|
|
//
|
|
// The FileGuid is zero (i.e. The file wasn't found in the IDTable).
|
|
// Make sure the location command is either a create or a movein.
|
|
//
|
|
FRS_ASSERT(IS_GUID_ZERO(&CoCmd->FileGuid));
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
if (!CO_NEW_FILE(LocationCmd)) {
|
|
|
|
DPRINT1(0, "++ WARN - GUID is zero, Location cmd is not a create: %d\n",
|
|
LocationCmd);
|
|
//
|
|
// An update CO could arrive out of order relative to its create
|
|
// if the create got delayed by a sharing violation. We don't
|
|
// want to lose the update so we make it look like a create.
|
|
// This also handles the case of delte change orders generated
|
|
// by name morph conflicts in which a rename arrives for a
|
|
// nonexistent file. We need to force a Name table conflict
|
|
// check below.
|
|
//
|
|
ForceNameConflictCheck = TRUE;
|
|
}
|
|
//
|
|
// We can't return yet. Still need to check the parent dir.
|
|
//
|
|
}
|
|
|
|
#endif
|
|
|
|
if (FlagChk(HI_EPA) && EitherParentAbsent) {
|
|
//
|
|
// If a parent DIR is missing (either original or new) then try later.
|
|
// Note: The parent could be deleted but in that case we will reanimate it
|
|
// if the CO is accepted.
|
|
//
|
|
|
|
FRS_ASSERT((ChangeOrder->OriginalParentFid == ZERO_FID) ||
|
|
(ChangeOrder->NewParentFid == ZERO_FID));
|
|
//
|
|
// Remove the CO entry from the queue.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
//
|
|
// Send this CO to retry to wait for the parent.
|
|
//
|
|
// we could still build the file/dir in the preinstall dir
|
|
return ISSUE_RETRY_LATER;
|
|
}
|
|
|
|
// need to check dep on old parent dir in the case of a MOVE DIR
|
|
|
|
|
|
|
|
if (FlagChk(HI_NC)) {
|
|
//
|
|
// Check if the parent DIR has an active CO on it. If it does then
|
|
// we can't issue this change order because the change on the parent
|
|
// could affect the ACL or the RO bit or ...
|
|
// Also check if there is a name conflict if this is a create or a rename.
|
|
//
|
|
|
|
FRS_ASSERT(SelectedParentPresent);
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(ChangeOrder->pParentGuid));
|
|
|
|
FRS_ASSERT(GuidLockSlot == UNDEFINED_LOCK_SLOT);
|
|
|
|
GuidLockSlot = ChgOrdGuidLock(ChangeOrder->pParentGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
|
|
//
|
|
// Check for name conflict on a create or a rename target.
|
|
// Aliasing is possible in the hash table since we don't have the
|
|
// full key saved. So check for COs that remove a name since a
|
|
// second insert with the same hash value would cause a conflict.
|
|
//
|
|
// If there is a conflict then set the "queue blocked" flag in
|
|
// the name conflict table entry. The flag will be picked up by
|
|
// the co that decs the entry's reference count to 0.
|
|
//
|
|
// NameConflictTable Entry: An entry in the NameConflictTable
|
|
// contains a reference count of the number of active change
|
|
// orders that hash to that entry and a Flags word that is set
|
|
// to COE_FLAG_VOL_COLIST_BLOCKED if the process queue for this
|
|
// volume is idled while waiting on the active change orders for
|
|
// this entry to retire. The queue is idled when the entry at
|
|
// the head of the queue hashes to this entry and so may
|
|
// have a conflicting name (hence the name, "name conflict table").
|
|
//
|
|
// The NameConflictTable can give false positives. But this is okay
|
|
// because a false positive is rare and will only idle the process
|
|
// queue until the active change order retires. Getting rid
|
|
// of the rare false positives would degrade performance. The
|
|
// false positives that happen when inserting an entry into the
|
|
// NameConflictTable are handled by using the QData field in
|
|
// the QHashEntry as a the reference count.
|
|
//
|
|
// But, you ask, how can there be multiple active cos hashing to this
|
|
// entry if the process queue is idled when a conflict is detected?
|
|
// Easy, I say, because the filename in the co is used to detect
|
|
// collisions while the filename in the idtable is used to reserve the
|
|
// qhash entry. Why? Well, the name morph code handles the case of a
|
|
// co's name colliding with an idtable entry. But that code wouldn't
|
|
// work if active change orders were constantly changing the idtable.
|
|
// So, the NameConflictTable synchronizes the namespace amoung active
|
|
// change orders so that the name morph code can work against a static
|
|
// namespace.
|
|
//
|
|
// If this CO is a MORPH_GEN_FOLLOWER make sure we interlock with the
|
|
// MORPH_GEN_LEADER. The leader is removing the name so it makes an
|
|
// entry in the name conflict table. If the follower is a create it
|
|
// will check against the name with one of the DOES_CO predicates
|
|
// below, but if it is a file reanimation it may just look like an
|
|
// update so always make the interlock check if this CO is a follower.
|
|
//
|
|
if (ForceNameConflictCheck ||
|
|
DOES_CO_CREATE_FILE_NAME(CoCmd) ||
|
|
DOES_CO_REMOVE_FILE_NAME(CoCmd) ||
|
|
DOES_CO_DO_SIMPLE_RENAME(CoCmd) ||
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER)) {
|
|
|
|
ChgOrdCalcHashGuidAndName(&ChangeOrder->UFileName,
|
|
ChangeOrder->pParentGuid,
|
|
&QuadHashValue);
|
|
//
|
|
// There should be no ref that needs to be dropped here.
|
|
//
|
|
FRS_ASSERT(!DropRef);
|
|
|
|
QHashAcquireLock(Replica->NameConflictTable);
|
|
// MOVERS: check if we are looking at correct replica if this is a movers")
|
|
QHashEntry = QHashLookupLock(Replica->NameConflictTable,
|
|
&QuadHashValue);
|
|
if (QHashEntry != NULL) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Name Table");
|
|
GuidToStr(ChangeOrder->pParentGuid, GuidStr);
|
|
DPRINT4(4, "++ NameConflictTable hit on file %ws, ParentGuid %s, "
|
|
"Key %08x %08x, RefCount %08x %08x\n",
|
|
ChangeOrder->UFileName.Buffer, GuidStr,
|
|
PRINTQUAD(QuadHashValue), PRINTQUAD(QHashEntry->QData));
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
//
|
|
// This flag will be picked up by the change order that
|
|
// decs this entry's count to 0.
|
|
//
|
|
QHashEntry->Flags |= (ULONG_PTR)COE_FLAG_VOL_COLIST_BLOCKED;
|
|
QHashReleaseLock(Replica->NameConflictTable);
|
|
goto NAME_CONFLICT;
|
|
}
|
|
QHashReleaseLock(Replica->NameConflictTable);
|
|
}
|
|
} // End of HI_NC
|
|
|
|
|
|
|
|
|
|
if (FlagChk(HI_ASP)) {
|
|
|
|
if (GuidLockSlot == UNDEFINED_LOCK_SLOT) {
|
|
GuidLockSlot = ChgOrdGuidLock(ChangeOrder->pParentGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
}
|
|
|
|
//
|
|
// Check for active change order on the parent Dir.
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
ChangeOrder->pParentGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
DropRef = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Parent Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
FRS_ASSERT(!DropRef);
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
} // end of HI_ASP
|
|
|
|
#if 0
|
|
// still need this?
|
|
} else {
|
|
DPRINT(0, "++ ERROR - ParentGuid is 0. Need to check if this op is on root\n");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
// add check for the FID or OID being the root dir of the tree
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
if (FlagChk(HI_AOP)) {
|
|
|
|
//
|
|
// Check for a conflict with some operation on the Original parent FID if
|
|
// this is a MOVEOUT, MOVERS or a MOVEDIR.
|
|
//
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
if (CO_MOVE_OUT_RS_OR_DIR(LocationCmd)) {
|
|
|
|
FRS_ASSERT(OrigParentPresent);
|
|
|
|
FRS_ASSERT(!IS_GUID_ZERO(&CoCmd->OldParentGuid));
|
|
|
|
FRS_ASSERT(GuidLockSlot == UNDEFINED_LOCK_SLOT);
|
|
|
|
GuidLockSlot = ChgOrdGuidLock(&CoCmd->OldParentGuid);
|
|
ChgOrdAcquireLock(GuidLockSlot);
|
|
//
|
|
// ** NOTE ** Be careful with ActiveChangeOrder, we drop the
|
|
// ref on it later.
|
|
//
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&CoCmd->OldParentGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
if (GStatus == GHT_STATUS_SUCCESS) {
|
|
DropRef = TRUE;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Orig Parent Busy");
|
|
goto CONFLICT;
|
|
}
|
|
|
|
FRS_ASSERT(!DropRef);
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
GuidLockSlot = UNDEFINED_LOCK_SLOT;
|
|
|
|
}
|
|
} // End of HI_AOP
|
|
|
|
|
|
|
|
#if 0
|
|
// Remove ???
|
|
//
|
|
// Check if this is an operation on a directory with change orders still
|
|
// active on any children. As an example there could be an active CO that
|
|
// deletes a file and this change order wants to delete the parent directory.
|
|
//
|
|
// But.. A new dir can't have any change orders active beneath it.
|
|
// Neither can a delete co generated to produce a tombstone.
|
|
//
|
|
if (!TargetPresent ||
|
|
(!RemoteCo && (LocationCmd == CO_LOCATION_DELETE) &&
|
|
CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN))) {
|
|
|
|
return ISSUE_OK;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
if (FlagChk(HI_AC)) {
|
|
|
|
QHashAcquireLock(pVme->ActiveChildren);
|
|
QHashEntry = QHashLookupLock(pVme->ActiveChildren,
|
|
&ChangeOrder->Cmd.FileGuid);
|
|
|
|
//
|
|
// If the conflict was on a dir that has change orders outstanding on one
|
|
// or more children then set the flag bit to unblock the queue when the
|
|
// count goes to zero.
|
|
//
|
|
if (QHashEntry != NULL) {
|
|
CHAR FileGuidString[GUID_CHAR_LEN];
|
|
GuidToStr(&ChangeOrder->Cmd.FileGuid, FileGuidString);
|
|
DPRINT2(4, "++ GAC: Interlock - Active children (%d) under GUID %s\n",
|
|
QHashEntry->QData>>1, FileGuidString);
|
|
|
|
if (!CoCmdIsDirectory(CoCmd)) {
|
|
DPRINT(0, "++ ERROR - Non Dir entry in pVme->ActiveChildren hash table.\n");
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
}
|
|
|
|
//
|
|
// Set the flag and idle the queue and drop the table lock.
|
|
//
|
|
QHashEntry->QData |= 1;
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
QHashReleaseLock(pVme->ActiveChildren);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IsConflict - Child Busy");
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
|
|
QHashReleaseLock(pVme->ActiveChildren);
|
|
|
|
} // End of HI_AC.
|
|
|
|
|
|
|
|
//
|
|
// If there are any remote change orders pending for retry of the install then
|
|
// check to see if this change order guid matches. If it does then we
|
|
// can retire this change order since it is a duplicate and we already
|
|
// have the staging file.
|
|
//
|
|
// need to enable")
|
|
if (0 && RemoteCo &&
|
|
!CoCmdIsDirectory(CoCmd) &&
|
|
(ChangeOrder->NewReplica->InLogRetryCount > 0)) {
|
|
|
|
// PERF: add code to lookup CO by GUID and check for match")
|
|
// return with a change order reject error code
|
|
//
|
|
// Remove the duplicate entry from the queue.
|
|
//
|
|
FrsRtlRemoveEntryQueueLock(CoProcessQueue, &ChangeOrder->ProcessList);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_ONLIST);
|
|
|
|
return ISSUE_REJECT_CO;
|
|
}
|
|
|
|
|
|
return ISSUE_OK;
|
|
|
|
//
|
|
// Issue conflict on the file or on the parent dir. Set flag in Active
|
|
// Change order to unblock the queue on retire and then drop the change
|
|
// order GUID lock.
|
|
//
|
|
CONFLICT:
|
|
|
|
SET_COE_FLAG(ActiveChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
|
|
DPRINT1(4, "++ GAC Setting COE_FLAG_VOL_COLIST_BLOCKED, block on: %08x %08x\n",
|
|
PRINTQUAD(ActiveChangeOrder->FileReferenceNumber));
|
|
|
|
//
|
|
// Conflict detected in name conflict table
|
|
//
|
|
NAME_CONFLICT:
|
|
|
|
if (DropRef) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// Idle the queue and drop the change order lock.
|
|
//
|
|
FrsRtlIdleQueueLock(CoProcessQueue);
|
|
|
|
FRS_ASSERT(GuidLockSlot != UNDEFINED_LOCK_SLOT);
|
|
|
|
ChgOrdReleaseLock(GuidLockSlot);
|
|
|
|
return ISSUE_CONFLICT;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
ChgOrdReconcile(
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PIDTABLE_RECORD IDTableRec
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Reconcile the incoming change order with the current state of the file
|
|
or dir in question.
|
|
|
|
Some special casing is done for duplicate remote change orders. The same
|
|
change order can arrive from multiple inbound partners before the version
|
|
vector can advance to dampen it. The first CO in the set of duplicates is
|
|
called X for the purpose of discussion. The rest are called the
|
|
duplicates. The duplicate COs will end up in the inbound log if CO X fails
|
|
to fetch the staging file. This allows any of the duplicates to retry the
|
|
Fetch operation later. Any CO (either X or a duplicate) that ultimately
|
|
completes the Fetch successfully updates the IDTable entry and activates
|
|
the version vector retire slot that maintains ordering of the change orders
|
|
as they propagate to the outbound log. It (along with the outlog process)
|
|
is also responsible for cleaning up the staging file. Finally this CO is
|
|
responsible for updating the version vector (through activation of the VV
|
|
slot).
|
|
|
|
At this point if the CO fails to complete the file install phase its state
|
|
is marked as either IBCO_INSTALL_RETRY or IBCO_INSTALL_REN_RETRY or
|
|
IBCO_INSTALL_DEL_RETRY. Any
|
|
other Dup COs are in the fetch retry or fetch request state. The reconcile
|
|
state (used in this function) in the IDTable entry for the file will now
|
|
match the state in any of the duplicate COs still in the inbound log. So
|
|
they will be rejected on retry. Note that the duplicate COs still need to
|
|
ACK their inbound partners and delete their entry from the inbound log.
|
|
They do not update the version vector since that was done by the CO
|
|
completing the fetch.
|
|
|
|
Arguments:
|
|
|
|
Replica - Replica set this change order applies to. Used to get config
|
|
record.
|
|
|
|
ChangeOrder - Change order entry
|
|
|
|
IDTableRec - The IDTable record to test this change order against.
|
|
|
|
Return Value:
|
|
|
|
TRUE if change order is accepted, FALSE if rejected.
|
|
|
|
--*/
|
|
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReconcile:"
|
|
|
|
LONGLONG EventTimeDelta;
|
|
LONGLONG SizeDelta;
|
|
LONG VersionDelta;
|
|
LONG RStatus;
|
|
PCHANGE_ORDER_COMMAND CoCmd = &ChangeOrder->Cmd;
|
|
|
|
|
|
FRS_ASSERT(IDTableRec != NULL);
|
|
|
|
//
|
|
// The sequence of events are complicated:
|
|
//
|
|
// 1. A remote co for an update comes in for a tombstoned idtable entry.
|
|
//
|
|
// 2. The remote co becomes a reanimate co.
|
|
//
|
|
// 3. The reanimate remote co loses a name conflict to another idtable entry.
|
|
//
|
|
// 4. A MorphGenCo for a local delete is generated for the
|
|
// reanimate remote co that lost the name conflict.
|
|
//
|
|
// 5. The MorphGenCo completes and updates the version so that the
|
|
// original reanimate co that lost the name conflict will be
|
|
// rejected.
|
|
//
|
|
// 6. The upcoming check for a leaf node (i.e. no outbound partners)
|
|
// accepts the original reanimate co and the service goes back to step 1
|
|
// causing a Morph Gen loop. To avoid this the following flag is
|
|
// set when the Morph Gen Co is created.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE)) {
|
|
DPRINT(4, "++ ChangeOrder rejected based on COE_FLAG_REJECT_AT_RECONCILE\n");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If this member has no outbound partners AND has changed the file
|
|
// previously we accept. We detect this by comparing the Originator GUID
|
|
// in the IDTable entry with the GUID of this member. This avoids problems
|
|
// where a leaf member modifies a file, thereby rejecting an update until
|
|
// the version number test or event time test causes an accept. A leaf
|
|
// member should not be trying to change replicated files.
|
|
//
|
|
if (NO_OUTLOG_PARTNERS(Replica) &&
|
|
GUIDS_EQUAL(&IDTableRec->OriginatorGuid, &Replica->ReplicaVersionGuid)) {
|
|
DPRINT(4, "++ ChangeOrder accepted based on leaf node\n");
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If the change order is in the IBCO_INSTALL_DEL_RETRY state then
|
|
// accept or reject it based on the COE_FLAG_NEED_DELETE flag.
|
|
// COE_FLAG_NEED_DELETE was set in ChgOrdReadIDRecord() when we found the
|
|
// IDREC_FLAGS_DELETE_DEFERRED flag set. See comments in schema.h for
|
|
// IDREC_FLAGS_DELETE_DEFERRED for more details on this scenario.
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY)) {
|
|
return COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
}
|
|
|
|
// PERF: Add test of USN on the file to USN in CO (if local) to see if
|
|
// another Local CO could be on its way.
|
|
//
|
|
// The EventTimeDelta is the difference between the changeorder EventTime
|
|
// and the saved EventTime in the IDTable of the last change order we
|
|
// accepted. If the EventTimeDelta is within plus or minus RecEventTimeWindow
|
|
// then we move to the next step of the decsion sequence. Otherwise
|
|
// we either accept or reject the changeorder.
|
|
//
|
|
EventTimeDelta = CoCmd->EventTime.QuadPart - IDTableRec->EventTime;
|
|
|
|
if (EventTimeDelta > (LONGLONG)(0)) {
|
|
if (EventTimeDelta > RecEventTimeWindow) {
|
|
DPRINT1(4, "++ ChangeOrder accepted based on EventTime > window: %08x %08x\n",
|
|
PRINTQUAD(EventTimeDelta));
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
if (-EventTimeDelta > RecEventTimeWindow) {
|
|
DPRINT1(4, "++ ChangeOrder rejected based on EventTime < window: %08x %08x\n",
|
|
PRINTQUAD(EventTimeDelta));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
DPRINT1(4, "++ ChangeOrder EventTime inside window: %08x %08x\n", PRINTQUAD(EventTimeDelta));
|
|
|
|
//
|
|
// The Changeorder eventtime is within the RecEventTimeWindow. The next
|
|
// step is to look at the version number of the file. The version number
|
|
// is updated each time the file is closed after being modified. So it
|
|
// acts like a time based on the rate of modification to the file.
|
|
//
|
|
VersionDelta = (LONG) (CoCmd->FileVersionNumber - IDTableRec->VersionNumber);
|
|
|
|
if ( VersionDelta != 0 ) {
|
|
DPRINT2(4, "++ ChangeOrder %s based on version number: %d\n",
|
|
(VersionDelta > 0) ? "accepted" : "rejected", VersionDelta);
|
|
return (VersionDelta > 0);
|
|
}
|
|
|
|
|
|
DPRINT(4, "++ ChangeOrder VersionDelta is zero.\n");
|
|
|
|
//
|
|
// The EventTimeDelta is inside the window and the Version Numbers match.
|
|
// This means that the file was changed at about the same time on two
|
|
// different members. We have to pick a winner so we use EventTimeDelta
|
|
// again to decide.
|
|
//
|
|
if ( EventTimeDelta != 0 ) {
|
|
DPRINT2(4, "++ ChangeOrder %s based on narrow EventTime: %08x %08x\n",
|
|
(EventTimeDelta > 0) ? "accepted" : "rejected",
|
|
PRINTQUAD(EventTimeDelta));
|
|
return (EventTimeDelta > 0);
|
|
}
|
|
|
|
DPRINT(4, "++ ChangeOrder EventTimeDelta is zero.\n");
|
|
|
|
//
|
|
// Both the version numbers and EventTimes match. Check the file sizes.
|
|
//
|
|
SizeDelta = (LONGLONG) (CoCmd->FileSize - IDTableRec->FileSize);
|
|
|
|
if ( SizeDelta != 0 ) {
|
|
DPRINT2(4, "++ ChangeOrder %s based on file size: %d\n",
|
|
(SizeDelta > 0) ? "accepted" : "rejected", SizeDelta);
|
|
return (SizeDelta > 0);
|
|
}
|
|
|
|
DPRINT(4, "++ ChangeOrder SizeDelta is zero.\n");
|
|
|
|
//
|
|
// The final compare is to do a comparison of the Originator Guids.
|
|
// If the Originator Guid of the incoming change order is greater
|
|
// or equal to the Guid in the IDTable Record then we accept the
|
|
// new change order.
|
|
//
|
|
RStatus = FrsGuidCompare(&CoCmd->OriginatorGuid,
|
|
&IDTableRec->OriginatorGuid);
|
|
|
|
//
|
|
// But first check to see if this is a duplicate change order
|
|
// (Originator GUID matches value in ID record and OriginatorVSN
|
|
// matches too). This closes a window where the duplicate changeorder
|
|
// is blocked behind another change order in the queue but past the point
|
|
// of the dampening check. The Active CO retires, unblocking the queue, and
|
|
// now the blocking CO issues followed by the dup CO issue.
|
|
//
|
|
if (RStatus == 0) {
|
|
//
|
|
// If the change order is in the IBCO_INSTALL_REN_RETRY state then
|
|
// accept or reject it based on the COE_FLAG_NEED_RENAME flag.
|
|
// COE_FLAG_NEED_RENAME was set in ChgOrdReadIDRecord() when we found the
|
|
// IDREC_FLAGS_RENAME_DEFERRED flag set. See comments in
|
|
// ChgOrdReadIDRecord() for more details on this scenario.
|
|
//
|
|
// Note: We still have to go thru the above reconcile tests since there
|
|
// could be multiple COs in the IBCO_INSTALL_REN_RETRY state and we
|
|
// need to reject all of them except the most recent. A sharing violation
|
|
// on the target could cause this and depending on the timing of the
|
|
// close of the file the next INSTALL_REN_RETRY CO to issue will do
|
|
// the rename. (This may not be a problem since the data from the
|
|
// final rename should come from the IDTable entry not the CO. The
|
|
// IDTable entry should be current so that subsequent name morph
|
|
// collision checks are accurate.)
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_REN_RETRY)) {
|
|
//
|
|
// Accept the CO if the file still needs to be renamed.
|
|
// If a dup CO got to it first we would still match but if the
|
|
// rename is done then don't do it again.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_RENAME)) {
|
|
DPRINT(4, "++ Remote ChangeOrder accepted based on retry rename-only\n");
|
|
}
|
|
return COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_RENAME);
|
|
}
|
|
|
|
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY)) {
|
|
//
|
|
// Accept the CO if the file still needs to be deleted.
|
|
// If a dup CO got to it first we would still match but if the
|
|
// delete is done then don't do it again.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE)) {
|
|
DPRINT(4, "++ Remote ChangeOrder accepted based on retry delete-only\n");
|
|
}
|
|
return COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
}
|
|
|
|
|
|
if (IDTableRec->OriginatorVSN == CoCmd->FrsVsn) {
|
|
//
|
|
// Duplicate retries are okay if the retry is a Dir Enum request.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY)) {
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO)) {
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_ENUM_REQUESTED)) {
|
|
DPRINT(4, "++ Local ChangeOrder accepted based on ENUM REQUESTED\n");
|
|
return TRUE;
|
|
}
|
|
}
|
|
#if 0
|
|
//
|
|
// This code will also do a retry on a CO in the INSTALL_RETRY state.
|
|
// This is wrong since if the reconcile state matches the install has
|
|
// already been done and the staging file may have been deleted. The
|
|
// Cases for rename retry and delete retry are handled above.
|
|
//
|
|
// Butttt...if the disk fills we also go into install retry but if the
|
|
// CO is not accepted here it is rejected for sameness. Then it doesn't
|
|
// get installed. ??? Why does the IDTable get updated on install retry?
|
|
//
|
|
// The above comment may be moot at this point (3/2000) but we need
|
|
// to do some more disk full related tests to be sure we aren't dropping
|
|
// any files. -- Davidor
|
|
//
|
|
else {
|
|
if (CO_STATE_IS_INSTALL_RETRY(ChangeOrder)) {
|
|
DPRINT(4, "++ Remote ChangeOrder accepted based on retry rename-only\n");
|
|
return TRUE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
DPRINT(4, "++ ChangeOrder rejected for sameness\n");
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
//
|
|
// In highly connected environments lots of identical name morph
|
|
// conflicts could occur, generating a flurry of undampened COs. Since
|
|
// it doesn't really matter which originator produced a given morph
|
|
// generated CO we use CO_FLAG_SKIP_ORIG_REC_CHK to tell downstream
|
|
// partners to skip this part of the reconcile check. When the
|
|
// same conflict is detected on two members they will produce change
|
|
// orders with the same event time, version number and size. When both
|
|
// COs arrive at another member the second will be rejected for sameness
|
|
// if we skip the originator guid check.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_SKIP_ORIG_REC_CHK)) {
|
|
DPRINT(4, "++ ChangeOrder rejected for sameness -- SKIP_ORIG_REC_CHK\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
DPRINT2(4, "++ ChangeOrder %s based on guid (RStatus = %d)\n",
|
|
(RStatus >= 0) ? "accepted" : "rejected", RStatus);
|
|
return (RStatus >= 0);
|
|
}
|
|
|
|
|
|
BOOL
|
|
ChgOrdGenMorphCo(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX TmpIDTableCtxNC,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
If there is a name morph conflict we have the following cases:
|
|
|
|
Incoming CO IDTable Entry with name conflict
|
|
File File Send out del CO for IDTable Entry to free name
|
|
File Dir Dir wins the name convert CO to delete CO. (1)
|
|
Dir File Dir wins the name build del CO for IDTable (2)
|
|
Dir Dir send out Tree del CO for IDTable Entry to free name. (3)
|
|
|
|
When a Local CO is built from an IDTable record the file version
|
|
number is set to the value in the IDTable record plus one. Since
|
|
this is marked as a MorphGenCo this version number will not be
|
|
revised later if this CO ends up going through retry.
|
|
|
|
If a Local Co is built from the incoming change order (ChgOrdConvertCo)
|
|
the file version number is set to the value of the incoming CO + 1.
|
|
This ensures the newly generated CO will be accepted even if there
|
|
already exists an IDTable entry for the incoming CO. The latter
|
|
could happen if the incoming CO is a rename or if it is a prior
|
|
create Co that has been stuck in the IBCO_INSTALL_RETRY or
|
|
IBCO_INSTALL_REN_RETRY states. In addition if the incoming CO is
|
|
a create CO that is losing its bid for the name then the delete Co
|
|
we create will go out first, creating a tombstone, and we want the
|
|
the original incoming CO to follow and be rejected when it matches
|
|
up against the tombstone.
|
|
|
|
Notes:
|
|
|
|
(1) The incoming CO must be sent out as a delete CO because other
|
|
replica set members may not experience the name conflict if a rename
|
|
on the Dir occurred before this CO arrived. So to ensure that the
|
|
file associated with the incoming CO is gone everywhere we send out
|
|
a delete Co.
|
|
|
|
(2) In the case of a name morph conflict where the incoming CO is a
|
|
directory and the current owner of the name is a file, the file
|
|
will lose. Otherwise we will have to abort all subsequent child creates.
|
|
Send out a Delete Co on the IDtable record for the same reason as in (1).
|
|
|
|
(3) re: tree del co.
|
|
This can be a problem. What if a new replica set member that has not
|
|
yet synced up gets a local create dir that conflicts with an existing
|
|
dir? We send out the create dir which then causes that entire
|
|
subtree to get deleted everywhere. This is not good. A better
|
|
solution in this case is to just morph the dir name, giving
|
|
priority to the name to the OLDER entry.
|
|
|
|
Notation:
|
|
|
|
The following 3 letter notation is used to distinguish the cases:
|
|
|
|
The first D or F refers to the incoming CO and says if it's a dir or a file.
|
|
The second D or F refers to the current entry in the IDTable with the same
|
|
name (hence the name morph conflict) and if it's a dir or a file.
|
|
The L appears as a suffix after the first (or second) D or F, indicating
|
|
that either the incoming CO lost the name morph conflict or the IDTable
|
|
entry lost the name morph conflict, respectively. So there are 6 cases:
|
|
|
|
|
|
Incoming CO is a IDTable entry is for a Outcome
|
|
FFL - file, file, IDT entry loses.
|
|
FLF - file, file, Incoming CO loses.
|
|
|
|
FLD - file, dir, Incoming CO loses.
|
|
DFL - dir, file, IDT entry loses.
|
|
|
|
DDL - dir, dir, IDT entry loses.
|
|
DLD - dir, dir, Incoming CO loses.
|
|
|
|
|
|
There are two change orders produced as the result of any name morph
|
|
conflict. One is the modified incoming CO and the other is refered to
|
|
as the MorphGenCo. The MorphGenCo is always a local CO that gets proped
|
|
to all other replica set members so they know the outcome of the morph
|
|
conflict decision. The MorphGenCo may be fabricated from either the
|
|
incoming CO or from the IDTable entry with the same name. If the loser
|
|
of a name morph conflict is a file then the MorphGenCo will be a delete
|
|
change order. On the other hand if the loser is a directory then the
|
|
MorphGenCo will be a rename change order to produce a nonconflicting
|
|
directory name.
|
|
|
|
In all cases above two change orders are pushed back onto the front of the
|
|
process queue and processing is started over again. The first CO that gets
|
|
pushed back onto the process queue is termed the "follower" since it will
|
|
issue second. The second CO that gets pushed onto the process queue is termed
|
|
the "leader" since it will issue first when processing resumes.
|
|
|
|
In all cases above, except DLD, the Leader is the MorphGenCo and its job
|
|
is to remove the name conflict. In the DLD case the incoming CO loses the
|
|
name so it is given a new name and will be leader. The MorphGenCo is
|
|
the follower in this case and is a rename CO that gets proped out to tell
|
|
all downstream partners about the name change.
|
|
|
|
Other details:
|
|
|
|
MorphGenCos never go into the inbound log. If the incoming CO fails to
|
|
complete and must go thru retry then the MorphGenCo is aborted if it is
|
|
the follower (DLD case). If the MorphGenCo is the leader and it fails
|
|
to complete then it is aborted and when the follower reissues it will
|
|
experience a recurrance of the name morph conflict implying that the leader
|
|
failed so it is sent thru retry and will be resubmitted later. The leader
|
|
might fail because of a sharing violation on the existing file or the
|
|
journal connection could be shutting down.
|
|
|
|
The reason that the MorphGenCos don't get put into the Inbound log is that
|
|
if we were to crash or shutdown between before completing both change
|
|
orders they could get restarted out of order. The MorphGenCo is a local
|
|
Co and the Incoming Change Order could be a remote CO. So at restart
|
|
the COs are inserted into the process queue as each inbound connection
|
|
is re-established.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
TmpIDTableCtxNC -- The table context to use for IDT table access for the
|
|
entry with the name conflict.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica set context.
|
|
|
|
Return Value:
|
|
|
|
True if morph change orders were generated OK.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdGenMorphCo:"
|
|
|
|
#define MORPH_FILE_FILE 0
|
|
#define MORPH_FILE_DIR 1
|
|
#define MORPH_DIR_FILE 2
|
|
#define MORPH_DIR_DIR 3
|
|
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
|
|
PIDTABLE_RECORD NameConflictIDTableRec;
|
|
|
|
ULONG MorphCase;
|
|
BOOL MorphNameIDTable, MorphOK, MorphGenLeader;
|
|
|
|
|
|
//
|
|
// If we have already been here once and marked as a MorphGenLeader
|
|
// then this is a create DIR and we go straight to the DIR/Dir case.
|
|
// NOTE: The NameConflictIDTableRec is not valid in this case since
|
|
// the name had already been morphed on a prior visit so use the
|
|
// existing name to refabricate the MorphGenFollower rename co.
|
|
//
|
|
MorphGenLeader = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER);
|
|
|
|
NameConflictIDTableRec = (PIDTABLE_RECORD) TmpIDTableCtxNC->pDataRecord;
|
|
|
|
|
|
MorphCase = 0;
|
|
if (CoIsDirectory(ChangeOrder)) {
|
|
MorphCase += 2;
|
|
}
|
|
if (MorphGenLeader ||
|
|
BooleanFlagOn(NameConflictIDTableRec->FileAttributes,
|
|
FILE_ATTRIBUTE_DIRECTORY)) {
|
|
MorphCase += 1;
|
|
}
|
|
|
|
MorphOK = FALSE;
|
|
|
|
switch (MorphCase) {
|
|
|
|
case MORPH_FILE_FILE:
|
|
//
|
|
// Incoming CO: File IDTable Entry: File
|
|
//
|
|
// Reconcile decides who wins the name.
|
|
// If incoming Co loses then generate a delete CO with us
|
|
// as the originator. (get new VSN).
|
|
// If IDtable loses the name then push incoming CO back onto
|
|
// the process queue and generate a delete CO for the IDTable
|
|
// entry to free the name.
|
|
//
|
|
if (ChgOrdReconcile(Replica, ChangeOrder, NameConflictIDTableRec)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - FFL, Name Morph, Del IDT");
|
|
|
|
//
|
|
// Push incoming CO first with same version number.
|
|
// Push local delete CO for IDT file with vers num = IDT+1
|
|
//
|
|
MorphOK = ChgOrdMakeDeleteCo(ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
NameConflictIDTableRec);
|
|
} else {
|
|
//
|
|
// Build a delete CO unless this is a duplicate CO.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co rejected - FLF, Name Morph, Del Inc CO");
|
|
|
|
//
|
|
// Push incoming CO first with same version number.
|
|
// Push local delete CO for incoming CO with
|
|
// vers num = incoming CO + 1
|
|
//
|
|
// Ensure that incoming Co gets rejected later. If this was a
|
|
// Leaf node member (i.e. no outbound partners) we would always
|
|
// accept remote Cos and get into a Morph Gen loop. See comments
|
|
// in ChgOrdReconcile().
|
|
// Set this flag here since after the call below the CO is back
|
|
// on the queue and we can't touch it.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE);
|
|
|
|
MorphOK = ChgOrdConvertCo(ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
CO_LOCATION_DELETE);
|
|
if (!MorphOK) {
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MORPH_FILE_DIR:
|
|
//
|
|
// Incoming CO: File IDTable Entry: Dir
|
|
//
|
|
// Name stays with the Dir in IDTable.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co rejected - FLD, Name Morph, Del Inc CO");
|
|
//
|
|
// Push incoming CO first with same version number.
|
|
// Push local delete CO for incoming CO with
|
|
// vers num = incoming CO + 1
|
|
//
|
|
// Ensure that incoming Co gets rejected later. If this was a
|
|
// Leaf node member (i.e. no outbound partners) we would always
|
|
// accept remote Cos and get into a Morph Gen loop. See comments
|
|
// in ChgOrdReconcile().
|
|
// Set this flag here since after the call below the CO is back
|
|
// on the queue and we can't touch it.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE);
|
|
|
|
MorphOK = ChgOrdConvertCo(ChangeOrder, Replica, ThreadCtx, CO_LOCATION_DELETE);
|
|
if (!MorphOK) {
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_REJECT_AT_RECONCILE);
|
|
}
|
|
break;
|
|
|
|
case MORPH_DIR_FILE:
|
|
//
|
|
// Incoming CO: Dir IDTable Entry: File
|
|
//
|
|
// Name goes with incoming Dir CO.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - DFL, Name Morph, Del IDT");
|
|
|
|
//
|
|
// Push incoming CO first with same version number.
|
|
// Push local delete CO for IDT file with vers num = IDT+1
|
|
//
|
|
MorphOK = ChgOrdMakeDeleteCo(ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
NameConflictIDTableRec);
|
|
break;
|
|
|
|
case MORPH_DIR_DIR:
|
|
//
|
|
// Incoming CO: Dir IDTable Entry: Dir
|
|
//
|
|
// Priority to the name goes to which ever is older.
|
|
// The other gets a new name.
|
|
// If incoming CO loses then just morph its name and send
|
|
// out a rename CO.
|
|
// If IDTable record loses then push this CO back onto the queue
|
|
// and generate a rename Co.
|
|
//
|
|
if (MorphGenLeader) {
|
|
// Use name as given in CO. See comment above.
|
|
MorphNameIDTable = FALSE;
|
|
//
|
|
// THis better be a dir CO and we should not be here if the
|
|
// MorphGenFollower has already been fabricated.
|
|
//
|
|
FRS_ASSERT(CoIsDirectory(ChangeOrder));
|
|
FRS_ASSERT(!COE_FLAG_ON(ChangeOrder, COE_FLAG_MG_FOLLOWER_MADE));
|
|
DPRINT(4, "++ MorphGenLeader - IDTable entry loses name\n");
|
|
} else
|
|
if (CoCmd->EventTime.QuadPart > NameConflictIDTableRec->EventTime) {
|
|
|
|
// IDTable wins the name. Morph the change order.
|
|
MorphNameIDTable = FALSE;
|
|
DPRINT2(4, "++ IDTable entry wins name - CoEvt: %08x %08x, IdEvt: %08x %08x\n",
|
|
PRINTQUAD(CoCmd->EventTime.QuadPart),
|
|
PRINTQUAD(NameConflictIDTableRec->EventTime));
|
|
} else
|
|
if (CoCmd->EventTime.QuadPart < NameConflictIDTableRec->EventTime) {
|
|
|
|
// Incoming CO wins the name, Morph the IDTable entry.
|
|
MorphNameIDTable = TRUE;
|
|
DPRINT2(4, "++ IDTable entry loses name - CoEvt: %08x %08x, IdEvt: %08x %08x\n",
|
|
PRINTQUAD(CoCmd->EventTime.QuadPart),
|
|
PRINTQUAD(NameConflictIDTableRec->EventTime));
|
|
} else {
|
|
|
|
LONG RStatus1;
|
|
|
|
DPRINT2(4, "++ IDTable Evt matches Co Evt - CoEvt: %08x %08x, IdEvt: %08x %08x\n",
|
|
PRINTQUAD(CoCmd->EventTime.QuadPart),
|
|
PRINTQUAD(NameConflictIDTableRec->EventTime));
|
|
|
|
RStatus1 = FrsGuidCompare(&CoCmd->OriginatorGuid,
|
|
&NameConflictIDTableRec->OriginatorGuid);
|
|
MorphNameIDTable = (RStatus1 >= 0);
|
|
DPRINT1(4, "++ Orig Guid Compare - IDTable entry %s name.\n",
|
|
(MorphNameIDTable ? "Loses" : "Wins"));
|
|
|
|
if (RStatus1 == 0) {
|
|
//
|
|
// Looks like a duplicate. Same event time same orig guid
|
|
//
|
|
DPRINT(0, "++ Warning: DIR_DIR Morph Conflict where Orig Guid in CO equals IDTrecord\n");
|
|
DBS_DISPLAY_RECORD_SEV(0, TmpIDTableCtxNC, TRUE);
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"DIR_DIR Morph Conflict where Orig Guid in CO equals IDTrecord");
|
|
|
|
//
|
|
// add the following as part of the return path if we can ever
|
|
// hit this case legitimately.
|
|
//
|
|
//CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - DupCo");
|
|
//ChgOrdReject(ChangeOrder, Replica);
|
|
//goto CO_CLEANUP;
|
|
}
|
|
}
|
|
|
|
if (MorphNameIDTable) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - DDL, Name Morph IDT");
|
|
//
|
|
// Push incoming CO first with same version number.
|
|
// Push local rename CO for IDT Dir with vers num = IDT+1
|
|
//
|
|
MorphOK = ChgOrdMakeRenameCo(ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
NameConflictIDTableRec);
|
|
} else {
|
|
//
|
|
// Incoming CO loses the name.
|
|
//
|
|
if (MorphGenLeader) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - DLD, Follower Regen");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Accepted - DLD, Dir-Dir Name Morph Inc CO");
|
|
}
|
|
//
|
|
// Push local rename CO for incoming CO with
|
|
// vers num = incoming CO + 1
|
|
// Push incoming CO with same version number and morphed name.
|
|
//
|
|
MorphOK = ChgOrdConvertCo(ChangeOrder,
|
|
Replica,
|
|
ThreadCtx,
|
|
CO_LOCATION_NO_CMD);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
FRS_ASSERT(!"Invalid morph case");
|
|
} // end switch
|
|
|
|
return MorphOK;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
ChgOrdApplyMorphCo(
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Apply the name change requested by the MorphGenCo to resolve the name
|
|
conflict.
|
|
|
|
|
|
Arguments:
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica set context.
|
|
|
|
Return Value:
|
|
|
|
True if name change required to resolve the name conflict succeeded.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdApplyMorphCo:"
|
|
|
|
|
|
|
|
ULONG WStatus;
|
|
ULONG LocationCmd;
|
|
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
if (LocationCmd == CO_LOCATION_DELETE) {
|
|
|
|
//
|
|
// Delete the underlying file to free the name.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Del Name Morph IDT loser");
|
|
|
|
WStatus = StuDelete(ChangeOrder);
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
return TRUE;
|
|
}
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "Del IDT loser FAILED. WStatus=", WStatus);
|
|
//
|
|
// Let the MorphGenCo go if it is not the leader.
|
|
// Otherwise reject it so the follower is forced thru retry.
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
} else
|
|
if (BooleanFlagOn(ChangeOrder->Cmd.ContentCmd, USN_REASON_RENAME_NEW_NAME)) {
|
|
|
|
//
|
|
// Rename the underlying dir to free the name.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Ren Name Morph IDT loser");
|
|
|
|
WStatus = StuInstallRename(ChangeOrder, FALSE, TRUE);
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
return TRUE;
|
|
}
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "Ren IDT loser FAILED. WStatus=", WStatus);
|
|
|
|
//
|
|
// Let the MorphGenCo go if it is not the leader.
|
|
// Otherwise reject it so the follower is forced thru retry.
|
|
// The install rename can fail if a dir create causes a
|
|
// parent reanimate and while the dir create is in process a second dir
|
|
// create arrives and wins the name. THis generates a rename MorphGenCo
|
|
// for the in-process dir create. Meanwhile if a local CO deletes the
|
|
// reanimated parent (no fooling) before the first dir create is renamed
|
|
// into place then the first dir create will go into rename retry state.
|
|
// That unblocks the process queue so the rename MorphGenco issues and
|
|
// it fails too because the parent dir is not present. It later tries
|
|
// to go thru retry and fails because local MorphGenCOs can't have
|
|
// rename retry failures.
|
|
// Another scenario is the DLD case (see ChgOrdGenMorphCo()) where
|
|
// the incoming dir create loses the name morph conflict. In this case
|
|
// the MorphGenCo is a follower that props out a rename CO. However if
|
|
// the stage file fetch for the leader Create CO fails (file was deleted
|
|
// upstream) then there is no file to rename. But since we can't tell
|
|
// if the file not found status is on the parent dir or on the actual
|
|
// target we let the FOLLOWER go. It will get aborted later in
|
|
// StageCsCreateStage() when we fail to generate the stage file.
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Invalid MorphGenCo");
|
|
}
|
|
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdReanimate(
|
|
IN PTABLE_CTX IDTableCtx,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TmpIDTableCtx
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Check if we have to reanimate the parent directory for this change order.
|
|
|
|
Arguments:
|
|
IDTableCtx -- The IDTable context for the current change order.
|
|
ChangeOrder -- The change order.
|
|
Replica -- The Replica struct.
|
|
ThreadCtx -- ptr to the thread context for DB
|
|
TmpIDTableCtx -- The temporary ID Table context to use for reading the IDTable
|
|
to reanimate the CO.
|
|
|
|
Return Value:
|
|
|
|
REANIMATE_SUCCESS : Reanimation was required and succeeded.
|
|
REAMIMATE_RETRY_LATER : Parent reanimation failed, send base CO thru retry.
|
|
REANIMATE_CO_REJECTED : Can't reanimation for this CO so reject the CO.
|
|
REANIMATE_FAILED : Internal error.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReanimate:"
|
|
|
|
JET_ERR jerr, jerr1;
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
PCHANGE_ORDER_ENTRY DeadCoe;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
ULONG FStatus, GStatus, WStatus;
|
|
BOOL LocalCo, ReAnimate, DemandRefreshCo;
|
|
|
|
PIDTABLE_RECORD IDTableRec = (PIDTABLE_RECORD) (IDTableCtx->pDataRecord);
|
|
PIDTABLE_RECORD IDTableRecParent = NULL;
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
DemandRefreshCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH);
|
|
ReAnimate = IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED);
|
|
|
|
//
|
|
// If this is a local file op that was updating a file just milliseconds
|
|
// before a previously issued remote co deleted the file then don't
|
|
// try to reanimate since there is no data upstream anyway. There are
|
|
// 3 cases here:
|
|
// 1. If the local user does the file update after the remote co delete
|
|
// (via file reopen and write back) then a new file is created with
|
|
// a new FID and object ID. New data is replicated, User is happy.
|
|
// 2. If the user did the update just before we started processing
|
|
// the remote CO then reconcile will probably reject the remote co delete.
|
|
// 3. If the user does the update between the time where we issue
|
|
// the remote co delete and we actually delete the file then the user
|
|
// loses. This is no different than if the user lost the reconcile
|
|
// decision in step 2. Since we reject the local CO the remote CO
|
|
// delete continues on and the file is deleted everywhere.
|
|
//
|
|
// Note: There is one case where reanimation by a Local Co does make sense.
|
|
// If an app has a file open with sharing mode deny delete and we get a
|
|
// remote co to delete the file, the remote co will get processed and the
|
|
// IDTable entry is marked deleted (although our attempt to actually delete
|
|
// the file will fail with a sharing violation). Later the app updates the
|
|
// file and then closes it. We see the update USN record and need to
|
|
// reanimate since it will have a more recent event time.
|
|
//
|
|
if (ReAnimate &&
|
|
LocalCo &&
|
|
!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
//
|
|
// A local Co can't reanimate a deleted file.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - No Local Co revival");
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, FALSE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
return REANIMATE_CO_REJECTED;
|
|
}
|
|
//
|
|
// Don't bother to reanimate the parent for a delete. This can happen
|
|
// if the file is stuck in the preinstall or staging areas and not
|
|
// really in the deleted directory. Let the delete continue w/o
|
|
// reanimation.
|
|
//
|
|
// Make this check before the local co check to avoid morphgenco
|
|
// asserts when this CoCmd is a del of the IDT loser.
|
|
//
|
|
if (DOES_CO_DELETE_FILE_NAME(CoCmd)) {
|
|
return REANIMATE_NO_NEED;
|
|
}
|
|
|
|
//
|
|
// Read the IDTable entry for the parent
|
|
//
|
|
jerr = DbsReadRecord(ThreadCtx, &CoCmd->NewParentGuid, GuidIndexx, TmpIDTableCtx);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT_JS(0,"++ ERROR - DbsReadRecord failed;", jerr);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
return REANIMATE_CO_REJECTED;
|
|
}
|
|
|
|
//
|
|
// Close the table, reset the TableCtx Tid and Sesid.
|
|
// DbsCloseTable is a Macro, writes 1st arg.
|
|
//
|
|
// perf: With multiple replica sets we may need a different
|
|
// IDTable for the next CO we process. Could just check if
|
|
// there is a replica change and only close then. Or put the
|
|
// tmp table ctx in the replica struct so we can just switch.
|
|
//
|
|
DbsCloseTable(jerr1, ThreadCtx->JSesid, TmpIDTableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable failed:", jerr1);
|
|
|
|
IDTableRecParent = (PIDTABLE_RECORD) (TmpIDTableCtx->pDataRecord);
|
|
//
|
|
// Parent should be marked deleted or why are we here?
|
|
//
|
|
FRS_ASSERT(IsIdRecFlagSet(IDTableRecParent, IDREC_FLAGS_DELETED));
|
|
|
|
if (LocalCo && !IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED) &&
|
|
!IsIdRecFlagSet(IDTableRecParent, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
//
|
|
// Don't know how to revive a dead parent of a local CO. How did it
|
|
// get to be dead in the first place? Here is how: If a local file op
|
|
// is updating a file just before a previously issued remote CO
|
|
// executes to delete the file followed by a second remote co to
|
|
// delete the parent then we end up here with an update Local Co with
|
|
// a deleted parent. So, don't assert just reject the local co and
|
|
// let the remote Co win since it already won upstream so there is no
|
|
// data around to reanimate the file with.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Cant revive dead par of lcl co");
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, FALSE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
return REANIMATE_CO_REJECTED;
|
|
}
|
|
|
|
// Need to revive the parent. Push this CO back on the front of
|
|
// the CO queue and then make a reanimation CO for the parent.
|
|
// Push this CO onto the front of the queue and then turn the
|
|
// loop. This handles nested reanimations.
|
|
//
|
|
|
|
//
|
|
// If this CO has already requested its parent to be reanimated then we
|
|
// shouldn't be here because we should have blocked on the issue check for
|
|
// its parent in the ActiveInboundChangeOrderTable. When the issue block
|
|
// was released we performed the GUIDFID translation again and this time
|
|
// the IDT Delete Status should show the parent is not deleted.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_RISE_REQ)) {
|
|
//
|
|
// The parent reanimation must have failed. Reject this CO.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION)) {
|
|
FRS_ASSERT(DemandRefreshCo);
|
|
|
|
//
|
|
// This CO is for a reanimated parent v.s. the base
|
|
// CO that started it all. There is no inbound log entry
|
|
// for it, no inbound partner to ack, etc.
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_INSTALL_INCOMPLETE);
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_ABORT_CO);
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_STAGE_FILE);
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Nested Parent Rise Failed");
|
|
return REANIMATE_CO_REJECTED;
|
|
|
|
} else {
|
|
//
|
|
// This is the base CO that triggered the reanimation.
|
|
// The parent still isn't here so send it through retry.
|
|
//
|
|
// This can happen if the parent and child were deleted on the
|
|
// inbound partner before we could fetch the child. And then we
|
|
// did a retry on the child CO before we see the delete CO for
|
|
// the child from the inbound partner. So the parent reanimation
|
|
// fails and the child has no parent FID. Eventually we will get
|
|
// a delete CO for the child and we will create a tombstone thus
|
|
// causing this CO to be rejected when it next is retried.
|
|
//
|
|
// We may not get the delete of this target CO if the upstream is
|
|
// waiting for us to ACK some COs. By not sending an ACK we will
|
|
// block any further progress. The downside of sending an ACK before
|
|
// fetching the file is the upstream may have to regenerate the staging
|
|
// file again but if the target file is absent on upstream then it is not
|
|
// going to be able to generate the staging file in any case.
|
|
// Send an ACK here so we keep getting the COs from downstream and
|
|
// the expectation is that eventually we will sought things out.
|
|
// Also this will be inserted in the inbound log (before ChgOrdIssueCleanup)
|
|
// so we will keep retrying it.
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND);
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Parent Rise Failed");
|
|
return REAMIMATE_RETRY_LATER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build a Remote change order entry for the parent from the IDtable
|
|
// Tombstone record. This is a fetch CO to the inbound partner
|
|
// that sent us the base CO to begin with.
|
|
//
|
|
|
|
if (LocalCo && IsIdRecFlagSet(IDTableRecParent, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
DeadCoe = ChgOrdMakeFromIDRecord(IDTableRecParent,
|
|
Replica,
|
|
CO_LOCATION_CREATE,
|
|
CO_FLAG_GROUP_RAISE_DEAD_PARENT | CO_FLAG_LOCALCO,
|
|
&CoCmd->CxtionGuid);
|
|
|
|
SET_CHANGE_ORDER_STATE(DeadCoe, IBCO_STAGING_REQUESTED);
|
|
} else {
|
|
DeadCoe = ChgOrdMakeFromIDRecord(IDTableRecParent,
|
|
Replica,
|
|
CO_LOCATION_CREATE,
|
|
CO_FLAG_GROUP_RAISE_DEAD_PARENT,
|
|
&CoCmd->CxtionGuid);
|
|
|
|
SET_CHANGE_ORDER_STATE(DeadCoe, IBCO_FETCH_REQUESTED);
|
|
}
|
|
|
|
//
|
|
// Update the version number by 1 and send the reanimation CO
|
|
// forward. This will make sure that this CO is accepted
|
|
// on the downstream partners that have the same tombstone.
|
|
//
|
|
|
|
DeadCoe->Cmd.FileVersionNumber = IDTableRecParent->VersionNumber + 1;
|
|
|
|
SET_COE_FLAG(DeadCoe, COE_FLAG_GROUP_RAISE_DEAD_PARENT);
|
|
|
|
//
|
|
// Mark this CO as reanimated if in fact it had been deleted
|
|
// and now a new update has come in, overriding the deletion.
|
|
// The other possibility is that it could be a NewFile create
|
|
// under a deleted parent. Not a reanimation in this case.
|
|
//
|
|
if (!COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION) && ReAnimate) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_REANIMATION);
|
|
}
|
|
|
|
//
|
|
// Do the change order cleanup but don't free the CO.
|
|
// There is a VV slot to clean up if this is the CO that started
|
|
// parent reanimation.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
}
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
//
|
|
// Remember that this CO has already requested its parent to rise.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_PARENT_RISE_REQ);
|
|
|
|
// Dont we still have this ref from when it was last on
|
|
// the process queue?
|
|
//INCREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder);
|
|
|
|
|
|
//
|
|
// Inherit the recovery flag so this CO will block if the
|
|
// cxtion is not yet joined.
|
|
//
|
|
if (RecoveryCo(ChangeOrder)) {
|
|
SET_COE_FLAG(DeadCoe, COE_FLAG_RECOVERY_CO);
|
|
}
|
|
|
|
//
|
|
// Inherit the Connection and the Join Guid from the child.
|
|
// If the Cxtion fails, all these COs should fail.
|
|
//
|
|
DeadCoe->Cxtion = ChangeOrder->Cxtion;
|
|
FRS_ASSERT(ChangeOrder->Cxtion);
|
|
|
|
LOCK_CXTION_TABLE(Replica);
|
|
INCREMENT_CXTION_CHANGE_ORDER_COUNT(Replica, DeadCoe->Cxtion);
|
|
DeadCoe->JoinGuid = ChangeOrder->JoinGuid;
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD | IPQ_TAKE_COREF |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return REANIMATE_FAILED;
|
|
}
|
|
|
|
//
|
|
// If we just reinserted a base local co on to the process list then increment the
|
|
// LocalCoQueueCount for this replica.
|
|
//
|
|
if (LocalCo &&
|
|
!CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY |
|
|
CO_FLAG_CONTROL |
|
|
CO_FLAG_MOVEIN_GEN |
|
|
CO_FLAG_MORPH_GEN) &&
|
|
!RecoveryCo(ChangeOrder) &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION)) {
|
|
|
|
INC_LOCAL_CO_QUEUE_COUNT(Replica);
|
|
}
|
|
//
|
|
// Push the reanimate CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, DeadCoe, "Co Push Parent to QHead");
|
|
|
|
WStatus = ChgOrdInsertProcQ(Replica, DeadCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return REANIMATE_FAILED;
|
|
}
|
|
|
|
//
|
|
// Now go give the parent CO we just pushed onto the QHead a spin.
|
|
// It could issue or it might generate another change order to
|
|
// reanimate its parent. The recursion will stop when we hit
|
|
// a live parent or the root of the replica tree.
|
|
//
|
|
return REANIMATE_SUCCESS;
|
|
}
|
|
|
|
|
|
BOOL
|
|
ChgOrdMakeDeleteCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PIDTABLE_RECORD IDTableRec
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Given the IDTable record build a delete change order.
|
|
Make us the originator.
|
|
Push the supplied change order onto the head of the process queue after
|
|
doing some cleanup and then push the delete CO.
|
|
|
|
Arguments:
|
|
ChangeOrder -- The change order.
|
|
Replica -- The Replica struct.
|
|
ThreadCtx -- ptr to the thread context for DB
|
|
IDTableRec -- The ID Table record to build the delte change order from.
|
|
|
|
Return Value:
|
|
|
|
TRUE: Success
|
|
FALSE: Failed to insert CO on queue. Probably queue was run down.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMakeDeleteCo:"
|
|
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PCHANGE_ORDER_ENTRY DelCoe;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
ULONG FStatus, WStatus;
|
|
BOOL LocalCo, DemandRefreshCo, MorphGenFollower;
|
|
|
|
|
|
FRS_ASSERT(!(IDTableRec->FileAttributes & FILE_ATTRIBUTE_DIRECTORY));
|
|
|
|
MorphGenFollower = COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
DemandRefreshCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH);
|
|
|
|
//
|
|
// There is a VV slot to clean up if this is the CO that caused the
|
|
// name morph conflict.
|
|
//
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
}
|
|
|
|
//
|
|
// If this CO has been here once before then this is a recurrance of a
|
|
// name morph conflict so the MorphGenCo Leader must have aborted.
|
|
// Send the follower thru retry so it can try again later.
|
|
//
|
|
if (MorphGenFollower) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Recurrance of Morph Confl, RETRY");
|
|
return FALSE;
|
|
}
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
|
|
|
|
//
|
|
// Build a delete change order entry for the IDtable record.
|
|
// This is a local CO that originates from this member.
|
|
//
|
|
DelCoe = ChgOrdMakeFromIDRecord(IDTableRec,
|
|
Replica,
|
|
CO_LOCATION_DELETE,
|
|
CO_FLAG_LOCATION_CMD,
|
|
NULL);
|
|
|
|
//
|
|
// Generate a new Volume Sequnce Number for the change order.
|
|
// But since it gets put on the front of the CO process queue it
|
|
// is probably out of order so set the flag to avoid screwing up
|
|
// dampening.
|
|
//
|
|
NEW_VSN(pVme, &DelCoe->Cmd.FrsVsn);
|
|
DelCoe->Cmd.OriginatorGuid = ConfigRecord->ReplicaVersionGuid;
|
|
|
|
//
|
|
// Use the event time from the winning CO as long as it is greater than
|
|
// the event time in the IDTable record. If we use current time then
|
|
// when the name morph conflict is detected at other nodes they would
|
|
// generate a Del Co with yet a different time and cause unnecessary
|
|
// replication of Delete Cos.
|
|
//
|
|
if (ChangeOrder->Cmd.EventTime.QuadPart > IDTableRec->EventTime) {
|
|
//
|
|
// Event time winning CO.
|
|
//
|
|
DelCoe->Cmd.EventTime.QuadPart = ChangeOrder->Cmd.EventTime.QuadPart;
|
|
} else {
|
|
//
|
|
// Event time from IDTable record biased by Reconcile EventTimeWindow+1
|
|
// The above had probs: Adding in RecEventTimeWindow may be causing desired
|
|
// reanimations to be rejected since the tombstone has a
|
|
// much larger event time so the version number check doesn't matter
|
|
// just make it +1 for now.
|
|
//
|
|
DelCoe->Cmd.EventTime.QuadPart = IDTableRec->EventTime +
|
|
/* RecEventTimeWindow */ + 1;
|
|
}
|
|
|
|
//
|
|
// Bump Version number to ensure the CO is accepted.
|
|
//
|
|
DelCoe->Cmd.FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
|
|
//
|
|
// Note: We wouldn't need to mark this CO as out of order if we
|
|
// resequenced all change order VSNs (for new COs only) as they
|
|
// were fetched off the process queue.
|
|
//
|
|
SET_CO_FLAG(DelCoe, CO_FLAG_LOCALCO |
|
|
CO_FLAG_MORPH_GEN |
|
|
CO_FLAG_OUT_OF_ORDER);
|
|
|
|
//
|
|
// Set the Jrnl Cxtion Guid and Cxtion ptr for this Local CO.
|
|
//
|
|
INIT_LOCALCO_CXTION_AND_COUNT(Replica, DelCoe);
|
|
|
|
//
|
|
// Tell downstream partners to skip the originator check on this Morph
|
|
// generated CO. See ChgOrdReconcile() for details.
|
|
//
|
|
SET_CO_FLAG(DelCoe, CO_FLAG_SKIP_ORIG_REC_CHK);
|
|
|
|
//
|
|
// Do the change order cleanup but don't free the CO.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
//
|
|
// Mark the base CO as a MorphGenFollower so it gets sent thru retry
|
|
// if the leader gets aborted.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push MORPH_GEN_FOLLOWER - Winner to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD | IPQ_TAKE_COREF |
|
|
IPQ_MG_LOOP_CHK | IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Push the IDTable Delete Co onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, DelCoe, "Co Push MORPH_GEN_LEADER - IDT loser to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, DelCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Now go give the delete CO we just pushed onto the QHead a spin.
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
ChgOrdMakeRenameCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PIDTABLE_RECORD IDTableRec
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Given the IDTable record build a rename change order.
|
|
Make us the originator.
|
|
Push the supplied change order onto the head of the process queue after
|
|
doing some cleanup and then push the rename CO.
|
|
|
|
Arguments:
|
|
ChangeOrder -- The change order.
|
|
Replica -- The Replica struct.
|
|
ThreadCtx -- ptr to the thread context for DB
|
|
IDTableRec -- The ID Table record to build the delte change order from.
|
|
|
|
Return Value:
|
|
|
|
TRUE: Success
|
|
FALSE: Failed to insert CO on queue. Probably queue was run down.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMakeRenameCo:"
|
|
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PCHANGE_ORDER_ENTRY RenameCoe;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
ULONG FStatus, WStatus;
|
|
BOOL LocalCo, DemandRefreshCo, MorphGenFollower;
|
|
ULONG NameLen;
|
|
|
|
|
|
FRS_ASSERT(IDTableRec->FileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
DemandRefreshCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH);
|
|
MorphGenFollower = COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
|
|
//
|
|
// There is a VV slot to clean up if this is the CO that caused the
|
|
// name morph conflict.
|
|
//
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
}
|
|
|
|
//
|
|
// If this CO has been here once before then this is a recurrance of a
|
|
// name morph conflict so the MorphGenCo Leader must have aborted.
|
|
// Send the follower thru retry so it can try again later.
|
|
//
|
|
if (MorphGenFollower) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Recurrance of Morph Confl, RETRY");
|
|
return FALSE;
|
|
}
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
|
|
|
|
//
|
|
// Build a rename change order entry for the IDtable record.
|
|
// This is a local CO that originates from this member.
|
|
//
|
|
RenameCoe = ChgOrdMakeFromIDRecord(IDTableRec,
|
|
Replica,
|
|
CO_LOCATION_NO_CMD,
|
|
CO_FLAG_CONTENT_CMD,
|
|
NULL);
|
|
|
|
RenameCoe->Cmd.ContentCmd = USN_REASON_RENAME_NEW_NAME;
|
|
|
|
//
|
|
// Suffix "_NTFRS_<tic count in hex>" to the filename.
|
|
//
|
|
DPRINT1(4, "++ %ws will be morphed\n", RenameCoe->Cmd.FileName);
|
|
NameLen = wcslen(RenameCoe->Cmd.FileName);
|
|
if (NameLen + SIZEOF_RENAME_SUFFIX > (MAX_PATH - 1)) {
|
|
NameLen -= ((NameLen + SIZEOF_RENAME_SUFFIX) - (MAX_PATH - 1));
|
|
}
|
|
swprintf(&RenameCoe->Cmd.FileName[NameLen], L"_NTFRS_%08x", GetTickCount());
|
|
RenameCoe->Cmd.FileNameLength = wcslen(RenameCoe->Cmd.FileName) * sizeof(WCHAR);
|
|
RenameCoe->UFileName.Length = RenameCoe->Cmd.FileNameLength;
|
|
|
|
DPRINT1(4, "++ Morphing to %ws\n", RenameCoe->Cmd.FileName);
|
|
|
|
|
|
//
|
|
// Generate a new Volume Sequnce Number for the change order.
|
|
// But since it gets put on the front of the CO process queue it
|
|
// is probably out of order so set the flag to avoid screwing up
|
|
// dampening.
|
|
//
|
|
NEW_VSN(pVme, &RenameCoe->Cmd.FrsVsn);
|
|
RenameCoe->Cmd.OriginatorGuid = ConfigRecord->ReplicaVersionGuid;
|
|
|
|
|
|
//
|
|
// Use the event time from the winning CO as long as it is greater than
|
|
// the event time in the IDTable record. If we use current time then
|
|
// when the name morph conflict is detected at other nodes they would
|
|
// generate a rename Co with yet a different time and cause unnecessary
|
|
// replication of rename Cos.
|
|
//
|
|
if (ChangeOrder->Cmd.EventTime.QuadPart > IDTableRec->EventTime) {
|
|
//
|
|
// Event time winning CO.
|
|
//
|
|
RenameCoe->Cmd.EventTime.QuadPart = ChangeOrder->Cmd.EventTime.QuadPart;
|
|
} else {
|
|
//
|
|
// Event time from IDTable record biased by Reconcile EventTimeWindow+1
|
|
//
|
|
RenameCoe->Cmd.EventTime.QuadPart = IDTableRec->EventTime +
|
|
/* RecEventTimeWindow */ + 1;
|
|
//GetSystemTimeAsFileTime((PFILETIME)&RenameCoe->Cmd.EventTime.QuadPart);
|
|
}
|
|
|
|
//
|
|
// Bump Version number to ensure the CO is accepted.
|
|
//
|
|
RenameCoe->Cmd.FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
|
|
//
|
|
// Note: We wouldn't need to mark this CO as out of order if we
|
|
// resequenced all change order VSNs (for new COs only) as they
|
|
// were fetched off the process queue.
|
|
//
|
|
SET_CO_FLAG(RenameCoe, CO_FLAG_LOCALCO |
|
|
CO_FLAG_MORPH_GEN |
|
|
CO_FLAG_OUT_OF_ORDER);
|
|
|
|
//
|
|
// Set the Jrnl Cxtion Guid and Cxtion ptr for this Local CO.
|
|
//
|
|
INIT_LOCALCO_CXTION_AND_COUNT(Replica, RenameCoe);
|
|
|
|
//
|
|
// Tell downstream partners to skip the originator check on this Morph
|
|
// generated CO. See ChgOrdReconcile() for details.
|
|
//
|
|
SET_CO_FLAG(RenameCoe, CO_FLAG_SKIP_ORIG_REC_CHK);
|
|
|
|
//
|
|
// Do the change order cleanup but don't free the CO.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
//
|
|
// Mark the base CO as a MorphGenFollower so it gets sent thru retry
|
|
// if the leader gets aborted.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push MORPH_GEN_FOLLOWER - Winner to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD | IPQ_TAKE_COREF |
|
|
IPQ_MG_LOOP_CHK | IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Push the IDTable Delete Co onto the head of the queue.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, RenameCoe, "Co Push MORPH_GEN_LEADER - IDT loser to QHead");
|
|
WStatus = ChgOrdInsertProcQ(Replica, RenameCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Now go give the delete CO we just pushed onto the QHead a spin.
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdInjectControlCo(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN ULONG ContentCmd
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Generate a directed control change order for Cxtion.
|
|
|
|
Arguments:
|
|
|
|
Replica - Replica for cxtion
|
|
Cxtion - outbound cxtion
|
|
ContentCmd - Type of control co
|
|
|
|
Thread Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInjectControlCo:"
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
|
|
//
|
|
// Change order entry
|
|
//
|
|
Coe = FrsAllocType(CHANGE_ORDER_ENTRY_TYPE);
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(Coe);
|
|
//
|
|
// Jet context for outlog insertion
|
|
//
|
|
Coe->RtCtx = FrsAllocTypeSize(REPLICA_THREAD_TYPE,
|
|
FLAG_FRSALLOC_NO_ALLOC_TBL_CTX);
|
|
FrsRtlInsertTailList(&Replica->ReplicaCtxListHead,
|
|
&Coe->RtCtx->ReplicaCtxList);
|
|
|
|
//
|
|
// Change order command
|
|
//
|
|
Coc = &Coe->Cmd;
|
|
|
|
//
|
|
// Original and New Replica are the same (not a rename)
|
|
//
|
|
Coe->OriginalReplica = Replica;
|
|
Coe->NewReplica = Replica;
|
|
Coc->OriginalReplicaNum = ReplicaAddrToId(Coe->OriginalReplica);
|
|
Coc->NewReplicaNum = ReplicaAddrToId(Coe->NewReplica);
|
|
|
|
//
|
|
// Assign a change order guid
|
|
//
|
|
FrsUuidCreate(&Coc->ChangeOrderGuid);
|
|
|
|
//
|
|
// Phoney File guid
|
|
//
|
|
FrsUuidCreate(&Coc->FileGuid);
|
|
if (ContentCmd == FCN_CO_ABNORMAL_VVJOIN_TERM) {
|
|
wcscpy(Coe->Cmd.FileName, L"VVJoinTerminateAbnormal"); // for tracing.
|
|
} else if (ContentCmd == FCN_CO_NORMAL_VVJOIN_TERM) {
|
|
wcscpy(Coe->Cmd.FileName, L"VVJoinTerminateNormal"); // for tracing.
|
|
} else if (ContentCmd == FCN_CO_END_OF_JOIN) {
|
|
wcscpy(Coe->Cmd.FileName, L"EndOfJoin"); // for tracing.
|
|
} else if (ContentCmd == FCN_CO_END_OF_OPTIMIZED_VVJOIN) {
|
|
wcscpy(Coe->Cmd.FileName, L"EndOfOptVVJoin"); // for tracing.
|
|
} else {
|
|
wcscpy(Coe->Cmd.FileName, L"UnknownControl"); // for tracing.
|
|
}
|
|
|
|
//
|
|
// Cxtion's guid (identifies the cxtion)
|
|
//
|
|
Coc->CxtionGuid = *Cxtion->Name->Guid;
|
|
|
|
//
|
|
// The location command is either create or delete
|
|
//
|
|
Coc->ContentCmd = ContentCmd;
|
|
|
|
//
|
|
// Control command co
|
|
//
|
|
SET_CO_FLAG(Coe, CO_FLAG_CONTROL);
|
|
|
|
//
|
|
// Directed at one cxtion
|
|
//
|
|
SET_CO_FLAG(Coe, CO_FLAG_DIRECTED_CO);
|
|
//
|
|
// Mascarade as a local change order since the staging file
|
|
// is created from the local version of the file and isn't
|
|
// from a inbound partner.
|
|
//
|
|
SET_CO_FLAG(Coe, CO_FLAG_LOCALCO);
|
|
|
|
//
|
|
// Refresh change orders will not be propagated by our outbound
|
|
// partner to its outbound partners.
|
|
//
|
|
// Note that only the termination change order is set to refresh.
|
|
// While we want the vvjoin change orders to propagate as regular
|
|
// change orders, we do not want the termination co to propagate.
|
|
//
|
|
SET_CO_FLAG(Coe, CO_FLAG_REFRESH);
|
|
|
|
//
|
|
// By "out of order" we mean that the VSN on this change order
|
|
// should not be used to update the version vector because there
|
|
// may be other files or dirs with lower VSNs that will be sent
|
|
// later. We wouldn't want our partner to dampen them.
|
|
//
|
|
SET_CO_FLAG(Coe, CO_FLAG_OUT_OF_ORDER);
|
|
|
|
//
|
|
// Some control Cos aren't valid across joins.
|
|
//
|
|
Coc->EventTime.QuadPart = Cxtion->LastJoinTime;
|
|
|
|
//
|
|
// Just a guess
|
|
//
|
|
SET_CHANGE_ORDER_STATE(Coe, IBCO_OUTBOUND_REQUEST);
|
|
|
|
//
|
|
// The DB server is responsible for updating the outbound log.
|
|
//
|
|
DbsPrepareCmdPkt(NULL, // CmdPkt,
|
|
Replica, // Replica,
|
|
CMD_DBS_INJECT_OUTBOUND_CO, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
Coe, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
TRUE); // Submit
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
ChgOrdConvertCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica,
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN ULONG LocationCmd
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Create a new CO of the specified type using the current CO.
|
|
Make us the originator.
|
|
Push the current CO back onto the queue.
|
|
Push the new change order onto the head of the process
|
|
queue after doing some cleanup.
|
|
The new CO is a local CO while the original CO may be a remote CO that
|
|
needs to ack its inbound partner.
|
|
|
|
Convert is generating a new change order (rename or delete) for an
|
|
incoming change order for a NEW FILE. As such there is no existing file
|
|
whose name needs to be removed, unlike ChgOrdMakeRenameCo() or
|
|
ChgOrdMakeDeleteCo() which produce change orders from an existing IDTable
|
|
entry.
|
|
|
|
Arguments:
|
|
ChangeOrder -- The change order.
|
|
Replica -- The Replica struct.
|
|
ThreadCtx -- ptr to the thread context for DB
|
|
LocationCmd -- Tells us what kind of CO to build.
|
|
Current choices are a rename CO or a delete CO.
|
|
|
|
Return Value:
|
|
|
|
TRUE: Success
|
|
FALSE: Failed to insert CO on queue. Probably queue was run down.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdConvertCo:"
|
|
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PCHANGE_ORDER_ENTRY CvtCoe;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
ULONG FStatus, WStatus, WStatus1;
|
|
BOOL LocalCo, DemandRefreshCo, MorphGenFollower;
|
|
ULONG NameLen;
|
|
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
DemandRefreshCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH);
|
|
MorphGenFollower = COE_FLAG_ON(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
|
|
//
|
|
// There is a VV slot to clean up if this is the CO that caused the
|
|
// name morph conflict.
|
|
//
|
|
if (!LocalCo && !DemandRefreshCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
}
|
|
|
|
//
|
|
// If this CO has been here once before then this is a recurrance of a
|
|
// name morph conflict so the MorphGenCo Leader must have aborted.
|
|
// Send the follower thru retry so it can try again later.
|
|
//
|
|
if (MorphGenFollower) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Recurrance of Morph Confl, RETRY");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
|
|
|
|
//
|
|
// Build a new CO out of this change order entry.
|
|
// This is a local CO that originates from this member.
|
|
//
|
|
// Allocate a change order entry with room for the filename and copy
|
|
// the change order + Filename into it. Init the ref count to one
|
|
// since the CO is going on the process queue.
|
|
//
|
|
CvtCoe = FrsAllocType(CHANGE_ORDER_ENTRY_TYPE);
|
|
CopyMemory(&CvtCoe->Cmd, &ChangeOrder->Cmd, sizeof(CHANGE_ORDER_COMMAND));
|
|
CvtCoe->Cmd.Extension = NULL;
|
|
CvtCoe->UFileName.Length = CvtCoe->Cmd.FileNameLength;
|
|
|
|
//
|
|
// Copy the Change Order Extension if provided.
|
|
//
|
|
if ((ChangeOrder->Cmd.Extension != NULL) &&
|
|
(ChangeOrder->Cmd.Extension->FieldSize > 0)) {
|
|
|
|
CvtCoe->Cmd.Extension = FrsAlloc(ChangeOrder->Cmd.Extension->FieldSize);
|
|
CopyMemory(CvtCoe->Cmd.Extension, ChangeOrder->Cmd.Extension,
|
|
ChangeOrder->Cmd.Extension->FieldSize);
|
|
}
|
|
|
|
//
|
|
// Normally the change order record extension is allocated for local COs
|
|
// when the cocmd is inserted into the inbound log (by the call to
|
|
// DbsAllocRecordStorage()). But MorphGenCos don't get inserted into
|
|
// the inlog so we need to do it here.
|
|
//
|
|
if (ChangeOrder->Cmd.Extension == NULL){
|
|
CvtCoe->Cmd.Extension = FrsAlloc(sizeof(CHANGE_ORDER_RECORD_EXTENSION));
|
|
DbsDataInitCocExtension(CvtCoe->Cmd.Extension);
|
|
DPRINT(4, "Allocating initial Coc Extension for morphco\n");
|
|
}
|
|
|
|
//
|
|
// Assign a change order guid and zero the connection GUID since this
|
|
// is a local CO.
|
|
//
|
|
FrsUuidCreate(&CvtCoe->Cmd.ChangeOrderGuid);
|
|
ZeroMemory(&CvtCoe->Cmd.CxtionGuid, sizeof(GUID));
|
|
|
|
CvtCoe->HashEntryHeader.ReferenceCount = 0;
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(CvtCoe); // for tracking
|
|
|
|
//
|
|
// File's fid and parent fid
|
|
//
|
|
CvtCoe->FileReferenceNumber = ChangeOrder->FileReferenceNumber;
|
|
CvtCoe->ParentFileReferenceNumber = ChangeOrder->ParentFileReferenceNumber;
|
|
|
|
//
|
|
// Original parent and New parent are the same (not a rename)
|
|
//
|
|
CvtCoe->OriginalParentFid = ChangeOrder->OriginalParentFid;
|
|
CvtCoe->NewParentFid = ChangeOrder->NewParentFid;
|
|
|
|
//
|
|
// Copy New and orig Replica. (CO Numbers got copied with CO above).
|
|
//
|
|
CvtCoe->OriginalReplica = ChangeOrder->OriginalReplica;
|
|
CvtCoe->NewReplica = ChangeOrder->NewReplica;
|
|
|
|
//
|
|
// File's attributes
|
|
//
|
|
CvtCoe->FileAttributes = ChangeOrder->FileAttributes;
|
|
|
|
//
|
|
// Create and write times
|
|
//
|
|
CvtCoe->FileCreateTime = ChangeOrder->FileCreateTime;
|
|
CvtCoe->FileWriteTime = ChangeOrder->FileWriteTime;
|
|
|
|
//
|
|
// The sequence number is zero initially. It may get a value when
|
|
// the CO is inserted into an inbound or outbound log.
|
|
//
|
|
CvtCoe->Cmd.SequenceNumber = 0;
|
|
CvtCoe->Cmd.PartnerAckSeqNumber = 0;
|
|
|
|
//
|
|
// Use the event time from the supplied CO biased by
|
|
// Reconcile EventTimeWindow+1. If we use current time then
|
|
// when the name morph conflict is detected at other nodes they would
|
|
// generate a Del or Rename Co with yet a different time and cause
|
|
// unnecessary replication of Del or rename Cos.
|
|
//
|
|
CvtCoe->Cmd.EventTime.QuadPart = ChangeOrder->Cmd.EventTime.QuadPart +
|
|
/* RecEventTimeWindow */ + 1;
|
|
|
|
// GetSystemTimeAsFileTime((PFILETIME)&CvtCoe->Cmd.EventTime.QuadPart);
|
|
|
|
//
|
|
// FileVersionNumber bumped by one so this CO will not get rejected.
|
|
//
|
|
CvtCoe->Cmd.FileVersionNumber += 1;
|
|
|
|
//
|
|
// File's USN
|
|
//
|
|
CvtCoe->Cmd.FileUsn = 0;
|
|
CvtCoe->Cmd.JrnlUsn = 0;
|
|
CvtCoe->Cmd.JrnlFirstUsn = 0;
|
|
|
|
//
|
|
// Generate a new Volume Sequnce Number for the change order.
|
|
// But since it gets put on the front of the CO process queue it
|
|
// is probably out of order so set the flag to avoid screwing up
|
|
// dampening.
|
|
//
|
|
NEW_VSN(pVme, &CvtCoe->Cmd.FrsVsn);
|
|
CvtCoe->Cmd.OriginatorGuid = ConfigRecord->ReplicaVersionGuid;
|
|
|
|
//
|
|
// Note: We wouldn't need to mark this CO as out of order if we
|
|
// resequenced all change order VSNs (for new COs only) as they
|
|
// were fetched off the process queue.
|
|
//
|
|
CvtCoe->Cmd.Flags = 0;
|
|
CvtCoe->Cmd.IFlags = 0;
|
|
SET_CO_FLAG(CvtCoe, CO_FLAG_LOCALCO |
|
|
CO_FLAG_MORPH_GEN |
|
|
CO_FLAG_OUT_OF_ORDER);
|
|
|
|
//
|
|
// Set the Jrnl Cxtion Guid and Cxtion ptr for this Local CO.
|
|
//
|
|
INIT_LOCALCO_CXTION_AND_COUNT(Replica, CvtCoe);
|
|
|
|
//
|
|
// Tell downstream partners to skip the originator check on this Morph
|
|
// generated CO. See ChgOrdReconcile() for details.
|
|
//
|
|
SET_CO_FLAG(CvtCoe, CO_FLAG_SKIP_ORIG_REC_CHK);
|
|
|
|
//
|
|
// Delete, Rename
|
|
//
|
|
SET_CO_LOCATION_CMD(CvtCoe->Cmd, Command, LocationCmd);
|
|
|
|
if (LocationCmd == CO_LOCATION_NO_CMD) {
|
|
//
|
|
// We're giving this object a new name. Init the new name into
|
|
// both the original and the new CO.
|
|
//
|
|
CvtCoe->Cmd.ContentCmd = USN_REASON_RENAME_NEW_NAME;
|
|
SET_CO_FLAG(CvtCoe, CO_FLAG_CONTENT_CMD);
|
|
|
|
ChangeOrder->Cmd.ContentCmd |= USN_REASON_RENAME_NEW_NAME;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_CONTENT_CMD);
|
|
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER)) {
|
|
//
|
|
// This CO is marked as a MorphGenLeader. This means it has been
|
|
// through here before and so it already has the Morphed dir
|
|
// name in the CO. Use it as is in the rename CO.
|
|
// It was already copied into the CvtCoe above.
|
|
//
|
|
NOTHING;
|
|
|
|
} else {
|
|
//
|
|
// Not a MorphGenLeader so build a new name.
|
|
// Suffix "_NTFRS_<tic count in hex>" to the filename.
|
|
//
|
|
DPRINT1(4, "++ %ws will be morphed\n", CvtCoe->Cmd.FileName);
|
|
NameLen = wcslen(CvtCoe->Cmd.FileName);
|
|
if (NameLen + SIZEOF_RENAME_SUFFIX > (MAX_PATH - 1)) {
|
|
NameLen -= ((NameLen + SIZEOF_RENAME_SUFFIX) - (MAX_PATH - 1));
|
|
}
|
|
swprintf(&CvtCoe->Cmd.FileName[NameLen], L"_NTFRS_%08x", GetTickCount());
|
|
CvtCoe->Cmd.FileNameLength = wcslen(CvtCoe->Cmd.FileName) * sizeof(WCHAR);
|
|
CvtCoe->UFileName.Length = CvtCoe->Cmd.FileNameLength;
|
|
|
|
DPRINT1(4, "++ Morphing to %ws\n", CvtCoe->Cmd.FileName);
|
|
|
|
//
|
|
// MAYBE: Though it is unlikely, still need to verify that the
|
|
// friggen name is not in use.
|
|
//
|
|
CopyMemory(ChangeOrder->Cmd.FileName, CvtCoe->Cmd.FileName,
|
|
CvtCoe->Cmd.FileNameLength + sizeof(WCHAR));
|
|
|
|
ChangeOrder->Cmd.FileNameLength = CvtCoe->Cmd.FileNameLength;
|
|
ChangeOrder->UFileName.Length = ChangeOrder->Cmd.FileNameLength;
|
|
}
|
|
|
|
|
|
} else
|
|
if (LocationCmd == CO_LOCATION_DELETE) {
|
|
//
|
|
// The old code was setting USN_REASON_FILE_DELETE and
|
|
// CO_FLAG_LOCATION_CMD. This is incorrect since later code
|
|
// treats the co as a CO_LOCATION_CREATE. The fix was to
|
|
// set both the content and location to specify delete.
|
|
//
|
|
CvtCoe->Cmd.ContentCmd = USN_REASON_FILE_DELETE;
|
|
SET_CO_FLAG(CvtCoe, CO_FLAG_CONTENT_CMD);
|
|
|
|
SET_CO_LOCATION_CMD(CvtCoe->Cmd, Command, CO_LOCATION_DELETE);
|
|
SET_CO_LOCATION_CMD(CvtCoe->Cmd, DirOrFile,
|
|
(CvtCoe->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
|
|
CO_LOCATION_DIR : CO_LOCATION_FILE);
|
|
SET_CO_FLAG(CvtCoe, CO_FLAG_LOCATION_CMD);
|
|
|
|
//
|
|
// If the FID is zero then load a fake value for it since this is
|
|
// an IDTable index and there is no actual file if we are generating
|
|
// a delete CO since it will be issued first.
|
|
//
|
|
if (CvtCoe->FileReferenceNumber == ZERO_FID) {
|
|
FrsInterlockedIncrement64(CvtCoe->FileReferenceNumber,
|
|
GlobSeqNum,
|
|
&GlobSeqNumLock);
|
|
}
|
|
|
|
} else {
|
|
DPRINT1(0, "++ ERROR: Invalid Location Cmd - %d\n", LocationCmd);
|
|
FRS_ASSERT(!"Invalid Location Cmd");
|
|
}
|
|
|
|
SET_CHANGE_ORDER_STATE(CvtCoe, IBCO_INITIALIZING);
|
|
|
|
if (LocationCmd == CO_LOCATION_NO_CMD) {
|
|
//
|
|
// Push the new Co onto the head of the queue. No aging delay.
|
|
// Do this first in the case of a rename because we want the
|
|
// create CO to issue first to create the object.
|
|
// We then follow this with a Local rename Co (using the same name)
|
|
// so we can ensure the rename will propagate everywhere.
|
|
//
|
|
// This is necessary because a down stream partner may not encounter
|
|
// the name morph conflict because the ultimate name winner may not
|
|
// have arrived yet or a delete could have occurred on our winner
|
|
// follwed by the arrival of this CO via another connection path to
|
|
// the downstream partner. In either case we must prop a rename to
|
|
// ensure the name is the same everywhere.
|
|
//
|
|
|
|
//
|
|
// Mark the Rename CO as a MorphGenFollower so it gets rejected if the
|
|
// MorphGenLeader fails to create the file.
|
|
//
|
|
SET_COE_FLAG(CvtCoe, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
|
|
CHANGE_ORDER_TRACE(3, CvtCoe, "Co Push MORPH_GEN_FOLLOWER to QHead");
|
|
WStatus1 = ChgOrdInsertProcQ(Replica, CvtCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
}
|
|
|
|
//
|
|
// Do the change order cleanup but don't free the CO.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO);
|
|
|
|
FStatus = ChgOrdIssueCleanup(ThreadCtx, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
|
|
if (LocationCmd != CO_LOCATION_NO_CMD) {
|
|
//
|
|
// Mark the base CO as a MorphGenFollower so it gets sent thru retry
|
|
// if the leader gets aborted. Only set this flag if this CO is the
|
|
// follower. In the CO_LOCATION_NO_CMD case the Morph Gen Co was
|
|
// pushed on the queue above so this CO is the Leader.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_MORPH_GEN_FOLLOWER);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push MORPH_GEN_FOLLOWER to QHead");
|
|
} else {
|
|
//
|
|
// Mark the base CO as a MorphGenLeader so if it gets reissued later
|
|
// we know to refabricate the MorphGenFollower rename CO.
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_MORPH_GEN_LEADER);
|
|
//
|
|
// Set volatile flag indicating we (the MorphGenLeader) has fabricated
|
|
// the MorphGenFollower and pushed it onto the process queue. This
|
|
// prevents us from doing it again when we leave here and restart
|
|
// processing on the MorphGenLeader CO.
|
|
//
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_MG_FOLLOWER_MADE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Push MORPH_GEN_LEADER to QHead");
|
|
}
|
|
|
|
//
|
|
// Push this CO onto the head of the queue.
|
|
//
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_HEAD | IPQ_TAKE_COREF |
|
|
IPQ_MG_LOOP_CHK | IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (LocationCmd != CO_LOCATION_NO_CMD) {
|
|
//
|
|
// Push the new Co onto the head of the queue. No aging delay.
|
|
// Do this second in the case of the DeleteCo since we want to
|
|
// issue the Delete first so we can prevent the subsequent name
|
|
// conflict when the original change order issues. The delete co
|
|
// issues as a local CO so it propagates everywhere. The original
|
|
// CO that follows behind it will hit the tombstone created by the
|
|
// delete and be aborted.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, CvtCoe, "Co Push MORPH_GEN_LEADER to QHead");
|
|
WStatus1 = ChgOrdInsertProcQ(Replica, CvtCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR | IPQ_ASSERT_IF_ERR);
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus1)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Now go give the delete CO we just pushed onto the QHead a spin.
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
ChgOrdReserve(
|
|
IN PIDTABLE_RECORD IDTableRec,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Reserve resources for this change order so that subsequent change orders
|
|
will properly interlock with this change order.
|
|
|
|
Arguments:
|
|
IDTableRec -- The IDTable record for this file / dir.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
TRUE if reservation succeeded.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReserve:"
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
PQHASH_ENTRY QHashEntry;
|
|
UNICODE_STRING UnicodeStr;
|
|
ULONG GStatus;
|
|
GUID *NParentGuid;
|
|
GUID *OldParentGuid;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
|
|
//
|
|
// Make entry in the Active inbound change order hash table. This is
|
|
// indexed by the file Guid in the change order so we can interlock against
|
|
// activity on files that are being reanimated. Their IDTable entries are
|
|
// marked deleted but the FID is not updated until the CO completes
|
|
// successfully so an incoming duplicate CO on a deleted file could get
|
|
// thru if the index was a FID.
|
|
//
|
|
GStatus = GhtInsert(pVme->ActiveInboundChangeOrderTable,
|
|
ChangeOrder,
|
|
TRUE,
|
|
FALSE);
|
|
|
|
if (GStatus != GHT_STATUS_SUCCESS) {
|
|
DPRINT1(0, "++ ERROR - ActiveInboundChangeOrderTable GhtInsert Failed: %d\n", GStatus);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Rejected - AIBCO Insert Failed");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"AIBCO Insert Failed");
|
|
ChgOrdReject(ChangeOrder, Replica);
|
|
return FALSE;
|
|
}
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_AIBCO);
|
|
|
|
//
|
|
// Make entry in the Name conflict table if a file name is being
|
|
// removed so this CO interlocks with a subsequent CO that wants
|
|
// to bring the name back.
|
|
//
|
|
// Don't update the name conflict table if this is a delete co
|
|
// for a deleted id table entry. Deleted id table entries are not
|
|
// factored into the name morph conflict detection and hence can
|
|
// lead to duplicate entries in the name conflict table.
|
|
//
|
|
// NameConflictTable Entry: An entry in the NameConflictTable
|
|
// contains a reference count of the number of active change
|
|
// orders that hash to that entry and a Flags word that is set
|
|
// to COE_FLAG_VOL_COLIST_BLOCKED if the process queue for this
|
|
// volume is idled while waiting on the active change orders for
|
|
// this entry to retire. The queue is idled when the entry at
|
|
// the head of the queue hashes to this entry and so may
|
|
// have a conflicting name (hence the name, "name conflict table").
|
|
//
|
|
// The NameConflictTable can give false positives. But this is okay
|
|
// because a false positive is rare and will only idle the process
|
|
// queue until the active change order retires. Getting rid
|
|
// of the rare false positives would degrade performance. The
|
|
// false positives that happen when inserting an entry into the
|
|
// NameConflictTable are handled by using the QData field in
|
|
// the QHashEntry as a the reference count.
|
|
//
|
|
// But, you ask, how can there be multiple active cos hashing
|
|
// to this entry if the process queue is idled when a conflict
|
|
// is detected? Easy, I say, because the filename in the co
|
|
// is used to detect collisions while the filename in the idtable
|
|
// is used to reserve the qhash entry. Why? Well, the name morph
|
|
// code handles the case of a co's name colliding with an
|
|
// idtable entry. But that code wouldn't work if active change
|
|
// orders were constantly changing the idtable. So, the
|
|
// NameConflictTable synchronizes the namespace amoung
|
|
// active change orders so that the name morph code can work
|
|
// against a static namespace.
|
|
//
|
|
if (DOES_CO_DO_SIMPLE_RENAME(CoCmd) ||
|
|
(DOES_CO_REMOVE_FILE_NAME(CoCmd) &&
|
|
!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED))) {
|
|
//
|
|
// The IDTable has the current location of the file. Note that in the
|
|
// case of a remote CO the file may have already been moved either by a
|
|
// local action or the processing of a remote CO that arrived earlier
|
|
// than this one. Save the hash value in the change order because when
|
|
// it retires the the data is gone since the IDTable has been updated.
|
|
//
|
|
RtlInitUnicodeString(&UnicodeStr,
|
|
IDTableRec->FileName);
|
|
ChgOrdCalcHashGuidAndName(&UnicodeStr,
|
|
&IDTableRec->ParentGuid,
|
|
&ChangeOrder->NameConflictHashValue);
|
|
|
|
QHashAcquireLock(Replica->NameConflictTable);
|
|
QHashEntry = QHashInsertLock(Replica->NameConflictTable,
|
|
&ChangeOrder->NameConflictHashValue,
|
|
NULL,
|
|
(ULONG_PTR) 0);
|
|
QHashEntry->QData++;
|
|
QHashReleaseLock(Replica->NameConflictTable);
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_NC_TABLE);
|
|
} else {
|
|
ChangeOrder->NameConflictHashValue = QUADZERO;
|
|
}
|
|
|
|
//
|
|
// Update the count in an existing parent entry or create a new entry
|
|
// in the ActiveChildren hash table.
|
|
// Skip it if this is a TombStone IDTable Entry create and the parent
|
|
// FID is zero.
|
|
//
|
|
if (!COE_FLAG_ON(ChangeOrder, COE_FLAG_JUST_TOMBSTONE) ||
|
|
(ChangeOrder->ParentFileReferenceNumber != ZERO_FID)) {
|
|
|
|
if (ChangeOrder->ParentFileReferenceNumber == ZERO_FID) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Warning - parent fid zero");
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
}
|
|
|
|
NParentGuid = FrsAlloc(sizeof(GUID));
|
|
|
|
OldParentGuid = FrsAlloc(sizeof(GUID));
|
|
|
|
if((NParentGuid != NULL) && (OldParentGuid != NULL)) {
|
|
memcpy(NParentGuid, &CoCmd->NewParentGuid , sizeof(GUID));
|
|
GuidToStr(NParentGuid, GuidStr);
|
|
|
|
QHashAcquireLock(pVme->ActiveChildren);
|
|
|
|
QHashEntry = QHashInsertLock(pVme->ActiveChildren,
|
|
NParentGuid,
|
|
NULL,
|
|
(ULONG_PTR)NParentGuid);
|
|
|
|
QHashEntry->QData += 2;
|
|
|
|
DPRINT2(4, "++ GAC - bump count on GUID: %s, QData %08x \n",
|
|
GuidStr, QHashEntry->QData);
|
|
QHashEntry = NULL;
|
|
|
|
//
|
|
// If this CO has a old parent and a new parent (e.g. MOVE_DIR) then
|
|
// we need to block both parents. Check for that and block both.
|
|
//
|
|
|
|
if (!GUIDS_EQUAL(&CoCmd->NewParentGuid, &CoCmd->OldParentGuid)) {
|
|
memcpy(OldParentGuid, &CoCmd->OldParentGuid , sizeof(GUID));
|
|
GuidToStr(OldParentGuid, GuidStr);
|
|
|
|
QHashEntry = QHashInsertLock(pVme->ActiveChildren,
|
|
OldParentGuid,
|
|
NULL,
|
|
(ULONG_PTR)OldParentGuid);
|
|
|
|
QHashEntry->QData += 2;
|
|
|
|
DPRINT2(4, "++ GAC - bump count on GUID: %s, QData %08x \n",
|
|
GuidStr, QHashEntry->QData);
|
|
QHashEntry = NULL;
|
|
|
|
} else {
|
|
FrsFree(OldParentGuid);
|
|
}
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVE_CHILD);
|
|
|
|
QHashReleaseLock(pVme->ActiveChildren);
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdDispatch(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PIDTABLE_RECORD IDTableRec,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Dispatch the change order to one of the following:
|
|
1. RemoteCoAccepted() ,2. LocalCoAccepted(), 3. InboundRetired()
|
|
4. InstallRetry()
|
|
|
|
NOTE: On return the ChangeOrder now belongs to some other thread.
|
|
|
|
Arguments:
|
|
IDTableRec -- The IDTable record for this file / dir.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
FrsErrorStatus
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdDispatch:"
|
|
|
|
JET_ERR jerr, jerr1;
|
|
PTABLE_CTX TmpIDTableCtx;
|
|
BOOL ChildrenExist;
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
BOOL LocalCo;
|
|
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
//
|
|
// Since we are going to issue this CO clear those flags that will get
|
|
// set later when the CO either goes thru Retire or are handled in
|
|
// a different way after this point.
|
|
//
|
|
CLEAR_ISSUE_CLEANUP(ChangeOrder, ISCU_GOIS_CLEANUP);
|
|
|
|
if (LocalCo) {
|
|
|
|
//
|
|
// Increment the Local Change Orders Issued
|
|
//
|
|
PM_INC_CTR_REPSET(Replica, LCOIssued, 1);
|
|
|
|
if (ChangeOrder->StreamLastMergeSeqNum > pVme->StreamSequenceNumberFetched) {
|
|
pVme->StreamSequenceNumberFetched = ChangeOrder->StreamLastMergeSeqNum;
|
|
}
|
|
|
|
FRS_ASSERT(CO_STATE_IS(ChangeOrder, IBCO_STAGING_REQUESTED) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_STAGING_RETRY));
|
|
|
|
VVReserveRetireSlot(Replica, ChangeOrder);
|
|
DPRINT(4,"++ Submitting ChangeOrder to stager\n");
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
RcsSubmitLocalCoAccepted(ChangeOrder);
|
|
|
|
} else {
|
|
//
|
|
// The code in StuDeleteEmptyDirectory() will
|
|
// delete all of the subdirs and files if the change order
|
|
// makes it past this check. Hence, any newly created files
|
|
// and subdirs will disappear once this retry-delete makes
|
|
// it past this check.
|
|
//
|
|
// The check isn't made when the change order is not in
|
|
// retry mode because the checks in
|
|
// JrnlDoesChangeOrderHaveChildren are expensive and, in most
|
|
// cases, the delete will not encounter children.
|
|
//
|
|
// The call is made in the context of the ChangeOrderAccetp
|
|
// thread because this thread has other references to the
|
|
// tables referenced by JrnlDoesChangeOrderHaveChildren() and
|
|
// doesn't require new hash table locks. Calling the function
|
|
// out of StuDeleteEmptyDirectory() may require new locks
|
|
// around the hash tables.
|
|
//
|
|
// 210642 B3SS:Replica Synchronization test throws Alpha computer into
|
|
// error state. No further replication is possible.
|
|
// StuDeleteEmptyDirectory() was deleting the contents whenever
|
|
// CO_FLAG_RETRY was set BUT this functin was only checking for
|
|
// children when the co's state was IBCO_INSTALL_RETRY. Fix by
|
|
// checking for any CO_FLAG_RETRY of a directory delete..
|
|
//
|
|
|
|
//
|
|
// Increment the Remote Change Orders Issued
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_CONTROL)){
|
|
//
|
|
// If its not local and control, its a remote CO
|
|
//
|
|
PM_INC_CTR_REPSET(Replica, RCOIssued, 1);
|
|
}
|
|
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY) &&
|
|
CoIsDirectory(ChangeOrder) &&
|
|
DOES_CO_DELETE_FILE_NAME(CoCmd) &&
|
|
(CO_STATE_IS(ChangeOrder, IBCO_FETCH_REQUESTED) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_FETCH_RETRY) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_INSTALL_DEL_RETRY) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_INSTALL_RETRY))){
|
|
|
|
TmpIDTableCtx = FrsAlloc(sizeof(TABLE_CTX));
|
|
TmpIDTableCtx->TableType = TABLE_TYPE_INVALID;
|
|
TmpIDTableCtx->Tid = JET_tableidNil;
|
|
|
|
jerr = DbsOpenTable(ThreadCtx, TmpIDTableCtx, Replica->ReplicaNumber, IDTablex, NULL);
|
|
|
|
if (JET_SUCCESS(jerr)) {
|
|
|
|
ChildrenExist = JrnlDoesChangeOrderHaveChildren(ThreadCtx, TmpIDTableCtx, ChangeOrder);
|
|
|
|
DbsCloseTable(jerr1, ThreadCtx->JSesid, TmpIDTableCtx);
|
|
DbsFreeTableCtx(TmpIDTableCtx, 1);
|
|
FrsFree(TmpIDTableCtx);
|
|
|
|
|
|
if (ChildrenExist) {
|
|
//
|
|
// Attempting to delete a directory with children;
|
|
// retry and hope the condition clears up later.
|
|
//
|
|
// Non-replicating children don't count; the Install
|
|
// thread will delete them.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Retry Delete; has children");
|
|
|
|
//
|
|
// Consider the case where machine A does a movein of a subdir
|
|
// and before all the files are populated on machine B, machine
|
|
// B does a moveout of the dir. Since B is not fully populated
|
|
// B does not issue delete COs for all the children in the tree.
|
|
// When the delete COs for the parent dirs arrive back at machine
|
|
// A we will see that it has populated children and we will
|
|
// retry those failing delete dir COs forever. Not good.
|
|
//
|
|
// Not clear if this is a real problem. A will continue to send
|
|
// COs for the rest of the subtree to machine B. This will cause
|
|
// B to reanimate the parent dirs, getting the dir state from A.
|
|
// The file delete COs sent from B to A for files will get deleted
|
|
// on A. The Dir delete Cos sent from B to A will have active
|
|
// children and will not get deleted on A and these same dirs
|
|
// should get reanimated on B.
|
|
//
|
|
// See bug 71033
|
|
|
|
return FrsErrorCantMoveOut;
|
|
}
|
|
|
|
} else {
|
|
DPRINT1_JS(0, "DbsOpenTable (IDTABLE) on replica number %d failed.",
|
|
Replica->ReplicaNumber, jerr);
|
|
DbsCloseTable(jerr1, ThreadCtx->JSesid, TmpIDTableCtx);
|
|
DbsFreeTableCtx(TmpIDTableCtx, 1);
|
|
FrsFree(TmpIDTableCtx);
|
|
}
|
|
}
|
|
//
|
|
// Check for install retry on the remote CO.
|
|
//
|
|
|
|
switch (CO_STATE(ChangeOrder)) {
|
|
|
|
case IBCO_FETCH_REQUESTED:
|
|
case IBCO_FETCH_RETRY:
|
|
|
|
//
|
|
// Go fetch the remote file.
|
|
//
|
|
RcsSubmitRemoteCoAccepted(ChangeOrder);
|
|
break;
|
|
|
|
case IBCO_INSTALL_RETRY:
|
|
|
|
FRS_ASSERT(CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY));
|
|
//
|
|
// Retry the install.
|
|
//
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, IBCO_INSTALL_REQUESTED);
|
|
RcsSubmitRemoteCoInstallRetry(ChangeOrder);
|
|
break;
|
|
|
|
|
|
case IBCO_INSTALL_REN_RETRY:
|
|
|
|
FRS_ASSERT(CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY));
|
|
//
|
|
// The install completed but the rename of a newly created
|
|
// file into its final name failed. Since this CO made it past
|
|
// reconcile it should have picked up the state of the deferred
|
|
// rename flag from the IDTable record. Check that it did.
|
|
// The retire code is responsible for doing the file rename.
|
|
//
|
|
FRS_ASSERT(COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_RENAME));
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Complete final rename");
|
|
ChgOrdInboundRetired(ChangeOrder);
|
|
break;
|
|
|
|
case IBCO_INSTALL_DEL_RETRY:
|
|
|
|
FRS_ASSERT(CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY));
|
|
//
|
|
// The CO completed but the actual delete has yet to be finished.
|
|
// Since this CO made it past reconcile it should have picked up
|
|
// the state of the deferred delete flag from the IDTable record.
|
|
// Check that it did. The retire code is responsible for doing
|
|
// the final delete.
|
|
//
|
|
FRS_ASSERT(COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE));
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Complete final delete");
|
|
ChgOrdInboundRetired(ChangeOrder);
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Invalid Issue State");
|
|
FRS_ASSERT(!"ChgOrdDispatch: Invalid Issue State");
|
|
|
|
} // end switch
|
|
|
|
|
|
}
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdReject(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
This change order has been rejected. Set the Issue Cleanup flags depending
|
|
on what needs to be undone.
|
|
|
|
Arguments:
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReject:"
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
BOOL RemoteCo, RetryCo, RecoveryCo;
|
|
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
RetryCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY);
|
|
RecoveryCo = RecoveryCo(ChangeOrder);
|
|
|
|
//
|
|
// For whatever reason, reject this changeorder. The flags in
|
|
// ChangeOrder->IssueCleanup specify what must be un-done.
|
|
// Note: Perf: The IFLAG abort bit may be sufficient?
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_ABORT_CO);
|
|
|
|
if (RetryCo || RecoveryCo) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_INLOG);
|
|
}
|
|
|
|
FRS_PRINT_TYPE(5, ChangeOrder);
|
|
|
|
if (RemoteCo) {
|
|
//
|
|
// Remote CO rejected. Activate VV retire slot if not yet done
|
|
// so we update the version vector entry for this originator.
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_VV_ACTIVATED)) {
|
|
FRS_ASSERT(CO_STATE_IS_LE(ChangeOrder, IBCO_FETCH_RETRY));
|
|
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACK_INBOUND);
|
|
|
|
#if 0
|
|
//
|
|
// If the Aborted CO is in the IBCO_FETCH_RETRY state then
|
|
// it is possible that a staging file was created before the
|
|
// CO was sent thru retry. It needs to be deleted.
|
|
//
|
|
// PERF: What if the same CO from mult inbound partners
|
|
// are in retry? Will the later COs that get aborted
|
|
// del the stage file still in use by first one to succeed?
|
|
// YUP. FOR NOW COMMENT THIS OUT AND LET THE RESTART CODE CLEAN
|
|
// UP THE LEFTOVER STAGING FILES,
|
|
//
|
|
if (CO_STATE_IS(ChangeOrder, IBCO_FETCH_RETRY)) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_DEL_STAGE_FILE);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Don't bother updating the ondisk version vector if the
|
|
// version vector has already seen this Vsn. This condition
|
|
// can arise if a machine receives the same series of change
|
|
// orders from two machines. The change orders could easily
|
|
// complete out of order.
|
|
//
|
|
if (VVHasVsn(Replica->VVector, CoCmd)) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD);
|
|
} else {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV);
|
|
//SET_CO_FLAG(ChangeOrder, CO_FLAG_VV_ACTIVATED);
|
|
}
|
|
|
|
|
|
} else {
|
|
//
|
|
// VVretire slot has been activated for this CO.
|
|
// If VVRETIRE_EXECuted is set then the the Outlog record has
|
|
// been inserted.
|
|
//
|
|
FRS_ASSERT(!CO_STATE_IS_LE(ChangeOrder, IBCO_FETCH_RETRY));
|
|
|
|
LOCK_GEN_TABLE(Replica->VVector);
|
|
if (CO_IFLAG_ON(ChangeOrder, CO_IFLAG_VVRETIRE_EXEC)) {
|
|
//
|
|
// Cause the install incomplete flag in the outlog record
|
|
// to be cleared so OutLog process can delete the staging
|
|
// file when it's done.
|
|
//
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_CO_ABORT |
|
|
ISCU_DEL_STAGE_FILE_IF);
|
|
} else {
|
|
//
|
|
// ISCU_ACTIVATE_VV_DISCARD will clear the INS_OUTLOG
|
|
// action in the VV slot but still updates the VV at the
|
|
// right time.
|
|
//
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_ACTIVATE_VV_DISCARD |
|
|
ISCU_DEL_STAGE_FILE);
|
|
SET_CO_IFLAG(ChangeOrder, CO_IFLAG_CO_ABORT);
|
|
}
|
|
UNLOCK_GEN_TABLE(Replica->VVector);
|
|
|
|
}
|
|
//
|
|
// Install is done (or if it's not, it is now).
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_INSTALL_INCOMPLETE);
|
|
} else {
|
|
|
|
//
|
|
// If this CO was going to do a Directory Enumeration then turn it off.
|
|
//
|
|
if (CO_IFLAG_ON(ChangeOrder, CO_IFLAG_DIR_ENUM_PENDING)) {
|
|
CLEAR_CO_IFLAG(ChangeOrder, CO_IFLAG_DIR_ENUM_PENDING);
|
|
}
|
|
|
|
//
|
|
// Advance the inlog commit point for local COs that are not reanimated
|
|
// COs. Need to check if greater than because a retry CO that gets
|
|
// rejected can move the commit point back.
|
|
//
|
|
if (CoCmd->JrnlFirstUsn != (USN) 0) {
|
|
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
|
|
AcquireQuadLock(&pVme->QuadWriteLock);
|
|
if (Replica->InlogCommitUsn < CoCmd->JrnlFirstUsn) {
|
|
Replica->InlogCommitUsn = CoCmd->JrnlFirstUsn;
|
|
DPRINT1(4, "++ Replica->InlogCommitUsn: %08x %08x\n",
|
|
PRINTQUAD(Replica->InlogCommitUsn));
|
|
}
|
|
ReleaseQuadLock(&pVme->QuadWriteLock);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdInboundRetired(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The processing of an inbound change order is completed.
|
|
|
|
Send a request to the DB subsystem to update the IDTable and delete the
|
|
record from the inbound log table.
|
|
|
|
Arguments:
|
|
|
|
ChangeOrder -- The change order
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInboundRetired:"
|
|
|
|
PREPLICA Replica;
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Enter Retire");
|
|
|
|
|
|
Replica = CO_REPLICA(ChangeOrder);
|
|
|
|
if (Replica == NULL) {
|
|
DPRINT(0, "++ ERROR - ChangeOrder NewReplica and OrigReplica are NULL.\n");
|
|
FRS_ASSERT(!"ChangeOrder NewReplica and OrigReplica are NULL");
|
|
return FrsErrorInternalError;
|
|
}
|
|
|
|
//
|
|
// This routine can execute in any thread so use the DB server to delete
|
|
// the record from the inbound log and update the IDTable.
|
|
// Once the Database updates are complete the DBService will call
|
|
// us back through ChgOrdIssueCleanup() so we can cleanup our structs.
|
|
//
|
|
DbsPrepareCmdPkt(NULL, // CmdPkt,
|
|
Replica, // Replica,
|
|
CMD_DBS_RETIRE_INBOUND_CO, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
ChangeOrder, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
TRUE); // Submit
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdInboundRetry(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN ULONG NewState
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The Install/generate phase of an inbound change order has failed
|
|
and must be retried later.
|
|
|
|
Send a request to the DB subsystem to update the change order in the inbound
|
|
log and get the outbound log process started. When the change order is
|
|
later retried it goes through all the normal issue and reconcilliation
|
|
checks.
|
|
|
|
Arguments:
|
|
|
|
ChangeOrder -- The change order
|
|
NewState -- The new IBCO_xx state telling what kind of CO retry is needed.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInboundRetry:"
|
|
|
|
PREPLICA Replica;
|
|
ULONG LocationCmd;
|
|
|
|
//
|
|
// If the connection to the inbound partner was lost then just send it
|
|
// to the inlog for later retry.
|
|
//
|
|
if (!COE_FLAG_ON(ChangeOrder, COE_FLAG_NO_INBOUND)) {
|
|
|
|
//
|
|
// We can't do retry for directory creates because subsequent file ops could
|
|
// fail. Although a local Co may be retried because of a lack of
|
|
// staging space. Hence,
|
|
//
|
|
if (CoIsDirectory(ChangeOrder)&& !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO)) {
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
if (CO_NEW_FILE(LocationCmd)) {
|
|
//
|
|
// Caller should have forced an UnJoin so we don't come here.
|
|
// This causes any further change orders from this partner
|
|
// to go to the INLOG.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Error - can't retry failed dir cre");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"ChgOrdInboundRetry: can't retry failed dir cre");
|
|
return ChgOrdInboundRetired(ChangeOrder);
|
|
}
|
|
}
|
|
}
|
|
|
|
Replica = CO_REPLICA(ChangeOrder);
|
|
if (Replica == NULL) {
|
|
DPRINT(0, "++ ERROR - ChangeOrder NewReplica and OrigReplica are NULL.\n");
|
|
FRS_ASSERT(!"ChgOrdInboundRetry: ChangeOrder NewReplica and OrigReplica are NULL");
|
|
return FrsErrorInternalError;
|
|
}
|
|
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, NewState);
|
|
//
|
|
// This routine can execute in any thread so use the DB server to update
|
|
// the record in the inbound log for retry.
|
|
// Once the Database updates are complete the DBService will call
|
|
// us back through ChgOrdIssueCleanup() so we can cleanup our structs.
|
|
//
|
|
DbsPrepareCmdPkt(NULL, // CmdPkt,
|
|
Replica, // Replica,
|
|
CMD_DBS_RETRY_INBOUND_CO, // CmdRequest,
|
|
NULL, // TableCtx,
|
|
ChangeOrder, // CallContext,
|
|
0, // TableType,
|
|
0, // AccessRequest,
|
|
0, // IndexType,
|
|
NULL, // KeyValue,
|
|
0, // KeyValueLength,
|
|
TRUE); // Submit
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdIssueCleanup(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
ULONG CleanUpFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a common cleanup routine used for change order abort during or after
|
|
issue and for change order completion.
|
|
|
|
It uses the DB thread context of the caller's thread.
|
|
|
|
The cleanup flags drive the opertions performed. See below.
|
|
The sequence of the code segments below is important for both correct
|
|
operation and recoverability. DO NOT CHANGE unless you understand.
|
|
|
|
Different calls use different sets of operation. They could be broken out
|
|
into seperate functions. However having the code segments in one routine
|
|
helps both maintainence and to see the overall relationship between segments.
|
|
|
|
Arguments:
|
|
|
|
ThreadCtx -- ptr to the thread context for DB
|
|
Replica -- ptr to replica struct.
|
|
ChangeOrder -- ptr to change order entry that has just committed.
|
|
CleanUpFlags -- The clean up flag bits control what cleanup operations are done.
|
|
|
|
ISCU_DEL_PREINSTALL - Delete the pre-install file
|
|
ISCU_DEL_IDT_ENTRY - Delete the IDTable entry
|
|
ISCU_UPDATE_IDT_ENTRY - Update the IDTable entry
|
|
ISCU_DEL_INLOG - Delete the INlog entry (if RefCnt == 0)
|
|
ISCU_AIBCO - Delete Active Inbound Change Order table entry
|
|
ISCU_ACTIVE_CHILD - Delete the Active child entry
|
|
ISCU_CO_GUID - Delete the entry from the change order GUID table
|
|
ISCU_CHECK_ISSUE_BLOCK - Check if CO is blocking another and unidle queue.
|
|
ISCU_DEL_RTCTX - Delete the Replica Thread Ctx struct (if RefCnt==0)
|
|
ISCU_ACTIVATE_VV - Activate the Version Vector retire slot for this CO
|
|
ISCU_UPDATEVV_DB - Update the Version Vector entry in the database.
|
|
ISCU_ACTIVATE_VV_DISCARD - Discard the VV retire slot.
|
|
ISCU_ACK_INBOUND - Notify the Inbound partner or update our local VV
|
|
ISCU_INS_OUTLOG - Insert the CO into the outbound log.
|
|
ISCU_INS_OUTLOG_NEW_GUID - Re-Insert the CO into the outbound log with a new CO Guid.
|
|
ISCU_UPDATE_INLOG - Update the State, Flags and IFlags fields in the DB CO record.
|
|
ISCU_DEL_STAGE_FILE - Delete the staging file.
|
|
ISCU_DEL_STAGE_FILE_IF - Delete staging file only IF no outbound partners
|
|
ISCU_FREE_CO - Free the Change Order (if RefCnt==0)
|
|
ISCU_DEC_CO_REF - Decrement the ref count on the CO.
|
|
ISCU_CO_ABORT - This CO is aborting.
|
|
ISCU_NC_TABLE - Delete the entry in the name conflict table.
|
|
ISCU_UPDATE_IDT_FLAGS - Update just IDTable record flags, not entire record
|
|
ISCU_UPDATE_IDT_FILEUSN- Update just IDTable record file USN, not entire record.
|
|
ISCU_UPDATE_IDT_VERSION- Update just the version info on the file.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdIssueCleanup:"
|
|
|
|
#undef FlagChk
|
|
#define FlagChk(_flag_) BooleanFlagOn(CleanUpFlags, _flag_)
|
|
|
|
ULONGLONG SeqNum;
|
|
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
PCHANGE_ORDER_ENTRY ActiveChangeOrder;
|
|
PCHANGE_ORDER_ENTRY DupChangeOrder;
|
|
ULONG GStatus, WStatus, WStatus2;
|
|
ULONG FStatus = FrsErrorSuccess;
|
|
BOOL RestartQueue = FALSE;
|
|
PQHASH_TABLE ActiveChildren;
|
|
PQHASH_ENTRY QHashEntry;
|
|
BOOL PendingCoOnParent;
|
|
BOOL RetryCo;
|
|
GUID *BusyParentGuid = NULL;
|
|
CHAR BusyParentGuidStr[GUID_CHAR_LEN];
|
|
PCHANGE_ORDER_COMMAND CoCmd = &ChangeOrder->Cmd;
|
|
PTABLE_CTX TableCtx;
|
|
PTABLE_CTX InLogTableCtx;
|
|
PWCHAR TmpName = NULL;
|
|
TABLE_CTX TempTableCtx;
|
|
PSINGLE_LIST_ENTRY Entry;
|
|
ULONG RefCount;
|
|
ULONG SaveState, SaveFlags;
|
|
JET_ERR jerr;
|
|
ULONG FieldIndex[8];
|
|
PVOID pDataRecord;
|
|
BOOL DelStage, UpdateOutLog, InstallIncomplete;
|
|
BOOL NotifyRetryThread = FALSE;
|
|
ULONG IdtFieldUpdateList[CO_ACCEPT_IDT_FIELD_UPDATE_MAX];
|
|
ULONG IdtFieldUpdateCount;
|
|
ULONG MissMatch;
|
|
GUID OldGuid;
|
|
CHAR FlagBuffer[160];
|
|
|
|
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO IssueCleanup, FlgsA", CleanUpFlags);
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO IssueCleanup, FlgsC", ChangeOrder->IssueCleanup);
|
|
MissMatch = (~CleanUpFlags) & ChangeOrder->IssueCleanup;
|
|
if (MissMatch != 0) {
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO IssueCleanup, FlgsE", MissMatch);
|
|
}
|
|
//
|
|
// Merge flags with bits from CO if caller says ok.
|
|
//
|
|
if (!FlagChk(ISCU_NO_CLEANUP_MERGE)) {
|
|
CleanUpFlags |= ChangeOrder->IssueCleanup;
|
|
}
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO IssueCleanup, Flgs ", CleanUpFlags);
|
|
|
|
if (CleanUpFlags == 0) {
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
FrsFlagsToStr(CleanUpFlags, IscuFlagNameTable, sizeof(FlagBuffer), FlagBuffer);
|
|
DPRINT2(3, ":: CoG %08x, Flags [%s]\n", CoCmd->ChangeOrderGuid.Data1, FlagBuffer);
|
|
|
|
FRS_ASSERT(Replica != NULL);
|
|
|
|
pVme = Replica->pVme;
|
|
FRS_ASSERT((pVme != NULL) || !REPLICA_IS_ACTIVE(Replica));
|
|
|
|
if ((pVme == NULL) || (AcquireVmeRef(pVme) == 0)) {
|
|
FStatus = FrsErrorChgOrderAbort;
|
|
pVme = NULL;
|
|
//
|
|
// Cleanup what we can. The following depend on the Vme.
|
|
//
|
|
ClearFlag(CleanUpFlags, ISCU_AIBCO |
|
|
ISCU_ACTIVE_CHILD |
|
|
ISCU_DEL_IDT_ENTRY |
|
|
ISCU_DEL_PREINSTALL );
|
|
} else {
|
|
ActiveChildren = pVme->ActiveChildren;
|
|
}
|
|
|
|
//
|
|
// If this is a Demand Refresh CO then some cleanup actions are not
|
|
// required. Instead of littering the various paths to this function,
|
|
// Retry, VV execute, Retire, Reject, ... with tests for Demand Refresh
|
|
// we just clear the appropriate flags here so the other paths can
|
|
// ignore it for the most part.
|
|
//
|
|
// We now want to send the demand refresh COs forward to other partners
|
|
// to ensure convergence so do not clear the ISCU_INS_OUTLOG and
|
|
// ISCU_INS_OUTLOG_NEW_GUID flags.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_DEMAND_REFRESH)) {
|
|
|
|
ClearFlag(CleanUpFlags, ISCU_ACK_INBOUND |
|
|
ISCU_ACTIVATE_VV |
|
|
ISCU_UPDATEVV_DB |
|
|
ISCU_ACTIVATE_VV_DISCARD |
|
|
// ISCU_INS_OUTLOG |
|
|
// ISCU_INS_OUTLOG_NEW_GUID |
|
|
ISCU_DEL_INLOG |
|
|
ISCU_UPDATE_INLOG);
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO ISCU DemandRefresh, Flgs", CleanUpFlags);
|
|
} else
|
|
//
|
|
// If this is a MorphGenCo then it is a local CO that never was inserted
|
|
// into the Inbound Log. Clear the Inlog related flags here.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN)) {
|
|
|
|
ClearFlag(CleanUpFlags, ISCU_DEL_INLOG |
|
|
ISCU_UPDATE_INLOG);
|
|
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "CO ISCU MORPH GEN, Flgs", CleanUpFlags);
|
|
}
|
|
|
|
|
|
RetryCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY);
|
|
|
|
//
|
|
// ISCU_DEL_PREINSTALL
|
|
// Delete the pre-Install File
|
|
//
|
|
if (FlagChk(ISCU_DEL_PREINSTALL)) {
|
|
HANDLE Handle;
|
|
|
|
//
|
|
// Open the pre-install file and delete it
|
|
//
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO PreInstall File Delete");
|
|
TmpName = FrsCreateGuidName(&CoCmd->ChangeOrderGuid,
|
|
PRE_INSTALL_PREFIX);
|
|
WStatus2 = FrsCreateFileRelativeById(&Handle,
|
|
ChangeOrder->NewReplica->PreInstallHandle,
|
|
NULL,
|
|
0,
|
|
0,
|
|
TmpName,
|
|
(USHORT)(wcslen(TmpName) *
|
|
sizeof(WCHAR)),
|
|
NULL,
|
|
FILE_OPEN,
|
|
ATTR_ACCESS | DELETE);
|
|
if (!WIN_SUCCESS(WStatus2)) {
|
|
DPRINT2_WS(0, "++ WARN - Failed to open pre-install file %ws for %ws for delete;",
|
|
TmpName, CoCmd->FileName, WStatus2);
|
|
} else {
|
|
FrsResetAttributesForReplication(TmpName, Handle);
|
|
WStatus2 = FrsDeleteByHandle(TmpName, Handle);
|
|
DPRINT2_WS(0, "++ WARN - Failed to delete pre-install file %ws for %ws;",
|
|
TmpName, CoCmd->FileName, WStatus2);
|
|
|
|
FrsCloseWithUsnDampening(TmpName,
|
|
&Handle,
|
|
ChangeOrder->NewReplica->pVme->FrsWriteFilter,
|
|
&CoCmd->FileUsn);
|
|
}
|
|
FrsFree(TmpName);
|
|
}
|
|
|
|
//
|
|
// ISCU_DEL_STAGE_FILE
|
|
// ISCU_DEL_STAGE_FILE_IF
|
|
// ISCU_CO_ABORT
|
|
// Delete the staging file if so ordered or if there are no outbound
|
|
// partners and either the CO is aborted or a conditional delete is requested.
|
|
//
|
|
DelStage = FlagChk(ISCU_DEL_STAGE_FILE) ||
|
|
(NO_OUTLOG_PARTNERS(Replica) &&
|
|
(FlagChk(ISCU_DEL_STAGE_FILE_IF) || FlagChk(ISCU_CO_ABORT)));
|
|
|
|
if (DelStage) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO StageFile Delete");
|
|
|
|
if (!StageDeleteFile(CoCmd, NULL, TRUE)) {
|
|
DPRINT1(0, "++ ERROR - Failed to delete staging file: %ws\n",
|
|
CoCmd->FileName);
|
|
FStatus = FrsErrorStageFileDelFailed;
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_DEL_STAGE_FILE_IF
|
|
// ISCU_CO_ABORT
|
|
// Update the Flags field on the outbound log record if we have partners
|
|
// and the record was created and either the CO is aborted or a conditional
|
|
// staging file delete is requested.
|
|
//
|
|
UpdateOutLog = !NO_OUTLOG_PARTNERS(Replica) &&
|
|
CO_IFLAG_ON(ChangeOrder, CO_IFLAG_VVRETIRE_EXEC) &&
|
|
(FlagChk(ISCU_DEL_STAGE_FILE_IF) || FlagChk(ISCU_CO_ABORT));
|
|
|
|
InstallIncomplete = COC_FLAG_ON(CoCmd, CO_FLAG_INSTALL_INCOMPLETE);
|
|
|
|
if (UpdateOutLog && (InstallIncomplete ||
|
|
CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY) ||
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_RECOVERY_CO) ||
|
|
FlagChk(ISCU_CO_ABORT))) {
|
|
//
|
|
// Clear the Install Incomplete flag or set abort flag so the staging
|
|
// file can be deleted when the outbound log process is done.
|
|
// Use local table ctx if not provided.
|
|
//
|
|
if (ChangeOrder->RtCtx == NULL) {
|
|
TableCtx = &TempTableCtx;
|
|
TableCtx->TableType = TABLE_TYPE_INVALID;
|
|
TableCtx->Tid = JET_tableidNil;
|
|
} else {
|
|
TableCtx = &ChangeOrder->RtCtx->OUTLOGTable;
|
|
}
|
|
|
|
DbsAllocTableCtxWithRecord(OUTLOGTablex, TableCtx, CoCmd);
|
|
|
|
DPRINT2(4, "++ Clear Install Incomplete in outlog for Index %d, File: %ws\n",
|
|
CoCmd->SequenceNumber, CoCmd->FileName);
|
|
|
|
CLEAR_COC_FLAG(CoCmd, CO_FLAG_INSTALL_INCOMPLETE);
|
|
|
|
// ISCU_CO_ABORT
|
|
if (FlagChk(ISCU_CO_ABORT)) {
|
|
SET_COC_FLAG(CoCmd, CO_FLAG_ABORT_CO);
|
|
DPRINT2(4, "++ Set abort flag in outlog for Index %d, File: %ws\n",
|
|
CoCmd->SequenceNumber, CoCmd->FileName);
|
|
}
|
|
//
|
|
// Clear the same flags that are cleared during insert
|
|
// (except for ABORT).
|
|
//
|
|
SAVE_CHANGE_ORDER_STATE(ChangeOrder, SaveState, SaveFlags);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_GROUP_OL_CLEAR);
|
|
if (FlagChk(ISCU_CO_ABORT)) {
|
|
SET_COC_FLAG(CoCmd, CO_FLAG_ABORT_CO);
|
|
}
|
|
|
|
//
|
|
// ** WARNING ** WARNING ** WARNING **
|
|
// The following doesn't work if the Outbound Log process saves any
|
|
// state in the flags word because we overwrite it here. The Outlog
|
|
// process currently has no need to modify these bits so this is OK.
|
|
// Change orders generated during VVJoin set these flags but these
|
|
// change orders are never processed by the inlog process so we do
|
|
// not write to their flags field. If the Outlog process needs to
|
|
// write these bits this code must be updated so state is not lost,
|
|
// or the Outlog process could add its own flag word to the change order.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO OutLog Update");
|
|
FStatus = DbsUpdateRecordField(ThreadCtx,
|
|
Replica,
|
|
TableCtx,
|
|
OLChangeOrderGuidIndexx,
|
|
&CoCmd->ChangeOrderGuid,
|
|
COFlagsx);
|
|
RESTORE_CHANGE_ORDER_STATE(ChangeOrder, SaveState, SaveFlags);
|
|
TableCtx->pDataRecord = NULL;
|
|
//
|
|
// Clear the Jet Set/Ret Col address fields for the Change Order
|
|
// Extension buffer to prevent reuse since that buffer goes with the CO.
|
|
//
|
|
DBS_SET_FIELD_ADDRESS(TableCtx, COExtensionx, NULL);
|
|
|
|
if (TableCtx == &TempTableCtx) {
|
|
DbsFreeTableCtx(TableCtx, 1);
|
|
}
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
CHANGE_ORDER_TRACEF(3, ChangeOrder, "Error Updating Outlog", FStatus);
|
|
//
|
|
// The Outlog record might not be there if we have previously gone
|
|
// through an IBCO_INSTALL_REN_RETRY state and already cleared the
|
|
// CO_FLAG_INSTALL_INCOMPLETE flag. This would have allowed the
|
|
// Outlog cleanup thread to delete the outlog record when it
|
|
// finished sending the CO even if the install rename had not yet
|
|
// completed. But if InstallIncomplete is TRUE then the out log
|
|
// record should be there.
|
|
//
|
|
// A similar situation occurs on deletes where a sharing violation
|
|
// marks the IDTable as IDREC_FLAGS_DELETE_DEFERRED and sends the CO
|
|
// thru retry in the IBCO_INSTALL_DEL_RETRY state. The outlog
|
|
// process can process the delete CO and cleanup so we don't find the record.
|
|
//
|
|
if (InstallIncomplete || (FStatus != FrsErrorNotFound)) {
|
|
DPRINT_FS(0,"++ ERROR - Update of Flags in Outlog record failed.", FStatus);
|
|
FStatus = FrsErrorSuccess;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ISCU_UPDATE_IDT_ENTRY
|
|
// Update the IDTable record for this CO. This must be done before we
|
|
// Activate the VV slot in case this CO is next in line to retire.
|
|
// OutLog process needs to see current IDTable entry.
|
|
//
|
|
if (FlagChk(ISCU_UPDATE_IDT_ENTRY)) {
|
|
DPRINT(5, "Updating the IDTable record -----------\n");
|
|
FStatus = ChgOrdUpdateIDTableRecord(ThreadCtx, Replica, ChangeOrder);
|
|
DPRINT_FS(0,"++ ERROR - ChgOrdUpdateIDTableRecord failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"IssueCleanup: ISCU_UPDATE_IDT_ENTRY failed");
|
|
} else {
|
|
|
|
PIDTABLE_RECORD IDTableRec;
|
|
//
|
|
// ISCU_UPDATE_IDT_FLAGS
|
|
// Not updating the entire record so check the field flags.
|
|
//
|
|
IdtFieldUpdateCount = 0;
|
|
if (FlagChk(ISCU_UPDATE_IDT_FLAGS)) {
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = Flagsx;
|
|
}
|
|
|
|
//
|
|
// ISCU_UPDATE_IDT_VVFLAGS
|
|
//
|
|
if (FlagChk(ISCU_UPDATE_IDT_VVFLAGS)) {
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = IdtVVFlagsx;
|
|
}
|
|
|
|
// ISCU_UPDATE_IDT_FILEUSN
|
|
if (FlagChk(ISCU_UPDATE_IDT_FILEUSN)) {
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = CurrentFileUsnx;
|
|
IDTableRec = (PIDTABLE_RECORD)(ChangeOrder->RtCtx->IDTable.pDataRecord);
|
|
IDTableRec->CurrentFileUsn = CoCmd->FileUsn;
|
|
}
|
|
|
|
|
|
// ISCU_UPDATE_IDT_VERSION
|
|
// Update all the info related to file reconcilation so the info is
|
|
// consistent for future reconcile checks and so correct version
|
|
// info can be generated when local changes occur to the file/dir.
|
|
//
|
|
if (FlagChk(ISCU_UPDATE_IDT_VERSION)) {
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = VersionNumberx;
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = EventTimex;
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = OriginatorGuidx;
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = OriginatorVSNx;
|
|
IdtFieldUpdateList[IdtFieldUpdateCount++] = FileSizex;
|
|
|
|
IDTableRec = (PIDTABLE_RECORD)(ChangeOrder->RtCtx->IDTable.pDataRecord);
|
|
IDTableRec->VersionNumber = CoCmd->FileVersionNumber;
|
|
IDTableRec->EventTime = CoCmd->EventTime.QuadPart;
|
|
IDTableRec->OriginatorGuid = CoCmd->OriginatorGuid;
|
|
IDTableRec->OriginatorVSN = CoCmd->FrsVsn;
|
|
IDTableRec->FileSize = CoCmd->FileSize;
|
|
}
|
|
|
|
|
|
FRS_ASSERT(IdtFieldUpdateCount <= CO_ACCEPT_IDT_FIELD_UPDATE_MAX);
|
|
if (IdtFieldUpdateCount > 0) {
|
|
FStatus = DbsUpdateIDTableFields(ThreadCtx,
|
|
Replica,
|
|
ChangeOrder,
|
|
IdtFieldUpdateList,
|
|
IdtFieldUpdateCount);
|
|
DPRINT_FS(0,"++ ERROR - DbsUpdateIDTableFields failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"ISCU_UPDATE_IDT_VERSION - update fields failed");
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_ACTIVATE_VV
|
|
// ISCU_ACTIVATE_VV_DISCARD
|
|
// Update the version vector. This could result in just marking the
|
|
// retire slot entry or we could end up getting called back to
|
|
// write the outbound log entry, decrement the ref count, or update the
|
|
// Version vector table in the database. The cleanup flags that are passed
|
|
// through are saved in the retire slot for use when the entry is processed.
|
|
// The INLOG flags may not be set if this CO is rejected on the first pass
|
|
// so the caller must tell us.
|
|
//
|
|
// code improvement: Use IssueCleanup flags in COe now instead of
|
|
// saving them in the vv retire slot.
|
|
//
|
|
if (FlagChk(ISCU_ACTIVATE_VV) || FlagChk(ISCU_ACTIVATE_VV_DISCARD)) {
|
|
//
|
|
// PERF: optimize the case where this CO is at the head of the VV retire
|
|
// list and is the only CO to retire. In this case the func could
|
|
// just return and avoid the recursive call back.
|
|
//
|
|
if (!FlagChk(ISCU_ACTIVATE_VV_DISCARD)) {
|
|
//
|
|
// Only do this once for the change order.
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_VV_ACTIVATED);
|
|
}
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO Activate VV slot");
|
|
FStatus = VVRetireChangeOrder(ThreadCtx,
|
|
Replica,
|
|
ChangeOrder,
|
|
(CleanUpFlags & (ISCU_INS_OUTLOG |
|
|
ISCU_INS_OUTLOG_NEW_GUID |
|
|
ISCU_DEL_INLOG |
|
|
ISCU_UPDATE_INLOG |
|
|
ISCU_ACTIVATE_VV_DISCARD))
|
|
| ISCU_NO_CLEANUP_MERGE
|
|
);
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// Propagating the change order is now the job of VVRetireChangeOrder.
|
|
//
|
|
ClearFlag(CleanUpFlags, (ISCU_INS_OUTLOG | ISCU_INS_OUTLOG_NEW_GUID));
|
|
} else
|
|
if (FStatus == FrsErrorVVSlotNotFound) {
|
|
//
|
|
// No slot for an Out of order CO so we handle all cleanup ourselves.
|
|
//
|
|
FStatus = FrsErrorSuccess;
|
|
|
|
} else {
|
|
DPRINT_FS(0, "++ ERROR - VVRetireChangeOrder failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"VVRetireChangeOrder failed");
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_UPDATEVV_DB
|
|
// Update the version vector in the database.
|
|
//
|
|
if (FlagChk(ISCU_UPDATEVV_DB)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO DB Update VV");
|
|
|
|
FStatus = DbsUpdateVV(ThreadCtx,
|
|
Replica,
|
|
ChangeOrder->RtCtx,
|
|
ChangeOrder->Cmd.FrsVsn,
|
|
&ChangeOrder->Cmd.OriginatorGuid);
|
|
DPRINT_FS(0,"++ ERROR - DbsUpdateVV failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"ISCU_UPDATEVV_DB failed");
|
|
}
|
|
|
|
//
|
|
// ISCU_ACK_INBOUND
|
|
// Let the replica subsystem know this change order is done.
|
|
// Restore the Partner's sequence number to use in the Ack.
|
|
// The Ack is done after the VV update so we pickup the VSN for the
|
|
// most recent VV update for this change order originator.
|
|
//
|
|
if (FlagChk(ISCU_ACK_INBOUND)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO Ack Inbound");
|
|
RcsInboundCommitOk(Replica, ChangeOrder);
|
|
}
|
|
|
|
//
|
|
// ISCU_INS_OUTLOG
|
|
// ISCU_INS_OUTLOG_NEW_GUID
|
|
// Insert the change order into the Outbound log if any outbound partners.
|
|
//
|
|
if (FlagChk((ISCU_INS_OUTLOG | ISCU_INS_OUTLOG_NEW_GUID)) &&
|
|
!NO_OUTLOG_PARTNERS(Replica)) {
|
|
|
|
//
|
|
// Need the list lock to preserve lock ordering when we unidle the queue.
|
|
// This serializes any insert into outlog and update of inlog.
|
|
// OutlogInserCo overwrites SequenceNumber which can confuse inlog update.
|
|
//
|
|
FrsRtlAcquireListLock(&FrsVolumeLayerCOList);
|
|
|
|
ChgOrdAcquireLockGuid(ChangeOrder);
|
|
|
|
//
|
|
// Increment the (Local and Remote) CO propagated counters
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO)) {
|
|
PM_INC_CTR_REPSET(Replica, LCOPropagated, 1);
|
|
}
|
|
//
|
|
// It's a Remote CO
|
|
//
|
|
else if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_CONTROL)) {
|
|
PM_INC_CTR_REPSET(Replica, RCOPropagated, 1);
|
|
}
|
|
|
|
TableCtx = (ChangeOrder->RtCtx != NULL) ?
|
|
&ChangeOrder->RtCtx->OUTLOGTable : NULL;
|
|
FRS_ASSERT(TableCtx != NULL);
|
|
|
|
SAVE_CHANGE_ORDER_STATE(ChangeOrder, SaveState, SaveFlags);
|
|
|
|
//
|
|
// Update the CO state and clear the flags that should not be
|
|
// sent to the outbound partner.
|
|
//
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, IBCO_OUTBOUND_REQUEST);
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_GROUP_OL_CLEAR);
|
|
|
|
//
|
|
// Remote retry change orders are inserted into the outbound log
|
|
// twice; once after the failed install and again after the successful
|
|
// install. Needless to say, the second insert may generate duplicate
|
|
// key errors unless it is assigned a new guid.
|
|
//
|
|
// This deals with the problem of an outbound partner doing a VVJOIN
|
|
// while we still have remote COs in an install retry state. If the
|
|
// original remote CO was inserted into the outlog before the VVJOIN
|
|
// request arrived then the remote CO may never have been sent
|
|
// on to the outbound partner. But when processing the VVJOIN, the
|
|
// IDTable won't be up to date yet (since the install isn't finished)
|
|
// so the downstream partner may not see the file (or the new contents
|
|
// if this is an update). Inserting the remote CO (for a retry) into
|
|
// the outlog a second time closes this window.
|
|
//
|
|
if (FlagChk(ISCU_INS_OUTLOG_NEW_GUID)) {
|
|
COPY_GUID(&OldGuid, &ChangeOrder->Cmd.ChangeOrderGuid);
|
|
FrsUuidCreate(&ChangeOrder->Cmd.ChangeOrderGuid);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO Ins Outlog with new guid");
|
|
}
|
|
|
|
FStatus = OutLogInsertCo(ThreadCtx, Replica, TableCtx, ChangeOrder);
|
|
|
|
//
|
|
// Restore the original guid
|
|
//
|
|
if (FlagChk(ISCU_INS_OUTLOG_NEW_GUID)) {
|
|
COPY_GUID(&ChangeOrder->Cmd.ChangeOrderGuid, &OldGuid);
|
|
}
|
|
|
|
RESTORE_CHANGE_ORDER_STATE(ChangeOrder, SaveState, SaveFlags);
|
|
|
|
if (FStatus == FrsErrorKeyDuplicate) {
|
|
|
|
//
|
|
// Currently we can't rely on this check to filter out bogus
|
|
// asserts. Consider the following case: The same CO arrives from
|
|
// two inbound partners, A & B. CO-A completes the stage file
|
|
// fetch but was blocked from the install due to sharing violation.
|
|
// It goes thru retry and does its partner ACK and the outlog
|
|
// insert and the VV Update.
|
|
//
|
|
// Now CO-B, which was in the process queue so it got past the VV
|
|
// checks, goes to Fetch and finds the stage file already there
|
|
// then it goes thru retry with the same sharing violation (or it
|
|
// may even finish the install) and then it too tries to do the
|
|
// OUTLOG insert. It's not a recovery CO so it will Assert. We
|
|
// could test to see if the stage file was already present and
|
|
// skip the assert but that doesn't handle the case of two delete
|
|
// Change Orders since they don't have a staging file.
|
|
//
|
|
// So, until we can devise a more competent test we
|
|
// will treat it as a warning and bag the assert for now.
|
|
//
|
|
if (!RecoveryCo(ChangeOrder)) {
|
|
DPRINT(1, "++ WARNING -- Duplicate key insert into OUTLOG. Not a recovery CO\n");
|
|
}
|
|
FStatus = FrsErrorSuccess;
|
|
}
|
|
else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT1(0, "++ ERROR - Don't send %08x %08x to outbound; database error\n",
|
|
PRINTQUAD(CoCmd->FrsVsn));
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"ISCU_INS_OUTLOG failed");
|
|
}
|
|
|
|
//
|
|
// Release the lock to allow parallelism.
|
|
//
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
}
|
|
|
|
//
|
|
// ISCU_DEL_IDT_ENTRY
|
|
// Delete the IDTable record for this CO.
|
|
//
|
|
if (FlagChk(ISCU_DEL_IDT_ENTRY)) {
|
|
TableCtx = (ChangeOrder->RtCtx != NULL) ? &ChangeOrder->RtCtx->IDTable
|
|
: NULL;
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO IDTable Delete");
|
|
FStatus = DbsDeleteIDTableRecord(ThreadCtx,
|
|
Replica,
|
|
TableCtx,
|
|
&CoCmd->FileGuid);
|
|
|
|
DPRINT_FS(0,"++ ERROR - DbsDeleteIDTableRecord failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"ISCU_DEL_IDT_ENTRY failed");
|
|
|
|
//
|
|
// Remove the parent FID table entry too. Might not be there if didn't
|
|
// get to Install Rename state on the CO.
|
|
//
|
|
FRS_ASSERT(pVme != NULL); // to make prefast happy
|
|
|
|
GStatus = QHashDelete(pVme->ParentFidTable, &ChangeOrder->FileReferenceNumber);
|
|
if (GStatus != GHT_STATUS_SUCCESS ) {
|
|
DPRINT1(4, "++ WARNING: QHashDelete of ParentFidTable Entry, status: %d\n", GStatus);
|
|
}
|
|
|
|
|
|
if (CoCmdIsDirectory(CoCmd)) {
|
|
//
|
|
// If this was an aborted create there might be a dir table entry.
|
|
// Delete it and the filter table entry too.
|
|
//
|
|
TableCtx = (ChangeOrder->RtCtx != NULL) ? &ChangeOrder->RtCtx->DIRTable
|
|
: NULL;
|
|
FStatus = DbsDeleteDIRTableRecord(ThreadCtx,
|
|
Replica,
|
|
TableCtx,
|
|
&CoCmd->FileGuid);
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// If this is a remote CO then we may need to clean up the
|
|
// Journal's Dir Filter table too. If it's a local CO
|
|
// then leave it alone since the journal handles the
|
|
// creates and deletes of filter table entries by itself.
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO)) {
|
|
JrnlAcquireChildLock(Replica);
|
|
WStatus = JrnlDeleteDirFilterEntry(pVme->FilterTable,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
NULL);
|
|
JrnlReleaseChildLock(Replica);
|
|
DPRINT1_WS(4, "++ WARN - deleting dir filter entry for %ws;", CoCmd->FileName, WStatus);
|
|
}
|
|
} else {
|
|
DPRINT_FS(4,"++ WARNING - Dir table record delete failed.", FStatus);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Check if any hold issue conflict table cleanup flag is on otherwise we can
|
|
// skip getting the lock.
|
|
//
|
|
if (FlagChk(ISCU_HOLDIS_CLEANUP)) {
|
|
|
|
//
|
|
// Need the list lock to preserve lock ordering when we unidle the queue.
|
|
//
|
|
FrsRtlAcquireListLock(&FrsVolumeLayerCOList);
|
|
|
|
ChgOrdAcquireLockGuid(ChangeOrder);
|
|
|
|
//
|
|
// Don't go to the Retire Started state if this is a Retry Co.
|
|
//
|
|
if (!RetryCo) {
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, IBCO_RETIRE_STARTED);
|
|
}
|
|
|
|
//
|
|
// ISCU_AIBCO
|
|
// Find the change order in the Active inbound change order table.
|
|
// Returns a reference.
|
|
//
|
|
if (FlagChk(ISCU_AIBCO)) {
|
|
GStatus = GhtLookup(pVme->ActiveInboundChangeOrderTable,
|
|
&CoCmd->FileGuid,
|
|
TRUE,
|
|
&ActiveChangeOrder);
|
|
|
|
if (GStatus != GHT_STATUS_SUCCESS) {
|
|
DPRINT(0, "++ ERROR - Inbound ChangeOrder not found in ActiveInboundChangeOrderTable.\n");
|
|
ReleaseVmeRef(pVme);
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
FRS_ASSERT(!"Inbound CO not found in ActiveInboundChangeOrderTable");
|
|
}
|
|
|
|
if (ChangeOrder != ActiveChangeOrder) {
|
|
DPRINT(0, "++ ERROR - Inbound ChangeOrder not equal ActiveChangeOrder\n");
|
|
DPRINT(0, "++ Change order in Active Table:\n");
|
|
FRS_PRINT_TYPE(0, ActiveChangeOrder);
|
|
DPRINT(0, "++ Change order to be retired:\n");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"Inbound ChangeOrder not equal ActiveChangeOrder");
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ISCU_NC_TABLE
|
|
// Remove the entry in the Name conflict table
|
|
//
|
|
// If the reference count goes to 0, then pull the "Unblock the pVme
|
|
// process queue" flag into the change order and remove the qhash entry.
|
|
//
|
|
// NameConflictTable Entry:
|
|
// An entry in the NameConflictTable contains a reference count of the
|
|
// number of active change orders that hash to that entry and a Flags
|
|
// word that is set to COE_FLAG_VOL_COLIST_BLOCKED if the process queue
|
|
// for this volume is idled while waiting on the active change orders
|
|
// for this entry to retire. The queue is idled when the entry at the
|
|
// head of the queue hashes to this entry and so may have a conflicting
|
|
// name (hence the name, "name conflict table").
|
|
//
|
|
// The NameConflictTable can give false positives. But this is okay
|
|
// because a false positive is rare and will only idle the process queue
|
|
// until the active change order retires. Getting rid of the rare false
|
|
// positives would degrade performance. The false positives that happen
|
|
// when inserting an entry into the NameConflictTable are handled by
|
|
// using the QData field in the QHashEntry as a the reference count.
|
|
//
|
|
// But, you ask, how can there be multiple active cos hashing to this
|
|
// entry if the process queue is idled when a conflict is detected?
|
|
// Easy, I say, because the filename in the co is used to detect
|
|
// collisions while the filename in the idtable is used to reserve the
|
|
// qhash entry. Why? Well, the name morph code handles the case of a
|
|
// co's name colliding with an idtable entry. But that code wouldn't
|
|
// work if active change orders were constantly changing the idtable.
|
|
// So, the NameConflictTable synchronizes the namespace amoung active
|
|
// change orders so that the name morph code can work against a static
|
|
// namespace.
|
|
//
|
|
if (FlagChk(ISCU_NC_TABLE) &&
|
|
(ChangeOrder->NameConflictHashValue != QUADZERO) &&
|
|
(DOES_CO_REMOVE_FILE_NAME(CoCmd) || DOES_CO_DO_SIMPLE_RENAME(CoCmd))) {
|
|
|
|
PQHASH_TABLE Nct = Replica->NameConflictTable;
|
|
//
|
|
// Lock the name conflict table and find the entry for this CO
|
|
//
|
|
QHashAcquireLock(Nct);
|
|
QHashEntry = QHashLookupLock(Nct, &ChangeOrder->NameConflictHashValue);
|
|
//
|
|
// NO ENTRY! ASSERT
|
|
//
|
|
if (QHashEntry == NULL) {
|
|
DPRINT1(0, "++ ERROR - QHashLookupLock(NameConflictTable, %08x %08x) not found.\n",
|
|
PRINTQUAD(ChangeOrder->NameConflictHashValue));
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FRS_ASSERT(!"IssueCleanup: NameConflictTable entry not found");
|
|
} else {
|
|
//
|
|
// If last reference, unblock the process queue if it's blocked
|
|
// and delete the entry.
|
|
//
|
|
if ((--QHashEntry->QData) == QUADZERO) {
|
|
if (QHashEntry->Flags & ((ULONG_PTR)COE_FLAG_VOL_COLIST_BLOCKED)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
}
|
|
|
|
QHashDeleteLock(Nct, &ChangeOrder->NameConflictHashValue);
|
|
}
|
|
//
|
|
// Unlock the name conflict table and remove the CO reference.
|
|
//
|
|
QHashReleaseLock(Nct);
|
|
ChangeOrder->NameConflictHashValue = QUADZERO;
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_CHECK_ISSUE_BLOCK
|
|
// Check the queue blocked flag on the change order.
|
|
// Note - The value for RestartQueue may be overwritten below.
|
|
//
|
|
if (FlagChk(ISCU_CHECK_ISSUE_BLOCK)) {
|
|
RestartQueue = COE_FLAG_ON(ChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
if (RestartQueue) {
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_VOL_COLIST_BLOCKED);
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_AIBCO
|
|
// Drop the reference above on the Change order and remove the entry
|
|
// in the AIBCO table.
|
|
//
|
|
if (FlagChk(ISCU_AIBCO)) {
|
|
GhtDereferenceEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
|
|
//
|
|
// Take the change order out of the Active table.
|
|
//
|
|
GhtRemoveEntryByAddress(pVme->ActiveInboundChangeOrderTable,
|
|
ActiveChangeOrder,
|
|
TRUE);
|
|
}
|
|
|
|
//
|
|
// ISCU_ACTIVE_CHILD
|
|
// Check the parent entry in the ActiveChildren hash table. If there is
|
|
// a change order pending on the parent and the count of active children
|
|
// has gone to zero then we can restart the queue.
|
|
//
|
|
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_JUST_TOMBSTONE) &&
|
|
(ChangeOrder->ParentFileReferenceNumber == ZERO_FID)) {
|
|
//
|
|
// There is no active child interlock if this was a tombstone
|
|
// create and there was no parent. Barf.
|
|
//
|
|
ClearFlag(CleanUpFlags, ISCU_ACTIVE_CHILD);
|
|
}
|
|
|
|
|
|
if (FlagChk(ISCU_ACTIVE_CHILD)) {
|
|
|
|
QHashAcquireLock(ActiveChildren);
|
|
|
|
BusyParentGuid = &CoCmd->NewParentGuid;
|
|
GuidToStr(BusyParentGuid, BusyParentGuidStr);
|
|
|
|
QHashEntry = QHashLookupLock(ActiveChildren, BusyParentGuid);
|
|
if (QHashEntry != NULL) {
|
|
|
|
ULONGLONG Count;
|
|
|
|
QHashEntry->QData -= 2;
|
|
DPRINT2(4, "++ GAC Dec Count on %s, QData %08x \n",
|
|
BusyParentGuidStr, QHashEntry->QData);
|
|
|
|
PendingCoOnParent = (QHashEntry->QData & 1) != 0;
|
|
Count = QHashEntry->QData >> 1;
|
|
|
|
//
|
|
// If the count is zero and there is a blocked pending CO
|
|
// waiting on our parent to be unbusy then unblock the queue.
|
|
// Either way when the count is zero remove the entry from the
|
|
// table. Also propagate the restart queue flag forward.
|
|
//
|
|
if (Count == 0) {
|
|
if (!RestartQueue) {
|
|
RestartQueue = PendingCoOnParent;
|
|
}
|
|
QHashDeleteLock(ActiveChildren, BusyParentGuid);
|
|
DPRINT3(4, "++ GAC - ActiveChild count zero on %s, PendCoOnParent %d, Rq %d\n",
|
|
BusyParentGuidStr, PendingCoOnParent, RestartQueue);
|
|
} else {
|
|
DPRINT4(4, "++ GAC - ActiveChild count (%d) not zero on %s, PendCoOnParent %d, Rq %d\n",
|
|
Count, BusyParentGuidStr, PendingCoOnParent,
|
|
RestartQueue);
|
|
}
|
|
|
|
} else {
|
|
DPRINT1(0, "++ ERROR - GAC - Did not find entry in ActiveChildren Table for %s\n",
|
|
BusyParentGuidStr);
|
|
FRS_ASSERT(!"Did not find entry in ActiveChildren Table");
|
|
}
|
|
|
|
//
|
|
// If this CO has a old parent and a new parent (e.g. MOVE_DIR) then
|
|
// we need to unblock both parents. Check for that and unblock both.
|
|
//
|
|
|
|
if (!GUIDS_EQUAL(&CoCmd->NewParentGuid, &CoCmd->OldParentGuid)) {
|
|
BusyParentGuid = &CoCmd->OldParentGuid;
|
|
GuidToStr(BusyParentGuid, BusyParentGuidStr);
|
|
|
|
QHashEntry = QHashLookupLock(ActiveChildren, BusyParentGuid);
|
|
if (QHashEntry != NULL) {
|
|
|
|
ULONGLONG Count;
|
|
|
|
QHashEntry->QData -= 2;
|
|
DPRINT2(4, "++ GAC Dec Count on %s, QData %08x \n",
|
|
BusyParentGuidStr, QHashEntry->QData);
|
|
|
|
PendingCoOnParent = (QHashEntry->QData & 1) != 0;
|
|
Count = QHashEntry->QData >> 1;
|
|
|
|
//
|
|
// If the count is zero and there is a blocked pending CO
|
|
// waiting on our parent to be unbusy then unblock the queue.
|
|
// Either way when the count is zero remove the entry from the
|
|
// table. Also propagate the restart queue flag forward.
|
|
//
|
|
if (Count == 0) {
|
|
if (!RestartQueue) {
|
|
RestartQueue = PendingCoOnParent;
|
|
}
|
|
QHashDeleteLock(ActiveChildren, BusyParentGuid);
|
|
DPRINT3(4, "++ GAC - ActiveChild count zero on %s, PendCoOnParent %d, Rq %d\n",
|
|
BusyParentGuidStr, PendingCoOnParent, RestartQueue);
|
|
} else {
|
|
DPRINT4(4, "++ GAC - ActiveChild count (%d) not zero on %s, PendCoOnParent %d, Rq %d\n",
|
|
Count, BusyParentGuidStr, PendingCoOnParent,
|
|
RestartQueue);
|
|
}
|
|
|
|
} else {
|
|
DPRINT1(0, "++ ERROR - GAC - Did not find entry in ActiveChildren Table for %s\n",
|
|
BusyParentGuidStr);
|
|
FRS_ASSERT(!"Did not find entry in ActiveChildren Table");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Now un-idle change order process queue for this volume and drop the locks.
|
|
//
|
|
if (RestartQueue && (pVme != NULL)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO Process Q Unblock");
|
|
FrsRtlUnIdledQueueLock(&pVme->ChangeOrderList);
|
|
}
|
|
|
|
if (FlagChk(ISCU_ACTIVE_CHILD)) {
|
|
QHashReleaseLock(ActiveChildren);
|
|
}
|
|
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
|
|
} // end of if (FlagChk(ISCU_HOLDIS_CLEANUP))
|
|
|
|
|
|
if (pVme != NULL) {
|
|
ReleaseVmeRef(pVme);
|
|
}
|
|
|
|
//
|
|
// Need the list lock to preserve lock ordering when we unidle the queue.
|
|
// This serializes any insert into outlog and update of inlog.
|
|
// OutlogInserCo overwrites SequenceNumber which can confuse inlog update.
|
|
//
|
|
FrsRtlAcquireListLock(&FrsVolumeLayerCOList);
|
|
|
|
ChgOrdAcquireLockGuid(ChangeOrder);
|
|
|
|
//
|
|
// ISCU_DEC_CO_REF
|
|
// Decrement the ref count on the change order.
|
|
// Then use the ref count to gate whether or not to do the mem free ops
|
|
// and delete the inbound log record.
|
|
//
|
|
if (FlagChk(ISCU_DEC_CO_REF)) {
|
|
RefCount = DECREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder);
|
|
} else {
|
|
RefCount = GET_CHANGE_ORDER_REF_COUNT(ChangeOrder);
|
|
}
|
|
|
|
|
|
//
|
|
// ISCU_DEL_INLOG (1)
|
|
// If this CO has directory enumeration pending then do not delete it
|
|
// from the inbound log. Instead update the state and flags.
|
|
// The retry thread will later reprocess it to perform the enumeration
|
|
// and perform the final cleanup.
|
|
// The dir enum decision was made by ChgOrdUpdateIDTableRecord() above.
|
|
// Currently this is the only instance of a change order record being
|
|
// recycled for a "second pass" operation. Once the inlog record state is
|
|
// updated we can notify the INLOG retry thread.
|
|
//
|
|
if ((RefCount == 0) && FlagChk(ISCU_DEL_INLOG) && !FlagChk(ISCU_CO_ABORT)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Checking ENUM Pending");
|
|
if (CO_IFLAG_ON(ChangeOrder, CO_IFLAG_DIR_ENUM_PENDING)) {
|
|
ClearFlag(CleanUpFlags, ISCU_DEL_INLOG);
|
|
SetFlag(CleanUpFlags, ISCU_UPDATE_INLOG);
|
|
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, IBCO_ENUM_REQUESTED);
|
|
NotifyRetryThread = TRUE;
|
|
|
|
//
|
|
// This CO is now a retry change order.
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_RETRY);
|
|
|
|
//
|
|
// Prevent deletion of IDTable entry later when we retry.
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_NEW_FILE);
|
|
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISCU_DEL_INLOG (2)
|
|
// Delete the inbound log record for this CO. This could happen either on
|
|
// the main retire path if the change order propagates to the outbound log
|
|
// immediately or it could happen later when the propagation is unblocked.
|
|
//
|
|
if ((RefCount == 0) && FlagChk(ISCU_DEL_INLOG)) {
|
|
|
|
if (ChangeOrder->RtCtx != NULL) {
|
|
TableCtx = &ChangeOrder->RtCtx->INLOGTable;
|
|
pDataRecord = TableCtx->pDataRecord;
|
|
} else {
|
|
TableCtx = NULL;
|
|
pDataRecord = NULL;
|
|
}
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO INlog Delete");
|
|
|
|
//
|
|
// If a retry CO, bump the Inbound retry table seq num so the retry
|
|
// thread can detect a table change when it is re-issuing retry COs.
|
|
//
|
|
if (RetryCo) {
|
|
InterlockedIncrement(&Replica->AIRSequenceNum);
|
|
}
|
|
|
|
FStatus = DbsDeleteTableRecordByIndex(ThreadCtx,
|
|
Replica,
|
|
TableCtx,
|
|
&CoCmd->SequenceNumber,
|
|
ILSequenceNumberIndexx,
|
|
INLOGTablex);
|
|
|
|
DPRINT_FS(0,"++ ERROR - DbsDeleteTableRecordByIndex failed.", FStatus);
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
|
|
if(FStatus == FrsErrorDbWriteConflict) {
|
|
|
|
//
|
|
// We weren't able to Delete the table record,
|
|
// possibly because another thread was processing a duplicate CO
|
|
// at the same time. So let's retry this later.
|
|
//
|
|
|
|
DPRINT(0, "ISCU_DEL_INLOG (2) failed.");
|
|
NotifyRetryThread = TRUE;
|
|
} else {
|
|
|
|
//
|
|
// We failed to delete the record for some other reason.
|
|
// ASSERT!!
|
|
//
|
|
FRS_ASSERT(!"ISCU_DEL_INLOG (2) failed.");
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// The data record for the Inbound Log is the Change Order Command.
|
|
// Make the ptr NULL here so when we free the TableCtx later we don't
|
|
// also try to free the data record.
|
|
//
|
|
if (pDataRecord != NULL) {
|
|
TableCtx->pDataRecord = NULL;
|
|
//
|
|
// Clear the Jet Set/Ret Col address fields for the Change Order
|
|
// Extension buffer to prevent reuse since that buffer goes with the CO.
|
|
//
|
|
DBS_SET_FIELD_ADDRESS(TableCtx, COExtensionx, NULL);
|
|
}
|
|
} else
|
|
// ISCU_UPDATE_INLOG
|
|
if (FlagChk(ISCU_UPDATE_INLOG)) {
|
|
//
|
|
// If we aren't deleting the inlog record and the request is to update
|
|
// the state fields then do that now.
|
|
//
|
|
// The inbound log table associated with this change order may not
|
|
// be open if this is a retry operation that has been re-issued.
|
|
//
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO INlog Update");
|
|
// FRS_ASSERT(ChangeOrder->RtCtx != NULL)
|
|
if (ChangeOrder->RtCtx == NULL) {
|
|
DPRINT(5, "Using temp inlog tablectx\n");
|
|
InLogTableCtx = &TempTableCtx;
|
|
InLogTableCtx->TableType = TABLE_TYPE_INVALID;
|
|
InLogTableCtx->Tid = JET_tableidNil;
|
|
InLogTableCtx->pDataRecord = NULL;
|
|
} else {
|
|
InLogTableCtx = &ChangeOrder->RtCtx->INLOGTable;
|
|
}
|
|
|
|
if (InLogTableCtx->pDataRecord == NULL) {
|
|
|
|
jerr = DbsOpenTable(ThreadCtx,
|
|
InLogTableCtx,
|
|
Replica->ReplicaNumber,
|
|
INLOGTablex,
|
|
CoCmd);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
JrnlSetReplicaState(Replica, REPLICA_STATE_ERROR);
|
|
FStatus = DbsTranslateJetError(jerr, TRUE);
|
|
FRS_ASSERT(!"ISCU_UPDATE_INLOG - DbsOpenTable failed");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Seek to the change order record. Use the SequenceNumber
|
|
// instead of the ChangeOrderGuid because the ChangeOrderGuid
|
|
// is not unique.
|
|
//
|
|
jerr = DbsSeekRecord(ThreadCtx,
|
|
&CoCmd->SequenceNumber,
|
|
ILSequenceNumberIndexx,
|
|
InLogTableCtx);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT1_JS(0, "++ ERROR - DbsSeekRecord on %ws :",
|
|
Replica->ReplicaName->Name, jerr);
|
|
JrnlSetReplicaState(Replica, REPLICA_STATE_ERROR);
|
|
FRS_ASSERT(!"ISCU_UPDATE_INLOG - DbsSeekRecord failed");
|
|
}
|
|
|
|
//
|
|
// Update the state fields in the inbound log change order record.
|
|
//
|
|
FStatus = DbsWriteTableFieldMult(ThreadCtx,
|
|
Replica->ReplicaNumber,
|
|
InLogTableCtx,
|
|
UpdateInlogState,
|
|
ARRAY_SZ(UpdateInlogState));
|
|
DPRINT_FS(0,"++ ERROR - Update of State in Inlog record failed.", FStatus);
|
|
FRS_ASSERT(FRS_SUCCESS(FStatus) || !"ISCU_UPDATE_INLOG: UpdateInlogState failed");
|
|
|
|
DPRINT2(4, "++ Updating CO Flags for retry: %08x , CO_IFLAGS %08x \n",
|
|
CoCmd->Flags, CoCmd->IFlags);
|
|
//
|
|
// The data record for the Inbound Log is in the Change Order Command.
|
|
// Make the ptr NULL here so when we free the TableCtx below we don't
|
|
// also try to free the data record.
|
|
//
|
|
InLogTableCtx->pDataRecord = NULL;
|
|
|
|
//
|
|
// Close the inlog table.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, InLogTableCtx);
|
|
|
|
//
|
|
// Clear the Jet Set/Ret Col address fields for the Change Order
|
|
// Extension buffer to prevent reuse since that buffer goes with the CO.
|
|
//
|
|
DBS_SET_FIELD_ADDRESS(InLogTableCtx, COExtensionx, NULL);
|
|
}
|
|
|
|
//
|
|
// ISCU_DEL_RTCTX
|
|
// Free the Replica-Thread context.
|
|
//
|
|
if ((RefCount == 0) && FlagChk(ISCU_DEL_RTCTX)) {
|
|
FStatus = DbsFreeRtCtx(ThreadCtx, Replica, ChangeOrder->RtCtx, TRUE);
|
|
ChangeOrder->RtCtx = NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// ISCU_FREE_CO
|
|
// Free the change order and any duplicates. The active change order
|
|
// serves as the head of the list.
|
|
//
|
|
if ((RefCount == 0) && FlagChk(ISCU_FREE_CO)) {
|
|
|
|
//
|
|
// Pull the entry for this CO out of the inlog retry table.
|
|
// If the sequence number is zero then it never went into the Inlog.
|
|
// e.g. A control change order or a rejected change order.
|
|
//
|
|
SeqNum = (ULONGLONG) CoCmd->SequenceNumber;
|
|
if (SeqNum != QUADZERO) {
|
|
|
|
//
|
|
// Get the lock on the Active Retry table.
|
|
//
|
|
QHashAcquireLock(Replica->ActiveInlogRetryTable);
|
|
|
|
if (QHashLookupLock(Replica->ActiveInlogRetryTable, &SeqNum) != NULL) {
|
|
QHashDeleteLock(Replica->ActiveInlogRetryTable, &SeqNum);
|
|
} else {
|
|
DPRINT1(1, "++ Warning: ActiveInlogRetryTable QHashDelete error on seq num: %08x %08x\n",
|
|
PRINTQUAD(SeqNum));
|
|
}
|
|
|
|
//
|
|
// Bump the sequence number to signal that the state of the
|
|
// Inlog has changed so the retry thread can know to do a re-read
|
|
// of the inlog record if the retry thread is currently active.
|
|
// See ChgOrdRetryWorker().
|
|
//
|
|
InterlockedIncrement(&Replica->AIRSequenceNum);
|
|
|
|
QHashReleaseLock(Replica->ActiveInlogRetryTable);
|
|
}
|
|
|
|
if (ChangeOrder->Cxtion != NULL) {
|
|
DROP_CO_CXTION_COUNT(Replica, ChangeOrder, ERROR_SUCCESS);
|
|
}
|
|
FRS_ASSERT(ChangeOrder->DupCoList.Next == NULL);
|
|
|
|
#if 0
|
|
while (ChangeOrder->DupCoList.Next != NULL) {
|
|
Entry = PopEntryList(&ChangeOrder->DupCoList);
|
|
DupChangeOrder = CONTAINING_RECORD(Entry, CHANGE_ORDER_ENTRY, DupCoList);
|
|
//
|
|
// Let the replica subsystem know this change order is done. These
|
|
// change orders were never issued so their Sequence number is OK.
|
|
//
|
|
RcsInboundCommitOk(Replica, DupChangeOrder);
|
|
FrsFreeType(DupChangeOrder);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// RELEASE the lock BEFORE freeing the CO.
|
|
//
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
|
|
FrsFreeType(ChangeOrder);
|
|
} else {
|
|
//
|
|
// RELEASE the lock IF NOT freeing the CO.
|
|
//
|
|
ChgOrdReleaseLockGuid(ChangeOrder);
|
|
FrsRtlReleaseListLock(&FrsVolumeLayerCOList);
|
|
}
|
|
|
|
//
|
|
// Tell the Inlog retry thread there is work to do.
|
|
//
|
|
if (NotifyRetryThread) {
|
|
InterlockedIncrement(&Replica->InLogRetryCount);
|
|
}
|
|
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdUpdateIDTableRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Update the ID Table record for this change order. Update the DIR table
|
|
entry as well.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
Replica -- The Replica ID table to do the lookup in.
|
|
ChangeOrder-- The change order.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdUpdateIDTableRecord:"
|
|
|
|
|
|
ULONGLONG TombstoneLife;
|
|
|
|
FRS_ERROR_CODE FStatus;
|
|
ULONG GStatus;
|
|
PREPLICA_THREAD_CTX RtCtx;
|
|
PTABLE_CTX IDTableCtx, DIRTableCtx;
|
|
PCHANGE_ORDER_COMMAND CoCmd = &ChangeOrder->Cmd;
|
|
PIDTABLE_RECORD IDTableRec;
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
ULONG Len;
|
|
ULONG LocationCmd;
|
|
BOOL DeleteCo, NewFile, RemoteCo;
|
|
BOOL InsertFlag;
|
|
|
|
|
|
FRS_ASSERT(Replica != NULL);
|
|
FRS_ASSERT(ChangeOrder != NULL);
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
DeleteCo = (LocationCmd == CO_LOCATION_DELETE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT);
|
|
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
//
|
|
// The Replica Thread Ctx in the change order has all the update info.
|
|
//
|
|
RtCtx = ChangeOrder->RtCtx;
|
|
FRS_ASSERT(RtCtx != NULL);
|
|
|
|
IDTableCtx = &RtCtx->IDTable;
|
|
|
|
FRS_PRINT_TYPE(4, ChangeOrder);
|
|
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
FRS_ASSERT(IDTableRec != NULL);
|
|
|
|
if (ChangeOrder->FileReferenceNumber == ZERO_FID) {
|
|
//
|
|
// This should not occur.
|
|
//
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
DBS_DISPLAY_RECORD_SEV(0, IDTableCtx, FALSE);
|
|
FRS_ASSERT(!"ChgOrdUpdateIDTableRecord failed with zero FID");
|
|
}
|
|
|
|
//
|
|
// Update the IDTable record from the change order.
|
|
//
|
|
IDTableRec->FileID = ChangeOrder->FileReferenceNumber;
|
|
|
|
IDTableRec->ParentGuid = CoCmd->NewParentGuid;
|
|
IDTableRec->ParentFileID = ChangeOrder->NewParentFid;
|
|
|
|
IDTableRec->VersionNumber = CoCmd->FileVersionNumber;
|
|
IDTableRec->EventTime = CoCmd->EventTime.QuadPart;
|
|
IDTableRec->OriginatorGuid = CoCmd->OriginatorGuid;
|
|
IDTableRec->OriginatorVSN = CoCmd->FrsVsn;
|
|
IDTableRec->CurrentFileUsn = CoCmd->FileUsn;
|
|
IDTableRec->FileCreateTime = ChangeOrder->FileCreateTime;
|
|
IDTableRec->FileWriteTime = ChangeOrder->FileWriteTime;
|
|
IDTableRec->FileSize = CoCmd->FileSize;
|
|
|
|
//IDTableRec->FileObjID =
|
|
Len = (ULONG) CoCmd->FileNameLength;
|
|
CopyMemory(IDTableRec->FileName, CoCmd->FileName, Len);
|
|
IDTableRec->FileName[Len/sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
IDTableRec->FileIsDir = CoCmdIsDirectory(CoCmd);
|
|
IDTableRec->FileAttributes = CoCmd->FileAttributes;
|
|
|
|
IDTableRec->ReplEnabled = TRUE;
|
|
|
|
|
|
if (!DeleteCo) {
|
|
|
|
PDATA_EXTENSION_CHECKSUM CocDataChkSum, IdtDataChkSum;
|
|
PIDTABLE_RECORD_EXTENSION IdtExt;
|
|
PCHANGE_ORDER_RECORD_EXTENSION CocExt;
|
|
|
|
//
|
|
// If the Change Order has a file checksum, save it in the IDTable Record.
|
|
//
|
|
CocExt = CoCmd->Extension;
|
|
CocDataChkSum = DbsDataExtensionFind(CocExt, DataExtend_MD5_CheckSum);
|
|
|
|
if (CocDataChkSum != NULL) {
|
|
if (CocDataChkSum->Prefix.Size != sizeof(DATA_EXTENSION_CHECKSUM)) {
|
|
DPRINT1(0, "<MD5_CheckSum Size (%08x) invalid>\n",
|
|
CocDataChkSum->Prefix.Size);
|
|
}
|
|
|
|
DPRINT4(4, "NEW COC MD5: %08x %08x %08x %08x\n",
|
|
*(((ULONG *) &CocDataChkSum->Data[0])),
|
|
*(((ULONG *) &CocDataChkSum->Data[4])),
|
|
*(((ULONG *) &CocDataChkSum->Data[8])),
|
|
*(((ULONG *) &CocDataChkSum->Data[12])));
|
|
|
|
//
|
|
// We have a change order data checksum. Look for
|
|
// a data checksum entry in the IDTable record.
|
|
//
|
|
IdtExt = &IDTableRec->Extension;
|
|
IdtDataChkSum = DbsDataExtensionFind(IdtExt, DataExtend_MD5_CheckSum);
|
|
|
|
if (IdtDataChkSum != NULL) {
|
|
if (IdtDataChkSum->Prefix.Size != sizeof(DATA_EXTENSION_CHECKSUM)) {
|
|
DPRINT1(0, "<MD5_CheckSum Size (%08x) invalid>\n",
|
|
IdtDataChkSum->Prefix.Size);
|
|
}
|
|
|
|
DPRINT4(4, "OLD IDT MD5: %08x %08x %08x %08x\n",
|
|
*(((ULONG *) &IdtDataChkSum->Data[0])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[4])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[8])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[12])));
|
|
|
|
} else {
|
|
//
|
|
// Init the extension buffer.
|
|
//
|
|
DPRINT(4, "OLD IDT MD5: Not present\n");
|
|
DbsDataInitIDTableExtension(IdtExt);
|
|
IdtDataChkSum = &IdtExt->DataChecksum;
|
|
}
|
|
|
|
//
|
|
// Copy the MD5 checksum into the IDTable Record.
|
|
//
|
|
if (IdtDataChkSum != NULL) {
|
|
CopyMemory(IdtDataChkSum->Data, CocDataChkSum->Data, MD5DIGESTLEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// This CO may have been on a New File. Always clear the flag here.
|
|
//
|
|
NewFile = IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS);
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS);
|
|
|
|
if (CO_NEW_FILE(LocationCmd)) {
|
|
//
|
|
// A staging file for the local change order could not be created.
|
|
// We are giving up but we need any following changes to the file
|
|
// to be marked as a "create" so that future updates or renames
|
|
// will create the remote file. Otherwise, the remote side may attempt
|
|
// to "rename" a non-existant file.
|
|
//
|
|
// Note: if this CO is for a dir MOVEIN then the affect of aborting
|
|
// will be to skip the ENUM of the subtree. Not the best result.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_STAGE_ABORTED)) {
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_CREATE_DEFERRED);
|
|
}
|
|
else
|
|
if (!RemoteCo && CO_MOVEIN_FILE(LocationCmd)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "MOVEIN Detected");
|
|
//
|
|
// This is a MOVEIN change order. If this is a directory then
|
|
// we need to enumerate the directory. Mark the IDTable entry as
|
|
// ENUM_PENDING. Ditto for the change order.
|
|
//
|
|
if (CoCmdIsDirectory(CoCmd)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "MOVEIN DIR Detected");
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_ENUM_PENDING);
|
|
SET_CO_IFLAG(ChangeOrder, CO_IFLAG_DIR_ENUM_PENDING);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// File is still using its temporary name. Rename the file when
|
|
// retiring the next change order for the file. Retry this
|
|
// change order in the interrim.
|
|
//
|
|
if ((!DeleteCo) &&
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_RENAME)) {
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_RENAME_DEFERRED);
|
|
}
|
|
|
|
if (DeleteCo) {
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_DELETE)) {
|
|
//
|
|
// Remember that we still need to delete this file/dir.
|
|
//
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED);
|
|
} else {
|
|
//
|
|
// Clear the flag if we don't need to delete this file/dir.
|
|
//
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED);
|
|
}
|
|
//
|
|
// Mark the file as deleted, clear the FID.
|
|
// Save the time in the TombStone garbage collect field.
|
|
//
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_DELETED);
|
|
TombstoneLife = (ULONGLONG) ConfigRecord->TombstoneLife; // days
|
|
TombstoneLife = TombstoneLife * (24 * 60 * 60); // convert to sec.
|
|
TombstoneLife = TombstoneLife * (10*1000*1000); // convert to 100 ns units.
|
|
TombstoneLife = TombstoneLife + CoCmd->EventTime.QuadPart;
|
|
COPY_TIME(&IDTableRec->TombStoneGC, &TombstoneLife);
|
|
} else
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION)) {
|
|
//
|
|
// This file or dir is coming back from the dead.
|
|
//
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_DELETED);
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED);
|
|
TombstoneLife = QUADZERO;
|
|
COPY_TIME(&IDTableRec->TombStoneGC, &TombstoneLife);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Reviving IDTable Rec");
|
|
}
|
|
|
|
//
|
|
// Update the ID Table record.
|
|
//
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION)) {
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, FALSE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
} else {
|
|
DBS_DISPLAY_RECORD_SEV(5, IDTableCtx, FALSE);
|
|
}
|
|
|
|
FStatus = DbsUpdateTableRecordByIndex(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&IDTableRec->FileGuid,
|
|
GuidIndexx,
|
|
IDTablex);
|
|
CLEANUP_FS(0,"++ ERROR - DbsUpdateTableRecordByIndex failed.", FStatus, ERROR_RETURN);
|
|
|
|
//
|
|
// Update the volume parent file ID table for:
|
|
// 1. Remote Change Orders that perform either a MOVEDIR or a MOVERS or
|
|
// 2. Local change orders that are generated by a MOVEIN sub-dir operation.
|
|
//
|
|
// Now that the file is installed we could start seeing local COs
|
|
// for it. For remote CO new file creates this update doesn't occur until
|
|
// the rename install of the pre-install file is done. See DbsRenameFid().
|
|
//
|
|
GStatus = GHT_STATUS_SUCCESS;
|
|
|
|
if (RemoteCo) {
|
|
|
|
if (CO_MOVE_RS_OR_DIR(LocationCmd)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "MOVEDIR Par Fid Update");
|
|
GStatus = QHashUpdate(Replica->pVme->ParentFidTable,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
&IDTableRec->ParentFileID,
|
|
Replica->ReplicaNumber);
|
|
}
|
|
} else {
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_MOVEIN_GEN)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "MOVEIN Par Fid Update");
|
|
GStatus = QHashUpdate(Replica->pVme->ParentFidTable,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
&IDTableRec->ParentFileID,
|
|
Replica->ReplicaNumber);
|
|
}
|
|
}
|
|
|
|
if (GStatus != GHT_STATUS_SUCCESS ) {
|
|
DPRINT1(0, "++ WARNING - QHashUpdate on parent FID table status: %d\n", GStatus);
|
|
}
|
|
|
|
//
|
|
// Update the DIR Table record.
|
|
//
|
|
// Currently at startup the Journal parent filter table is constructed
|
|
// from the DIRTable. If we were to crash between the update of the IDTable
|
|
// above and the update below it is possible that the change order retry
|
|
// at the next startup could fail to correct this problem. This would leave
|
|
// a missing dir entry in the Journal Filter table.
|
|
// This problem should get fixed as part of the support for a guest
|
|
// FRS member since the content of the dir tree will be sparse.
|
|
//
|
|
if (CoCmdIsDirectory(CoCmd)) {
|
|
DIRTableCtx = &RtCtx->DIRTable;
|
|
|
|
//
|
|
// Insert or update the DIR table and the journal filter table.
|
|
//
|
|
if (!DeleteCo) {
|
|
FStatus = ChgOrdInsertDirRecord(ThreadCtx,
|
|
DIRTableCtx,
|
|
IDTableRec,
|
|
ChangeOrder,
|
|
Replica,
|
|
FALSE);
|
|
//
|
|
// DirTable entry may not exist if this is a reanimation
|
|
// CO. For all other COs it should exist.
|
|
//
|
|
if ((FStatus == FrsErrorNotFound) &&
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION)) {
|
|
|
|
FStatus = ChgOrdInsertDirRecord(ThreadCtx,
|
|
DIRTableCtx,
|
|
IDTableRec,
|
|
ChangeOrder,
|
|
Replica,
|
|
TRUE);
|
|
}
|
|
|
|
CLEANUP_FS(0,"++ ERROR - ChgOrdInsertDirRecord failed.", FStatus, ERROR_RETURN);
|
|
} else if (!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
|
|
DPRINT(4, "++ Deleting the DirTable entry -----------\n");
|
|
//
|
|
// Note: We don't have to update the Volume Filter Table here
|
|
// because that was handled by the journal code when the
|
|
// delete change order was processed for the directory. Even if
|
|
// this was a remote co where we issue the directory delete we still
|
|
// see an NTFS journal close record because the file isn't deleted
|
|
// until the last handle is closed. Our attempt to filter that
|
|
// USN record fails so the delete CO is processed by the journal
|
|
// code and later discarded by change order accept.
|
|
//
|
|
FStatus = DbsDeleteDIRTableRecord(ThreadCtx,
|
|
Replica,
|
|
DIRTableCtx,
|
|
&IDTableRec->FileGuid);
|
|
//
|
|
// If this update is on a new file entry there won't be a DirTable entry.
|
|
// e.g. A Dir delete arrives before the create and we just create
|
|
// a tombstone.
|
|
//
|
|
if (!FRS_SUCCESS(FStatus) && !NewFile) {
|
|
DPRINT_FS(0,"++ ERROR - DbsDeleteTableRecordByIndex failed.", FStatus);
|
|
//
|
|
// Note: if we crashed in the middle of a retire the record
|
|
// would not be there. See comment above re: making the whole
|
|
// retire a single transaction.
|
|
}
|
|
}
|
|
}
|
|
|
|
FStatus = FrsErrorSuccess;
|
|
|
|
ERROR_RETURN:
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
JrnlSetReplicaState(Replica, REPLICA_STATE_ERROR);
|
|
Replica->FStatus = FStatus; // note: not thread safe
|
|
}
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdReadIDRecordNewFile(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PTABLE_CTX IDTableCtxNC,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL *IDTableRecExists
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Create an IDTable entry for a new file. Also called when the flag
|
|
IDREC_FLAGS_NEW_FILE_IN_PROGRESS is set to perform some re-init.
|
|
|
|
This is for the change order from the replica set
|
|
specified by the ReplicaNumber. If there is no ID Table record for
|
|
this file and the location operation is a create or a movein then
|
|
open the file and get or set the object ID on the file. In the case
|
|
of no ID Table record we init one here, all except a few fields for
|
|
remote change orders.
|
|
|
|
The volume monitor entry in the Replica struct provides the volume
|
|
handle for the open. On return the IDTable is closed.
|
|
|
|
If this turns out to be a new file or a reanimation of an old file then
|
|
IDTableCtxNC is used to do an ID Table lookup on the Parent Guid/Name
|
|
Index to check for a potential name conflict.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtx -- The table context containing the ID Table record.
|
|
IDTableCtxNC -- The table context containing the ID Table record for the Name Conflict.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica ID table to do the lookup in.
|
|
IDTableRecExists - Set to TRUE if the idtable record exists. The
|
|
caller then knows that the idtable entry shouldn't
|
|
be inserted.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
FrsErrorSuccess - IDTable record was found. Data returned.
|
|
|
|
FrsErrorNotFound - IDTable record was not found so treat as new file.
|
|
|
|
FrsErrorNameMorphConflict - This is like FrsErrorNotFound, i.e. it is
|
|
a create or reanimate (i.e. tombstone
|
|
found) but there is a parent guid/name conflict with
|
|
another entry in the IDTable. Check for a Tunneled
|
|
Object ID to see if this is a bogus conflict.
|
|
|
|
FrsErrorTunnelConflict - OID got tunneled so this is really like FrsErrorSuccess
|
|
except the ChgOrdAccept code needs to restart the processing
|
|
of this CO so the proper HoldIssue interlock checks get made.
|
|
|
|
For other error status return codes see description for ChgOrdReadIDRecord().
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReadIDRecordNewFile:"
|
|
|
|
JET_ERR jerr;
|
|
DWORD WStatus;
|
|
ULONG FStatus, Status, FStatus2;
|
|
ULONG LocationCmd;
|
|
ULONG ReplicaNumber;
|
|
HANDLE FileHandle;
|
|
BOOL MorphGenCo, ExistingOid;
|
|
BOOL LocalCo;
|
|
PIDTABLE_RECORD IDTableRec;
|
|
PCHANGE_ORDER_COMMAND CoCmd;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// IDTable record not found. See if change order is a create or a Movein.//
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ReplicaNumber = Replica->ReplicaNumber;
|
|
CoCmd = &ChangeOrder->Cmd;
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
|
|
FStatus = FrsErrorNotFound;
|
|
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
|
|
if (CO_NEW_FILE(LocationCmd)) {
|
|
|
|
//
|
|
// New file create. Check if name conflict would occur.
|
|
//
|
|
FStatus = ChgOrdCheckNameMorphConflict(ThreadCtx,
|
|
IDTableCtxNC,
|
|
ReplicaNumber,
|
|
ChangeOrder);
|
|
|
|
if (FStatus == FrsErrorNameMorphConflict) {
|
|
DPRINT(0,"++ NM: Possible Name Morph Conflict on new file\n");
|
|
DBS_DISPLAY_RECORD_SEV(4, IDTableCtxNC, TRUE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Poss Morph Conflict - cre");
|
|
} else
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
//
|
|
// Return not found so caller will treat the CO as a new file.
|
|
//
|
|
FStatus = FrsErrorNotFound;
|
|
|
|
} else {
|
|
DPRINT_FS(0,"++ NM: WARNING - Unexpected result from ChgOrdCheckNameMorphConflict.", FStatus);
|
|
return FStatus;
|
|
}
|
|
} else
|
|
|
|
if ((!LocalCo || MorphGenCo) &&
|
|
((LocationCmd == CO_LOCATION_DELETE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT))) {
|
|
//
|
|
// Return not found for remote (or name morph gened) co deletes so
|
|
// the tombstone gets created. Needed to make name morphing work.
|
|
// Load a fake value for the File ID since this is an IDTable
|
|
// index and there is no actual file. In the case of a local Co that
|
|
// generates a name conflict we will use its FID.
|
|
//
|
|
if (ChangeOrder->FileReferenceNumber == ZERO_FID) {
|
|
FrsInterlockedIncrement64(ChangeOrder->FileReferenceNumber,
|
|
GlobSeqNum,
|
|
&GlobSeqNumLock);
|
|
}
|
|
|
|
IDTableRec->FileID = ChangeOrder->FileReferenceNumber;
|
|
|
|
FStatus = FrsErrorNotFound;
|
|
|
|
} else
|
|
|
|
if (!LocalCo && (LocationCmd == CO_LOCATION_MOVEDIR)) {
|
|
//
|
|
// MOVERS: need to handle MOVERS too when that code is implemented
|
|
//
|
|
// A MoveDir can show up if a file is created before we VVJoin with
|
|
// an inbound partner and then during the VVJoin a MoveDir is done
|
|
// on the file. The MoveDir on the file causes the VVJoin scan to
|
|
// skip over the file since it knows the MoveDir CO will get sent
|
|
// when we rescan the outlog. So in this case we see the MoveDir
|
|
// but we have not seen the original create for the file.
|
|
// Since we can't be sure, treat it as a create and be happy.
|
|
// BTW: It should be marked as an out of order CO.
|
|
//
|
|
SET_CO_LOCATION_CMD(*CoCmd, Command, CO_LOCATION_CREATE);
|
|
LocationCmd = CO_LOCATION_CREATE;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SetFlag(CoCmd->ContentCmd, USN_REASON_FILE_CREATE);
|
|
FStatus = FrsErrorNotFound;
|
|
} else
|
|
|
|
if (LocalCo && (LocationCmd == CO_LOCATION_MOVEDIR)) {
|
|
//
|
|
// We come here in the case of a local Co that is a movedir.
|
|
// In the case of the following sequence where the object ID arrived
|
|
// on the new file (i.e. NEW FID) via the tunnel cache and the tunnel
|
|
// cache hit was triggered by a MOVEDIR on the file then we need
|
|
// to handle this by calling ChgOrdCheckAndFixTunnelConflict() later on.
|
|
// The sequence is:
|
|
//
|
|
// A. create \BankFile\1000013106\100000497\1597.TXT
|
|
//
|
|
// B. delete \BankFile\1000013106\100001385\1613.TXT
|
|
//
|
|
// C. rename \BankFile\1000013106\100000497\1597.TXT TO
|
|
// \BankFile\1000013106\100001385\1613.TXT
|
|
//
|
|
// By the time we process CO-A the OID tunneling from 1613 to 1597 will
|
|
// already be complete (aging cache delay). So CO-A is rejected because
|
|
// of a tunneling conflict. CO-B is processed as usual. CO-C is then
|
|
// treated as a movedir so we are here. It renames the file across
|
|
// parent dirs.
|
|
//
|
|
FStatus = FrsErrorNotFound;
|
|
|
|
} else
|
|
|
|
if (LocationCmd == CO_LOCATION_NO_CMD) {
|
|
//
|
|
// An update CO could arrive out of order relative to its create
|
|
// if the create got delayed by a sharing violation. We don't
|
|
// want to lose the update so we make it look like a create.
|
|
// This also handles the case of delete change orders generated
|
|
// by name morph conflicts in which a rename arrives for a
|
|
// nonexistent file. Also above MOVEDIR example applies regarding
|
|
// Update during a VVJoin.
|
|
//
|
|
|
|
//
|
|
// if (!LocalCo &&
|
|
// (LocationCmd == CO_LOCATION_NO_CMD) &&
|
|
// BooleanFlagOn(CoCmd->ContentCmd, USN_REASON_RENAME_NEW_NAME)) {
|
|
//
|
|
// Looks like a bare rename. Probably originated as a MorphGenCo
|
|
// so turn it into a create.
|
|
//
|
|
SET_CO_LOCATION_CMD(*CoCmd, Command, CO_LOCATION_CREATE);
|
|
LocationCmd = CO_LOCATION_CREATE;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SetFlag(CoCmd->ContentCmd, USN_REASON_FILE_CREATE);
|
|
FStatus = FrsErrorNotFound;
|
|
|
|
} else {
|
|
//
|
|
// The IDTable entry is deleted if a file disappears early in the
|
|
// change order processing. The change order for the delete is then
|
|
// rejected by returning an error from this function.
|
|
//
|
|
FStatus = FrsErrorInvalidChangeOrder;
|
|
|
|
if (LocationCmd != CO_LOCATION_DELETE &&
|
|
LocationCmd != CO_LOCATION_MOVEOUT) {
|
|
DPRINT(0, "++ WARN - IDTable record not found and Location cmd not create or movein\n");
|
|
FRS_PRINT_TYPE(1, ChangeOrder);
|
|
}
|
|
return FStatus;
|
|
}
|
|
|
|
//
|
|
// FStatus is either FrsErrorNameMorphConflict or FrsErrorNotFound
|
|
// at this point.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, IDTableCtx);
|
|
//
|
|
// New fILE. Initialize an IDTable Entry for it.
|
|
// Caller will decide if IDTable gets updated.
|
|
//
|
|
ExistingOid = PreserveFileOID;
|
|
WStatus = DbsInitializeIDTableRecord(IDTableCtx,
|
|
NULL,
|
|
Replica,
|
|
ChangeOrder,
|
|
CoCmd->FileName,
|
|
&ExistingOid);
|
|
if (LocalCo) {
|
|
//
|
|
// Get the File's current USN so we can check for consistency later
|
|
// when the change order is about to be sent to an outbound partner.
|
|
// Put the FileGuid for the new local file in the change order.
|
|
//
|
|
CoCmd->FileUsn = IDTableRec->CurrentFileUsn;
|
|
CoCmd->FileGuid = IDTableRec->FileGuid;
|
|
//
|
|
// Return the FileSize in the Local Change order.
|
|
//
|
|
CoCmd->FileSize = IDTableRec->FileSize;
|
|
} else {
|
|
//
|
|
// For remote change order, set the Parent FID from the NewParent Fid.
|
|
//
|
|
ChangeOrder->ParentFileReferenceNumber = ChangeOrder->NewParentFid;
|
|
}
|
|
|
|
//
|
|
// The file was deleted before we could get to it; ignore change order
|
|
//
|
|
if (WIN_NOT_FOUND(WStatus)) {
|
|
// FStatus = FrsErrorInvalidChangeOrder;
|
|
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_DELETE);
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
if (ExistingOid && WIN_SUCCESS(WStatus)) {
|
|
FStatus2 = ChgOrdCheckAndFixTunnelConflict(ThreadCtx,
|
|
IDTableCtx,
|
|
IDTableCtxNC,
|
|
Replica,
|
|
ChangeOrder,
|
|
IDTableRecExists);
|
|
if (FStatus2 == FrsErrorTunnelConflict) {
|
|
//
|
|
// If the OID got tunneled to this new file then any Name Morph
|
|
// conflict detected above is bogus.
|
|
//
|
|
if (FStatus == FrsErrorNameMorphConflict) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "No Morph Conflict - Tunnel");
|
|
}
|
|
return FrsErrorTunnelConflict;
|
|
|
|
} else
|
|
|
|
if (FStatus2 != FrsErrorSuccess) {
|
|
FStatus = FStatus2;
|
|
return FStatus;
|
|
}
|
|
}
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// Initialize the JetSet/RetCol arrays and data record buffer
|
|
// addresses to read and write the fields of the data record.
|
|
//
|
|
DbsSetJetColSize(IDTableCtx);
|
|
DbsSetJetColAddr(IDTableCtx);
|
|
|
|
//
|
|
// Update the JetSet/RetCol arrays for variable len fields.
|
|
//
|
|
Status = DbsAllocRecordStorage(IDTableCtx);
|
|
if (!NT_SUCCESS(Status)) {
|
|
DPRINT_NT(0, "++ ERROR - DbsAllocRecordStorage failed to alloc buffers.", Status);
|
|
return FrsErrorResource;
|
|
}
|
|
|
|
//
|
|
// FStatus is either FrsErrorNameMorphConflict
|
|
// or FrsErrorNotFound
|
|
// or FrsErrorTunnelConflict
|
|
//
|
|
// Set new file flag so a crashed CO can restart correctly.
|
|
// If we crash and later the creating change order gets rejected
|
|
// when reprocessed at recovery this flag is used by the tombstone
|
|
// delete code to clean out the IDTable entry. A corrected tunnel
|
|
// conflict means the file already exists so don't set new file flag.
|
|
//
|
|
if (FStatus != FrsErrorTunnelConflict) {
|
|
SetIdRecFlag(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS);
|
|
}
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
//
|
|
// Couldn't construct the entry. Probably a local change order and we
|
|
// couldn't open the file to get the required info or set the object ID.
|
|
//
|
|
FStatus = FrsErrorAccess;
|
|
return FStatus;
|
|
}
|
|
|
|
ULONG
|
|
ChgOrdReadIDRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PTABLE_CTX IDTableCtxNC,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL *IDTableRecExists
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Read the ID Table record for this change order from the replica set
|
|
specified by the ReplicaNumber. If there is no ID Table record for
|
|
this file and the location operation is a create or a movein then
|
|
open the file and get or set the object ID on the file. In the case
|
|
of no ID Table record we init one here, all except a few fields for
|
|
remote change orders.
|
|
|
|
The volume monitor entry in the Replica struct provides the volume
|
|
handle for the open. On return the IDTable is closed.
|
|
|
|
If this turns out to be a new file or a reanimation of an old file then
|
|
IDTableCtxNC is used to do an ID Table lookup on the Parent Guid/Name
|
|
Index to check for a potential name conflict.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtx -- The table context containing the ID Table record.
|
|
IDTableCtxNC -- The table context containing the ID Table record for the Name Conflict.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica ID table to do the lookup in.
|
|
IDTableRecExists - Set to TRUE if the idtable record exists. The
|
|
caller then knows that the idtable entry shouldn't
|
|
be inserted.
|
|
David, please review -- should the idtable entry be updated in this
|
|
case?
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
FrsErrorSuccess - IDTable record was found. Data returned.
|
|
|
|
FrsErrorInvalidChangeOrder - IDTable record not found and the change order
|
|
is not a create or movein.
|
|
|
|
FrsErrorNotFound - IDTable record was not found but this is a create
|
|
or movein change order so we constructed what we could
|
|
based on whether the CO is local or remote.
|
|
|
|
FrsErrorNameMorphConflict - This is like FrsErrorNotFound, i.e. it is
|
|
a create or reanimate (i.e. tombstone
|
|
found) but there is a parent guid/name conflict with
|
|
another entry in the IDTable. Data for this conflicting
|
|
entry is returned in IDTableCtxNC.
|
|
|
|
Note: This can also be returned in the event of a
|
|
reanimation or a rename with a conflict on the target.
|
|
|
|
FrsErrorTunnelConflict - OID got tunneled so this is really like FrsErrorSuccess
|
|
except the ChgOrdAccept code needs to restart the processing
|
|
of this CO so the proper HoldIssue interlock checks get made.
|
|
|
|
|
|
Any other error is a failure.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdReadIDRecord:"
|
|
|
|
|
|
USN CurrentFileUsn;
|
|
ULONGLONG CurrParentFid;
|
|
FILE_NETWORK_OPEN_INFORMATION FileNetworkOpenInfo;
|
|
|
|
JET_ERR jerr, jerr1;
|
|
DWORD WStatus, WStatus1;
|
|
ULONG FStatus, Status, GStatus, FStatus2;
|
|
ULONG LocationCmd;
|
|
PVOID pKey;
|
|
PVOID KeyArray[2];
|
|
ULONG IndexCode;
|
|
ULONG ReplicaNumber;
|
|
HANDLE FileHandle;
|
|
LONG RStatus;
|
|
BOOL MorphGenCo, RetryCo, ExistingOid;
|
|
|
|
PIDTABLE_RECORD IDTableRec;
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PCHANGE_ORDER_COMMAND CoCmd;
|
|
|
|
BOOL LocalCo;
|
|
|
|
|
|
ReplicaNumber = Replica->ReplicaNumber;
|
|
CoCmd = &ChangeOrder->Cmd;
|
|
LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
*IDTableRecExists = FALSE;
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
RetryCo = BooleanFlagOn(CoCmd->Flags, CO_FLAG_RETRY);
|
|
|
|
//
|
|
// Read the IDTable Record for this file. If a local ChangeOrder then
|
|
// do the lookup by FID and if remote do it by Guid.
|
|
// Insert the GUIDs for originator, File, Old and new parent.
|
|
//
|
|
jerr = DbsOpenTable(ThreadCtx, IDTableCtx, ReplicaNumber, IDTablex, NULL);
|
|
CLEANUP_JS(0, "IDTable open failed.", jerr, RETURN_JERR);
|
|
|
|
if (!LocalCo || MorphGenCo) {
|
|
pKey = (PVOID)&CoCmd->FileGuid;
|
|
IndexCode = GuidIndexx;
|
|
} else {
|
|
pKey = (PVOID)&ChangeOrder->FileReferenceNumber;
|
|
IndexCode = FileIDIndexx;
|
|
}
|
|
|
|
|
|
jerr = DbsReadRecord(ThreadCtx, pKey, IndexCode, IDTableCtx);
|
|
|
|
//
|
|
// If no record there then we have a new object, file/dir.
|
|
//
|
|
if (jerr == JET_errRecordNotFound) {
|
|
FStatus = ChgOrdReadIDRecordNewFile(ThreadCtx,
|
|
IDTableCtx,
|
|
IDTableCtxNC,
|
|
ChangeOrder,
|
|
Replica,
|
|
IDTableRecExists);
|
|
|
|
if ((FStatus == FrsErrorNotFound) ||
|
|
//
|
|
// For tunnel conflict, Fid to Guid xlate is now updated, start over.
|
|
//
|
|
(FStatus == FrsErrorTunnelConflict) ||
|
|
(FStatus == FrsErrorNameMorphConflict)) {
|
|
return FStatus;
|
|
}
|
|
|
|
goto RETURN_ERROR;
|
|
}
|
|
*IDTableRecExists = TRUE;
|
|
|
|
//
|
|
// Bail if some error other than record not found.
|
|
//
|
|
CLEANUP_JS(4, "IDTable record not found.", jerr, RETURN_JERR);
|
|
|
|
|
|
//
|
|
// We found the record in the table. Fill in the remaining local
|
|
// fields in the change order entry from the IDTable entry
|
|
// if this is a Remote Change Order.
|
|
//
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
|
|
//
|
|
// Reflect state of IDREC_FLAGS_DELETE_DEFERRED in the change order.
|
|
// See related comment below on IDREC_FLAGS_RENAME_DEFERRED.
|
|
//
|
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_NEED_DELETE);
|
|
// SUDARC-DEV - Comment out this part so that we preserve the flags in the idtable
|
|
// record.
|
|
// ClearIdRecFlag(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IDREC_FLAGS_DELETE_DEFERRED set");
|
|
}
|
|
|
|
//
|
|
// If the IDREC_FLAGS_NEW_FILE_IN_PROGRESS flag is set then this or some
|
|
// other CO did not finish. There are a few ways in which this could happen.
|
|
//
|
|
// 1. A local or remote CO was issued and the IDTable record created but
|
|
// the system crashed before the issued CO went thru retire or retry paths
|
|
// to clear the NEW_FILE_IN_PROGRESS Flag.
|
|
//
|
|
// 2. A remote CO that fails to fetch the file (e.g. cxtion unjoins) will
|
|
// go thru the retry path in the IBCO_FETCH_RETRY state. This leaves the
|
|
// NEW_FILE_IN_PROGRESS Flag set because another remote CO from a different
|
|
// inbound partner could arrive and it should be treated as a new file
|
|
// by reconcile (since the first inbound partner may never reconnect).
|
|
//
|
|
// 3. A local CO gets thru the retry path in the IBCO_STAGING_RETRY state
|
|
// because a sharing violation has prevented us from opening the file to
|
|
// generate the staging file. We may be giving up for now or we may be
|
|
// shutting down. Either way the Local CO is in the inbound log so the
|
|
// journal commit point has advanced past the USN record that gave rise
|
|
// to the CO. On journal restart we will not process that USN record again.
|
|
// But the CO saved in the inbound log does NOT have the file ID for the
|
|
// file. This is kept in the IDTable record and used in the Guid to Fid
|
|
// translation to reconstruct the FID. Actually this case is really the
|
|
// local CO varient of case 1 since, for local COs in the staging retry
|
|
// state, the retry path will set the IDREC_FLAGS_CREATE_DEFERRED flag
|
|
// in the IDTable record and clear the IDREC_FLAGS_NEW_FILE_IN_PROGRESS bit.
|
|
//
|
|
// 4. A remote CO on a new-file-create fails to install because of a disk
|
|
// full condition. The initial ID table contents will have matching version
|
|
// info so on retry the CO is rejected for sameness. The NEW_FILE_IN_PROGRESS
|
|
// flag ensures that the CO gets accepted.
|
|
//
|
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS)) {
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "IDREC_FLAGS_NEW_FILE_IN_PROGRESS");
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, TRUE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
|
|
//
|
|
// If the CO came from the INlog as a recovery CO then we did a Guid
|
|
// to FID translation earlier so the FID should be valid.
|
|
// If this is a remote CO then a new pre-install file may need to
|
|
// be created if startup recovery removed it.
|
|
//
|
|
// Even though IDREC_FLAGS_NEW_FILE_IN_PROGRESS is set we still need
|
|
// to check for the presence of the pre-install file.
|
|
// The scenario is:
|
|
// If a remote CO arrives and creates a pre-install file but we crash
|
|
// before writing the INLOG record (say the disk was full) then when we
|
|
// restart the preinstall file cleanup code will delete the preinstall
|
|
// file since there is no change order for it in the inlog. Later when
|
|
// the upstream partner re-sends the CO we end up here with a FID in the
|
|
// IDTable for a deleted file. If we don't check for it then the CO
|
|
// will fetch the staging file but will then fail to install the CO
|
|
// because the pre-install file is gone. The CO will then enter an
|
|
// install retry loop.
|
|
//
|
|
if (!LocalCo && (IDTableRec->FileID != ZERO_FID)) {
|
|
FileHandle = INVALID_HANDLE_VALUE;
|
|
WStatus = FrsOpenSourceFileById(&FileHandle,
|
|
&FileNetworkOpenInfo,
|
|
NULL,
|
|
Replica->pVme->VolumeHandle,
|
|
&IDTableRec->FileID,
|
|
FILE_ID_LENGTH,
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
if (WIN_SUCCESS(WStatus) &&
|
|
WIN_SUCCESS(FrsReadFileParentFid(FileHandle, &CurrParentFid)) &&
|
|
(CurrParentFid == Replica->PreInstallFid)) {
|
|
|
|
NOTHING;
|
|
} else {
|
|
//
|
|
// Either can't access the file or it's not in pre-install dir
|
|
//
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "Force create of new Pre-Install file", WStatus);
|
|
IDTableRec->FileID = ZERO_FID;
|
|
ChangeOrder->FileReferenceNumber = ZERO_FID;
|
|
}
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
}
|
|
|
|
//
|
|
// Treat this CO like a new file.
|
|
//
|
|
FStatus = ChgOrdReadIDRecordNewFile(ThreadCtx,
|
|
IDTableCtx,
|
|
IDTableCtxNC,
|
|
ChangeOrder,
|
|
Replica,
|
|
IDTableRecExists);
|
|
|
|
|
|
|
|
if ((FStatus == FrsErrorNotFound) ||
|
|
//
|
|
// For tunnel conflict, Fid to Guid xlate is now updated, start over.
|
|
//
|
|
(FStatus == FrsErrorTunnelConflict) ||
|
|
(FStatus == FrsErrorNameMorphConflict)) {
|
|
return FStatus;
|
|
}
|
|
|
|
goto RETURN_ERROR;
|
|
}
|
|
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, IDTableCtx);
|
|
|
|
|
|
//
|
|
// If the IDTable record is marked deleted and
|
|
// o the incoming CO is a delete or a moveout then continue processing
|
|
// the delete CO because the event time / version info may be more
|
|
// recent then what we already have recorded in our IDTable entry.
|
|
// This is necessary because of the following case. Consider three
|
|
// members A, B and C (all disconnected at the moment). At time T=0
|
|
// MA deletes file foo, At time T=5 MB updates file foo, and at time
|
|
// T=10 MC deletes file foo. Depending on the topology and therefor
|
|
// the order of arrival of the COs from A, B and C a given machine
|
|
// could decide to reanimate foo if we didn't process the second delete
|
|
// CO. E.G. if the arrival order was CoA, CoC, CoB foo would get
|
|
// reanimated because we ignored Coc. If the arrival order was Coc,
|
|
// CoA, CoB we would not reanimate foo since the event time on CoC is
|
|
// more recent that the time on CoB. Therefore send the delete COs
|
|
// on to reconcile to decide.
|
|
//
|
|
// o this CO is an update, rename, ... then transform it into a Create
|
|
// so the file can come back.
|
|
//
|
|
// Make the IDTable record FID zero here rather than using zero for all
|
|
// deleted entries which would create a duplicate key in the DB and make
|
|
// the index less balanced.
|
|
//
|
|
// Depending on the execution timing of local and remote COs where
|
|
// one is a delete another could well be pending while the delete
|
|
// is being performed. Reconcile will decide if we reanimate the file.
|
|
//
|
|
// Skip this test if this is a Morph Generated CO because of the following:
|
|
//
|
|
// 1. A remote co for an update comes in for a tombstoned idtable entry.
|
|
//
|
|
// 2. The remote co becomes a reanimate co.
|
|
//
|
|
// 3. The reanimate remote co loses a name conflict to another idtable entry.
|
|
//
|
|
// 4. A MorphGenCo for a local delete is generated for the
|
|
// reanimate remote co that lost the name conflict.
|
|
//
|
|
// 5. The MorphGenCo is skipped here because the idtable entry is
|
|
// tombstoned. The service returns to step 1.
|
|
//
|
|
// The fix is to allow MorphGenCo's to proceed even if the idtable
|
|
// entry is deleted. The delete will end up being a NOP (file doesn't
|
|
// exist) but will then be sent out to the partners to insure
|
|
// consistency amoung the members.
|
|
//
|
|
if (!MorphGenCo && IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED)
|
|
&& !IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED)) {
|
|
|
|
if ((LocationCmd == CO_LOCATION_DELETE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT)) {
|
|
|
|
if (LocalCo && (IDTableRec->EventTime != CoCmd->EventTime.QuadPart)) {
|
|
//
|
|
// Swat down this CO if it is local and the eventtime in the idtable
|
|
// is not the same as the event time in the CoCmd.
|
|
// We do this because if
|
|
// a remote CO delete comes in with a very old event time we
|
|
// update the event time in the IDTable record with the old time.
|
|
// Now when the delete is installed we generate a very recent
|
|
// USN record. If we let this local delete get to reconcile it
|
|
// will accept it based on the event time difference. But this
|
|
// local delete was generated by us processing a remote co delete
|
|
// and now we are going to propagate it down stream.
|
|
// Once change order backlogs form this can result in a cascade
|
|
// effect until the backlogs dissapate. Note that backlogs
|
|
// could form at either outbound or inbound logs but the effect
|
|
// is the same even though the specific scenario is different.
|
|
//
|
|
// If we get a valid local delete and update the idtable but crash
|
|
// before we write it to the outlog. Then when we come back and process
|
|
// the local delete from the inlog we do not want to skip it. Hence we
|
|
// don't skip the local delete if the event times match.
|
|
//
|
|
FStatus = FrsErrorInvalidChangeOrder;
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Lcl Del Co rcvd on deleted file -- Skipping");
|
|
return FStatus;
|
|
}
|
|
if (LocalCo) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Lcl Del Co rcvd on deleted file");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Rmt Del Co rcvd on deleted file");
|
|
}
|
|
FStatus = FrsErrorSuccess;
|
|
return FStatus;
|
|
}
|
|
|
|
FStatus = FrsErrorSuccess;
|
|
|
|
if (!LocalCo) {
|
|
//
|
|
// If the FID is non-zero in the IDTable then check if it is valid.
|
|
// If this is a retry fetch CO for a reanimate then the pre-install
|
|
// file was already created the first time around so it should be
|
|
// there. This could also be a recovery CO if we crashed after
|
|
// updating the FID but before the CO was marked as retry in the
|
|
// INLOG. This could also be a dup CO that arrived after a previous
|
|
// CO started the reanimation but went into fetch retry cuz the
|
|
// connection failed. So we can't condition this check on the
|
|
// retry or recovery flags in the CO. The only way we can know is
|
|
// to try to open the FID and see if the file is there.
|
|
//
|
|
if (IDTableRec->FileID != ZERO_FID) {
|
|
FileHandle = INVALID_HANDLE_VALUE;
|
|
WStatus = FrsOpenSourceFileById(&FileHandle,
|
|
&FileNetworkOpenInfo,
|
|
NULL,
|
|
Replica->pVme->VolumeHandle,
|
|
&IDTableRec->FileID,
|
|
FILE_ID_LENGTH,
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
if (WIN_SUCCESS(WStatus) &&
|
|
WIN_SUCCESS(FrsReadFileParentFid(FileHandle, &CurrParentFid)) &&
|
|
(CurrParentFid == Replica->PreInstallFid)) {
|
|
|
|
NOTHING;
|
|
} else {
|
|
//
|
|
// Either can't access the file or it's not in pre-install dir
|
|
//
|
|
CHANGE_ORDER_TRACEX(3, ChangeOrder, "Force create of new Pre-Install file", WStatus);
|
|
IDTableRec->FileID = ZERO_FID;
|
|
ChangeOrder->FileReferenceNumber = ZERO_FID;
|
|
}
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
}
|
|
|
|
//
|
|
// Not a delete CO. Check if name conflict would occur
|
|
// if reanimation took place. Caller will decide if this
|
|
// CO is actually rejected or not before any name morph
|
|
// change orders are created.
|
|
//
|
|
FStatus = ChgOrdCheckNameMorphConflict(ThreadCtx,
|
|
IDTableCtxNC,
|
|
ReplicaNumber,
|
|
ChangeOrder);
|
|
|
|
if (FStatus == FrsErrorNameMorphConflict) {
|
|
DPRINT(0,"++ NM: Possible Name Morph Conflict - reanimate\n");
|
|
DBS_DISPLAY_RECORD_SEV(4, IDTableCtxNC, TRUE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Poss Morph Conflict - reani");
|
|
} else {
|
|
CLEANUP_FS(0,"++ NM: WARNING - Unexpected result from ChgOrdCheckNameMorphConflict.",
|
|
FStatus, RETURN_ERROR);
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// LOCAL CO.
|
|
// Tombstoned IDTable entry hit on Non-MorphGen Local Change Order.
|
|
// Update FileUsn to the USN of the most recent USN record that
|
|
// we've seen. This is probably a MOVEIN local CO.
|
|
//
|
|
CoCmd->FileUsn = CoCmd->JrnlUsn;
|
|
|
|
if (!RetryCo) {
|
|
//
|
|
// Bump Version number to ensure the CO is accepted if this is
|
|
// the first time we have seen this Local Co.
|
|
//
|
|
CoCmd->FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
}
|
|
}
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Co Possible Reanimate");
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
|
|
//
|
|
// Done if this is a Tombstone Reanimation.
|
|
//
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// IDTable record found and not marked deleted. //
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
FStatus = FrsErrorSuccess;
|
|
|
|
if (!LocalCo || MorphGenCo) {
|
|
|
|
//
|
|
// Handle Remote Co
|
|
//
|
|
ChangeOrder->FileReferenceNumber = IDTableRec->FileID;
|
|
|
|
//
|
|
// Check and update the state of the parent fid.
|
|
//
|
|
if (ChangeOrder->ParentFileReferenceNumber !=
|
|
(ULONGLONG) IDTableRec->ParentFileID) {
|
|
|
|
if (CO_MOVE_OUT_RS_OR_DIR(LocationCmd)) {
|
|
//
|
|
// Expected current location of file is in IDTable.
|
|
//
|
|
ChangeOrder->ParentFileReferenceNumber = IDTableRec->ParentFileID;
|
|
|
|
} else {
|
|
//
|
|
// If this CO has requested its parent to be reanimated then
|
|
// the Guid to Fid translation done earlier now has the current
|
|
// (and correct) parent FID for this file. Update the IDTable
|
|
// entry to reflect the change in parent FID. This can happen
|
|
// if a local file op deletes the parent while create for a
|
|
// child is in process. when the Child create tries to do the
|
|
// final rename it will fail since the parent is now gone. This
|
|
// sends it thru retry in the IBCO_INSTALL_REN_RETRY state.
|
|
// Later the local delete is processed and the parent is marked
|
|
// as deleted. When the remote CO for the child is retried
|
|
// it reanimates the parent but the IDTable entry that was
|
|
// originally created for the child still has the File ID for
|
|
// the old parent.
|
|
//
|
|
if (!COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_RISE_REQ)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "WARN - Unexpected mismatch in parent FIDs");
|
|
DBS_DISPLAY_RECORD_SEV(3, IDTableCtx, TRUE);
|
|
FRS_PRINT_TYPE(3, ChangeOrder);
|
|
|
|
//
|
|
// This condition may not be an error because we could get
|
|
// the above case (rmt child create with concurrent parent
|
|
// local delete) where the rmt child create is followed by
|
|
// the local Del and then immediately by a 2nd rmt file
|
|
// update. The first rmt CO will go thru install and then
|
|
// go to retry on the final rename. The local delete is
|
|
// processed for the parent. Then the 2nd rmt co finds the
|
|
// IDTable for the parent is marked as deleted and so it
|
|
// reanimates the parent. It then finds the IDTable entry
|
|
// for the target file (created by the first rmt CO) with
|
|
// the IDREC_FLAGS_RENAME_DEFERRED bit set and the stale
|
|
// value for the parent FID created by the first rmt co.
|
|
// So the parent FIDs will again be mismatched but the 2nd
|
|
// remote CO is not any kind of a retry CO.
|
|
//
|
|
// A simpler scenario is a local rename (MOVDIR) happening at the
|
|
// same time as a remote update. If the rename is processed
|
|
// first then the parent FID in the IDTable is updated to
|
|
// the new parent. When the remote CO arrives its parent
|
|
// GUID is for the old parent which is translated to the
|
|
// old parent FID in the change order.
|
|
//
|
|
// What all this means is that if the parent FIDs don't
|
|
// match and this is not some flavor of a directory MOVE
|
|
// then the GUID to FID XLATE done on the CO before we
|
|
// get here has the correct value for the parent FID so
|
|
// we need to update the IDTable record accordingly.
|
|
//
|
|
// This is taken care of later in change order accept where
|
|
// we deal with the problem of simultaneous update and rename
|
|
// COs where the rename CO arrives first but the update CO
|
|
// ultimately wins and must implicitly rename the file back
|
|
// to its previous parent. An update to IDTableRec->ParentFileID
|
|
// here will interfere with that test.
|
|
} else {
|
|
IDTableRec->ParentFileID = ChangeOrder->ParentFileReferenceNumber;
|
|
}
|
|
}
|
|
}
|
|
|
|
CoCmd->FileUsn = IDTableRec->CurrentFileUsn;
|
|
|
|
//
|
|
// A newly created file is first installed into a temporary file
|
|
// and then renamed to its final destination. The deferred rename
|
|
// flag is set in the IDTable record if the rename fails because
|
|
// of sharing violations or a name conflict. However a subsequent
|
|
// CO may arrive for this file so the flag in the IDTable record
|
|
// tells us to retry the rename when this change order completes.
|
|
// The old create change order will later be discarded by the reconcile
|
|
// code in ChgOrdAccept().
|
|
// NOTE: the flag is ignored when retiring the change order if
|
|
// the change order is a delete.
|
|
// NOTE: Clearing the IDTable flag here does not actually update the
|
|
// database record until the CO finally retires. At that point if
|
|
// the COE_FLAG_NEED_RENAME is still set then the IDTable flag is
|
|
// turned back on.
|
|
//
|
|
//
|
|
// COE_FLAG_NEED_RENAME is an incore flag that tells the retire
|
|
// code to rename the successfully installed file to its final
|
|
// destination. It is set when the staging file is successfully
|
|
// installed in the preinstall file or when IDREC_FLAGS_RENAME_DEFERRED
|
|
// is set in the idtable entry. Note that there can be multiple COs
|
|
// active against the same file (serialized of course). Some may have
|
|
// made it to the rename retry state and have set IDREC_FLAGS_RENAME_DEFERRED
|
|
// while others may be in fetch retry or install retry states. Only when
|
|
// a CO makes it as far as the install rename retry state do we update
|
|
// the version (and file name) info in the idtable record.
|
|
//
|
|
// IDREC_FLAGS_RENAME_DEFERRED is set when renaming the preinstall
|
|
// file to its final destination failed. The first change order that
|
|
// next comes through for this file picks up the COE_FLAG_NEED_RENAME
|
|
// bit and the IDREC_FLAGS_RENAME_DEFERRED bit is cleared.
|
|
//
|
|
// IBCO_INSTALL_REN_RETRY is a CO command State that is set when
|
|
// a rename fails with a retriable condition. The IDtable entry
|
|
// is marked with IDREC_FLAGS_RENAME_DEFERRED incase another change
|
|
// order is accepted prior to this retry change order.
|
|
// Only one CO at a time can have the COE_FLAG_NEED_RENAME set because
|
|
// ChgOrdHoldIssue() ensures only one CO at a time is in process on a
|
|
// given file.
|
|
//
|
|
// Sharing violations preventing final rename install --
|
|
//
|
|
// Note that more than one change order can be pending in the
|
|
// inbound log for the same file and all can be in the IBCO_INSTALL_REN_RETRY
|
|
// state. Each one could be renaming the file to a different name
|
|
// or parent dir and could have been blocked by a sharing violation.
|
|
// Because of this the data in the IDTable entry will have the
|
|
// currently correct target name/ parent dir for the file based on
|
|
// those COs that have won reconcilliation. An added wrinkle is that
|
|
// in the case of a DIR-DIR name morph conflict where the incoming
|
|
// remote CO loses the name we insert a local rename CO into the stream
|
|
// to prop the name change to all outbound partners. This increases the
|
|
// version number but since it is a local CO it will not go through
|
|
// rename path. If the original incoming CO failed to complete the
|
|
// rename when it later retries it will lose in reconcile based on
|
|
// a lower version number. This strands the file in the pre-install
|
|
// area. To avoid this problem we always accept retry change orders that
|
|
// are in the IBCO_INSTALL_REN_RETRY state when the
|
|
// IDREC_FLAGS_RENAME_DEFERRED flag is set. If the RENAME_DEFERRED
|
|
// flag is clear then the final rename has been done by another CO and
|
|
// we can just flush the REN_RETRY change order.
|
|
//
|
|
// BTW: Rename is coded to be idempotent. The opens are by ID, not name,
|
|
// and renaming a file to itself turns into a successful NOP.
|
|
//
|
|
// A parallel set of flags exist for deferred deletes. That is,
|
|
// COE_FLAG_NEED_DELETE, IDREC_FLAGS_DELETE_DEFERRED,
|
|
// IBCO_INSTALL_DEL_RETRY. See scenario description for
|
|
// IDREC_FLAGS_DELETE_DEFERRED in schema.h.
|
|
//
|
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_RENAME_DEFERRED)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_NEED_RENAME);
|
|
// SUDARC-DEV - Comment out this part so that we preserve the flags in the idtable
|
|
// record.
|
|
// ClearIdRecFlag(IDTableRec, IDREC_FLAGS_RENAME_DEFERRED);
|
|
}
|
|
|
|
//
|
|
// If this is a rename CO or there is a pending rename on this file
|
|
// (was marked in the IDTable) then check for possible name morph
|
|
// conflict on the new name.
|
|
//
|
|
if (!MorphGenCo &&
|
|
(BooleanFlagOn(CoCmd->ContentCmd, USN_REASON_RENAME_NEW_NAME) ||
|
|
CO_STATE_IS(ChangeOrder, IBCO_INSTALL_REN_RETRY) ||
|
|
COE_FLAG_ON(ChangeOrder, COE_FLAG_NEED_RENAME))) {
|
|
|
|
FStatus = ChgOrdCheckNameMorphConflict(ThreadCtx,
|
|
IDTableCtxNC,
|
|
ReplicaNumber,
|
|
ChangeOrder);
|
|
|
|
if (FStatus == FrsErrorNameMorphConflict) {
|
|
|
|
DPRINT(0,"++ NM: Possible Name Morph Conflict on rename\n");
|
|
DBS_DISPLAY_RECORD_SEV(4, IDTableCtxNC, TRUE);
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Poss Morph Conflict - ren");
|
|
//
|
|
// It is possible that a previous change order has already
|
|
// resolved the conflict. If so don't do it again.
|
|
// The originator guid and the VSN must match but the filename
|
|
// must be different.
|
|
//
|
|
DPRINT(0,"++ NM: Checking for name conflict already resolved.\n");
|
|
RStatus = FrsGuidCompare(&CoCmd->OriginatorGuid,
|
|
&IDTableRec->OriginatorGuid);
|
|
|
|
if ((RStatus == 0) &&
|
|
(IDTableRec->OriginatorVSN == CoCmd->FrsVsn)) {
|
|
//
|
|
// Conflict resolved previously. Bag this CO.
|
|
//
|
|
FStatus = FrsErrorInvalidChangeOrder;
|
|
//
|
|
// File name on this IDTable entry better be different.
|
|
//
|
|
// The file name in the IDTable entry may be the same
|
|
// if a previous duplicate co won the name and the
|
|
// delete of the loser follows this soon-to-be rejected
|
|
// change order. The original remote co won the name
|
|
// because the name did not yet exist. The rename of
|
|
// the preinstall file to its final name resulted in
|
|
// the deletion of the local file. I assume this is
|
|
// the correct protocol for a remote co to "win" the
|
|
// name over a very recently named local file. The
|
|
// local file appeared with the same name as the
|
|
// result of a rename of an existing local file while
|
|
// the staging file was being fetched or installed
|
|
// into the preinstall area.
|
|
//
|
|
// FRS_ASSERT(wcscmp(CoCmd->FileName, IDTableRec->FileName) != 0);
|
|
}
|
|
} else {
|
|
CLEANUP_FS(0,"++ NM: WARNING - Unexpected result from ChgOrdCheckNameMorphConflict.",
|
|
FStatus, RETURN_ERROR);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// TEST CODE VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
|
//
|
|
// test code to be sure the CurrentFileUsn Is still right.
|
|
//
|
|
WStatus1 = FrsOpenSourceFileById(&FileHandle,
|
|
&FileNetworkOpenInfo,
|
|
NULL,
|
|
Replica->pVme->VolumeHandle,
|
|
(PULONG)&ChangeOrder->FileReferenceNumber,
|
|
sizeof(ULONGLONG),
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
if (WIN_SUCCESS(WStatus1)) {
|
|
|
|
FrsReadFileUsnData(FileHandle, &CurrentFileUsn);
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
if (CurrentFileUsn != IDTableRec->CurrentFileUsn) {
|
|
DPRINT(0, "++ WARNING - ID Table record to file mismatch\n");
|
|
DPRINT1(0, "++ File's Usn is: %08x %08x\n",
|
|
PRINTQUAD(CurrentFileUsn));
|
|
DPRINT1(0, "++ IDTable->CurrentFileUsn is: %08x %08x\n",
|
|
PRINTQUAD(IDTableRec->CurrentFileUsn));
|
|
}
|
|
} else {
|
|
DPRINT(0, "++ ERROR - Can't open target file to check USN\n");
|
|
}
|
|
// TEST CODE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
|
|
#endif
|
|
|
|
} else {
|
|
//
|
|
// IDTable hit on Non-MorphGen Local Change Order. Update FileUsn to
|
|
// the USN of the most recent USN record that we've seen.
|
|
//
|
|
CoCmd->FileUsn = CoCmd->JrnlUsn;
|
|
|
|
|
|
if (!RetryCo) {
|
|
//
|
|
// Bump Version number to ensure the CO is accepted if this is
|
|
// the first time we have seen this Local Co.
|
|
//
|
|
CoCmd->FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
}
|
|
|
|
|
|
//
|
|
// Needed to dampen basic info changes (e.g., resetting the archive bit)
|
|
// Copied from the idtable entry when the change order is created and
|
|
// used to update the change order when the change order is retired.
|
|
//
|
|
ChangeOrder->FileCreateTime = IDTableRec->FileCreateTime;
|
|
ChangeOrder->FileWriteTime = IDTableRec->FileWriteTime;
|
|
ChangeOrder->FileAttributes = IDTableRec->FileAttributes;
|
|
//
|
|
// Note: If this CO was generated off of a directory filter table
|
|
// entry (e.g. Moveout) then the ChangeOrder->Cmd.FileAttributes will
|
|
// be zero. Insert the file attributes from the IDTable record since
|
|
// we can't get them from the original file.
|
|
//
|
|
if (ChangeOrder->Cmd.FileAttributes == 0) {
|
|
ChangeOrder->Cmd.FileAttributes = IDTableRec->FileAttributes;
|
|
}
|
|
|
|
//
|
|
// Check if a create change order on this file has been deferred
|
|
// because of:
|
|
// 1. a USN change detected by the stager,
|
|
// 2. a sharing violation prevented us from generating the stage file
|
|
// or stamping the OID on the file.
|
|
//
|
|
// If so change the Location Command to a create and clear the
|
|
// Create Deferred bit. Don't do this for a Delete or Moveout
|
|
// since the file is gone and the stager will just bag the change
|
|
// order.
|
|
//
|
|
if (IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_CREATE_DEFERRED) &&
|
|
!((LocationCmd == CO_LOCATION_DELETE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT))) {
|
|
|
|
if (RetryCo) {
|
|
//
|
|
// Bump Version number to ensure the CO is accepted if this is
|
|
// a retry CO AND we are still trying to generate the create
|
|
// change order for the file. See DbsRetryInboundCo() for
|
|
// explanation.
|
|
//
|
|
CoCmd->FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
}
|
|
|
|
ClearIdRecFlag(IDTableRec, IDREC_FLAGS_CREATE_DEFERRED);
|
|
SET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command, CO_LOCATION_CREATE);
|
|
LocationCmd = CO_LOCATION_CREATE;
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_LOCATION_CMD);
|
|
SetFlag(CoCmd->ContentCmd, USN_REASON_FILE_CREATE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// End of IDTable record found code.
|
|
//
|
|
return FStatus;
|
|
|
|
|
|
RETURN_JERR:
|
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|
|
|
RETURN_ERROR:
|
|
//
|
|
// LOG an error message so the user or admin can see what happened.
|
|
//
|
|
DPRINT1(1, "++ WARN - Replication disabled for file %ws\n", CoCmd->FileName);
|
|
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
IDTableRec->ReplEnabled = FALSE;
|
|
|
|
//
|
|
// Close the table, reset the TableCtx Tid and Sesid.
|
|
// DbsCloseTable is a Macro, writes 1st arg.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, IDTableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable on IDTable failed:", jerr);
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdInsertIDRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PTABLE_CTX DIRTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert a new record into the ID table and DIR table for files that
|
|
have either been created or renamed into the replcia set.
|
|
|
|
|
|
The caller supplies the ID and DIR table contexts to avoid
|
|
a storage realloc on every call.
|
|
|
|
On return the DIR and IDTables are closed.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtx -- The table context containing the ID Table record.
|
|
DIRTableCtx -- The table context to use for DIR table access.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica ID table to do the lookup in.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertIDRecord:"
|
|
|
|
|
|
FILE_OBJECTID_BUFFER FileObjID;
|
|
|
|
JET_ERR jerr, jerr1;
|
|
ULONG FStatus;
|
|
ULONG WStatus;
|
|
ULONG GStatus;
|
|
ULONG ReplicaNumber = Replica->ReplicaNumber;
|
|
BOOL RemoteCo, MorphGenCo;
|
|
|
|
PDIRTABLE_RECORD DIRTableRec;
|
|
PIDTABLE_RECORD IDTableRec;
|
|
|
|
|
|
//
|
|
// remote vs. local change order
|
|
//
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
IDTableRec = IDTableCtx->pDataRecord;
|
|
|
|
//
|
|
// We should never be inserting an IDTable record with a zero fid.
|
|
//
|
|
FRS_ASSERT(IDTableRec->FileID != ZERO_FID);
|
|
|
|
//
|
|
// Write the new IDTable record into the database.
|
|
//
|
|
FStatus = DbsInsertTable(ThreadCtx, Replica, IDTableCtx, IDTablex, NULL);
|
|
//
|
|
// NTFS tunneling will tunnel the object from a recently deleted
|
|
// file onto a newly created file. This confuses the idtable because
|
|
// we now have a fid whose object id matches an existing fid. We
|
|
// workaround the problem by assigning a new object id to the file.
|
|
//
|
|
// link tracking: The tombstone retains the old OID so FRS blocks OID Tunneling.
|
|
// This might be fixed by implementing tunneling support in FRS.
|
|
//
|
|
if (FStatus == FrsErrorKeyDuplicate && !RemoteCo && !MorphGenCo) {
|
|
ZeroMemory(&IDTableRec->FileObjID, sizeof(IDTableRec->FileObjID));
|
|
FrsUuidCreate((GUID *)(&IDTableRec->FileObjID.ObjectId[0]));
|
|
WStatus = ChgOrdStealObjectId(ChangeOrder->Cmd.FileName,
|
|
(PULONG)&ChangeOrder->FileReferenceNumber,
|
|
Replica->pVme,
|
|
&ChangeOrder->Cmd.FileUsn,
|
|
&IDTableRec->FileObjID);
|
|
CLEANUP_WS(0, "++ WORKAROUND FAILED: duplicate key error.", WStatus, ERROR_RETURN);
|
|
|
|
COPY_GUID(&ChangeOrder->Cmd.FileGuid, IDTableRec->FileObjID.ObjectId);
|
|
COPY_GUID(&IDTableRec->FileGuid, IDTableRec->FileObjID.ObjectId);
|
|
|
|
FStatus = DbsInsertTable(ThreadCtx, Replica, IDTableCtx, IDTablex, NULL);
|
|
CLEANUP_FS(0, "++ WORKAROUND FAILED: duplicate key error.", FStatus, ERROR_RETURN);
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DPRINT(3, "++ WORKAROUND SUCCEEDED: Ignore duplicate key error\n");
|
|
}
|
|
}
|
|
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
goto ERROR_RETURN;
|
|
}
|
|
|
|
//
|
|
// If this is a dir entry insert a new entry in the DIR Table.
|
|
// For remote COs, defer the update of the journal ParentFidTable and
|
|
// the directory FilterTable until the rename install occurs.
|
|
//
|
|
//
|
|
if (IDTableRec->FileIsDir) {
|
|
FStatus = ChgOrdInsertDirRecord(ThreadCtx,
|
|
DIRTableCtx,
|
|
IDTableRec,
|
|
ChangeOrder,
|
|
Replica,
|
|
TRUE);
|
|
}
|
|
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
ERROR_RETURN:
|
|
//
|
|
// LOG an error message so the user or admin can see what happened.
|
|
//
|
|
DPRINT1(0, "++ ERROR - Replication disabled for file %ws\n", ChangeOrder->Cmd.FileName);
|
|
|
|
//
|
|
// Close the table, reset the TableCtx Tid and Sesid.
|
|
// DbsCloseTable is a Macro, writes 1st arg.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, DIRTableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable on DIRTable failed:", jerr);
|
|
|
|
return FStatus;
|
|
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdInsertDirRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX DIRTableCtx,
|
|
PIDTABLE_RECORD IDTableRec,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL InsertFlag
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert or update a record into the DIR table for directories that
|
|
have either been created, reanimated, or renamed into the replcia set.
|
|
|
|
The caller supplies the DIR table contexts to avoid a storage realloc
|
|
on every call.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
DIRTableCtx -- The table context to use for DIR table access.
|
|
IDTableRec - ID table record.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica ID table to do the lookup in.
|
|
InsertFlag -- True if this is an insert. False if this is an update.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertDirRecord:"
|
|
|
|
JET_ERR jerr, jerr1;
|
|
ULONG FStatus;
|
|
ULONG WStatus;
|
|
ULONG ReplicaNumber = Replica->ReplicaNumber;
|
|
ULONG LocationCmd;
|
|
BOOL RemoteCo;
|
|
BOOL MoveInGenCo;
|
|
PDIRTABLE_RECORD DIRTableRec;
|
|
|
|
|
|
//
|
|
// If this is a directory entry insert a new entry in the DIR Table.
|
|
//
|
|
if (!IDTableRec->FileIsDir) {
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
//
|
|
// remote vs. local change order
|
|
//
|
|
RemoteCo = !CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
|
|
//
|
|
// Open the DIR table for this replica.
|
|
//
|
|
jerr = DbsOpenTable(ThreadCtx, DIRTableCtx, ReplicaNumber, DIRTablex, NULL);
|
|
CLEANUP_JS(0, "++ error opening DIRTable.", jerr, ERROR_RETURN_JERR);
|
|
|
|
DIRTableRec = DIRTableCtx->pDataRecord;
|
|
|
|
//
|
|
// Build the DIRTable record and insert it into the DIR table.
|
|
//
|
|
DIRTableRec->DFileGuid = IDTableRec->FileGuid;
|
|
DIRTableRec->DFileID = IDTableRec->FileID;
|
|
DIRTableRec->DParentFileID = IDTableRec->ParentFileID;
|
|
DIRTableRec->DReplicaNumber = Replica->ReplicaNumber;
|
|
wcscpy(DIRTableRec->DFileName, IDTableRec->FileName);
|
|
|
|
if (InsertFlag) {
|
|
jerr = DbsInsertTable2(DIRTableCtx);
|
|
|
|
//
|
|
// Update the volume filter table for a local CO that:
|
|
// 1. Is marked recovery since we won't see it in the Journal again, or
|
|
// 2. Is the product of a MoveIn sub-dir op since it never came from
|
|
// the Journal process.
|
|
//
|
|
MoveInGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MOVEIN_GEN);
|
|
|
|
if (!RemoteCo && (RecoveryCo(ChangeOrder) || MoveInGenCo)) {
|
|
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter - Reanimate");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter");
|
|
}
|
|
|
|
if (MoveInGenCo) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter - MoveInGenCo");
|
|
}
|
|
|
|
WStatus = JrnlAddFilterEntryFromCo(Replica, ChangeOrder, NULL);
|
|
FStatus = FrsTranslateWin32Error(WStatus);
|
|
CLEANUP_WS(4, "++ JrnlAddFilterEntryFromCo failed.", WStatus, ERROR_RETURN);
|
|
}
|
|
} else {
|
|
//
|
|
// Update an existing record.
|
|
//
|
|
jerr = DbsSeekRecord(ThreadCtx,
|
|
&DIRTableRec->DFileGuid,
|
|
DFileGuidIndexx,
|
|
DIRTableCtx);
|
|
|
|
if (JET_SUCCESS(jerr)) {
|
|
|
|
jerr = DbsUpdateTable(DIRTableCtx);
|
|
|
|
if (JET_SUCCESS(jerr) && RemoteCo && CO_MOVE_RS_OR_DIR(LocationCmd)) {
|
|
|
|
//
|
|
// Update the volume filter table for new dir in
|
|
// remote change orders. For new file creates this update doesn't
|
|
// occur until the rename install of the pre-install file is done.
|
|
// See DbsRenameFid().
|
|
//
|
|
if (LocationCmd == CO_LOCATION_MOVERS) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "RmtCo UpdVolDir Filter - MoveRs");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "RmtCo UpdVolDir Filter - MoveDir");
|
|
}
|
|
|
|
WStatus = JrnlAddFilterEntryFromCo(Replica, ChangeOrder, NULL);
|
|
DPRINT_WS(4, "++ JrnlAddFilterEntryFromCo failed.", WStatus);
|
|
}
|
|
} else if (jerr == JET_errRecordNotFound){
|
|
|
|
//
|
|
// The record may not be found in the dir table in some cases.
|
|
// e.g. A update that needs reanimation goes through retry. At this point
|
|
// it has updated the idtable and removed the delete flag. Also going through
|
|
// retry has caused it to lose the entry flags (reanimation flag). For this CO
|
|
// the update will fail with RecordNotFound.
|
|
//
|
|
DPRINT2(1, "++ WARN - DbsSeekRecord on %ws failed. Attempting insert: JStatus = %s\n",
|
|
Replica->ReplicaName->Name, ErrLabelJet(jerr));
|
|
|
|
jerr = DbsInsertTable2(DIRTableCtx);
|
|
//
|
|
// Update the volume filter table for a local CO that:
|
|
// 1. Is marked recovery since we won't see it in the Journal again, or
|
|
// 2. Is the product of a MoveIn sub-dir op since it never came from
|
|
// the Journal process.
|
|
//
|
|
MoveInGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MOVEIN_GEN);
|
|
|
|
if (!RemoteCo && (RecoveryCo(ChangeOrder) || MoveInGenCo)) {
|
|
|
|
if (COE_FLAG_ON(ChangeOrder, COE_FLAG_REANIMATION)) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter - Reanimate");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter");
|
|
}
|
|
|
|
if (MoveInGenCo) {
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Add Vol Dir Filter - MoveInGenCo");
|
|
}
|
|
|
|
WStatus = JrnlAddFilterEntryFromCo(Replica, ChangeOrder, NULL);
|
|
FStatus = FrsTranslateWin32Error(WStatus);
|
|
CLEANUP_WS(4, "++ JrnlAddFilterEntryFromCo failed.", WStatus, ERROR_RETURN);
|
|
}
|
|
|
|
} else {
|
|
goto ERROR_RETURN_JERR;
|
|
}
|
|
}
|
|
|
|
CLEANUP1_JS(0, "++ Error %s DIRTable record :",
|
|
(InsertFlag ? "inserting" : "updating"), jerr, ERROR_RETURN_JERR);
|
|
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, DIRTableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable on DIRTable failed:", jerr);
|
|
|
|
return FrsErrorSuccess;
|
|
|
|
|
|
ERROR_RETURN_JERR:
|
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|
|
|
ERROR_RETURN:
|
|
//
|
|
// LOG an error message so the user or admin can see what happened.
|
|
//
|
|
DBS_DISPLAY_RECORD_SEV(0, DIRTableCtx, FALSE);
|
|
DPRINT1(0, "++ ERROR - Replication disabled for Dir %ws\n", ChangeOrder->Cmd.FileName);
|
|
|
|
//
|
|
// Close the table, reset the TableCtx Tid and Sesid.
|
|
// DbsCloseTable is a Macro, writes 1st arg.
|
|
//
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, DIRTableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable on DIRTable failed:", jerr);
|
|
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdCheckAndFixTunnelConflict(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtxNew,
|
|
PTABLE_CTX IDTableCtxExist,
|
|
PREPLICA Replica,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
BOOL *IDTableRecExists
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
On input, the ChangeOrder must be for a new file and have been
|
|
assigned a guid (even if the guid has not been hammered onto
|
|
the file, yet). In other words, this function is designed to
|
|
be called out of ChgOrdReadIDRecord() after the call to
|
|
DbsInitializeIDTableRecord().
|
|
|
|
267544 ChangeOrder->COMorphGenCount < 50 assertion in NTFRS.
|
|
Tunneling assigns the guid of a deleted file to a newly created
|
|
local file. In ChgOrdReadIDRecord(), a new idtable entry for
|
|
the file is initialized with the tunneled guid. The file
|
|
then loses a morph conflict. A delete-co is generated.
|
|
When the delete-co is passed to ChgOrdReadIDRecord(), the
|
|
wrong idtable record is read because the file's guid is used
|
|
as the index. The delete-co is updated with the wrong FID and
|
|
finally retires and updates the wrong idtable entry. The co
|
|
for the newly created file again enters ChgOrdReadIDRecord()
|
|
and the process is repeated until the ASSERT(morphgencount < 50).
|
|
|
|
Read the ID Table record by guid for this change order.
|
|
If found and the FID does not match then steal the existing
|
|
id table entry by updating the FID to be the FID for this file.
|
|
|
|
In other words, keep the guid/fid relationship intact in the idtable.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtxNew -- The table context for new ID Table record
|
|
IDTableCtxExist -- The table context for existing ID Table record
|
|
Replica -- The Replica for the ID Table to do the lookup in.
|
|
ChangeOrder-- The change order.
|
|
IDTableRecExists - Set to TRUE if the idtable record exists. The
|
|
caller then knows that the idtable entry shouldn't
|
|
be inserted.
|
|
|
|
Return Value:
|
|
|
|
Frs Status:
|
|
FrsErrorTunnelConflict - tunnel conflict detected and resolved.
|
|
FrsErrorSuccess - no conflict
|
|
Any other error is a failure.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdCheckAndFixTunnelConflict:"
|
|
JET_ERR jerr;
|
|
ULONG FStatus;
|
|
ULONG WStatus;
|
|
PIDTABLE_RECORD IDTableRecNew;
|
|
PIDTABLE_RECORD IDTableRecExist;
|
|
ULONG IdtFieldUpdateList[1];
|
|
PCHANGE_ORDER_COMMAND CoCmd = &ChangeOrder->Cmd;
|
|
|
|
//
|
|
// Open the idtable
|
|
//
|
|
jerr = DbsOpenTable(ThreadCtx,
|
|
IDTableCtxExist,
|
|
Replica->ReplicaNumber,
|
|
IDTablex,
|
|
NULL);
|
|
|
|
//
|
|
// Unexpected problem opening the idtable
|
|
//
|
|
if (!JET_SUCCESS(jerr)) {
|
|
FStatus = DbsTranslateJetError(jerr, TRUE);
|
|
return FStatus;
|
|
}
|
|
|
|
//
|
|
// Read the existing idtable record by guid (stored in new idtable entry)
|
|
//
|
|
IDTableRecNew = IDTableCtxNew->pDataRecord;
|
|
jerr = DbsReadRecord(ThreadCtx,
|
|
&IDTableRecNew->FileGuid,
|
|
GuidIndexx,
|
|
IDTableCtxExist);
|
|
|
|
//
|
|
// No conflict; done
|
|
//
|
|
if (jerr == JET_errRecordNotFound) {
|
|
FStatus = FrsErrorSuccess;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Unexpected problem reading the idtable record
|
|
//
|
|
if (!JET_SUCCESS(jerr)) {
|
|
FStatus = DbsTranslateJetError(jerr, TRUE);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// Does the existing idtable entry have a different fid? If so,
|
|
// steal the entry if it is for a deleted file. This can happen
|
|
// when tunneling assigns an old OID to a new FID.
|
|
//
|
|
IDTableRecExist = IDTableCtxExist->pDataRecord;
|
|
if (IDTableRecNew->FileID == IDTableRecExist->FileID) {
|
|
FStatus = FrsErrorSuccess;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
//
|
|
// TUNNEL CONFLICT
|
|
//
|
|
DBS_DISPLAY_RECORD_SEV(5, IDTableCtxExist, TRUE);
|
|
DBS_DISPLAY_RECORD_SEV(5, IDTableCtxNew, TRUE);
|
|
if (IsIdRecFlagSet(IDTableRecExist, IDREC_FLAGS_DELETED)) {
|
|
//
|
|
// This case occurs when the delete of the original file preceeds the
|
|
// create and rename of the new file to the target name.
|
|
//
|
|
// del FID_original (has target_name, loads NTFS tunnel cache)
|
|
// cre FID_new (has a temp name)
|
|
// ren FID_new to target_name (hits in NTFS tunnel cache)
|
|
//
|
|
// When we see the USN record for the create the rename has already
|
|
// been done and FID_new has the OID from FID_original via the tunnel
|
|
// cache. Since we have seen the delete the IDT entry has been marked
|
|
// deleted.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Tunnel Conflict - Updating IDT");
|
|
} else {
|
|
//
|
|
// This case occurs when the delete of the original file follows the
|
|
// create of the new file.
|
|
//
|
|
// cre FID_new (has a temp name)
|
|
// del FID_original (has target_name, loads NTFS tunnel cache)
|
|
// ren FID_new to target_name (hits in NTFS tunnel cache)
|
|
//
|
|
// As above when we see the USN record for the create the rename has
|
|
// already been done and FID_new has the OID from FID_original via
|
|
// the tunnel cache. BUT in this case we have not yet seen the
|
|
// USN record for the delete so the IDT entry has not been marked
|
|
// deleted. Since the OID got tunneled we know there is another USN
|
|
// record coming that will reference FID_new so we can reject the
|
|
// create CO now and let it be handled at that time. This is similar
|
|
// to updating a file that wasn't in the IDTable because it matched
|
|
// the file filter at the time it was created so the USN record was
|
|
// skipped.
|
|
//
|
|
//
|
|
// Actually we cannot skip the create because the other CO that "must"
|
|
// be coming could have been combined with this one in the aging cache.
|
|
// THis problem occurred on saves of excel files.
|
|
// It is even possible not to have another CO on the way:
|
|
//
|
|
// Consider an ap that does this:
|
|
// Open A
|
|
// Rename A => B
|
|
// Close "B" (opened as A)
|
|
// Open B
|
|
// Mark B Deleted
|
|
// Open A
|
|
// Close A
|
|
// Close B
|
|
//
|
|
// The Oid from the original A will be tunneled to the new A,
|
|
// but the order of the closes will make the USN look like this:
|
|
//
|
|
// Rename A => B
|
|
// Create A
|
|
// Delete B
|
|
//
|
|
//
|
|
// Now we process the create of A with the tunneled Oid before the delete of B.
|
|
//
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "Tunnel Conflict - Updating IDT [Original File not marked Deleted]");
|
|
|
|
// CHANGE_ORDER_TRACE(3, ChangeOrder, "Tunnel Conflict - Reject CO");
|
|
// FStatus = FrsErrorTunnelConflictRejectCO;
|
|
// goto CLEANUP;
|
|
|
|
}
|
|
|
|
//
|
|
// Pick up the version of the existing record + 1 just as if this
|
|
// change order were a local co against the existing record (which
|
|
// it is). Otherwise, the change order will be rejected on our
|
|
// partners because the version on a new file is set to 0.
|
|
//
|
|
CoCmd->FileVersionNumber = IDTableRecExist->VersionNumber + 1;
|
|
|
|
//
|
|
// Update the fid in the existing id table entry
|
|
//
|
|
// Perf: Is the below necessary? the idtable rec will be updated
|
|
// by ChgOrdAccept() on return because IDTableRecExists
|
|
// is set to TRUE below.
|
|
//
|
|
IdtFieldUpdateList[0] = FileIDx;
|
|
FStatus = DbsUpdateIDTableFields(ThreadCtx, Replica, ChangeOrder, IdtFieldUpdateList, 1);
|
|
CLEANUP_FS(0, "++ ERROR - DbsUpdateIDTableFields failed.", FStatus, CLEANUP);
|
|
|
|
//
|
|
// The existing idtable entry now belongs to the new file that
|
|
// was assigned an old OID (probably by tunneling).
|
|
//
|
|
*IDTableRecExists = TRUE;
|
|
|
|
//
|
|
// Tunnel fixup was a success.
|
|
//
|
|
FStatus = FrsErrorTunnelConflict;
|
|
|
|
CLEANUP:
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, IDTableCtxExist);
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdCheckNameMorphConflict(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtxNC,
|
|
ULONG ReplicaNumber,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Read the ID Table record for the parent guid - filename for this change
|
|
order from the replica set specified by the ReplicaNumber.
|
|
Scan all records with matching parent guid and filename looking for an
|
|
undeleted entry with a non-matching file guid. If found then we have
|
|
a name morph conflict.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtxNC -- The table context containing the ID Table record for the Name Conflict.
|
|
ReplicaNumber -- The Replica ID for the ID Table to do the lookup in.
|
|
ChangeOrder-- The change order.
|
|
|
|
Return Value:
|
|
|
|
Frs Status:
|
|
|
|
FrsErrorSuccess - IDTable record was not found so there is no conflict.
|
|
|
|
FrsErrorNameMorphConflict - A parent guid/name conflict with
|
|
another entry in the IDTable was found.
|
|
Data for this conflicting
|
|
entry is returned in IDTableCtxNC.
|
|
|
|
Any other error is a failure.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdCheckNameMorphConflict:"
|
|
|
|
|
|
UNICODE_STRING TempUStr;
|
|
JET_ERR jerr, jerr1;
|
|
ULONG FStatus;
|
|
PVOID KeyArray[2];
|
|
PIDTABLE_RECORD IDTableRec;
|
|
|
|
JET_SESID Sesid;
|
|
JET_TABLEID Tid;
|
|
USHORT IDTFileNameLength;
|
|
USHORT SaveCoFileNameLength, LenLimit;
|
|
|
|
PCHANGE_ORDER_COMMAND CoCmd = &ChangeOrder->Cmd;
|
|
|
|
|
|
#define ESE_INDEX_LENGTH_LIMIT (JET_cbKeyMost & 0XFFFFFFFE)
|
|
|
|
jerr = DbsOpenTable(ThreadCtx, IDTableCtxNC, ReplicaNumber, IDTablex, NULL);
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
FStatus = DbsTranslateJetError(jerr, TRUE);
|
|
return FStatus;
|
|
}
|
|
|
|
Sesid = ThreadCtx->JSesid;
|
|
Tid = IDTableCtxNC->Tid;
|
|
|
|
FRS_ASSERT(ChangeOrder->pParentGuid != NULL);
|
|
|
|
KeyArray[0] = (PVOID)ChangeOrder->pParentGuid;
|
|
KeyArray[1] = (PVOID)CoCmd->FileName;
|
|
|
|
SaveCoFileNameLength = ChangeOrder->UFileName.Length;
|
|
|
|
//
|
|
// Seek to the first record with a matching parent GUID and Filename.
|
|
//
|
|
FStatus = DbsRecordOperationMKey(ThreadCtx,
|
|
ROP_SEEK,
|
|
KeyArray,
|
|
ParGuidFileNameIndexx,
|
|
IDTableCtxNC);
|
|
//
|
|
// Read subsequent records (there could be multiples)
|
|
// checking for a non-tombstone.
|
|
//
|
|
while (FRS_SUCCESS(FStatus)) {
|
|
|
|
//
|
|
// Read the record and check for tombstone
|
|
//
|
|
FStatus = DbsTableRead(ThreadCtx, IDTableCtxNC);
|
|
|
|
IDTableRec = IDTableCtxNC->pDataRecord;
|
|
|
|
|
|
if (!GUIDS_EQUAL(ChangeOrder->pParentGuid, &IDTableRec->ParentGuid)) {
|
|
//
|
|
// No more matching records.
|
|
//
|
|
FStatus = FrsErrorNotFound;
|
|
break;
|
|
}
|
|
|
|
IDTFileNameLength = wcslen(IDTableRec->FileName) * sizeof(WCHAR);
|
|
|
|
|
|
|
|
//
|
|
// Indexes in Jet are limited to a max size of ESE_INDEX_LENGTH_LIMIT
|
|
// bytes for LongText Columns.
|
|
//
|
|
// So we can only rely on the length check to tell us when there
|
|
// are no more matchng entries in this key when we are going after
|
|
// a filename shorter than this or Jet returns us a result shorter
|
|
// than this.
|
|
//
|
|
if ((SaveCoFileNameLength != IDTFileNameLength) &&
|
|
((SaveCoFileNameLength < ESE_INDEX_LENGTH_LIMIT) ||
|
|
(IDTFileNameLength < ESE_INDEX_LENGTH_LIMIT))){
|
|
FStatus = FrsErrorNotFound;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Finally do the case insensitive compare.
|
|
//
|
|
FrsSetUnicodeStringFromRawString(&TempUStr,
|
|
SIZEOF(IDTABLE_RECORD, FileName),
|
|
IDTableRec->FileName,
|
|
IDTFileNameLength);
|
|
|
|
//
|
|
// Either the lengths are the same or they are both over
|
|
// ESE_INDEX_LENGTH_LIMIT, possibly both. Either way, for the
|
|
// purpose of loop termination we can't compare more than this.
|
|
//
|
|
LenLimit = min(SaveCoFileNameLength, ESE_INDEX_LENGTH_LIMIT);
|
|
LenLimit = min(LenLimit, IDTFileNameLength);
|
|
|
|
if (LenLimit == ESE_INDEX_LENGTH_LIMIT) {
|
|
TempUStr.Length = LenLimit;
|
|
ChangeOrder->UFileName.Length = LenLimit;
|
|
}
|
|
|
|
if (!RtlEqualUnicodeString(&TempUStr, &ChangeOrder->UFileName, TRUE)) {
|
|
ChangeOrder->UFileName.Length = SaveCoFileNameLength;
|
|
FStatus = FrsErrorNotFound;
|
|
break;
|
|
}
|
|
|
|
|
|
if (LenLimit == ESE_INDEX_LENGTH_LIMIT) {
|
|
TempUStr.Length = IDTFileNameLength;
|
|
ChangeOrder->UFileName.Length = SaveCoFileNameLength;
|
|
}
|
|
|
|
//
|
|
// We found a match, maybe.
|
|
//
|
|
DBS_DISPLAY_RECORD_SEV(5, IDTableCtxNC, TRUE);
|
|
|
|
//
|
|
// Check for undeleted record where the File Guid does not match the CO.
|
|
// If such a record is found then we have a name morph conflict.
|
|
//
|
|
if ((!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED) ||
|
|
IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETE_DEFERRED))&&
|
|
!GUIDS_EQUAL(&CoCmd->FileGuid, &IDTableRec->FileGuid)) {
|
|
|
|
//
|
|
// Now do the full len compare if the lengths were greater than
|
|
// ESE_INDEX_LENGTH_LIMIT. If the lengths are over the index
|
|
// limit and the strings fail to compare equal then keep looking.
|
|
//
|
|
if ((LenLimit != ESE_INDEX_LENGTH_LIMIT) ||
|
|
RtlEqualUnicodeString(&TempUStr, &ChangeOrder->UFileName, TRUE)) {
|
|
FStatus = FrsErrorSuccess;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// go to next record in this index.
|
|
//
|
|
jerr = JetMove(Sesid, Tid, JET_MoveNext, 0);
|
|
|
|
//
|
|
// If the record is not there then no conflict
|
|
//
|
|
if (jerr == JET_errNoCurrentRecord ) {
|
|
FStatus = FrsErrorNotFound;
|
|
break;
|
|
}
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|
DPRINT_FS(0, "++ JetMove error:", FStatus);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Success return above means we found a record with a matching parent
|
|
// Guid and File name but a different File Guid. This is a name morph
|
|
// conflict.
|
|
//
|
|
if (FRS_SUCCESS(FStatus)) {
|
|
DPRINT(0,"++ NM: Possible Name Morph Conflict\n");
|
|
DBS_DISPLAY_RECORD_SEV(4, IDTableCtxNC, TRUE);
|
|
FStatus = FrsErrorNameMorphConflict;
|
|
}
|
|
else
|
|
if (FStatus != FrsErrorNotFound) {
|
|
DPRINT_FS(0,"++ NM: WARNING - Unexpected result from DbsTableRead.", FStatus);
|
|
} else {
|
|
FStatus = FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, IDTableCtxNC);
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ChgOrdProcessControlCo(
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
IN PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Process the control change order.
|
|
|
|
Arguments:
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdProcessControlCo:"
|
|
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
PCXTION CtlCxtion;
|
|
|
|
switch (CoCmd->ContentCmd) {
|
|
|
|
|
|
case FCN_CORETRY_LOCAL_ONLY:
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CORETRY_LOCAL_ONLY Ctrl CO");
|
|
break;
|
|
|
|
|
|
|
|
//
|
|
// Retrying COs from a single inbound connection.
|
|
//
|
|
case FCN_CORETRY_ONE_CXTION:
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CORETRY_ONE_CXTION Ctrl CO");
|
|
|
|
//
|
|
// Find the inbound cxtion for this CO and advance state to starting.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
CtlCxtion = GTabLookupNoLock(Replica->Cxtions, &CoCmd->CxtionGuid, NULL);
|
|
if (CtlCxtion != NULL) {
|
|
FRS_ASSERT(CtlCxtion->Inbound);
|
|
|
|
//
|
|
// If cxtion no longer in the request start state leave it alone
|
|
// else advance to STARTING. Doing this here ensures that we
|
|
// don't hold issue on the process queue (and hang) if there are
|
|
// lingering COs for this Conection ahead of us. I.E. any COs
|
|
// we see for this cxtion now are either retry COs from the inlog
|
|
// or newly arrived COs.
|
|
//
|
|
if (CxtionStateIs(CtlCxtion, CxtionStateStarting)) {
|
|
//
|
|
// The cxtion is attempting to unjoin. Skip the inbound log
|
|
// scan and advance to the SENDJOIN state so the UNJOIN will
|
|
// proceed.
|
|
//
|
|
if (CxtionFlagIs(CtlCxtion, (CXTION_FLAGS_DEFERRED_UNJOIN |
|
|
CXTION_FLAGS_DEFERRED_DELETE))) {
|
|
SetCxtionState(CtlCxtion, CxtionStateSendJoin);
|
|
RcsSubmitReplicaCxtion(Replica, CtlCxtion, CMD_JOIN_CXTION);
|
|
} else {
|
|
SetCxtionState(CtlCxtion, CxtionStateScanning);
|
|
//
|
|
// Connection startup request. Fork a thread and
|
|
// paw thru Inlog.
|
|
//
|
|
ChgOrdRetrySubmit(Replica,
|
|
CtlCxtion,
|
|
FCN_CORETRY_ONE_CXTION,
|
|
FALSE);
|
|
}
|
|
}
|
|
} else {
|
|
DPRINT(0, "++ ERROR - No connection struct for control cmd.\n");
|
|
FRS_ASSERT(!"No connection struct for control CO");
|
|
}
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
break;
|
|
|
|
|
|
case FCN_CORETRY_ALL_CXTIONS:
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CORETRY_ALL_CXTIONS Ctrl CO");
|
|
|
|
break;
|
|
|
|
|
|
case FCN_CO_NORMAL_VVJOIN_TERM:
|
|
|
|
CHANGE_ORDER_TRACE(3, ChangeOrder, "CO_NORMAL_VVJOIN_TER Ctrl CO");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
DPRINT1(0, "++ ERROR - Invalid control command: %d\n", CoCmd->ContentCmd);
|
|
FRS_ASSERT(!"Invalid control command in CO");
|
|
}
|
|
|
|
|
|
//
|
|
// Done with control cmd. Caller is expected to
|
|
// release process queue lock, cleanup and unidle the queue.
|
|
//
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ChgOrdTranslateGuidFid(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX IDTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Perform translation of FIDs to Guids and vice versa.
|
|
Return bit mask for delete status of target file and the original and
|
|
new parent dirs.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
IDTableCtx -- The table context to use for the IDTable lookup.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
|
|
Return Value:
|
|
|
|
The results of the call are returned in the change order.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdTranslateGuidFid:"
|
|
|
|
|
|
FILE_OBJECTID_BUFFER ObjectIdBuffer;
|
|
ULONGLONG TargetFid;
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
BOOL CoFromInlog, MorphGenCo;
|
|
ULONG FStatus;
|
|
HANDLE Handle;
|
|
DWORD WStatus;
|
|
|
|
|
|
CoFromInlog = CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY) ||
|
|
RecoveryCo(ChangeOrder);
|
|
|
|
MorphGenCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_MORPH_GEN);
|
|
|
|
CLEAR_COE_FLAG(ChangeOrder, (COE_FLAG_IDT_ORIG_PARENT_DEL |
|
|
COE_FLAG_IDT_ORIG_PARENT_ABS |
|
|
COE_FLAG_IDT_NEW_PARENT_DEL |
|
|
COE_FLAG_IDT_NEW_PARENT_ABS |
|
|
COE_FLAG_IDT_NEW_PARENT_DEL_DEF |
|
|
COE_FLAG_IDT_TARGET_DEL |
|
|
COE_FLAG_IDT_TARGET_ABS));
|
|
|
|
//
|
|
// Local COs translate FIDs to Guids. Unless they came from the Inlog
|
|
// or were produced by Name Morph resolution.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO) && !MorphGenCo && !CoFromInlog) {
|
|
//
|
|
// Fill in the Originator Guid for the local change order.
|
|
//
|
|
COPY_GUID(&CoCmd->OriginatorGuid, &Replica->ReplicaVersionGuid);
|
|
|
|
//
|
|
// (Do it here in case we create a new ID table record).
|
|
// We supply IDTableCtx to avoid a storage realloc on every call.
|
|
//
|
|
// Get status of original parent.
|
|
//
|
|
FStatus = DbsFidToGuid(ThreadCtx,
|
|
ChangeOrder->OriginalReplica,
|
|
IDTableCtx,
|
|
&ChangeOrder->OriginalParentFid,
|
|
&CoCmd->OldParentGuid);
|
|
|
|
if ((FStatus == FrsErrorIdtFileIsDeleted) ||
|
|
(FStatus == FrsErrorIdtFileIsDeleteDef)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_DEL);
|
|
} else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
}
|
|
|
|
//
|
|
// Get Status of New Parent.
|
|
//
|
|
if (ChangeOrder->NewParentFid != ChangeOrder->OriginalParentFid) {
|
|
FStatus = DbsFidToGuid(ThreadCtx,
|
|
ChangeOrder->NewReplica,
|
|
IDTableCtx,
|
|
&ChangeOrder->NewParentFid,
|
|
&CoCmd->NewParentGuid);
|
|
} else {
|
|
CoCmd->NewParentGuid = CoCmd->OldParentGuid;
|
|
}
|
|
|
|
if (FStatus == FrsErrorIdtFileIsDeleted) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
|
|
} else if (FStatus == FrsErrorIdtFileIsDeleteDef) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL_DEF);
|
|
|
|
} else if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_ABS);
|
|
}
|
|
|
|
ChangeOrder->pParentGuid = (ChangeOrder->NewReplica != NULL) ?
|
|
&CoCmd->NewParentGuid :
|
|
&CoCmd->OldParentGuid;
|
|
//
|
|
// Get status of target file or dir.
|
|
//
|
|
FStatus = DbsFidToGuid(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
&CoCmd->FileGuid);
|
|
|
|
if ((FStatus == FrsErrorIdtFileIsDeleted) ||
|
|
(FStatus == FrsErrorIdtFileIsDeleteDef)){
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_TARGET_DEL);
|
|
} else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_TARGET_ABS);
|
|
}
|
|
|
|
//
|
|
// Read object ID from file. This is done here so we have it for
|
|
// the hold issue check to deal with a tunnelled OID.
|
|
//
|
|
CLEAR_COE_FLAG(ChangeOrder, COE_FLAG_OID_FROM_FILE);
|
|
|
|
WStatus = FrsOpenSourceFileById(&Handle,
|
|
NULL,
|
|
NULL,
|
|
Replica->pVme->VolumeHandle,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
FILE_ID_LENGTH,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
WStatus = FrsGetObjectId(Handle, &ObjectIdBuffer);
|
|
FRS_CLOSE(Handle);
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
COPY_GUID(&ChangeOrder->FileObjectId, &ObjectIdBuffer.ObjectId);
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_OID_FROM_FILE);
|
|
}
|
|
} else {
|
|
DPRINT1_WS(4, "++ Could not open (Id %08x %08x) for oid read;",
|
|
PRINTQUAD(ChangeOrder->FileReferenceNumber), WStatus);
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// Remote COs and recovery and retry Local COs translate Guids to Fids
|
|
// because the FID is not saved in the change order command.
|
|
// **NOTE** -- If this happened to be a CO for a child that just
|
|
// forced the reanimation of its parent then this CO was pushed back
|
|
// onto the CO process queue with the "old parent FID" so the parent
|
|
// reanimation could then proceed. Now when the child has been
|
|
// restarted the parent FID will have changed and the GUID to FID
|
|
// translation here will pick that change up.
|
|
//
|
|
Replica = ChangeOrder->NewReplica;
|
|
|
|
//
|
|
// Get status of original parent.
|
|
//
|
|
FStatus = DbsGuidToFid(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&CoCmd->OldParentGuid,
|
|
&ChangeOrder->OriginalParentFid);
|
|
if ((FStatus == FrsErrorIdtFileIsDeleted) ||
|
|
(FStatus == FrsErrorIdtFileIsDeleteDef)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_DEL);
|
|
} else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_ORIG_PARENT_ABS);
|
|
}
|
|
|
|
//
|
|
// Get Status of New Parent.
|
|
//
|
|
if (!GUIDS_EQUAL(&CoCmd->NewParentGuid, &CoCmd->OldParentGuid)) {
|
|
FStatus = DbsGuidToFid(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&CoCmd->NewParentGuid,
|
|
&ChangeOrder->NewParentFid);
|
|
} else {
|
|
ChangeOrder->NewParentFid = ChangeOrder->OriginalParentFid;
|
|
}
|
|
|
|
if (FStatus == FrsErrorIdtFileIsDeleted) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
|
|
} else if (FStatus == FrsErrorIdtFileIsDeleteDef) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL);
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_DEL_DEF);
|
|
|
|
} else if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_NEW_PARENT_ABS);
|
|
}
|
|
|
|
//
|
|
// For remote change orders, set the Parent FID from the NewParent Fid.
|
|
//
|
|
ChangeOrder->ParentFileReferenceNumber = ChangeOrder->NewParentFid;
|
|
ChangeOrder->pParentGuid = &CoCmd->NewParentGuid;
|
|
|
|
//
|
|
// For a morph generated CO that has a FID already, preserve it.
|
|
// Delete MorphGenCos are an example where the incoming CO is losing
|
|
// so the FID is fake for the tombstone.
|
|
// Still need to get the status of the IDTable entry for the target.
|
|
// This is used to abort the MorphGenCo later in those cases where
|
|
// the conflict causing entry was deleted or aborted.
|
|
//
|
|
FStatus = DbsGuidToFid(ThreadCtx,
|
|
Replica,
|
|
IDTableCtx,
|
|
&CoCmd->FileGuid,
|
|
&TargetFid);
|
|
|
|
if ((FStatus == FrsErrorIdtFileIsDeleted) ||
|
|
(FStatus == FrsErrorIdtFileIsDeleteDef)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_TARGET_DEL);
|
|
} else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
SET_COE_FLAG(ChangeOrder, COE_FLAG_IDT_TARGET_ABS);
|
|
}
|
|
|
|
if (!MorphGenCo || (ChangeOrder->FileReferenceNumber == ZERO_FID)) {
|
|
ChangeOrder->FileReferenceNumber = TargetFid;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdInsertInlogRecord(
|
|
PTHREAD_CTX ThreadCtx,
|
|
PTABLE_CTX InLogTableCtx,
|
|
PCHANGE_ORDER_ENTRY ChangeOrder,
|
|
PREPLICA Replica,
|
|
BOOL RetryOutOfOrder
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert the Change order command into the inbound log.
|
|
Update the Inlog Commit USN.
|
|
|
|
Arguments:
|
|
ThreadCtx -- A Thread context to use for dbid and sesid.
|
|
InLogTableCtx -- The table context to use for the Inbound log insert.
|
|
ChangeOrder-- The change order.
|
|
Replica -- The Replica struct.
|
|
RetryOutOfOrder -- TRUE - insert into the inbound log with the
|
|
retry flag set and marked as out of order.
|
|
FALSE - Don't set retry or out-of-order.
|
|
|
|
Return Value:
|
|
|
|
FrsErrorStatus
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertInlogRecord:"
|
|
|
|
|
|
ULONGLONG SeqNum;
|
|
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
|
|
ULONG FStatus;
|
|
ULONG LocationCmd;
|
|
ULONG NewState;
|
|
BOOL LocalCo = CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO);
|
|
|
|
|
|
//
|
|
// Convert the replica pointers to the local replica ID numbers for storing
|
|
// the record in the database.
|
|
//
|
|
CoCmd->OriginalReplicaNum = ReplicaAddrToId(ChangeOrder->OriginalReplica);
|
|
CoCmd->NewReplicaNum = ReplicaAddrToId(ChangeOrder->NewReplica);
|
|
|
|
//
|
|
// Transform ChangeOrder as needed:
|
|
// 1. MOVEIN -> CREATE
|
|
// 2. MOVEOUT -> DELETE
|
|
// 3. MOVEDIR -> MOVEDIR
|
|
// 4. MOVERS -> DELETE to old rs, CREATE to new rs.
|
|
//
|
|
LocationCmd = GET_CO_LOCATION_CMD(ChangeOrder->Cmd, Command);
|
|
//
|
|
// MOVERS: Add code for movers case when supported.
|
|
//
|
|
|
|
//
|
|
// Change order starts out in the staging file requested state unless it
|
|
// is a duplicate co or a retry-preinstall co. If duplicate co or a
|
|
// retry-preinstall co then mark it as retry out-of-order.
|
|
//
|
|
if (LocalCo) {
|
|
NewState = IBCO_STAGING_REQUESTED;
|
|
|
|
//
|
|
// Normally the change order record extension is allocated for local COs
|
|
// when the co cmd is inserted into the inbound log (by the call to
|
|
// DbsAllocRecordStorage()). But this can be a problem if the local
|
|
// co ends up going thru retry (e.g. sharing violation) where we update
|
|
// the extension in the inlog record. If the extension is null then
|
|
// we end up failing an assert. So allocate and init the contents here.
|
|
//
|
|
if (CoCmd->Extension == NULL) {
|
|
CoCmd->Extension = FrsAlloc(sizeof(CHANGE_ORDER_RECORD_EXTENSION));
|
|
DbsDataInitCocExtension(CoCmd->Extension);
|
|
DPRINT(4, "Allocating initial Coc Extension for localco\n");
|
|
}
|
|
|
|
|
|
} else {
|
|
NewState = (RetryOutOfOrder ? IBCO_FETCH_RETRY : IBCO_FETCH_REQUESTED);
|
|
}
|
|
|
|
//
|
|
// During retry, the duplicate co or preinstall-retry co will not
|
|
// get a retire slot because it is marked out-of-order.
|
|
//
|
|
if (RetryOutOfOrder) {
|
|
SET_CO_FLAG(ChangeOrder, CO_FLAG_RETRY | CO_FLAG_OUT_OF_ORDER);
|
|
}
|
|
|
|
SET_CHANGE_ORDER_STATE(ChangeOrder, NewState);
|
|
|
|
//
|
|
// Get the lock on the Active Retry table.
|
|
//
|
|
QHashAcquireLock(Replica->ActiveInlogRetryTable);
|
|
|
|
//
|
|
// Insert the record into the Inbound log table. The value of the Jet
|
|
// assigned sequence number is returned in the Sequence number field.
|
|
// Set the record data pointer to command portion of the change order
|
|
// entry. The RtCtx (containing the InLogTableCtx) accompanies the
|
|
// change order as it is processed.
|
|
//
|
|
FStatus = DbsInsertTable(ThreadCtx,
|
|
Replica,
|
|
InLogTableCtx,
|
|
INLOGTablex,
|
|
&ChangeOrder->Cmd);
|
|
if (!FRS_SUCCESS(FStatus) && (RetryOutOfOrder)) {
|
|
//
|
|
// Never made it into the inlog so clear the flags.
|
|
//
|
|
CLEAR_CO_FLAG(ChangeOrder, CO_FLAG_RETRY | CO_FLAG_OUT_OF_ORDER);
|
|
}
|
|
|
|
|
|
if ((FStatus == FrsErrorKeyDuplicate) ||
|
|
(FStatus == FrsErrorDbWriteConflict)) {
|
|
//
|
|
// This can happen duing restart if we already have a change order
|
|
// from a given connection in the inbound log and the inbound
|
|
// partner sends it again because it didn't get an Ack when it sent
|
|
// it the first time. This should be a remote CO.
|
|
//
|
|
// The FrsErrorDbWriteConflict is returned when the change order is
|
|
// being deleted while the insert is attempted. Treat this insert
|
|
// as a duplicate.
|
|
//
|
|
QHashReleaseLock(Replica->ActiveInlogRetryTable);
|
|
return FrsErrorKeyDuplicate;
|
|
} else
|
|
if (!FRS_SUCCESS(FStatus)) {
|
|
DPRINT_FS(0, "++ Error from DbsInsertTable.", FStatus);
|
|
QHashReleaseLock(Replica->ActiveInlogRetryTable);
|
|
return FStatus;
|
|
}
|
|
|
|
|
|
// There is a small window here where the new CO is in the inlog
|
|
// and the CO SequenceNumber is not in the ActiveInlogRetryTable
|
|
// below. If the retry thread was active and saw the inlog record
|
|
// but did not see the entry in the ActiveInlogRetryTable then it
|
|
// could process the entry and try to ref the CO on the VVRetire
|
|
// list. If this was a DUP CO it may cause an assert because the CO
|
|
// is not marked activated yet this is a retry CO. The problem is
|
|
// that entry on the VVRetire list is for ANOTHER CO already in
|
|
// process. Although that in-process CO has not yet activated the
|
|
// VV Slot.
|
|
// >>>>> One way to fix this is to stop having Jet assign the seqnum
|
|
// Additional problem below requires the table lock.
|
|
//
|
|
// Another way we run into problems is as follows: (BUG 213163)
|
|
// 1. Dup Remote Co arrives
|
|
// 2. Insert into inlog and code below adds seq num to ActiveInlogRetryTable.
|
|
// 3. Ctx switch to retry thread resubmits this CO (Instance A) and makes
|
|
// entry in ActiveInlogRetryTable.
|
|
// 4. Ctx switch back to Dup Co which cleans up and removes entry from
|
|
// ActiveInlogRetryTable.
|
|
// 5. Retry thread runs again and re-inserts Dup Remote Co a 2nd time, Instance B.
|
|
// 6. Instance A runs to completion (may get rejected) and deletes inlog record.
|
|
// 7. Instance B runs to completion and asserts when it tries to delete
|
|
// inlog record a 2nd time and doesn't find it.
|
|
//
|
|
// To prevent this we take the lock on the ActiveInlogRetryTable above.
|
|
//
|
|
|
|
//
|
|
// All COs go into the inlog retry table to lock against reissue by the
|
|
// retry thread. See comment at FCN_CORETRY_ALL_CXTIONS for reason.
|
|
// Qhash Insert has to go after DB insert cuz DB Insert assigns the seq num.
|
|
//
|
|
SeqNum = (ULONGLONG) ChangeOrder->Cmd.SequenceNumber;
|
|
QHashInsertLock(Replica->ActiveInlogRetryTable, &SeqNum, NULL, 0);
|
|
QHashReleaseLock(Replica->ActiveInlogRetryTable);
|
|
|
|
//
|
|
// Update count of change orders we need to retry for this replica.
|
|
//
|
|
if (RetryOutOfOrder) {
|
|
InterlockedIncrement(&Replica->InLogRetryCount);
|
|
}
|
|
|
|
//
|
|
// Update the USN Journal Save point for this replica set. This is the
|
|
// value saved the next time we update the save point in the config record.
|
|
// Local change orders enter the volume process queue in order.
|
|
// So any change orders earlier than this have either been dampened, rejected,
|
|
// or written to the inbound log. JrnlFirstUsn can be zero if it is a
|
|
// change order produced by a sub-tree operation. Only the Change Order
|
|
// on the root dir will update the InlogCommitUsn.
|
|
//
|
|
if (LocalCo) {
|
|
if (CoCmd->JrnlFirstUsn != (USN) 0) {
|
|
DPRINT1(4, "++ Replica->InlogCommitUsn: %08x %08x\n",
|
|
PRINTQUAD(Replica->InlogCommitUsn));
|
|
|
|
if (Replica->InlogCommitUsn < CoCmd->JrnlFirstUsn) {
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
//
|
|
// Get the lock and make the test again.
|
|
//
|
|
AcquireQuadLock(&pVme->QuadWriteLock);
|
|
if (Replica->InlogCommitUsn <= CoCmd->JrnlFirstUsn) {
|
|
Replica->InlogCommitUsn = CoCmd->JrnlFirstUsn;
|
|
ReleaseQuadLock(&pVme->QuadWriteLock);
|
|
} else {
|
|
ReleaseQuadLock(&pVme->QuadWriteLock);
|
|
FRS_ASSERT(Replica->InlogCommitUsn <= CoCmd->JrnlFirstUsn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
|
|
|
|
JET_ERR
|
|
ChgOrdMoveoutWorker(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Record,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a worker function passed to FrsEnumerateTable(). Each time
|
|
it is called with an IDTable record it tests the IDTable for a match
|
|
with the parent file ID parameter. If it matches it generates a
|
|
Delete Change Order for the child and pushes it onto the change order
|
|
process queue.
|
|
|
|
Arguments:
|
|
|
|
ThreadCtx - Needed to access Jet.
|
|
TableCtx - A ptr to an IDTable context struct.
|
|
Record - A ptr to a IDTable record.
|
|
Context - A ptr to a MOVEOUT_CONTEXT struct.
|
|
|
|
Thread Return Value:
|
|
|
|
A Jet error status. Success means call us with the next record.
|
|
Failure means don't call again and pass our status back to the
|
|
caller of FrsEnumerateTable().
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMoveoutWorker:"
|
|
|
|
JET_ERR jerr = JET_errSuccess;
|
|
PCHANGE_ORDER_ENTRY DelCoe;
|
|
ULONG WStatus;
|
|
ULONG LocationCmd;
|
|
|
|
PREPLICA Replica;
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
|
|
PMOVEOUT_CONTEXT MoveOutContext = (PMOVEOUT_CONTEXT) Context;
|
|
PIDTABLE_RECORD IDTableRec = (PIDTABLE_RECORD) Record ;
|
|
|
|
|
|
Replica = MoveOutContext->Replica;
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
pVme = Replica->pVme;
|
|
|
|
//
|
|
// Include the entry if replication is enabled and not marked for deletion
|
|
// and not a new file being created when we last shutdown.
|
|
//
|
|
if (IDTableRec->ReplEnabled &&
|
|
!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_DELETED) &&
|
|
!IsIdRecFlagSet(IDTableRec, IDREC_FLAGS_NEW_FILE_IN_PROGRESS) &&
|
|
(MoveOutContext->ParentFileID == IDTableRec->ParentFileID)) {
|
|
|
|
//
|
|
// Normally there should be no dirs that are children of this parent
|
|
// but this can happen if a remote co create is executed for a
|
|
// dir at the same time the subtree containing this dir is being
|
|
// moved out of the replica tree (which is why we are here).
|
|
// The journal code removes the filter entries immediately when it
|
|
// detects a moveout so it can skip future file changes in the subtree.
|
|
// So any dir we now find must have come from a remote CO create that
|
|
// occurred between the time when the journal processed the rename for
|
|
// moveout subdir and now when we are processing the resulting dir
|
|
// moveout CO. We handle this by continuing the delete process on this
|
|
// subidr. This will also get rid of the orphaned journal filter table
|
|
// entry created when the remote co for this dir create was processed.
|
|
// See comment in JrnlFilterLinkChildNoError().
|
|
//
|
|
LocationCmd = CO_LOCATION_DELETE;
|
|
if (IDTRecIsDirectory(IDTableRec)) {
|
|
LocationCmd = CO_LOCATION_MOVEOUT;
|
|
DBS_DISPLAY_RECORD_SEV(0, TableCtx, TRUE);
|
|
DPRINT(3, "++ Hit a dir during MOVEOUT subdir processing\n");
|
|
}
|
|
|
|
//
|
|
// Build a delete or moveout change order entry for the IDtable record.
|
|
// This is a local CO that originates from this member.
|
|
//
|
|
DelCoe = ChgOrdMakeFromIDRecord(IDTableRec,
|
|
Replica,
|
|
LocationCmd,
|
|
CO_FLAG_LOCATION_CMD,
|
|
NULL);
|
|
|
|
//
|
|
// Set the Jrnl Cxtion Guid and Cxtion ptr for this Local CO.
|
|
//
|
|
INIT_LOCALCO_CXTION_AND_COUNT(Replica, DelCoe);
|
|
|
|
//
|
|
// Generate a new Volume Sequnce Number for the change order.
|
|
// But since it gets put on the front of the CO process queue it
|
|
// is probably out of order so set the flag to avoid screwing up
|
|
// dampening.
|
|
//
|
|
NEW_VSN(pVme, &DelCoe->Cmd.FrsVsn);
|
|
DelCoe->Cmd.OriginatorGuid = ConfigRecord->ReplicaVersionGuid;
|
|
|
|
//
|
|
// Event time from current time.
|
|
//
|
|
// Note: An alternative is to base this event time on the event time
|
|
// in the original USN record that initiated the moveout?
|
|
// Currently there is no compelling reason to change.
|
|
//
|
|
GetSystemTimeAsFileTime((PFILETIME)&DelCoe->Cmd.EventTime.QuadPart);
|
|
|
|
//
|
|
// Bump Version number to ensure the CO is accepted.
|
|
//
|
|
DelCoe->Cmd.FileVersionNumber = IDTableRec->VersionNumber + 1;
|
|
|
|
//
|
|
// Note: We wouldn't need to mark this CO as out of order if we
|
|
// resequenced all change order VSNs (for new COs only) as they
|
|
// were fetched off the process queue.
|
|
//
|
|
SET_CO_FLAG(DelCoe, CO_FLAG_LOCALCO |
|
|
CO_FLAG_MORPH_GEN |
|
|
CO_FLAG_OUT_OF_ORDER);
|
|
//
|
|
// Mark this as a MOVEOUT generated Delete CO. This prevents the
|
|
// CO from being inserted into the INLOG and keeps it from deleting
|
|
// the actual file on the Local Machine.
|
|
//
|
|
SET_COE_FLAG(DelCoe, COE_FLAG_DELETE_GEN_CO);
|
|
|
|
SET_CHANGE_ORDER_STATE(DelCoe, IBCO_STAGING_REQUESTED);
|
|
|
|
if (LocationCmd == CO_LOCATION_DELETE) {
|
|
CHANGE_ORDER_TRACE(3, DelCoe, "Co Push Moveout DelCo to QHead");
|
|
} else {
|
|
CHANGE_ORDER_TRACE(3, DelCoe, "Co Push Moveout subdir to QHead");
|
|
}
|
|
|
|
//
|
|
// Push the IDTable Delete Co onto the head of the queue.
|
|
//
|
|
MoveOutContext->NumFiles += 1;
|
|
|
|
WStatus = ChgOrdInsertProcQ(Replica, DelCoe, IPQ_HEAD |
|
|
IPQ_DEREF_CXTION_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
jerr = JET_errNotInitialized;
|
|
}
|
|
}
|
|
|
|
return jerr;
|
|
}
|
|
|
|
|
|
|
|
|
|
ChgOrdMoveInDirTreeWorker(
|
|
IN HANDLE DirectoryHandle,
|
|
IN PWCHAR DirectoryName,
|
|
IN DWORD DirectoryLevel,
|
|
IN PFILE_DIRECTORY_INFORMATION DirectoryRecord,
|
|
IN DWORD DirectoryFlags,
|
|
IN PWCHAR FileName,
|
|
IN PMOVEIN_CONTEXT MoveInContext
|
|
)
|
|
{
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a worker function passed to FrsEnumerateDirectory() to touch files
|
|
and cause USN records to be created so the files will be replicated.
|
|
|
|
Before the files are touched checks are made against the filename or
|
|
directory name exclusion filters and the directory level.
|
|
|
|
The MoveInContext supplies the FID of the parent dir, the pointer to the
|
|
Replica struct and a flag indicating if this is a Directory scan pass
|
|
(if TRUE) or a File Scan Pass (if FALSE).
|
|
|
|
Arguments:
|
|
|
|
DirectoryHandle - Handle for this directory.
|
|
DirectoryName - Relative name of directory
|
|
DirectoryLevel - Directory level (0 == root)
|
|
DirectoryFlags - See tablefcn.h, ENUMERATE_DIRECTORY_FLAGS_
|
|
DirectoryRecord - Record from DirectoryHandle
|
|
FileName - From DirectoryRecord (w/terminating NULL)
|
|
MoveInContext - global info and state
|
|
|
|
Return Value:
|
|
|
|
WIN32 Error Status.
|
|
|
|
--*/
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMoveInDirTreeWorker:"
|
|
|
|
|
|
FILE_BASIC_INFORMATION FileBasicInfo;
|
|
FILE_INTERNAL_INFORMATION FileInternalInfo;
|
|
|
|
UNICODE_STRING ObjectName;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
HANDLE FileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
DWORD WStatus;
|
|
NTSTATUS NtStatus;
|
|
|
|
PREPLICA Replica;
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
ULONG LevelCheck;
|
|
|
|
BOOL FileIsDir;
|
|
BOOL Excluded;
|
|
|
|
|
|
if (FrsIsShuttingDown) {
|
|
DPRINT(0, "++ WARN - Movein aborted; service shutting down\n");
|
|
return ERROR_OPERATION_ABORTED;
|
|
}
|
|
|
|
//
|
|
// Filter out temporary files.
|
|
//
|
|
if (DirectoryRecord->FileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
FileIsDir = BooleanFlagOn(DirectoryRecord->FileAttributes,
|
|
FILE_ATTRIBUTE_DIRECTORY);
|
|
|
|
//
|
|
// Choose filter list and level (caller has filtered . and ..)
|
|
//
|
|
Replica = MoveInContext->Replica;
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
pVme = Replica->pVme;
|
|
|
|
if (FileIsDir) {
|
|
//
|
|
// No dirs at the bottom level in the volume filter table.
|
|
//
|
|
LevelCheck = ConfigRecord->ReplDirLevelLimit-1;
|
|
MoveInContext->NumDirs++;
|
|
} else {
|
|
//
|
|
// Files are allowed at the bottom level.
|
|
//
|
|
LevelCheck = ConfigRecord->ReplDirLevelLimit;
|
|
MoveInContext->NumFiles++;
|
|
}
|
|
|
|
//
|
|
// If the Level Limit is exceeded then skip the file or dir.
|
|
// Skip files or dirs matching an entry in the respective exclusion list.
|
|
//
|
|
if ((DirectoryLevel >= LevelCheck)) {
|
|
MoveInContext->NumFiltered++;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
ObjectName.Length = (USHORT)DirectoryRecord->FileNameLength;
|
|
ObjectName.MaximumLength = (USHORT)DirectoryRecord->FileNameLength;
|
|
ObjectName.Buffer = DirectoryRecord->FileName;
|
|
|
|
LOCK_REPLICA(Replica);
|
|
Excluded = FALSE;
|
|
|
|
if (FileIsDir) {
|
|
if (!FrsCheckNameFilter(&ObjectName, &Replica->DirNameInclFilterHead)) {
|
|
Excluded = FrsCheckNameFilter(&ObjectName, &Replica->DirNameFilterHead);
|
|
}
|
|
} else {
|
|
if (!FrsCheckNameFilter(&ObjectName, &Replica->FileNameInclFilterHead)) {
|
|
Excluded = FrsCheckNameFilter(&ObjectName, &Replica->FileNameFilterHead);
|
|
}
|
|
}
|
|
|
|
UNLOCK_REPLICA(Replica);
|
|
|
|
if (Excluded) {
|
|
MoveInContext->NumFiltered++;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Open the file to get the FID for the local CO.
|
|
// Open with WRITE Access in case we need to write the Object ID.
|
|
// Relative open
|
|
//
|
|
ZeroMemory(&ObjectAttributes, sizeof(OBJECT_ATTRIBUTES));
|
|
ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
|
|
ObjectAttributes.ObjectName = &ObjectName;
|
|
ObjectAttributes.RootDirectory = DirectoryHandle;
|
|
|
|
NtStatus = NtCreateFile(
|
|
&FileHandle,
|
|
// READ_ACCESS | WRITE_ACCESS | ATTR_ACCESS,
|
|
READ_ATTRIB_ACCESS | WRITE_ATTRIB_ACCESS,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL, // AllocationSize
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
OPEN_OPTIONS,
|
|
NULL, // EA buffer
|
|
0); // EA buffer size
|
|
|
|
//
|
|
// Error opening file or directory
|
|
//
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
DPRINT1_NT(0, "++ ERROR - Skipping %ws: NtCreateFile().", FileName, NtStatus);
|
|
|
|
//
|
|
// Could be a file attribute problem. Get the FID and try again.
|
|
//
|
|
NtStatus = NtCreateFile(&FileHandle,
|
|
READ_ATTRIB_ACCESS,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL, // AllocationSize
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
SHARE_ALL,
|
|
FILE_OPEN,
|
|
OPEN_OPTIONS,
|
|
NULL, // EA buffer
|
|
0); // EA buffer size
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
DPRINT1_NT(0, "++ ERROR - Skipping %ws: NtCreateFile()", FileName, NtStatus);
|
|
|
|
FileHandle = INVALID_HANDLE_VALUE;
|
|
WStatus = FrsSetLastNTError(NtStatus);
|
|
goto RETURN;
|
|
}
|
|
|
|
WStatus = FrsGetFileInternalInfoByHandle(FileHandle, &FileInternalInfo);
|
|
CLEANUP1_WS(0, "++ ERROR - FrsGetFileInternalInfoByHandle(%ws); ",
|
|
FileName, WStatus, RETURN);
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
WStatus = FrsForceOpenId(&FileHandle,
|
|
NULL,
|
|
pVme,
|
|
&FileInternalInfo.IndexNumber.QuadPart,
|
|
FILE_ID_LENGTH,
|
|
// READ_ACCESS | WRITE_ACCESS | ATTR_ACCESS,
|
|
READ_ATTRIB_ACCESS | WRITE_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
|
|
//
|
|
// File has been deleted; done
|
|
//
|
|
if (WIN_NOT_FOUND(WStatus)) {
|
|
DPRINT2(4, "++ %ws (Id %08x %08x) has been deleted\n",
|
|
FileName, PRINTQUAD(FileInternalInfo.IndexNumber.QuadPart));
|
|
goto RETURN;
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
goto RETURN;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Get the file's last write time.
|
|
//
|
|
ZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
|
|
NtStatus = NtQueryInformationFile(FileHandle,
|
|
&IoStatusBlock,
|
|
&FileBasicInfo,
|
|
sizeof(FileBasicInfo),
|
|
FileBasicInformation);
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
DPRINT_NT(0, "++ NtQueryInformationFile - FileBasicInformation failed.",
|
|
NtStatus);
|
|
WStatus = FrsSetLastNTError(NtStatus);
|
|
goto RETURN;
|
|
}
|
|
|
|
//
|
|
// Poke the file by setting the last write time.
|
|
//
|
|
FileBasicInfo.LastWriteTime.QuadPart += 1;
|
|
if (!SetFileTime(FileHandle, NULL, NULL, (PFILETIME) &FileBasicInfo.LastWriteTime)) {
|
|
WStatus = GetLastError();
|
|
DPRINT1_WS(0, "++ ERROR - Unable to set last write time on %ws.", FileName, WStatus);
|
|
goto RETURN;
|
|
}
|
|
|
|
FileBasicInfo.LastWriteTime.QuadPart -= 1;
|
|
if (!SetFileTime(FileHandle, NULL, NULL, (PFILETIME) &FileBasicInfo.LastWriteTime)) {
|
|
WStatus = GetLastError();
|
|
DPRINT1_WS(0, "++ ERROR - Unable to set last write time on %ws.", FileName, WStatus);
|
|
goto RETURN;
|
|
}
|
|
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
|
RETURN:
|
|
|
|
if (DirectoryFlags & ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE) {
|
|
WStatus = ERROR_SUCCESS;
|
|
}
|
|
|
|
MoveInContext->NumSkipped++;
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdMoveInDirTree(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_ENTRY ChangeOrder
|
|
)
|
|
{
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Walk the Sub dir specified in the MOVEIN Change order and produce change
|
|
orders for each child.
|
|
|
|
Arguments:
|
|
|
|
Replica -- ptr to the replica struct.
|
|
ChangeOrder -- The MoveIn Dir change order.
|
|
|
|
Return Value:
|
|
|
|
WIN32 Error Status.
|
|
|
|
--*/
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMoveInDirTree:"
|
|
|
|
MOVEIN_CONTEXT MoveInContext;
|
|
|
|
ULONG WStatus;
|
|
ULONG FStatus;
|
|
HANDLE FileHandle = INVALID_HANDLE_VALUE;
|
|
ULONG Level;
|
|
PCHANGE_ORDER_RECORD CoCmd = &ChangeOrder->Cmd;
|
|
PVOLUME_MONITOR_ENTRY pVme = Replica->pVme;
|
|
|
|
|
|
FRS_ASSERT(CoIsDirectory(ChangeOrder));
|
|
|
|
//
|
|
// Open the subdir directory that is the root of the MoveIn.
|
|
//
|
|
WStatus = FrsForceOpenId(&FileHandle,
|
|
NULL,
|
|
pVme,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
FILE_ID_LENGTH,
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS | FILE_LIST_DIRECTORY,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
CLEANUP1_WS(4, "++ Couldn't force open the fid for %ws;",
|
|
CoCmd->FileName, WStatus, CLEANUP);
|
|
|
|
//
|
|
// Get the sub-dir nesting level on this directory.
|
|
// Note - Can only make this call on a local CO that came from the journal.
|
|
// Otherwise get the level from the CO itself. If this dir was generated
|
|
// elsewhere (like in move in dir tree worker) it may not have gone far
|
|
// enough to make it into the filter table.
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_MOVEIN_GEN)) {
|
|
FStatus = JrnlGetPathAndLevel(pVme->FilterTable,
|
|
&ChangeOrder->FileReferenceNumber,
|
|
&Level);
|
|
CLEANUP_FS(0, "JrnlGetPathAndLevel Failed.", FStatus, CLEANUP);
|
|
} else {
|
|
Level = ChangeOrder->DirNestingLevel;
|
|
}
|
|
|
|
//
|
|
// Advance to the next level
|
|
//
|
|
ZeroMemory(&MoveInContext, sizeof(MoveInContext));
|
|
MoveInContext.ParentFileID = ChangeOrder->FileReferenceNumber;
|
|
MoveInContext.Replica = Replica;
|
|
|
|
WStatus = FrsEnumerateDirectory(FileHandle,
|
|
CoCmd->FileName,
|
|
Level,
|
|
ENUMERATE_DIRECTORY_FLAGS_ERROR_CONTINUE,
|
|
&MoveInContext,
|
|
ChgOrdMoveInDirTreeWorker);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
goto CLEANUP;
|
|
}
|
|
DPRINT4(4, "++ MoveIn dir done: %d dirs; %d files; %d skipped, %d filtered\n",
|
|
MoveInContext.NumDirs, MoveInContext.NumFiles,
|
|
MoveInContext.NumSkipped, MoveInContext.NumFiltered);
|
|
|
|
WStatus = ERROR_SUCCESS;
|
|
|
|
CLEANUP:
|
|
|
|
FRS_CLOSE(FileHandle);
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
|
|
#if _MSC_FULL_VER >= 13008827
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
|
|
#endif
|
|
|
|
ULONG
|
|
ChgOrdRetryThread(
|
|
PVOID FrsThreadCtxArg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Entry point for processing inbound change orders that need to be retried.
|
|
This is a command server thread. It gets its actual command requests
|
|
from the ChgOrdRetryCS queue. Submitting a request to this queue with
|
|
FrsSubmitCommandServer() starts this thread if it is not already running.
|
|
|
|
Arguments:
|
|
|
|
FrsThreadCtxArg - FrsThread struct.
|
|
|
|
Return Value:
|
|
|
|
WIN32 Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdRetryThread:"
|
|
|
|
JET_ERR jerr, jerr1;
|
|
ULONG WStatus;
|
|
ULONG FStatus;
|
|
|
|
PFRS_THREAD FrsThread = (PFRS_THREAD)FrsThreadCtxArg;
|
|
PTHREAD_CTX ThreadCtx;
|
|
PCOMMAND_PACKET Cmd;
|
|
ULONG ReplicaNumber;
|
|
TABLE_CTX TempTableCtx;
|
|
PTABLE_CTX TableCtx = &TempTableCtx;
|
|
PREPLICA Replica;
|
|
PCXTION Cxtion;
|
|
|
|
DPRINT(4, "++ IBCO ChgOrdRetryCS processor is starting.\n");
|
|
|
|
|
|
//
|
|
// Thread is pointing at the correct command server
|
|
//
|
|
FRS_ASSERT(FrsThread->Data == &ChgOrdRetryCS);
|
|
|
|
cant_exit_yet:
|
|
//
|
|
// Allocate a context for Jet to run in this thread.
|
|
//
|
|
ThreadCtx = FrsAllocType(THREAD_CONTEXT_TYPE);
|
|
|
|
//
|
|
// Setup a Jet Session returning the session ID in ThreadCtx.
|
|
//
|
|
jerr = DbsCreateJetSession(ThreadCtx);
|
|
if (JET_SUCCESS(jerr)) {
|
|
|
|
DPRINT(4,"++ JetOpenDatabase complete\n");
|
|
} else {
|
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|
DPRINT_FS(0,"++ ERROR - OpenDatabase failed. Thread exiting:", FStatus);
|
|
WStatus = ERROR_GEN_FAILURE;
|
|
goto EXIT_THREAD;
|
|
}
|
|
|
|
TableCtx->TableType = TABLE_TYPE_INVALID;
|
|
TableCtx->Tid = JET_tableidNil;
|
|
|
|
|
|
//
|
|
// Look for command packets with the Replcia and Cxtion args to process.
|
|
//
|
|
while (Cmd = FrsGetCommandServer(&ChgOrdRetryCS)) {
|
|
|
|
Replica = CoRetryReplica(Cmd);
|
|
ReplicaNumber = Replica->ReplicaNumber;
|
|
|
|
//
|
|
// Init the table ctx and open the inbound log table for this Replica.
|
|
//
|
|
jerr = DbsOpenTable(ThreadCtx, TableCtx, ReplicaNumber, INLOGTablex, NULL);
|
|
if (!JET_SUCCESS(jerr)) {
|
|
FStatus = DbsTranslateJetError(jerr, FALSE);
|
|
DPRINT1_FS(0,"++ ERROR - INLOG open for replica %d failed:",
|
|
ReplicaNumber, FStatus);
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, TableCtx);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Walk through the inbound log looking for change orders to reissue.
|
|
//
|
|
// Clear the retry CO count before making the scan. If any reissued
|
|
// change orders go thru retry again they bump the count to enable
|
|
// a later re-scan.
|
|
//
|
|
Replica->InLogRetryCount = 0;
|
|
|
|
jerr = DbsEnumerateTable2(ThreadCtx,
|
|
TableCtx,
|
|
ILSequenceNumberIndexx,
|
|
ChgOrdRetryWorker,
|
|
Cmd,
|
|
ChgOrdRetryWorkerPreRead);
|
|
|
|
if ((!JET_SUCCESS(jerr)) &&
|
|
(jerr != JET_errRecordNotFound) &&
|
|
(jerr != JET_errNoCurrentRecord) &&
|
|
(jerr != JET_wrnTableEmpty)) {
|
|
DPRINT_JS(0, "++ ERROR - FrsEnumerateTable for ChgOrdRetryThread :", jerr);
|
|
}
|
|
|
|
DbsCloseTable(jerr, ThreadCtx->JSesid, TableCtx);
|
|
DPRINT_JS(0,"++ ERROR - JetCloseTable on ChgOrdRetryThread failed:", jerr);
|
|
|
|
//
|
|
// If we are restarting a single connection then find
|
|
// Cxtion struct and advance the state to JOINING.
|
|
//
|
|
if (Cmd->Command == FCN_CORETRY_ONE_CXTION) {
|
|
Cxtion = CoRetryCxtion(Cmd);
|
|
LOCK_CXTION_TABLE(Replica);
|
|
//
|
|
// If it is no longer in the Connection Starting state leave it
|
|
// alone else advance to JOINING
|
|
//
|
|
if (CxtionStateIs(Cxtion, CxtionStateScanning)) {
|
|
SetCxtionState(Cxtion, CxtionStateSendJoin);
|
|
//
|
|
// Build cmd pkt with Cxtion GName
|
|
// Submit to Replica cmd server.
|
|
// Calls ReplicaJoinOne() and xlate cxtion GName to ptr
|
|
// Calls SubmitReplicaJoin()
|
|
// Builds Comm pkt for cxtion and call SndCsSubmit() to send it.
|
|
//
|
|
RcsSubmitReplicaCxtion(Replica, Cxtion, CMD_JOIN_CXTION);
|
|
}
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
}
|
|
|
|
//
|
|
// Retire the command.
|
|
//
|
|
FrsCompleteCommand(Cmd, ERROR_SUCCESS);
|
|
|
|
} // End while
|
|
|
|
DbsFreeTableCtx(TableCtx, 1);
|
|
|
|
EXIT_THREAD:
|
|
|
|
//
|
|
// No work left. Close the jet session and free the Jet ThreadCtx.
|
|
//
|
|
jerr = DbsCloseJetSession(ThreadCtx);
|
|
|
|
if (!JET_SUCCESS(jerr)) {
|
|
DPRINT_JS(0,"++ DbsCloseJetSession error:", jerr);
|
|
} else {
|
|
DPRINT(4,"++ DbsCloseJetSession complete\n");
|
|
}
|
|
|
|
ThreadCtx = FrsFreeType(ThreadCtx);
|
|
|
|
DPRINT(4, "++ Inbound change order retry thread is exiting.\n");
|
|
|
|
|
|
//
|
|
// Exit
|
|
//
|
|
FrsExitCommandServer(&ChgOrdRetryCS, FrsThread);
|
|
|
|
//
|
|
// A new command packet may have appeared on our queue while we were
|
|
// cleaning up for exiting. The command server subsystem will not
|
|
// allow us to exit until we check the command queue again because
|
|
// we may be the only thread active on the command queue at this time.
|
|
//
|
|
goto cant_exit_yet;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
#if _MSC_FULL_VER >= 13008827
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
|
|
|
|
JET_ERR
|
|
ChgOrdRetryWorker(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Record,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a worker function passed to FrsEnumerateTable(). Each time
|
|
it is called it processes a record from the Inbound log table.
|
|
There are three cases:
|
|
|
|
1. A normal retry where all Local COs and remote COs from all inbound
|
|
cxtions are considered (cmd is FCN_CORETRY_ALL_CXTIONS). If the record is
|
|
marked for retry (has the CO_FLAG_RETRY bit set) then resubmit the CO to try
|
|
and complete it. The necessary work could be to complete the install,
|
|
or the fetch, or generate a stage file.
|
|
|
|
2. A single connection restart/recovery request (FCN_CORETRY_ONE_CXTION).
|
|
In this case the connection is assumed to be inactive and this is part of
|
|
the startup procedure. Scan the entire inbound log and requeue any change
|
|
order with a matching connection guid. This ensures these COs are in the
|
|
queue ahead of any new COs that arrive after we join with the inbound
|
|
partner.
|
|
|
|
Note - If a remote CO from a partner was in process when the cxtion
|
|
went away and it was a RETRY CO, that CO will not be reissued when the
|
|
FCN_CORETRY_ONE_CXTION" "is processed because the CO is still in the
|
|
active retry table. Since it is a retry CO it is already out of order
|
|
so it doesn't really matter. If the dead connection causes it to fail
|
|
it will get retried again later.
|
|
|
|
3. Resubmit all Local COs (FCN_CORETRY_LOCAL_ONLY). This is typically
|
|
only done at Replica set startup where the local COs are restarted
|
|
regardless if they are in a retry state or not. If this is done at any other
|
|
time we could end up reissuing a CO that is still on its "First Issue"
|
|
because it is not in the ActiveInlogRetry table. This could cause a Local
|
|
CO to look like a duplicate which will probably ASSERT.
|
|
|
|
|
|
Arguments:
|
|
|
|
ThreadCtx - Needed to access Jet.
|
|
TableCtx - A ptr to an inbound log context struct.
|
|
Record - A ptr to a change order command record.
|
|
Context - A ptr to the CO Retry command we are working on.
|
|
|
|
Thread Return Value:
|
|
|
|
JET_errSuccess if enum is to continue.
|
|
JET_errInvalidObject if table may have changed and the record must be re-read.
|
|
JET_errRecordNotFound when we hit the first record without the retry flag set.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdRetryWorker:"
|
|
|
|
ULONG FStatus;
|
|
PCOMMAND_PACKET Cmd = (PCOMMAND_PACKET) Context;
|
|
PCHANGE_ORDER_COMMAND CoCmd = (PCHANGE_ORDER_COMMAND)Record;
|
|
PREPLICA Replica;
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
ULONGLONG SeqNum;
|
|
PQHASH_TABLE ActiveInlogRetryTable;
|
|
PCXTION Cxtion = NULL;
|
|
GUID *SingleCxtionGuid = NULL;
|
|
ULONG CoeFlags = 0;
|
|
BOOL RemoteCo, RetryCo;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
JET_ERR jerr;
|
|
|
|
//
|
|
// Abort retry worker if FRS is shutting down.
|
|
//
|
|
if (FrsIsShuttingDown) {
|
|
return JET_errTermInProgress;
|
|
}
|
|
|
|
Replica = CoRetryReplica(Cmd);
|
|
pVme = Replica->pVme;
|
|
ActiveInlogRetryTable = Replica->ActiveInlogRetryTable;
|
|
GuidToStr(&CoCmd->ChangeOrderGuid, GuidStr);
|
|
|
|
DBS_DISPLAY_RECORD_SEV(4, TableCtx, TRUE);
|
|
|
|
//
|
|
// Get the lock on the Active Retry table.
|
|
//
|
|
QHashAcquireLock(ActiveInlogRetryTable);
|
|
|
|
//
|
|
// Check if an inlog record has been deleted (seq num changed).
|
|
// If so then return to re-read the record incase this was the one deleted.
|
|
//
|
|
if (Replica->AIRSequenceNum != Replica->AIRSequenceNumSample) {
|
|
DPRINT2(4, "++ Seq num changed: Sample %08x, Counter %08x -- reread record.\n",
|
|
Replica->AIRSequenceNumSample, Replica->AIRSequenceNum);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
return JET_errInvalidObject;
|
|
}
|
|
|
|
//
|
|
// Check if it's in Active Retry table. If so then it has not yet
|
|
// finished from a prior retry so we skip it this time.
|
|
//
|
|
SeqNum = (ULONGLONG) CoCmd->SequenceNumber;
|
|
|
|
if (QHashLookupLock(ActiveInlogRetryTable, &SeqNum) != NULL) {
|
|
DPRINT1(4, "++ ActiveInlogRetryTable hit on seq num %08x %08x, skipping reissue\n",
|
|
PRINTQUAD(SeqNum));
|
|
DPRINT3(4, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- Active\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
return JET_errSuccess;
|
|
}
|
|
|
|
RemoteCo = !BooleanFlagOn(CoCmd->Flags, CO_FLAG_LOCALCO);
|
|
RetryCo = BooleanFlagOn(CoCmd->Flags, CO_FLAG_RETRY);
|
|
|
|
//
|
|
// Initialize the Ghost Cxtion. This cxtion is assigned to orphan remote change
|
|
// orders in the inbound log whose cxtion is deleted from the DS but who have already
|
|
// past the fetching state and do not need the cxtion to complete processing. No
|
|
// authentication checks are made for this dummy cxtion.
|
|
//
|
|
|
|
if (FrsGhostCxtion == NULL) {
|
|
FrsGhostCxtion = FrsAllocType(CXTION_TYPE);
|
|
FrsGhostCxtion->Name = FrsBuildGName(FrsDupGuid(&FrsGuidGhostCxtion),
|
|
FrsWcsDup(L"<Ghost Cxtion>"));
|
|
FrsGhostCxtion->Partner = FrsBuildGName(FrsDupGuid(&FrsGuidGhostCxtion),
|
|
FrsWcsDup(L"<Ghost Cxtion>"));
|
|
FrsGhostCxtion->PartSrvName = FrsWcsDup(L"<Ghost Cxtion>");
|
|
FrsGhostCxtion->PartnerPrincName = FrsWcsDup(L"<Ghost Cxtion>");
|
|
FrsGhostCxtion->PartnerDnsName = FrsWcsDup(L"<Ghost Cxtion>");
|
|
FrsGhostCxtion->PartnerSid = FrsWcsDup(L"<Ghost Cxtion>");
|
|
FrsGhostCxtion->PartnerAuthLevel = CXTION_AUTH_NONE;
|
|
FrsGhostCxtion->Inbound = TRUE;
|
|
//
|
|
// Start the ghost connection out as Joined and give it a JOIN guid.
|
|
//
|
|
DPRINT1(0, "***** JOINED "FORMAT_CXTION_PATH2"\n",
|
|
PRINT_CXTION_PATH2(Replica, FrsGhostCxtion));
|
|
SetCxtionState(FrsGhostCxtion, CxtionStateJoined);
|
|
COPY_GUID(&FrsGhostCxtion->JoinGuid, &FrsGhostJoinGuid);
|
|
SetCxtionFlag(FrsGhostCxtion, CXTION_FLAGS_JOIN_GUID_VALID |
|
|
CXTION_FLAGS_UNJOIN_GUID_VALID);
|
|
}
|
|
|
|
switch (Cmd->Command) {
|
|
|
|
case FCN_CORETRY_LOCAL_ONLY:
|
|
//
|
|
// Skip the remote COs.
|
|
//
|
|
if (RemoteCo) {
|
|
DPRINT3(3, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- Local only\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
goto SKIP_RECORD;
|
|
}
|
|
CoeFlags = COE_FLAG_RECOVERY_CO;
|
|
COPY_GUID(&CoCmd->CxtionGuid, &Replica->JrnlCxtionGuid);
|
|
CHANGE_ORDER_COMMAND_TRACE(3, CoCmd, "Co FCN_CORETRY_LOCAL_ONLY");
|
|
break;
|
|
|
|
|
|
case FCN_CORETRY_ONE_CXTION:
|
|
//
|
|
// Doing single connection restart. CxtionGuid must match.
|
|
// Don't bother checking cxtion state since we were explicitly told
|
|
// to retry these COs. Some of these COs may not have the retry
|
|
// flag set since they could be first issue COs when the inbound partner
|
|
// connection died or the system crashed.
|
|
//
|
|
Cxtion = CoRetryCxtion(Cmd);
|
|
SingleCxtionGuid = Cxtion->Name->Guid;
|
|
FRS_ASSERT(Cxtion);
|
|
|
|
if ((SingleCxtionGuid != NULL) &&
|
|
!GUIDS_EQUAL(&CoCmd->CxtionGuid, SingleCxtionGuid)) {
|
|
DPRINT3(3, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- Wrong Cxtion\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
goto SKIP_RECORD;
|
|
}
|
|
//
|
|
// Mark this as a recovery change order. This state moderates error
|
|
// checking behavior. E.G. CO could already be in OutLog which is not
|
|
// an error if this is a recovery / restart CO.
|
|
//
|
|
CoeFlags = COE_FLAG_RECOVERY_CO;
|
|
CHANGE_ORDER_COMMAND_TRACE(3, CoCmd, "Co FCN_CORETRY_ONE_CXTION");
|
|
break;
|
|
|
|
|
|
case FCN_CORETRY_ALL_CXTIONS:
|
|
|
|
//
|
|
// Skip the COs that don't have retry set. We used to stop the enum
|
|
// when the first CO without retry set was hit. This does not work
|
|
// because dup COs are marked as retry and they can get traped behind
|
|
// new COs. When an inlog scan is triggered it clears the scan flag
|
|
// and when the new CO is hit it would stop the scan. Now the
|
|
// condition is the scan is done, the flag is clear but there are retry
|
|
// COs still in the inlog that need to be processed. Their inbound
|
|
// partner is still waiting for the ACK so it can delete the staging
|
|
// file (and clear any Ack Vector wrap condition).
|
|
//
|
|
if (!RetryCo) {
|
|
DPRINT3(3, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- Not retry\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
goto SKIP_RECORD;
|
|
}
|
|
|
|
//
|
|
// If the inbound connection is not JOINED then there is no point in processing it.
|
|
//
|
|
// NOTE: If the connection were to go live during a scan then COs could be
|
|
// retried out of order. This should not happen because we are called
|
|
// before the connection join begins and the connection won't proceed
|
|
// until we finish the scan and change the connection state. If later
|
|
// a Retry All is in process when a connection goes to the JOINED state
|
|
// all its COs will be in the Active retry table so they will not be
|
|
// requeued.
|
|
//
|
|
// Perf: Since we don't need the joined connections once the stage file
|
|
// is fetched we could process those remote COs that are in install
|
|
// retry or rename retry state but for now keep them in order.
|
|
//
|
|
if (!RemoteCo) {
|
|
//
|
|
// Update the Local Co's connection guid to match current value
|
|
// in case this replica set was stoped and then restarted.
|
|
//
|
|
COPY_GUID(&CoCmd->CxtionGuid, &Replica->JrnlCxtionGuid);
|
|
}
|
|
|
|
//
|
|
// Find the inbound cxtion for this CO
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
|
|
Cxtion = GTabLookupNoLock(Replica->Cxtions, &CoCmd->CxtionGuid, NULL);
|
|
if (Cxtion == NULL) {
|
|
|
|
if (CoCmd->State > IBCO_FETCH_RETRY) {
|
|
|
|
DPRINT3(2, "++ CO Retry submit, vsn %08x %08x, CoGuid: %s for %ws -- No Cxtion - Using Ghost Cxtion.\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
|
|
FRS_ASSERT(FrsGhostCxtion != NULL);
|
|
Cxtion = FrsGhostCxtion;
|
|
} else {
|
|
|
|
DPRINT3(2, "++ CO Retry delete, vsn %08x %08x, CoGuid: %s for %ws -- No Cxtion - Deleting inlog record.\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
|
|
jerr = DbsDeleteTableRecord(TableCtx);
|
|
DPRINT1_JS(0, "ERROR - DbsDeleteRecord on %ws :", Replica->ReplicaName->Name, jerr);
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
goto SKIP_RECORD;
|
|
}
|
|
}
|
|
|
|
FRS_ASSERT(Cxtion->Inbound);
|
|
//
|
|
// We are in the middle of a scan all so we should not see any
|
|
// remote CO with a connection in the JOINING state. All those
|
|
// COs should have been requeued when the cxtion was in the
|
|
// STARTING State and be in the ActiveInlogRetryTable Table where they
|
|
// are filtered out above.
|
|
//
|
|
// WELL THIS ISN'T QUITE TRUE.
|
|
// There appears to be a case where a previously submitted retry CO
|
|
// for a cxtion is flushed because of an invalid join guid but the
|
|
// cxtion is left in WaitJoin state so when the next FCN_CORETRY_ALL_CXTIONS
|
|
// occurs it is not in the ActiveInlogRetryTable and we trigger the
|
|
// assert. This might be happening when the join attempt times out
|
|
// and the cxtion state isn't changed to unjoined. Bug 319812 hit this.
|
|
//
|
|
// It looks like there is a potential for COs to get resubmitted out of
|
|
// order when a previous join attempt fails and some COs were delayed
|
|
// behind some other CO that blocked the queue. In this case we could
|
|
// be in the middle of flushing the old series of COs when the next
|
|
// rejoin starts. Since some of these could still be in the
|
|
// ActiveInlogRetryTable they won't get resubmitted. Not sure if this
|
|
// is a real problem though.
|
|
//
|
|
//FRS_ASSERT(!CxtionStateIs(Cxtion, CxtionStateSendJoin) &&
|
|
// !CxtionStateIs(Cxtion, CxtionStateWaitJoin));
|
|
|
|
//
|
|
// If the connection isn't joined then skip the CO.
|
|
// COs for connections in the starting state are processed
|
|
// by a single connection restart request.
|
|
//
|
|
if (!CxtionStateIs(Cxtion, CxtionStateJoined)) {
|
|
if (CoCmd->State > IBCO_FETCH_RETRY) {
|
|
|
|
DPRINT3(2, "++ CO Retry submit, vsn %08x %08x, CoGuid: %s for %ws -- Cxtion not joined - Using Ghost Cxtion.\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
|
|
FRS_ASSERT(FrsGhostCxtion != NULL);
|
|
Cxtion = FrsGhostCxtion;
|
|
} else {
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
DPRINT3(2, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- Cxtion not joined\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
goto SKIP_RECORD;
|
|
}
|
|
}
|
|
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
CHANGE_ORDER_COMMAND_TRACE(3, CoCmd, "Co FCN_CORETRY_ALL_CXTIONS");
|
|
break;
|
|
|
|
|
|
default:
|
|
DPRINT1(0, "ChgOrdRetryWorker: unknown command 0x%x\n", Cmd->Command);
|
|
DPRINT3(0, "++ CO Retry skip, vsn %08x %08x, CoGuid: %s for %ws -- bad cmd\n",
|
|
PRINTQUAD(CoCmd->FrsVsn), GuidStr, CoCmd->FileName);
|
|
FRS_ASSERT(!"ChgOrdRetryWorker: bad cmd");
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
goto SKIP_RECORD;
|
|
}
|
|
|
|
//
|
|
// All COs go into the inlog retry table to lock against reissue by the
|
|
// retry thread. This is because of the change to the retry thread where
|
|
// we no longer stop the inlog enum when we hit the first CO that is not
|
|
// marked as retry. See comment above at FCN_CORETRY_ALL_CXTIONS.
|
|
//
|
|
|
|
if (!RetryCo) {
|
|
FRS_ASSERT(BooleanFlagOn(CoeFlags, COE_FLAG_RECOVERY_CO));
|
|
}
|
|
QHashInsertLock(ActiveInlogRetryTable, &SeqNum, NULL, 0);
|
|
|
|
QHashReleaseLock(ActiveInlogRetryTable);
|
|
|
|
CHANGE_ORDER_COMMAND_TRACE(3, CoCmd, "CO Retry Submit");
|
|
|
|
DPRINT2(4, "++ ++ CO retry submit for Index %d, State: %s\n",
|
|
CoCmd->SequenceNumber, PRINT_COCMD_STATE(CoCmd));
|
|
|
|
//
|
|
// Re-insert the change order into the process queue.
|
|
//
|
|
FStatus = ChgOrdInsertProcessQueue(Replica, CoCmd, CoeFlags, Cxtion);
|
|
|
|
return JET_errSuccess;
|
|
|
|
|
|
SKIP_RECORD:
|
|
//
|
|
// The record is being skipped but bump the count so we know to come
|
|
// back and try again later.
|
|
//
|
|
InterlockedIncrement(&Replica->InLogRetryCount);
|
|
return JET_errSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
JET_ERR
|
|
ChgOrdRetryWorkerPreRead(
|
|
IN PTHREAD_CTX ThreadCtx,
|
|
IN PTABLE_CTX TableCtx,
|
|
IN PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a worker function passed to DbsEnumerateTable2().
|
|
It is called before each DB record read operation.
|
|
It is used to change the seek location in the table or to setup
|
|
table access synchronization.
|
|
|
|
Arguments:
|
|
|
|
ThreadCtx - Needed to access Jet.
|
|
TableCtx - A ptr to an inbound log context struct.
|
|
Context - A ptr to the CO Retry command we are working on.
|
|
|
|
Thread Return Value:
|
|
|
|
JET_errSuccess if enum is to continue.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdRetryWorkerPreRead:"
|
|
|
|
PCOMMAND_PACKET Cmd = (PCOMMAND_PACKET) Context;
|
|
PREPLICA Replica = CoRetryReplica(Cmd);
|
|
|
|
//
|
|
// Sample the Active Inlog retry sequence number. If it changes after
|
|
// the DB read then the record may have been deleted from the table and
|
|
// the read must be tried again.
|
|
//
|
|
Replica->AIRSequenceNumSample = Replica->AIRSequenceNum;
|
|
|
|
return JET_errSuccess;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ChgOrdSkipBasicInfoChange(
|
|
IN PCHANGE_ORDER_ENTRY Coe,
|
|
IN PBOOL SkipCo
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
If the changes to the file were unimportant, skip the change order.
|
|
An example of an unimportant change is resetting the archive bit.
|
|
|
|
Arguments:
|
|
Coe
|
|
SkipCo
|
|
|
|
Thread Return Value:
|
|
|
|
WIN32 STATUS and TRUE if the caller should skip the change order,
|
|
otherwise FALSE.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdSkipBasicInfoChange:"
|
|
ULONG CocAttrs;
|
|
ULONG CoeAttrs;
|
|
ULONG CocContentCmd;
|
|
DWORD WStatus;
|
|
HANDLE Handle;
|
|
PCHANGE_ORDER_COMMAND Coc = &Coe->Cmd;
|
|
FILE_NETWORK_OPEN_INFORMATION FileInfo;
|
|
|
|
//
|
|
// Don't skip it, yet
|
|
//
|
|
*SkipCo = FALSE;
|
|
|
|
//
|
|
// Remote Co can't be skipped
|
|
//
|
|
if (!CO_FLAG_ON(Coe, CO_FLAG_LOCALCO)) {
|
|
DPRINT1(4, "++ Don't skip %ws; not local\n", Coc->FileName);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Location commands can't be skipped
|
|
//
|
|
if (CO_FLAG_ON(Coe, CO_FLAG_LOCATION_CMD)) {
|
|
DPRINT1(4, "++ Don't skip %ws; location cmd\n", Coc->FileName);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// No location command *AND* no content changes! Huh?
|
|
//
|
|
if (!CO_FLAG_ON(Coe, CO_FLAG_CONTENT_CMD)) {
|
|
DPRINT1(4, "++ Don't skip %ws; no content cmd\n", Coc->FileName);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Something other than just a basic info change
|
|
//
|
|
CocContentCmd = (Coc->ContentCmd & CO_CONTENT_MASK) &
|
|
~USN_REASON_BASIC_INFO_CHANGE;
|
|
if (CocContentCmd) {
|
|
DPRINT2(4, "++ Don't skip %ws; not just basic info change\n",
|
|
Coc->FileName, CocContentCmd);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Only ARCHIVE changes can be skipped
|
|
//
|
|
CocAttrs = Coc->FileAttributes &
|
|
~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NORMAL);
|
|
CoeAttrs = Coe->FileAttributes &
|
|
~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NORMAL);
|
|
if (CocAttrs != CoeAttrs) {
|
|
DPRINT3(4, "++ Don't skip %ws; not just archive (%08x != %08x)\n",
|
|
Coc->FileName, CocAttrs, CoeAttrs);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
//
|
|
// Check create and write times
|
|
// If can't access the file. The caller should retry later (if possible)
|
|
//
|
|
WStatus = FrsOpenSourceFileById(&Handle,
|
|
&FileInfo,
|
|
NULL,
|
|
Coe->NewReplica->pVme->VolumeHandle,
|
|
&Coe->FileReferenceNumber,
|
|
FILE_ID_LENGTH,
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
CLEANUP1_WS(4, "++ Cannot check basic info changes for %ws;",
|
|
Coc->FileName, WStatus, RETURN);
|
|
|
|
FRS_CLOSE(Handle);
|
|
|
|
//
|
|
// It is possible for the file attributes to have changed between the
|
|
// time the USN record was processed and now. Record the current
|
|
// attributes in the change order. This is especially true for dir
|
|
// creates since while the dir was open other changes may have occurred.
|
|
//
|
|
if (Coc->FileAttributes != FileInfo.FileAttributes) {
|
|
CHANGE_ORDER_TRACEX(3, Coe, "New File Attr= ", FileInfo.FileAttributes);
|
|
Coc->FileAttributes = FileInfo.FileAttributes;
|
|
|
|
WStatus = ERROR_SUCCESS;
|
|
if (Coc->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
|
WStatus = FrsCheckReparse(Coc->FileName,
|
|
(PULONG)&Coe->FileReferenceNumber,
|
|
FILE_ID_LENGTH,
|
|
Coe->NewReplica->pVme->VolumeHandle);
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return WStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FileInfo.CreationTime.QuadPart != Coe->FileCreateTime.QuadPart) {
|
|
DPRINT3(4, "++ Don't skip %ws; create time %08x %08x %08x %08x\n",
|
|
Coc->FileName, PRINTQUAD(FileInfo.CreationTime.QuadPart),
|
|
PRINTQUAD(Coe->FileCreateTime.QuadPart));
|
|
return ERROR_SUCCESS;
|
|
}
|
|
if (FileInfo.LastWriteTime.QuadPart != Coe->FileWriteTime.QuadPart) {
|
|
DPRINT3(4, "++ Don't skip %ws; write time %08x %08x %08x %08x\n",
|
|
Coc->FileName, PRINTQUAD(FileInfo.LastWriteTime.QuadPart),
|
|
PRINTQUAD(Coe->FileWriteTime.QuadPart));
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// SKIP IT
|
|
//
|
|
DPRINT1(0, "++ Skip local co for %ws\n", Coc->FileName);
|
|
*SkipCo = TRUE;
|
|
|
|
RETURN:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ChgOrdHammerObjectId(
|
|
IN PWCHAR Name,
|
|
IN PVOID Id,
|
|
IN DWORD IdLen,
|
|
IN PVOLUME_MONITOR_ENTRY pVme,
|
|
IN BOOL CallerSupplied,
|
|
OUT USN *Usn,
|
|
IN OUT PFILE_OBJECTID_BUFFER FileObjID,
|
|
IN OUT BOOL *ExistingOid
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Hammer an object id onto the file by fid. The open-for-write-access
|
|
is retried several times before giving up. The file's attributes
|
|
may be temporarily reset.
|
|
|
|
Arguments:
|
|
Name - File name for error messages
|
|
Id - Fid or Oid
|
|
pVme - volume monitor entry
|
|
CallerSupplied - TRUE if caller is supplying object id
|
|
Usn - Address for dampened usn
|
|
FileObjID - New object id if CallerSupplied is TRUE.
|
|
Assigned object id if returning ERROR_SUCCESS
|
|
ExistingOid -- INPUT: TRUE means use existing File OID if found.
|
|
RETURN: TRUE means an existing File OID was used.
|
|
|
|
Thread Return Value:
|
|
|
|
Win32 Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdHammerObjectId:"
|
|
|
|
DWORD WStatus;
|
|
ULONG RetrySetObjectId;
|
|
HANDLE Handle;
|
|
BOOL OidMatch = FALSE;
|
|
|
|
DPRINT3(5, "++ Attempting to hammer object id for %ws Id 0x%08x 0x%08x (%d bytes)\n",
|
|
Name, PRINTQUAD((*((PULONGLONG)Id))), IdLen);
|
|
|
|
|
|
//
|
|
// For proper cleanup in the event of failure
|
|
//
|
|
Handle = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// Attempt to open the file for write access
|
|
//
|
|
// The loop is repeated 10 times with a .1 second sleep between cycles
|
|
//
|
|
for (RetrySetObjectId = 0;
|
|
RetrySetObjectId <= MAX_RETRY_SET_OBJECT_ID;
|
|
++RetrySetObjectId, Sleep(100)) {
|
|
|
|
//
|
|
// FIRST see if the object id is already on the file
|
|
//
|
|
|
|
WStatus = FrsOpenSourceFileById(&Handle,
|
|
NULL,
|
|
NULL,
|
|
pVme->VolumeHandle,
|
|
Id,
|
|
IdLen,
|
|
// READ_ACCESS,
|
|
READ_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
//
|
|
// File has been deleted; done
|
|
//
|
|
if (WIN_NOT_FOUND(WStatus)) {
|
|
DPRINT2(4, "++ %ws (Id %08x %08x) has been deleted\n",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)));
|
|
return WStatus;
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
//
|
|
// We are having problems...
|
|
//
|
|
DPRINT3_WS(4, "++ Problems opening %ws (Id %08x %08x) for read hammer (loop %d);",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), RetrySetObjectId, WStatus);
|
|
continue;
|
|
}
|
|
if (CallerSupplied) {
|
|
WStatus = FrsCheckObjectId(Name,
|
|
Handle,
|
|
(GUID *)&FileObjID->ObjectId[0]);
|
|
OidMatch = WIN_SUCCESS(WStatus);
|
|
} else {
|
|
WStatus = FrsGetObjectId(Handle, FileObjID);
|
|
}
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
WStatus = FrsReadFileUsnData(Handle, Usn);
|
|
}
|
|
FRS_CLOSE(Handle);
|
|
|
|
//
|
|
// If an object id is already on the file and we are keeping them
|
|
// then we're done.
|
|
//
|
|
if (OidMatch || (ExistingOid && WIN_SUCCESS(WStatus))) {
|
|
DPRINT2(4, "++ Using existing oid for %ws (Id %08x %08x)\n",
|
|
Name, PRINTQUAD((*((PULONGLONG)Id))) );
|
|
*ExistingOid = TRUE;
|
|
return WStatus;
|
|
} else
|
|
if (!CallerSupplied && !ExistingOid) {
|
|
//
|
|
// Set up to slam a new OID on the file.
|
|
//
|
|
CallerSupplied = TRUE;
|
|
ZeroMemory(FileObjID, sizeof(FILE_OBJECTID_BUFFER));
|
|
FrsUuidCreate((GUID *)FileObjID->ObjectId);
|
|
}
|
|
|
|
//
|
|
// HAMMER THE OBJECT ID
|
|
//
|
|
*ExistingOid = FALSE;
|
|
|
|
//
|
|
// Open the file for write access
|
|
//
|
|
// overlap disables (FILE_SYNCHRONOUS_IO_NONALERT) during
|
|
// the following open. Hence I think this causes IO_PENDING
|
|
// to be returned by GetOrSetObjectId() below.
|
|
// I am disabling oplocks for now.
|
|
//
|
|
WStatus = FrsForceOpenId(&Handle,
|
|
NULL, // use oplock when safe
|
|
pVme,
|
|
Id,
|
|
IdLen,
|
|
// READ_ACCESS | WRITE_ACCESS | ATTR_ACCESS,
|
|
// STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | ACCESS_SYSTEM_SECURITY | SYNCHRONIZE,
|
|
READ_ATTRIB_ACCESS | WRITE_ATTRIB_ACCESS,
|
|
ID_OPTIONS,
|
|
SHARE_ALL,
|
|
FILE_OPEN);
|
|
//
|
|
// File has been opened for write access; hammer the object id
|
|
//
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// File has been deleted; done
|
|
//
|
|
if (WIN_NOT_FOUND(WStatus)) {
|
|
CLEANUP2_WS(4, "++ %ws (Id %08x %08x) has been deleted :",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), WStatus, RETURN);
|
|
}
|
|
|
|
//
|
|
// We are having problems...
|
|
//
|
|
DPRINT3_WS(4, "++ Problems opening %ws (Fid %08x %08x) for hammer (loop %d);",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), RetrySetObjectId, WStatus);
|
|
}
|
|
|
|
//
|
|
// Couldn't open the file
|
|
//
|
|
CLEANUP2_WS(0, "++ ERROR - Giving up on %ws (Id %08x %08x):",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), WStatus, RETURN);
|
|
|
|
//
|
|
// Handles can be marked so that any usn records resulting from
|
|
// operations on the handle will have the same "mark". In this
|
|
// case, the mark is a bit in the SourceInfo field of the usn
|
|
// record. The mark tells NtFrs to ignore the usn record during
|
|
// recovery because this was a NtFrs generated change.
|
|
//
|
|
WStatus = FrsMarkHandle(pVme->VolumeHandle, Handle);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(0, "++ WARN - FrsMarkHandle(%ws (Id %08x %08x));",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), WStatus);
|
|
WStatus = ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Get or set the object ID.
|
|
//
|
|
WStatus = FrsGetOrSetFileObjectId(Handle, Name, CallerSupplied, FileObjID);
|
|
|
|
|
|
#if 0
|
|
//
|
|
// WARN - Even though we marked the handle above we are not seeing the source
|
|
// info data in the USN journal. The following may be affecting it.
|
|
// SP1:
|
|
// In any case the following does not work because the Journal thread
|
|
// can process the close record before this thread is able to update the
|
|
// Write Filter. The net effect is that we could end up processing
|
|
// an install as a local CO update and re-replicate the file.
|
|
//
|
|
FrsCloseWithUsnDampening(Name, &Handle, pVme->FrsWriteFilter, Usn);
|
|
#endif
|
|
// Not the USN of the close record but.. whatever.
|
|
if (Usn != NULL) {
|
|
FrsReadFileUsnData(Handle, Usn);
|
|
}
|
|
FRS_CLOSE(Handle);
|
|
|
|
|
|
//
|
|
// Couldn't set object id
|
|
//
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT2_WS(0, "++ ERROR - Cannot hammer object id for %ws (Id %08x %08x) :",
|
|
Name, PRINTQUAD(*((PULONGLONG)Id)), WStatus);
|
|
//
|
|
// Just to make sure the error code doesn't imply a deleted file
|
|
// BUT don't remap ERROR_DUP_NAME because the callers of this
|
|
// function may key off this error code and retry this operation
|
|
// after stealing the object id from another file.
|
|
//
|
|
// We do not want to mask the error if it is a retriable error.
|
|
// E.g. A DISK_FULL condition will cause the above FrsGetOrSetFileObjectId
|
|
// call to fail. We do not want StageCsCreateStage to sbort this
|
|
// local CO on that error. Masking a retriable error will cause
|
|
// StageCsCreateStage to abort such COs. (Bug 164114)
|
|
//
|
|
if ((WStatus != ERROR_DUP_NAME) && !WIN_RETRY_STAGE(WStatus)) {
|
|
WIN_SET_FAIL(WStatus);
|
|
}
|
|
}
|
|
|
|
RETURN:
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ChgOrdStealObjectId(
|
|
IN PWCHAR Name,
|
|
IN PVOID Fid,
|
|
IN PVOLUME_MONITOR_ENTRY pVme,
|
|
OUT USN *Usn,
|
|
IN OUT PFILE_OBJECTID_BUFFER FileObjID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Hammer an object id onto the file by fid. The open-for-write-access
|
|
is retried several times before giving up. The file's attributes
|
|
may be temporarily reset. If some other file is using our object
|
|
id, assign a new object id to the other file.
|
|
|
|
Arguments:
|
|
Name - File name for error messages
|
|
Id - Fid or Oid
|
|
IdLen - FILE_ID_LENGTH or OBJECT_ID_LENGTH
|
|
pVme - volume monitor entry
|
|
Usn - Address for dampened usn
|
|
FileObjID - New object id
|
|
Assigned object id if returning ERROR_SUCCESS
|
|
|
|
Thread Return Value:
|
|
|
|
Win32 Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdStealObjectId:"
|
|
DWORD WStatus;
|
|
FILE_OBJECTID_BUFFER NewFileObjID;
|
|
BOOL ExistingOid;
|
|
|
|
RETRY_HAMMER:
|
|
//
|
|
// Hammer the object id to the desired value
|
|
//
|
|
DPRINT2(5, "++ Hammering object id for %ws (Fid %08x %08x)\n",
|
|
Name, PRINTQUAD((*((PULONGLONG)Fid))));
|
|
|
|
|
|
ExistingOid = FALSE;
|
|
WStatus = ChgOrdHammerObjectId(Name, //Name,
|
|
Fid, //Id,
|
|
FILE_ID_LENGTH, //IdLen,
|
|
pVme, //pVme,
|
|
TRUE, //CallerSupplied
|
|
Usn, //*Usn,
|
|
FileObjID, //FileObjID,
|
|
&ExistingOid); //*ExistingOid
|
|
//
|
|
// Object id in use; replace the object id on whatever file is claiming it
|
|
//
|
|
if (WStatus == ERROR_DUP_NAME) {
|
|
DPRINT2(4, "++ Stealing object id for %ws (Fid %08x %08x)\n",
|
|
Name, PRINTQUAD((*((PULONGLONG)Fid))));
|
|
|
|
ZeroMemory(&NewFileObjID, sizeof(NewFileObjID));
|
|
FrsUuidCreate((GUID *)(&NewFileObjID.ObjectId[0]));
|
|
ExistingOid = FALSE;
|
|
|
|
WStatus = ChgOrdHammerObjectId(Name, //Name,
|
|
&FileObjID->ObjectId[0], //Id,
|
|
OBJECT_ID_LENGTH, //IdLen,
|
|
pVme, //pVme,
|
|
TRUE, //CallerSupplied
|
|
Usn, //*Usn,
|
|
&NewFileObjID, //FileObjID,
|
|
&ExistingOid); //*ExistingOid
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
DPRINT2(4, "++ Success Stealing object id for %ws (Fid %08x %08x)\n",
|
|
Name, PRINTQUAD((*((PULONGLONG)Fid))));
|
|
goto RETRY_HAMMER;
|
|
} else {
|
|
DPRINT2_WS(0, "++ ERROR - Could not steal object id for %ws (Fid %08x %08x);",
|
|
Name, PRINTQUAD((*((PULONGLONG)Fid))), WStatus);
|
|
}
|
|
} else {
|
|
DPRINT2_WS(4, "++ Hammer(%ws, Fid %08x %08x) Failed:",
|
|
Name, PRINTQUAD((*((PULONGLONG)Fid))), WStatus);
|
|
}
|
|
|
|
return WStatus;
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdAcceptInitialize(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Initialize the send command server subsystem.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdAcceptInitialize:"
|
|
|
|
//
|
|
// Create the File System monitor thread. It inits its process queue
|
|
// and then waits for a packet. First packet should be to init.
|
|
//
|
|
if (!ThSupCreateThread(L"COAccept", NULL, ChgOrdAccept, ThSupExitThreadNOP)) {
|
|
|
|
DPRINT(0, "++ ERROR - Could not create ChgOrdAccept thread\n");
|
|
return FrsErrorResource;
|
|
}
|
|
|
|
return FrsErrorSuccess;
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdAcceptShutdown(
|
|
VOID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Run down the change order list.
|
|
queue a shutdown change order to the process queue for this volume.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdAcceptShutdown:"
|
|
|
|
LIST_ENTRY RunDown;
|
|
|
|
DPRINT1(3, "<<<<<<<...E N T E R I N G -- %s...>>>>>>>>\n", DEBSUB);
|
|
|
|
FrsRtlRunDownQueue(&FrsVolumeLayerCOQueue, &RunDown);
|
|
ChangeOrderAcceptIsShuttingDown = TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
DWORD
|
|
ChgOrdTestJournalPause(
|
|
PVOID FrsThreadCtxArg
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Entry point for test thread that pauses and unpauses the Journal.
|
|
|
|
Arguments:
|
|
|
|
FrsThreadCtxArg - thread
|
|
|
|
Return Value:
|
|
|
|
WIN32 status
|
|
|
|
--*/
|
|
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdTestJournalPause:"
|
|
|
|
|
|
PVOLUME_MONITOR_ENTRY pVme;
|
|
ULONG i, Select, WStatus;;
|
|
|
|
|
|
//
|
|
// Wait till we see a completion port for the journal.
|
|
//
|
|
while (!HANDLE_IS_VALID(JournalCompletionPort)) {
|
|
Sleep(1000);
|
|
}
|
|
|
|
Sleep(3000);
|
|
|
|
while (TRUE) {
|
|
|
|
for (i=0; i<FrsRtlCountQueue(&VolumeMonitorQueue); i++) {
|
|
//
|
|
// Every 3 sec get an entry from the VolumeMonitorQueue
|
|
// and pause its journal for 5 seconds.
|
|
//
|
|
Select = i;
|
|
pVme = NULL;
|
|
ForEachListEntry(&VolumeMonitorQueue, VOLUME_MONITOR_ENTRY, ListEntry,
|
|
pVme = pE;
|
|
if ((Select--) == 0) {
|
|
break;
|
|
}
|
|
);
|
|
|
|
if (pVme != NULL) {
|
|
DPRINT(4, "Pausing the volume ----------------------\n");
|
|
WStatus = JrnlPauseVolume(pVme, 5000);
|
|
DPRINT_WS(0, "Status from Pause", WStatus);
|
|
|
|
if (WIN_SUCCESS(WStatus)) {
|
|
DPRINT(4, "Delaying 3 sec ----------------------\n");
|
|
Sleep(3000);
|
|
|
|
DPRINT(4, "Unpausing the volume ----------------------\n");
|
|
|
|
pVme->JournalState = JRNL_STATE_STARTING;
|
|
WStatus = JrnlUnPauseVolume(pVme, NULL, FALSE);
|
|
|
|
DPRINT_WS(0, "Status from Unpause", WStatus);
|
|
DPRINT(4, "Delaying 1 sec ----------------------\n");
|
|
Sleep(1000);
|
|
|
|
}
|
|
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
DPRINT(0, "Error Abort-----------------------------\n");
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif 0
|
|
|
|
|
|
|
|
ULONG
|
|
ChgOrdInsertRemoteCo(
|
|
IN PCOMMAND_PACKET Cmd,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
This is how remote change orders arrive for input acceptance processing.
|
|
The communication layer calls this routine to insert the remote
|
|
change order on the volume change order list used by this Replica
|
|
set. Allocate a change order entry and copy the change order command
|
|
into it.
|
|
|
|
Arguments:
|
|
|
|
Cmd -- The command packet containing the change order command.
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertRemoteCo:"
|
|
|
|
PREPLICA Replica;
|
|
PCHANGE_ORDER_COMMAND CoCmd;
|
|
|
|
Replica = RsReplica(Cmd);
|
|
CoCmd = RsPartnerCoc(Cmd);
|
|
|
|
//
|
|
// Increment the Remote Change Orders Received Variable for both the
|
|
// replica set and the connection.
|
|
//
|
|
PM_INC_CTR_REPSET(Replica, RCOReceived, 1);
|
|
PM_INC_CTR_CXTION(Cxtion, RCOReceived, 1);
|
|
|
|
//
|
|
// Mark this as a remote change order and insert it into the process queue.
|
|
// A zero value for the SequenceNumber tells the cleanup code that the
|
|
// CO was never inserted into the ActiveInlogRetryTable which happens
|
|
// if the CO is rejected on the first attempt.
|
|
//
|
|
CoCmd->SequenceNumber = 0;
|
|
ClearFlag(CoCmd->Flags, CO_FLAG_NOT_REMOTELY_VALID);
|
|
CoCmd->Spare1Wcs = NULL;
|
|
CoCmd->Spare2Wcs = NULL;
|
|
CoCmd->Spare2Bin = NULL;
|
|
|
|
|
|
SET_CHANGE_ORDER_STATE_CMD(CoCmd, IBCO_INITIALIZING);
|
|
|
|
return ChgOrdInsertProcessQueue(RsReplica(Cmd), CoCmd, 0, Cxtion);
|
|
}
|
|
|
|
|
|
ULONG
|
|
ChgOrdInsertProcessQueue(
|
|
IN PREPLICA Replica,
|
|
IN PCHANGE_ORDER_COMMAND CoCmd,
|
|
IN ULONG CoeFlags,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Insert the Change order into the volume process queue for input
|
|
acceptance processing. This could be a remote change order or a retry of
|
|
either a local or remote CO. Allocate a change order entry and copy the
|
|
change order command into it. Translate what we can to local member
|
|
information.
|
|
|
|
The change order process thread handles the GUID to FID translations
|
|
since the since it already has the database tables open and it would
|
|
tie up our caller's thread to do it here.
|
|
|
|
The NewReplica field of the change order command is set to the pointer
|
|
to the Replica struct.
|
|
|
|
Arguments:
|
|
|
|
Replica -- ptr to replica struct.
|
|
|
|
CoCmd -- ptr to change order command to submit.
|
|
|
|
CoeFlags -- Additional control flags to OR into Coe->EntryFlags.
|
|
|
|
Cxtion - The cxtion that received the remote co
|
|
|
|
Return Value:
|
|
|
|
Frs Status
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdInsertProcessQueue:"
|
|
|
|
ULONG WStatus, FStatus;
|
|
ULONG ExtSize;
|
|
PCHANGE_ORDER_ENTRY ChangeOrder = NULL;
|
|
BOOL Activated, Executed;
|
|
PULONG pULong;
|
|
PDATA_EXTENSION_RETRY_TIMEOUT CoCmdRetryTimeout;
|
|
LONGLONG CurrentTime;
|
|
|
|
//
|
|
// If this is a change order retry we may already have a change order
|
|
// entry sitting in the Version Vector retire list. If so, find it,
|
|
// get a reference and use that one (if the connection Guid matches).
|
|
// This way if we end up aborting we can discard the VV retire slot
|
|
// and not propagate the CO to the outbound log. We can also accurately
|
|
// determine the state of the VV retire executed flag.
|
|
//
|
|
// The following table relates the state of the VV retire activated and
|
|
// executed flags in the change order being retried with its presence on
|
|
// the VV retire list.
|
|
//
|
|
// Found INLog Retry
|
|
// On List State
|
|
// Activated
|
|
// Executed Description
|
|
// y y y DB update beat the retry read but entry
|
|
// is still on the VV retire list. Use Reference.
|
|
// y y n DB read beat the update. Use Reference.
|
|
// y n y Error - Can't have exec set w/o activate.
|
|
// y n n Error - Not activated. Shouldn't be on list.
|
|
// n y y DB update beat the read & list entry removed.
|
|
// n y n DB read beat the update but then update completed
|
|
// & list entry deleted before retry could get the
|
|
// reference. Set Executed flag new CO for retry.
|
|
// n n y Error. Can't have exec set w/o activate.
|
|
// n n n OK. CO didn't make it far enough last time
|
|
// to activate the VV slot.
|
|
//
|
|
if (BooleanFlagOn(CoCmd->Flags, CO_FLAG_RETRY)) {
|
|
Executed = BooleanFlagOn(CoCmd->IFlags, CO_IFLAG_VVRETIRE_EXEC);
|
|
Activated = BooleanFlagOn(CoCmd->Flags, CO_FLAG_VV_ACTIVATED);
|
|
|
|
if (!Activated && Executed) {
|
|
DPRINT(0, "++ ERROR - Retry CO has CO_IFLAG_VVRETIRE_EXEC flag set but"
|
|
" CO_FLAG_VV_ACTIVATED is clear.\n");
|
|
FRS_ASSERT(!"ChgOrdInsertProcessQueue: CO_IFLAG_VVRETIRE_EXEC flag set, CO_FLAG_VV_ACTIVATED is clear");
|
|
}
|
|
|
|
ChangeOrder = VVReferenceRetireSlot(Replica, CoCmd);
|
|
|
|
if (ChangeOrder != NULL) {
|
|
if (!Activated) {
|
|
DPRINT(0, "++ ERROR - Retry CO has CO_FLAG_VV_ACTIVATED flag clear"
|
|
" but is on the VV retire list.\n");
|
|
FRS_ASSERT(!"ChgOrdInsertProcessQueue: CO_FLAG_VV_ACTIVATED is clear but is on the VV retire list");
|
|
}
|
|
} else {
|
|
//
|
|
// If CO_FLAG_VV_ACTIVATED is set then set CO_IFLAG_VVRETIRE_EXEC.
|
|
// Since we didn't find the slot on the VV retire list it must have
|
|
// been retired while this CO was waiting for retry in the INLOG.
|
|
// This is only true if this is not a change order submitted as
|
|
// part of recovery. With a recovery CO there may be no entry
|
|
// so leave the bits alone.
|
|
//
|
|
// NO, IGNORE RECOVERY FLAG!
|
|
// CO_FLAG_VV_ACTIVATED prevents this co from reserving a slot
|
|
// and from being activated during retire. Hence, the slot
|
|
// has been effectively executed. The executed flag should be
|
|
// set so that the INSTALL_INCOMPLETE bit can be turned off in
|
|
// the outbound log. So ignore the recovery flag.
|
|
//
|
|
// Although, David thought that the original intent of the
|
|
// check was to help recover from crashes. In that case,
|
|
// "Activated but not Executed" implies that the co was not
|
|
// propagated to the outlog. However, not setting _EXEC
|
|
// doesn't help that case since Activated prevents outlog
|
|
// insertion. It might help to clear _ACTIVATED in this case
|
|
// instead of setting _EXEC. Of course, the co may have
|
|
// made it into the outlog so be prepared for dup errors.
|
|
//
|
|
// WARN: inlog cos may not be propagated to outlog during crash recovery.
|
|
//
|
|
// if (Activated && !Executed &&
|
|
// !BooleanFlagOn(CoeFlags, COE_FLAG_RECOVERY_CO)) {
|
|
if (Activated && !Executed) {
|
|
SetFlag(CoCmd->IFlags, CO_IFLAG_VVRETIRE_EXEC);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ChangeOrder == NULL) {
|
|
|
|
//
|
|
// Allocate a change order entry with room for the filename and copy
|
|
// the change order + Filename into it. Init the ref count to one
|
|
// since the CO is going on the process queue.
|
|
//
|
|
ChangeOrder = FrsAllocType(CHANGE_ORDER_ENTRY_TYPE);
|
|
CopyMemory(&ChangeOrder->Cmd, CoCmd, sizeof(CHANGE_ORDER_COMMAND));
|
|
ChangeOrder->Cmd.Extension = NULL;
|
|
ChangeOrder->UFileName.Length = ChangeOrder->Cmd.FileNameLength;
|
|
ChangeOrder->HashEntryHeader.ReferenceCount = 0;
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder); // for tracking
|
|
|
|
//
|
|
// Copy the Change Order Extension if provided.
|
|
//
|
|
ExtSize = sizeof(CHANGE_ORDER_RECORD_EXTENSION);
|
|
|
|
if ((CoCmd->Extension != NULL) &&
|
|
(CoCmd->Extension->FieldSize > 0)) {
|
|
|
|
if (CoCmd->Extension->FieldSize >= REALLY_BIG_EXTENSION_SIZE) {
|
|
pULong = (PULONG) CoCmd->Extension;
|
|
|
|
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));
|
|
|
|
FRS_ASSERT(!"CoCmd->Extension->FieldSize corrupted");
|
|
}
|
|
|
|
//
|
|
// Convert the CO_RECORD_EXTENSION_WIN2K struct from downlevel members
|
|
// to a CHANGE_ORDER_RECORD_EXTENSION struct that we understand.
|
|
//
|
|
if (CoCmd->Extension->Major == CO_RECORD_EXTENSION_VERSION_WIN2K) {
|
|
|
|
ChangeOrder->Cmd.Extension =
|
|
DbsDataConvertCocExtensionFromWin2K((PCO_RECORD_EXTENSION_WIN2K)CoCmd->Extension);
|
|
} else {
|
|
//
|
|
// If incoming CO has larger extension, use it.
|
|
//
|
|
if (ExtSize < CoCmd->Extension->FieldSize) {
|
|
ExtSize = CoCmd->Extension->FieldSize;
|
|
}
|
|
|
|
ChangeOrder->Cmd.Extension = FrsAlloc(ExtSize);
|
|
CopyMemory(ChangeOrder->Cmd.Extension, CoCmd->Extension, ExtSize);
|
|
}
|
|
} else {
|
|
//
|
|
// Incoming extension NULL or Field Size zero. Init new one.
|
|
//
|
|
ChangeOrder->Cmd.Extension = FrsAlloc(ExtSize);
|
|
DbsDataInitCocExtension(ChangeOrder->Cmd.Extension);
|
|
}
|
|
|
|
CoCmdRetryTimeout = DbsDataExtensionFind(ChangeOrder->Cmd.Extension, DataExtend_Retry_Timeout);
|
|
|
|
if(CoCmdRetryTimeout != NULL) {
|
|
GetSystemTimeAsFileTime((PFILETIME)&CurrentTime);
|
|
|
|
//
|
|
// if this is the first time through, set the time.
|
|
//
|
|
if(CoCmdRetryTimeout->Count == 0) {
|
|
CoCmdRetryTimeout->FirstTryTime = CurrentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// MOVERS is only for local ChangeOrders. They are transformed into
|
|
// a delete and create change orders targeted to their respective
|
|
// replica sets.
|
|
//
|
|
if (CO_LOCN_CMD_IS(ChangeOrder, CO_LOCATION_MOVERS)) {
|
|
DPRINT(0, "++ ERROR - change order command is MOVERS.");
|
|
FRS_PRINT_TYPE(0, ChangeOrder);
|
|
FrsFreeType(ChangeOrder);
|
|
return FrsErrorInvalidChangeOrder;
|
|
}
|
|
|
|
//
|
|
// Save ptr to target replica struct.
|
|
//
|
|
ChangeOrder->NewReplica = Replica;
|
|
ChangeOrder->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.
|
|
//
|
|
// Note: MOVERS: This may not work for a LocalCo retry of a MOVERS.
|
|
ChangeOrder->OriginalReplica = Replica;
|
|
ChangeOrder->Cmd.OriginalReplicaNum = ReplicaAddrToId(Replica);
|
|
|
|
FRS_ASSERT( CO_STATE(ChangeOrder) < IBCO_MAX_STATE );
|
|
|
|
SET_COE_FLAG(ChangeOrder, CoeFlags);
|
|
|
|
//
|
|
// Put the change order on the tail end of the volume change order list.
|
|
//
|
|
if (CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO) &&
|
|
!CO_FLAG_ON(ChangeOrder, CO_FLAG_RETRY |
|
|
CO_FLAG_CONTROL |
|
|
CO_FLAG_MOVEIN_GEN |
|
|
CO_FLAG_MORPH_GEN) &&
|
|
!RecoveryCo(ChangeOrder) &&
|
|
!COE_FLAG_ON(ChangeOrder, COE_FLAG_PARENT_REANIMATION)) {
|
|
|
|
INC_LOCAL_CO_QUEUE_COUNT(Replica);
|
|
}
|
|
|
|
//
|
|
// Pick up the cxtion's join guid (session id). The change orders
|
|
// will be sent back through the retry path if the cxtion's
|
|
// state has changed by the time the change orders make it
|
|
// to the replica command server. The join guid is used for
|
|
// asserting that we never have a change order for a joined
|
|
// cxtion with a mismatched join guid.
|
|
//
|
|
// The count of remote change orders is used when transitioning
|
|
// the cxtion from UNJOINING to UNJOINED. Further requests to
|
|
// join are ignored until the transition. Basically, we are
|
|
// waiting for the change orders to be put into the retry state
|
|
// for later recovery.
|
|
//
|
|
// Synchronize with change order accept and the replica command server
|
|
//
|
|
if (!CO_FLAG_ON(ChangeOrder, CO_FLAG_LOCALCO)) {
|
|
//
|
|
// This change order may already have a cxtion if it was
|
|
// pulled from the active retry table and there is no
|
|
// guarantee that the change order in the table is from
|
|
// the same cxtion as this change order since the table
|
|
// is sorted by change order guid.
|
|
//
|
|
if (ChangeOrder->Cxtion == NULL) {
|
|
ChangeOrder->Cxtion = Cxtion;
|
|
}
|
|
FRS_ASSERT(ChangeOrder->Cxtion);
|
|
|
|
//
|
|
// Bump the change order count associated with this connection.
|
|
// Any change order with a non-NULL ChangeOrder->Cxtion will have
|
|
// its remote change order count decremented at issue cleanup.
|
|
// The count applies to control change orders too.
|
|
//
|
|
LOCK_CXTION_TABLE(Replica);
|
|
INCREMENT_CXTION_CHANGE_ORDER_COUNT(Replica, ChangeOrder->Cxtion);
|
|
UNLOCK_CXTION_TABLE(Replica);
|
|
|
|
} else {
|
|
//
|
|
// Set the Jrnl Cxtion Guid and Cxtion ptr for this Local CO.
|
|
// This bumps the Cxtion CO count.
|
|
// Any change order with a non-NULL ChangeOrder->Cxtion will have
|
|
// its remote change order count decremented at issue cleanup.
|
|
// This GUID changes each time the service starts the replica set.
|
|
//
|
|
ChangeOrder->Cmd.CxtionGuid = Replica->JrnlCxtionGuid;
|
|
ACQUIRE_CXTION_CO_REFERENCE(Replica, ChangeOrder);
|
|
|
|
if (ChangeOrder->Cxtion == NULL) {
|
|
return FrsErrorInvalidChangeOrder;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Refresh the Join Guid.
|
|
//
|
|
ChangeOrder->JoinGuid = ChangeOrder->Cxtion->JoinGuid;
|
|
|
|
WStatus = ChgOrdInsertProcQ(Replica, ChangeOrder, IPQ_TAIL | IPQ_DEREF_CXTION_IF_ERR);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
SET_ISSUE_CLEANUP(ChangeOrder, ISCU_FREE_CO | ISCU_DEC_CO_REF);
|
|
FStatus = ChgOrdIssueCleanup(NULL, Replica, ChangeOrder, 0);
|
|
DPRINT_FS(0, "++ ChgOrdIssueCleanup error.", FStatus);
|
|
return FrsErrorInternalError;
|
|
}
|
|
|
|
return FrsErrorSuccess;
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdStartJoinRequest(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
An inbound connection wants to start up. Submit a control change order
|
|
that will scan the inlog and requeue any pending COs from this connection.
|
|
|
|
Arguments:
|
|
|
|
Replica -- The replica set owning the connection.
|
|
Cxtion -- the connection being started.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdStartJoinRequest:"
|
|
|
|
CHANGE_ORDER_COMMAND CoCmd;
|
|
//
|
|
// Mark this as a control change order and insert it into the process queue.
|
|
// When it gets to the head of the queue we know all COs from this Cxtion
|
|
// that may have been ahead of it in the queue are now in the inbound Log.
|
|
//
|
|
// Use a temp CoCmd since it gets copied into the change order entry when
|
|
// it is inserted onto the process queue.
|
|
//
|
|
ZeroMemory( &CoCmd, sizeof(CHANGE_ORDER_COMMAND));
|
|
|
|
CoCmd.ContentCmd = FCN_CORETRY_ONE_CXTION;
|
|
COPY_GUID(&CoCmd.CxtionGuid, Cxtion->Name->Guid);
|
|
|
|
SetFlag(CoCmd.Flags, CO_FLAG_CONTROL);
|
|
|
|
wcscpy(CoCmd.FileName, L"VVJoinStartRequest"); // for tracing.
|
|
|
|
SET_CHANGE_ORDER_STATE_CMD(&CoCmd, IBCO_INITIALIZING);
|
|
|
|
ChgOrdInsertProcessQueue(Replica, &CoCmd, 0, Cxtion);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
PCHANGE_ORDER_ENTRY
|
|
ChgOrdMakeFromFile(
|
|
IN PREPLICA Replica,
|
|
IN HANDLE FileHandle,
|
|
IN PULONGLONG ParentFid,
|
|
IN ULONG LocationCmd,
|
|
IN ULONG CoFlags,
|
|
IN PWCHAR FileName,
|
|
IN USHORT Length
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This functions allocates a change order entry and inits some of the fields.
|
|
|
|
Depending on the change order some of these fields may be overwritten later.
|
|
|
|
Example Call:
|
|
|
|
//
|
|
// Allocate and init a local change order using the supplied file handle.
|
|
//
|
|
//NewCoe = ChgOrdMakeFromFile(Replica,
|
|
// FileHandle,
|
|
// &MoveInContext->ParentFileID,
|
|
// CO_LOCATION_MOVEIN,
|
|
// CO_FLAG_LOCALCO | CO_FLAG_LOCATION_CMD,
|
|
// DirectoryRecord->FileName,
|
|
// (USHORT)DirectoryRecord->FileNameLength);
|
|
|
|
Arguments:
|
|
|
|
Replica - ptr to replica set for this change order.
|
|
FileHandle - The open file handle.
|
|
ParentFid - The parent file reference number for this file.
|
|
LocationCmd -- A Create, delete, ... change order.
|
|
CoFlags -- The change order option flags. see schema.h
|
|
FileName - Filename for this file. For a sub tree op it comes from the
|
|
filter entry.
|
|
Length - the file name length in bytes.
|
|
|
|
Return Value:
|
|
|
|
ptr to change order entry. NULL if can't get file info.
|
|
|
|
--*/
|
|
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMakeFromFile:"
|
|
|
|
DWORD WStatus;
|
|
|
|
FILE_INTERNAL_INFORMATION FileInternalInfo;
|
|
|
|
FILE_NETWORK_OPEN_INFORMATION FileInfo;
|
|
|
|
BOOL IsDirectory;
|
|
PCHANGE_ORDER_ENTRY ChangeOrder;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
PCONFIG_TABLE_RECORD ConfigRecord;
|
|
|
|
//
|
|
// Get the file's attributes
|
|
//
|
|
if (!FrsGetFileInfoByHandle(FileName, FileHandle, &FileInfo)) {
|
|
DPRINT1(4, "++ Can't get attributes for %ws\n", FileName);
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Get the fid of preinstall area for filtering.
|
|
//
|
|
WStatus = FrsGetFileInternalInfoByHandle(Replica->PreInstallHandle,
|
|
&FileInternalInfo);
|
|
DPRINT_WS(0, "++ ERROR - FrsGetFileInternalInfoByHandle(PreInstallDir).", WStatus);
|
|
if (!WIN_SUCCESS(WStatus)) {
|
|
return NULL;
|
|
}
|
|
|
|
IsDirectory = (FileInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
|
|
//
|
|
// Note: This function is currently (11/19/98) unused. If it is ever used,
|
|
// and the change order allocated below turns out to be a local CO,
|
|
// remember to set the JrnlCxtion field of the change order command
|
|
//
|
|
// Construct new change order and allocate a change order extension.
|
|
//
|
|
ChangeOrder = FrsAllocType(CHANGE_ORDER_ENTRY_TYPE);
|
|
ChangeOrder->Cmd.Extension = FrsAlloc(sizeof(CHANGE_ORDER_RECORD_EXTENSION));
|
|
DbsDataInitCocExtension(ChangeOrder->Cmd.Extension);
|
|
|
|
Coc = &ChangeOrder->Cmd;
|
|
|
|
//
|
|
// Set the initial reference count to 1.
|
|
//
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(ChangeOrder);
|
|
|
|
//
|
|
// Capture the file name.
|
|
//
|
|
FRS_ASSERT(Length <= MAX_PATH*2);
|
|
CopyMemory(ChangeOrder->Cmd.FileName, FileName, Length);
|
|
Coc->FileName[Length/2] = UNICODE_NULL;
|
|
ChangeOrder->UFileName.Length = Length;
|
|
Coc->FileNameLength = Length;
|
|
|
|
//
|
|
// Set New and orig Replica fields to the replica.
|
|
//
|
|
ChangeOrder->OriginalReplica = Replica;
|
|
ChangeOrder->NewReplica = Replica;
|
|
Coc->OriginalReplicaNum = ReplicaAddrToId(ChangeOrder->OriginalReplica);
|
|
Coc->NewReplicaNum = ReplicaAddrToId(ChangeOrder->NewReplica);
|
|
|
|
//
|
|
// Set New and orig parent FID fields to the parent FID.
|
|
//
|
|
ChangeOrder->OriginalParentFid = *ParentFid;
|
|
ChangeOrder->NewParentFid = *ParentFid;
|
|
ChangeOrder->FileReferenceNumber = FileInternalInfo.IndexNumber.QuadPart;
|
|
ChangeOrder->ParentFileReferenceNumber = *ParentFid;
|
|
|
|
//
|
|
// EntryCreateTime is a tick count for aging.
|
|
//
|
|
ChangeOrder->EntryCreateTime = CO_TIME_NOW(Replica->pVme);
|
|
|
|
//
|
|
// Event time from current time.
|
|
//
|
|
GetSystemTimeAsFileTime((PFILETIME)&Coc->EventTime.QuadPart);
|
|
|
|
//
|
|
// File's attributes, Create time, Write time
|
|
//
|
|
ChangeOrder->FileAttributes = FileInfo.FileAttributes;
|
|
|
|
Coc->FileAttributes = FileInfo.FileAttributes;
|
|
|
|
ChangeOrder->FileCreateTime = FileInfo.CreationTime;
|
|
ChangeOrder->FileWriteTime = FileInfo.LastWriteTime;
|
|
|
|
Coc->FileSize = FileInfo.AllocationSize.QuadPart;
|
|
|
|
|
|
ConfigRecord = (PCONFIG_TABLE_RECORD) (Replica->ConfigTable.pDataRecord);
|
|
Coc->OriginatorGuid = ConfigRecord->ReplicaVersionGuid;
|
|
|
|
Coc->FileVersionNumber = 0;
|
|
|
|
//
|
|
// File's USN
|
|
//
|
|
Coc->FileUsn = 0;
|
|
Coc->JrnlUsn = 0;
|
|
Coc->JrnlFirstUsn = 0;
|
|
|
|
//
|
|
// File is dir?
|
|
//
|
|
SET_CO_LOCATION_CMD(*Coc,
|
|
DirOrFile,
|
|
(IsDirectory ? CO_LOCATION_DIR : CO_LOCATION_FILE));
|
|
|
|
//
|
|
// Create, Delete, ..
|
|
//
|
|
SET_CO_LOCATION_CMD(*Coc, Command, LocationCmd);
|
|
|
|
if (LocationCmd == CO_LOCATION_NO_CMD) {
|
|
Coc->ContentCmd = USN_REASON_DATA_OVERWRITE;
|
|
} else
|
|
if (CO_NEW_FILE(LocationCmd)) {
|
|
Coc->ContentCmd = USN_REASON_FILE_CREATE;
|
|
} else
|
|
if (CO_DELETE_FILE(LocationCmd)) {
|
|
Coc->ContentCmd = USN_REASON_FILE_DELETE;
|
|
} else {
|
|
DPRINT1(0, "++ Error - Invalid location cmd: %d\n", LocationCmd);
|
|
Coc->ContentCmd = USN_REASON_DATA_OVERWRITE;
|
|
}
|
|
|
|
//
|
|
// Set the change order flags.
|
|
//
|
|
SET_CO_FLAG(ChangeOrder, CoFlags);
|
|
|
|
return ChangeOrder;
|
|
}
|
|
|
|
|
|
|
|
|
|
PCHANGE_ORDER_ENTRY
|
|
ChgOrdMakeFromIDRecord(
|
|
IN PIDTABLE_RECORD IDTableRec,
|
|
IN PREPLICA Replica,
|
|
IN ULONG LocationCmd,
|
|
IN ULONG CoFlags,
|
|
IN GUID *CxtionGuid
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create and init a change order. It starts out with a ref count of one.
|
|
|
|
Note - Since only a single Replica arg is passed in and only a single
|
|
parent guid is available for the IDTable record this function can not
|
|
create rename change orders.
|
|
|
|
Arguments:
|
|
|
|
IDTableRec - ID table record.
|
|
Replica -- The Replica set this CO is for.
|
|
LocationCmd -- A Create, delete, ... change order.
|
|
CoFlags -- The change order option flags. see schema.h
|
|
CxtionGuid -- The Guid of the connection this CO will be sent to.
|
|
NULL if unneeded.
|
|
|
|
Thread Return Value:
|
|
|
|
A ptr to a change order entry.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdMakeFromIDRecord:"
|
|
|
|
PCHANGE_ORDER_ENTRY Coe;
|
|
PCHANGE_ORDER_COMMAND Coc;
|
|
|
|
PDATA_EXTENSION_CHECKSUM IdtDataChkSum;
|
|
PIDTABLE_RECORD_EXTENSION IdtExt;
|
|
PCHANGE_ORDER_RECORD_EXTENSION CocExt;
|
|
|
|
|
|
FRS_ASSERT((LocationCmd == CO_LOCATION_NO_CMD) ||
|
|
(LocationCmd == CO_LOCATION_CREATE) ||
|
|
(LocationCmd == CO_LOCATION_MOVEOUT) ||
|
|
(LocationCmd == CO_LOCATION_DELETE));
|
|
|
|
//
|
|
// Alloc Change order entry and extension.
|
|
//
|
|
Coe = FrsAllocType(CHANGE_ORDER_ENTRY_TYPE);
|
|
Coe->Cmd.Extension = FrsAlloc(sizeof(CHANGE_ORDER_RECORD_EXTENSION));
|
|
DbsDataInitCocExtension(Coe->Cmd.Extension);
|
|
|
|
Coc = &Coe->Cmd;
|
|
|
|
//
|
|
// Assign a change order guid and init ref count.
|
|
//
|
|
FrsUuidCreate(&Coc->ChangeOrderGuid);
|
|
INCREMENT_CHANGE_ORDER_REF_COUNT(Coe);
|
|
//
|
|
// File's fid and parent FID
|
|
//
|
|
Coe->FileReferenceNumber = IDTableRec->FileID;
|
|
Coe->ParentFileReferenceNumber = IDTableRec->ParentFileID;
|
|
//
|
|
// Original parent and New parent are the same (not a rename)
|
|
//
|
|
Coe->OriginalParentFid = IDTableRec->ParentFileID;
|
|
Coe->NewParentFid = IDTableRec->ParentFileID;
|
|
//
|
|
// File's attributes, create and write times.
|
|
//
|
|
Coe->FileAttributes = IDTableRec->FileAttributes;
|
|
Coe->FileCreateTime = IDTableRec->FileCreateTime;
|
|
Coe->FileWriteTime = IDTableRec->FileWriteTime;
|
|
//
|
|
// Jet context for outlog insertion
|
|
//
|
|
Coe->RtCtx = FrsAllocTypeSize(REPLICA_THREAD_TYPE,
|
|
FLAG_FRSALLOC_NO_ALLOC_TBL_CTX);
|
|
|
|
FrsRtlInsertTailList(&Replica->ReplicaCtxListHead,
|
|
&Coe->RtCtx->ReplicaCtxList);
|
|
//
|
|
// The sequence number is zero initially. It may get a value when
|
|
// the CO is inserted into an inbound or outbound log.
|
|
//
|
|
Coc->SequenceNumber = 0;
|
|
Coc->PartnerAckSeqNumber = 0;
|
|
//
|
|
// File's attributes, again
|
|
//
|
|
Coc->FileAttributes = Coe->FileAttributes;
|
|
//
|
|
// File's version number, size, VSN, and event time.
|
|
//
|
|
Coc->FileVersionNumber = IDTableRec->VersionNumber;
|
|
Coc->FileSize = IDTableRec->FileSize;
|
|
Coc->FrsVsn = IDTableRec->OriginatorVSN;
|
|
Coc->EventTime.QuadPart = IDTableRec->EventTime;
|
|
//
|
|
// Original and New Replica are the same (not a rename)
|
|
//
|
|
Coe->OriginalReplica = Replica;
|
|
Coe->NewReplica = Replica;
|
|
Coc->OriginalReplicaNum = ReplicaAddrToId(Coe->OriginalReplica);
|
|
Coc->NewReplicaNum = ReplicaAddrToId(Coe->NewReplica);
|
|
//
|
|
// Originator guid, File guid, old and new parent guid.
|
|
//
|
|
Coc->OriginatorGuid = IDTableRec->OriginatorGuid;
|
|
Coc->FileGuid = IDTableRec->FileGuid;
|
|
Coc->OldParentGuid = IDTableRec->ParentGuid;
|
|
Coc->NewParentGuid = IDTableRec->ParentGuid;
|
|
//
|
|
// Cxtion's guid (identifies the cxtion)
|
|
//
|
|
if (CxtionGuid != NULL) {
|
|
Coc->CxtionGuid = *CxtionGuid;
|
|
}
|
|
//
|
|
// Filename length in bytes not including the terminating NULL
|
|
//
|
|
Coc->FileNameLength = wcslen(IDTableRec->FileName) * sizeof(WCHAR);
|
|
Coe->UFileName.Length = Coc->FileNameLength;
|
|
//
|
|
// Filename (including the terminating NULL)
|
|
//
|
|
CopyMemory(Coc->FileName, IDTableRec->FileName, Coc->FileNameLength + sizeof(WCHAR));
|
|
//
|
|
// File's USN
|
|
//
|
|
Coc->FileUsn = IDTableRec->CurrentFileUsn;
|
|
Coc->JrnlUsn = 0;
|
|
Coc->JrnlFirstUsn = 0;
|
|
|
|
//
|
|
// If the IDTable Record has a file checksum, copy it into the changeorder.
|
|
//
|
|
IdtExt = &IDTableRec->Extension;
|
|
IdtDataChkSum = DbsDataExtensionFind(IdtExt, DataExtend_MD5_CheckSum);
|
|
CocExt = Coc->Extension;
|
|
|
|
if (IdtDataChkSum != NULL) {
|
|
if (IdtDataChkSum->Prefix.Size != sizeof(DATA_EXTENSION_CHECKSUM)) {
|
|
DPRINT1(0, "<MD5_CheckSum Size (%08x) invalid>\n",
|
|
IdtDataChkSum->Prefix.Size);
|
|
//
|
|
// Format is hosed. Reinit it and zero checksum.
|
|
//
|
|
DbsDataInitIDTableExtension(IdtExt);
|
|
IdtDataChkSum = DbsDataExtensionFind(IdtExt, DataExtend_MD5_CheckSum);
|
|
ZeroMemory(IdtDataChkSum->Data, MD5DIGESTLEN);
|
|
}
|
|
|
|
DPRINT4(4, "IDT MD5: %08x %08x %08x %08x\n",
|
|
*(((ULONG *) &IdtDataChkSum->Data[0])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[4])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[8])),
|
|
*(((ULONG *) &IdtDataChkSum->Data[12])));
|
|
|
|
CopyMemory(CocExt->DataChecksum.Data, IdtDataChkSum->Data, MD5DIGESTLEN);
|
|
} else {
|
|
ZeroMemory(CocExt->DataChecksum.Data, MD5DIGESTLEN);
|
|
}
|
|
|
|
//
|
|
// File is dir?
|
|
//
|
|
SET_CO_LOCATION_CMD(*Coc,
|
|
DirOrFile,
|
|
((IDTableRec->FileIsDir) ?
|
|
CO_LOCATION_DIR : CO_LOCATION_FILE));
|
|
//
|
|
// Consistency check.
|
|
//
|
|
FRS_ASSERT(BooleanFlagOn(IDTableRec->FileAttributes, FILE_ATTRIBUTE_DIRECTORY)
|
|
== ((BOOLEAN) IDTableRec->FileIsDir));
|
|
|
|
//
|
|
// Create, Delete, ..
|
|
//
|
|
SET_CO_LOCATION_CMD(*Coc, Command, LocationCmd);
|
|
|
|
if (LocationCmd == CO_LOCATION_NO_CMD) {
|
|
Coc->ContentCmd = USN_REASON_DATA_OVERWRITE;
|
|
} else
|
|
if (LocationCmd == CO_LOCATION_CREATE) {
|
|
Coc->ContentCmd = USN_REASON_FILE_CREATE;
|
|
} else
|
|
if (LocationCmd == CO_LOCATION_DELETE) {
|
|
Coc->ContentCmd = USN_REASON_FILE_DELETE;
|
|
} else
|
|
if (LocationCmd == CO_LOCATION_MOVEOUT) {
|
|
Coc->ContentCmd = 0;
|
|
} else {
|
|
DPRINT1(0, "++ Error - Invalid location cmd: %d\n", LocationCmd);
|
|
Coc->ContentCmd = USN_REASON_DATA_OVERWRITE;
|
|
}
|
|
|
|
//
|
|
// Set the change order flags.
|
|
//
|
|
SET_CO_FLAG(Coe, CoFlags);
|
|
|
|
//
|
|
// Return CO to caller.
|
|
//
|
|
return Coe;
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdRetrySubmit(
|
|
IN PREPLICA Replica,
|
|
IN PCXTION Cxtion,
|
|
IN USHORT Command,
|
|
IN BOOL Wait
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
Submit retry request to change order retry cmd server.
|
|
|
|
Arguments:
|
|
|
|
Replica -- Replica set on which to perform retry scan.
|
|
CxtionGuid -- Inbound cxtion guid if doing a single cxtion only.
|
|
Command --
|
|
FCN_CORETRY_ONE_CXTION -- Do scan for COs from single inbound partner.
|
|
FCN_CORETRY_ALL_CXTIONS -- Do Scan for COs from all inbound partners.
|
|
FCN_CORETRY_LOCAL_ONLY -- Do scan for Local COs only.
|
|
Wait -- True if we are to wait until command completes.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdRetrySubmit:"
|
|
|
|
PCOMMAND_PACKET Cmd;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
ULONG WStatus;
|
|
|
|
//
|
|
// Build command to scan inlog for retry COs. Pass Replica ptr and
|
|
// Cxtion Guid. If Cxtion Quid is Null then all Cxtions are done.
|
|
//
|
|
|
|
Cmd = FrsAllocCommand(&ChgOrdRetryCS.Queue, Command);
|
|
|
|
CoRetryReplica(Cmd) = Replica;
|
|
CoRetryCxtion(Cmd) = Cxtion;
|
|
if (Cxtion) {
|
|
GuidToStr(Cxtion->Name->Guid, GuidStr);
|
|
DPRINT2(1, "++ ChgOrdRetryCS: submit for Replica %ws for Cxtion %s\n",
|
|
Replica->ReplicaName->Name, GuidStr);
|
|
} else {
|
|
DPRINT1(1, "++ ChgOrdRetryCS: submit for Replica %ws\n",
|
|
Replica->ReplicaName->Name);
|
|
}
|
|
|
|
|
|
if (Wait) {
|
|
//
|
|
// Make the call synchronous.
|
|
// Don't free the packet when the command completes.
|
|
//
|
|
FrsSetCompletionRoutine(Cmd, FrsCompleteKeepPkt, NULL);
|
|
|
|
//
|
|
// SUBMIT retry request Cmd and wait for completion.
|
|
//
|
|
WStatus = FrsSubmitCommandServerAndWait(&ChgOrdRetryCS, Cmd, INFINITE);
|
|
DPRINT_WS(0, "++ ERROR - DB Command failed.", WStatus);
|
|
FrsFreeCommand(Cmd, NULL);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Fire and forget the command.
|
|
//
|
|
FrsSubmitCommandServer(&ChgOrdRetryCS, Cmd);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ChgOrdCalcHashGuidAndName (
|
|
IN PUNICODE_STRING Name,
|
|
IN GUID *Guid,
|
|
OUT PULONGLONG HashValue
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine forms a 32 bit hash of the name and guid args.
|
|
It returns this in the low 32 bits of HashValue. The upper 32 bits are zero.
|
|
|
|
Arguments:
|
|
|
|
Name - The filename to hash.
|
|
Guid - The Guid to hash.
|
|
HashValue - The resulting quadword hash value.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
#undef DEBSUB
|
|
#define DEBSUB "ChgOrdCalcHashGuidAndName:"
|
|
|
|
PUSHORT p;
|
|
ULONG NameHash = 0;
|
|
ULONG Shift = 0;
|
|
ULONG GuidHash;
|
|
CHAR GuidStr[GUID_CHAR_LEN];
|
|
|
|
FRS_ASSERT( ValueIsMultOf2(Name->Length) );
|
|
FRS_ASSERT( Name->Length != 0 );
|
|
|
|
|
|
//
|
|
// Combine each unicode character into the hash value, shifting 4 bits
|
|
// each time. Start at the end of the name so file names with different
|
|
// type codes will hash to different table offsets.
|
|
//
|
|
for( p = Name->Buffer + (Name->Length / sizeof(WCHAR)) - 1;
|
|
p >= Name->Buffer;
|
|
p-- ) {
|
|
|
|
// NameHash = NameHash ^ (((ULONG)*p) << Shift);
|
|
NameHash = NameHash ^ (((ULONG)towupper(*p)) << Shift);
|
|
|
|
Shift = (Shift < 16) ? Shift + 4 : 0;
|
|
}
|
|
|
|
//
|
|
// Combine each USHORT of the Guid into the hash value, shifting 4 bits
|
|
// each time.
|
|
//
|
|
GuidHash = JrnlHashCalcGuid(Guid, sizeof(GUID));
|
|
|
|
//p = (PUSHORT) Guid;
|
|
//GuidHash = (((ULONG)*(p+0)) << 0) ^ (((ULONG)*(p+1)) << 4)
|
|
// ^ (((ULONG)*(p+2)) << 8) ^ (((ULONG)*(p+3)) << 12)
|
|
// ^ (((ULONG)*(p+4)) << 16) ^ (((ULONG)*(p+5)) << 0)
|
|
// ^ (((ULONG)*(p+6)) << 4) ^ (((ULONG)*(p+7)) << 8);
|
|
|
|
if (GuidHash == 0) {
|
|
GuidToStr(Guid, GuidStr);
|
|
DPRINT2(0, "++ Warning - GuidHash is zero. Guid: %s Name: %ws\n",
|
|
Guid, Name->Buffer);
|
|
}
|
|
|
|
*HashValue = (ULONGLONG) (NameHash + GuidHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|