/*++ 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 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); }