diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 755b5c1db7..5ee935bc3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8.4) project(monad_native) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") #include gtest include_directories(../ext-src/gtest/fused-src impl) @@ -11,26 +11,22 @@ link_directories(${monad_native_BINARY_DIR}) set(LIB_SOURCE_FILES impl/getcurrentprocessorid.cpp impl/getusername.cpp impl/terminal.cpp impl/getcomputername.cpp) set(HOST_COMMON_SOURCE_FILES host/common/coreclrutil.cpp) set(HOST_COMMON_TEST_SOURCE_FILES tests/host/test-hostutil.cpp) -set(TEST_SOURCE_FILES tests/test-getcurrentprocessid.cpp tests/test-getcomputername ${HOST_COMMON_SOURCE_FILES} ${HOST_COMMON_TEST_SOURCE_FILES}) +set(TEST_SOURCE_FILES tests/test-getcurrentprocessid.cpp tests/test-getusername.cpp tests/test-getcomputername ${HOST_COMMON_SOURCE_FILES} ${HOST_COMMON_TEST_SOURCE_FILES}) set(SOURCE_FILES main.cpp ../ext-src/gtest/fused-src/gtest/gtest-all.cc) -SET(HOST_CMDLINE_SOURCE_FILES host/cmdline/main.cpp ${HOST_COMMON_SOURCE_FILES}) +set(HOST_CMDLINE_SOURCE_FILES host/cmdline/main.cpp ${HOST_COMMON_SOURCE_FILES}) # target definitions add_library(ps SHARED ${LIB_SOURCE_FILES}) -add_library(scxcore STATIC IMPORTED GLOBAL) -set_property(TARGET scxcore PROPERTY IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../ext-src/pal/lib/libscxcore.a) -target_link_libraries(ps scxcore) add_executable(monad_native ${SOURCE_FILES} ${TEST_SOURCE_FILES}) add_executable(host_cmdline ${HOST_CMDLINE_SOURCE_FILES}) # add pthread and other libs find_package(Threads) +target_link_libraries(ps icuuc) target_link_libraries(monad_native ${CMAKE_THREAD_LIBS_INIT} ps) target_link_libraries(host_cmdline dl icuuc) # target specific include directories target_include_directories(monad_native PRIVATE host) target_include_directories(host_cmdline PRIVATE host) - - diff --git a/src/impl/getusername.cpp b/src/impl/getusername.cpp index b37b1cffc6..5d0bec0361 100644 --- a/src/impl/getusername.cpp +++ b/src/impl/getusername.cpp @@ -1,8 +1,146 @@ -#include "getusername.h" +//! @file getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Implements GetUserName Win32 API + +#include +#include +#include #include +#include +#include +#include +#include +#include +#include "getusername.h" -BOOL GetUserName(WCHAR_T* userName, UINT32* maxLength) +//! @brief GetUserName retrieves the name of the user associated with +//! the current thread. +//! +//! GetUserNameW is the Unicode variation. See [MSDN documentation]. +//! +//! @param[out] lpBuffer +//! @parblock +//! A pointer to the buffer to receive the user's +//! logon name. If this buffer is not large enough to contain the +//! entire user name, the function fails. +//! +//! WCHAR_T* is a Unicode [LPTSTR]. +//! @endparblock +//! +//! @param[in, out] lpnSize +//! @parblock +//! On input, this variable specifies the size of the lpBuffer buffer, +//! in TCHARs. On output, the variable receives the number of TCHARs +//! copied to the buffer, including the terminating null character. +//! +//! TCHAR is a Unicode 16-bit [WCHAR]. +//! +//! If lpBuffer is too small, the function fails and GetLastError +//! returns ERROR_INSUFFICIENT_BUFFER. This parameter receives the +//! required buffer size, including the terminating null character. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_BAD_ENVIRONMENT: locale is not UTF-8 +//! - ERROR_TOO_MANY_OPEN_FILES: already have the maximum allowed number of open files +//! - ERROR_NO_ASSOCIATION: calling process has no controlling terminal +//! - ERROR_INSUFFICIENT_BUFFER: buffer not large enough to hold username string +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_OUTOFMEMORY: insufficient memory to allocate passwd structure +//! - ERROR_NO_ASSOCIATION: standard input didn't refer to a terminal +//! - ERROR_INVALID_FUNCTION: getlogin_r() returned an unrecognized error code +//! +//! @retval 1 If the function succeeds, the return value is a nonzero +//! value, and the variable pointed to by lpnSize contains the number +//! of TCHARs copied to the buffer specified by lpBuffer, including +//! the terminating null character. +//! +//! @retval 0 If the function fails, the return value is zero. To get +//! extended error information, call GetLastError. +//! +//! [MSDN documentation]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724432(v=vs.85).aspx +//! [WCHAR]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx#WCHAR +//! [LPTSTR]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx#LPTSTR +BOOL GetUserNameW(WCHAR_T* lpBuffer, LPDWORD lpnSize) { - return 0; -} + const std::string utf8 = "UTF-8"; + errno = FALSE; + + // Check parameters + if (!lpBuffer || !lpnSize) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + // Select locale from environment + setlocale(LC_ALL, ""); + // Check that locale is UTF-8 + if (nl_langinfo(CODESET) != utf8) + { + errno = ERROR_BAD_ENVIRONMENT; + return FALSE; + } + + // Get username from system in a thread-safe manner + std::string username(LOGIN_NAME_MAX, '\0'); + int ret = getlogin_r(&username[0], username.size()); + // Map errno to Win32 Error Codes + if (ret) + { + switch (errno) + { + case EMFILE: + case ENFILE: + errno = ERROR_TOO_MANY_OPEN_FILES; + break; + case ENXIO: + errno = ERROR_NO_ASSOCIATION; + break; + case ERANGE: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_NO_SUCH_USER; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTTY: + errno = ERROR_NO_ASSOCIATION; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return FALSE; + } + + // Convert to char* to WCHAR_T* (UTF-8 to UTF-16 LE w/o BOM) + std::basic_string username16(LOGIN_NAME_MAX+1, 0); + icu::UnicodeString username8(username.c_str(), "UTF-8"); + int32_t targetSize = username8.extract(0, username8.length(), + reinterpret_cast(&username16[0]), + (username16.size()-1)*sizeof(char16_t), + "UTF-16LE"); + // Number of characters including null + username16.resize(targetSize/sizeof(char16_t)+1); + + // Size in WCHARs including null + const DWORD size = username16.length(); + if (size > *lpnSize) + { + errno = ERROR_INSUFFICIENT_BUFFER; + // Set lpnSize if buffer is too small to inform user + // of necessary size + *lpnSize = size; + return 0; + } + + // Copy bytes from string to buffer + memcpy(lpBuffer, &username16[0], size*sizeof(char16_t)); + *lpnSize = size; + + return TRUE; +} diff --git a/src/impl/getusername.h b/src/impl/getusername.h index 922a4e96ad..6083078bc0 100644 --- a/src/impl/getusername.h +++ b/src/impl/getusername.h @@ -4,7 +4,6 @@ PAL_BEGIN_EXTERNC -BOOL GetUserName(WCHAR_T* userName, UINT32* maxLength); +BOOL GetUserNameW(WCHAR_T* lpBuffer, LPDWORD lpnSize); PAL_END_EXTERNC - diff --git a/src/tests/test-getusername.cpp b/src/tests/test-getusername.cpp new file mode 100644 index 0000000000..e27aeef28b --- /dev/null +++ b/src/tests/test-getusername.cpp @@ -0,0 +1,159 @@ +//! @file test-getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Unit tests for GetUserName + +#include +#include +#include +#include +#include +#include +#include +#include +#include "getusername.h" + +//! Test fixture for GetUserNameW +class GetUserNameTest : public ::testing::Test +{ +protected: + DWORD lpnSize; + std::vector lpBuffer; + BOOL result; + std::string expectedUsername; + DWORD expectedSize; + + GetUserNameTest(): expectedUsername(std::string(getlogin())), + expectedSize(expectedUsername.length()+1) + { + } + + //! Invokes GetUserNameW with lpnSize and lpBuffer, saves result. + //! + //! @param size Assigns to lpnSize and allocates lpBuffer with + //! size number of null characters. + void TestWithSize(DWORD size) + { + lpnSize = size; + // allocate a WCHAR_T buffer to receive username + lpBuffer.assign(lpnSize, '\0'); + result = GetUserNameW(&lpBuffer[0], &lpnSize); + } + + //! Checks the effects of GetUserNameW for success. + void TestSuccess() + { + SCOPED_TRACE(""); + + //! Returns TRUE on success. + EXPECT_EQ(TRUE, result); + + //! Sets lpnSize to number of WCHARs including null. + ASSERT_EQ(expectedSize, lpnSize); + + // setup for conversion from UTF-16LE + const char* begin = reinterpret_cast(&lpBuffer[0]); + // multiply to get number of bytes + icu::UnicodeString username16(begin, lpnSize*sizeof(char16_t), "UTF-16LE"); + // username16 length includes null and is number of characters + ASSERT_EQ(expectedSize, username16.length()); + + // convert (minus null) to UTF-8 for comparison + std::string username(lpnSize-1, 0); + ASSERT_EQ(expectedUsername.length(), username.length()); + username16.extract(0, username.length(), + reinterpret_cast(&username[0]), "UTF-8"); + + //! Returned username (after conversion) is what was expected. + EXPECT_EQ(expectedUsername, username); + } + + //! Checks the effects of GetUserNameW on failure with invalid parameters. + void TestInvalidParameter() + { + SCOPED_TRACE(""); + + //! Returns FALSE on failure. + EXPECT_EQ(FALSE, result); + + //! Sets errno to ERROR_INVALID_PARAMETER when lpBuffer is null + //! (which is the case for an empty vector). + EXPECT_EQ(errno, ERROR_INVALID_PARAMETER); + } + + //! Checks the effects of GetUserNameW on failure with a buffer that is too small. + void TestInsufficientBuffer() + { + SCOPED_TRACE(""); + + //! Returns FALSE on failure. + EXPECT_EQ(FALSE, result); + + //! Sets errno to ERROR_INSUFFICIENT_BUFFER. + EXPECT_EQ(errno, ERROR_INSUFFICIENT_BUFFER); + + //! Sets lpnSize to length of username plus null. + EXPECT_EQ(expectedSize, lpnSize); + } +}; + +TEST_F(GetUserNameTest, BufferAsNullButNotBufferSize) +{ + lpnSize = 1; + result = GetUserNameW(NULL, &lpnSize); + + TestInvalidParameter(); + // does not reset lpnSize + EXPECT_EQ(1, lpnSize); +} + +TEST_F(GetUserNameTest, BufferSizeAsNullButNotBuffer) +{ + lpBuffer.push_back('\0'); + result = GetUserNameW(&lpBuffer[0], NULL); + + TestInvalidParameter(); +} + +TEST_F(GetUserNameTest, BufferSizeAsZero) +{ + TestWithSize(0); + TestInvalidParameter(); + // does not reset lpnSize + EXPECT_EQ(0, lpnSize); +} + +TEST_F(GetUserNameTest, BufferSizeAsOne) +{ + // theoretically this should never fail because any non-empty + // username length will be >1 with trailing null + TestWithSize(1); + TestInsufficientBuffer(); +} + +TEST_F(GetUserNameTest, BufferSizeAsUsername) +{ + // the buffer is too small because it does not account for null + TestWithSize(expectedUsername.length()); + TestInsufficientBuffer(); +} + +TEST_F(GetUserNameTest, BufferSizeAsUsernamePlusOne) +{ + // includes null and so should be sufficient + TestWithSize(expectedUsername.length()+1); + TestSuccess(); +} + +TEST_F(GetUserNameTest, BufferSizeAsExpectedSize) +{ + // expectedSize is the same as username.size()+1 + TestWithSize(expectedSize); + TestSuccess(); +} + +TEST_F(GetUserNameTest, BufferSizeAsLoginNameMax) +{ + // LoginNameMax is big enough to hold any username, including null + TestWithSize(LOGIN_NAME_MAX); + TestSuccess(); +}