1167 lines
31 KiB
C
1167 lines
31 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1996 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
dmlocal.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Contains the routines for local transactions that can be called within
|
||
|
gum handlers.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Sunita Shrivastava (sunitas) 24-Apr-1996
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
#include "dmp.h"
|
||
|
#include "clusudef.h"
|
||
|
|
||
|
extern BOOL gbIsQuoLoggingOn;
|
||
|
extern PFM_RESOURCE gpQuoResource;
|
||
|
extern DWORD gbIsQuoResOnline;
|
||
|
extern HLOG ghQuoLog;
|
||
|
#if NO_SHARED_LOCKS
|
||
|
extern CRITICAL_SECTION gLockDmpRoot;
|
||
|
#else
|
||
|
extern RTL_RESOURCE gLockDmpRoot;
|
||
|
#endif
|
||
|
/****
|
||
|
@doc EXTERNAL INTERFACES CLUSSVC DM
|
||
|
****/
|
||
|
|
||
|
/****
|
||
|
@func HXSACTION | DmBeginLocalUpdate| Called by gum handlers to make consistent
|
||
|
changes to the local registry. The log is reset and a start transaction
|
||
|
record is written to the log, if the log is active.
|
||
|
|
||
|
@comm When GumHandlers need to update the registry consitently they must use the
|
||
|
LocalApis provided by DM.
|
||
|
|
||
|
@rdesc Returns a transaction handle. NULL on failure. Call GetLastError()
|
||
|
for error code.
|
||
|
|
||
|
@xref <f DmAbortLocalUpdate> <f DmCommitLocalUpdate>
|
||
|
****/
|
||
|
HLOCALXSACTION DmBeginLocalUpdate()
|
||
|
{
|
||
|
DWORD dwError=ERROR_SUCCESS;
|
||
|
LSN StartXsactionLsn;
|
||
|
DWORD dwSequence;
|
||
|
HXSACTION hXsaction = NULL;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmBeginLocalUpdate Entry\r\n");
|
||
|
|
||
|
//lock the data base, so that a check point is not taken in this duration
|
||
|
//this lock is released in DmCommitLocalUpdate() or DmAbortLocalUpdate()
|
||
|
//this lock also prevents the the registry from being flushed.
|
||
|
ACQUIRE_EXCLUSIVE_LOCK(gLockDmpRoot);
|
||
|
|
||
|
|
||
|
//Commit the registry so that it can be restored on abort
|
||
|
if ((dwError = DmCommitRegistry()) != ERROR_SUCCESS)
|
||
|
{
|
||
|
goto FnExit;
|
||
|
}
|
||
|
//allocate memory for local transaction
|
||
|
pLocalXsaction = LocalAlloc(LMEM_FIXED, sizeof(LOCALXSACTION));
|
||
|
if (!pLocalXsaction)
|
||
|
{
|
||
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
pLocalXsaction->dwSig = LOCALXSAC_SIG;
|
||
|
dwSequence = GumGetCurrentSequence(GumUpdateRegistry);
|
||
|
pLocalXsaction->dwSequence = dwSequence;
|
||
|
pLocalXsaction->hLogXsaction = NULL;
|
||
|
InitializeListHead(&pLocalXsaction->PendingNotifyListHead);
|
||
|
|
||
|
//log the start checkpoint record
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource) && ghQuoLog)
|
||
|
{
|
||
|
|
||
|
hXsaction = LogStartXsaction(ghQuoLog, dwSequence ,RMRegistryMgr, 0);
|
||
|
if (!hXsaction)
|
||
|
{
|
||
|
dwError = GetLastError();
|
||
|
}
|
||
|
pLocalXsaction->hLogXsaction = hXsaction;
|
||
|
}
|
||
|
FnExit:
|
||
|
if (dwError != ERROR_SUCCESS)
|
||
|
{
|
||
|
if (pLocalXsaction) LocalFree(pLocalXsaction);
|
||
|
pLocalXsaction = NULL;
|
||
|
RELEASE_LOCK(gLockDmpRoot);
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmBeginLocalUpdate Exit, pLocalXsaction=0x%1!08lx! Error=0x%2!08lx!\r\n",
|
||
|
pLocalXsaction, dwError);
|
||
|
} else {
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmBeginLocalUpdate Exit, pLocalXsaction=0x%1!08lx!\r\n",
|
||
|
pLocalXsaction);
|
||
|
}
|
||
|
|
||
|
return((HLOCALXSACTION)pLocalXsaction);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/****
|
||
|
@func DWORD | DmCommitLocalUpdate| This api must be called to commit
|
||
|
the changes to the local registry.
|
||
|
|
||
|
@parm IN HXSACTION | hXsaction | The handle to the transaction to be committed.
|
||
|
|
||
|
@comm A commit record is written the quorum log if logging is active.
|
||
|
|
||
|
@rdesc Returns a result code. ERROR_SUCCESS on success.
|
||
|
|
||
|
@xref <f DmBeginLocalUpdate> <f DmAbortLocalUpdate>
|
||
|
****/
|
||
|
DWORD DmCommitLocalUpdate(IN HLOCALXSACTION hLocalXsaction)
|
||
|
{
|
||
|
DWORD dwError=ERROR_SUCCESS;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmCommitLocalUpdate Entry\r\n");
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
|
||
|
//update the gum sequence
|
||
|
DmpUpdateSequence();
|
||
|
|
||
|
DmpReportPendingNotifications(pLocalXsaction, TRUE );
|
||
|
|
||
|
//write a commit record to the quorum log
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
CL_ASSERT(pLocalXsaction->hLogXsaction);
|
||
|
dwError = LogCommitXsaction(ghQuoLog, pLocalXsaction->hLogXsaction, 0);
|
||
|
//
|
||
|
// Chittur Subbaraman (chitturs) - 1/19/99
|
||
|
//
|
||
|
pLocalXsaction->hLogXsaction = NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Chittur Subbaraman (chitturs) - 1/19/99
|
||
|
//
|
||
|
// Make sure that the hLogXsaction memory is freed (even in the case
|
||
|
// in which you started a local xsaction and didn't get a chance to
|
||
|
// commit it or abort it to the log because quorum logging got turned
|
||
|
// off in the middle, for example. This turning off of the logging
|
||
|
// in the middle of a transaction could be considered as a bug ?)
|
||
|
//
|
||
|
LocalFree( pLocalXsaction->hLogXsaction );
|
||
|
|
||
|
//invalidate the signature and free the transaction structure
|
||
|
pLocalXsaction->dwSig = 0;
|
||
|
LocalFree(pLocalXsaction);
|
||
|
//release the database
|
||
|
RELEASE_LOCK(gLockDmpRoot);
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmCommitLocalUpdate Exit, returning 0x%1!08lx!\r\n",
|
||
|
dwError);
|
||
|
|
||
|
return(dwError);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****
|
||
|
@func DWORD | DmAbortLocalUpdate| DmAbortLocalUpdate aborts all the changes
|
||
|
to the local registry associated with this transaction.
|
||
|
|
||
|
@parm IN HXSACTION | hXsaction | The handle to the transaction to be committed.
|
||
|
|
||
|
@rdesc Returns a result code. ERROR_SUCCESS on success.
|
||
|
|
||
|
@xref <f DmBeginLocalUpdate> <f DmCommitLocalUpdate>
|
||
|
****/
|
||
|
DWORD DmAbortLocalUpdate(IN HLOCALXSACTION hLocalXsaction)
|
||
|
{
|
||
|
DWORD dwError=ERROR_SUCCESS;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmAbortLocalUpdate Entry\r\n");
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
//write the abort chkpoint record
|
||
|
//if the locker node is logging this is valid,
|
||
|
//if the nonlocker node is logging, and it aborts
|
||
|
// some other node will inherit the quorum log and
|
||
|
//checkpoint and hence commit this update.
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
CL_ASSERT(pLocalXsaction->hLogXsaction);
|
||
|
LogAbortXsaction(ghQuoLog, pLocalXsaction->hLogXsaction, 0);
|
||
|
//
|
||
|
// Chittur Subbaraman (chitturs) - 1/19/99
|
||
|
//
|
||
|
pLocalXsaction->hLogXsaction = NULL;
|
||
|
}
|
||
|
|
||
|
//SS: if the rollback fails, then we kill ourselves??
|
||
|
//restore the old registry
|
||
|
if ((dwError = DmRollbackRegistry()) != ERROR_SUCCESS)
|
||
|
{
|
||
|
CL_UNEXPECTED_ERROR(dwError);
|
||
|
}
|
||
|
|
||
|
//free any pending notifications that were built up for
|
||
|
//this transaction
|
||
|
DmpReportPendingNotifications(pLocalXsaction, FALSE );
|
||
|
|
||
|
//
|
||
|
// Chittur Subbaraman (chitturs) - 1/19/99
|
||
|
//
|
||
|
// Make sure that the hLogXsaction memory is freed (even in the case
|
||
|
// in which you started a local xsaction and didn't get a chance to
|
||
|
// commit it or abort it to the log because quorum logging got turned
|
||
|
// off in the middle, for example. This turning off of the logging
|
||
|
// in the middle of a transaction could be considered as a bug ?)
|
||
|
//
|
||
|
LocalFree( pLocalXsaction->hLogXsaction );
|
||
|
|
||
|
//free the transaction structure, it cannot be used any more
|
||
|
pLocalXsaction->dwSig = 0;
|
||
|
LocalFree(pLocalXsaction);
|
||
|
|
||
|
//release the database
|
||
|
RELEASE_LOCK(gLockDmpRoot);
|
||
|
|
||
|
ClRtlLogPrint(LOG_NOISE,
|
||
|
"[DM] DmAbortLocalUpdate Exit, returning 0x%1!08lx!\r\n",
|
||
|
dwError);
|
||
|
|
||
|
return(dwError);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
DmLocalSetValue(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpValueName,
|
||
|
IN DWORD dwType,
|
||
|
IN CONST BYTE *lpData,
|
||
|
IN DWORD cbData
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine sets the named value for the specified
|
||
|
cluster registry key on the local machine
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hKey - Supplies the cluster registry subkey whose value is to be set
|
||
|
|
||
|
lpValueName - Supplies the name of the value to be set.
|
||
|
|
||
|
dwType - Supplies the value data type
|
||
|
|
||
|
lpData - Supplies a pointer to the value data
|
||
|
|
||
|
cbData - Supplies the length of the value data.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
ERROR_SUCCESS if successful
|
||
|
|
||
|
Win32 error code otherwise
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
|
||
|
DWORD Status = ERROR_SUCCESS;
|
||
|
PDMKEY Key;
|
||
|
PUCHAR Dest;
|
||
|
DWORD NameLength;
|
||
|
DWORD ValueNameLength;
|
||
|
DWORD UpdateLength;
|
||
|
PDM_SET_VALUE_UPDATE Update = NULL;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
Key = (PDMKEY)hKey;
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
Status = RegSetValueExW(Key->hKey,
|
||
|
lpValueName,
|
||
|
0,
|
||
|
dwType,
|
||
|
lpData,
|
||
|
cbData);
|
||
|
|
||
|
if (Status != ERROR_SUCCESS)
|
||
|
{
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
DmpAddToPendingNotifications(pLocalXsaction, Key->Name, CLUSTER_CHANGE_REGISTRY_VALUE);
|
||
|
|
||
|
//write it to the quorum log
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
Key = (PDMKEY)hKey;
|
||
|
NameLength = (lstrlenW(Key->Name)+1)*sizeof(WCHAR);
|
||
|
ValueNameLength = (lstrlenW(lpValueName)+1)*sizeof(WCHAR);
|
||
|
UpdateLength = sizeof(DM_SET_VALUE_UPDATE) +
|
||
|
NameLength +
|
||
|
ValueNameLength +
|
||
|
cbData;
|
||
|
|
||
|
|
||
|
Update = (PDM_SET_VALUE_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);
|
||
|
if (Update == NULL)
|
||
|
{
|
||
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
|
||
|
Update->lpStatus = NULL;
|
||
|
Update->NameOffset = FIELD_OFFSET(DM_SET_VALUE_UPDATE, KeyName)+NameLength;
|
||
|
Update->DataOffset = Update->NameOffset + ValueNameLength;
|
||
|
Update->DataLength = cbData;
|
||
|
Update->Type = dwType;
|
||
|
CopyMemory(Update->KeyName, Key->Name, NameLength);
|
||
|
|
||
|
Dest = (PUCHAR)Update + Update->NameOffset;
|
||
|
CopyMemory(Dest, lpValueName, ValueNameLength);
|
||
|
|
||
|
Dest = (PUCHAR)Update + Update->DataOffset;
|
||
|
CopyMemory(Dest, lpData, cbData);
|
||
|
|
||
|
|
||
|
if (LogWriteXsaction(ghQuoLog, pLocalXsaction->hLogXsaction,
|
||
|
DmUpdateSetValue, Update, UpdateLength) == NULL_LSN)
|
||
|
{
|
||
|
Status = GetLastError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FnExit:
|
||
|
if (Update) LocalFree(Update);
|
||
|
return(Status);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
HDMKEY
|
||
|
DmLocalCreateKey(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpSubKey,
|
||
|
IN DWORD dwOptions,
|
||
|
IN DWORD samDesired,
|
||
|
IN OPTIONAL LPVOID lpSecurityDescriptor,
|
||
|
OUT LPDWORD lpDisposition
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Creates a key in the local registry. If the key exists, it
|
||
|
is opened. If it does not exist, it is created.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hKey - Supplies the key that the create is relative to.
|
||
|
|
||
|
lpSubKey - Supplies the key name relative to hKey
|
||
|
|
||
|
dwOptions - Supplies any registry option flags.
|
||
|
|
||
|
samDesired - Supplies desired security access mask
|
||
|
|
||
|
lpSecurityDescriptor - Supplies security for the newly created key.
|
||
|
|
||
|
Disposition - Returns whether the key was opened (REG_OPENED_EXISTING_KEY)
|
||
|
or created (REG_CREATED_NEW_KEY)
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
A handle to the specified key if successful
|
||
|
|
||
|
NULL otherwise. LastError will be set to the specific error code.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
PDMKEY Parent;
|
||
|
PDMKEY Key = NULL;
|
||
|
DWORD NameLength;
|
||
|
DWORD Status;
|
||
|
PDM_CREATE_KEY_UPDATE CreateUpdate = NULL;
|
||
|
PVOID pBuffer = NULL;
|
||
|
DWORD dwBufLength;
|
||
|
DWORD dwSecurityLength;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
if (dwOptions == REG_OPTION_VOLATILE)
|
||
|
{
|
||
|
Status = ERROR_INVALID_PARAMETER;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
Parent = (PDMKEY)hKey;
|
||
|
|
||
|
//
|
||
|
// Allocate the DMKEY structure.
|
||
|
//
|
||
|
NameLength = (lstrlenW(Parent->Name) + 1 + lstrlenW(lpSubKey) + 1)*sizeof(WCHAR);
|
||
|
Key = LocalAlloc(LMEM_FIXED, sizeof(DMKEY)+NameLength);
|
||
|
if (Key == NULL) {
|
||
|
CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Create the key on the local machine.
|
||
|
//
|
||
|
Status = RegCreateKeyExW(Parent->hKey,
|
||
|
lpSubKey,
|
||
|
0,
|
||
|
NULL,
|
||
|
0,
|
||
|
samDesired,
|
||
|
lpSecurityDescriptor,
|
||
|
&Key->hKey,
|
||
|
lpDisposition);
|
||
|
if (Status != ERROR_SUCCESS) {
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Create the key name
|
||
|
//
|
||
|
lstrcpyW(Key->Name, Parent->Name);
|
||
|
if (Key->Name[0] != UNICODE_NULL) {
|
||
|
lstrcatW(Key->Name, L"\\");
|
||
|
}
|
||
|
lstrcatW(Key->Name, lpSubKey);
|
||
|
Key->GrantedAccess = samDesired;
|
||
|
|
||
|
EnterCriticalSection(&KeyLock);
|
||
|
InsertHeadList(&KeyList, &Key->ListEntry);
|
||
|
InitializeListHead(&Key->NotifyList);
|
||
|
LeaveCriticalSection(&KeyLock);
|
||
|
|
||
|
//add the pending notification to be delivered on commit
|
||
|
DmpAddToPendingNotifications(pLocalXsaction, Key->Name, CLUSTER_CHANGE_REGISTRY_NAME);
|
||
|
|
||
|
//successfully created key, write to the log
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
|
||
|
//get the length of the security structure
|
||
|
if (ARGUMENT_PRESENT(lpSecurityDescriptor))
|
||
|
{
|
||
|
dwSecurityLength = GetSecurityDescriptorLength(lpSecurityDescriptor);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dwSecurityLength = 0;
|
||
|
}
|
||
|
|
||
|
CreateUpdate = (PDM_CREATE_KEY_UPDATE)LocalAlloc(LMEM_FIXED, sizeof(DM_CREATE_KEY_UPDATE));
|
||
|
if (CreateUpdate == NULL) {
|
||
|
CL_UNEXPECTED_ERROR(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Issue the update.
|
||
|
//
|
||
|
CreateUpdate->lpDisposition = lpDisposition;
|
||
|
CreateUpdate->phKey = &Key->hKey;
|
||
|
CreateUpdate->samDesired = samDesired;
|
||
|
CreateUpdate->dwOptions = dwOptions;
|
||
|
|
||
|
if (ARGUMENT_PRESENT(lpSecurityDescriptor)) {
|
||
|
CreateUpdate->SecurityPresent = TRUE;
|
||
|
} else {
|
||
|
CreateUpdate->SecurityPresent = FALSE;
|
||
|
}
|
||
|
|
||
|
//marshall the data,
|
||
|
pBuffer = GumMarshallArgs(&dwBufLength,
|
||
|
3,
|
||
|
sizeof(DM_CREATE_KEY_UPDATE),
|
||
|
CreateUpdate,
|
||
|
(lstrlenW(Key->Name)+1)*sizeof(WCHAR),
|
||
|
Key->Name,
|
||
|
dwSecurityLength,
|
||
|
lpSecurityDescriptor);
|
||
|
if (pBuffer)
|
||
|
{
|
||
|
CL_ASSERT(pLocalXsaction->hLogXsaction);
|
||
|
//write it to the logger
|
||
|
if (LogWriteXsaction(ghQuoLog, pLocalXsaction->hLogXsaction,
|
||
|
DmUpdateCreateKey, pBuffer, dwBufLength) == NULL_LSN)
|
||
|
{
|
||
|
Status = GetLastError();
|
||
|
goto FnExit;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
FnExit:
|
||
|
if (Status != ERROR_SUCCESS)
|
||
|
{
|
||
|
if (Key) LocalFree(Key);
|
||
|
Key = NULL;
|
||
|
SetLastError(Status);
|
||
|
}
|
||
|
if (CreateUpdate) LocalFree(CreateUpdate);
|
||
|
if (pBuffer) LocalFree(pBuffer);
|
||
|
return((HDMKEY)Key);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
DmLocalRemoveFromMultiSz(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpValueName,
|
||
|
IN LPCWSTR lpString
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Removes a string from a REG_MULTI_SZ value.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hKey - Supplies the key where the value exists. This key must
|
||
|
have been opened with READ | KEY_SET_VALUE access
|
||
|
|
||
|
lpValueName - Supplies the name of the value.
|
||
|
|
||
|
lpString - Supplies the string to be removed from the REG_MULTI_SZ value
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
ERROR_SUCCESS if successful
|
||
|
|
||
|
Win32 error code otherwise
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
DWORD Status;
|
||
|
LPWSTR Buffer=NULL;
|
||
|
DWORD BufferSize;
|
||
|
DWORD DataSize;
|
||
|
LPWSTR Current;
|
||
|
DWORD CurrentLength;
|
||
|
DWORD i;
|
||
|
LPWSTR Next;
|
||
|
PCHAR Src, Dest;
|
||
|
DWORD NextLength;
|
||
|
DWORD MultiLength;
|
||
|
|
||
|
|
||
|
|
||
|
BufferSize = 0;
|
||
|
Status = DmQueryString(hKey,
|
||
|
lpValueName,
|
||
|
REG_MULTI_SZ,
|
||
|
&Buffer,
|
||
|
&BufferSize,
|
||
|
&DataSize);
|
||
|
if (Status != ERROR_SUCCESS) {
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
MultiLength = DataSize/sizeof(WCHAR);
|
||
|
Status = ClRtlMultiSzRemove(Buffer,
|
||
|
&MultiLength,
|
||
|
lpString);
|
||
|
if (Status == ERROR_SUCCESS) {
|
||
|
//
|
||
|
// Set the new value back.
|
||
|
//
|
||
|
Status = DmLocalSetValue(hLocalXsaction,
|
||
|
hKey,
|
||
|
lpValueName,
|
||
|
REG_MULTI_SZ,
|
||
|
(CONST BYTE *)Buffer,
|
||
|
MultiLength * sizeof(WCHAR));
|
||
|
|
||
|
} else if (Status == ERROR_FILE_NOT_FOUND) {
|
||
|
Status = ERROR_SUCCESS;
|
||
|
}
|
||
|
if (Buffer) LocalFree(Buffer);
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
DmLocalAppendToMultiSz(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpValueName,
|
||
|
IN LPCWSTR lpString
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Adds another string to a REG_MULTI_SZ value. If the value does
|
||
|
not exist, it will be created.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hLocalXsaction - A handle to a local transaction.
|
||
|
|
||
|
hKey - Supplies the key where the value exists. This key must
|
||
|
have been opened with KEY_READ | KEY_SET_VALUE access
|
||
|
|
||
|
lpValueName - Supplies the name of the value.
|
||
|
|
||
|
lpString - Supplies the string to be appended to the REG_MULTI_SZ value
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
ERROR_SUCCESS if successful
|
||
|
|
||
|
Win32 error code otherwise
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
DWORD ValueLength = 512;
|
||
|
DWORD ReturnedLength;
|
||
|
LPWSTR ValueData;
|
||
|
DWORD StringLength;
|
||
|
DWORD Status;
|
||
|
DWORD cbValueData;
|
||
|
PWSTR s;
|
||
|
DWORD Type;
|
||
|
|
||
|
StringLength = (lstrlenW(lpString)+1)*sizeof(WCHAR);
|
||
|
retry:
|
||
|
ValueData = LocalAlloc(LMEM_FIXED, ValueLength + StringLength);
|
||
|
if (ValueData == NULL) {
|
||
|
return(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
}
|
||
|
|
||
|
cbValueData = ValueLength;
|
||
|
Status = DmQueryValue(hKey,
|
||
|
lpValueName,
|
||
|
&Type,
|
||
|
(LPBYTE)ValueData,
|
||
|
&cbValueData);
|
||
|
if (Status == ERROR_MORE_DATA) {
|
||
|
//
|
||
|
// The existing value is too large for our buffer.
|
||
|
// Retry with a larger buffer.
|
||
|
//
|
||
|
ValueLength = cbValueData;
|
||
|
LocalFree(ValueData);
|
||
|
goto retry;
|
||
|
}
|
||
|
if (Status == ERROR_FILE_NOT_FOUND) {
|
||
|
//
|
||
|
// The value does not currently exist. Create the
|
||
|
// value with our data.
|
||
|
//
|
||
|
s = ValueData;
|
||
|
|
||
|
} else if (Status == ERROR_SUCCESS) {
|
||
|
//
|
||
|
// A value already exists. Append our string to the
|
||
|
// MULTI_SZ.
|
||
|
//
|
||
|
s = (PWSTR)((PCHAR)ValueData + cbValueData) - 1;
|
||
|
} else {
|
||
|
LocalFree(ValueData);
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
CopyMemory(s, lpString, StringLength);
|
||
|
s += (StringLength / sizeof(WCHAR));
|
||
|
*s++ = L'\0';
|
||
|
|
||
|
Status = DmLocalSetValue(
|
||
|
hLocalXsaction,
|
||
|
hKey,
|
||
|
lpValueName,
|
||
|
REG_MULTI_SZ,
|
||
|
(CONST BYTE *)ValueData,
|
||
|
(DWORD)((s-ValueData)*sizeof(WCHAR)));
|
||
|
LocalFree(ValueData);
|
||
|
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
DmLocalDeleteKey(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpSubKey
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Deletes the specified key from the local registry. A key that has subkeys cannot
|
||
|
be deleted.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hKey - Supplies a handle to a currently open key.
|
||
|
|
||
|
lpSubKey - Points to a null-terminated string specifying the
|
||
|
name of the key to delete. This parameter cannot be NULL,
|
||
|
and the specified key must not have subkeys.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
If the function succeeds, the return value is ERROR_SUCCESS.
|
||
|
|
||
|
If the function fails, the return value is an error value.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
PDMKEY Key;
|
||
|
DWORD NameLength;
|
||
|
DWORD UpdateLength;
|
||
|
PDM_DELETE_KEY_UPDATE Update=NULL;
|
||
|
DWORD Status;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
Key = (PDMKEY)hKey;
|
||
|
NameLength = (lstrlenW(Key->Name) + 1 + lstrlenW(lpSubKey) + 1)*sizeof(WCHAR);
|
||
|
UpdateLength = NameLength + sizeof(DM_DELETE_KEY_UPDATE);
|
||
|
|
||
|
Update = (PDM_DELETE_KEY_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);
|
||
|
|
||
|
if (Update == NULL)
|
||
|
{
|
||
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
|
||
|
//dont need an update on status thru marshalled data
|
||
|
Update->lpStatus = NULL;
|
||
|
CopyMemory(Update->Name, Key->Name, (lstrlenW(Key->Name) + 1) * sizeof(WCHAR));
|
||
|
if (Update->Name[0] != L'\0')
|
||
|
{
|
||
|
lstrcatW(Update->Name, L"\\");
|
||
|
}
|
||
|
lstrcatW(Update->Name, lpSubKey);
|
||
|
|
||
|
Status = RegDeleteKeyW(DmpRoot, Update->Name);
|
||
|
|
||
|
if (Status != ERROR_SUCCESS)
|
||
|
goto FnExit;
|
||
|
|
||
|
//add the pending notification to be delivered on commit
|
||
|
DmpAddToPendingNotifications(pLocalXsaction, Update->Name, CLUSTER_CHANGE_REGISTRY_NAME);
|
||
|
|
||
|
//successfully deleted key, write to the log
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
|
||
|
if (LogWriteXsaction(ghQuoLog, pLocalXsaction->hLogXsaction,
|
||
|
DmUpdateDeleteKey, Update, UpdateLength) == NULL_LSN)
|
||
|
{
|
||
|
Status = GetLastError();
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
FnExit:
|
||
|
if (Update) LocalFree(Update);
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
DmLocalDeleteTree(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpSubKey
|
||
|
)
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Deletes the specified registry subtree in the local registry.
|
||
|
All subkeys are deleted.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
hKey - Supplies a handle to a currently open key.
|
||
|
|
||
|
lpSubKey - Points to a null-terminated string specifying the
|
||
|
name of the key to delete. This parameter cannot be NULL.
|
||
|
Any subkeys of the specified key will also be deleted.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
If the function succeeds, the return value is ERROR_SUCCESS.
|
||
|
|
||
|
If the function fails, the return value is an error value.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
HDMKEY Subkey;
|
||
|
DWORD i;
|
||
|
DWORD Status;
|
||
|
LPWSTR KeyBuffer=NULL;
|
||
|
DWORD MaxKeyLen;
|
||
|
DWORD NeededSize;
|
||
|
|
||
|
Subkey = DmOpenKey(hKey,
|
||
|
lpSubKey,
|
||
|
MAXIMUM_ALLOWED);
|
||
|
if (Subkey == NULL) {
|
||
|
Status = GetLastError();
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the size of name buffer we will need.
|
||
|
//
|
||
|
Status = DmQueryInfoKey(Subkey,
|
||
|
NULL,
|
||
|
&MaxKeyLen,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL);
|
||
|
if (Status != ERROR_SUCCESS) {
|
||
|
CL_UNEXPECTED_ERROR( Status );
|
||
|
DmCloseKey(Subkey);
|
||
|
return(Status);
|
||
|
}
|
||
|
KeyBuffer = LocalAlloc(LMEM_FIXED, (MaxKeyLen+1)*sizeof(WCHAR));
|
||
|
if (KeyBuffer == NULL) {
|
||
|
CL_UNEXPECTED_ERROR( ERROR_NOT_ENOUGH_MEMORY );
|
||
|
DmCloseKey(Subkey);
|
||
|
return(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Enumerate the subkeys and apply ourselves recursively to each one.
|
||
|
//
|
||
|
i=0;
|
||
|
do {
|
||
|
NeededSize = MaxKeyLen+1;
|
||
|
Status = DmEnumKey(Subkey,
|
||
|
i,
|
||
|
KeyBuffer,
|
||
|
&NeededSize,
|
||
|
NULL);
|
||
|
if (Status == ERROR_SUCCESS) {
|
||
|
//
|
||
|
// Call ourselves recursively on this keyname.
|
||
|
//
|
||
|
DmLocalDeleteTree(hLocalXsaction, Subkey, KeyBuffer);
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// Some odd error, keep going with the next key.
|
||
|
//
|
||
|
++i;
|
||
|
}
|
||
|
|
||
|
} while ( Status != ERROR_NO_MORE_ITEMS );
|
||
|
|
||
|
DmCloseKey(Subkey);
|
||
|
|
||
|
Status = DmLocalDeleteKey(hLocalXsaction, hKey, lpSubKey);
|
||
|
|
||
|
if (KeyBuffer != NULL) {
|
||
|
LocalFree(KeyBuffer);
|
||
|
}
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
DmLocalDeleteValue(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpValueName
|
||
|
)
|
||
|
{
|
||
|
PDMKEY Key;
|
||
|
DWORD NameLength;
|
||
|
DWORD ValueNameLength;
|
||
|
DWORD UpdateLength;
|
||
|
PDM_DELETE_VALUE_UPDATE Update=NULL;
|
||
|
PUCHAR Dest;
|
||
|
DWORD Status;
|
||
|
HKEY hRegKey;
|
||
|
PLOCALXSACTION pLocalXsaction;
|
||
|
|
||
|
GETLOCALXSACTION(pLocalXsaction, hLocalXsaction);
|
||
|
|
||
|
Key = (PDMKEY)hKey;
|
||
|
|
||
|
Status = RegOpenKeyExW(DmpRoot,
|
||
|
Key->Name,
|
||
|
0,
|
||
|
KEY_SET_VALUE,
|
||
|
&hRegKey);
|
||
|
if (Status != ERROR_SUCCESS) {
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
|
||
|
Status = RegDeleteValueW(hRegKey, lpValueName);
|
||
|
RegCloseKey(hRegKey);
|
||
|
|
||
|
if (Status!=ERROR_SUCCESS)
|
||
|
goto FnExit;
|
||
|
|
||
|
//add the pending notification to be delivered on commit
|
||
|
DmpAddToPendingNotifications(pLocalXsaction, Key->Name, CLUSTER_CHANGE_REGISTRY_VALUE);
|
||
|
|
||
|
//successfully created key, write to the log
|
||
|
if (gbIsQuoLoggingOn && gbIsQuoResOnline && AMIOWNEROFQUORES(gpQuoResource)
|
||
|
&& ghQuoLog && pLocalXsaction->hLogXsaction)
|
||
|
{
|
||
|
|
||
|
//if successful and this is the logging node, then log
|
||
|
// the transaction
|
||
|
NameLength = (lstrlenW(Key->Name)+1)*sizeof(WCHAR);
|
||
|
ValueNameLength = (lstrlenW(lpValueName)+1)*sizeof(WCHAR);
|
||
|
UpdateLength = sizeof(DM_DELETE_VALUE_UPDATE) +
|
||
|
NameLength +
|
||
|
ValueNameLength;
|
||
|
|
||
|
Update = (PDM_DELETE_VALUE_UPDATE)LocalAlloc(LMEM_FIXED, UpdateLength);
|
||
|
if (Update == NULL) {
|
||
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
|
||
|
Update->lpStatus = NULL;
|
||
|
Update->NameOffset = FIELD_OFFSET(DM_DELETE_VALUE_UPDATE, KeyName)+NameLength;
|
||
|
|
||
|
CopyMemory(Update->KeyName, Key->Name, NameLength);
|
||
|
|
||
|
Dest = (PUCHAR)Update + Update->NameOffset;
|
||
|
CopyMemory(Dest, lpValueName, ValueNameLength);
|
||
|
|
||
|
if (LogWriteXsaction(ghQuoLog, pLocalXsaction->hLogXsaction,
|
||
|
DmUpdateDeleteValue, Update, UpdateLength) == NULL_LSN)
|
||
|
{
|
||
|
Status = GetLastError();
|
||
|
goto FnExit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FnExit:
|
||
|
if (Update) LocalFree(Update);
|
||
|
return(Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****
|
||
|
@func VOID | DmpReportPendingNotifications| This is called
|
||
|
on commit or abort of a local transactions. On a commit,
|
||
|
notifications related to changes within a transaction
|
||
|
are delivered.
|
||
|
|
||
|
@parm IN PLOCALXSACTION | pLocalXsaction| A pointer to the local
|
||
|
transation context.
|
||
|
|
||
|
@parm IN BOOL | bCommit| Set to TRUE when the transaction is
|
||
|
commited.
|
||
|
|
||
|
@comm The pending notification structure associated with the
|
||
|
transaction is cleaned up.
|
||
|
|
||
|
@xref <f DmAbortLocalUpdate> <f DmCommitLocalUpdate>
|
||
|
****/
|
||
|
VOID
|
||
|
DmpReportPendingNotifications(
|
||
|
IN PLOCALXSACTION pLocalXsaction,
|
||
|
IN BOOL bCommit
|
||
|
)
|
||
|
{
|
||
|
PLIST_ENTRY pListEntry;
|
||
|
PDM_PENDING_NOTIFY pDmPendingNotify;
|
||
|
|
||
|
pListEntry = pLocalXsaction->PendingNotifyListHead.Flink;
|
||
|
|
||
|
//remove the entries and proces them one by one
|
||
|
//free them when done
|
||
|
while (pListEntry != &pLocalXsaction->PendingNotifyListHead)
|
||
|
{
|
||
|
pDmPendingNotify = CONTAINING_RECORD(pListEntry, DM_PENDING_NOTIFY, ListEntry);
|
||
|
// if transaction is commited
|
||
|
if (bCommit)
|
||
|
DmpReportNotify(pDmPendingNotify->pszKeyName, pDmPendingNotify->dwFilter);
|
||
|
pListEntry = pListEntry->Flink;
|
||
|
RemoveEntryList( &pDmPendingNotify->ListEntry );
|
||
|
LocalFree(pDmPendingNotify->pszKeyName);
|
||
|
LocalFree(pDmPendingNotify);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****
|
||
|
@func DWORD | DmpAddToPendingNotifications| This is called
|
||
|
by the DmLocal Api's to queue registry notifications
|
||
|
on success. The notifications are delivered or
|
||
|
thrown away depending on whether the transaction commits
|
||
|
or aborts.
|
||
|
|
||
|
@parm IN PLOCALXSACTION | pLocalXsaction| A pointer to the local
|
||
|
transation context.
|
||
|
|
||
|
@parm IN LPCWSTR | pszName| A pointer to the registry key name.
|
||
|
|
||
|
@parm IN DWORD | dwFilter | The filters associated with the notification.
|
||
|
|
||
|
@comm A new pending notification structure is created and associated
|
||
|
with the transaction.
|
||
|
|
||
|
@xref <f DmAbortLocalUpdate> <f DmCommitLocalUpdate>
|
||
|
****/
|
||
|
DWORD
|
||
|
DmpAddToPendingNotifications(
|
||
|
IN PLOCALXSACTION pLocalXsaction,
|
||
|
IN LPCWSTR pszName,
|
||
|
IN DWORD dwFilter
|
||
|
)
|
||
|
{
|
||
|
DWORD dwError = ERROR_SUCCESS;
|
||
|
PDM_PENDING_NOTIFY pDmPendingNotify;
|
||
|
|
||
|
pDmPendingNotify = LocalAlloc(LPTR, sizeof(DM_PENDING_NOTIFY));
|
||
|
if (!pDmPendingNotify)
|
||
|
{
|
||
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
CL_LOGFAILURE(dwError);
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
pDmPendingNotify->pszKeyName = LocalAlloc(LMEM_FIXED,
|
||
|
((lstrlenW(pszName) + 1 ) * sizeof(WCHAR)));
|
||
|
if (!pDmPendingNotify->pszKeyName)
|
||
|
{
|
||
|
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
CL_LOGFAILURE(dwError);
|
||
|
goto FnExit;
|
||
|
}
|
||
|
|
||
|
//initialize the structure
|
||
|
lstrcpyW(pDmPendingNotify->pszKeyName, pszName);
|
||
|
pDmPendingNotify->dwFilter = dwFilter;
|
||
|
InitializeListHead(&pDmPendingNotify->ListEntry);
|
||
|
|
||
|
//add to the list
|
||
|
InsertTailList(&pLocalXsaction->PendingNotifyListHead,
|
||
|
&pDmPendingNotify->ListEntry);
|
||
|
|
||
|
|
||
|
FnExit:
|
||
|
return(dwError);
|
||
|
}
|
||
|
|
||
|
/****
|
||
|
@func DWORD | DmAmITheOwnerOfTheQuorumResource| This harmless
|
||
|
function is used by regroup module to determine whether
|
||
|
a node thinks that it is the owner of the quorum resource or not.
|
||
|
****/
|
||
|
DWORD DmAmITheOwnerOfTheQuorumResource() {
|
||
|
return gpQuoResource
|
||
|
&& gpQuoResource->Group
|
||
|
&& gbIsQuoResOnline
|
||
|
&& AMIOWNEROFQUORES(gpQuoResource);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
DmRtlLocalCreateKey(
|
||
|
IN HLOCALXSACTION hLocalXsaction,
|
||
|
IN HDMKEY hKey,
|
||
|
IN LPCWSTR lpSubKey,
|
||
|
IN DWORD dwOptions,
|
||
|
IN DWORD samDesired,
|
||
|
IN OPTIONAL LPVOID lpSecurityDescriptor,
|
||
|
OUT HDMKEY * phkResult,
|
||
|
OUT LPDWORD lpDisposition
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Wrapper function for DmLocalCreateKey to be used with CLRtl* functions.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
DWORD status;
|
||
|
|
||
|
*phkResult= DmLocalCreateKey(
|
||
|
hLocalXsaction,
|
||
|
hKey,
|
||
|
lpSubKey,
|
||
|
dwOptions,
|
||
|
samDesired,
|
||
|
lpSecurityDescriptor,
|
||
|
lpDisposition
|
||
|
);
|
||
|
if(* phkResult == NULL)
|
||
|
status=GetLastError();
|
||
|
else
|
||
|
status=ERROR_SUCCESS;
|
||
|
return status;
|
||
|
}
|