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.
826 lines
24 KiB
826 lines
24 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 2000
|
|
//
|
|
// File: nopin.cpp
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
#include "pch.h"
|
|
#pragma hdrstop
|
|
|
|
#include "eventlog.h"
|
|
#include "nopin.h"
|
|
#include "strings.h"
|
|
#include "msg.h"
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// class CNoPinList
|
|
//--------------------------------------------------------------------------
|
|
CNoPinList::CNoPinList(
|
|
void
|
|
) : m_pRoot(NULL)
|
|
{
|
|
}
|
|
|
|
|
|
CNoPinList::~CNoPinList(
|
|
void
|
|
)
|
|
{
|
|
delete m_pRoot;
|
|
}
|
|
|
|
|
|
//
|
|
// Searches the tree for a COMPLETE path that is a subpath of
|
|
// pszPath. If found, pszPath specifies a file or folder
|
|
// that cannot be pinned.
|
|
//
|
|
// Returns:
|
|
// S_OK - Pinning is allowed.
|
|
// S_FALSE - Pinning is NOT allowed.
|
|
// NOPIN_E_BADPATH - Path is not a valid UNC.
|
|
//
|
|
HRESULT
|
|
CNoPinList::IsPinAllowed(
|
|
LPCTSTR pszPath
|
|
)
|
|
{
|
|
TraceEnter(TRACE_UTIL, "CNoPinList::IsPinAllowed");
|
|
TraceAssert(NULL != pszPath);
|
|
TraceAssert(::PathIsUNC(pszPath));
|
|
|
|
HRESULT hr = _Initialize();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = S_OK;
|
|
//
|
|
// A quick optimization is to see if the tree is empty.
|
|
// If it is, any file/folder may be pinned. This helps
|
|
// performance when no pinning restriction is in place.
|
|
//
|
|
TraceAssert(NULL != m_pRoot);
|
|
if (m_pRoot->HasChildren())
|
|
{
|
|
if (::PathIsUNC(pszPath))
|
|
{
|
|
//
|
|
// SubPathExists modifies the path. Need to make a local copy.
|
|
//
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pRoot->SubPathExists(szPath);
|
|
if (S_FALSE == hr)
|
|
{
|
|
//
|
|
// Absence from the tree means pinning is allowed.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
else if (S_OK == hr)
|
|
{
|
|
//
|
|
// Presence in the tree means pinning is not allowed.
|
|
//
|
|
Trace((TEXT("Policy disallows pinning \"%s\""), pszPath));
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
else if (STRSAFE_E_INSUFFICIENT_BUFFER == hr)
|
|
{
|
|
hr = NOPIN_E_BADPATH;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = NOPIN_E_BADPATH;
|
|
}
|
|
}
|
|
}
|
|
TraceAssert(S_OK == hr ||
|
|
S_FALSE == hr ||
|
|
NOPIN_E_BADPATH == hr);
|
|
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Quick check to see if ANY pin might be disallowed.
|
|
// Returns:
|
|
// S_OK - Tree has content.
|
|
// S_FALSE - Tree is empty.
|
|
//
|
|
HRESULT
|
|
CNoPinList::IsAnyPinDisallowed(
|
|
void
|
|
)
|
|
{
|
|
HRESULT hr = _Initialize();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TraceAssert(NULL != m_pRoot);
|
|
hr = m_pRoot->HasChildren() ? S_OK : S_FALSE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Initializes the no-pin list by reading path strings from the
|
|
// registry. The paths are stored in both HKLM and HKCU under
|
|
// the following key:
|
|
//
|
|
// Software\Policies\Microsoft\Windows\NetCache\NoMakeAvailableOfflineList
|
|
//
|
|
// Path strings may contain environment variables.
|
|
// Upon return the object contains a tree representing the union of all
|
|
// files and folders listed in both registry keys.
|
|
//
|
|
// Errors in reading the registry result only in paths not being added
|
|
// to the tree. No error is returned as a result of registry errors.
|
|
// If an invalid UNC path is found in the registry, an even log entry
|
|
// is recorded.
|
|
//
|
|
// Returns:
|
|
// S_OK - List successfully loaded.
|
|
// S_FALSE - List already initialized.
|
|
// E_OUTOFMEMORY - Insufficient memory.
|
|
// Other errors are possible.
|
|
//
|
|
HRESULT
|
|
CNoPinList::_Initialize(
|
|
void
|
|
)
|
|
{
|
|
TraceEnter(TRACE_UTIL, "CNoPinList::_Initialize");
|
|
HRESULT hr = S_OK;
|
|
|
|
if (NULL != m_pRoot)
|
|
{
|
|
//
|
|
// List is already initialized.
|
|
//
|
|
hr = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_pRoot = new CNode;
|
|
if (NULL == m_pRoot)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
const HKEY rghkeyRoot[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
|
|
|
|
TCHAR szKey[MAX_PATH];
|
|
PathCombine(szKey, REGSTR_KEY_OFFLINEFILESPOLICY, REGSTR_SUBKEY_NOMAKEAVAILABLEOFFLINELIST);
|
|
|
|
for (int i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(rghkeyRoot); i++)
|
|
{
|
|
HKEY hkey;
|
|
LONG lResult = ::RegOpenKeyEx(rghkeyRoot[i],
|
|
szKey,
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&hkey);
|
|
|
|
if (ERROR_SUCCESS == lResult)
|
|
{
|
|
TCHAR szName[MAX_PATH];
|
|
DWORD dwIndex = 0;
|
|
DWORD cchName = ARRAYSIZE(szName);
|
|
//
|
|
// Enumerate the paths listed in the registry.
|
|
//
|
|
while (SUCCEEDED(hr) &&
|
|
ERROR_SUCCESS == ::RegEnumValue(hkey,
|
|
dwIndex,
|
|
szName,
|
|
&cchName,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL))
|
|
{
|
|
//
|
|
// Install the path string from the registry into the
|
|
// tree. This function will expand any embedded environment strings
|
|
// as well as convert mapped drive specs to remote UNC paths.
|
|
//
|
|
hr = _InitPathFromRegistry(szName);
|
|
if (NOPIN_E_BADPATH == hr)
|
|
{
|
|
//
|
|
// This is a special error. It means someone has
|
|
// put bad data into the registry. "Bad" meaning
|
|
// that the path is not or does not expand to a valid UNC
|
|
// path string.
|
|
// Write an event log entry to tell the admin. The
|
|
// entry is generated at event logging level 1. I don't want
|
|
// it filling up an event log under normal conditions but
|
|
// I want an admin to figure it out in case their no-pin
|
|
// policy appears to be not working.
|
|
//
|
|
// The MSG template is this (english):
|
|
//
|
|
// "The registry value '%1' in key '%2\%3' is not, or does not
|
|
// expand to, a valid UNC path."
|
|
//
|
|
// We handle the error here because this is where we still have the
|
|
// value read from the registry. We include that in the event
|
|
// log entry so the admin can easily find it.
|
|
//
|
|
CscuiEventLog log;
|
|
log.Push(szName);
|
|
|
|
if (HKEY_LOCAL_MACHINE == rghkeyRoot[i])
|
|
{
|
|
log.Push(TEXT("HKEY_LOCAL_MACHINE"));
|
|
}
|
|
else
|
|
{
|
|
log.Push(TEXT("HKEY_CURRENT_USER"));
|
|
}
|
|
log.Push(szKey);
|
|
log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_INVALID_UNCPATH_INREG, 1);
|
|
//
|
|
// We do not abort processing because of a bad reg value.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
cchName = ARRAYSIZE(szName);
|
|
dwIndex++;
|
|
}
|
|
|
|
::RegCloseKey(hkey);
|
|
hkey = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Given a path string read from the registry, this function expands
|
|
// any embedded environment strings, converts any mapped drive letters
|
|
// to their corresponding remote UNC paths and installs the resulting
|
|
// path string into the tree.
|
|
//
|
|
HRESULT
|
|
CNoPinList::_InitPathFromRegistry(
|
|
LPCTSTR pszPath
|
|
)
|
|
{
|
|
TraceEnter(TRACE_UTIL, "CNoPinList::_InitPathFromRegistry");
|
|
TraceAssert(NULL != pszPath);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
TCHAR szNameExp[MAX_PATH]; // Expanded name string buffer.
|
|
|
|
//
|
|
// Expand any embedded environment strings.
|
|
//
|
|
if (0 == ::ExpandEnvironmentStrings(pszPath, szNameExp, ARRAYSIZE(szNameExp)))
|
|
{
|
|
const DWORD dwErr = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
Trace((TEXT("Error %d expanding \"%s\""), dwErr, pszPath));
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPCTSTR pszUncPath = NULL;
|
|
LPTSTR pszRemotePath = NULL; // Created by GetRemotePath if necessary.
|
|
//
|
|
// Convert a common typing mistake.
|
|
// Remember, these are reg entries. They could contain most anything.
|
|
//
|
|
for (LPTSTR s = szNameExp; *s; s++)
|
|
{
|
|
if (TEXT('/') == *s)
|
|
{
|
|
*s = TEXT('\\');
|
|
}
|
|
}
|
|
|
|
if (::PathIsUNC(szNameExp))
|
|
{
|
|
//
|
|
// Path is a UNC path. We're golden.
|
|
//
|
|
pszUncPath = szNameExp;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Path is probably a mapped drive.
|
|
// Get its remote UNC path. This API returns S_FALSE
|
|
// if the remote drive is not connected or if it's a local drive.
|
|
//
|
|
hr = ::GetRemotePath(szNameExp, &pszRemotePath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (S_OK == hr)
|
|
{
|
|
pszUncPath = pszRemotePath;
|
|
}
|
|
else if (S_FALSE == hr)
|
|
{
|
|
//
|
|
// Path was either to a local drive or to a net drive that
|
|
// isn't connected. Either way it's an invalid drive that
|
|
// won't be considered in the no-pin logic. Use the expanded
|
|
// value from the registry and pass that through to AddPath()
|
|
// where it will be rejected as an invalid UNC path.
|
|
//
|
|
TraceAssert(NULL == pszRemotePath);
|
|
pszUncPath = szNameExp;
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TraceAssert(NULL != pszUncPath);
|
|
TraceAssert(pszUncPath == szNameExp || pszUncPath == pszRemotePath);
|
|
//
|
|
// Insert the UNC path into the tree.
|
|
// At this point, a path may or may not be UNC. _AddPath()
|
|
// will verify this.
|
|
//
|
|
hr = _AddPath(pszUncPath);
|
|
}
|
|
if (NULL != pszRemotePath)
|
|
{
|
|
::LocalFree(pszRemotePath);
|
|
}
|
|
}
|
|
TraceLeaveResult(hr);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Adds a path to the tree. If this is a sub-path of an existing
|
|
// path in the tree, the remainder of the existing path is removed
|
|
// from the tree.
|
|
//
|
|
// Returns:
|
|
// S_OK - Path successfully added.
|
|
// E_OUTOFMEMORY - Insufficient memory.
|
|
// NOPIN_E_BADPATH - Invalid path string. Not a UNC.
|
|
//
|
|
HRESULT
|
|
CNoPinList::_AddPath(
|
|
LPCTSTR pszPath
|
|
)
|
|
{
|
|
TraceAssert(NULL != pszPath);
|
|
|
|
HRESULT hr = NOPIN_E_BADPATH;
|
|
|
|
if (::PathIsUNC(pszPath))
|
|
{
|
|
//
|
|
// AddPath modifies the path. Need to make a local copy.
|
|
//
|
|
TraceAssert(NULL != m_pRoot);
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = StringCchCopy(szPath, ARRAYSIZE(szPath), pszPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pRoot->AddPath(szPath);
|
|
}
|
|
else if (STRSAFE_E_INSUFFICIENT_BUFFER == hr)
|
|
{
|
|
hr = NOPIN_E_BADPATH;
|
|
}
|
|
}
|
|
|
|
TraceAssert(S_OK == hr ||
|
|
E_OUTOFMEMORY == hr ||
|
|
NOPIN_E_BADPATH == hr);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// class CNoPinList::CNode
|
|
//--------------------------------------------------------------------------
|
|
|
|
CNoPinList::CNode::~CNode(
|
|
void
|
|
)
|
|
{
|
|
if (NULL != m_pszName)
|
|
{
|
|
::LocalFree(m_pszName);
|
|
}
|
|
delete m_pChildren;
|
|
delete m_pNext;
|
|
}
|
|
|
|
|
|
//
|
|
// Initializes a node's name value.
|
|
//
|
|
HRESULT
|
|
CNoPinList::CNode::Initialize(
|
|
LPCTSTR pszName
|
|
)
|
|
{
|
|
TraceAssert(NULL != pszName);
|
|
TraceAssert(NULL == m_pszName);
|
|
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
|
|
if (LocalAllocString(&m_pszName, pszName))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// Add a child, keeping the children in alphabetical
|
|
// order by name. We trade a little time during creation
|
|
// for the speed benefits during lookup.
|
|
//
|
|
void
|
|
CNoPinList::CNode::_AddChild(
|
|
CNode *pChild
|
|
)
|
|
{
|
|
TraceAssert(NULL != pChild);
|
|
|
|
CNode **ppNode = &m_pChildren;
|
|
while(NULL != *ppNode)
|
|
{
|
|
CNode *pNode = *ppNode;
|
|
//
|
|
// Find the alphabetical insertion point.
|
|
//
|
|
TraceAssert(NULL != pNode->m_pszName);
|
|
TraceAssert(NULL != pChild->m_pszName);
|
|
|
|
int diff = ::lstrcmpi(pChild->m_pszName, pNode->m_pszName);
|
|
if (0 == diff)
|
|
{
|
|
//
|
|
// Child already exists. Don't allow duplicates.
|
|
//
|
|
return;
|
|
}
|
|
if (diff < 0)
|
|
{
|
|
//
|
|
// The new child is alphabetically "greater" than the currently
|
|
// visited node.
|
|
// Exit the loop with ppNode pointing to the pointer variable
|
|
// where we'll put the address of pChild.
|
|
//
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Advance to the next node in the list.
|
|
//
|
|
ppNode = &pNode->m_pNext;
|
|
}
|
|
}
|
|
//
|
|
// Insert the child.
|
|
//
|
|
pChild->m_pNext = *ppNode;
|
|
*ppNode = pChild;
|
|
}
|
|
|
|
|
|
//
|
|
// Locates a child node in a node's list of children.
|
|
// Comparison is by node name.
|
|
// Returns the address of the node if found. NULL otherwise.
|
|
//
|
|
CNoPinList::CNode *
|
|
CNoPinList::CNode::_FindChild(
|
|
LPCTSTR pszName
|
|
) const
|
|
{
|
|
TraceAssert(NULL != pszName);
|
|
|
|
CNode *pChild = NULL;
|
|
for (CNode *pNode = m_pChildren; pNode; pNode = pNode->m_pNext)
|
|
{
|
|
//
|
|
// The list is sorted alphabetically.
|
|
//
|
|
int diff = ::lstrcmpi(pszName, pNode->m_pszName);
|
|
if (diff <= 0)
|
|
{
|
|
//
|
|
// Either we found a match or we've passed all possible
|
|
// matches.
|
|
//
|
|
if (0 == diff)
|
|
{
|
|
//
|
|
// Exact match.
|
|
//
|
|
pChild = pNode;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return pChild;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Given "\\brianau1\public\bin"
|
|
// Returns address of "brianau1\public\bin", with *pcchComponent == 8.
|
|
//
|
|
// Given "public\bin"
|
|
// Returns address of "bin", with *pcchComponent == 3.
|
|
//
|
|
LPCTSTR
|
|
CNoPinList::CNode::_FindNextPathComponent( // [static]
|
|
LPCTSTR pszPath,
|
|
int *pcchComponent // [optional] Can be NULL.
|
|
)
|
|
{
|
|
TraceAssert(NULL != pszPath);
|
|
|
|
LPCTSTR pszBegin = pszPath;
|
|
|
|
const TCHAR CH_BS = TEXT('\\');
|
|
//
|
|
// Skip any leading backslashes.
|
|
//
|
|
while(*pszBegin && CH_BS == *pszBegin)
|
|
++pszBegin;
|
|
|
|
//
|
|
// Find the end of the path component.
|
|
//
|
|
LPCTSTR pszEnd = pszBegin;
|
|
while(*pszEnd && CH_BS != *pszEnd)
|
|
++pszEnd;
|
|
|
|
if (NULL != pcchComponent)
|
|
{
|
|
*pcchComponent = int(pszEnd - pszBegin);
|
|
TraceAssert(0 <= *pcchComponent);
|
|
}
|
|
|
|
//
|
|
// Validate the final position of the begin and end ptrs.
|
|
//
|
|
TraceAssert(NULL != pszBegin);
|
|
TraceAssert(NULL != pszEnd);
|
|
TraceAssert(pszBegin >= pszPath);
|
|
TraceAssert(pszBegin <= (pszPath + lstrlen(pszPath)));
|
|
TraceAssert(pszEnd >= pszPath);
|
|
TraceAssert(pszEnd <= (pszPath + lstrlen(pszPath)));
|
|
TraceAssert(TEXT('\\') != *pszBegin);
|
|
return pszBegin;
|
|
}
|
|
|
|
|
|
//
|
|
// Recursively adds components of a path string to the tree.
|
|
//
|
|
HRESULT
|
|
CNoPinList::CNode::AddPath(
|
|
LPTSTR pszPath
|
|
)
|
|
{
|
|
TraceAssert(NULL != pszPath);
|
|
|
|
HRESULT hr = NOPIN_E_BADPATH;
|
|
if (NULL != pszPath)
|
|
{
|
|
hr = S_OK;
|
|
|
|
int cchPart = 0;
|
|
LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart);
|
|
if (*pszPart)
|
|
{
|
|
TCHAR chTemp = TEXT('\0');
|
|
_SwapChars(&chTemp, pszPart + cchPart);
|
|
CNode *pChild = _FindChild(pszPart);
|
|
|
|
if (NULL != pChild)
|
|
{
|
|
//
|
|
// Found an existing node for this part of the path.
|
|
// If the node has children, give the remainder of the path
|
|
// to this node for addition. If it doesn't that means
|
|
// it's a leaf node and all it's children are excluded from
|
|
// pinning. No reason to add any children to it.
|
|
//
|
|
_SwapChars(&chTemp, pszPart + cchPart);
|
|
if (pChild->HasChildren())
|
|
{
|
|
hr = pChild->AddPath(pszPart + cchPart);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a new sub-path that is not yet in the tree.
|
|
//
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
pChild = new CNode();
|
|
if (NULL != pChild)
|
|
{
|
|
//
|
|
// Initialize the new child.
|
|
//
|
|
hr = pChild->Initialize(pszPart);
|
|
_SwapChars(&chTemp, pszPart + cchPart);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Have the new child add the remainder of
|
|
// the path as it's children.
|
|
//
|
|
hr = pChild->AddPath(pszPart + cchPart);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Link the new child into the list of children.
|
|
//
|
|
_AddChild(pChild);
|
|
}
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
delete pChild;
|
|
pChild = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We're at the end of the path, that means we're at a leaf node
|
|
// and this file or directory is excluded from pinning. If it's
|
|
// a directory, all children are excluded from pinning so there's
|
|
// no reason to keep any child nodes in the tree. This keeps the
|
|
// tree trimmed to a minimum necessary size.
|
|
//
|
|
delete m_pChildren;
|
|
m_pChildren = NULL;
|
|
}
|
|
}
|
|
|
|
TraceAssert(S_OK == hr ||
|
|
E_OUTOFMEMORY == hr ||
|
|
NOPIN_E_BADPATH == hr);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// Recursively determines if a given complete subpath exists for a
|
|
// path string. If a match occurs at a given level in the tree,
|
|
// the remainder of the path string is given to the matching node
|
|
// for further searching. This process continues recursively until
|
|
// we hit a leaf node in the tree or the end of the path string,
|
|
// whichever occurs first.
|
|
//
|
|
// Returns:
|
|
// S_OK - A complete path exists that is a subpath of pszPath.
|
|
// S_FALSE - A complete path does not exist.
|
|
//
|
|
HRESULT
|
|
CNoPinList::CNode::SubPathExists(
|
|
LPTSTR pszPath
|
|
) const
|
|
{
|
|
HRESULT hr = NOPIN_E_BADPATH;
|
|
if (NULL != pszPath)
|
|
{
|
|
hr = S_FALSE;
|
|
|
|
int cchPart = 0;
|
|
LPTSTR pszPart = (LPTSTR)_FindNextPathComponent(pszPath, &cchPart);
|
|
if (*pszPart)
|
|
{
|
|
TCHAR chTemp = TEXT('\0');
|
|
|
|
_SwapChars(&chTemp, pszPart + cchPart);
|
|
CNode *pChild = _FindChild(pszPart);
|
|
_SwapChars(&chTemp, pszPart + cchPart);
|
|
|
|
if (NULL != pChild)
|
|
{
|
|
if (pChild->HasChildren())
|
|
{
|
|
hr = pChild->SubPathExists(pszPart + cchPart);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Hit a leaf node. That means that we've traversed
|
|
// down a complete subpath of the path in question.
|
|
// Pinning of this path is not allowed.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TraceAssert(S_OK == hr ||
|
|
S_FALSE == hr ||
|
|
NOPIN_E_BADPATH == hr);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// This function dumps the contents of a tree node and all it's decendents.
|
|
// The result is an indented list of nodes in the debugger output.
|
|
// Handy for debugging tree build problems.
|
|
//
|
|
void
|
|
CNoPinList::_DumpNode(
|
|
const CNoPinList::CNode *pNode,
|
|
int iIndent
|
|
)
|
|
{
|
|
CNodeInspector ni(pNode);
|
|
TCHAR szText[1024] = {0};
|
|
|
|
iIndent = min(iIndent, 60);
|
|
|
|
LPTSTR pszWrite = szText;
|
|
UINT cchWrite = ARRAYSIZE(szText);
|
|
for (int i = 0; i < iIndent; i++)
|
|
{
|
|
*pszWrite++ = TEXT(' ');
|
|
cchWrite--;
|
|
}
|
|
|
|
::OutputDebugString(TEXT("\n\r"));
|
|
::wnsprintf(pszWrite, cchWrite, TEXT("Node Address.: 0x%08X\n\r"), pNode);
|
|
::OutputDebugString(szText);
|
|
::wnsprintf(pszWrite, cchWrite, TEXT("Name.........: %s\n\r"), ni.NodeName() ? ni.NodeName() : TEXT("<null>"));
|
|
::OutputDebugString(szText);
|
|
::wnsprintf(pszWrite, cchWrite, TEXT("Children.....: 0x%08X\n\r"), ni.ChildList());
|
|
::OutputDebugString(szText);
|
|
::wnsprintf(pszWrite, cchWrite, TEXT("Next Sibling.: 0x%08X\n\r"), ni.NextSibling());
|
|
::OutputDebugString(szText);
|
|
|
|
if (NULL != ni.ChildList())
|
|
{
|
|
_DumpNode(ni.ChildList(), iIndent + 5);
|
|
}
|
|
if (NULL != ni.NextSibling())
|
|
{
|
|
_DumpNode(ni.NextSibling(), iIndent);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Dump the entire tree starting with the root.
|
|
//
|
|
void
|
|
CNoPinList::Dump(
|
|
void
|
|
)
|
|
{
|
|
::OutputDebugString(TEXT("\n\rDumping CNoPinList\n\r"));
|
|
if (NULL != m_pRoot)
|
|
{
|
|
_DumpNode(m_pRoot, 0);
|
|
}
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
|