diff --git a/CMakeLists.txt b/CMakeLists.txt index ae57dc376..d799e590b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,16 +8,16 @@ include_directories(../ext-src/gtest/fused-src impl) link_directories(${monad_native_BINARY_DIR}) # source file definitions -set(LIB_SOURCE_FILES impl/getcurrentprocessorid.cpp impl/getusername.cpp impl/terminal.cpp) +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-getusername.cpp ${HOST_COMMON_SOURCE_FILES} ${HOST_COMMON_TEST_SOURCE_FILES}) +set(TEST_SOURCE_FILES tests/test-getcurrentprocessid.cpp tests/test-getusername.cpp tests/test-getcomputername.cpp ${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}) + # target definitions add_library(ps SHARED ${LIB_SOURCE_FILES}) - add_executable(monad_native ${SOURCE_FILES} ${TEST_SOURCE_FILES}) add_executable(host_cmdline ${HOST_CMDLINE_SOURCE_FILES}) diff --git a/impl/getcomputername.cpp b/impl/getcomputername.cpp new file mode 100644 index 000000000..818623ddf --- /dev/null +++ b/impl/getcomputername.cpp @@ -0,0 +1,127 @@ +//! @file getcomputername.cpp +//! @author Aaron Katz +//! @brief Implements GetComputerName Win32 API + +#include +#include +#include +#include +#include +#include +#include "getcomputername.h" + +//! @brief GetComputerName retrieves the name of the host associated with +//! the current thread. +//! +//! GetComputerNameW is the Unicode variation. See [MSDN documentation]. +//! +//! @param[out] lpBuffer +//! @parblock +//! A pointer to the buffer to receive the hosts's +//! name. If this buffer is not large enough to contain the +//! entire user hosts 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 TRUE 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 FALSE 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/ms724295%28v=vs.85%29.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 GetComputerNameW(WCHAR_T* lpBuffer, LPDWORD lpnSize) +{ + errno = 0; + + // Check parameters + if (!lpBuffer || !lpnSize) + { + errno = ERROR_INVALID_PARAMETER; + return FALSE; + } + + // Select locale from environment + setlocale(LC_ALL, ""); + // Check that locale is UTF-8 + if (nl_langinfo(CODESET) != std::string("UTF-8")) + { + errno = ERROR_BAD_ENVIRONMENT; + return FALSE; + } + + // Get computername from system in a thread-safe manner + std::string computername(HOST_NAME_MAX, 0); + int err = gethostname(&computername[0], computername.length()); + // Map errno to Win32 Error Codes + if (err != 0) + { + switch (errno) + { + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return FALSE; + } + + // Convert to UnicodeString + auto computername16 = icu::UnicodeString::fromUTF8(computername.c_str()); + // Terminate string with null + computername16.append('\0'); + + // Size in WCHARs including null + const DWORD size = computername16.length(); + + //Check if parameters passed enough buffer space + if (size > *lpnSize) + { + errno = ERROR_INSUFFICIENT_BUFFER; + // Set lpnSize if buffer is too small to inform user + // of necessary size + *lpnSize= size; + return FALSE; + } + + // Extract string as UTF-16LE to buffer + computername16.extract(0, size, reinterpret_cast(lpBuffer), "UTF-16LE"); + + *lpnSize = size; + return TRUE; + +} + diff --git a/impl/getcomputername.h b/impl/getcomputername.h new file mode 100644 index 000000000..8d39d3f9f --- /dev/null +++ b/impl/getcomputername.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +BOOL GetComputerNameW(WCHAR_T* lpBuffer, LPDWORD lpnSize); + +PAL_END_EXTERNC \ No newline at end of file diff --git a/impl/pal.h b/impl/pal.h index f30903a2f..8bf54dbce 100644 --- a/impl/pal.h +++ b/impl/pal.h @@ -78,6 +78,8 @@ #define ERROR_NO_SUCH_USER 0x00000525 #define ERROR_INVALID_FUNCTION 0x00000001 #define MAX_PATH 0x00000104 + #define ERROR_INVALID_ADDRESS 0x000001e7 + #define ERROR_GEN_FAILURE 0x0000001F typedef unsigned long long uint64; #endif diff --git a/tests/test-getcomputername.cpp b/tests/test-getcomputername.cpp new file mode 100644 index 000000000..51ebd0ccc --- /dev/null +++ b/tests/test-getcomputername.cpp @@ -0,0 +1,134 @@ +//! @file test-getcomputername.cpp +//! @author Aaron Katz +//! @brief Unit tests for GetComputerNameW + +#include +#include +#include +#include +#include +#include +#include "getcomputername.h" + +//! Test fixture for GetComputerNameTest +class GetComputerNameTest : public ::testing::Test +{ +protected: + DWORD lpnSize; + std::vector lpBuffer; + BOOL result; + std::string expectedComputerName; + DWORD expectedSize; + + //Get expected result from using linux call + GetComputerNameTest() + { + expectedComputerName.resize(HOST_NAME_MAX); + BOOL ret = gethostname(&expectedComputerName[0], expectedComputerName.length()); + EXPECT_EQ(0, ret); + expectedSize = std::strlen(expectedComputerName.c_str()) + 1; + expectedComputerName.resize(expectedSize - 1); + } + + //! Invokes GetComputerNameW 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; + lpBuffer.assign(lpnSize, 0); + result = GetComputerNameW(&lpBuffer[0], &lpnSize); + } + + void TestSuccess() + { + SCOPED_TRACE(""); + + //! Returns TRUE on success. + EXPECT_EQ(TRUE, result); + + //! Sets lpnSize to number of WCHARs including null. + ASSERT_EQ(expectedSize, lpnSize); + + // Read lpBuffer into UnicodeString (without null) + const char* begin = reinterpret_cast(&lpBuffer[0]); + icu::UnicodeString computername16(begin, (lpnSize-1)*sizeof(UChar), "UTF-16LE"); + // Convert to UTF-8 for comparison + std::string computername; + computername16.toUTF8String(computername); + + ASSERT_EQ(expectedSize, computername.length() + 1); + + //! Returned computername(after conversion) is what was expected. + EXPECT_EQ(expectedComputerName, computername); + } + + void TestInvalidParameter() + { + SCOPED_TRACE(""); + + // returns FALSE on failure + EXPECT_EQ(FALSE, result); + + // sets errno to ERROR_INVALID_PARAMETER when lpBuffer is null + EXPECT_EQ(errno, ERROR_INVALID_PARAMETER); + } + + 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 + null + ASSERT_EQ(expectedSize, lpnSize); + } +}; + +TEST_F(GetComputerNameTest, BufferAsNullButNotBufferSize) +{ + lpnSize = 1; + result = GetComputerNameW(NULL, &lpnSize); + TestInvalidParameter(); + // does not reset lpnSize + EXPECT_EQ(1, lpnSize); +} + +TEST_F(GetComputerNameTest, BufferSizeAsNullButNotBuffer) +{ + lpBuffer.push_back('\0'); + result = GetComputerNameW(&lpBuffer[0], NULL); + TestInvalidParameter(); +} + +TEST_F(GetComputerNameTest, BufferSizeAsZero) +{ + TestWithSize(0); + EXPECT_EQ(errno, ERROR_INVALID_PARAMETER); +} + +TEST_F(GetComputerNameTest, BufferSizeAsUserNameMinusOne) +{ + // the buffer is also too small + TestWithSize(expectedSize-1); + TestInsufficientBuffer(); +} + +TEST_F(GetComputerNameTest, BufferSizeAsUserNamePlusOne) +{ + // the buffer is exactly big enough + TestWithSize(expectedSize+1); + TestSuccess(); +} + +TEST_F(GetComputerNameTest, BufferSizeAsLoginNameMax) +{ + // the buffer larger than needed + TestWithSize(HOST_NAME_MAX); + TestSuccess(); +} \ No newline at end of file