From 677609b7224b2cd1e02e5866218f2e0d1fce57ba Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 19 Jul 2011 09:05:54 -0400 Subject: Per Josh's comments in http://codereview.lindenlab.com/6510035/ Instead of low-level open(O_CREAT | O_EXCL) loop on all platforms, use GetTempFileName() on Windows and mkstemp() elsewhere. Don't append a final newline to NamedTempFile: use caller's data literally. Tweak a couple comments. --- indra/llcommon/tests/llsdserialize_test.cpp | 179 +++++++++++++++++++++------- 1 file changed, 133 insertions(+), 46 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 7ce7ada29e..1fe3dc13c0 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -37,12 +37,12 @@ typedef U32 uint32_t; #include #include #include +#include +#include #include #include "llprocesslauncher.h" #endif -#include -#include #include /*==========================================================================*| @@ -89,6 +89,45 @@ std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } +#if ! LL_WINDOWS +// We want to call strerror_r(), but alarmingly, there are two different +// variants. The one that returns int always populates the passed buffer +// (except in case of error), whereas the other one always returns a valid +// char* but might or might not populate the passed buffer. How do we know +// which one we're getting? Define adapters for each and let the compiler +// select the applicable adapter. + +// strerror_r() returns char* +std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret) +{ + return strerror_ret; +} + +// strerror_r() returns int +std::string message_from(int orig_errno, const char* buffer, int strerror_ret) +{ + if (strerror_ret == 0) + { + return buffer; + } + // Here strerror_r() has set errno. Since strerror_r() has already failed, + // seems like a poor bet to call it again to diagnose its own error... + int stre_errno = errno; + if (stre_errno == ERANGE) + { + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (buffer too small)"); + } + if (stre_errno == EINVAL) + { + return STRINGIZE("unknown errno " << orig_errno); + } + // Here we don't even understand the errno from strerror_r()! + return STRINGIZE("strerror_r() can't explain errno " << orig_errno + << " (error " << stre_errno << ')'); +} +#endif // ! LL_WINDOWS + // boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( std::string temp_directory_path() { @@ -119,11 +158,6 @@ std::string temp_directory_path() #define _write write #define _close close #define _remove remove -#define _O_WRONLY O_WRONLY -#define _O_CREAT O_CREAT -#define _O_EXCL O_EXCL -#define _S_IWRITE S_IWUSR -#define _S_IREAD S_IRUSR #endif // ! LL_WINDOWS // Create a text file with specified content "somewhere in the @@ -165,6 +199,11 @@ public: private: void createFile(const std::string& ext, const Streamer& func) { + // Silly maybe, but use 'ext' as the name prefix. Strip off a leading + // '.' if present. + int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0; + +#if ! LL_WINDOWS // Make sure mPath ends with a directory separator, if it doesn't already. if (mPath.empty() || ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) @@ -172,49 +211,92 @@ private: mPath.append("/"); } - // Open a file with a unique name in the mPath directory. - int fd(-1); - for (int i(0);; ++i) + // mkstemp() accepts and modifies a char* template string. Generate + // the template string, then copy to modifiable storage. + // mkstemp() requires its template string to end in six X's. + mPath += ext.substr(pfx_offset) + "XXXXXX"; + // Copy to vector + std::vector pathtemplate(mPath.begin(), mPath.end()); + // append a nul byte for classic-C semantics + pathtemplate.push_back('\0'); + // std::vector promises that a pointer to the 0th element is the same + // as a pointer to a contiguous classic-C array + int fd(mkstemp(&pathtemplate[0])); + if (fd == -1) { - // Append an integer name to mPath. It need not be zero-filled, - // but I think it's neater that way. - std::string testname(STRINGIZE(mPath - << std::setw(8) << std::setfill('0') << i - << ext)); - // The key to this whole loop is the _O_CREAT | _O_EXCL bitmask, - // which requests error EEXIST if the file already exists. A - // proper implementation will check atomically, ensuring that - // racing processes will end up with two different filenames. - fd = _open(testname.c_str(), - _O_WRONLY | _O_CREAT | _O_EXCL, - _S_IREAD | _S_IWRITE); - if (fd != -1) // managed to open the file + // The documented errno values (http://linux.die.net/man/3/mkstemp) + // are used in a somewhat unusual way, so provide context-specific + // errors. + if (errno == EEXIST) + { + LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath + << "\") could not create unique file " << LL_ENDL; + } + if (errno == EINVAL) { - mPath = testname; // remember its actual name - break; + LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '" + << mPath << "'" << LL_ENDL; } - // This loop is specifically coded to handle EEXIST. Any other - // error is a problem. - llassert_always(errno == EEXIST); - // loop back to try another filename + // Shrug, something else + int mkst_errno = errno; + char buffer[256]; + LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: " + << message_from(mkst_errno, buffer, + strerror_r(mkst_errno, buffer, sizeof(buffer))) + << LL_ENDL; } - // fd is open, its name is in mPath: write it and close it. + // mkstemp() seems to have worked! Capture the modified filename. + // Avoid the nul byte we appended. + mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1)); /*==========================================================================*| // Define an ostream on the open fd. Tell it to close fd on destruction. boost::iostreams::stream out(fd, boost::iostreams::close_handle); |*==========================================================================*/ + + // Write desired content. std::ostringstream out; // Stream stuff to it. func(out); - // toss in a final newline for good measure - out << '\n'; std::string data(out.str()); int written(_write(fd, data.c_str(), data.length())); int closed(_close(fd)); llassert_always(written == data.length() && closed == 0); + +#else // LL_WINDOWS + // GetTempFileName() is documented to require a MAX_PATH buffer. + char tempname[MAX_PATH]; + // Use 'ext' as filename prefix, but skip leading '.' if any. + // The 0 param is very important: requests iterating until we get a + // unique name. + if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) + { + // I always have to look up this call... :-P + LPVOID msgptr; + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + LPTSTR(&lpMsgBuf), + 0, NULL ); + LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" + << (ext.c_str() + pfx_offset) << "\") failed: " + << msgptr << LL_ENDL; + LocalFree(msgptr); + } + // GetTempFileName() appears to have worked! Capture the actual + // filename. + mPath = tempname; + // Open the file and stream content to it. Destructor will close. + std::ofstream out(tempname); + func(out); + +#endif // LL_WINDOWS } void peep() @@ -1674,14 +1756,13 @@ namespace tut struct TestPythonCompatible { TestPythonCompatible(): - // Note the peculiar insertion of __FILE__ into this string. - // Normally I like to make a Python script navigate relative to - // its own placement in the repo directory tree (__file__) -- but - // in this case, the script is being written into a platform- - // dependent temp directory! So locate indra/lib/python relative - // to this C++ source file rather than the Python module. - // Use Python raw-string syntax so Windows pathname backslashes - // won't mislead Python's string scanner. + // Note the peculiar insertion of __FILE__ into this string. Since + // this script is being written into a platform-dependent temp + // directory, we can't locate indra/lib/python relative to + // Python's __file__. Use __FILE__ instead, navigating relative + // to this C++ source file. Use Python raw-string syntax so + // Windows pathname backslashes won't mislead Python's string + // scanner. import_llsd("import os.path\n" "import sys\n" "sys.path.insert(0,\n" @@ -1708,7 +1789,7 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc(_spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL)); + int rc = _spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL); if (rc == -1) { char buffer[256]; @@ -1768,7 +1849,7 @@ namespace tut set_test_name("verify python()"); python("hello", "import sys\n" - "sys.exit(17)", + "sys.exit(17)\n", 17); // expect nonzero rc } @@ -1778,7 +1859,7 @@ namespace tut set_test_name("verify NamedTempFile"); python("platform", "import sys\n" - "print 'Running on', sys.platform"); + "print 'Running on', sys.platform\n"); } template<> template<> @@ -1811,14 +1892,20 @@ namespace tut // notation. It's important to separate with newlines because Python's // llsd module doesn't support parsing from a file stream, only from a // string, so we have to know how much of the file to read into a - // string. Avoid final newline because NamedTempFile implicitly adds - // one. + // string. NamedTempFile file(".llsd", + // NamedTempFile's boost::function constructor + // takes a callable. To this callable it passes the + // std::ostream with which it's writing the + // NamedTempFile. This lambda-based expression + // first calls LLSD::Serialize() with that ostream, + // then streams a newline to it, etc. (lambda::bind(LLSDSerialize::toNotation, cdata[0], lambda::_1), lambda::_1 << '\n', lambda::bind(LLSDSerialize::toNotation, cdata[1], lambda::_1), lambda::_1 << '\n', - lambda::bind(LLSDSerialize::toNotation, cdata[2], lambda::_1))); + lambda::bind(LLSDSerialize::toNotation, cdata[2], lambda::_1), + lambda::_1 << '\n')); python("read C++ notation", lambda::_1 << -- cgit v1.2.3