From 7a336f63cc77a93362f5d4fcc274d1d908ef2f63 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Wed, 5 Dec 2018 16:57:00 -0500 Subject: SL-10153: If $APPDATA isn't already good, try SHGetFolderPath(). In that case, also update $APPDATA for child processes. --- indra/llvfs/lldir_win32.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index 9836fa28f2..3e48e086d7 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -48,10 +48,38 @@ LLDir_Win32::LLDir_Win32() // set this first: used by append() and add() methods mDirDelimiter = "\\"; + WCHAR w_str[MAX_PATH]; // Application Data is where user settings go. We rely on $APPDATA being // correct; in fact the VMP makes a point of setting it properly, since // Windows itself botches the job for non-ASCII usernames (MAINT-8087). mOSUserDir = ll_safe_string(getenv("APPDATA")); + // On Windows, it's a Bad Thing if a pathname contains ASCII question + // marks. In our experience, it means that the original pathname contained + // non-ASCII characters that were munged to '?' somewhere along the way. + // Convert to LLWString first, though, in case one of the bytes in a + // non-ASCII UTF-8 string accidentally resembles '?'. + if (utf8string_to_wstring(mOSUserDir).find(llwchar('?')) != LLWString::npos) + { + // It is really unclear what we should do if the following call fails. + // We use it, among other things, to find where to put our log file! + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str))) + { + // But of course, only update mOSUserDir if SHGetFolderPathW() works. + mOSUserDir = utf16str_to_utf8str(llutf16string(w_str)); + // Not only that: update our environment so that child processes + // will see a reasonable value as well. Use _putenv_s() rather + // than _wputenv_s() because WE want to control the encoding with + // which APPDATA is passed to child processes, instead of letting + // somebody else pick it. + _putenv_s("APPDATA", mOSUserDir.c_str()); + // SL-10153: It is really tempting to make the above _putenv_s() + // call unconditional, since we've observed cases in which the + // parent viewer receives a valid non-ASCII APPDATA value while + // the child SLVersionChecker process receives one containing + // question marks. But if what we see is already valid, what do we + // gain by storing it again? + } + } // We want cache files to go on the local disk, even if the // user is on a network with a "roaming profile". @@ -63,7 +91,6 @@ LLDir_Win32::LLDir_Win32() // cleans up that version on upgrade. JC mOSCacheDir = ll_safe_string(getenv("LOCALAPPDATA")); - WCHAR w_str[MAX_PATH]; if (GetTempPath(MAX_PATH, w_str)) { if (wcslen(w_str)) /* Flawfinder: ignore */ -- cgit v1.2.3 From 758e7e8df753a9b63782ff2964c26c0b0a4015fe Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Thu, 6 Dec 2018 14:33:51 -0500 Subject: SL-10174: LOCALAPPDATA bad? Try SHGetFolderPath(CSIDL_LOCAL_APPDATA). This logic is essentially copy-and-edited from the same suspenders-and-belt concerning APPDATA and CSIDL_APPDATA for SL-10153. --- indra/llvfs/lldir_win32.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index 3e48e086d7..f972f27fd0 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -90,6 +90,19 @@ LLDir_Win32::LLDir_Win32() // We used to store the cache in AppData\Roaming, and the installer // cleans up that version on upgrade. JC mOSCacheDir = ll_safe_string(getenv("LOCALAPPDATA")); + // Windows really does not deal well with pathnames containing non-ASCII + // characters. See above remarks about APPDATA. + if (utf8string_to_wstring(mOSCacheDir).find(llwchar('?')) != LLWString::npos) + { + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str))) + { + // But of course, only update mOSCacheDir if SHGetFolderPathW() works. + mOSCacheDir = utf16str_to_utf8str(llutf16string(w_str)); + // Update our environment so that child processes will see a + // reasonable value as well. + _putenv_s("LOCALAPPDATA", mOSCacheDir.c_str()); + } + } if (GetTempPath(MAX_PATH, w_str)) { -- cgit v1.2.3 From a36cd3a2925bf77aa804a5617e0a227cabacde46 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Sat, 8 Dec 2018 10:30:18 -0500 Subject: SL-10153: Try to handle non-English non-ASCII Windows APPDATA. --- indra/llvfs/lldir_win32.cpp | 113 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 24 deletions(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index f972f27fd0..96bd779b5f 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -31,6 +31,8 @@ #include "lldir_win32.h" #include "llerror.h" #include "llrand.h" // for gLindenLabRandomNumber +#include "stringize.h" +#include "llfile.h" #include <shlobj.h> #include <fstream> @@ -43,6 +45,48 @@ #define PACKVERSION(major,minor) MAKELONG(minor,major) DWORD GetDllVersion(LPCTSTR lpszDllName); +namespace +{ // anonymous + enum class prst { INIT, OPEN, SKIP } state; + llofstream prelogf; + + void prelog(const std::string& message) + { + switch (state) + { + case prst::INIT: + // assume we failed, until we succeed + state = prst::SKIP; + + // can't initialize within one case of a switch statement + const char* prelog_name; + prelog_name = getenv("PRELOG"); + if (! prelog_name) + // no PRELOG variable set, carry on + return; + prelogf.open(prelog_name, std::ios_base::app); + if (! prelogf.is_open()) + // can't complain to anybody; how? + return; + // got the log file open, cool! + state = prst::OPEN; + prelogf << "========================================================================" + << std::endl; + // fall through, don't break + + case prst::OPEN: + prelogf << message << std::endl; + break; + + case prst::SKIP: + // either PRELOG isn't set, or we failed to open that pathname + break; + } + } +} // anonymous namespace + +#define PRELOG(expression) prelog(STRINGIZE(expression)) + LLDir_Win32::LLDir_Win32() { // set this first: used by append() and add() methods @@ -52,32 +96,38 @@ LLDir_Win32::LLDir_Win32() // Application Data is where user settings go. We rely on $APPDATA being // correct; in fact the VMP makes a point of setting it properly, since // Windows itself botches the job for non-ASCII usernames (MAINT-8087). - mOSUserDir = ll_safe_string(getenv("APPDATA")); + // Try using wide-character getenv()?? + wchar_t *APPDATA = _wgetenv(L"APPDATA"); + if (APPDATA) + { + mOSUserDir = ll_convert_wide_to_string(APPDATA, CP_UTF8); + } + PRELOG("APPDATA='" << mOSUserDir << "'"); // On Windows, it's a Bad Thing if a pathname contains ASCII question // marks. In our experience, it means that the original pathname contained // non-ASCII characters that were munged to '?' somewhere along the way. // Convert to LLWString first, though, in case one of the bytes in a // non-ASCII UTF-8 string accidentally resembles '?'. - if (utf8string_to_wstring(mOSUserDir).find(llwchar('?')) != LLWString::npos) + // Bear in mind that llwchar is not necessarily wchar_t, therefore L'?' is + // not necessarily the right type. + if (mOSUserDir.empty() || + utf8string_to_wstring(mOSUserDir).find(llwchar('?')) != LLWString::npos) { - // It is really unclear what we should do if the following call fails. - // We use it, among other things, to find where to put our log file! - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str))) + PRELOG("APPDATA empty or contains ASCII '?'"); + //HRESULT okay = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str); + wchar_t *pwstr = NULL; + HRESULT okay = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pwstr); + PRELOG("SHGetKnownFolderPath(FOLDERID_RoamingAppData) returned " << okay); + if (SUCCEEDED(okay) && pwstr) { - // But of course, only update mOSUserDir if SHGetFolderPathW() works. - mOSUserDir = utf16str_to_utf8str(llutf16string(w_str)); + // But of course, only update mOSUserDir if SHGetKnownFolderPath() works. + mOSUserDir = ll_convert_wide_to_string(pwstr, CP_UTF8); // Not only that: update our environment so that child processes - // will see a reasonable value as well. Use _putenv_s() rather - // than _wputenv_s() because WE want to control the encoding with - // which APPDATA is passed to child processes, instead of letting - // somebody else pick it. - _putenv_s("APPDATA", mOSUserDir.c_str()); - // SL-10153: It is really tempting to make the above _putenv_s() - // call unconditional, since we've observed cases in which the - // parent viewer receives a valid non-ASCII APPDATA value while - // the child SLVersionChecker process receives one containing - // question marks. But if what we see is already valid, what do we - // gain by storing it again? + // will see a reasonable value as well. + _wputenv_s(L"APPDATA", pwstr); + // SHGetKnownFolderPath() contract requires us to free pwstr + CoTaskMemFree(pwstr); + PRELOG("mOSUserDir='" << mOSUserDir << "'"); } } @@ -89,18 +139,33 @@ LLDir_Win32::LLDir_Win32() // // We used to store the cache in AppData\Roaming, and the installer // cleans up that version on upgrade. JC - mOSCacheDir = ll_safe_string(getenv("LOCALAPPDATA")); + // Again, try using wide-character getenv(). + wchar_t *LOCALAPPDATA = _wgetenv(L"LOCALAPPDATA"); + if (LOCALAPPDATA) + { + mOSCacheDir = ll_convert_wide_to_string(LOCALAPPDATA, CP_UTF8); + } + PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'"); // Windows really does not deal well with pathnames containing non-ASCII // characters. See above remarks about APPDATA. - if (utf8string_to_wstring(mOSCacheDir).find(llwchar('?')) != LLWString::npos) + if (mOSCacheDir.empty() || + utf8string_to_wstring(mOSCacheDir).find(llwchar('?')) != LLWString::npos) { - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str))) + PRELOG("LOCALAPPDATA empty or contains ASCII '?'"); + //HRESULT okay = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str); + wchar_t *pwstr = NULL; + HRESULT okay = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pwstr); + PRELOG("SHGetKnownFolderPath(FOLDERID_LocalAppData) returned " << okay); + if (SUCCEEDED(okay) && pwstr) { - // But of course, only update mOSCacheDir if SHGetFolderPathW() works. - mOSCacheDir = utf16str_to_utf8str(llutf16string(w_str)); + // But of course, only update mOSCacheDir if SHGetKnownFolderPath() works. + mOSCacheDir = ll_convert_wide_to_string(pwstr, CP_UTF8); // Update our environment so that child processes will see a // reasonable value as well. - _putenv_s("LOCALAPPDATA", mOSCacheDir.c_str()); + _wputenv_s(L"LOCALAPPDATA", pwstr); + // SHGetKnownFolderPath() contract requires us to free pwstr + CoTaskMemFree(pwstr); + PRELOG("mOSCacheDir='" << mOSCacheDir << "'"); } } -- cgit v1.2.3 From 9479d422d4b34e2ffd08017272412ab3bd1ae00e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Mon, 10 Dec 2018 17:14:31 -0500 Subject: SL-10153: Use a degenerate singleton for PRELOG log file. The previous build declared a static std::ofstream; but the code that determines the pathname for the log file is called so early that static objects have not yet been constructed. Declare a pointer instead, and instantiate it on demand. --- indra/llvfs/lldir_win32.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index 96bd779b5f..e97424c9a9 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -30,7 +30,6 @@ #include "lldir_win32.h" #include "llerror.h" -#include "llrand.h" // for gLindenLabRandomNumber #include "stringize.h" #include "llfile.h" #include <shlobj.h> @@ -47,8 +46,10 @@ DWORD GetDllVersion(LPCTSTR lpszDllName); namespace { // anonymous - enum class prst { INIT, OPEN, SKIP } state; - llofstream prelogf; + enum class prst { INIT, OPEN, SKIP } state = prst::INIT; + // This is called so early that we can't count on static objects being + // properly constructed yet, so declare a pointer instead of an instance. + std::ofstream* prelogf = nullptr; void prelog(const std::string& message) { @@ -64,18 +65,18 @@ namespace if (! prelog_name) // no PRELOG variable set, carry on return; - prelogf.open(prelog_name, std::ios_base::app); - if (! prelogf.is_open()) + prelogf = new std::ofstream(prelog_name, std::ios_base::app); + if (! (prelogf && prelogf->is_open())) // can't complain to anybody; how? return; // got the log file open, cool! state = prst::OPEN; - prelogf << "========================================================================" - << std::endl; + (*prelogf) << "========================================================================" + << std::endl; // fall through, don't break case prst::OPEN: - prelogf << message << std::endl; + (*prelogf) << message << std::endl; break; case prst::SKIP: -- cgit v1.2.3 From bb4a649b9c324e7989de6e59398c85e80fe2acc7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Tue, 11 Dec 2018 15:47:29 -0500 Subject: SL-10153: Validate APPDATA, LOCALAPPDATA by checking existence. --- indra/llvfs/lldir_win32.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index e97424c9a9..fc4680bbfb 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -104,17 +104,13 @@ LLDir_Win32::LLDir_Win32() mOSUserDir = ll_convert_wide_to_string(APPDATA, CP_UTF8); } PRELOG("APPDATA='" << mOSUserDir << "'"); - // On Windows, it's a Bad Thing if a pathname contains ASCII question - // marks. In our experience, it means that the original pathname contained - // non-ASCII characters that were munged to '?' somewhere along the way. - // Convert to LLWString first, though, in case one of the bytes in a - // non-ASCII UTF-8 string accidentally resembles '?'. - // Bear in mind that llwchar is not necessarily wchar_t, therefore L'?' is - // not necessarily the right type. - if (mOSUserDir.empty() || - utf8string_to_wstring(mOSUserDir).find(llwchar('?')) != LLWString::npos) + // On Windows, we could have received a plain-ASCII pathname in which + // non-ASCII characters have been munged to '?', or the pathname could + // have been badly encoded and decoded such that we now have garbage + // instead of a valid path. Check that mOSUserDir actually exists. + if (mOSUserDir.empty() || ! fileExists(mOSUserDir)) { - PRELOG("APPDATA empty or contains ASCII '?'"); + PRELOG("APPDATA does not exist"); //HRESULT okay = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str); wchar_t *pwstr = NULL; HRESULT okay = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pwstr); @@ -149,10 +145,9 @@ LLDir_Win32::LLDir_Win32() PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'"); // Windows really does not deal well with pathnames containing non-ASCII // characters. See above remarks about APPDATA. - if (mOSCacheDir.empty() || - utf8string_to_wstring(mOSCacheDir).find(llwchar('?')) != LLWString::npos) + if (mOSCacheDir.empty() || ! fileExists(mOSCacheDir)) { - PRELOG("LOCALAPPDATA empty or contains ASCII '?'"); + PRELOG("LOCALAPPDATA does not exist"); //HRESULT okay = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str); wchar_t *pwstr = NULL; HRESULT okay = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pwstr); -- cgit v1.2.3 From c4096f670c7b3d43f8a5c1f65ef7e02033b0329d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Fri, 14 Dec 2018 15:38:13 -0500 Subject: SL-10153: Review and rationalize fetching paths from environment. Use LLStringUtil::getenv() or getoptenv() whenever we fetch a string that will be used as a pathname. Use LLFile::tmpdir() instead of getenv("TEMP"). As an added extra-special bonus, finally clean up $TMP/llcontrol-test-zzzzzz directories that have been accumulating every time we run a local build! --- indra/llvfs/lldir_win32.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index fc4680bbfb..acf734f16b 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -30,6 +30,7 @@ #include "lldir_win32.h" #include "llerror.h" +#include "llstring.h" #include "stringize.h" #include "llfile.h" #include <shlobj.h> @@ -55,17 +56,17 @@ namespace { switch (state) { + boost::optional<std::string> prelog_name; + case prst::INIT: // assume we failed, until we succeed state = prst::SKIP; - // can't initialize within one case of a switch statement - const char* prelog_name; - prelog_name = getenv("PRELOG"); + prelog_name = LLDirUtil::getoptenv("PRELOG"); if (! prelog_name) // no PRELOG variable set, carry on return; - prelogf = new std::ofstream(prelog_name, std::ios_base::app); + prelogf = new llofstream(*prelog_name, std::ios_base::app); if (! (prelogf && prelogf->is_open())) // can't complain to anybody; how? return; @@ -95,13 +96,11 @@ LLDir_Win32::LLDir_Win32() WCHAR w_str[MAX_PATH]; // Application Data is where user settings go. We rely on $APPDATA being - // correct; in fact the VMP makes a point of setting it properly, since - // Windows itself botches the job for non-ASCII usernames (MAINT-8087). - // Try using wide-character getenv()?? - wchar_t *APPDATA = _wgetenv(L"APPDATA"); + // correct. + auto APPDATA = LLStringUtil::getoptenv("APPDATA"); if (APPDATA) { - mOSUserDir = ll_convert_wide_to_string(APPDATA, CP_UTF8); + mOSUserDir = *APPDATA; } PRELOG("APPDATA='" << mOSUserDir << "'"); // On Windows, we could have received a plain-ASCII pathname in which @@ -118,7 +117,7 @@ LLDir_Win32::LLDir_Win32() if (SUCCEEDED(okay) && pwstr) { // But of course, only update mOSUserDir if SHGetKnownFolderPath() works. - mOSUserDir = ll_convert_wide_to_string(pwstr, CP_UTF8); + mOSUserDir = ll_convert_wide_to_string(pwstr); // Not only that: update our environment so that child processes // will see a reasonable value as well. _wputenv_s(L"APPDATA", pwstr); @@ -136,11 +135,10 @@ LLDir_Win32::LLDir_Win32() // // We used to store the cache in AppData\Roaming, and the installer // cleans up that version on upgrade. JC - // Again, try using wide-character getenv(). - wchar_t *LOCALAPPDATA = _wgetenv(L"LOCALAPPDATA"); + auto LOCALAPPDATA = LLStringUtil::getoptenv("LOCALAPPDATA"); if (LOCALAPPDATA) { - mOSCacheDir = ll_convert_wide_to_string(LOCALAPPDATA, CP_UTF8); + mOSCacheDir = *LOCALAPPDATA; } PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'"); // Windows really does not deal well with pathnames containing non-ASCII @@ -155,7 +153,7 @@ LLDir_Win32::LLDir_Win32() if (SUCCEEDED(okay) && pwstr) { // But of course, only update mOSCacheDir if SHGetKnownFolderPath() works. - mOSCacheDir = ll_convert_wide_to_string(pwstr, CP_UTF8); + mOSCacheDir = ll_convert_wide_to_string(pwstr); // Update our environment so that child processes will see a // reasonable value as well. _wputenv_s(L"LOCALAPPDATA", pwstr); -- cgit v1.2.3 From 4a136572857fcf5d5fd21789a777bbde67c1076d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed <nat@lindenlab.com> Date: Sat, 15 Dec 2018 09:13:24 -0500 Subject: SL-10153: auto name{expression} declares an initializer_list instead of a variable of type decltype(expression). Using SHGetKnownFolderPath(FOLDERID_Fonts) in LLFontGL::getFontPathSystem() requires new Windows #include files. A variable with a constructor can't be declared within the braces of a switch statement, even outside any of its case clauses. --- indra/llvfs/lldir_win32.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llvfs/lldir_win32.cpp') diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index acf734f16b..b3b3afb37e 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -54,15 +54,15 @@ namespace void prelog(const std::string& message) { + boost::optional<std::string> prelog_name; + switch (state) { - boost::optional<std::string> prelog_name; - case prst::INIT: // assume we failed, until we succeed state = prst::SKIP; - prelog_name = LLDirUtil::getoptenv("PRELOG"); + prelog_name = LLStringUtil::getoptenv("PRELOG"); if (! prelog_name) // no PRELOG variable set, carry on return; -- cgit v1.2.3