Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5372 lines
129 KiB

/*++
Copyright (c) 1998-2001 Microsoft Corporation
Module Name:
cgroup.cxx
Abstract:
Note that most of the routines in this module assume they are called
at PASSIVE_LEVEL.
Author:
Paul McDaniel (paulmcd) 12-Jan-1999
Revision History:
--*/
#include "precomp.h" // Project wide headers
#include "cgroupp.h" // Private data structures
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, UlInitializeCG )
#pragma alloc_text( PAGE, UlTerminateCG )
#pragma alloc_text( PAGE, UlAddUrlToConfigGroup )
#pragma alloc_text( PAGE, UlConfigGroupFromListEntry )
#pragma alloc_text( PAGE, UlCreateConfigGroup )
#pragma alloc_text( PAGE, UlDeleteConfigGroup )
#pragma alloc_text( PAGE, UlGetConfigGroupInfoForUrl )
#pragma alloc_text( PAGE, UlQueryConfigGroupInformation )
#pragma alloc_text( PAGE, UlRemoveUrlFromConfigGroup )
#pragma alloc_text( PAGE, UlRemoveAllUrlsFromConfigGroup )
#pragma alloc_text( PAGE, UlAddTransientUrl )
#pragma alloc_text( PAGE, UlRemoveTransientUrl )
#pragma alloc_text( PAGE, UlSetConfigGroupInformation )
#pragma alloc_text( PAGE, UlNotifyOrphanedConfigGroup )
#pragma alloc_text( PAGE, UlpCreateConfigGroupObject )
#pragma alloc_text( PAGE, UlpCleanAllUrls )
#pragma alloc_text( PAGE, UlpDeferredRemoveSite )
#pragma alloc_text( PAGE, UlpDeferredRemoveSiteWorker )
#pragma alloc_text( PAGE, UlpSanitizeUrl )
#pragma alloc_text( PAGE, UlpSetUrlInfo )
#pragma alloc_text( PAGE, UlpConfigGroupInfoRelease )
#pragma alloc_text( PAGE, UlpConfigGroupInfoDeepCopy )
#pragma alloc_text( PAGE, UlpTreeFreeNode )
#pragma alloc_text( PAGE, UlpTreeFindNode )
#pragma alloc_text( PAGE, UlpTreeFindNodeWalker )
#pragma alloc_text( PAGE, UlpTreeFindEntry )
#pragma alloc_text( PAGE, UlpTreeFindSite )
#pragma alloc_text( PAGE, UlpTreeFindWildcardMatch )
#pragma alloc_text( PAGE, UlpTreeFindIpMatch )
#pragma alloc_text( PAGE, UlpTreeInsert )
#pragma alloc_text( PAGE, UlpTreeInsertEntry )
#endif // ALLOC_PRAGMA
#if 0
#endif
//
// Globals
//
PUL_CG_URL_TREE_HEADER g_pSites = NULL;
BOOLEAN g_InitCGCalled = FALSE;
/***************************************************************************++
Routine Description:
Free's the node pEntry. This funciton walks up and tree of parent entries
and deletes them if they are supposed to free'd (dummy nodes) .
it will free the memory pEntry + unlink it from the config group LIST_ENTRY.
Arguments:
IN PUL_CG_URL_TREE_ENTRY pEntry - the entry to free
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpTreeFreeNode(
IN PUL_CG_URL_TREE_ENTRY pEntry
)
{
NTSTATUS Status;
PUL_CG_URL_TREE_HEADER pHeader;
ULONG Index;
PUL_CG_URL_TREE_ENTRY pParent;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
Status = STATUS_SUCCESS;
//
// Loop! we are going to walk up the tree deleting as much
// as we can of this branch.
//
while (pEntry != NULL)
{
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// Init
//
pParent = NULL;
UlTrace(
CONFIG_GROUP_TREE, (
"http!UlpTreeFreeNode - pEntry('%S', %d, %S, %S%S)\n",
pEntry->pToken,
pEntry->FullUrl,
(pEntry->pChildren == NULL || pEntry->pChildren->UsedCount == 0) ? L"no children" : L"children",
pEntry->pParent == NULL ? L"no parent" : L"parent=",
pEntry->pParent == NULL ? L"" : pEntry->pParent->pToken
)
);
//
// 1) might not be a "real" leaf - we are walking up the tree in this loop
//
// 2) also we clean this first because we might not be deleting
// this node at all, if it has dependent children.
//
if (pEntry->FullUrl == 1)
{
//
// Remove it from the cfg group list
//
RemoveEntryList(&(pEntry->ConfigGroupListEntry));
pEntry->ConfigGroupListEntry.Flink = pEntry->ConfigGroupListEntry.Blink = NULL;
pEntry->pConfigGroup = NULL;
//
// Mark it as a dummy node
//
pEntry->FullUrl = 0;
}
//
// do we have children?
//
if (pEntry->pChildren != NULL && pEntry->pChildren->UsedCount > 0)
{
//
// can't delete it. dependant children exist.
// it's already be converted to a dummy node above.
//
// leave it. it will get cleaned by a subsequent child.
//
break;
}
//
// we are really deleting this one, remove it from the sibling list.
//
//
// find our location in the sibling list
//
if (pEntry->pParent == NULL)
{
pHeader = g_pSites;
}
else
{
pHeader = pEntry->pParent->pChildren;
}
Status = UlpTreeFindEntry(
pHeader,
pEntry->pToken,
pEntry->TokenLength,
pEntry->TokenHash,
&Index,
NULL
);
if (NT_SUCCESS(Status) == FALSE)
{
ASSERT(FALSE);
goto end;
}
//
// time to remove it
//
// if not the last one, shift left the array at Index
//
if (Index < (pHeader->UsedCount-1))
{
RtlMoveMemory(
&(pHeader->pEntries[Index]),
&(pHeader->pEntries[Index+1]),
(pHeader->UsedCount - 1 - Index) * sizeof(UL_CG_HEADER_ENTRY)
);
}
//
// now we have 1 less
//
pHeader->UsedCount -= 1;
//
// need to clean parent entries that were here just for this leaf
//
if (pEntry->pParent != NULL)
{
//
// Does this parent have any other children?
//
ASSERT(IS_VALID_TREE_HEADER(pEntry->pParent->pChildren));
if (pEntry->pParent->pChildren->UsedCount == 0)
{
//
// no more, time to clean the child list
//
UL_FREE_POOL_WITH_SIG(pEntry->pParent->pChildren, UL_CG_TREE_HEADER_POOL_TAG);
//
// is the parent a real url entry?
//
if (pEntry->pParent->FullUrl != 1)
{
//
// nope . let's scrub it.
//
pParent = pEntry->pParent;
}
}
else
{
//
// ouch. siblings. can't nuke parent.
//
}
}
else
{
//
// we are deleting a site node; stop listening.
// We have to stop listening on another thread
// because otherwise there will be a deadlock
// between the config group lock and http connection
// locks.
//
UlpDeferredRemoveSite(pEntry);
}
//
// Free the entry
//
UL_FREE_POOL_WITH_SIG(pEntry, UL_CG_TREE_ENTRY_POOL_TAG);
//
// Move on to the next one
//
pEntry = pParent;
}
end:
return Status;
}
/***************************************************************************++
Routine Description:
Allocates and initializes a config group object.
Arguments:
ppObject - gets a pointer to the object on success
--***************************************************************************/
NTSTATUS
UlpCreateConfigGroupObject(
PUL_CONFIG_GROUP_OBJECT * ppObject
)
{
HTTP_CONFIG_GROUP_ID NewId = HTTP_NULL_ID;
PUL_CONFIG_GROUP_OBJECT pNewObject = NULL;
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(ppObject != NULL);
UlTrace(CONFIG_GROUP_FNC, ("http!UlpCreateConfigGroupObject\n"));
//
// Create an empty config group object structure - PAGED
//
pNewObject = UL_ALLOCATE_STRUCT(
NonPagedPool,
UL_CONFIG_GROUP_OBJECT,
UL_CG_OBJECT_POOL_TAG
);
if (pNewObject == NULL)
{
//
// Oops. Couldn't allocate the memory for it.
//
Status = STATUS_NO_MEMORY;
goto end;
}
RtlZeroMemory(pNewObject, sizeof(UL_CONFIG_GROUP_OBJECT));
//
// Create an opaque id for it
//
Status = UlAllocateOpaqueId(
&NewId, // pOpaqueId
UlOpaqueIdTypeConfigGroup, // OpaqueIdType
pNewObject // pContext
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// Fill in the structure
//
pNewObject->Signature = UL_CG_OBJECT_POOL_TAG;
pNewObject->RefCount = 1;
pNewObject->ConfigGroupId = NewId;
pNewObject->AppPoolFlags.Present = 0;
pNewObject->pAppPool = NULL;
pNewObject->pAutoResponse = NULL;
pNewObject->MaxBandwidth.Flags.Present = 0;
pNewObject->MaxConnections.Flags.Present = 0;
pNewObject->State.Flags.Present = 0;
pNewObject->Security.Flags.Present = 0;
pNewObject->LoggingConfig.Flags.Present = 0;
pNewObject->pLogFileEntry = NULL;
//
// init the bandwidth throttling flow list
//
InitializeListHead(&pNewObject->FlowListHead);
//
// init notification entries & head
//
UlInitializeNotifyEntry(&pNewObject->HandleEntry, pNewObject);
UlInitializeNotifyEntry(&pNewObject->ParentEntry, pNewObject);
UlInitializeNotifyHead(
&pNewObject->ChildHead,
&g_pUlNonpagedData->ConfigGroupResource
);
//
// init the url list
//
InitializeListHead(&pNewObject->UrlListHead);
//
// return the pointer
//
*ppObject = pNewObject;
end:
if (!NT_SUCCESS(Status))
{
//
// Something failed. Let's clean up.
//
if (pNewObject != NULL)
{
UL_FREE_POOL_WITH_SIG(pNewObject, UL_CG_OBJECT_POOL_TAG);
}
if (!HTTP_IS_NULL_ID(&NewId))
{
UlFreeOpaqueId(NewId, UlOpaqueIdTypeConfigGroup);
}
}
return Status;
} // UlpCreateConfigGroupObject
/***************************************************************************++
Routine Description:
This will clean all of the urls in the LIST_ENTRY for the config group
Arguments:
IN PUL_CONFIG_GROUP_OBJECT pObject the group to clean the urls for
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpCleanAllUrls(
IN PUL_CONFIG_GROUP_OBJECT pObject
)
{
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pObject != NULL);
Status = STATUS_SUCCESS;
//
// Remove all of the url's associated with this cfg group
//
//
// walk the list
//
while (IsListEmpty(&pObject->UrlListHead) == FALSE)
{
PUL_CG_URL_TREE_ENTRY pTreeEntry;
//
// get the containing struct
//
pTreeEntry = CONTAINING_RECORD(
pObject->UrlListHead.Flink,
UL_CG_URL_TREE_ENTRY,
ConfigGroupListEntry
);
ASSERT(IS_VALID_TREE_ENTRY(pTreeEntry) && pTreeEntry->FullUrl == 1);
//
// delete it - this unlinks it from the list
//
if (NT_SUCCESS(Status))
{
Status = UlpTreeFreeNode(pTreeEntry);
}
else
{
//
// just record the first error, but still attempt to free all
//
UlpTreeFreeNode(pTreeEntry);
}
}
// the list is empty now
//
return Status;
} // UlpCleanAllUrls
/***************************************************************************++
Routine Description:
Removes a site entry's url from the listening endpoint.
Arguments:
pEntry - the site entry
--***************************************************************************/
VOID
UlpDeferredRemoveSite(
IN PUL_CG_URL_TREE_ENTRY pEntry
)
{
PUL_DEFERRED_REMOVE_ITEM pItem;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_TREE_ENTRY(pEntry) );
ASSERT( pEntry->pParent == NULL );
ASSERT( pEntry->TokenLength > 0 );
pItem = UL_ALLOCATE_STRUCT_WITH_SPACE(
NonPagedPool,
UL_DEFERRED_REMOVE_ITEM,
pEntry->TokenLength + sizeof(WCHAR),
UL_DEFERRED_REMOVE_ITEM_POOL_TAG
);
if (pItem) {
pItem->NameLength = pEntry->TokenLength;
RtlCopyMemory(
pItem->pName,
pEntry->pToken,
pEntry->TokenLength + sizeof(WCHAR)
);
//
// REVIEW: Because UlRemoveSiteFromEndpointList can block
// REVIEW: indefinitely while waiting for other work items
// REVIEW: to complete, we must not queue it with UlQueueWorkItem.
// REVIEW: (could lead to deadlock, esp. in a single-threded queue)
//
UL_QUEUE_BLOCKING_ITEM(
&pItem->WorkItem,
&UlpDeferredRemoveSiteWorker
);
}
}
/***************************************************************************++
Routine Description:
Removes a site entry's url from the listening endpoint.
Arguments:
pWorkItem - in a UL_DEFERRED_REMOVE_ITEM struct with the endpoint name
--***************************************************************************/
VOID
UlpDeferredRemoveSiteWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
NTSTATUS Status;
PUL_DEFERRED_REMOVE_ITEM pItem;
//
// Sanity check
//
PAGED_CODE();
ASSERT( pWorkItem );
//
// get the name of the site
//
pItem = CONTAINING_RECORD(
pWorkItem,
UL_DEFERRED_REMOVE_ITEM,
WorkItem
);
ASSERT( pItem->pName[pItem->NameLength / 2] == UNICODE_NULL );
//
// remove it
//
Status = UlRemoveSiteFromEndpointList(pItem->pName);
if (!NT_SUCCESS(Status)) {
// c'est la vie
UlTrace(CONFIG_GROUP_TREE, (
"http!UlpDeferredRemoveSiteWorker(%ws) failed %08x\n",
pItem->pName,
Status
));
}
//
// free the work item
//
UL_FREE_POOL(pItem, UL_DEFERRED_REMOVE_ITEM_POOL_TAG);
}
/***************************************************************************++
Routine Description:
This will return a fresh buffer containing the url sanitized. caller
must free this from paged pool.
Arguments:
IN PUNICODE_STRING pUrl, the url to clean
OUT PWSTR * ppUrl the cleaned url
Return Value:
NTSTATUS - Completion status.
STATUS_NO_MEMORY; the memroy alloc failed
--***************************************************************************/
NTSTATUS
UlpSanitizeUrl(
IN PUNICODE_STRING pUrl,
OUT PWSTR * ppUrl
)
{
NTSTATUS Status;
PWSTR pNewUrl = NULL;
PWSTR pPort;
PWSTR pEnd;
PWSTR pStart;
ULONGLONG PortNum;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL && pUrl->Length > 0 && pUrl->Buffer != NULL);
ASSERT(ppUrl != NULL);
//
// Is the url prefix too long?
//
if ( (pUrl->Length / sizeof(WCHAR) ) > g_UlMaxUrlLength )
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Make a private copy of the url
//
pNewUrl = UL_ALLOCATE_ARRAY(
PagedPool,
WCHAR,
(pUrl->Length / sizeof(WCHAR)) + 1,
URL_POOL_TAG
);
if (pNewUrl == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
RtlCopyMemory(pNewUrl, pUrl->Buffer, pUrl->Length);
//
// NULL terminate it
//
pNewUrl[pUrl->Length/sizeof(WCHAR)] = UNICODE_NULL;
//
// Validate it
//
if (pUrl->Length < (sizeof(L"https://")-sizeof(WCHAR)))
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
if (_wcsnicmp(pNewUrl, L"http://", (sizeof(L"http://")-1)/sizeof(WCHAR)) != 0 &&
_wcsnicmp(pNewUrl, L"https://", (sizeof(L"https://")-1)/sizeof(WCHAR)) != 0)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
pStart = wcschr(pNewUrl, ':');
ASSERT(pStart != NULL); // already checked above
//
// skip the "://" to find the start
//
pStart += 3;
//
// find the end, right before the trailing slash
//
pEnd = wcschr(pStart, '/');
if (pEnd == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// find the port (right after the colon) colon
//
pPort = wcschr(pStart, ':');
if (pPort == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
pPort += 1;
//
// no hostname || no port (or colon past the end)
//
if (pPort == (pStart + 1) || pPort >= pEnd)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// NULL terminate it
//
pEnd[0] = UNICODE_NULL;
//
// Now convert the port to a number
//
Status = UlUnicodeToULongLong(pPort, 10, &PortNum);
pEnd[0] = L'/';
if (NT_SUCCESS(Status) == FALSE)
goto end;
if (PortNum > 0xffff || PortNum == 0)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// return the clean url
//
*ppUrl = pNewUrl;
Status = STATUS_SUCCESS;
end:
if (NT_SUCCESS(Status) == FALSE)
{
if (pNewUrl != NULL)
{
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
pNewUrl = NULL;
}
}
RETURN(Status);
}
/***************************************************************************++
Routine Description:
walks the tree and find's a matching entry for pUrl. 2 output options,
you can get the entry back, or a computed URL_INFO with inheritence applied.
you must free the URL_INFO from NonPagedPool.
Arguments:
IN PUL_CG_URL_TREE_ENTRY pEntry, the top of the tree
IN PWSTR pNextToken, where to start looking under
the tree
IN OUT PUL_URL_CONFIG_GROUP_INFO * ppInfo, [optional] the info to set,
might have to grow it.
OUT PUL_CG_URL_TREE_ENTRY * ppEntry [optional] returns the found
entry
Return Value:
NTSTATUS - Completion status.
STATUS_OBJECT_NAME_NOT_FOUND no entry found
--***************************************************************************/
NTSTATUS
UlpTreeFindNodeWalker(
IN PUL_CG_URL_TREE_ENTRY pEntry,
IN PWSTR pNextToken,
IN OUT PUL_URL_CONFIG_GROUP_INFO pInfo OPTIONAL,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry OPTIONAL
)
{
NTSTATUS Status;
PWSTR pToken = NULL;
ULONG TokenLength;
PUL_CG_URL_TREE_ENTRY pMatchEntry;
ULONG Index;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
ASSERT(pNextToken != NULL);
ASSERT(pInfo != NULL || ppEntry != NULL);
//
// walk the site tree looking for more matches
//
pMatchEntry = NULL;
Status = STATUS_OBJECT_NAME_NOT_FOUND;
while (TRUE)
{
//
// A bonafide match?
//
if (pEntry->FullUrl == 1)
{
pMatchEntry = pEntry;
if (pInfo != NULL)
{
Status = UlpSetUrlInfo(pInfo, pMatchEntry);
if (NT_SUCCESS(Status) == FALSE)
goto end;
}
}
//
// Are we already at the end of the url?
//
if (pNextToken == NULL || *pNextToken == UNICODE_NULL)
break;
//
// find the next token
//
pToken = pNextToken;
pNextToken = wcschr(pNextToken, L'/');
//
// can be null if this is a leaf
//
if (pNextToken != NULL)
{
//
// replace the '/' with a null, we'll fix it later
//
pNextToken[0] = UNICODE_NULL;
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
pNextToken += 1;
}
else
{
TokenLength = wcslen(pToken) * sizeof(WCHAR);
}
//
// match?
//
Status = UlpTreeBinaryFindEntry(
pEntry->pChildren,
pToken,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index
);
if (pNextToken != NULL)
{
//
// Fix the string, i replaced the '/' with a UNICODE_NULL
//
(pNextToken-1)[0] = L'/';
}
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
//
// it's a sorted tree, the first non-match means we're done
//
break;
}
//
// other error?
//
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// found a match, look for deeper matches.
//
pEntry = pEntry->pChildren->pEntries[Index].pEntry;
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
}
//
// did we find a match?
//
if (pMatchEntry != NULL)
{
Status = STATUS_SUCCESS;
if (ppEntry != NULL)
{
*ppEntry = pMatchEntry;
}
}
else
{
ASSERT(Status == STATUS_OBJECT_NAME_NOT_FOUND || NT_SUCCESS(Status));
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
end:
return Status;
}
/***************************************************************************++
Routine Description:
walks the tree and find's a matching entry for pUrl. 2 output options,
you can get the entry back, or a computed URL_INFO with inheritence applied.
you must free the URL_INFO from NonPagedPool.
Arguments:
IN PWSTR pUrl, the entry to find
CheckWildcard Should we do a wildcard match?
pHttpConn [optional] If non-NULL, use IP of
server to find Node (if not found
on first pass). This search is done
prior to the Wildcard search.
OUT PUL_URL_CONFIG_GROUP_INFO * ppInfo, [optional] will be alloced
and generated.
OUT PUL_CG_URL_TREE_ENTRY * ppEntry [optional] returns the found
entry
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpTreeFindNode(
IN PWSTR pUrl,
IN BOOLEAN CheckWildcard,
IN PUL_HTTP_CONNECTION pHttpConn OPTIONAL,
OUT PUL_URL_CONFIG_GROUP_INFO pInfo OPTIONAL,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry OPTIONAL
)
{
NTSTATUS Status;
PWSTR pNextToken;
PUL_CG_URL_TREE_ENTRY pEntry;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL);
ASSERT(pInfo != NULL || ppEntry != NULL);
//
// get the site match
//
Status = UlpTreeFindSite(pUrl, &pNextToken, &pEntry, FALSE);
if (NT_SUCCESS(Status))
{
//
// does it exist in this tree?
//
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
}
if ( (Status == STATUS_OBJECT_NAME_NOT_FOUND) && (NULL != pHttpConn) )
{
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConn));
//
// Didn't find it in first try. See if there is a binding for
// the IP address & TCP Port on which the request was received.
//
Status = UlpTreeFindIpMatch(pUrl, pHttpConn, &pNextToken, &pEntry);
if (NT_SUCCESS(Status))
{
//
// and now check in the wildcard tree
//
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
}
}
if (Status == STATUS_OBJECT_NAME_NOT_FOUND && CheckWildcard)
{
//
// shoot, didn't find a match. let's check wildcards
//
Status = UlpTreeFindWildcardMatch(pUrl, &pNextToken, &pEntry);
if (NT_SUCCESS(Status))
{
//
// and now check in the wildcard tree
//
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
}
}
//
// all done.
//
return Status;
} // UlpTreeFindNode
/***************************************************************************++
Routine Description:
finds any matching wildcard site in g_pSites for pUrl.
Arguments:
IN PWSTR pUrl, the url to match
OUT PWSTR * ppNextToken, output's the next token after
matching the url
OUT PUL_CG_URL_TREE_ENTRY * ppEntry, returns the entry
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpTreeFindWildcardMatch(
IN PWSTR pUrl,
OUT PWSTR * ppNextToken,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
)
{
NTSTATUS Status;
PWSTR pNextToken;
ULONG TokenLength;
ULONG Index;
PWSTR pPortNum;
ULONG PortLength;
WCHAR WildSiteUrl[HTTPS_WILD_PREFIX_LENGTH + MAX_PORT_LENGTH + 1];
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL);
ASSERT(ppNextToken != NULL);
ASSERT(ppEntry != NULL);
//
// find the port # (colon location + 1 for https + 1 to skip the colon = + 2)
//
pPortNum = wcschr(pUrl + HTTP_PREFIX_COLON_INDEX + 2, L':');
if (pPortNum == NULL)
{
//
// ouch
//
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Skip the ':'
//
pPortNum += 1;
//
// find the trailing '/' after the port number
//
pNextToken = wcschr(pPortNum, '/');
if (pNextToken == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// HTTPS or HTTP?
//
if (pUrl[HTTP_PREFIX_COLON_INDEX] == L':')
{
RtlCopyMemory(
WildSiteUrl,
HTTP_WILD_PREFIX,
HTTP_WILD_PREFIX_LENGTH * sizeof(WCHAR)
);
PortLength = DIFF(pNextToken - pPortNum) * sizeof(WCHAR);
TokenLength = HTTP_WILD_PREFIX_LENGTH + PortLength;
ASSERT(TokenLength < (sizeof(WildSiteUrl)-sizeof(WCHAR)));
RtlCopyMemory(
&(WildSiteUrl[HTTP_WILD_PREFIX_LENGTH/sizeof(WCHAR)]),
pPortNum,
PortLength
);
WildSiteUrl[TokenLength/sizeof(WCHAR)] = UNICODE_NULL;
}
else
{
RtlCopyMemory(
WildSiteUrl,
HTTPS_WILD_PREFIX,
HTTPS_WILD_PREFIX_LENGTH * sizeof(WCHAR)
);
PortLength = DIFF(pNextToken - pPortNum) * sizeof(WCHAR);
TokenLength = HTTPS_WILD_PREFIX_LENGTH + PortLength;
ASSERT(TokenLength < (sizeof(WildSiteUrl)-sizeof(WCHAR)));
RtlCopyMemory(
&(WildSiteUrl[HTTPS_WILD_PREFIX_LENGTH/sizeof(WCHAR)]),
pPortNum,
PortLength
);
WildSiteUrl[TokenLength/sizeof(WCHAR)] = UNICODE_NULL;
}
//
// is there a wildcard entry?
//
Status = UlpTreeBinaryFindEntry(
g_pSites,
WildSiteUrl,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// return the spot right after the token we just ate
//
*ppNextToken = pNextToken + 1;
//
// and return the entry
//
*ppEntry = g_pSites->pEntries[Index].pEntry;
end:
if (NT_SUCCESS(Status) == FALSE)
{
*ppEntry = NULL;
*ppNextToken = NULL;
}
return Status;
} // UlpTreeFindWildcardMatch
/***************************************************************************++
Routine Description:
finds any matching wildcard site in g_pSites for pUrl.
ASSUMES that the pUrl ALWASY contains a port number and trailing slash,
e.g.:
http://<host>:<port>/
https://<host>:<port>/
http://<host>:<port>/foo/bar/banana.htm
https://<host>:<port>/foo/bar/
Arguments:
IN PWSTR pUrl, the url to match
pHttpConn HTTP Connection object to
get the Server's IP & Port.
CODEWORK: If TDI ever starts giving us back the LocalAddress by default,
we should change this param to be that address (since that's the only
reason we need the pHttpConn).
OUT PWSTR * ppNextToken, output's the next token after
matching the url
OUT PUL_CG_URL_TREE_ENTRY * ppEntry, returns the entry
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpTreeFindIpMatch(
IN PWSTR pUrl,
IN PUL_HTTP_CONNECTION pHttpConn,
OUT PWSTR * ppNextToken,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
)
{
NTSTATUS Status;
PWSTR pNextToken;
ULONG TokenLength; // Length, in bytes, of token, not counting the NULL
ULONG Index;
WCHAR IpSiteUrl[HTTPS_IP_PREFIX_LENGTH + MAX_ADDRESS_LENGTH + 1];
PWSTR pPortNum;
PWSTR pTmp;
TA_IP_ADDRESS RawAddress;
USHORT IpPortNum;
ULONG IpAddress;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL);
ASSERT(ppNextToken != NULL);
ASSERT(ppEntry != NULL);
//
// find the port # (colon location + 1 for https + 1 to skip the colon = + 2)
//
pPortNum = wcschr(pUrl + HTTP_PREFIX_COLON_INDEX + 2, L':');
if (pPortNum == NULL)
{
//
// ouch
//
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Skip the ':'
//
pPortNum += 1;
//
// find the trailing '/' after the port number
//
pNextToken = wcschr(pPortNum, '/');
if (pNextToken == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Build IP & Port URL
//
//
// HTTPS or HTTP?
//
if (pUrl[HTTP_PREFIX_COLON_INDEX] == L':')
{
// HTTP
RtlCopyMemory(
IpSiteUrl,
HTTP_IP_PREFIX,
HTTP_IP_PREFIX_LENGTH /* * sizeof(WCHAR) */
);
TokenLength = HTTP_IP_PREFIX_LENGTH;
}
else
{
// HTTPS
RtlCopyMemory(
IpSiteUrl,
HTTPS_IP_PREFIX,
HTTPS_IP_PREFIX_LENGTH /* * sizeof(WCHAR) */
);
TokenLength = HTTPS_IP_PREFIX_LENGTH;
}
//
// Add IP & PORT to url
//
ASSERT( IS_VALID_CONNECTION(pHttpConn->pConnection) );
IpAddress = pHttpConn->pConnection->LocalAddress;
IpPortNum = pHttpConn->pConnection->LocalPort;
TokenLength += HostAddressAndPortToStringW(
&(IpSiteUrl[TokenLength/sizeof(WCHAR)]),
IpAddress,
IpPortNum
);
//
// Finally, is there an IP & PORT entry?
//
Status = UlpTreeFindEntry(
g_pSites,
IpSiteUrl,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index,
NULL
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// return the spot right after the token we just ate
//
*ppNextToken = pNextToken + 1;
//
// and return the entry
//
*ppEntry = g_pSites->pEntries[Index].pEntry;
end:
if (NT_SUCCESS(Status) == FALSE)
{
*ppEntry = NULL;
*ppNextToken = NULL;
}
return Status;
} // UlpTreeFindIpMatch
/***************************************************************************++
Routine Description:
finds the matching site in g_pSites for pUrl.
Arguments:
IN PWSTR pUrl, the url to match
OUT PWSTR * ppNextToken, output's the next token after
matching the url
OUT PUL_CG_URL_TREE_ENTRY * ppEntry, returns the entry
BOOLEAN AutoCreate should the entry be created?
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpTreeFindSite(
IN PWSTR pUrl,
OUT PWSTR * ppNextToken,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry,
OUT BOOLEAN AutoCreate
)
{
NTSTATUS Status;
PWSTR pToken;
PWSTR pNextToken = NULL;
ULONG TokenLength;
ULONG TokenHash;
ULONG Index;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL);
ASSERT(ppNextToken != NULL);
ASSERT(ppEntry != NULL);
//
// find the very first '/'
//
pToken = wcschr(pUrl, L'/');
if (pToken == NULL)
{
//
// ouch
//
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// skip the '/' and the trailing '/'
//
if (pToken[1] != L'/')
{
//
// ouch
//
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// skip the "//"
//
pToken += 2;
//
// Find the closing '/'
//
pNextToken = wcschr(pToken, L'/');
if (pNextToken == NULL)
{
//
// ouch
//
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// replace the '/' with a null, we'll fix it later
//
pNextToken[0] = UNICODE_NULL;
//
// Use the entire prefix as the site token
//
pToken = pUrl;
//
// CODEWORK longer than 64k token?
//
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
pNextToken += 1;
//
// find the matching site
//
if (AutoCreate)
{
Status = UlpTreeFindEntry(
g_pSites,
pToken,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index,
&TokenHash
);
//
// no match?
//
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
//
// autocreate the new site
//
Status = UlpTreeInsertEntry(
&g_pSites,
NULL,
pToken,
TokenLength,
TokenHash,
Index
);
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// start listening for this site
//
Status = UlAddSiteToEndpointList(pToken);
if (!NT_SUCCESS(Status))
{
NTSTATUS TempStatus;
//
// free the site we just created
//
TempStatus = UlpTreeFreeNode(g_pSites->pEntries[Index].pEntry);
ASSERT(NT_SUCCESS(TempStatus));
goto end;
}
}
else if (!NT_SUCCESS(Status))
{
//
// UlpTreeFindEntry returned an error other than
// "not found". Bail out.
//
goto end;
}
}
else
{
Status = UlpTreeBinaryFindEntry(
g_pSites,
pToken,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index
);
if (!NT_SUCCESS(Status))
{
goto end;
}
}
//
// set returns
//
*ppEntry = g_pSites->pEntries[Index].pEntry;
*ppNextToken = pNextToken;
end:
if (pNextToken != NULL)
{
//
// Fix the string, i replaced the '/' with a UNICODE_NULL
//
(pNextToken-1)[0] = L'/';
}
if (!NT_SUCCESS(Status))
{
*ppEntry = NULL;
*ppNextToken = NULL;
}
return Status;
}
/***************************************************************************++
Routine Description:
walks the sorted children array pHeader looking for a matching entry
for pToken.
Arguments:
IN PUL_CG_URL_TREE_HEADER pHeader, The children array to look in
IN PWSTR pToken, the token to look for
IN ULONG TokenLength, the length in char's of pToken
OUT ULONG * pIndex the found index. or if not found
the index of the place an entry
with pToken should be inserted.
Return Value:
NTSTATUS - Completion status.
STATUS_OBJECT_NAME_NOT_FOUND didn't find it
--***************************************************************************/
NTSTATUS
UlpTreeFindEntry(
IN PUL_CG_URL_TREE_HEADER pHeader OPTIONAL,
IN PWSTR pToken,
IN ULONG TokenLength,
IN ULONG TokenHash OPTIONAL,
OUT ULONG * pIndex,
OUT ULONG * pTokenHash OPTIONAL
)
{
NTSTATUS Status;
ULONG Index = 0;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pHeader == NULL || IS_VALID_TREE_HEADER(pHeader));
ASSERT(pIndex != NULL);
if (TokenLength == 0)
{
return STATUS_INVALID_PARAMETER;
}
//
// Assume we didn't find it
//
Status = STATUS_OBJECT_NAME_NOT_FOUND;
ASSERT(TokenLength > 0 && pToken != NULL && pToken[0] != UNICODE_NULL);
//
// compute the hash
//
if (TokenHash == HASH_INVALID_SIGNATURE)
{
TokenHash = HashRandomizeBits(HashStringNoCaseW(pToken, 0));
ASSERT(TokenHash != HASH_INVALID_SIGNATURE);
}
//
// any siblings to search through?
//
if (pHeader != NULL)
{
//
// Walk the sorted array looking for a match (linear)
//
//
// CODEWORK: make this a binary search!
//
for (Index = 0; Index < pHeader->UsedCount; Index++)
{
ASSERT(IS_VALID_TREE_ENTRY(pHeader->pEntries[Index].pEntry));
//
// How does the hash compare?
//
if (TokenHash == pHeader->pEntries[Index].TokenHash)
{
//
// and the length?
//
if (TokenLength == pHeader->pEntries[Index].pEntry->TokenLength)
{
//
// double check with a strcmp just for fun
//
int Temp = _wcsnicmp(
pToken,
pHeader->pEntries[Index].pEntry->pToken,
TokenLength/sizeof(WCHAR)
);
if (Temp > 0)
{
//
// larger , time to stop
//
break;
}
else if (Temp == 0)
{
// Found it
//
Status = STATUS_SUCCESS;
break;
}
//
// else if (Temp < 0) keep scanning,
//
}
else if (TokenLength > pHeader->pEntries[Index].pEntry->TokenLength)
{
//
// larger, time to stop
//
break;
}
}
else if (TokenHash > pHeader->pEntries[Index].TokenHash)
{
//
// larger, time to stop
//
break;
}
}
}
//
// return vals
//
*pIndex = Index;
if (pTokenHash != NULL)
{
*pTokenHash = TokenHash;
}
return Status;
}
/***************************************************************************++
Routine Description:
walks the sorted children array pHeader looking for a matching entry
for pToken.
Arguments:
IN PUL_CG_URL_TREE_HEADER pHeader, The children array to look in
IN PWSTR pToken, the token to look for
IN ULONG TokenLength, the length in char's of pToken
OUT ULONG * pIndex the found index. or if not found
the index of the place an entry
with pToken should be inserted.
Return Value:
NTSTATUS - Completion status.
STATUS_OBJECT_NAME_NOT_FOUND didn't find it
--***************************************************************************/
NTSTATUS
UlpTreeBinaryFindEntry(
IN PUL_CG_URL_TREE_HEADER pHeader OPTIONAL,
IN PWSTR pToken,
IN ULONG TokenLength,
IN ULONG TokenHash OPTIONAL,
OUT PULONG pIndex
)
{
NTSTATUS Status;
LONG Index = 0;
LONG StartIndex;
LONG EndIndex;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pHeader == NULL || IS_VALID_TREE_HEADER(pHeader));
ASSERT(pIndex != NULL);
if (TokenLength == 0)
{
return STATUS_INVALID_PARAMETER;
}
//
// Assume we didn't find it
//
Status = STATUS_OBJECT_NAME_NOT_FOUND;
ASSERT(TokenLength > 0 && pToken != NULL && pToken[0] != UNICODE_NULL);
//
// any siblings to search through?
//
if (pHeader != NULL)
{
//
// compute the hash
//
if (TokenHash == HASH_INVALID_SIGNATURE)
{
TokenHash = HashRandomizeBits(HashStringNoCaseW(pToken, 0));
ASSERT(TokenHash != HASH_INVALID_SIGNATURE);
}
//
// Walk the sorted array looking for a match (binary search)
//
StartIndex = 0;
EndIndex = pHeader->UsedCount - 1;
while (StartIndex <= EndIndex)
{
Index = (StartIndex + EndIndex) / 2;
ASSERT(IS_VALID_TREE_ENTRY(pHeader->pEntries[Index].pEntry));
//
// How does the hash compare?
//
if (TokenHash == pHeader->pEntries[Index].TokenHash)
{
//
// and the length?
//
if (TokenLength == pHeader->pEntries[Index].pEntry->TokenLength)
{
//
// double check with a strcmp just for fun
//
int Temp = _wcsnicmp(
pToken,
pHeader->pEntries[Index].pEntry->pToken,
TokenLength/sizeof(WCHAR)
);
if (Temp == 0)
{
// Found it
//
Status = STATUS_SUCCESS;
break;
}
else
{
if (Temp < 0)
{
//
// Adjust StartIndex forward.
//
StartIndex = Index + 1;
}
else
{
//
// Adjust EndIndex backward.
//
EndIndex = Index - 1;
}
}
}
else
{
if (TokenLength < pHeader->pEntries[Index].pEntry->TokenLength)
{
//
// Adjust StartIndex forward.
//
StartIndex = Index + 1;
}
else
{
//
// Adjust EndIndex backward.
//
EndIndex = Index - 1;
}
}
}
else
{
if (TokenHash < pHeader->pEntries[Index].TokenHash)
{
//
// Adjust StartIndex forward.
//
StartIndex = Index + 1;
}
else
{
//
// Adjust EndIndex backward.
//
EndIndex = Index - 1;
}
}
}
}
//
// return vals
//
*pIndex = Index;
return Status;
}
/***************************************************************************++
Routine Description:
inserts a new entry storing pToken as a child in the array ppHeader.
it will grow/allocate ppHeader as necessary.
Arguments:
IN OUT PUL_CG_URL_TREE_HEADER * ppHeader, the children array (might change)
IN PUL_CG_URL_TREE_ENTRY pParent, the parent to set this child to
IN PWSTR pToken, the token of the new entry
IN ULONG TokenLength, token length
IN ULONG Index the index to insert it at.
it will shuffle the array
if necessary.
Return Value:
NTSTATUS - Completion status.
STATUS_NO_MEMORY allocation failed
--***************************************************************************/
NTSTATUS
UlpTreeInsertEntry(
IN OUT PUL_CG_URL_TREE_HEADER * ppHeader,
IN PUL_CG_URL_TREE_ENTRY pParent OPTIONAL,
IN PWSTR pToken,
IN ULONG TokenLength,
IN ULONG TokenHash,
IN ULONG Index
)
{
NTSTATUS Status;
PUL_CG_URL_TREE_HEADER pHeader = NULL;
PUL_CG_URL_TREE_ENTRY pEntry = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(ppHeader != NULL);
ASSERT(pParent == NULL || IS_VALID_TREE_ENTRY(pParent));
ASSERT(pToken != NULL);
ASSERT(TokenLength > 0);
ASSERT(TokenHash != HASH_INVALID_SIGNATURE);
ASSERT(
(*ppHeader == NULL) ?
Index == 0 :
IS_VALID_TREE_HEADER(*ppHeader) && (Index <= (*ppHeader)->UsedCount)
);
pHeader = *ppHeader;
//
// any exiting siblings?
//
if (pHeader == NULL)
{
//
// allocate a sibling array
//
pHeader = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_CG_URL_TREE_HEADER,
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH,
UL_CG_TREE_HEADER_POOL_TAG
);
if (pHeader == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
RtlZeroMemory(
pHeader,
sizeof(UL_CG_URL_TREE_HEADER) +
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH
);
pHeader->Signature = UL_CG_TREE_HEADER_POOL_TAG;
pHeader->AllocCount = UL_CG_DEFAULT_TREE_WIDTH;
}
else if ((pHeader->UsedCount + 1) > pHeader->AllocCount)
{
PUL_CG_URL_TREE_HEADER pNewHeader;
//
// Grow a bigger array
//
pNewHeader = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_CG_URL_TREE_HEADER,
sizeof(UL_CG_HEADER_ENTRY) * (pHeader->AllocCount * 2),
UL_CG_TREE_HEADER_POOL_TAG
);
if (pNewHeader == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
RtlCopyMemory(
pNewHeader,
pHeader,
sizeof(UL_CG_URL_TREE_HEADER) +
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount
);
RtlZeroMemory(
((PUCHAR)pNewHeader) + sizeof(UL_CG_URL_TREE_HEADER) +
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount,
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount
);
pNewHeader->AllocCount *= 2;
pHeader = pNewHeader;
}
//
// Allocate an entry
//
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_CG_URL_TREE_ENTRY,
TokenLength + sizeof(WCHAR),
UL_CG_TREE_ENTRY_POOL_TAG
);
if (pEntry == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
RtlZeroMemory(
pEntry,
sizeof(UL_CG_URL_TREE_ENTRY) +
TokenLength + sizeof(WCHAR)
);
pEntry->Signature = UL_CG_TREE_ENTRY_POOL_TAG;
pEntry->pParent = pParent;
pEntry->TokenHash = TokenHash;
pEntry->TokenLength = TokenLength;
RtlCopyMemory(pEntry->pToken, pToken, TokenLength + sizeof(WCHAR));
//
// need to shuffle things around?
//
if (Index < pHeader->UsedCount)
{
//
// shift right the chunk at Index
//
RtlMoveMemory(
&(pHeader->pEntries[Index+1]),
&(pHeader->pEntries[Index]),
(pHeader->UsedCount - Index) * sizeof(UL_CG_HEADER_ENTRY)
);
}
pHeader->pEntries[Index].TokenHash = TokenHash;
pHeader->pEntries[Index].pEntry = pEntry;
pHeader->UsedCount += 1;
Status = STATUS_SUCCESS;
UlTrace(
CONFIG_GROUP_TREE, (
"http!UlpTreeInsertEntry('%S', %d) %S%S\n",
pToken,
Index,
(Index < (pHeader->UsedCount - 1)) ? L"[shifted]" : L"",
(*ppHeader == NULL) ? L"[alloc'd siblings]" : L""
)
);
end:
if (NT_SUCCESS(Status) == FALSE)
{
if (*ppHeader != pHeader && pHeader != NULL)
{
UL_FREE_POOL_WITH_SIG(pHeader, UL_CG_TREE_HEADER_POOL_TAG);
}
if (pEntry != NULL)
{
UL_FREE_POOL_WITH_SIG(pEntry, UL_CG_TREE_ENTRY_POOL_TAG);
}
}
else
{
//
// return a new buffer to the caller ?
//
if (*ppHeader != pHeader)
{
if (*ppHeader != NULL)
{
//
// free the old one
//
UL_FREE_POOL_WITH_SIG(*ppHeader, UL_CG_TREE_HEADER_POOL_TAG);
}
*ppHeader = pHeader;
}
}
return Status;
}
/***************************************************************************++
Routine Description:
Inserts pUrl into the url tree. returns the inserted entry.
Arguments:
IN PWSTR pUrl, the url to insert
OUT PUL_CG_URL_TREE_ENTRY * ppEntry the new entry
Return Value:
NTSTATUS - Completion status.
STATUS_ADDRESS_ALREADY_EXISTS this url is already in the tree
--***************************************************************************/
NTSTATUS
UlpTreeInsert(
IN PWSTR pUrl,
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
)
{
NTSTATUS Status;
PWSTR pToken;
PWSTR pNextToken;
ULONG TokenLength;
ULONG TokenHash;
PUL_CG_URL_TREE_ENTRY pEntry = NULL;
ULONG Index;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(g_pSites != NULL);
ASSERT(ppEntry != NULL);
ASSERT(pUrl != NULL && pUrl[0] != UNICODE_NULL);
UlTrace(CONFIG_GROUP_TREE, ("http!UlpTreeInsert('%S')\n", pUrl));
//
// get the site match
//
Status = UlpTreeFindSite(pUrl, &pNextToken, &pEntry, TRUE);
if (NT_SUCCESS(Status) == FALSE)
goto end;
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// any abs_path to add also?
//
while (pNextToken != NULL && pNextToken[0] != UNICODE_NULL)
{
pToken = pNextToken;
pNextToken = wcschr(pNextToken, L'/');
//
// can be null if this is a leaf
//
if (pNextToken != NULL)
{
pNextToken[0] = UNICODE_NULL;
// CODEWORK, longer than 64k token?
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
pNextToken += 1;
}
else
{
TokenLength = wcslen(pToken) * sizeof(WCHAR);
}
//
// insert this token as a child
//
Status = UlpTreeFindEntry(
pEntry->pChildren,
pToken,
TokenLength,
HASH_INVALID_SIGNATURE,
&Index,
&TokenHash
);
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
//
// no match, let's add this new one
//
Status = UlpTreeInsertEntry(
&pEntry->pChildren,
pEntry,
pToken,
TokenLength,
TokenHash,
Index
);
}
if (pNextToken != NULL)
{
//
// fixup the UNICODE_NULL from above
//
(pNextToken-1)[0] = L'/';
}
if (NT_SUCCESS(Status) == FALSE)
goto end;
//
// dive down deeper !
//
pEntry = pEntry->pChildren->pEntries[Index].pEntry;
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// loop!
//
}
//
// Are we actually creating anything?
//
if (pEntry->FullUrl == 1)
{
//
// nope, a dup. bail.
//
Status = STATUS_ADDRESS_ALREADY_EXISTS;
goto end;
}
//
// at the leaf node, set our leaf marks
//
pEntry->FullUrl = 1;
//
// return the entry
//
*ppEntry = pEntry;
//
// all done
//
Status = STATUS_SUCCESS;
end:
if (NT_SUCCESS(Status) == FALSE)
{
//
// Something failed. Need to clean up the partial branch
//
if (pEntry != NULL && pEntry->FullUrl == 0)
{
NTSTATUS TempStatus;
TempStatus = UlpTreeFreeNode(pEntry);
ASSERT(NT_SUCCESS(TempStatus));
}
}
return Status;
}
/***************************************************************************++
Routine Description:
init code. not re-entrant.
Arguments:
none.
Return Value:
NTSTATUS - Completion status.
STATUS_NO_MEMORY allocation failed
--***************************************************************************/
NTSTATUS
UlInitializeCG(
VOID
)
{
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( g_InitCGCalled == FALSE );
if (g_InitCGCalled == FALSE)
{
//
// init our globals
//
//
// Alloc our site array
//
g_pSites = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_CG_URL_TREE_HEADER,
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH,
UL_CG_TREE_HEADER_POOL_TAG
);
if (g_pSites == NULL)
return STATUS_NO_MEMORY;
RtlZeroMemory(
g_pSites,
sizeof(UL_CG_URL_TREE_HEADER) +
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH
);
g_pSites->Signature = UL_CG_TREE_HEADER_POOL_TAG;
g_pSites->AllocCount = UL_CG_DEFAULT_TREE_WIDTH;
//
// init our non-paged entries
//
Status = UlInitializeResource(
&(g_pUlNonpagedData->ConfigGroupResource),
"ConfigGroupResource",
0,
UL_CG_RESOURCE_TAG
);
if (NT_SUCCESS(Status) == FALSE)
{
UL_FREE_POOL_WITH_SIG(g_pSites, UL_CG_TREE_HEADER_POOL_TAG);
return Status;
}
g_InitCGCalled = TRUE;
}
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
termination code
Arguments:
none.
Return Value:
none.
--***************************************************************************/
VOID
UlTerminateCG(
VOID
)
{
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
if (g_InitCGCalled)
{
Status = UlDeleteResource(&(g_pUlNonpagedData->ConfigGroupResource));
ASSERT(NT_SUCCESS(Status));
if (g_pSites != NULL)
{
ASSERT( g_pSites->UsedCount == 0 );
//
// Nuke the header.
//
UL_FREE_POOL_WITH_SIG(
g_pSites,
UL_CG_TREE_HEADER_POOL_TAG
);
}
//
// The tree should be gone, all handles have been closed
//
ASSERT(g_pSites == NULL || g_pSites->UsedCount == 0);
g_InitCGCalled = FALSE;
}
}
/***************************************************************************++
Routine Description:
creates a new config group and returns the id
Arguments:
OUT PUL_CONFIG_GROUP_ID pConfigGroupId returns the new id
Return Value:
NTSTATUS - Completion status.
STATUS_NO_MEMORY allocation failed
--***************************************************************************/
NTSTATUS
UlCreateConfigGroup(
IN PUL_CONTROL_CHANNEL pControlChannel,
OUT PHTTP_CONFIG_GROUP_ID pConfigGroupId
)
{
PUL_CONFIG_GROUP_OBJECT pNewObject = NULL;
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pControlChannel != NULL);
ASSERT(pConfigGroupId != NULL);
UlTrace(CONFIG_GROUP_FNC, ("http!UlCreateConfigGroup\n"));
//
// Create an empty config group object structure - PAGED
//
Status = UlpCreateConfigGroupObject(&pNewObject);
if (!NT_SUCCESS(Status)) {
goto end;
}
//
// Link it into the control channel
//
UlAddNotifyEntry(
&pControlChannel->ConfigGroupHead,
&pNewObject->HandleEntry
);
//
// remember the control channel
//
pNewObject->pControlChannel = pControlChannel;
//
// Return the new id.
//
*pConfigGroupId = pNewObject->ConfigGroupId;
end:
if (!NT_SUCCESS(Status))
{
//
// Something failed. Let's clean up.
//
HTTP_SET_NULL_ID(pConfigGroupId);
if (pNewObject != NULL)
{
UlDeleteConfigGroup(pNewObject->ConfigGroupId);
}
}
return Status;
}
/***************************************************************************++
Routine Description:
returns the config group id that matches the config group object linked
in list_entry
Arguments:
IN PLIST_ENTRY pControlChannelEntry - the listentry for this config group
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
HTTP_CONFIG_GROUP_ID
UlConfigGroupFromListEntry(
IN PLIST_ENTRY pControlChannelEntry
)
{
PUL_CONFIG_GROUP_OBJECT pObject;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pControlChannelEntry != NULL);
pObject = CONTAINING_RECORD(
pControlChannelEntry,
UL_CONFIG_GROUP_OBJECT,
ControlChannelEntry
);
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
return pObject->ConfigGroupId;
}
/***************************************************************************++
Routine Description:
deletes the config group ConfigGroupId cleaning all of it's urls.
Arguments:
IN HTTP_CONFIG_GROUP_ID ConfigGroupId the group to delete
Return Value:
NTSTATUS - Completion status.
STATUS_INVALID_PARAMETER bad config group id
--***************************************************************************/
NTSTATUS
UlDeleteConfigGroup(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pObject;
//
// Sanity check.
//
PAGED_CODE();
UlTrace(CONFIG_GROUP_FNC, ("http!UlDeleteConfigGroup\n"));
CG_LOCK_WRITE();
//
// Get ConfigGroup from opaque id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
);
if (pObject == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
HTTP_SET_NULL_ID(&(pObject->ConfigGroupId));
//
// Drop the extra reference as a result of the successful get
//
DEREFERENCE_CONFIG_GROUP(pObject);
//
// Unlink it from the control channel and parent
//
UlRemoveNotifyEntry(&pObject->HandleEntry);
UlRemoveNotifyEntry(&pObject->ParentEntry);
//
// let the control channel go
//
pObject->pControlChannel = NULL;
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
//
// unlink any transient urls below us
//
UlNotifyAllEntries(
UlNotifyOrphanedConfigGroup,
&pObject->ChildHead,
NULL
);
//
// Unlink all of the url's in the config group
//
Status = UlpCleanAllUrls(pObject);
//
// let the error fall through ....
//
//
// remove the opaque id and its reference
//
UlFreeOpaqueId(ConfigGroupId, UlOpaqueIdTypeConfigGroup);
DEREFERENCE_CONFIG_GROUP(pObject);
//
// all done
//
end:
CG_UNLOCK_WRITE();
return Status;
} // UlDeleteConfigGroup
/***************************************************************************++
Routine Description:
Addref's the config group object
Arguments:
pConfigGroup - the object to add ref
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
VOID
UlReferenceConfigGroup(
IN PVOID pObject
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG refCount;
PUL_CONFIG_GROUP_OBJECT pConfigGroup = (PUL_CONFIG_GROUP_OBJECT) pObject;
//
// Sanity check.
//
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
refCount = InterlockedIncrement(&pConfigGroup->RefCount);
WRITE_REF_TRACE_LOG(
g_pConfigGroupTraceLog,
REF_ACTION_REFERENCE_CONFIG_GROUP,
refCount,
pConfigGroup,
pFileName,
LineNumber
);
UlTrace(
REFCOUNT, (
"http!UlReferenceConfigGroup cgroup=%p refcount=%d\n",
pConfigGroup,
refCount)
);
} // UlReferenceConfigGroup
/***************************************************************************++
Routine Description:
Releases the config group object
Arguments:
pConfigGroup - the object to deref
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
VOID
UlDereferenceConfigGroup(
PUL_CONFIG_GROUP_OBJECT pConfigGroup
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG refCount;
//
// Sanity check.
//
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
refCount = InterlockedDecrement( &pConfigGroup->RefCount );
WRITE_REF_TRACE_LOG(
g_pConfigGroupTraceLog,
REF_ACTION_DEREFERENCE_CONFIG_GROUP,
refCount,
pConfigGroup,
pFileName,
LineNumber
);
UlTrace(
REFCOUNT, (
"http!UlDereferenceConfigGroup cgroup=%p refcount=%d\n",
pConfigGroup,
refCount)
);
if (refCount == 0)
{
//
// now it's time to free the object
//
// If OpaqueId is non-zero, then refCount should not be zero
ASSERT(HTTP_IS_NULL_ID(&pConfigGroup->ConfigGroupId));
#if INVESTIGATE_LATER
//
// Release the opaque id
//
UlFreeOpaqueId(pConfigGroup->ConfigGroupId, UlOpaqueIdTypeConfigGroup);
#endif
//
// Release the app pool
//
if (pConfigGroup->AppPoolFlags.Present == 1)
{
if (pConfigGroup->pAppPool != NULL)
{
DEREFERENCE_APP_POOL(pConfigGroup->pAppPool);
pConfigGroup->pAppPool = NULL;
}
pConfigGroup->AppPoolFlags.Present = 0;
}
//
// Release the autoresponse buffer
//
if (pConfigGroup->pAutoResponse != NULL)
{
UL_DEREFERENCE_INTERNAL_RESPONSE(pConfigGroup->pAutoResponse);
pConfigGroup->pAutoResponse = NULL;
}
//
// Release the entire object
//
if (pConfigGroup->LoggingConfig.Flags.Present &&
pConfigGroup->LoggingConfig.LogFileDir.Buffer != NULL )
{
UL_FREE_POOL(
pConfigGroup->LoggingConfig.LogFileDir.Buffer,
UL_CG_LOGDIR_POOL_TAG );
if ( pConfigGroup->pLogFileEntry )
UlRemoveLogFileEntry( pConfigGroup->pLogFileEntry );
}
else
{
ASSERT( NULL == pConfigGroup->pLogFileEntry );
}
//
// Remove any qos flows for this site. This settings should
// only exists for the root app's cgroup.
//
if (pConfigGroup->MaxBandwidth.Flags.Present &&
pConfigGroup->MaxBandwidth.MaxBandwidth != HTTP_LIMIT_INFINITE )
{
UlTcRemoveFlowsForSite( pConfigGroup );
}
ASSERT(IsListEmpty(&pConfigGroup->FlowListHead));
// Deref the connection limit stuff
if (pConfigGroup->pConnectionCountEntry)
{
DEREFERENCE_CONNECTION_COUNT_ENTRY(pConfigGroup->pConnectionCountEntry);
}
// deref Site Counters object
if (pConfigGroup->pSiteCounters)
{
DEREFERENCE_SITE_COUNTER_ENTRY(pConfigGroup->pSiteCounters);
}
UL_FREE_POOL_WITH_SIG(pConfigGroup, UL_CG_OBJECT_POOL_TAG);
}
} // UlDereferenceConfigGroup
/***************************************************************************++
Routine Description:
adds pUrl to the config group ConfigGroupId.
Arguments:
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, the cgroup id
IN PUNICODE_STRING pUrl, the url. must be null terminated.
IN HTTP_URL_CONTEXT UrlContext the context to associate
Return Value:
NTSTATUS - Completion status.
STATUS_INVALID_PARAMETER bad config group id
--***************************************************************************/
NTSTATUS
UlAddUrlToConfigGroup(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
IN PUNICODE_STRING pUrl,
IN HTTP_URL_CONTEXT UrlContext
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
PUL_CG_URL_TREE_ENTRY pEntry;
PUL_CG_URL_TREE_ENTRY pParent;
PWSTR pNewUrl = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL && pUrl->Length > 0 && pUrl->Buffer != NULL);
ASSERT(pUrl->Buffer[pUrl->Length / sizeof(WCHAR)] == UNICODE_NULL);
UlTrace(CONFIG_GROUP_FNC,
("http!UlAddUrlToConfigGroup('%S' -> %d)\n", pUrl->Buffer, ConfigGroupId));
//
// Clean up the url
//
Status = UlpSanitizeUrl(pUrl, &pNewUrl);
if (NT_SUCCESS(Status) == FALSE)
{
//
// no goto end, resource not grabbed
//
RETURN(Status);
}
CG_LOCK_WRITE();
//
// Get the object ptr from id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)(
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
)
);
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// insert it into the tree
//
Status = UlpTreeInsert(pNewUrl, &pEntry);
if (NT_SUCCESS(Status) == FALSE)
goto end;
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// context associated with this url
//
pEntry->UrlContext = UrlContext;
//
// link the cfg group + the url
//
ASSERT(pEntry->pConfigGroup == NULL);
ASSERT(pEntry->ConfigGroupListEntry.Flink == NULL);
pEntry->pConfigGroup = pObject;
InsertTailList(&pObject->UrlListHead, &pEntry->ConfigGroupListEntry);
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
//
// Make sure to also mark a parent dirty .
// need to search for a non-dummy parent
//
// pParent can be NULL for site entries
//
//
pParent = pEntry->pParent;
while (pParent != NULL)
{
ASSERT(IS_VALID_TREE_ENTRY(pParent));
//
// a non-dummy?
//
if (pParent->FullUrl == 1)
{
ASSERT(pParent->pConfigGroup != NULL);
ASSERT(IS_VALID_CONFIG_GROUP(pParent->pConfigGroup));
//
// 1 is enough
//
break;
}
//
// walk higher, keep looking for a non-dummy
//
pParent = pParent->pParent;
}
end:
if (pObject != NULL)
{
DEREFERENCE_CONFIG_GROUP(pObject);
pObject = NULL;
}
CG_UNLOCK_WRITE();
if (pNewUrl != NULL)
{
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
pNewUrl = NULL;
}
RETURN(Status);
} // UlAddUrlToConfigGroup
/***************************************************************************++
Routine Description:
removes pUrl from the url tree (and thus the config group) .
Arguments:
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, the cgroup id. ignored.
IN PUNICODE_STRING pUrl, the url. must be null terminated.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlRemoveUrlFromConfigGroup(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
IN PUNICODE_STRING pUrl
)
{
NTSTATUS Status;
PUL_CG_URL_TREE_ENTRY pEntry;
PWSTR pNewUrl = NULL;
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL && pUrl->Buffer != NULL && pUrl->Length > 0);
UlTrace(CONFIG_GROUP_FNC, ("http!UlRemoveUrlFromConfigGroup\n"));
//
// Cleanup the passed in url
//
Status = UlpSanitizeUrl(pUrl, &pNewUrl);
if (!NT_SUCCESS(Status))
{
//
// no goto end, resource not grabbed
//
return Status;
}
//
// grab the lock
//
CG_LOCK_WRITE();
//
// Get the object ptr from id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)(
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
)
);
if (!IS_VALID_CONFIG_GROUP(pObject))
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Lookup the entry in the tree
//
Status = UlpTreeFindNode(pNewUrl, FALSE, NULL, NULL, &pEntry);
if (!NT_SUCCESS(Status)) {
goto end;
}
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// Does this tree entry match this config group?
//
if (pEntry->pConfigGroup != pObject)
{
Status = STATUS_INVALID_OWNER;
goto end;
}
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
//
// Everything looks good, free the node!
//
Status = UlpTreeFreeNode(pEntry);
if (!NT_SUCCESS(Status)) {
goto end;
}
//
// NOTE: don't do any more cleanup here... put it in freenode.
// otherwise it won't get cleaned on handle closes.
//
end:
if (pObject != NULL)
{
DEREFERENCE_CONFIG_GROUP(pObject);
pObject = NULL;
}
CG_UNLOCK_WRITE();
if (pNewUrl != NULL)
{
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
pNewUrl = NULL;
}
return Status;
} // UlRemoveUrlFromConfigGroup
/***************************************************************************++
Routine Description:
Removes all URLS from the config group.
Arguments:
ConfigGroupId - Supplies the config group ID.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlRemoveAllUrlsFromConfigGroup(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
//
// Sanity check.
//
PAGED_CODE();
UlTrace(CONFIG_GROUP_FNC, ("http!UlRemoveAllUrlsFromConfigGroup\n"));
//
// grab the lock
//
CG_LOCK_WRITE();
//
// Get the object ptr from id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)(
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
)
);
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
//
// Clean it.
//
Status = UlpCleanAllUrls( pObject );
end:
if (pObject != NULL)
{
DEREFERENCE_CONFIG_GROUP(pObject);
pObject = NULL;
}
CG_UNLOCK_WRITE();
return Status;
} // UlRemoveAllUrlsFromConfigGroup
/***************************************************************************++
Routine Description:
Adds a transiently registered URL to the processes special transient
registration config group. Requests matching this URL will be routed
to this pool.
Arguments:
pProcess - the apool process that will receive the requests
pUrl - the URL prefix to be mapped in
--***************************************************************************/
NTSTATUS
UlAddTransientUrl(
PUL_APP_POOL_OBJECT pAppPool,
PUNICODE_STRING pUrl
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
PWSTR pNewUrl = NULL;
PUL_CG_URL_TREE_ENTRY pEntry = NULL;
PUL_CG_URL_TREE_ENTRY pParent;
PUL_CONFIG_GROUP_OBJECT pParentObject;
//
// Sanity check
//
PAGED_CODE();
ASSERT(pAppPool);
ASSERT(pUrl);
UlTrace(CONFIG_GROUP_FNC, (
"http!UlAddTransientUrl(pAppPool = %p, Url '%S')\n",
pAppPool,
pUrl->Buffer
));
//
// Clean up the url
//
Status = UlpSanitizeUrl(pUrl, &pNewUrl);
if (!NT_SUCCESS(Status)) {
RETURN(Status);
}
//
// create a new config group
//
Status = UlpCreateConfigGroupObject(&pObject);
if (!NT_SUCCESS(Status)) {
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
RETURN(Status);
}
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
UlTrace(CONFIG_GROUP_FNC, (
"http!UlAddTransientUrl(pAppPool = %p, Url '%S')\n"
" Created pObject = %p\n",
pAppPool,
pUrl->Buffer,
pObject
));
//
// Set the app pool in the object
//
REFERENCE_APP_POOL(pAppPool);
pObject->AppPoolFlags.Present = 1;
pObject->pAppPool = pAppPool;
//
// lock the tree
//
CG_LOCK_WRITE();
//
// Add to the tree
//
Status = UlpTreeInsert(pNewUrl, &pEntry);
if (NT_SUCCESS(Status)) {
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
UlTrace(CONFIG_GROUP_FNC, (
"http!UlAddTransientUrl(pAppPool = %p, Url '%S')\n"
" Created pEntry = %p\n",
pAppPool,
pUrl->Buffer,
pEntry
));
//
// remember that this is a transient registration
//
pEntry->TransientUrl = 1;
//
// link the cfg group + the url
//
ASSERT(pEntry->pConfigGroup == NULL);
ASSERT(pEntry->ConfigGroupListEntry.Flink == NULL);
pEntry->pConfigGroup = pObject;
InsertTailList(&pObject->UrlListHead, &pEntry->ConfigGroupListEntry);
//
// Find parent config group
//
pParent = pEntry->pParent;
while (pParent && (pParent->TransientUrl || !pParent->FullUrl)) {
pParent = pParent->pParent;
}
if (pParent) {
pParentObject = pParent->pConfigGroup;
//
// check security
//
if (pParentObject->Security.Flags.Present) {
SECURITY_SUBJECT_CONTEXT SubjectContext;
BOOLEAN AccessOk;
PPRIVILEGE_SET pPrivileges = NULL;
ACCESS_MASK grantedAccess;
SeCaptureSubjectContext(&SubjectContext);
AccessOk = SeAccessCheck(
pParentObject->Security.pSecurityDescriptor,
&SubjectContext,
FALSE, // SubjectContextLocked
FILE_GENERIC_WRITE,
0, // PrevioslyGrantedAccess
&pPrivileges,
IoGetFileObjectGenericMapping(),
UserMode,
&grantedAccess,
&Status
);
SeReleaseSubjectContext(&SubjectContext);
} else {
//
// if there is no security we do not allow
// transient registration
//
Status = STATUS_ACCESS_DENIED;
}
if (NT_SUCCESS(Status)) {
//
// link the group to the app pool
//
UlAddNotifyEntry(
&pParentObject->ChildHead,
&pObject->ParentEntry
);
}
} else {
//
// couldn't find a valid parent. We don't allow
// transients without parents.
//
Status = STATUS_ACCESS_DENIED;
}
}
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
//
// Let go of the tree
//
CG_UNLOCK_WRITE();
if (NT_SUCCESS(Status)) {
//
// Link into app pool
//
UlLinkConfigGroupToAppPool(pObject, pAppPool);
} else {
//
// No static parent was found or we failed the
// access check. Clean up.
//
UlDeleteConfigGroup(pObject->ConfigGroupId);
}
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
RETURN(Status);
}
/***************************************************************************++
Routine Description:
Removes a transiently registered URL from the processes special transient
registration config group.
Arguments:
pProcess - the apool process that had the registration
pUrl - the URL prefix to be removed
--***************************************************************************/
NTSTATUS
UlRemoveTransientUrl(
PUL_APP_POOL_OBJECT pAppPool,
PUNICODE_STRING pUrl
)
{
NTSTATUS Status;
PUL_CG_URL_TREE_ENTRY pEntry;
PWSTR pNewUrl = NULL;
HTTP_CONFIG_GROUP_ID ConfigId = HTTP_NULL_ID;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pAppPool);
ASSERT(pUrl != NULL && pUrl->Buffer != NULL && pUrl->Length > 0);
UlTrace(CONFIG_GROUP_FNC, (
"http!UlRemoveTransientUrl(pAppPool = %p, Url '%S')\n",
pAppPool,
pUrl->Buffer
));
//
// Cleanup the passed in url
//
Status = UlpSanitizeUrl(pUrl, &pNewUrl);
if (!NT_SUCCESS(Status))
{
RETURN(Status);
}
//
// grab the lock
//
CG_LOCK_WRITE();
//
// Lookup the entry in the tree
//
Status = UlpTreeFindNode(pNewUrl, FALSE, NULL, NULL, &pEntry);
if (NT_SUCCESS(Status)) {
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
//
// Is it a transient URL for this app pool?
//
if (pEntry->TransientUrl &&
pEntry->pConfigGroup->AppPoolFlags.Present &&
pEntry->pConfigGroup->pAppPool == pAppPool)
{
ConfigId = pEntry->pConfigGroup->ConfigGroupId;
} else {
Status = STATUS_INVALID_OWNER;
}
}
CG_UNLOCK_WRITE();
//
// Everything looks good, free the group!
//
if (!HTTP_IS_NULL_ID(&ConfigId)) {
ASSERT(NT_SUCCESS(Status));
Status = UlDeleteConfigGroup(ConfigId);
}
if (pNewUrl != NULL)
{
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
pNewUrl = NULL;
}
RETURN(Status);
} // UlRemoveTransientUrl
/***************************************************************************++
Routine Description:
allows query information for cgroups. see uldef.h
Arguments:
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, cgroup id
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass, what to fetch
IN PVOID pConfigGroupInformation, output buffer
IN ULONG Length, length of pConfigGroupInformation
OUT PULONG pReturnLength OPTIONAL how much was copied into the
output buffer
Return Value:
NTSTATUS - Completion status.
STATUS_INVALID_PARAMETER bad cgroup id
STATUS_BUFFER_TOO_SMALL output buffer too small
STATUS_INVALID_PARAMETER invalid infoclass
--***************************************************************************/
NTSTATUS
UlQueryConfigGroupInformation(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass,
IN PVOID pConfigGroupInformation,
IN ULONG Length,
OUT PULONG pReturnLength OPTIONAL
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pConfigGroupInformation != NULL || pReturnLength != NULL);
ASSERT(pConfigGroupInformation == NULL || Length > 0);
//
// If no buffer is supplied, we are being asked to return the length needed
//
if (pConfigGroupInformation == NULL && pReturnLength == NULL)
return STATUS_INVALID_PARAMETER;
CG_LOCK_READ();
//
// Get the object ptr from id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)(
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
)
);
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// What are we being asked to do?
//
switch (InformationClass)
{
case HttpConfigGroupBandwidthInformation:
if (pConfigGroupInformation == NULL)
{
//
// return the size needed
//
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_MAX_BANDWIDTH);
}
else
{
if (Length < sizeof(HTTP_CONFIG_GROUP_MAX_BANDWIDTH))
{
Status = STATUS_BUFFER_TOO_SMALL;
goto end;
}
*((PHTTP_CONFIG_GROUP_MAX_BANDWIDTH)pConfigGroupInformation) = pObject->MaxBandwidth;
}
break;
case HttpConfigGroupConnectionInformation:
if (pConfigGroupInformation == NULL)
{
//
// return the size needed
//
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_MAX_CONNECTIONS);
}
else
{
if (Length < sizeof(HTTP_CONFIG_GROUP_MAX_CONNECTIONS))
{
Status = STATUS_BUFFER_TOO_SMALL;
goto end;
}
*((PHTTP_CONFIG_GROUP_MAX_CONNECTIONS)pConfigGroupInformation) = pObject->MaxConnections;
}
break;
case HttpConfigGroupStateInformation:
if (pConfigGroupInformation == NULL)
{
//
// return the size needed
//
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_STATE);
}
else
{
if (Length < sizeof(HTTP_CONFIG_GROUP_STATE))
{
Status = STATUS_BUFFER_TOO_SMALL;
goto end;
}
*((PHTTP_CONFIG_GROUP_STATE)pConfigGroupInformation) = pObject->State;
}
break;
case HttpConfigGroupConnectionTimeoutInformation:
if (pConfigGroupInformation == NULL)
{
//
// return the size needed
//
*pReturnLength = sizeof(ULONG);
}
else
{
*((ULONG *)pConfigGroupInformation) =
(ULONG)(pObject->ConnectionTimeout / C_NS_TICKS_PER_SEC);
}
break;
case HttpConfigGroupAppPoolInformation:
case HttpConfigGroupAutoResponseInformation:
case HttpConfigGroupSecurityInformation:
//
// this is illegal
//
default:
Status = STATUS_INVALID_PARAMETER;
goto end;
}
Status = STATUS_SUCCESS;
end:
if (pObject != NULL)
{
DEREFERENCE_CONFIG_GROUP(pObject);
pObject = NULL;
}
CG_UNLOCK_READ();
return Status;
} // UlQueryConfigGroupInformation
/***************************************************************************++
Routine Description:
allows you to set info for the cgroup. see uldef.h
Arguments:
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, cgroup id
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass, what to fetch
IN PVOID pConfigGroupInformation, input buffer
IN ULONG Length, length of pConfigGroupInformation
Return Value:
NTSTATUS - Completion status.
STATUS_INVALID_PARAMETER bad cgroup id
STATUS_BUFFER_TOO_SMALL input buffer too small
STATUS_INVALID_PARAMETER invalid infoclass
--***************************************************************************/
NTSTATUS
UlSetConfigGroupInformation(
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass,
IN PVOID pConfigGroupInformation,
IN ULONG Length
)
{
NTSTATUS Status = STATUS_SUCCESS;
PUL_CONFIG_GROUP_OBJECT pObject;
PHTTP_CONFIG_GROUP_LOGGING pLoggingInfo;
PHTTP_CONFIG_GROUP_MAX_BANDWIDTH pMaxBandwidth;
//
// Sanity check.
//
PAGED_CODE();
//
// no buffer?
//
if (pConfigGroupInformation == NULL)
return STATUS_INVALID_PARAMETER;
CG_LOCK_WRITE();
__try
{
//
// Get the object ptr from id
//
pObject = (PUL_CONFIG_GROUP_OBJECT)(
UlGetObjectFromOpaqueId(
ConfigGroupId,
UlOpaqueIdTypeConfigGroup,
UlReferenceConfigGroup
)
);
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
{
Status = STATUS_INVALID_PARAMETER;
__leave;
}
//
// What are we being asked to do?
//
switch (InformationClass)
{
case HttpConfigGroupAutoResponseInformation:
{
PHTTP_AUTO_RESPONSE pAutoResponse;
pAutoResponse = (PHTTP_AUTO_RESPONSE)pConfigGroupInformation;
//
// double check the buffer is big enough
//
if (Length < sizeof(HTTP_AUTO_RESPONSE))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
//
// Is there an auto-response?
//
// BUGBUG: temp optional
// if (pAutoResponse->Flags.Present == 1 && pAutoResponse->pResponse != NULL)
if (pAutoResponse->Flags.Present == 1)
{
HTTP_VERSION HttpVersion11 = {1,1};
//
// Are we replacing one already there?
//
if (pObject->pAutoResponse != NULL)
{
UL_DEREFERENCE_INTERNAL_RESPONSE(pObject->pAutoResponse);
pObject->pAutoResponse = NULL;
}
//
// Convert it to kernel mode
//
Status = UlCaptureHttpResponse(
pAutoResponse->pResponse,
NULL,
HttpVersion11,
HttpVerbUnknown,
pAutoResponse->EntityChunkCount,
pAutoResponse->pEntityChunks,
UlCaptureCopyData,
FALSE,
NULL,
&pObject->pAutoResponse
);
if (NT_SUCCESS(Status) == FALSE)
{
pObject->pAutoResponse = NULL;
__leave;
}
Status = UlPrepareHttpResponse(
HttpVersion11,
pAutoResponse->pResponse,
pObject->pAutoResponse
);
if (NT_SUCCESS(Status) == FALSE)
{
UL_DEREFERENCE_INTERNAL_RESPONSE(pObject->pAutoResponse);
pObject->pAutoResponse = NULL;
__leave;
}
}
else
{
//
// Remove ours?
//
if (pObject->pAutoResponse != NULL)
{
UL_DEREFERENCE_INTERNAL_RESPONSE(pObject->pAutoResponse);
}
pObject->pAutoResponse = NULL;
}
}
break;
case HttpConfigGroupAppPoolInformation:
{
PHTTP_CONFIG_GROUP_APP_POOL pAppPoolInfo;
PUL_APP_POOL_OBJECT pOldAppPool;
pAppPoolInfo = (PHTTP_CONFIG_GROUP_APP_POOL)pConfigGroupInformation;
//
// double check the buffer is big enough
//
if (Length < sizeof(HTTP_CONFIG_GROUP_APP_POOL))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
//
// remember the old app pool if there is one, so we can deref it
// if we need to
//
if (pObject->AppPoolFlags.Present == 1 && pObject->pAppPool != NULL)
{
pOldAppPool = pObject->pAppPool;
} else {
pOldAppPool = NULL;
}
if (pAppPoolInfo->Flags.Present == 1)
{
//
// ok, were expecting a handle to the file object for the app pool
//
// let's open it
//
Status = UlGetPoolFromHandle(
pAppPoolInfo->AppPoolHandle,
&pObject->pAppPool
);
if (NT_SUCCESS(Status) == FALSE)
__leave;
pObject->AppPoolFlags.Present = 1;
}
else
{
pObject->AppPoolFlags.Present = 0;
pObject->pAppPool = NULL;
}
//
// deref the old app pool
//
if (pOldAppPool) {
DEREFERENCE_APP_POOL(pOldAppPool);
}
}
break;
case HttpConfigGroupLogInformation:
{
//
// Sanity Check first
//
if (Length < sizeof(HTTP_CONFIG_GROUP_LOGGING))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
pLoggingInfo = (PHTTP_CONFIG_GROUP_LOGGING)pConfigGroupInformation;
if ( !IS_VALID_DIR_NAME(&pLoggingInfo->LogFileDir) )
{
//
// If the passed down Directory_Name is corrupted or
// abnormally big. Then reject it.
//
Status = STATUS_INVALID_PARAMETER;
__leave;
}
__try
{
if (pObject->LoggingConfig.Flags.Present)
{
//
// Check to see if log dir is correct
//
Status = UlCheckLogDirectory( &pLoggingInfo->LogFileDir );
if ( NT_SUCCESS(Status) )
{
//
// Log settings are being reconfigured
//
Status = UlReConfigureLogEntry(
pObject, // For the file entry
&pObject->LoggingConfig, // The old config
pLoggingInfo // The new config
);
}
//
// Otherwise keep the old settings
//
}
else
{
// Save to config group
pObject->LoggingConfig = *pLoggingInfo;
pObject->LoggingConfig.LogFileDir.Buffer =
(PWSTR) UL_ALLOCATE_ARRAY(
PagedPool,
UCHAR,
pLoggingInfo->LogFileDir.MaximumLength,
UL_CG_LOGDIR_POOL_TAG
);
if (pObject->LoggingConfig.LogFileDir.Buffer == NULL)
{
Status = STATUS_NO_MEMORY;
__leave;
}
RtlCopyMemory(
pObject->LoggingConfig.LogFileDir.Buffer,
pLoggingInfo->LogFileDir.Buffer,
pObject->LoggingConfig.LogFileDir.MaximumLength
);
pObject->LoggingConfig.Flags.Present = 1;
Status = UlCreateLog( pObject );
if ( !NT_SUCCESS(Status) )
{
//
// Clean up and return to logging not present state
//
UlTrace( LOGGING,
("UlSetConfigGroupInformation: directory %S failure %08lx\n",
pObject->LoggingConfig.LogFileDir.Buffer,
Status
));
UL_FREE_POOL(
pObject->LoggingConfig.LogFileDir.Buffer,
UL_CG_LOGDIR_POOL_TAG
);
pObject->LoggingConfig.LogFileDir.Buffer = NULL;
if ( pObject->pLogFileEntry )
UlRemoveLogFileEntry( pObject->pLogFileEntry );
pObject->pLogFileEntry = NULL;
pObject->LoggingConfig.Flags.Present = 0;
pObject->LoggingConfig.LoggingEnabled = FALSE;
}
}
}
__except( UL_EXCEPTION_FILTER() )
{
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status))
__leave;
// both cleared or both set
ASSERT(!pObject->LoggingConfig.LoggingEnabled
== !pObject->pLogFileEntry);
}
break;
case HttpConfigGroupBandwidthInformation:
{
//
// Sanity Check first
//
if (Length < sizeof(HTTP_CONFIG_GROUP_MAX_BANDWIDTH))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
pMaxBandwidth = (PHTTP_CONFIG_GROUP_MAX_BANDWIDTH) pConfigGroupInformation;
//
// Interpret the ZERO as HTTP_LIMIT_INFINITE
//
if (pMaxBandwidth->MaxBandwidth == 0)
{
pMaxBandwidth->MaxBandwidth = HTTP_LIMIT_INFINITE;
}
//
// But check to see if PSched is installed or not before proceeding.
// By returning an error here, WAS will raise an event warning but
// proceed w/o terminating the web server
//
if (!UlTcPSchedInstalled())
{
Status = STATUS_SUCCESS;
__leave;
#if 0
// Enable when WAS get updated to expect this error.
if (pMaxBandwidth->MaxBandwidth != HTTP_LIMIT_INFINITE)
{
// There's a BWT limit coming down but PSched is not installed
Status = STATUS_INVALID_DEVICE_REQUEST;
__leave;
}
else
{
// By default Config Store has HTTP_LIMIT_INFINITE. Therefore
// return success for non-actions to prevent unnecessary event
// warnings.
Status = STATUS_SUCCESS;
__leave;
}
#endif
}
//
// Create the flow if this is the first time we see Bandwidth set
// otherwise call reconfiguration for the existing flow. The case
// that the limit is infinite can be interpreted as BTW disabled
//
if (pObject->MaxBandwidth.Flags.Present &&
pObject->MaxBandwidth.MaxBandwidth != HTTP_LIMIT_INFINITE)
{
// To see if there is a real change
if (pMaxBandwidth->MaxBandwidth != pObject->MaxBandwidth.MaxBandwidth)
{
if (pMaxBandwidth->MaxBandwidth != HTTP_LIMIT_INFINITE)
{
// This will modify all the flows on all interfaces for this site
Status = UlTcModifyFlowsForSite(
pObject, // for this site
pMaxBandwidth->MaxBandwidth // the new bandwidth
);
if (!NT_SUCCESS(Status))
__leave;
}
else
{
// Handle BTW disabling by removing the existing flows
Status = UlTcRemoveFlowsForSite(pObject);
if (!NT_SUCCESS(Status))
__leave;
}
// Don't forget to update the cgroup object if it was a success
pObject->MaxBandwidth.MaxBandwidth = pMaxBandwidth->MaxBandwidth;
}
}
else
{
// Its about time to add a flow for a site entry.
if (pMaxBandwidth->MaxBandwidth != HTTP_LIMIT_INFINITE)
{
Status = UlTcAddFlowsForSite(
pObject,
pMaxBandwidth->MaxBandwidth
);
if (!NT_SUCCESS(Status))
__leave;
}
//
// Success! Remember the bandwidth limit inside the cgroup
//
pObject->MaxBandwidth = *pMaxBandwidth;
pObject->MaxBandwidth.Flags.Present = 1;
//
// When the last reference to this cgroup released, corresponding
// flows are going to be removed.Alternatively flows might be removed
// by explicitly setting the bandwidth throttling limit to infinite
// or reseting the flags.present.The latter case is handled above
// Look at the deref config group for the former.
//
}
}
break;
case HttpConfigGroupConnectionInformation:
if (Length < sizeof(HTTP_CONFIG_GROUP_MAX_CONNECTIONS))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
pObject->MaxConnections =
*((PHTTP_CONFIG_GROUP_MAX_CONNECTIONS)pConfigGroupInformation);
if (pObject->pConnectionCountEntry)
{
// Update
UlSetMaxConnections(
&pObject->pConnectionCountEntry->MaxConnections,
pObject->MaxConnections.MaxConnections
);
}
else
{
// Create
Status = UlCreateConnectionCountEntry(
pObject,
pObject->MaxConnections.MaxConnections
);
}
break;
case HttpConfigGroupStateInformation:
if (Length < sizeof(HTTP_CONFIG_GROUP_STATE))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
else
{
PHTTP_CONFIG_GROUP_STATE pCGState =
((PHTTP_CONFIG_GROUP_STATE) pConfigGroupInformation);
HTTP_ENABLED_STATE NewState = pCGState->State;
if ((NewState != HttpEnabledStateActive)
&& (NewState != HttpEnabledStateInactive))
{
Status = STATUS_INVALID_PARAMETER;
}
else
{
pObject->State = *pCGState;
UlTrace(ROUTING,
("UlSetConfigGroupInfo(StateInfo): obj=%p, "
"Flags.Present=%lu, State=%sactive.\n",
pObject,
(ULONG) pObject->State.Flags.Present,
(NewState == HttpEnabledStateActive) ? "" : "in"
));
}
}
break;
case HttpConfigGroupSecurityInformation:
{
PHTTP_CONFIG_GROUP_SECURITY pSecurity;
PSECURITY_DESCRIPTOR pSecurityDescriptor;
if (Length < sizeof(HTTP_CONFIG_GROUP_SECURITY))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
//
// get a copy of the new descriptor (do we need this step?)
//
pSecurity = (PHTTP_CONFIG_GROUP_SECURITY) pConfigGroupInformation;
if (pSecurity->Flags.Present && pSecurity->pSecurityDescriptor) {
Status = SeCaptureSecurityDescriptor(
pSecurity->pSecurityDescriptor,
UserMode,
PagedPool,
FALSE,
&pSecurityDescriptor
);
if (NT_SUCCESS(Status)) {
SECURITY_SUBJECT_CONTEXT SubjectContext;
SeCaptureSubjectContext(&SubjectContext);
SeLockSubjectContext(&SubjectContext);
//
// stuff it in the structure
//
Status = SeAssignSecurity(
NULL, // ParentDescriptor
pSecurityDescriptor,
&pSecurity->pSecurityDescriptor,
FALSE, // IsDirectoryObject
&SubjectContext,
IoGetFileObjectGenericMapping(),
PagedPool
);
SeUnlockSubjectContext(&SubjectContext);
SeReleaseSubjectContext(&SubjectContext);
ExFreePool(pSecurityDescriptor);
}
} else {
pSecurity->Flags.Present = 0;
pSecurityDescriptor = NULL;
}
//
// replace the old information
//
if (NT_SUCCESS(Status)) {
if (pObject->Security.Flags.Present) {
ASSERT(pObject->Security.pSecurityDescriptor);
ExFreePool(pObject->Security.pSecurityDescriptor);
}
pObject->Security = *pSecurity;
} else {
// return the error
__leave;
}
}
break;
case HttpConfigGroupSiteInformation:
{
PHTTP_CONFIG_GROUP_SITE pSite;
if (Length < sizeof(HTTP_CONFIG_GROUP_SITE))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
pSite = (PHTTP_CONFIG_GROUP_SITE)pConfigGroupInformation;
Status = UlCreateSiteCounterEntry(
pObject,
pSite->SiteId
);
}
break;
case HttpConfigGroupConnectionTimeoutInformation:
if (Length < sizeof(ULONG))
{
Status = STATUS_BUFFER_TOO_SMALL;
__leave;
}
else
{
LONGLONG Timeout;
Timeout = *((ULONG *)pConfigGroupInformation);
//
// Set the per site Connection Timeout limit override
//
pObject->ConnectionTimeout = Timeout * C_NS_TICKS_PER_SEC;
}
break;
default:
Status = STATUS_INVALID_PARAMETER;
__leave;
}
//
// flush the URI cache.
// CODEWORK: if we were smarter we could make this more granular
//
UlFlushCache();
Status = STATUS_SUCCESS;
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
}
if (pObject != NULL)
{
DEREFERENCE_CONFIG_GROUP(pObject);
pObject = NULL;
}
CG_UNLOCK_WRITE();
return Status;
} // UlSetConfigGroupInformation
/***************************************************************************++
Routine Description:
applies the inheritence, gradually setting the information from pMatchEntry
into pInfo. it only copies present info from pMatchEntry.
also updates the timestamp info in pInfo. there MUST be enough space for
1 more index prior to calling this function.
Arguments:
IN PUL_URL_CONFIG_GROUP_INFO pInfo, the place to set the info
IN PUL_CG_URL_TREE_ENTRY pMatchEntry the entry to use to set it
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpSetUrlInfo(
IN OUT PUL_URL_CONFIG_GROUP_INFO pInfo,
IN PUL_CG_URL_TREE_ENTRY pMatchEntry
)
{
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pInfo != NULL && IS_VALID_URL_CONFIG_GROUP_INFO(pInfo));
ASSERT(IS_VALID_TREE_ENTRY(pMatchEntry));
ASSERT(pMatchEntry->FullUrl == 1);
ASSERT(IS_VALID_CONFIG_GROUP(pMatchEntry->pConfigGroup));
//
// set the control channel. The current level might
// not have one (if it's transient), but in that
// case a parent should have had one.
//
if (pMatchEntry->pConfigGroup->pControlChannel) {
pInfo->pControlChannel = pMatchEntry->pConfigGroup->pControlChannel;
}
ASSERT(pInfo->pControlChannel);
if (pMatchEntry->pConfigGroup->AppPoolFlags.Present == 1)
{
if (pInfo->pAppPool != NULL)
{
//
// let go the one already there
//
DEREFERENCE_APP_POOL(pInfo->pAppPool);
}
pInfo->pAppPool = pMatchEntry->pConfigGroup->pAppPool;
REFERENCE_APP_POOL(pInfo->pAppPool);
}
//
// and the auto response
//
if (pMatchEntry->pConfigGroup->pAutoResponse != NULL)
{
if (pInfo->pAutoResponse != NULL)
{
//
// let go the one already there
//
UL_DEREFERENCE_INTERNAL_RESPONSE(pInfo->pAutoResponse);
}
pInfo->pAutoResponse = pMatchEntry->pConfigGroup->pAutoResponse;
UL_REFERENCE_INTERNAL_RESPONSE(pInfo->pAutoResponse);
}
else if (pInfo->pAutoResponse == NULL &&
pInfo->pControlChannel->pAutoResponse != NULL)
{
//
// set the one from the control channel
//
pInfo->pAutoResponse = pInfo->pControlChannel->pAutoResponse;
UL_REFERENCE_INTERNAL_RESPONSE(pInfo->pAutoResponse);
}
//
// url context
//
pInfo->UrlContext = pMatchEntry->UrlContext;
//
if (pMatchEntry->pConfigGroup->MaxBandwidth.Flags.Present == 1)
{
if (pInfo->pMaxBandwidth == NULL)
{
pInfo->pMaxBandwidth = pMatchEntry->pConfigGroup;
REFERENCE_CONFIG_GROUP(pInfo->pMaxBandwidth);
}
}
if (pMatchEntry->pConfigGroup->MaxConnections.Flags.Present == 1)
{
if (pInfo->pMaxConnections == NULL)
{
pInfo->pMaxConnections = pMatchEntry->pConfigGroup;
REFERENCE_CONFIG_GROUP(pInfo->pMaxConnections);
pInfo->pConnectionCountEntry = pMatchEntry->pConfigGroup->pConnectionCountEntry;
REFERENCE_CONNECTION_COUNT_ENTRY(pInfo->pConnectionCountEntry);
}
}
//
// Logging Info config can only be set from the Root App of
// the site. We do not need to keep updating it down the tree
// Therefore its update is slightly different.
//
if (pMatchEntry->pConfigGroup->LoggingConfig.Flags.Present == 1)
{
if (pInfo->pLoggingConfig == NULL)
{
pInfo->pLoggingConfig = pMatchEntry->pConfigGroup;
REFERENCE_CONFIG_GROUP(pInfo->pLoggingConfig);
}
}
//
// Site Counter Entry
//
if (pMatchEntry->pConfigGroup->pSiteCounters)
{
// the pSiteCounters entry will only be set on
// the "Site" ConfigGroup object.
if (NULL == pInfo->pSiteCounters)
{
UlTrace(PERF_COUNTERS,
("http!UlpSetUrlInfo: pSiteCounters %p set on pInfo %p for SiteId %d\n",
pMatchEntry->pConfigGroup->pSiteCounters,
pInfo,
pMatchEntry->pConfigGroup->pSiteCounters->Counters.SiteId
));
pInfo->pSiteCounters = pMatchEntry->pConfigGroup->pSiteCounters;
pInfo->SiteId = pInfo->pSiteCounters->Counters.SiteId;
REFERENCE_SITE_COUNTER_ENTRY(pInfo->pSiteCounters);
}
}
//
// Connection Timeout (100ns Ticks)
//
if ( pMatchEntry->pConfigGroup->ConnectionTimeout )
{
pInfo->ConnectionTimeout = pMatchEntry->pConfigGroup->ConnectionTimeout;
}
if (pMatchEntry->pConfigGroup->State.Flags.Present == 1)
{
if (pInfo->pCurrentState != NULL)
{
DEREFERENCE_CONFIG_GROUP(pInfo->pCurrentState);
}
pInfo->pCurrentState = pMatchEntry->pConfigGroup;
REFERENCE_CONFIG_GROUP(pInfo->pCurrentState);
//
// and a copy
//
pInfo->CurrentState = pInfo->pCurrentState->State.State;
}
UlTrace(
CONFIG_GROUP_TREE, (
"http!UlpSetUrlInfo: Matching entry(%S) points to cfg group(%p)\n",
pMatchEntry->pToken,
pMatchEntry->pConfigGroup
)
);
return STATUS_SUCCESS;
} // UlpSetUrlInfo
/***************************************************************************++
Routine Description:
walks the url tree and builds the URL_INFO.
Arguments:
IN PUNICODE_STRING pUrl, the url to fetch info for
OUT PUL_URL_CONFIG_GROUP_INFO * ppInfo the info. caller must free it.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlGetConfigGroupInfoForUrl(
IN PWSTR pUrl,
IN PUL_HTTP_CONNECTION pHttpConn,
OUT PUL_URL_CONFIG_GROUP_INFO pInfo
)
{
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pUrl != NULL);
ASSERT(pInfo != NULL);
UlTrace(CONFIG_GROUP_FNC, ("http!UlGetConfigGroupInfoForUrl(%S)\n", pUrl));
//
// grab the lock
//
CG_LOCK_READ();
Status = UlpTreeFindNode(pUrl, TRUE, pHttpConn, pInfo, NULL);
CG_UNLOCK_READ();
return Status;
}
/***************************************************************************++
Routine Description:
must be called to free the info buffer.
Arguments:
IN PUL_URL_CONFIG_GROUP_INFO pInfo the info to free
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpConfigGroupInfoRelease(
IN PUL_URL_CONFIG_GROUP_INFO pInfo
)
{
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pInfo != NULL);
UlTrace(CONFIG_GROUP_FNC, ("http!UlpConfigGroupInfoRelease(%p)\n", pInfo));
if (pInfo->pAppPool != NULL)
{
DEREFERENCE_APP_POOL(pInfo->pAppPool);
}
if (pInfo->pAutoResponse != NULL)
{
UL_DEREFERENCE_INTERNAL_RESPONSE(pInfo->pAutoResponse);
}
if (pInfo->pMaxBandwidth != NULL)
{
DEREFERENCE_CONFIG_GROUP(pInfo->pMaxBandwidth);
}
if (pInfo->pMaxConnections != NULL)
{
DEREFERENCE_CONFIG_GROUP(pInfo->pMaxConnections);
}
if (pInfo->pCurrentState != NULL)
{
DEREFERENCE_CONFIG_GROUP(pInfo->pCurrentState);
}
if (pInfo->pLoggingConfig != NULL)
{
DEREFERENCE_CONFIG_GROUP(pInfo->pLoggingConfig);
}
// UL_SITE_COUNTER_ENTRY
if (pInfo->pSiteCounters != NULL)
{
DEREFERENCE_SITE_COUNTER_ENTRY(pInfo->pSiteCounters);
}
if (pInfo->pConnectionCountEntry != NULL)
{
DEREFERENCE_CONNECTION_COUNT_ENTRY(pInfo->pConnectionCountEntry);
}
return STATUS_SUCCESS;
} // UlpConfigGroupInfoRelease
/***************************************************************************++
Routine Description:
Rough equivalent of the asignment operator for safely copying the
UL_URL_CONFIG_GROUP_INFO object and all of its contained pointers.
Arguments:
IN pOrigInfo the info to copy from
IN OUT pNewInfo the destination object
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlpConfigGroupInfoDeepCopy(
IN const PUL_URL_CONFIG_GROUP_INFO pOrigInfo,
IN OUT PUL_URL_CONFIG_GROUP_INFO pNewInfo
)
{
//
// Sanity check.
//
PAGED_CODE();
UlTrace(CONFIG_GROUP_FNC,
("http!UlpConfigGroupInfoDeepCopy(Orig: %p, New: %p)\n",
pOrigInfo,
pNewInfo
));
ASSERT( pOrigInfo != NULL && pNewInfo != NULL );
if (pOrigInfo->pAppPool != NULL)
{
REFERENCE_APP_POOL(pOrigInfo->pAppPool);
}
if (pOrigInfo->pAutoResponse != NULL)
{
UL_REFERENCE_INTERNAL_RESPONSE(pOrigInfo->pAutoResponse);
}
if (pOrigInfo->pMaxBandwidth != NULL)
{
REFERENCE_CONFIG_GROUP(pOrigInfo->pMaxBandwidth);
}
if (pOrigInfo->pMaxConnections != NULL)
{
REFERENCE_CONFIG_GROUP(pOrigInfo->pMaxConnections);
}
if (pOrigInfo->pCurrentState != NULL)
{
REFERENCE_CONFIG_GROUP(pOrigInfo->pCurrentState);
}
if (pOrigInfo->pLoggingConfig != NULL)
{
REFERENCE_CONFIG_GROUP(pOrigInfo->pLoggingConfig);
}
// UL_SITE_COUNTER_ENTRY
if (pOrigInfo->pSiteCounters != NULL)
{
REFERENCE_SITE_COUNTER_ENTRY(pOrigInfo->pSiteCounters);
}
if (pOrigInfo->pConnectionCountEntry != NULL)
{
REFERENCE_CONNECTION_COUNT_ENTRY(pOrigInfo->pConnectionCountEntry);
}
//
// Copy the old stuff over
//
RtlCopyMemory(
pNewInfo,
pOrigInfo,
sizeof(UL_URL_CONFIG_GROUP_INFO)
);
return STATUS_SUCCESS;
} // UlpConfigGroupInfoDeepCopy
/***************************************************************************++
Routine Description:
This function gets called when a static config group's control channel
goes away, or when a transient config group's app pool or static parent
goes away.
Deletes the config group.
Arguments:
pEntry - A pointer to HandleEntry or ParentEntry.
pHost - Pointer to the config group
pv - unused
--***************************************************************************/
BOOLEAN
UlNotifyOrphanedConfigGroup(
IN PUL_NOTIFY_ENTRY pEntry,
IN PVOID pHost,
IN PVOID pv
)
{
PUL_CONFIG_GROUP_OBJECT pObject = (PUL_CONFIG_GROUP_OBJECT) pHost;
//
// Sanity check
//
PAGED_CODE();
ASSERT(pEntry);
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
UlDeleteConfigGroup(pObject->ConfigGroupId);
return TRUE;
} // UlNotifyOrphanedConfigGroup
/***************************************************************************++
[design notes]
[url format]
url format = http[s]://[ ip-address|hostname|* :port-number/ [abs_path] ]
no escaping is allowed.
[caching cfg group info]
the tree was designed for quick lookup, but it is still costly to do the
traversal. so the cfg info for an url was designed to be cached and stored
in the actual response cache, which will be able to be directly hashed into.
this is the fast fast path.
in order to do this, the buffer for is allocated in non-paged pool. the
timestamp for each cfg group used to build the info (see building the url
info) is remembered in the returned struct. actually just indexes into the
global timestamp array are kept. the latest timestamp is then stored in the
struct itself.
later, if a cfg group is updated, the timestamp for that cfg group (in the
global array) is updated to current time.
if a request comes in, and we have a resposne cache hit, the driver will
check to see if it's cfg group info is stale. this simple requires scanning
the global timestamp array to see if any stamps are greater than the stamp
in the struct. this is not expensive . 1 memory lookup per level of url
depth.
this means that the passive mode code + dispatch mode code contend for the
timestamp spinlock. care is made to not hold this lock long. memory
allocs + frees are carefully moved outside the spinlocks.
care is also made as to the nature of tree updates + invalidating cached
data. to do this, the parent cfg group (non-dummy) is marked dirty. this
is to prevent the case that a new child cfg group is added that suddenly
affects an url it didn't effect before. image http://paul.com:80/ being
the only registered cfg group. a request comes in for /images/1.jpg.
the matching cfg group is that root one. later, a /images cfg group
is created. it now is a matching cfg group for that url sitting in the
response cache. thus needs to be invalidated.
[paging + irq]
the entire module assumes that it is called at IRQ==PASSIVE except for the
stale detection code.
this code accesses the timestamp array (see caching cfg group info) which
is stored in non-paged memory and synch'd with passive level access
using a spinlock.
[the tree]
the tree is made up of headers + entries. headers represent a group of entries
that share a parent. it's basically a length prefixed array of entries.
the parent pointer is in the entry not the header, as the entry does not really
now it's an element in an array.
entries have a pointer to a header that represents it's children. a header is
all of an entries children. headers don't link horizontally. they dyna-grow.
if you need to add a child to an entry, and his children header is full, boom.
gotta realloc to grow .
each node in the tree represents a token in a url, the things between the '/'
characters.
the entries in a header array are sorted. today they are sorted by their hash
value. this might change if the tokens are small enough, it's probably more
expensive to compute the hash value then just strcmp. the hash is 2bytes. the
token length is also 2bytes so no tokens greater than 32k.
i chose an array at first to attempt to get rid of right-left pointers. it
turned out to be futile as my array is an array of pointers. it has to be
an array of pointers as i grow + shrink it to keep it sorted. so i'm not
saving memory. however, as an array of pointers, it enables a binary search
as the array is sorted. this yields log(n) perf on a width search.
there are 2 types of entries in the tree. dummy entries and full url entries.
a full url is the leaf of an actual entry to UlAddUrl... dummy nodes are ones
that are there only to be parents to full url nodes.
dummy nodes have 2 ptrs + 2 ulongs.
full urls nodes have an extra 4 ptrs.
both store the actual token along with the entry. (unicode baby. 2 bytes per char).
at the top of the tree are sites. these are stored in a global header as siblings
in g_pSites. this can grow quite wide.
adding a full url entry creates the branch down to the entry.
deleting a full entry removes as far up the branch as possible without removing other
entries parents.
delete is also careful to not actually delete if other children exist. in this case
the full url entry is converted to a dummy node entry.
it was attempted to have big string url stored in the leaf node and the dummy nodes
simply point into this string for it's pToken. this doesn't work as the dummy nodes
pointers become invalid if the leaf node is later deleted. individual ownership of
tokens is needed to allow shared parents in the tree, with arbitrary node deletion.
an assumption throughout this code is that the tree is relatively static. changes
don't happen that often. basically inserts only on boot. and deletes only on
shutdown. this is why single clusters of siblings were chosen, as opposed to linked
clusters. children group width will remain fairly static meaning array growth is
rare.
[is it a graph or a tree?]
notice that the cfg groups are stored as a simple list with no relationships. its
the urls that are indexed with their relations.
so the urls build a tree, however do to the links from url to cfg group, it kind
of builds a graph also. 2 urls can be in the same cfg group. 1 url's child
can be in the same cfg group as the original url's parent.
when you focus on the actual url tree, it becomes less confusing. it really is a
tree. there can even be duplicates in the inheritence model, but the tree cleans
everything.
example of duplicates:
cgroup A = a/b + a/b/c/d
cgroup B = a/b/c
this walking the lineage branch for a/b/c/d you get A, then B, and A again. nodes
lower in the tree override parent values, so in this case A's values override B's .
[recursion]
[a sample tree]
server runs sites msw and has EVERY directory mapped to a cfg groups (really obscene)
<coming later>
[memory assumptions with the url tree]
[Paged]
a node per token in the url. 2 types. dummy nodes + leaf nodes.
(note: when 2 sizes are given, the second size is the sundown size)
dummy node = 4/6 longs
leaf node = 8/14 longs
+ each node holds 2 * TokenLength+1 for the token.
+ each cluster of siblings holds 2 longs + 1/2 longs per node in the cluster.
[NonPaged]
2 longs per config group
[assume]
sites
max 100k
average <10
assume 32 char hostname
max apps per site
in the max case : 2 (main + admin)
in the avg case : 10
max apps : 1000s (in 1 site)
assume 32 char app name
(assume just hostheader access for now)
[max]
hostname strings = 100000*((32+1)*2) = 6.6 MB
cluster entries = 100000*8*4 = 3.2 MB
cluster header = 1*(2+(1*100000))*4 = .4 MB
total = 10.2 MB
per site:
app strings = 2*((32+1)*2) = 132 B
cluster entries = 2*8*4 = 64 B
cluster header = 1*(2+(1*2))*4 = 16 B
= 132 + 64 + 16 = 212 B
total = 100000*(212) = 21.2 MB
Paged Total = 10.2mb + 21.2mb = 31.4 MB
NonPaged Total = 200k*2*4 = 1.6 MB
[avg]
hostname strings = 10*((32+1)*2) = 660 B
cluster entries = 10*8*4 = 320 B
cluster header = 1*(2+(1*10))*4 = 48 B
total = 1028 B
per site:
app strings = 10*((32+1)*2) = 660 B
cluster entries = 10*8*4 = 320 B
cluster header = 1*(2+(1*10))*4 = 48 B
total = 10*(1028) = 10.2 KB
Paged Total = 1028b + 10.2lKB = 11.3 KB
NonPaged Total = 110*2*4 = 880 B
note: can we save space by refcounting strings. if all of these
100k have apps with the same name, we save massive string space.
~13MB .
[efficiency of the tree]
[lookup]
[insert]
[delete]
[data locality]
[alterates investigated to the tree]
hashing - was pretty much scrapped due to the longest prefix match issue.
basically to hash, we would have to compute a hash for each level in
the url, to see if there is a match. then we have the complete list
of matches. assuming an additive hash, the hash expense could be
minimized, but the lookup still requires cycles.
alpha trie - was expense for memory. however the tree we have though
is very very similar to a trie. each level of the tree is a token,
however, not a letter.
[building the url info]
the url info is actually built walking down the tree. for each match
node, we set the info. this allows for the top-down inheritence.
we also snapshot the timestamp offsets for each matching node in the
tree as we dive down it (see caching) .
this dynamic building of an urls' match was chosen over precomputing
every possible config through the inheritence tree. each leaf node
could have stored a cooked version of this, but it would have made
updates a bear. it's not that expensive to build it while we walk down
as we already visit each node in our lineage branch.
autoresponses are never copied, but refcounted.
[locking]
2 locks are used in the module. a spinlock to protect the global
timestamp array, and a resource to protect both the cgroup objects
and the url tree (which links to the cgroup objects) .
the spinlock is sometimes acquired while the resource is locked,
but never vice-versa. also, while the spinlock is held, very
little is done, and definetly no other locks are contended.
1 issue here is granularity of contention. currently the entire
tree + object list is protected with 1 resource, which is reader
writer. if this is a perf issue, we can look into locking single
lineage branches (sites) .
[legal]
> 1 cfg groups in an app pool
children cfg groups with no app pool (they inherit)
children cfg groups with no auto-response (they inherit)
children cfg groups with no max bandwitdth (they inherit)
children cfg groups with no max connections (they inherit)
* for a host name
fully qualified url of for http[s]://[host|*]:post/[abs_path]/
must have trailing slash if not pting to a file
only 1 cfg group for the site AND the root url. e.g. http://foo.com:80/
allow you to set config on http:// and https:// for "root" config info
[not legal]
an url in > 1 cfg group
> 1 app pools for 1 cfg group
> 1 root cfg group
* embedded anywhere in the url other than replacing a hostname
query strings in url's.
url's not ending in slash.
--***************************************************************************/