mirror of https://github.com/tongzx/nt5src
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.
2327 lines
55 KiB
2327 lines
55 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
groupman.cxx
|
|
|
|
Abstract:
|
|
|
|
Contains code for the Group List Database manager. This includes
|
|
all the linked list routines. This file contains the following
|
|
functions:
|
|
ScGetOrderGroupList
|
|
ScGetStandaloneGroupList
|
|
ScGetUnresolvedDependList
|
|
ScGetNamedGroupRecord
|
|
|
|
ScCreateOrderGroupEntry
|
|
ScAllocateGroupEntry
|
|
ScCreateGroupMembership
|
|
ScDeleteGroupMembership
|
|
ScCreateRegistryGroupPointer
|
|
ScDeleteRegistryGroupPointer
|
|
ScCreateStandaloneGroup
|
|
ScDeleteStandaloneGroup
|
|
|
|
ScGenerateDependencies
|
|
ScSetDependencyPointers
|
|
ScResolveDependencyToService
|
|
ScCreateDependencies
|
|
ScCreateUnresolvedDepend
|
|
ScDeleteUnresolvedDepend
|
|
ScCreateDependRecord
|
|
ScDeleteStartDependencies
|
|
ScDeleteStopDependencies
|
|
ScSetServiceDependList
|
|
ScGetUniqueTag
|
|
ScCompareVector
|
|
|
|
ScGetDependencySize
|
|
ScGetDependencyString
|
|
|
|
ScDumpGroups
|
|
ScDumpServiceDependencies
|
|
|
|
Author:
|
|
|
|
Dan Lafferty (danl) 04-Feb-1992
|
|
|
|
Environment:
|
|
|
|
User Mode -Win32
|
|
|
|
Revision History:
|
|
|
|
22-Oct-1993 danl
|
|
Created by splitting these functions out of dataman.c because it was
|
|
getting too large.
|
|
|
|
--*/
|
|
|
|
//
|
|
// INCLUDES
|
|
//
|
|
|
|
#include "precomp.hxx"
|
|
#include <stdlib.h> // wide character c runtimes.
|
|
#include <tstr.h> // Unicode string macros
|
|
#include <ntrpcp.h> // MIDL_user_allocate
|
|
#include <control.h> // SendControl
|
|
#include "scconfig.h" // ScGenerateServiceDB,ScInitSecurityProcess
|
|
#include "scsec.h" // ScCreateScServiceObject
|
|
#include "account.h" // ScRemoveAccount
|
|
#include <sclib.h> // ScImagePathsMatch().
|
|
#include "bootcfg.h" // ScDeleteRegTree().
|
|
#include <strarray.h> // ScWStrArraySize
|
|
|
|
//
|
|
// Defines
|
|
//
|
|
|
|
// Names of specially treated groups
|
|
#define SC_GROUPNAME_TDI L"TDI"
|
|
#define SC_GROUPNAME_PNP_TDI L"PNP_TDI"
|
|
|
|
// A value that will not match any real pointer to a load order group
|
|
#define SC_INVALID_GROUP ((LPLOAD_ORDER_GROUP)(DWORD_PTR) 0xFFFFFFFF)
|
|
|
|
|
|
//
|
|
// External Globals
|
|
//
|
|
|
|
//
|
|
// TDI GROUP SPECIAL: The groups named TDI and PNP_TDI are treated
|
|
// specially during dependency handling. This is done by remembering a
|
|
// pointer to each of those groups, if it occurs in the group order list,
|
|
// and checking against the remembered pointers during dependency
|
|
// handling.
|
|
//
|
|
|
|
LPLOAD_ORDER_GROUP ScGlobalTDIGroup = SC_INVALID_GROUP;
|
|
LPLOAD_ORDER_GROUP ScGlobalPNP_TDIGroup = SC_INVALID_GROUP;
|
|
|
|
//
|
|
// Static Globals
|
|
//
|
|
|
|
//
|
|
// These are the linked list heads for each of the databases
|
|
// that are maintained.
|
|
//
|
|
|
|
LOAD_ORDER_GROUP OrderGroupList; // empty header for doubly linked
|
|
LOAD_ORDER_GROUP StandaloneGroupList; // empty header for doubly linked
|
|
|
|
UNRESOLVED_DEPEND UnresolvedDependList; // empty header for doubly linked
|
|
|
|
//
|
|
// Local Function Prototypes
|
|
//
|
|
|
|
DWORD
|
|
ScAllocateOrderGroupEntry(
|
|
OUT LPLOAD_ORDER_GROUP *NewGroup,
|
|
IN LPWSTR GroupName
|
|
);
|
|
|
|
DWORD
|
|
ScCreateStandaloneGroup(
|
|
IN LPWSTR GroupName,
|
|
OUT LPLOAD_ORDER_GROUP *GroupPointer
|
|
);
|
|
|
|
VOID
|
|
ScDeleteStandaloneGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
);
|
|
|
|
VOID
|
|
ScRememberSpecialGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
);
|
|
|
|
VOID
|
|
ScForgetSpecialGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
);
|
|
|
|
DWORD
|
|
ScCreateUnresolvedDepend(
|
|
IN LPWSTR Name,
|
|
OUT LPUNRESOLVED_DEPEND *Unresolved
|
|
);
|
|
|
|
VOID
|
|
ScDeleteUnresolvedDepend(
|
|
IN OUT LPUNRESOLVED_DEPEND *Unresolved
|
|
);
|
|
|
|
DWORD
|
|
ScSetServiceDependList(
|
|
LPDEPEND_RECORD Start,
|
|
LPSERVICE_RECORD ServiceRecord,
|
|
PVOID DependOnRecord,
|
|
DEPEND_TYPE DependOnType
|
|
);
|
|
|
|
VOID
|
|
ScCompareVector(
|
|
IN LPDWORD TagArray,
|
|
IN DWORD TagArrayLength,
|
|
IN OUT LPDWORD ReturnTagPtr
|
|
);
|
|
|
|
|
|
|
|
//****************************************************************************/
|
|
// Miscellaneous Short Functions
|
|
//****************************************************************************/
|
|
LPLOAD_ORDER_GROUP
|
|
ScGetOrderGroupList(
|
|
VOID
|
|
)
|
|
{
|
|
SC_ASSERT(ScGroupListLock.Have());
|
|
return OrderGroupList.Next;
|
|
}
|
|
|
|
LPLOAD_ORDER_GROUP
|
|
ScGetStandaloneGroupList(
|
|
VOID
|
|
)
|
|
{
|
|
SC_ASSERT(ScGroupListLock.Have());
|
|
return StandaloneGroupList.Next;
|
|
}
|
|
|
|
LPUNRESOLVED_DEPEND
|
|
ScGetUnresolvedDependList(
|
|
VOID
|
|
)
|
|
{
|
|
SC_ASSERT(ScGroupListLock.Have());
|
|
return UnresolvedDependList.Next;
|
|
}
|
|
|
|
VOID
|
|
ScInitGroupDatabase(VOID)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
OrderGroupList.Next = NULL;
|
|
OrderGroupList.Prev = NULL;
|
|
|
|
StandaloneGroupList.Next = NULL;
|
|
StandaloneGroupList.Prev = NULL;
|
|
|
|
UnresolvedDependList.Next = NULL;
|
|
UnresolvedDependList.Prev = NULL;
|
|
|
|
}
|
|
|
|
VOID
|
|
ScEndGroupDatabase(VOID)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
LPLOAD_ORDER_GROUP Group;
|
|
LPLOAD_ORDER_GROUP Grp;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
Group = OrderGroupList.Next;
|
|
|
|
while (Group != NULL) {
|
|
|
|
Grp = Group;
|
|
Group = Group->Next;
|
|
|
|
REMOVE_FROM_LIST(Grp);
|
|
LocalFree(Grp);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateOrderGroupEntry(
|
|
IN LPWSTR GroupName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function adds a group entry into the end of the load order group
|
|
list.
|
|
|
|
Arguments:
|
|
|
|
GroupName - Supplies the name of the load group.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a new
|
|
group entry failed.
|
|
|
|
Note:
|
|
|
|
The GroupListLock must be held exclusively prior to calling this function.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
LPLOAD_ORDER_GROUP NewGroup;
|
|
LPLOAD_ORDER_GROUP GroupListPointer;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
if ((status = ScAllocateOrderGroupEntry(
|
|
&NewGroup,
|
|
GroupName
|
|
)) != NO_ERROR) {
|
|
return status;
|
|
}
|
|
|
|
GroupListPointer = &OrderGroupList;
|
|
|
|
//
|
|
// Add the group entry to the group list at the end.
|
|
//
|
|
ADD_TO_LIST(GroupListPointer, NewGroup);
|
|
|
|
SC_LOG(CONFIG, "ScCreateOrderGroupEntry: Added %ws to GroupList\n", GroupName);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScAllocateOrderGroupEntry(
|
|
OUT LPLOAD_ORDER_GROUP *NewGroup,
|
|
IN LPWSTR GroupName
|
|
)
|
|
{
|
|
|
|
//
|
|
// Allocate memory for the new group.
|
|
//
|
|
*NewGroup = (LPLOAD_ORDER_GROUP)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
WCSSIZE(GroupName) + sizeof(LOAD_ORDER_GROUP)
|
|
);
|
|
|
|
if (*NewGroup == NULL) {
|
|
SC_LOG(ERROR,"ScAllocateOrderGroupEntry: LocalAlloc failure %ld\n",
|
|
GetLastError());
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Save away the GroupName
|
|
//
|
|
(*NewGroup)->GroupName = (LPWSTR) ((LPBYTE) (*NewGroup) + sizeof(LOAD_ORDER_GROUP));
|
|
wcscpy((*NewGroup)->GroupName, GroupName);
|
|
|
|
ScRememberSpecialGroup(*NewGroup);
|
|
|
|
//
|
|
// Set the RefCount field to 0xffffffff so that we can differentiate an
|
|
// order group from a standalone group. This field actually indicates
|
|
// the number of members in a group and dependency references to it if
|
|
// the group is standalone so that we can delete the standalone group
|
|
// when it goes to 0.
|
|
//
|
|
(*NewGroup)->RefCount = MAXULONG;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateGroupMembership(
|
|
OUT PSERVICE_RECORD ServiceRecord,
|
|
IN LPWSTR Group OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function assigns the load order group membership information
|
|
of the service to its specified service record. If the service
|
|
belongs to a group in OrderGroupList, a pointer to the group in the load
|
|
order group list is saved. If the service belongs to a group which
|
|
is not in the load order group list, the name of the group is saved
|
|
in the service record in case the group gets added to the load order
|
|
group list later. If Group is not specified, no group membership
|
|
information is saved.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Receives the group membership information in this service
|
|
record.
|
|
|
|
Group - Supplies the string which contains the name of the group. This
|
|
is the raw string read from the registry which may contain blank,
|
|
tab or newline characters which we should ignore.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a group name
|
|
failed.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the database lock is already held. It is
|
|
called by ScAddConfigInfoServiceRecord.
|
|
|
|
It also assumes that the caller has exclusive access to the group
|
|
list lock.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
LPWSTR GroupPtr = Group;
|
|
LPWSTR GroupName;
|
|
PLOAD_ORDER_GROUP GroupEntry = ScGetOrderGroupList();
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
//
|
|
// Extract the group name from the string read in from the registry.
|
|
//
|
|
if ((! ARGUMENT_PRESENT(GroupPtr)) || (! ScGetToken(&GroupPtr, &GroupName))) {
|
|
ServiceRecord->MemberOfGroup = (PLOAD_ORDER_GROUP) NULL;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Search for matching group name in load order list
|
|
//
|
|
while (GroupEntry != NULL) {
|
|
if (_wcsicmp(GroupEntry->GroupName, GroupName) == 0) {
|
|
ServiceRecord->MemberOfGroup = GroupEntry;
|
|
return NO_ERROR;
|
|
}
|
|
GroupEntry = GroupEntry->Next;
|
|
}
|
|
|
|
//
|
|
// Group name not NULL, and not found in load order group list.
|
|
// Group is a standalone group.
|
|
//
|
|
status = ScCreateStandaloneGroup(
|
|
GroupName,
|
|
&(ServiceRecord->MemberOfGroup)
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
return status;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteGroupMembership(
|
|
IN OUT PSERVICE_RECORD ServiceRecord
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes any memory allocated for group membership
|
|
association.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Supplies the group membership information in this
|
|
service record.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the database lock is already held. It is
|
|
called by ScAddConfigInfoServiceRecord and ScDecrementUseCountAndDelete.
|
|
|
|
It also assumes that the group list lock is held exclusively.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
if (ServiceRecord->MemberOfGroup != NULL &&
|
|
ServiceRecord->MemberOfGroup->RefCount != MAXULONG) {
|
|
ScDeleteStandaloneGroup(ServiceRecord->MemberOfGroup);
|
|
}
|
|
|
|
ServiceRecord->MemberOfGroup = NULL;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateRegistryGroupPointer(
|
|
OUT PSERVICE_RECORD ServiceRecord,
|
|
IN LPWSTR Group OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function assigns the load order group RegistryGroup
|
|
information in the service record to match the load order group
|
|
stored in the registry, which is not the same as MemberOfGroup
|
|
information if the load order group of the service is changed
|
|
while the service is running. However, we need to know what the
|
|
resultant load order group of the service is when it stops so
|
|
that when we can guarantee uniqueness of a tag based on all
|
|
members the group.
|
|
|
|
This function does exactly the same thing as the
|
|
ScCreateGroupMembership function but alters the RegistryGroup
|
|
pointer instead of the MemberOfGroup pointer in the service
|
|
record.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Receives the group membership information in this service
|
|
record.
|
|
|
|
Group - Supplies the string which contains the name of the group. This
|
|
is the raw string read from the registry which may contain blank,
|
|
tab or newline characters which we should ignore.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a group name
|
|
failed.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the database lock is already held. It is
|
|
called by ScAddConfigInfoServiceRecord.
|
|
|
|
It also assumes that the caller has exclusive access to the group
|
|
list lock.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
LPWSTR GroupPtr = Group;
|
|
LPWSTR GroupName;
|
|
PLOAD_ORDER_GROUP GroupEntry = ScGetOrderGroupList();
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
|
|
//
|
|
// Extract the group name from the string read in from the registry.
|
|
//
|
|
if ((! ARGUMENT_PRESENT(GroupPtr)) || (! ScGetToken(&GroupPtr, &GroupName))) {
|
|
ServiceRecord->RegistryGroup = (PLOAD_ORDER_GROUP) NULL;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Search for matching group name in load order list
|
|
//
|
|
while (GroupEntry != NULL) {
|
|
if (_wcsicmp(GroupEntry->GroupName, GroupName) == 0) {
|
|
ServiceRecord->RegistryGroup = GroupEntry;
|
|
return NO_ERROR;
|
|
}
|
|
GroupEntry = GroupEntry->Next;
|
|
}
|
|
|
|
//
|
|
// Group name not NULL, and not found in load order group list.
|
|
// Group is a standalone group.
|
|
//
|
|
status = ScCreateStandaloneGroup(
|
|
GroupName,
|
|
&(ServiceRecord->RegistryGroup)
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
return status;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteRegistryGroupPointer(
|
|
IN OUT PSERVICE_RECORD ServiceRecord
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes any memory allocated for registry group
|
|
association.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Supplies the registry group information in this
|
|
service record.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the database lock is already held. It is
|
|
called by ScAddConfigInfoServiceRecord and ScDecrementUseCountAndDelete.
|
|
|
|
It also assumes that the group list lock is held exclusively.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
if (ServiceRecord->RegistryGroup != NULL &&
|
|
ServiceRecord->RegistryGroup->RefCount != MAXULONG) {
|
|
ScDeleteStandaloneGroup(ServiceRecord->RegistryGroup);
|
|
}
|
|
|
|
ServiceRecord->RegistryGroup = NULL;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateStandaloneGroup(
|
|
IN LPWSTR GroupName,
|
|
OUT LPLOAD_ORDER_GROUP *GroupPointer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks for a matching standalone group entry in the
|
|
standalone group list. If a match is found, the reference count is
|
|
incremented and the pointer to the matching entry is returned.
|
|
|
|
If no matching entry is found, this function creates a new standalone
|
|
group entry, insert it into the end of the standalone group list, and
|
|
return a pointer to the new entry.
|
|
|
|
|
|
Arguments:
|
|
|
|
Name - Supplies the name of the group which is not in the
|
|
ServiceOrderList.
|
|
|
|
GroupPointer - Receives a pointer to the unresolved entry.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - Allocation of memory failed.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
group list lock. It is called by ScCreateGroupMembership.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
LPLOAD_ORDER_GROUP Group = ScGetStandaloneGroupList();
|
|
|
|
BOOL Found = FALSE;
|
|
|
|
|
|
//
|
|
// Search the existing standalone group list for the matching
|
|
// standalone group entry.
|
|
//
|
|
while (Group != NULL) {
|
|
|
|
if (_wcsicmp(Group->GroupName, GroupName) == 0) {
|
|
Found = TRUE;
|
|
break;
|
|
}
|
|
|
|
Group = Group->Next;
|
|
}
|
|
|
|
if (Found) {
|
|
|
|
Group->RefCount++;
|
|
|
|
SC_LOG2(DEPEND_DUMP,
|
|
"Found existing group entry for " FORMAT_LPWSTR
|
|
", just increment refcount to %lu\n", Group->GroupName,
|
|
Group->RefCount);
|
|
|
|
*GroupPointer = Group;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Not found. Allocate a new group entry.
|
|
//
|
|
if ((*GroupPointer = (LPLOAD_ORDER_GROUP)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
sizeof(LOAD_ORDER_GROUP) + WCSSIZE(GroupName)
|
|
)) == NULL) {
|
|
SC_LOG(ERROR,"ScCreateStandaloneGroup: LocalAlloc failure %lu\n",
|
|
GetLastError());
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
(*GroupPointer)->GroupName = (LPWSTR) ((DWORD_PTR) *GroupPointer +
|
|
sizeof(LOAD_ORDER_GROUP));
|
|
wcscpy((*GroupPointer)->GroupName, GroupName);
|
|
|
|
(*GroupPointer)->RefCount = 1;
|
|
|
|
SC_LOG1(DEPEND_DUMP, "Created new standalone group entry "
|
|
FORMAT_LPWSTR "\n", (*GroupPointer)->GroupName);
|
|
|
|
ScRememberSpecialGroup(*GroupPointer);
|
|
|
|
Group = &StandaloneGroupList;
|
|
|
|
//
|
|
// Add the new group entry to the standalone group list at the end.
|
|
//
|
|
ADD_TO_LIST(Group, *GroupPointer);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteStandaloneGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
)
|
|
{
|
|
if (Group->RefCount) {
|
|
Group->RefCount--;
|
|
|
|
SC_LOG1(DEPEND, "DeleteStandaloneGroup: Subtracted RefCount is "
|
|
FORMAT_DWORD "\n", Group->RefCount);
|
|
}
|
|
else {
|
|
SC_LOG0(ERROR, "ScDeleteStandaloneGroup: Before delete, refcount is 0!\n");
|
|
SC_ASSERT(FALSE);
|
|
}
|
|
|
|
if (Group->RefCount == 0) {
|
|
|
|
SC_LOG1(DEPEND, "Deleting standalone group " FORMAT_LPWSTR "\n",
|
|
Group->GroupName);
|
|
|
|
REMOVE_FROM_LIST(Group);
|
|
|
|
ScForgetSpecialGroup(Group);
|
|
|
|
LocalFree(Group);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ScRememberSpecialGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compares the group name against a set of known group names to see if it
|
|
is a group that requires special handling, and if so, saves the pointer
|
|
to the group in a global variable.
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
// CODEWORK: If the number of special groups keeps growing, do this
|
|
// in a table-driven way!
|
|
|
|
if (_wcsicmp(Group->GroupName, SC_GROUPNAME_TDI) == 0)
|
|
{
|
|
if (ScGlobalTDIGroup != SC_INVALID_GROUP)
|
|
{
|
|
SC_LOG0(ERROR, "Warning: TDI group occurs more than once in load order group list\n");
|
|
}
|
|
ScGlobalTDIGroup = Group;
|
|
}
|
|
else if (_wcsicmp(Group->GroupName, SC_GROUPNAME_PNP_TDI) == 0)
|
|
{
|
|
if (ScGlobalPNP_TDIGroup != SC_INVALID_GROUP)
|
|
{
|
|
SC_LOG0(ERROR, "Warning: PNP_TDI group occurs more than once in load order group list\n");
|
|
}
|
|
ScGlobalPNP_TDIGroup = Group;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ScForgetSpecialGroup(
|
|
IN LPLOAD_ORDER_GROUP Group
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
if (Group == ScGlobalTDIGroup)
|
|
{
|
|
ScGlobalTDIGroup = SC_INVALID_GROUP;
|
|
}
|
|
else if (Group == ScGlobalPNP_TDIGroup)
|
|
{
|
|
ScGlobalPNP_TDIGroup = SC_INVALID_GROUP;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ScGenerateDependencies(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
The GroupListLock must be held exclusively prior to calling this routine.
|
|
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
FOR_ALL_SERVICES(Service)
|
|
{
|
|
(void) ScSetDependencyPointers(Service);
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScSetDependencyPointers(
|
|
IN LPSERVICE_RECORD Service
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
Note:
|
|
|
|
The GroupListLock must be held exclusively prior to calling this routine.
|
|
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
|
|
DWORD status;
|
|
|
|
if (Service->Dependencies != NULL) {
|
|
|
|
status = ScCreateDependencies(
|
|
Service,
|
|
Service->Dependencies
|
|
);
|
|
|
|
if (status == NO_ERROR) {
|
|
LocalFree(Service->Dependencies);
|
|
Service->Dependencies = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScResolveDependencyToService(
|
|
LPSERVICE_RECORD DependOnService
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function resolves all dependencies to the service we are
|
|
currently installing via CreateService. The start depend entry
|
|
of these services will point to the service record of the service
|
|
we are installing instead of the unresolved depend record. A
|
|
stop depend entry is created for the service we are installing to
|
|
point back to every service that depends on it.
|
|
|
|
Arguments:
|
|
|
|
DependOnService - Supplies a pointer to the service we are installing
|
|
via CreateService which other services may depend on.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - Fail to allocate memory for required data
|
|
structures.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by RCreateServiceW.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.Have());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
DWORD status;
|
|
LPUNRESOLVED_DEPEND UnresolvedEntry = ScGetUnresolvedDependList();
|
|
|
|
|
|
//
|
|
// Search the unresolved depend list for a matching entry
|
|
//
|
|
while (UnresolvedEntry != NULL) {
|
|
|
|
if (_wcsicmp(UnresolvedEntry->Name, DependOnService->ServiceName) == 0) {
|
|
SC_LOG1(DEPEND, "Found unresolved entry for " FORMAT_LPWSTR "\n",
|
|
DependOnService->ServiceName);
|
|
break;
|
|
}
|
|
|
|
UnresolvedEntry = UnresolvedEntry->Next;
|
|
}
|
|
|
|
if (UnresolvedEntry == NULL) {
|
|
//
|
|
// There are no service which depends on the service we are
|
|
// installing; hence, no unresolved dependency to resolve.
|
|
//
|
|
SC_LOG1(DEPEND, "No service depends on " FORMAT_LPWSTR "\n",
|
|
DependOnService->ServiceName);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
//
|
|
// Loop through all services to see if any of them has a start depend
|
|
// entry that points to UnresolvedEntry.
|
|
//
|
|
FOR_ALL_SERVICES(Service)
|
|
{
|
|
if (UnresolvedEntry == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (LPDEPEND_RECORD Start = Service->StartDepend;
|
|
Start != NULL;
|
|
Start = Start->Next)
|
|
{
|
|
if (Start->DependUnresolved == UnresolvedEntry)
|
|
{
|
|
status = ScSetServiceDependList(
|
|
Start,
|
|
Service,
|
|
(PVOID)DependOnService,
|
|
TypeDependOnService
|
|
);
|
|
|
|
if (status != NO_ERROR)
|
|
{
|
|
//
|
|
// Error with creating the stop depend entry for
|
|
// DependOnService. Back out changes set for the
|
|
// current start depend entry.
|
|
//
|
|
Start->DependType = TypeDependOnUnresolved;
|
|
Start->DependUnresolved = UnresolvedEntry;
|
|
|
|
//
|
|
// Back out of all other resolved dependencies to
|
|
// DependOnService will be done in ScDecrementUseCountAndDelete.
|
|
//
|
|
SC_LOG2(ERROR, "ScResolvedDependencyToService " FORMAT_LPWSTR
|
|
" failed " FORMAT_DWORD "\n",
|
|
DependOnService->ServiceName, status);
|
|
return status;
|
|
}
|
|
|
|
SC_LOG2(DEPEND, FORMAT_LPWSTR " depends on " FORMAT_LPWSTR
|
|
". Dependency resolved!\n", Service->ServiceName,
|
|
UnresolvedEntry->Name);
|
|
|
|
ScDeleteUnresolvedDepend(&UnresolvedEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateDependencies(
|
|
OUT PSERVICE_RECORD ServiceRecord,
|
|
IN LPWSTR Dependencies OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function creates the start dependencies list of a service.
|
|
If the service specified by ServiceRecord depends on a service that
|
|
has not been inserted into the service list yet, that service entry
|
|
will be created and inserted into the service list so that the depend
|
|
record can point to it. The service this one points to in its start
|
|
dependency list will get a new entry in its stop dependency list because
|
|
this one must be stopped before it can stop.
|
|
|
|
The dependencies list is not ordered.
|
|
|
|
NOTE: This function is for call from RChangeServiceConfig.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Receives the start dependencies information in this
|
|
service record.
|
|
|
|
Dependencies - Supplies the string which contains the names this service
|
|
depend on to be started first. This is a multi-sz string of
|
|
service or group names.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - Fail to allocate memory for required data
|
|
structures.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScSetDependencyPointers.
|
|
|
|
It also assumes that the caller has exclusively acquired the group
|
|
list lock.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
|
|
LPWSTR DependPtr = Dependencies;
|
|
LPWSTR DependOnName;
|
|
PVOID DependOnRecord = NULL;
|
|
DEPEND_TYPE Type;
|
|
|
|
PDEPEND_RECORD Start;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
if (! ARGUMENT_PRESENT(DependPtr)) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
while (*DependPtr != 0) {
|
|
|
|
if (ScGetToken(&DependPtr, &DependOnName)) {
|
|
|
|
//
|
|
// Initialize flag for noting that a new service record is
|
|
// created for resolving the dependency chain.
|
|
//
|
|
Type = TypeNone;
|
|
|
|
if (*DependOnName != SC_GROUP_IDENTIFIERW) {
|
|
|
|
//
|
|
// Depend on a service
|
|
//
|
|
|
|
//
|
|
// Look for the service we depend on in the service record list
|
|
//
|
|
status = ScGetNamedServiceRecord(
|
|
DependOnName,
|
|
(LPSERVICE_RECORD *) &DependOnRecord
|
|
);
|
|
|
|
if (status == ERROR_SERVICE_DOES_NOT_EXIST) {
|
|
|
|
//
|
|
// Could not find the service we depend on. Create an
|
|
// unresolved dependency entry.
|
|
//
|
|
status = ScCreateUnresolvedDepend(
|
|
DependOnName,
|
|
(PUNRESOLVED_DEPEND *) &DependOnRecord
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// New unresolved depend entry created. We have to remove
|
|
// it if any error occurs later.
|
|
//
|
|
Type = TypeDependOnUnresolved;
|
|
|
|
}
|
|
else {
|
|
|
|
Type = TypeDependOnService;
|
|
}
|
|
|
|
if (status != NO_ERROR) {
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Depend on a group
|
|
//
|
|
|
|
PLOAD_ORDER_GROUP GroupEntry = ScGetOrderGroupList();
|
|
|
|
DependOnName++;
|
|
|
|
//
|
|
// Search for matching group name in load order list
|
|
//
|
|
while (GroupEntry != NULL) {
|
|
if (_wcsicmp(GroupEntry->GroupName, DependOnName) == 0) {
|
|
DependOnRecord = (PVOID) GroupEntry;
|
|
Type = TypeDependOnGroup;
|
|
break;
|
|
}
|
|
GroupEntry = GroupEntry->Next;
|
|
}
|
|
|
|
if (GroupEntry == NULL) {
|
|
//
|
|
// Could not find group in the OrderGroup list. Must
|
|
// depend on a standalone group.
|
|
//
|
|
status = ScCreateStandaloneGroup(
|
|
DependOnName,
|
|
(LPLOAD_ORDER_GROUP *) &DependOnRecord
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
goto ErrorExit;
|
|
}
|
|
|
|
Type = TypeDependOnGroup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate memory for start depend record and insert it in the
|
|
// front of the start depend list of the service we are processing.
|
|
//
|
|
if ((status = ScCreateDependRecord(
|
|
TRUE, // For start list
|
|
ServiceRecord,
|
|
&Start
|
|
)) != NO_ERROR) {
|
|
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Start depend record created OK, set fields. Set stop
|
|
// depend if appropriate (Type == TypeDependOnService).
|
|
//
|
|
status = ScSetServiceDependList(
|
|
Start,
|
|
ServiceRecord,
|
|
DependOnRecord,
|
|
Type
|
|
);
|
|
|
|
if (status != NO_ERROR) {
|
|
|
|
//
|
|
// Remove the start depend record just created in the front of
|
|
// the start depend list and delete it.
|
|
//
|
|
ServiceRecord->StartDepend = Start->Next;
|
|
LocalFree(Start);
|
|
|
|
goto ErrorExit;
|
|
}
|
|
|
|
} // if got token
|
|
|
|
} // while there is a dependency
|
|
|
|
return NO_ERROR;
|
|
|
|
ErrorExit:
|
|
|
|
//
|
|
// Remove newly created service record because of errors and we cannot
|
|
// proceed.
|
|
//
|
|
if (Type == TypeDependOnUnresolved) {
|
|
|
|
ScDeleteUnresolvedDepend((PUNRESOLVED_DEPEND *) &DependOnRecord);
|
|
}
|
|
|
|
//
|
|
// Clean up dependencies created up to the point of failure
|
|
//
|
|
ScDeleteStartDependencies(ServiceRecord);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateUnresolvedDepend(
|
|
IN LPWSTR Name,
|
|
OUT LPUNRESOLVED_DEPEND *Unresolved
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks for a matching unresolved entry in the unresolved
|
|
depend list. If a match is found, the reference count is incremented
|
|
and the pointer to the matching entry is returned.
|
|
|
|
If no matching entry is found, this function creates a new unresolved
|
|
entry, insert it into the end of the unresolved depend list, and
|
|
return a pointer to the new entry.
|
|
|
|
|
|
Arguments:
|
|
|
|
Name - Supplies the name of the service or group which has not been
|
|
installed yet, and thus needing this unresolved depend entry.
|
|
|
|
Unresolved - Receives a pointer to the unresolved entry.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - Allocation of memory failed.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScCreateDependencies.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
LPUNRESOLVED_DEPEND UnresolvedList = ScGetUnresolvedDependList();
|
|
|
|
BOOL Found = FALSE;
|
|
|
|
|
|
//
|
|
// Search the existing unresolved depend list for the matching
|
|
// unresolved depend entry.
|
|
//
|
|
while (UnresolvedList != NULL) {
|
|
|
|
if (_wcsicmp(UnresolvedList->Name, Name) == 0) {
|
|
Found = TRUE;
|
|
break;
|
|
}
|
|
|
|
UnresolvedList = UnresolvedList->Next;
|
|
}
|
|
|
|
if (Found) {
|
|
|
|
UnresolvedList->RefCount++;
|
|
|
|
SC_LOG2(DEPEND,
|
|
"Found existing unresolved entry for " FORMAT_LPWSTR
|
|
", just increment refcount to %lu\n", UnresolvedList->Name,
|
|
UnresolvedList->RefCount);
|
|
|
|
*Unresolved = UnresolvedList;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Not found. Allocate a new unresolved entry.
|
|
//
|
|
if ((*Unresolved = (LPUNRESOLVED_DEPEND)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
sizeof(UNRESOLVED_DEPEND) + WCSSIZE(Name)
|
|
)) == NULL) {
|
|
SC_LOG1(ERROR,"ScCreateUnresolvedDepend: LocalAlloc failure %lu\n",
|
|
GetLastError());
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
(*Unresolved)->Name = (LPWSTR) ((DWORD_PTR) *Unresolved +
|
|
sizeof(UNRESOLVED_DEPEND));
|
|
wcscpy((*Unresolved)->Name, Name);
|
|
|
|
(*Unresolved)->RefCount = 1;
|
|
|
|
SC_LOG1(DEPEND, "Created new unresolved depend entry "
|
|
FORMAT_LPWSTR "\n", (*Unresolved)->Name);
|
|
|
|
UnresolvedList = &UnresolvedDependList;
|
|
|
|
//
|
|
// Add the new unresolved entry to the unresolved list at the end.
|
|
//
|
|
ADD_TO_LIST(UnresolvedList, *Unresolved);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteUnresolvedDepend(
|
|
IN OUT LPUNRESOLVED_DEPEND *Unresolved
|
|
)
|
|
{
|
|
|
|
if ((*Unresolved)->RefCount) {
|
|
(*Unresolved)->RefCount--;
|
|
SC_LOG1(DEPEND, "ScDeleteUnresolvedDepend: Subtracted RefCount is "
|
|
FORMAT_DWORD "\n", (*Unresolved)->RefCount);
|
|
}
|
|
else {
|
|
//
|
|
// The reference count better not be 0.
|
|
//
|
|
SC_LOG0(ERROR, "ScDeleteUnresolvedDepend: Before delete, refcount is 0!\n");
|
|
SC_ASSERT(FALSE);
|
|
}
|
|
|
|
if ((*Unresolved)->RefCount == 0) {
|
|
REMOVE_FROM_LIST(*Unresolved);
|
|
LocalFree(*Unresolved);
|
|
*Unresolved = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScCreateDependRecord(
|
|
IN BOOL IsStartList,
|
|
IN OUT PSERVICE_RECORD ServiceRecord,
|
|
OUT PDEPEND_RECORD *DependRecord
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function allocates the memory for a depend record, and insert
|
|
it into the front of the specific depend list. If IsStartList is
|
|
TRUE, the depend record goes into the start depend list of
|
|
ServiceRecord, otherwise the depend record goes into the stop
|
|
depend list of the ServiceRecord.
|
|
|
|
Arguments:
|
|
|
|
IsStartList - TRUE indicates to insert into start list, FALSE indicates
|
|
to insert into stop list.
|
|
|
|
ServiceRecord - Receives the start/stop depend record in its dependency
|
|
list.
|
|
|
|
DependRecord - Receives a pointer to the new depend record created.
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The operation was successful.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - The call to allocate memory for a depend
|
|
record failed.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScCreateDependencies.
|
|
|
|
--*/
|
|
{
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
if ((*DependRecord = (PDEPEND_RECORD)LocalAlloc(
|
|
LMEM_ZEROINIT,
|
|
sizeof(DEPEND_RECORD)
|
|
)) == NULL) {
|
|
SC_LOG(ERROR,"ScCreateDependRecord: LocalAlloc failure %ld\n",
|
|
GetLastError());
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
|
|
//
|
|
// Insert the depend record into the front of the list
|
|
//
|
|
if (IsStartList) {
|
|
//
|
|
// Start depend
|
|
//
|
|
(*DependRecord)->Next = ServiceRecord->StartDepend;
|
|
ServiceRecord->StartDepend = *DependRecord;
|
|
}
|
|
else {
|
|
//
|
|
// Stop depend
|
|
//
|
|
(*DependRecord)->Next = ServiceRecord->StopDepend;
|
|
ServiceRecord->StopDepend = *DependRecord;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteStartDependencies(
|
|
IN PSERVICE_RECORD ServiceRecord
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes the start dependencies list of a service. It also
|
|
deletes the the stop dependencies of other services which need this
|
|
service to be stopped first.
|
|
|
|
NOTE: This function is for call from RChangeServiceConfig.
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord - Supplies the start dependencies information in this
|
|
service record.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScAddConfigInfoServiceRecord and
|
|
ScDecrementUseCountAndDelete.
|
|
|
|
It also assumes that the caller has exclusively acquired the group
|
|
list lock.
|
|
|
|
--*/
|
|
{
|
|
PDEPEND_RECORD StartEntry;
|
|
PDEPEND_RECORD StopEntry;
|
|
PDEPEND_RECORD StopBackPointer;
|
|
|
|
SC_ASSERT(ScGroupListLock.HaveExclusive());
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
StartEntry = ServiceRecord->StartDepend;
|
|
|
|
while (StartEntry != NULL) {
|
|
|
|
if (StartEntry->DependType == TypeDependOnService) {
|
|
|
|
LPSERVICE_RECORD DependencyService = StartEntry->DependService;
|
|
|
|
|
|
//
|
|
// Find the stop depend record for the service which depends on this
|
|
// service to be stopped first, and delete it.
|
|
//
|
|
StopEntry = DependencyService->StopDepend;
|
|
StopBackPointer = StopEntry;
|
|
|
|
while ((StopEntry != NULL) &&
|
|
(StopEntry->DependService != ServiceRecord)) {
|
|
|
|
StopBackPointer = StopEntry;
|
|
StopEntry = StopEntry->Next;
|
|
}
|
|
|
|
if (StopEntry == NULL) {
|
|
#ifndef _CAIRO_
|
|
//
|
|
// We allow Netlogon to appear in the start dependency list, but
|
|
// not in the stop dependency list. This is for the case where
|
|
// we add a "soft" dependency on netlogon because the service runs
|
|
// in a remove account.
|
|
//
|
|
if (_wcsicmp(DependencyService->ServiceName,L"Netlogon") != 0) {
|
|
#endif // _CAIRO_
|
|
SC_LOG1(ERROR,
|
|
"ScDeleteStartDependencies: Failed to find matching stop depend node for "
|
|
FORMAT_LPWSTR "\n",
|
|
DependencyService->ServiceName);
|
|
SC_ASSERT(FALSE);
|
|
return;
|
|
#ifndef _CAIRO_
|
|
}
|
|
#endif // _CAIRO_
|
|
}
|
|
else {
|
|
|
|
if (StopEntry->DependService == ServiceRecord) {
|
|
|
|
if ((PVOID) StopBackPointer == StopEntry) {
|
|
//
|
|
// Unchaining from the front of the list
|
|
//
|
|
DependencyService->StopDepend = StopEntry->Next;
|
|
}
|
|
else {
|
|
//
|
|
// Unchaining from the middle or end of the list
|
|
//
|
|
StopBackPointer->Next = StopEntry->Next;
|
|
}
|
|
|
|
LocalFree(StopEntry);
|
|
}
|
|
}
|
|
}
|
|
else if (StartEntry->DependType == TypeDependOnGroup) {
|
|
|
|
//
|
|
// Decrement the reference count on the standalone group
|
|
// entry and deletes it if 0.
|
|
//
|
|
if (StartEntry->DependGroup->RefCount != MAXULONG) {
|
|
ScDeleteStandaloneGroup(StartEntry->DependGroup);
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Dependency type is unresolved.
|
|
//
|
|
ScDeleteUnresolvedDepend(&StartEntry->DependUnresolved);
|
|
}
|
|
|
|
//
|
|
// Now delete the start depend record.
|
|
//
|
|
ServiceRecord->StartDepend = StartEntry->Next;
|
|
LocalFree(StartEntry);
|
|
StartEntry = ServiceRecord->StartDepend;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ScDeleteStopDependencies(
|
|
IN PSERVICE_RECORD ServiceToBeDeleted
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes the stop dependencies list of a service. For
|
|
every stop depend service, it makes the start depend pointer of that
|
|
service to point to an unresolved depend entry.
|
|
|
|
This function is called when the service is to be deleted.
|
|
|
|
Arguments:
|
|
|
|
ServiceToBeDeleted - Supplies the pointer to the service that will
|
|
be deleted.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScDecrementUseCountAndDelete.
|
|
|
|
--*/
|
|
{
|
|
|
|
DWORD status;
|
|
PDEPEND_RECORD StartEntry;
|
|
PDEPEND_RECORD StopEntry;
|
|
LPUNRESOLVED_DEPEND Unresolved;
|
|
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
StopEntry = ServiceToBeDeleted->StopDepend;
|
|
|
|
while (StopEntry != NULL) {
|
|
|
|
LPSERVICE_RECORD DependencyService = StopEntry->DependService;
|
|
|
|
//
|
|
// Loop through the start depend entries of the service which
|
|
// depends on ServiceToBeDeleted.
|
|
//
|
|
StartEntry = DependencyService->StartDepend;
|
|
|
|
while (StartEntry != NULL) {
|
|
|
|
if (StartEntry->DependService == ServiceToBeDeleted) {
|
|
break;
|
|
}
|
|
|
|
StartEntry = StartEntry->Next;
|
|
}
|
|
|
|
if (StartEntry != NULL) {
|
|
|
|
//
|
|
// Found a start depend entry that points to the service to be.
|
|
// deleted. Make it point to an unresolved depend entry that
|
|
// represents that service.
|
|
//
|
|
status = ScCreateUnresolvedDepend(
|
|
ServiceToBeDeleted->ServiceName,
|
|
&Unresolved
|
|
);
|
|
|
|
if (status == NO_ERROR) {
|
|
StartEntry->DependType = TypeDependOnUnresolved;
|
|
StartEntry->DependUnresolved = Unresolved;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now delete the start depend record.
|
|
//
|
|
ServiceToBeDeleted->StopDepend = StopEntry->Next;
|
|
LocalFree(StopEntry);
|
|
StopEntry = ServiceToBeDeleted->StopDepend;
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ScSetServiceDependList(
|
|
LPDEPEND_RECORD Start,
|
|
LPSERVICE_RECORD ServiceRecord,
|
|
PVOID DependOnRecord,
|
|
DEPEND_TYPE DependOnType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
database lock. It is called by ScResolveDependencyToService and
|
|
ScCreateDependencies.
|
|
|
|
--*/
|
|
{
|
|
DWORD status;
|
|
LPDEPEND_RECORD Stop;
|
|
|
|
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
|
|
|
//
|
|
// Set fields for start depend entry.
|
|
//
|
|
Start->DependType = DependOnType;
|
|
Start->Depend = DependOnRecord;
|
|
|
|
if (DependOnType == TypeDependOnService) {
|
|
|
|
//
|
|
// Allocate memory for stop depend record and insert it in the
|
|
// front of the stop depend list of the service we depend on.
|
|
//
|
|
if ((status = ScCreateDependRecord(
|
|
FALSE, // For stop list
|
|
(LPSERVICE_RECORD) DependOnRecord,
|
|
&Stop
|
|
)) != NO_ERROR) {
|
|
|
|
return status;
|
|
}
|
|
|
|
Stop->DependType = TypeDependOnService;
|
|
Stop->DependService = ServiceRecord;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
LPLOAD_ORDER_GROUP
|
|
ScGetNamedGroupRecord(
|
|
IN LPCWSTR GroupName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function searches for a named group, first in the order group
|
|
list and next in the standalone group list.
|
|
|
|
Arguments:
|
|
|
|
GroupName - Supplies the name of the group to look for.
|
|
|
|
Return Value:
|
|
|
|
Returns the pointer to group found. If not found, this value is
|
|
NULL.
|
|
|
|
Note:
|
|
|
|
This routine assumes that the caller has exclusively acquired the
|
|
group list lock.
|
|
|
|
--*/
|
|
{
|
|
LPLOAD_ORDER_GROUP Group;
|
|
|
|
for (Group = ScGetOrderGroupList();
|
|
Group != NULL;
|
|
Group = Group->Next)
|
|
{
|
|
if (_wcsicmp(Group->GroupName, GroupName) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Group == NULL)
|
|
{
|
|
for (Group = ScGetStandaloneGroupList();
|
|
Group != NULL;
|
|
Group = Group->Next)
|
|
{
|
|
if (_wcsicmp(Group->GroupName, GroupName) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Group;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScGetUniqueTag(
|
|
IN LPWSTR GroupName,
|
|
OUT LPDWORD Tag
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks for a unique tag value within the specified
|
|
group.
|
|
|
|
Arguments:
|
|
|
|
GroupName - Specifies the group name within which the value tag
|
|
returned must be unique.
|
|
|
|
Tag - Receives the unique tag value.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
This function acquires share access to the group list lock.
|
|
|
|
It assumes that the exclusive service database lock is already
|
|
acquired so that no other caller can execute this code until
|
|
the returned tag is fully assigned to the service, and that the
|
|
service entries in the database list don't change.
|
|
|
|
The GroupListLock must be held exclusively prior to calling this
|
|
function.
|
|
|
|
--*/
|
|
{
|
|
LPDWORD TagArray;
|
|
DWORD TagArrayLength;
|
|
|
|
DWORD ReturnTag = 1;
|
|
|
|
LPLOAD_ORDER_GROUP Group;
|
|
|
|
SC_ASSERT(ScGroupListLock.Have());
|
|
SC_ASSERT(ScServiceRecordLock.Have());
|
|
|
|
if (ScGetGroupVector(
|
|
GroupName,
|
|
(LPBYTE *) &TagArray,
|
|
&TagArrayLength
|
|
) == NO_ERROR) {
|
|
|
|
if (TagArray != NULL) {
|
|
//
|
|
// The returned values is actually the number of bytes. Divide it
|
|
// by size of DWORD to make it the number of array entries.
|
|
//
|
|
TagArrayLength = TagArrayLength / sizeof(DWORD);
|
|
|
|
SC_ASSERT((TagArrayLength - 1) == TagArray[0]);
|
|
|
|
ScCompareVector(
|
|
TagArray,
|
|
TagArrayLength,
|
|
&ReturnTag
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
|
|
TagArray = NULL;
|
|
}
|
|
|
|
Group = ScGetNamedGroupRecord(GroupName);
|
|
|
|
if (Group != NULL) {
|
|
|
|
GroupAgain:
|
|
FOR_ALL_SERVICES(Service) {
|
|
|
|
if ((Service->RegistryGroup == Group) &&
|
|
(Service->Tag == ReturnTag)) {
|
|
|
|
ReturnTag++;
|
|
|
|
if (TagArray != NULL) {
|
|
|
|
ScCompareVector(
|
|
TagArray,
|
|
TagArrayLength,
|
|
&ReturnTag
|
|
);
|
|
}
|
|
|
|
goto GroupAgain;
|
|
}
|
|
} // while all services
|
|
}
|
|
|
|
|
|
*Tag = ReturnTag;
|
|
|
|
SC_LOG(DEPEND, "ScGetUniqueTag: Tag=" FORMAT_DWORD "\n", *Tag);
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
ScCompareVector(
|
|
IN LPDWORD TagArray,
|
|
IN DWORD TagArrayLength,
|
|
IN OUT LPDWORD ReturnTagPtr
|
|
)
|
|
{
|
|
DWORD i;
|
|
|
|
VectorAgain:
|
|
for (i = 1; i < TagArrayLength; i++) {
|
|
|
|
if (TagArray[i] == (*ReturnTagPtr)) {
|
|
|
|
SC_LOG(DEPEND_DUMP, "Tag " FORMAT_DWORD " is not unique\n",
|
|
*ReturnTagPtr);
|
|
|
|
(*ReturnTagPtr)++;
|
|
goto VectorAgain;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
ScGetDependencySize(
|
|
LPSERVICE_RECORD ServiceRecord,
|
|
LPDWORD DependSize,
|
|
LPDWORD MaxDependSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord -
|
|
|
|
DependSize - This points to a location that will contain the number
|
|
of bytes required for the list of dependency strings.
|
|
|
|
MaxDependSize - This points to a location that will contain the
|
|
number of bytes in the longest dependency string in the set.
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
--*/
|
|
{
|
|
LPDEPEND_RECORD dependRecord;
|
|
DWORD bytesNeeded = 0;
|
|
DWORD StrSize=0;
|
|
|
|
dependRecord = ServiceRecord->StartDepend;
|
|
|
|
//
|
|
// NOTE: Dependencies are expected to be a double NULL terminated
|
|
// terminated set of strings.
|
|
//
|
|
bytesNeeded += sizeof(WCHAR);
|
|
|
|
if (dependRecord == NULL) {
|
|
bytesNeeded += sizeof(WCHAR);
|
|
}
|
|
while (dependRecord != NULL) {
|
|
|
|
SC_ASSERT( dependRecord->Depend != NULL );
|
|
|
|
// Add room. WCSSIZE adds 1 char. For final entry, we'll
|
|
// use null char. In between, we'll put some separator.
|
|
|
|
if (dependRecord->DependType == TypeDependOnService) {
|
|
StrSize =
|
|
(DWORD) WCSSIZE(dependRecord->DependService->ServiceName); // sizes...
|
|
|
|
}
|
|
else if (dependRecord->DependType == TypeDependOnGroup) {
|
|
StrSize =
|
|
(DWORD) WCSSIZE(dependRecord->DependGroup->GroupName) +
|
|
sizeof(WCHAR); // name size plus SC_GROUP_IDENTIFIERW
|
|
}
|
|
else {
|
|
//
|
|
// Unresolved service dependency
|
|
//
|
|
StrSize = (DWORD) WCSSIZE(dependRecord->DependUnresolved->Name);
|
|
}
|
|
|
|
bytesNeeded += StrSize;
|
|
|
|
if (StrSize > *MaxDependSize) {
|
|
*MaxDependSize = StrSize;
|
|
}
|
|
|
|
dependRecord = dependRecord->Next;
|
|
}
|
|
*DependSize = bytesNeeded;
|
|
}
|
|
|
|
DWORD
|
|
ScGetDependencyString(
|
|
LPSERVICE_RECORD ServiceRecord,
|
|
DWORD MaxDependSize,
|
|
DWORD DependSize,
|
|
LPWSTR lpDependencies
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
ServiceRecord -
|
|
|
|
MaxDependSize - This is the size of the largest string in the
|
|
dependency list.
|
|
|
|
lpDependencies - This is a pointer to the location where the
|
|
list of dependency strings is to be stored.
|
|
|
|
Return Value:
|
|
|
|
|
|
|
|
--*/
|
|
{
|
|
LPWSTR endOfVariableData;
|
|
LPWSTR fixedDataEnd;
|
|
LPDEPEND_RECORD dependRecord;
|
|
DWORD bufSize;
|
|
DWORD ApiStatus = NO_ERROR;
|
|
|
|
//
|
|
// Put dependencies in the return buffer. Since it is a NULL-NULL
|
|
// string, put an extra NULL at the end to begin with.
|
|
//
|
|
endOfVariableData = (LPWSTR) (((LPBYTE)lpDependencies) + DependSize);
|
|
|
|
endOfVariableData = endOfVariableData - 1;
|
|
* endOfVariableData = L'\0';
|
|
|
|
fixedDataEnd = lpDependencies;
|
|
|
|
dependRecord = ServiceRecord->StartDepend;
|
|
|
|
if (dependRecord == NULL) {
|
|
//
|
|
// If there are no dependencies, then we need to add a separator
|
|
// that will be followed by the NULL (immediately above).
|
|
// This separator is used to get us across the RPC interface.
|
|
// Then on the client side, it is changed to a NULL. So we end
|
|
// up with an empty-double-NULL-terminated-string.
|
|
//
|
|
endOfVariableData = endOfVariableData - 1;
|
|
* endOfVariableData = L'/';
|
|
lpDependencies = endOfVariableData;
|
|
}
|
|
else {
|
|
|
|
LPWSTR DependName;
|
|
|
|
|
|
DependName = (LPWSTR)LocalAlloc(0, (UINT) MaxDependSize);
|
|
|
|
if (DependName == NULL) {
|
|
ApiStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
lpDependencies = endOfVariableData;
|
|
while (dependRecord != NULL) {
|
|
|
|
SC_ASSERT( dependRecord->Depend != NULL );
|
|
|
|
|
|
if (dependRecord->DependType == TypeDependOnService) {
|
|
|
|
wcscpy(DependName, dependRecord->DependService->ServiceName);
|
|
|
|
}
|
|
else if (dependRecord->DependType == TypeDependOnGroup) {
|
|
|
|
*DependName = SC_GROUP_IDENTIFIERW;
|
|
|
|
wcscpy(DependName + 1, dependRecord->DependGroup->GroupName);
|
|
}
|
|
else {
|
|
//
|
|
// Unresolved service dependency
|
|
//
|
|
wcscpy(DependName, dependRecord->DependUnresolved->Name);
|
|
}
|
|
|
|
bufSize = (DWORD) wcslen(DependName);
|
|
|
|
if ( !ScCopyStringToBufferW (
|
|
DependName,
|
|
bufSize,
|
|
fixedDataEnd,
|
|
&endOfVariableData,
|
|
&lpDependencies,
|
|
NULL
|
|
) ) {
|
|
|
|
SC_LOG0(ERROR,
|
|
"RQueryServiceConfigW:ScCopyStringtoBufferW (Dependencies)Failed\n");
|
|
|
|
SC_ASSERT( FALSE );
|
|
ApiStatus = ERROR_INSUFFICIENT_BUFFER;
|
|
LocalFree(DependName);
|
|
goto Cleanup;
|
|
}
|
|
else {
|
|
//
|
|
// Add separator character.
|
|
//
|
|
|
|
lpDependencies[bufSize] = L'/';
|
|
}
|
|
|
|
dependRecord = dependRecord->Next;
|
|
}
|
|
|
|
LocalFree(DependName);
|
|
}
|
|
Cleanup:
|
|
return(ApiStatus);
|
|
}
|
|
|
|
|
|
#if DBG
|
|
VOID
|
|
ScDumpGroups(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks group list prints out each entry.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
Calls to this routine must be enclosed within #if DBG.
|
|
|
|
--*/
|
|
{
|
|
PLOAD_ORDER_GROUP GroupEntry = ScGetOrderGroupList();
|
|
|
|
|
|
while (GroupEntry != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"\nOrdered Groups:\n"));
|
|
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"Group: Handle=%08lx Name=%ws RefCount=x%lx\n",
|
|
GroupEntry,
|
|
GroupEntry->GroupName,
|
|
GroupEntry->RefCount));
|
|
|
|
GroupEntry = GroupEntry->Next;
|
|
}
|
|
|
|
GroupEntry = ScGetStandaloneGroupList();
|
|
|
|
while (GroupEntry != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"Standalone Groups:\n"));
|
|
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"Group: Handle=%08lx Name=%ws RefCount=x%lx\n",
|
|
GroupEntry,
|
|
GroupEntry->GroupName,
|
|
GroupEntry->RefCount));
|
|
|
|
GroupEntry = GroupEntry->Next;
|
|
}
|
|
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"\nTDI group is at %08lx" "\nPNP_TDI group is at %08lx\n",
|
|
ScGlobalTDIGroup,
|
|
ScGlobalPNP_TDIGroup));
|
|
}
|
|
|
|
VOID
|
|
ScDumpServiceDependencies(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function walks the start and stop dependencies lists of every
|
|
service in the service record list.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Note:
|
|
|
|
Calls to this routine must be enclosed within #if DBG.
|
|
|
|
--*/
|
|
{
|
|
PDEPEND_RECORD DependList;
|
|
|
|
FOR_ALL_SERVICES(ServiceRecord)
|
|
{
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"Service: %-20ws UseCount=%lu",
|
|
ServiceRecord->ServiceName,
|
|
ServiceRecord->UseCount));
|
|
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
" MemberOfGroup=%08lx ",
|
|
ServiceRecord->MemberOfGroup));
|
|
|
|
if (ServiceRecord->MemberOfGroup != NULL) {
|
|
if (ServiceRecord->MemberOfGroup->RefCount != MAXULONG) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"SG=%ws\n",
|
|
ServiceRecord->MemberOfGroup->GroupName));
|
|
}
|
|
else if (ServiceRecord->MemberOfGroup->RefCount == MAXULONG) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
"OG=%ws\n",
|
|
ServiceRecord->MemberOfGroup->GroupName));
|
|
}
|
|
}
|
|
else {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_DEPEND_DUMP, "\n"));
|
|
}
|
|
|
|
if (ServiceRecord->RegistryGroup != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
" RG=%ws\n",
|
|
ServiceRecord->RegistryGroup->GroupName));
|
|
}
|
|
|
|
//
|
|
// Dump start depend
|
|
//
|
|
DependList = ServiceRecord->StartDepend;
|
|
|
|
if (DependList != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_DEPEND_DUMP, " StartDepend:\n"));
|
|
}
|
|
|
|
while (DependList != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
" %ws\n",
|
|
DependList->DependService->ServiceName));
|
|
|
|
DependList = DependList->Next;
|
|
}
|
|
|
|
//
|
|
// Dump stop depend
|
|
//
|
|
DependList = ServiceRecord->StopDepend;
|
|
if (DependList != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_DEPEND_DUMP, " StopDepend:\n"));
|
|
}
|
|
|
|
while (DependList != NULL) {
|
|
KdPrintEx((DPFLTR_SCSERVER_ID,
|
|
DEBUG_DEPEND_DUMP,
|
|
" %ws\n",
|
|
DependList->DependService->ServiceName));
|
|
|
|
DependList = DependList->Next;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // #if DBG
|