1347 lines
30 KiB
C++
1347 lines
30 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 2001 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
LUA_RedirectFS_Cleanup.cpp
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Delete the redirected copies in every user's directory.
|
||
|
|
||
|
Created:
|
||
|
|
||
|
02/12/2001 maonis
|
||
|
|
||
|
Modified:
|
||
|
|
||
|
|
||
|
--*/
|
||
|
#include "precomp.h"
|
||
|
#include "secutils.h"
|
||
|
#include "utils.h"
|
||
|
#include <userenv.h>
|
||
|
|
||
|
WCHAR g_wszUserProfile[MAX_PATH] = L"";
|
||
|
DWORD g_cUserProfile = 0;
|
||
|
WCHAR g_wszSystemRoot[MAX_PATH] = L"";
|
||
|
DWORD g_cSystemRoot = 0;
|
||
|
|
||
|
DWORD
|
||
|
GetUserProfileDirW()
|
||
|
{
|
||
|
if (g_cUserProfile == 0)
|
||
|
{
|
||
|
HANDLE hToken;
|
||
|
if (OpenProcessToken(
|
||
|
GetCurrentProcess(),
|
||
|
TOKEN_QUERY,
|
||
|
&hToken))
|
||
|
{
|
||
|
WCHAR wszProfileDir[MAX_PATH] = L"";
|
||
|
DWORD dwSize = MAX_PATH;
|
||
|
|
||
|
if (GetUserProfileDirectoryW(hToken, wszProfileDir, &dwSize))
|
||
|
{
|
||
|
dwSize = GetLongPathNameW(wszProfileDir, g_wszUserProfile, MAX_PATH);
|
||
|
|
||
|
if (dwSize <= MAX_PATH)
|
||
|
{
|
||
|
//
|
||
|
// Only if we successfully got the path and it's not more
|
||
|
// than MAX_PATH will we set the global values.
|
||
|
//
|
||
|
g_cUserProfile = dwSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_wszUserProfile[0] = L'\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CloseHandle(hToken);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return g_cUserProfile;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
IsUserDirectory(LPCWSTR pwszPath)
|
||
|
{
|
||
|
GetUserProfileDirW();
|
||
|
|
||
|
if (g_cUserProfile)
|
||
|
{
|
||
|
return !_wcsnicmp(pwszPath, g_wszUserProfile, g_cUserProfile);
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
GetSystemRootDirW()
|
||
|
{
|
||
|
if (g_cSystemRoot == 0)
|
||
|
{
|
||
|
if (g_cSystemRoot = GetSystemWindowsDirectoryW(g_wszSystemRoot, MAX_PATH))
|
||
|
{
|
||
|
//
|
||
|
// Just to be cautious - if we really have a system directory that's
|
||
|
// longer than MAX_PATH, most likely something suspicious is going on
|
||
|
// here, so we bail out.
|
||
|
//
|
||
|
if (g_cSystemRoot >= MAX_PATH)
|
||
|
{
|
||
|
g_wszSystemRoot[0] = L'\0';
|
||
|
g_cSystemRoot = 0;
|
||
|
}
|
||
|
else if (g_cSystemRoot > 3)
|
||
|
{
|
||
|
g_wszSystemRoot[g_cSystemRoot] = L'\\';
|
||
|
g_wszSystemRoot[g_cSystemRoot + 1] = L'\0';
|
||
|
++g_cSystemRoot;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_wszSystemRoot[g_cSystemRoot] = L'\0';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return g_cSystemRoot;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
For the GetPrivateProfile* and WritePrivateProfile* APIs,
|
||
|
if the app didn't specify the path, we append the windows dir
|
||
|
in the front as that's where it'll be looking for and creating
|
||
|
the file it doesn't already exist.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN lpFileName - The file name specified by the profile API.
|
||
|
IN/OUT pwszFullPath - Pointer to the buffer to receive the full path.
|
||
|
This buffer is at least MAX_PATH WCHARs long.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Successfully got the path.
|
||
|
FALSE - We don't handle this filename, either because an error
|
||
|
occured or the file name is longer than MAX_PATH.
|
||
|
|
||
|
History:
|
||
|
|
||
|
05/16/2001 maonis Created
|
||
|
02/13/2002 maonis Modified to signal errors.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
MakeFileNameForProfileAPIsW(
|
||
|
IN LPCWSTR lpFileName,
|
||
|
IN OUT LPWSTR pwszFullPath // at least MAX_PATH in length
|
||
|
)
|
||
|
{
|
||
|
BOOL fIsSuccess = FALSE;
|
||
|
|
||
|
if (lpFileName)
|
||
|
{
|
||
|
DWORD cFileNameLen = wcslen(lpFileName);
|
||
|
|
||
|
if (wcschr(lpFileName, L'\\'))
|
||
|
{
|
||
|
if (cFileNameLen < MAX_PATH)
|
||
|
{
|
||
|
//
|
||
|
// The filename already contains the path, just copy it over.
|
||
|
//
|
||
|
wcsncpy(pwszFullPath, lpFileName, cFileNameLen);
|
||
|
fIsSuccess = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else if (GetSystemRootDirW() && g_cSystemRoot)
|
||
|
{
|
||
|
DWORD cLen = g_cSystemRoot + cFileNameLen;
|
||
|
|
||
|
//
|
||
|
// Only copy when we know the buffer is big enough.
|
||
|
//
|
||
|
if (cLen < MAX_PATH)
|
||
|
{
|
||
|
wcsncpy(pwszFullPath, g_wszSystemRoot, g_cSystemRoot);
|
||
|
wcsncpy(pwszFullPath + g_cSystemRoot, lpFileName, cFileNameLen);
|
||
|
pwszFullPath[cLen - 1] = L'\0';
|
||
|
|
||
|
fIsSuccess = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fIsSuccess;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If the .exe name is *setup*, *install* or _INS*._MP, we consider
|
||
|
// them a setup program and won't shim them.
|
||
|
//
|
||
|
BOOL IsSetup(
|
||
|
)
|
||
|
{
|
||
|
WCHAR wszModuleName[MAX_PATH + 1];
|
||
|
ZeroMemory(wszModuleName, (MAX_PATH + 1) * sizeof(WCHAR));
|
||
|
|
||
|
GetModuleFileNameW(NULL, wszModuleName, MAX_PATH + 1);
|
||
|
|
||
|
wszModuleName[MAX_PATH] = 0;
|
||
|
_wcslwr(wszModuleName);
|
||
|
|
||
|
if (wcsstr(wszModuleName, L"setup") || wcsstr(wszModuleName, L"install"))
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
LPWSTR pwsz;
|
||
|
if (pwsz = wcsstr(wszModuleName, L"_ins"))
|
||
|
{
|
||
|
if (wcsstr(pwsz + 4, L"_mp"))
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL LuaShouldApplyShim(
|
||
|
)
|
||
|
{
|
||
|
return (!IsSetup() && ShouldApplyShim());
|
||
|
}
|
||
|
|
||
|
#define REDIRECT_DIR L"\\Local Settings\\Application Data\\Redirected\\"
|
||
|
// We look at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList for the users.
|
||
|
#define PROFILELIST_STR L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
|
||
|
|
||
|
#define CLASSES_HIVE_SUFFIX L"_Classes"
|
||
|
#define CLASSES_HIVE_SUFFIX_LEN (sizeof(CLASSES_HIVE_SUFFIX) / sizeof(WCHAR) - 1)
|
||
|
|
||
|
#define USER_HIVE_NAME L"\\NtUser.dat"
|
||
|
#define USER_HIVE_NAME_LEN (sizeof(USER_HIVE_NAME) / sizeof(WCHAR) - 1)
|
||
|
#define USER_CLASSES_HIVE_NAME L"\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat"
|
||
|
#define USER_CLASSES_HIVE_NAME_LEN (sizeof(USER_CLASSES_HIVE_NAME) / sizeof(WCHAR) - 1)
|
||
|
|
||
|
// Total number of users which is the number of subkeys of
|
||
|
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
|
||
|
static DWORD g_cUsers = 0;
|
||
|
|
||
|
// We need to keep a list of keys we had to load under HKEY_USERS and unload them
|
||
|
// when the process exits.
|
||
|
static WCHAR** g_wszLoadedKeys = NULL;
|
||
|
static DWORD g_cLoadedKeys = 0;
|
||
|
|
||
|
// The number of users is the number of subkeys under
|
||
|
// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
|
||
|
LONG
|
||
|
InitGetUsers(
|
||
|
OUT DWORD* pcUsers,
|
||
|
OUT HKEY* phKey
|
||
|
)
|
||
|
{
|
||
|
LONG lRes;
|
||
|
|
||
|
if ((lRes = RegOpenKeyExW(
|
||
|
HKEY_LOCAL_MACHINE,
|
||
|
PROFILELIST_STR,
|
||
|
0,
|
||
|
KEY_READ,
|
||
|
phKey)) == ERROR_SUCCESS)
|
||
|
{
|
||
|
lRes = RegQueryInfoKeyW(*phKey, NULL, NULL, NULL, pcUsers,
|
||
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||
|
|
||
|
RegCloseKey(*phKey);
|
||
|
}
|
||
|
|
||
|
return lRes;
|
||
|
}
|
||
|
|
||
|
// In case of failure we need to clean up our array.
|
||
|
VOID
|
||
|
FreeUserDirectoryArray(
|
||
|
REDIRECTED_USER_PATH* pRedirectUserPaths
|
||
|
)
|
||
|
{
|
||
|
for (DWORD ui = 0; ui < g_cUsers; ++ui)
|
||
|
{
|
||
|
delete [] pRedirectUserPaths[ui].pwszPath;
|
||
|
}
|
||
|
|
||
|
delete [] pRedirectUserPaths;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
IsDirectory(
|
||
|
WCHAR* pwszName
|
||
|
)
|
||
|
{
|
||
|
DWORD dwAttrib = GetFileAttributesW(pwszName);
|
||
|
|
||
|
return (dwAttrib != -1 && dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
|
||
|
}
|
||
|
|
||
|
LONG GetProfilePath(
|
||
|
HKEY hkProfileList,
|
||
|
LPCWSTR pwszUserSID,
|
||
|
LPWSTR pwszUserDirectory
|
||
|
)
|
||
|
{
|
||
|
LONG lRes;
|
||
|
HKEY hkUserSID;
|
||
|
DWORD dwFlags;
|
||
|
|
||
|
// Open the user SID key.
|
||
|
if ((lRes = RegOpenKeyExW(
|
||
|
hkProfileList,
|
||
|
pwszUserSID,
|
||
|
0,
|
||
|
KEY_QUERY_VALUE,
|
||
|
&hkUserSID)) == ERROR_SUCCESS)
|
||
|
{
|
||
|
DWORD dwSize = sizeof(DWORD);
|
||
|
if ((lRes = RegQueryValueExW(
|
||
|
hkUserSID,
|
||
|
L"Flags",
|
||
|
NULL,
|
||
|
NULL,
|
||
|
(LPBYTE)&dwFlags,
|
||
|
&dwSize)) == ERROR_SUCCESS)
|
||
|
{
|
||
|
// Check if the value of Flag is 0, if so it's the user we care about.
|
||
|
if (dwFlags == 0)
|
||
|
{
|
||
|
DWORD cTemp = MAX_PATH;
|
||
|
WCHAR wszTemp[MAX_PATH] = L"";
|
||
|
|
||
|
if ((lRes = RegQueryValueExW(
|
||
|
hkUserSID,
|
||
|
L"ProfileImagePath",
|
||
|
NULL,
|
||
|
NULL,
|
||
|
(LPBYTE)wszTemp,
|
||
|
&cTemp)) == ERROR_SUCCESS)
|
||
|
{
|
||
|
DWORD cExpandLen = ExpandEnvironmentStringsW(wszTemp, pwszUserDirectory, MAX_PATH);
|
||
|
|
||
|
if (cExpandLen > MAX_PATH)
|
||
|
{
|
||
|
lRes = ERROR_MORE_DATA;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lRes = ERROR_INVALID_HANDLE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RegCloseKey(hkUserSID);
|
||
|
}
|
||
|
|
||
|
return lRes;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
GetUsersFS(
|
||
|
REDIRECTED_USER_PATH** ppRedirectUserPaths,
|
||
|
DWORD* pcUsers
|
||
|
)
|
||
|
{
|
||
|
WCHAR wszRedirectDir[MAX_PATH] = L"";
|
||
|
DWORD cUsers;
|
||
|
HKEY hkProfileList;
|
||
|
if (InitGetUsers(&cUsers, &hkProfileList) != ERROR_SUCCESS)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError, "[GetUsersFS] Error initializing");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
*ppRedirectUserPaths = new REDIRECTED_USER_PATH [cUsers];
|
||
|
if (!*ppRedirectUserPaths)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError, "[GetUsersFS] Error allocating memory");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
REDIRECTED_USER_PATH* pRedirectUserPaths = *ppRedirectUserPaths;
|
||
|
|
||
|
ZeroMemory((PVOID)pRedirectUserPaths, cUsers * sizeof(REDIRECTED_USER_PATH));
|
||
|
|
||
|
WCHAR wszSubKey[MAX_PATH] = L"";
|
||
|
DWORD cSubKey = 0;
|
||
|
HKEY hkUserSID;
|
||
|
LONG lRes;
|
||
|
// The number of users we care about.
|
||
|
DWORD cLUAUsers = 0;
|
||
|
DWORD dwIndex = 0;
|
||
|
|
||
|
while (TRUE)
|
||
|
{
|
||
|
cSubKey = MAX_PATH;
|
||
|
|
||
|
lRes = RegEnumKeyExW(hkProfileList, dwIndex, wszSubKey, &cSubKey,
|
||
|
NULL, NULL, NULL, NULL);
|
||
|
|
||
|
if (lRes == ERROR_SUCCESS)
|
||
|
{
|
||
|
WCHAR wszUserDirectory[MAX_PATH] = L"";
|
||
|
|
||
|
if ((lRes = GetProfilePath(hkProfileList, wszSubKey, wszUserDirectory))
|
||
|
== ERROR_SUCCESS)
|
||
|
{
|
||
|
//
|
||
|
// If the directory doesn't exist, it means either the user
|
||
|
// never logged on, or there are no redirected files for that
|
||
|
// user. We simply skip it.
|
||
|
//
|
||
|
if (IsDirectory(wszUserDirectory))
|
||
|
{
|
||
|
DWORD cPath = wcslen(wszUserDirectory) + 1;
|
||
|
LPWSTR pwszPath = new WCHAR [cPath];
|
||
|
|
||
|
if (pwszPath)
|
||
|
{
|
||
|
wcscpy(pwszPath, wszUserDirectory);
|
||
|
pRedirectUserPaths[cLUAUsers].pwszPath = pwszPath;
|
||
|
pRedirectUserPaths[cLUAUsers].cLen = cPath;
|
||
|
++cLUAUsers;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersFS] Error allocating memory");
|
||
|
lRes = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
goto EXIT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (lRes == ERROR_NO_MORE_ITEMS)
|
||
|
{
|
||
|
*pcUsers = cLUAUsers;
|
||
|
lRes = ERROR_SUCCESS;
|
||
|
goto EXIT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
++dwIndex;
|
||
|
}
|
||
|
|
||
|
EXIT:
|
||
|
|
||
|
RegCloseKey(hkProfileList);
|
||
|
|
||
|
if (lRes == ERROR_SUCCESS)
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
FreeUserDirectoryArray(pRedirectUserPaths);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
FreeUsersFS(
|
||
|
REDIRECTED_USER_PATH* pRedirectUserPaths
|
||
|
)
|
||
|
{
|
||
|
FreeUserDirectoryArray(pRedirectUserPaths);
|
||
|
}
|
||
|
|
||
|
LONG
|
||
|
LoadHive(
|
||
|
LPCWSTR pwszHiveName,
|
||
|
LPCWSTR pwszHiveFile,
|
||
|
HKEY* phKey
|
||
|
)
|
||
|
{
|
||
|
LONG lRes;
|
||
|
|
||
|
// If the hive is already loaded, we'll get a sharing violation so
|
||
|
// check that as well.
|
||
|
if ((lRes = RegLoadKeyW(HKEY_USERS, pwszHiveName, pwszHiveFile))
|
||
|
== ERROR_SUCCESS || lRes == ERROR_SHARING_VIOLATION)
|
||
|
{
|
||
|
if (lRes == ERROR_SUCCESS)
|
||
|
{
|
||
|
DWORD cLen = wcslen(pwszHiveName) + 1;
|
||
|
g_wszLoadedKeys[g_cLoadedKeys] = new WCHAR [cLen];
|
||
|
if (!(g_wszLoadedKeys[g_cLoadedKeys]))
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[LoadHive] Error allocating %d WCHARs",
|
||
|
cLen);
|
||
|
|
||
|
return ERROR_NOT_ENOUGH_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Store the hive name so later on we can unload this hive.
|
||
|
wcscpy(g_wszLoadedKeys[g_cLoadedKeys++], pwszHiveName);
|
||
|
}
|
||
|
|
||
|
lRes = RegOpenKeyExW(
|
||
|
HKEY_USERS,
|
||
|
pwszHiveName,
|
||
|
0,
|
||
|
KEY_ALL_ACCESS,
|
||
|
phKey);
|
||
|
}
|
||
|
|
||
|
return lRes;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
GetUsersReg(
|
||
|
USER_HIVE_KEY** pphkUsers,
|
||
|
DWORD* pcUsers
|
||
|
)
|
||
|
{
|
||
|
// We have to enable the "Restore files and directories" privilege to
|
||
|
// load each user's hive.
|
||
|
if (!AdjustPrivilege(SE_RESTORE_NAME, TRUE))
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersReg] Error enabling the SE_RESTORE_NAME privilege");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
DWORD cUsers;
|
||
|
HKEY hkProfileList;
|
||
|
if (InitGetUsers(&cUsers, &hkProfileList) != ERROR_SUCCESS)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError, "[GetUsersReg] Error initializing");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
*pphkUsers = new USER_HIVE_KEY [cUsers];
|
||
|
if (!*pphkUsers)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersReg] Error allocating memory for %d USER_HIVE_KEYs",
|
||
|
cUsers);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
g_wszLoadedKeys = new WCHAR* [cUsers * 2];
|
||
|
if (!g_wszLoadedKeys)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersReg] Error allocating memory for %d WCHARs",
|
||
|
cUsers * 2);
|
||
|
|
||
|
delete [] *pphkUsers;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
USER_HIVE_KEY* phkUsers = *pphkUsers;
|
||
|
|
||
|
ZeroMemory((PVOID)phkUsers, cUsers * sizeof(USER_HIVE_KEY));
|
||
|
ZeroMemory((PVOID)g_wszLoadedKeys, cUsers * 2 * sizeof (WCHAR*));
|
||
|
|
||
|
WCHAR wszSubKey[MAX_PATH] = L"";
|
||
|
WCHAR wszUserHive[MAX_PATH] = L"";
|
||
|
WCHAR wszUserClassesHive[MAX_PATH] = L"";
|
||
|
DWORD cSubKey = 0;
|
||
|
HKEY hkSubKey;
|
||
|
LONG lRes;
|
||
|
// The number of users we care about.
|
||
|
DWORD cLUAUsers = 0;
|
||
|
DWORD dwIndex = 0;
|
||
|
DWORD cUserHive = 0;
|
||
|
|
||
|
while (TRUE)
|
||
|
{
|
||
|
cSubKey = MAX_PATH;
|
||
|
|
||
|
lRes = RegEnumKeyExW(hkProfileList, dwIndex, wszSubKey, &cSubKey,
|
||
|
NULL, NULL, NULL, NULL);
|
||
|
|
||
|
if (lRes == ERROR_SUCCESS)
|
||
|
{
|
||
|
if ((lRes = GetProfilePath(hkProfileList, wszSubKey, wszUserHive))
|
||
|
== ERROR_SUCCESS)
|
||
|
{
|
||
|
//
|
||
|
// Make sure we don't buffer overflow.
|
||
|
//
|
||
|
cUserHive = wcslen(wszUserHive);
|
||
|
if ((cUserHive + USER_CLASSES_HIVE_NAME_LEN + 1) > MAX_PATH ||
|
||
|
(cUserHive + USER_HIVE_NAME_LEN + 1) > MAX_PATH)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersReg] The hive key names are too long - we don't handle them");
|
||
|
goto EXIT;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Construct the locations for the user hive and user classes data hive.
|
||
|
//
|
||
|
wcsncpy(wszUserClassesHive, wszUserHive, cUserHive);
|
||
|
wcsncpy(
|
||
|
wszUserClassesHive + cUserHive,
|
||
|
USER_CLASSES_HIVE_NAME,
|
||
|
USER_CLASSES_HIVE_NAME_LEN);
|
||
|
wszUserClassesHive[cUserHive + USER_CLASSES_HIVE_NAME_LEN] = L'\0';
|
||
|
|
||
|
wcsncpy(wszUserHive + cUserHive, USER_HIVE_NAME, USER_HIVE_NAME_LEN);
|
||
|
wszUserHive[cUserHive + USER_HIVE_NAME_LEN] = L'\0';
|
||
|
|
||
|
//
|
||
|
// Load the HKCU for this user.
|
||
|
//
|
||
|
if ((lRes = LoadHive(
|
||
|
wszSubKey,
|
||
|
wszUserHive,
|
||
|
&phkUsers[cLUAUsers].hkUser)) == ERROR_SUCCESS)
|
||
|
{
|
||
|
//
|
||
|
// We can't necessarily load the HKCR for this user - it might
|
||
|
// contain no data so we only attemp to load it.
|
||
|
//
|
||
|
|
||
|
if ((cSubKey + CLASSES_HIVE_SUFFIX_LEN + 1) > MAX_PATH)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[GetUsersReg] The CR key name is too long - we don't handle it");
|
||
|
goto EXIT;
|
||
|
}
|
||
|
|
||
|
wcsncpy(wszSubKey + cSubKey, CLASSES_HIVE_SUFFIX, CLASSES_HIVE_SUFFIX_LEN);
|
||
|
wszSubKey[cSubKey + CLASSES_HIVE_SUFFIX_LEN] = L'\0';
|
||
|
|
||
|
LoadHive(
|
||
|
wszSubKey,
|
||
|
wszUserClassesHive,
|
||
|
&phkUsers[cLUAUsers].hkUserClasses);
|
||
|
|
||
|
++cLUAUsers;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (lRes == ERROR_NO_MORE_ITEMS)
|
||
|
{
|
||
|
*pcUsers = cLUAUsers;
|
||
|
lRes = ERROR_SUCCESS;
|
||
|
goto EXIT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
++dwIndex;
|
||
|
}
|
||
|
|
||
|
EXIT:
|
||
|
|
||
|
RegCloseKey(hkProfileList);
|
||
|
|
||
|
if (lRes == ERROR_SUCCESS)
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
FreeUsersReg(phkUsers, cUsers);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
FreeUsersReg(
|
||
|
USER_HIVE_KEY* phkUsers,
|
||
|
DWORD cUsers
|
||
|
)
|
||
|
{
|
||
|
DWORD dw;
|
||
|
|
||
|
// Close all the open keys.
|
||
|
for (dw = 0; dw < cUsers; ++dw)
|
||
|
{
|
||
|
RegCloseKey(phkUsers[dw].hkUser);
|
||
|
RegCloseKey(phkUsers[dw].hkUserClasses);
|
||
|
}
|
||
|
|
||
|
delete [] phkUsers;
|
||
|
|
||
|
for (dw = 0; dw < g_cLoadedKeys; ++dw)
|
||
|
{
|
||
|
// Unloaded the keys we had to load under HKEY_USERS.
|
||
|
RegUnLoadKey(HKEY_USERS, g_wszLoadedKeys[dw]);
|
||
|
|
||
|
delete [] g_wszLoadedKeys[dw];
|
||
|
}
|
||
|
|
||
|
delete [] g_wszLoadedKeys;
|
||
|
|
||
|
// Disable the "Restore files and directories" privilege.
|
||
|
AdjustPrivilege(SE_RESTORE_NAME, FALSE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Registry utilies.
|
||
|
//
|
||
|
|
||
|
HKEY g_hkRedirectRoot = NULL;
|
||
|
HKEY g_hkCurrentUserClasses = NULL;
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
We only return TRUE if it's one of the predefined keys we are interested in.
|
||
|
We don't redirect the HKEY_USERS and HKEY_PERFORMANCE_DATA keys.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN hKey - the key handle.
|
||
|
IN lpSubKey - subkey to check.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - It's one of our predefined keys.
|
||
|
FALSE - It's either a non-predefined key or a predefined key that we are not
|
||
|
interested in.
|
||
|
|
||
|
History:
|
||
|
|
||
|
03/27/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
IsPredefinedKey(
|
||
|
IN HKEY hKey
|
||
|
)
|
||
|
{
|
||
|
return (
|
||
|
hKey == HKEY_CLASSES_ROOT ||
|
||
|
hKey == HKEY_CURRENT_USER ||
|
||
|
hKey == HKEY_LOCAL_MACHINE);
|
||
|
}
|
||
|
|
||
|
LONG
|
||
|
GetRegRedirectKeys()
|
||
|
{
|
||
|
LONG lRet;
|
||
|
|
||
|
if (lRet = RegCreateKeyExW(
|
||
|
HKEY_CURRENT_USER,
|
||
|
LUA_REG_REDIRECT_KEY,
|
||
|
0,
|
||
|
0,
|
||
|
REG_OPTION_NON_VOLATILE,
|
||
|
KEY_ALL_ACCESS,
|
||
|
NULL,
|
||
|
&g_hkRedirectRoot,
|
||
|
NULL) == ERROR_SUCCESS)
|
||
|
{
|
||
|
lRet = RegCreateKeyExW(
|
||
|
HKEY_CURRENT_USER,
|
||
|
LUA_SOFTWARE_CLASSES,
|
||
|
0,
|
||
|
0,
|
||
|
REG_OPTION_NON_VOLATILE,
|
||
|
KEY_ALL_ACCESS,
|
||
|
NULL,
|
||
|
&g_hkCurrentUserClasses,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
return lRet;
|
||
|
}
|
||
|
|
||
|
#define IS_END_OF_COMPONENT(x) (*x == L'\\' || *x == L'\0')
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Determines if 2 components match - one with wildcards and the other
|
||
|
without.
|
||
|
|
||
|
Note: this function is specialized for the LUA shims - the pattern
|
||
|
is all lowercase. If the components match, we advance the string
|
||
|
to the end of the component so when we do the whole path/file name
|
||
|
matching we don't need to go through the string twice.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN ppPattern - component with wildcards.
|
||
|
IN ppString - component without wildcards.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - the components match.
|
||
|
FALSE - the components don't match.
|
||
|
|
||
|
History:
|
||
|
|
||
|
05/10/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
DoComponentsMatch(
|
||
|
LPCWSTR* ppwszPattern,
|
||
|
LPCWSTR* ppwszString)
|
||
|
{
|
||
|
LPCWSTR pwszPattern = *ppwszPattern;
|
||
|
LPCWSTR pwszString = *ppwszString;
|
||
|
LPCWSTR pwszSearch = NULL;
|
||
|
LPCWSTR pwszSearchPattern = NULL;
|
||
|
|
||
|
BOOL fIsSuccess = TRUE;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
if (*pwszPattern == L'*')
|
||
|
{
|
||
|
while (*++pwszPattern == L'*');
|
||
|
|
||
|
if (IS_END_OF_COMPONENT(pwszPattern))
|
||
|
{
|
||
|
// Advanced the string to the end.
|
||
|
while (!IS_END_OF_COMPONENT(pwszString))
|
||
|
{
|
||
|
++pwszString;
|
||
|
}
|
||
|
|
||
|
goto EXIT;
|
||
|
}
|
||
|
|
||
|
pwszSearch = pwszString;
|
||
|
pwszSearchPattern = pwszPattern;
|
||
|
}
|
||
|
|
||
|
if (IS_END_OF_COMPONENT(pwszString))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ((*pwszPattern == L'?') ||
|
||
|
(*pwszPattern == *pwszString))
|
||
|
{
|
||
|
pwszPattern++;
|
||
|
}
|
||
|
else if (pwszSearch == NULL)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pwszString = pwszSearch++;
|
||
|
pwszPattern = pwszSearchPattern;
|
||
|
}
|
||
|
|
||
|
++pwszString;
|
||
|
|
||
|
} while (!IS_END_OF_COMPONENT(pwszString));
|
||
|
|
||
|
if (*pwszPattern == L'*')
|
||
|
{
|
||
|
fIsSuccess = TRUE;
|
||
|
++pwszPattern;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fIsSuccess = IS_END_OF_COMPONENT(pwszPattern);
|
||
|
}
|
||
|
|
||
|
EXIT:
|
||
|
|
||
|
*ppwszPattern = pwszPattern;
|
||
|
*ppwszString = pwszString;
|
||
|
return fIsSuccess;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Determines if the item is in the redirect list.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN pwszDirectory - All lowercase name.
|
||
|
IN cDirectory - The length of the directory.
|
||
|
IN pwszFile - The file name.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - the names match.
|
||
|
FALSE - the names don't match.
|
||
|
|
||
|
History:
|
||
|
|
||
|
11/30/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
DoesItemMatchRedirect(
|
||
|
LPCWSTR pwszItem,
|
||
|
const RITEM* pItem,
|
||
|
BOOL fIsDirectory
|
||
|
)
|
||
|
{
|
||
|
LPCWSTR pwszName = &(pItem->wszName[0]);
|
||
|
BOOL fMatchComponents;
|
||
|
|
||
|
if (pItem->fHasWC)
|
||
|
{
|
||
|
while (*pwszItem && *pwszName)
|
||
|
{
|
||
|
if (!DoComponentsMatch(&pwszName, &pwszItem))
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (fIsDirectory)
|
||
|
{
|
||
|
if (!*pwszName)
|
||
|
{
|
||
|
//
|
||
|
// directory has exhausted. It's a match.
|
||
|
//
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if (!*pwszItem)
|
||
|
{
|
||
|
//
|
||
|
// directory hasn't exhausted but item has, no match.
|
||
|
//
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!*pwszItem)
|
||
|
{
|
||
|
//
|
||
|
// item has exhausted. It's a match.
|
||
|
//
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if (!*pwszName)
|
||
|
{
|
||
|
//
|
||
|
// item hasn't exhausted but file has, no match.
|
||
|
//
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
++pwszName;
|
||
|
++pwszItem;
|
||
|
}
|
||
|
|
||
|
if (fIsDirectory)
|
||
|
{
|
||
|
return (!*pwszName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (!*pwszItem);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
while (*pwszItem && *pwszName && *pwszItem == *pwszName)
|
||
|
{
|
||
|
++pwszItem;
|
||
|
++pwszName;
|
||
|
}
|
||
|
|
||
|
if (fIsDirectory)
|
||
|
{
|
||
|
return (!*pwszName && (!*pwszItem || *pwszItem == L'\\'));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (!*pwszItem && (!*pwszName || *pwszName == L'\\'));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Parse the commandline argument for the LUA shims using ' ' as the delimiter.
|
||
|
If a token has spaces, use double quotes around it. Use this function the
|
||
|
same way you use wcstok except you don't have to specify the delimiter.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN/OUT pwsz - the string to parse.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
pointer to the next token.
|
||
|
|
||
|
History:
|
||
|
|
||
|
05/17/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
LPWSTR GetNextToken(
|
||
|
LPWSTR pwsz
|
||
|
)
|
||
|
{
|
||
|
static LPWSTR pwszToken;
|
||
|
static LPWSTR pwszEndOfLastToken;
|
||
|
|
||
|
if (!pwsz)
|
||
|
{
|
||
|
pwsz = pwszEndOfLastToken;
|
||
|
}
|
||
|
|
||
|
// Skip the white space.
|
||
|
while (*pwsz && *pwsz == ' ')
|
||
|
{
|
||
|
++pwsz;
|
||
|
}
|
||
|
|
||
|
pwszToken = pwsz;
|
||
|
|
||
|
BOOL fInsideQuotes = 0;
|
||
|
|
||
|
while (*pwsz)
|
||
|
{
|
||
|
switch(*pwsz)
|
||
|
{
|
||
|
case L'"':
|
||
|
fInsideQuotes ^= 1;
|
||
|
|
||
|
if (fInsideQuotes)
|
||
|
{
|
||
|
++pwszToken;
|
||
|
}
|
||
|
|
||
|
case L' ':
|
||
|
if (!fInsideQuotes)
|
||
|
{
|
||
|
goto EXIT;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
++pwsz;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EXIT:
|
||
|
if (*pwsz)
|
||
|
{
|
||
|
*pwsz = L'\0';
|
||
|
pwszEndOfLastToken = ++pwsz;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pwszEndOfLastToken = pwsz;
|
||
|
}
|
||
|
|
||
|
return pwszToken;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Starting from the end going backward and find the first non whitespace
|
||
|
char. Set the whitespace char after it to '\0'.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN pwsz - Beginning pointer.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
History:
|
||
|
|
||
|
06/27/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
VOID TrimTrailingSpaces(
|
||
|
LPWSTR pwsz
|
||
|
)
|
||
|
{
|
||
|
if (pwsz)
|
||
|
{
|
||
|
DWORD cLen = wcslen(pwsz);
|
||
|
LPWSTR pwszEnd = pwsz + cLen - 1;
|
||
|
|
||
|
while (pwszEnd >= pwsz && (*pwszEnd == L' ' || *pwszEnd == L'\t'))
|
||
|
{
|
||
|
--pwszEnd;
|
||
|
}
|
||
|
|
||
|
*(++pwszEnd) = L'\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
If the directory doesn't exist, we create it.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN pwszDir - The name of the directory to create. The directory should NOT
|
||
|
start with \\?\ and it should haved a trailing slash.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - the directory was created.
|
||
|
FALSE - otherwise.
|
||
|
|
||
|
History:
|
||
|
|
||
|
05/17/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
CreateDirectoryOnDemand(
|
||
|
LPWSTR pwszDir
|
||
|
)
|
||
|
{
|
||
|
if (!pwszDir || !*pwszDir)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelSpew,
|
||
|
"[CreateDirectoryOnDemand] Empty directory name - nothing to do");
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
WCHAR* pwszStartPath = pwszDir;
|
||
|
WCHAR* pwszEndPath = pwszDir + wcslen(pwszDir);
|
||
|
WCHAR* pwszStartNext = pwszStartPath;
|
||
|
|
||
|
// Find the end of the next sub dir.
|
||
|
WCHAR* pwszEndNext;
|
||
|
DWORD dwAttrib;
|
||
|
|
||
|
while (pwszStartNext < pwszEndPath)
|
||
|
{
|
||
|
pwszEndNext = wcschr(pwszStartNext, L'\\');
|
||
|
if (pwszEndNext)
|
||
|
{
|
||
|
*pwszEndNext = L'\0';
|
||
|
if ((dwAttrib = GetFileAttributesW(pwszStartPath)) != -1)
|
||
|
{
|
||
|
// If the directory already exists, we probe its sub directory.
|
||
|
*pwszEndNext = L'\\';
|
||
|
pwszStartNext = pwszEndNext + 1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!CreateDirectoryW(pwszStartPath, NULL))
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[CreateDirectoryOnDemand] CreateDirectory %S failed: %d",
|
||
|
pwszStartPath,
|
||
|
GetLastError());
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
*pwszEndNext = L'\\';
|
||
|
pwszStartNext = pwszEndNext + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[CreateDirectoryOnDemand] Invalid directory name: %S", pwszStartPath);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Expand a string which might have enviorment variables embedded in it.
|
||
|
It gives you options to
|
||
|
1) Add a trailing slash if there's not one;
|
||
|
2) Create the directory if it doesn't exist;
|
||
|
3) Add the \\?\ prefix;
|
||
|
|
||
|
NOTE: The caller is responsible of free the memory using delete [].
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
IN pwszItem - string to expand.
|
||
|
OUT pcItemExpand - number of characters in the resulting string.
|
||
|
NOTE: this *includes* the terminating NULL.
|
||
|
IN fEnsureTrailingSlash - option 1.
|
||
|
IN fCreateDirectory - option 2.
|
||
|
IN fAddPrefix - option 3.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
The expanded string or NULL if error occured.
|
||
|
|
||
|
History:
|
||
|
|
||
|
05/17/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
LPWSTR
|
||
|
ExpandItem(
|
||
|
LPCWSTR pwszItem,
|
||
|
DWORD* pcItemExpand,
|
||
|
BOOL fEnsureTrailingSlash,
|
||
|
BOOL fCreateDirectory,
|
||
|
BOOL fAddPrefix
|
||
|
)
|
||
|
{
|
||
|
BOOL fIsSuccess = FALSE;
|
||
|
|
||
|
//
|
||
|
// Get the required length.
|
||
|
//
|
||
|
DWORD cLenExpand = ExpandEnvironmentStringsW(pwszItem, NULL, 0);
|
||
|
|
||
|
if (!cLenExpand)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[ExpandItem] Failed to get the required buffer size "
|
||
|
"when expanding %S: %d",
|
||
|
pwszItem, GetLastError());
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (fEnsureTrailingSlash)
|
||
|
{
|
||
|
++cLenExpand;
|
||
|
}
|
||
|
|
||
|
if (fAddPrefix)
|
||
|
{
|
||
|
cLenExpand += FILE_NAME_PREFIX_LEN;
|
||
|
}
|
||
|
|
||
|
LPWSTR pwszItemExpand = new WCHAR [cLenExpand];
|
||
|
if (!pwszItemExpand)
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[ExpandItem] Error allocating %d WCHARs", cLenExpand);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
LPWSTR pwszTemp = pwszItemExpand;
|
||
|
DWORD cTemp = cLenExpand;
|
||
|
|
||
|
if (fAddPrefix)
|
||
|
{
|
||
|
wcscpy(pwszItemExpand, FILE_NAME_PREFIX);
|
||
|
pwszTemp += FILE_NAME_PREFIX_LEN;
|
||
|
cTemp -= FILE_NAME_PREFIX_LEN;
|
||
|
}
|
||
|
|
||
|
if (!ExpandEnvironmentStringsW(pwszItem, pwszTemp, cTemp))
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[ExpandItem] Failed to expand %S: %d",
|
||
|
pwszItem, GetLastError());
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
// Ensure the trailing slash.
|
||
|
if (fEnsureTrailingSlash)
|
||
|
{
|
||
|
if (pwszItemExpand[cLenExpand - 3] != L'\\')
|
||
|
{
|
||
|
pwszItemExpand[cLenExpand - 2] = L'\\';
|
||
|
pwszItemExpand[cLenExpand - 1] = L'\0';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
--cLenExpand;
|
||
|
}
|
||
|
|
||
|
if (fCreateDirectory &&
|
||
|
!CreateDirectoryOnDemand(pwszItemExpand + (fAddPrefix ? 4 : 0)))
|
||
|
{
|
||
|
DPF("LUAUtils", eDbgLevelError,
|
||
|
"[ExpandItem] Failed to create %S",
|
||
|
pwszItemExpand);
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*pcItemExpand = cLenExpand;
|
||
|
|
||
|
fIsSuccess = TRUE;
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
if (!fIsSuccess)
|
||
|
{
|
||
|
delete [] pwszItemExpand;
|
||
|
pwszItemExpand = NULL;
|
||
|
}
|
||
|
|
||
|
return pwszItemExpand;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function Description:
|
||
|
|
||
|
Given a delimiter character, returns the number of items in the string.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Number of items in the string.
|
||
|
|
||
|
History:
|
||
|
|
||
|
11/13/2001 maonis Created
|
||
|
|
||
|
--*/
|
||
|
|
||
|
DWORD
|
||
|
GetItemsCount(
|
||
|
LPCWSTR pwsz,
|
||
|
WCHAR chDelimiter
|
||
|
)
|
||
|
{
|
||
|
DWORD cItems = 0;
|
||
|
|
||
|
while (*pwsz) {
|
||
|
|
||
|
if (*pwsz == chDelimiter) {
|
||
|
++cItems;
|
||
|
}
|
||
|
++pwsz;
|
||
|
}
|
||
|
|
||
|
return (cItems + 1);
|
||
|
}
|