diff --git a/src/libpsl-native/.gitignore b/src/libpsl-native/.gitignore new file mode 100644 index 0000000000..0dff693725 --- /dev/null +++ b/src/libpsl-native/.gitignore @@ -0,0 +1,12 @@ +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake +Testing/ +test/psl-native-test +src/libpsl-native.so +src/libpsl-native.dylib +test/native-tests.xml diff --git a/src/libpsl-native/.gitmodules b/src/libpsl-native/.gitmodules new file mode 100644 index 0000000000..7cdedd8478 --- /dev/null +++ b/src/libpsl-native/.gitmodules @@ -0,0 +1,4 @@ +[submodule "test/googletest"] + path = test/googletest + url = https://github.com/google/googletest.git + ignore = dirty diff --git a/src/libpsl-native/CMakeLists.txt b/src/libpsl-native/CMakeLists.txt new file mode 100644 index 0000000000..d0e83e6bba --- /dev/null +++ b/src/libpsl-native/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.4) +project(PSL-NATIVE) + +add_compile_options(-std=c++11 -Wall -Werror) +set(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/../Microsoft.PowerShell.Linux.Host") + +# test in BUILD_DIR +enable_testing() +add_subdirectory(src) +add_subdirectory(test) diff --git a/src/libpsl-native/README.md b/src/libpsl-native/README.md new file mode 100644 index 0000000000..930ea3630d --- /dev/null +++ b/src/libpsl-native/README.md @@ -0,0 +1,56 @@ +# libpsl-native + +This library provides functionality missing from .NET Core via system calls, +that are called from from the `CorePsPlatform.cs` file of PowerShell. The +method to do this is a Platform Invoke, which is C#'s Foreign Function +Interface to C code (and C++ by way of `extern C`). + +## Build + +[CMake][] is used to build the project, which results in a `libpsl-native.so` +library on Linux, and `libpsl-native.dylib` on OS X. + +```sh +cmake -DCMAKE_BUILD_TYPE=Debug . +make -j +``` + +[CMake]: https://cmake.org/cmake/help/v2.8.12/cmake.html + +## Test + +The [Google Test][] framework is used for unit tests. + +Use either `make test` or `ctest --verbose` for more output. + +[Google Test]: https://github.com/google/googletest/tree/release-1.7.0 + +## Notes + +Marshalling data from native to managed code is much easier on Linux than it is +on Windows. For instance, to return a string, you simply return a copy of it on +the heap. Since only one memory allocator is used on Linux, the .NET runtime +has no problem later freeing the buffer. Additionally, .NET presumes that the +codepage "Ansi" on Linux is always UTF-8. So just marshal the string as +`UnmanagedType.LPStr`. + +### C# (Managed) + +```c# +[DllImport("libpsl-native", CharSet = CharSet.Ansi)] +[return: MarshalAs(UnmanagedType.LPStr)] +internal static extern string GetSomeString(); +``` + +### C (Native) + +```c +char *GetSomeString() +{ + return strdup("some string"); +} +``` + +The CoreFX team has an excellent guide for [UNIX Interop][]. + +[UNIX Interop]: https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/interop-guidelines.md#unix-shims diff --git a/src/libpsl-native/src/CMakeLists.txt b/src/libpsl-native/src/CMakeLists.txt new file mode 100644 index 0000000000..5b2ec24199 --- /dev/null +++ b/src/libpsl-native/src/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(psl-native SHARED + getcurrentprocessorid.cpp + getusername.cpp + getcomputername.cpp + getlinkcount.cpp + getfullyqualifiedname.cpp + issymlink.cpp + isexecutable.cpp + setdate.cpp + createhardlink.cpp + createsymlink.cpp + followsymlink.cpp) + +target_include_directories(psl-native PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/libpsl-native/src/createhardlink.cpp b/src/libpsl-native/src/createhardlink.cpp new file mode 100644 index 0000000000..2f131efaa5 --- /dev/null +++ b/src/libpsl-native/src/createhardlink.cpp @@ -0,0 +1,115 @@ +//! @file createsymlink.cpp +//! @author George FLeming +//! @brief create new hard link + +#include +#include +#include +#include "createhardlink.h" + +//! @brief Createhardlink create new symbolic link +//! +//! Createhardlink +//! +//! @param[in] link +//! @parblock +//! A pointer to the buffer that contains the symbolic link to create +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[in] target +//! @parblock +//! A pointer to the buffer that contains the existing file +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_TOO_MANY_LINK: max number of hard links has been exceeded +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if creation is successful +//! @retval 0 if createion failed +//! + +int32_t CreateHardLink(const char *newlink, const char *target) +{ + errno = 0; + + // Check parameters + if (!newlink || !target) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + int returnCode = link(target, newlink); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EDQUOT: + errno = ERROR_DISK_FULL; + break; + case EEXIST: + errno = ERROR_FILE_EXISTS; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_TOO_MANY_LINKS; + break; + case EMLINK: + errno = ERROR_TOO_MANY_LINKS; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case ENOSPC: + errno = ERROR_DISK_FULL; + break; + case EPERM: + errno = ERROR_ACCESS_DENIED; + break; + case EROFS: + errno = ERROR_ACCESS_DENIED; + break; + case EXDEV: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; +} diff --git a/src/libpsl-native/src/createhardlink.h b/src/libpsl-native/src/createhardlink.h new file mode 100644 index 0000000000..b8e62ddcc2 --- /dev/null +++ b/src/libpsl-native/src/createhardlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t CreateHardLink(const char *link, const char *target); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/createsymlink.cpp b/src/libpsl-native/src/createsymlink.cpp new file mode 100644 index 0000000000..4ea8d6fd9d --- /dev/null +++ b/src/libpsl-native/src/createsymlink.cpp @@ -0,0 +1,106 @@ +//! @file createsymlink.cpp +//! @author George FLeming +//! @brief create new symbolic link + +#include +#include +#include +#include "createsymlink.h" + +//! @brief Createsymlink create new symbolic link +//! +//! Createsymlink +//! +//! @param[in] link +//! @parblock +//! A pointer to the buffer that contains the symbolic link to create +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[in] target +//! @parblock +//! A pointer to the buffer that contains the existing file +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if creation is successful +//! @retval 0 if createion failed +//! + +int32_t CreateSymLink(const char *link, const char *target) +{ + errno = 0; + + // Check parameters + if (!link || !target) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + int returnCode = symlink(target, link); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EDQUOT: + errno = ERROR_DISK_FULL; + break; + case EEXIST: + errno = ERROR_FILE_EXISTS; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case ENOSPC: + errno = ERROR_DISK_FULL; + break; + case EPERM: + errno = ERROR_GEN_FAILURE; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; +} diff --git a/src/libpsl-native/src/createsymlink.h b/src/libpsl-native/src/createsymlink.h new file mode 100644 index 0000000000..e47bce3fa1 --- /dev/null +++ b/src/libpsl-native/src/createsymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t CreateSymLink(const char *link, const char *target); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/followsymlink.cpp b/src/libpsl-native/src/followsymlink.cpp new file mode 100644 index 0000000000..40e12b9b78 --- /dev/null +++ b/src/libpsl-native/src/followsymlink.cpp @@ -0,0 +1,90 @@ +//! @file followSymLink.cpp +//! @author George FLeming +//! @brief returns whether a path is a symbolic link + +#include +#include +#include +#include +#include "followsymlink.h" + +//! @brief Followsymlink determines target path of a sym link +//! +//! Followsymlink +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: too many symbolic links +//! - ERROR_GEN_FAILURE: I/O error occurred +//! - ERROR_INVALID_NAME: file provided is not a symbolic link +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long +//! - ERROR_OUTOFMEMORY insufficient kernal memory +//! +//! @retval target path, or NULL if unsuccessful +//! + +char* FollowSymLink(const char* fileName) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return NULL; + } + + char buffer[PATH_MAX]; + ssize_t sz = readlink(fileName, buffer, PATH_MAX); + + if (sz == -1) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case EINVAL: + errno = ERROR_INVALID_NAME; + break; + case EIO: + errno = ERROR_GEN_FAILURE; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_BAD_PATH_NAME; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_OUTOFMEMORY; + break; + case ENOTDIR: + errno = ERROR_BAD_PATH_NAME; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return NULL; + } + + buffer[sz] = '\0'; + return strndup(buffer, sz + 1); +} diff --git a/src/libpsl-native/src/followsymlink.h b/src/libpsl-native/src/followsymlink.h new file mode 100644 index 0000000000..fac7f5244f --- /dev/null +++ b/src/libpsl-native/src/followsymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char* FollowSymLink(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getcomputername.cpp b/src/libpsl-native/src/getcomputername.cpp new file mode 100644 index 0000000000..3e480b9ad4 --- /dev/null +++ b/src/libpsl-native/src/getcomputername.cpp @@ -0,0 +1,45 @@ +//! @file getcomputername.cpp +//! @author George Fleming +//! @brief Implements GetComputerName Win32 API + +#include +#include +#include +#include "getcomputername.h" + +//! @brief GetComputerName retrieves the name of the host associated with +//! the current thread. +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_FUNCTION: getlogin_r() returned an unrecognized error code +//! - ERROR_INVALID_ADDRESS: buffer is an invalid address +//! - ERROR_GEN_FAILURE: buffer not large enough +//! +//! @retval username as UTF-8 string, or null if unsuccessful + +char* GetComputerName() +{ + errno = 0; + // Get computername from system, note that gethostname(2) gets the + // nodename from uname + std::string computername(_POSIX_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 NULL; + } + + return strdup(computername.c_str()); +} diff --git a/src/libpsl-native/src/getcomputername.h b/src/libpsl-native/src/getcomputername.h new file mode 100644 index 0000000000..363210921b --- /dev/null +++ b/src/libpsl-native/src/getcomputername.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char *GetComputerName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getcurrentprocessorid.cpp b/src/libpsl-native/src/getcurrentprocessorid.cpp new file mode 100644 index 0000000000..eea80937b9 --- /dev/null +++ b/src/libpsl-native/src/getcurrentprocessorid.cpp @@ -0,0 +1,9 @@ +#include "getcurrentprocessorid.h" +#include + +int32_t GetCurrentProcessId() +{ + pid_t pid = getpid(); + return static_cast(pid); +} + diff --git a/src/libpsl-native/src/getcurrentprocessorid.h b/src/libpsl-native/src/getcurrentprocessorid.h new file mode 100644 index 0000000000..3c768e72e5 --- /dev/null +++ b/src/libpsl-native/src/getcurrentprocessorid.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t GetCurrentProcessId(); + +PAL_END_EXTERNC + diff --git a/src/libpsl-native/src/getcurrentthreadid.cpp b/src/libpsl-native/src/getcurrentthreadid.cpp new file mode 100644 index 0000000000..efd7a31361 --- /dev/null +++ b/src/libpsl-native/src/getcurrentthreadid.cpp @@ -0,0 +1,9 @@ +#include "getcurrentthreadid.h" +#include +#include + +HANDLE GetCurrentThreadId() +{ + pid_t tid = pthread_self(); + return reinterpret_cast(tid); +} diff --git a/src/libpsl-native/src/getcurrentthreadid.h b/src/libpsl-native/src/getcurrentthreadid.h new file mode 100644 index 0000000000..258c3c397f --- /dev/null +++ b/src/libpsl-native/src/getcurrentthreadid.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +HANDLE GetCurrentThreadId(); + +PAL_END_EXTERNC + diff --git a/src/libpsl-native/src/getfullyqualifiedname.cpp b/src/libpsl-native/src/getfullyqualifiedname.cpp new file mode 100644 index 0000000000..44178a0a5a --- /dev/null +++ b/src/libpsl-native/src/getfullyqualifiedname.cpp @@ -0,0 +1,64 @@ +//! @file getfullyqualifiedname.cpp +//! @author George Fleming +//! @brief Implements GetFullyQualifiedName on Linux + +#include +#include +#include +#include +#include "getcomputername.h" +#include "getfullyqualifiedname.h" + +//! @brief GetFullyQualifiedName retrieves the fully qualifed dns name of the host +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_FUNCTION: getlogin_r() returned an unrecognized error code (from GetComputerName) +//! - ERROR_INVALID_ADDRESS: buffer is an invalid address (from GetComputerName) +//! - ERROR_GEN_FAILURE: buffer not large enough (from GetComputerName) +//! - ERROR_BAD_NET_NAME: Cannot determine network short name +//! +//! @retval username as UTF-8 string, or null if unsuccessful +//! + +char *GetFullyQualifiedName() +{ + errno = 0; + + char *computerName = GetComputerName(); + if (computerName == NULL) + { + return NULL; + } + + if (strchr(computerName, '.') != NULL) + { + return computerName; + } + + struct addrinfo hints, *info; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + + /* There are several ways to get the domain name: + * uname(2), gethostbyname(3), resolver(3), getdomainname(2), + * and getaddrinfo(3). Some of these are not portable, some aren't + * POSIX compliant, and some are being deprecated. getaddrinfo seems + * to be the best choice. + */ + if (getaddrinfo(computerName, "http", &hints, &info) != 0) + { + errno = ERROR_BAD_NET_NAME; + return NULL; + } + + // info is actually a link-list. We'll just return the first full name + + char *fullName = strndup(info->ai_canonname, strlen(info->ai_canonname)); + + freeaddrinfo(info); + free(computerName); + return fullName; +} diff --git a/src/libpsl-native/src/getfullyqualifiedname.h b/src/libpsl-native/src/getfullyqualifiedname.h new file mode 100644 index 0000000000..b44ab68fd5 --- /dev/null +++ b/src/libpsl-native/src/getfullyqualifiedname.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char *GetFullyQualifiedName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getlinkcount.cpp b/src/libpsl-native/src/getlinkcount.cpp new file mode 100644 index 0000000000..1b97a41671 --- /dev/null +++ b/src/libpsl-native/src/getlinkcount.cpp @@ -0,0 +1,104 @@ +//! @file getlinkcount.cpp +//! @author George FLeming +//! @brief Retrieve link count of a file + +#include +#include +#include +#include +#include +#include +#include "getlinkcount.h" + +//! @brief GetLinkCount retrieves the file link count (number of hard links) +//! for the given file +//! +//! GetLinkCount +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @param[out] count +//! @parblock +//! This function returns the number of hard links associated with this file +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 If the function succeeds, and the variable pointed to by buffer contains +//! infomation about the files +//! @retval 0 If the function fails, the return value is zero. To get +//! extended error information, call GetLastError. +//! + +int32_t GetLinkCount(const char* fileName, int32_t *count) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return 0; + } + + struct stat statBuf; + + int returnCode = lstat(fileName, &statBuf); + + if (returnCode != 0) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EBADF: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_NO_SUCH_USER; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case EOVERFLOW: + errno = ERROR_BUFFER_OVERFLOW; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return 0; + } + + *count = statBuf.st_nlink; + return 1; +} diff --git a/src/libpsl-native/src/getlinkcount.h b/src/libpsl-native/src/getlinkcount.h new file mode 100644 index 0000000000..6a3c8b8e41 --- /dev/null +++ b/src/libpsl-native/src/getlinkcount.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t GetLinkCount(const char* fileName, int32_t *count); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/getusername.cpp b/src/libpsl-native/src/getusername.cpp new file mode 100644 index 0000000000..0efab96688 --- /dev/null +++ b/src/libpsl-native/src/getusername.cpp @@ -0,0 +1,65 @@ +//! @file getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Implements GetUserName for Linux + +#include +#include +#include +#include +#include +#include "getusername.h" + +//! @brief GetUserName retrieves the name of the user associated with +//! the current thread. +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_NO_SUCH_USER: there was no corresponding user +//! - ERROR_GEN_FAILURE: sysconf() or getpwuid() failed for unknown reasons +//! +//! @retval username as UTF-8 string, or null if unsuccessful +char* GetUserName() +{ + errno = 0; + + struct passwd pwd; + struct passwd* result; + // gets the initial suggested size for buf + int buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buflen == -1) + { + errno = ERROR_GEN_FAILURE; + return NULL; + } + std::string buf(buflen, 0); + + // geteuid() gets the effective user ID of the calling process, and is always successful + int ret = getpwuid_r(geteuid(), &pwd, &buf[0], buflen, &result); + + // Map errno to Win32 Error Codes + if (ret) + { + switch (errno) + { + case ENOENT: + case ESRCH: + case EBADF: + case EPERM: + errno = ERROR_NO_SUCH_USER; + break; + default: + errno = ERROR_GEN_FAILURE; + } + return NULL; + } + + // Check if no user matched + if (result == NULL) + { + errno = ERROR_NO_SUCH_USER; + return NULL; + } + + // allocate copy on heap so CLR can free it + return strdup(result->pw_name); +} diff --git a/src/libpsl-native/src/getusername.h b/src/libpsl-native/src/getusername.h new file mode 100644 index 0000000000..1326298f42 --- /dev/null +++ b/src/libpsl-native/src/getusername.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +char* GetUserName(); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/isexecutable.cpp b/src/libpsl-native/src/isexecutable.cpp new file mode 100644 index 0000000000..38f6ad1844 --- /dev/null +++ b/src/libpsl-native/src/isexecutable.cpp @@ -0,0 +1,81 @@ +//! @file isExecutable.cpp +//! @author George FLeming +//! @brief returns whether a file is executable + +#include +#include +#include +#include "isexecutable.h" + +//! @brief IsExecutable determines if path is executable +//! +//! IsExecutable +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_INVALID_PARAMETER: parameter to access(2) call is incorrect +//! +//! @retval 1 if path is an executable +//! @retval 0 if path is not a executable +//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! + +int32_t IsExecutable(const char* fileName) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + int returnCode = access(fileName, X_OK); + + if (returnCode == 0) + { + return 1; + } + + switch(errno) + { + case EACCES: + return 0; + case EBADF: + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case EIO: + case ENOMEM: + errno = ERROR_GEN_FAILURE; + break; + case ENOTDIR: + case ENAMETOOLONG: + errno = ERROR_INVALID_NAME; + break; + case EINVAL: + errno = ERROR_INVALID_PARAMETER; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return -1; +} diff --git a/src/libpsl-native/src/isexecutable.h b/src/libpsl-native/src/isexecutable.h new file mode 100644 index 0000000000..da4e9f8f36 --- /dev/null +++ b/src/libpsl-native/src/isexecutable.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t IsExecutable(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/issymlink.cpp b/src/libpsl-native/src/issymlink.cpp new file mode 100644 index 0000000000..d4ce011cba --- /dev/null +++ b/src/libpsl-native/src/issymlink.cpp @@ -0,0 +1,96 @@ +//! @file isSymLink.cpp +//! @author George FLeming +//! @brief returns whether a path is a symbolic link + +#include +#include +#include +#include +#include +#include "issymlink.h" + +//! @brief IsSymLink determines if path is a symbolic link +//! +//! IsSymLink +//! +//! @param[in] fileName +//! @parblock +//! A pointer to the buffer that contains the file name +//! +//! char* is marshaled as an LPStr, which on Linux is UTF-8. +//! @endparblock +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_INVALID_PARAMETER: parameter is not valid +//! - ERROR_FILE_NOT_FOUND: file does not exist +//! - ERROR_ACCESS_DENIED: access is denied +//! - ERROR_FILE_NOT_FOUND: the system cannot find the file specified +//! - ERROR_INVALID_ADDRESS: attempt to access invalid address +//! - ERROR_STOPPED_ON_SYMLINK: the operation stopped after reaching a symbolic link +//! - ERROR_GEN_FAILURE: device attached to the system is not functioning +//! - ERROR_NO_SUCH_USER: there was no corresponding entry in the utmp-file +//! - ERROR_INVALID_NAME: filename, directory name, or volume label syntax is incorrect +//! - ERROR_BUFFER_OVERFLOW: file name is too long +//! - ERROR_INVALID_FUNCTION: incorrect function +//! - ERROR_BAD_PATH_NAME: pathname is too long, or contains invalid characters +//! +//! @retval 1 if path is a symbolic link +//! @retval 0 if path is not a symbolic link +//! @retval -1 If the function fails.. To get extended error information, call GetLastError. +//! + +int32_t IsSymLink(const char* fileName) +{ + + errno = 0; + + // Check parameters + if (!fileName) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + struct stat statBuf; + + int returnCode = lstat(fileName, &statBuf); + + if (returnCode != 0) + { + switch(errno) + { + case EACCES: + errno = ERROR_ACCESS_DENIED; + break; + case EBADF: + errno = ERROR_FILE_NOT_FOUND; + break; + case EFAULT: + errno = ERROR_INVALID_ADDRESS; + break; + case ELOOP: + errno = ERROR_STOPPED_ON_SYMLINK; + break; + case ENAMETOOLONG: + errno = ERROR_GEN_FAILURE; + break; + case ENOENT: + errno = ERROR_FILE_NOT_FOUND; + break; + case ENOMEM: + errno = ERROR_NO_SUCH_USER; + break; + case ENOTDIR: + errno = ERROR_INVALID_NAME; + break; + case EOVERFLOW: + errno = ERROR_BUFFER_OVERFLOW; + break; + default: + errno = ERROR_INVALID_FUNCTION; + } + return -1; + } + + return S_ISLNK(statBuf.st_mode) ? 1 : 0; +} diff --git a/src/libpsl-native/src/issymlink.h b/src/libpsl-native/src/issymlink.h new file mode 100644 index 0000000000..066a4060c1 --- /dev/null +++ b/src/libpsl-native/src/issymlink.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +int32_t IsSymLink(const char* fileName); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/src/pal.h b/src/libpsl-native/src/pal.h new file mode 100644 index 0000000000..39fb6db82c --- /dev/null +++ b/src/libpsl-native/src/pal.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +#define ERROR_INVALID_PARAMETER 87 +#define ERROR_OUTOFMEMORY 14 +#define ERROR_BAD_ENVIRONMENT 0x0000000A +#define ERROR_TOO_MANY_OPEN_FILES 0x00000004 +#define ERROR_INSUFFICIENT_BUFFER 0x0000007A +#define ERROR_NO_ASSOCIATION 0x00000483 +#define ERROR_NO_SUCH_USER 0x00000525 +#define ERROR_INVALID_FUNCTION 0x00000001 +#define ERROR_INVALID_ADDRESS 0x000001e7 +#define ERROR_GEN_FAILURE 0x0000001F +#define ERROR_ACCESS_DENIED 0x00000005 +#define ERROR_INVALID_NAME 0x0000007B +#define ERROR_STOPPED_ON_SYMLINK 0x000002A9 +#define ERROR_BUFFER_OVERFLOW 0x0000006F +#define ERROR_FILE_NOT_FOUND 0x00000002 +#define ERROR_BAD_PATH_NAME 0x000000A1 +#define ERROR_BAD_NET_NAME 0x00000043 +#define ERROR_DISK_FULL 0x00000070 +#define ERROR_FILE_EXISTS 0x00000050 +#define ERROR_TOO_MANY_LINKS 0x00000476 + +/* +**============================================================================== +** +** PAL_BEGIN_EXTERNC +** PAL_END_EXTERNC +** +**============================================================================== +*/ + +#if defined(__cplusplus) +# define PAL_BEGIN_EXTERNC extern "C" { +# define PAL_END_EXTERNC } +#else +# define PAL_BEGIN_EXTERNC +# define PAL_END_EXTERNC +#endif + diff --git a/src/libpsl-native/src/setdate.cpp b/src/libpsl-native/src/setdate.cpp new file mode 100644 index 0000000000..81bfa3417e --- /dev/null +++ b/src/libpsl-native/src/setdate.cpp @@ -0,0 +1,73 @@ +//! @file setdate.cpp +//! @author George FLeming +//! @brief set local/system date and time + +#include +#include +#include +#include +#include +#include +#include "setdate.h" + +//! @brief SetDate sets the date and time on local computer. You must +//! be super-user to set the time. +//! +//! SetDate +//! +//! @param[in] info +//! @parblock +//! A struct that contains program to execute and its parameters +//! +//! @exception errno Passes these errors via errno to GetLastError: +//! - ERROR_BAD_ENVIRONMENT: locale is not UTF-8 +//! - ERROR_INVALID_PARAMETER: time was not passed in correctly +//! - ERROR_ACCESS_DENIED: you must be super-user to set the date +//! +//! @retval 0 successfully set date +//! @retval -1 if failure occurred. To get extended error information, call GetLastError. +//! + +int32_t SetDate(const SetDateInfo &info) +{ + errno = 0; + + // 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 -1; + } + + struct tm bdTime; + struct timeval tv; + + bdTime.tm_year = info.Year - 1900; + bdTime.tm_mon = info.Month - 1; // This is zero-based + bdTime.tm_mday = info.Day; + bdTime.tm_hour = info.Hour; + bdTime.tm_min = info.Minute; + bdTime.tm_sec = info.Second; + bdTime.tm_isdst = info.DST; + + time_t newTime = mktime(&bdTime); + if (newTime == -1) + { + errno = ERROR_INVALID_PARAMETER; + return -1; + } + + tv.tv_sec = newTime; + tv.tv_usec = 0; + + int result = settimeofday(&tv, NULL); + if (result == -1) + { + errno = ERROR_ACCESS_DENIED; + return -1; + } + + return 0; +} diff --git a/src/libpsl-native/src/setdate.h b/src/libpsl-native/src/setdate.h new file mode 100644 index 0000000000..b55a7d1e7a --- /dev/null +++ b/src/libpsl-native/src/setdate.h @@ -0,0 +1,22 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +typedef struct setDateInfo +{ + // the order of members does matter here + int32_t Year; + int32_t Month; + int32_t Day; + int32_t Hour; + int32_t Minute; + int32_t Second; + int32_t Millisecond; + int32_t DST; +} SetDateInfo; + +int32_t SetDate(const SetDateInfo &info); + +PAL_END_EXTERNC diff --git a/src/libpsl-native/test/CMakeLists.txt b/src/libpsl-native/test/CMakeLists.txt new file mode 100644 index 0000000000..4ddc538361 --- /dev/null +++ b/src/libpsl-native/test/CMakeLists.txt @@ -0,0 +1,22 @@ +add_subdirectory(googletest) + +add_executable(psl-native-test + test-locale.cpp + test-getcurrentprocessid.cpp + test-getusername.cpp + test-getcomputername.cpp + test-getlinkcount.cpp + test-getfullyqualifiedname.cpp + test-issymlink.cpp + test-isexecutable.cpp + test-createsymlink.cpp + test-createhardlink.cpp + main.cpp) + +# manually include gtest headers +target_include_directories(psl-native-test PRIVATE ${gtest_SOURCE_DIR}/include) + +target_link_libraries(psl-native-test psl-native gtest) + +add_test(NAME psl-native-test + COMMAND psl-native-test --gtest_output=xml:native-tests.xml) diff --git a/src/libpsl-native/test/googletest b/src/libpsl-native/test/googletest new file mode 160000 index 0000000000..c99458533a --- /dev/null +++ b/src/libpsl-native/test/googletest @@ -0,0 +1 @@ +Subproject commit c99458533a9b4c743ed51537e25989ea55944908 diff --git a/src/libpsl-native/test/main.cpp b/src/libpsl-native/test/main.cpp new file mode 100644 index 0000000000..9bb465e024 --- /dev/null +++ b/src/libpsl-native/test/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/libpsl-native/test/test-createhardlink.cpp b/src/libpsl-native/test/test-createhardlink.cpp new file mode 100644 index 0000000000..090864b9f7 --- /dev/null +++ b/src/libpsl-native/test/test-createhardlink.cpp @@ -0,0 +1,91 @@ +//! @file test-createhardlink.cpp +//! @author George Fleming +//! @brief Implements test for CreateHardLink() + +#include +#include +#include +#include "getlinkcount.h" +#include "createhardlink.h" + +using namespace std; + +class CreateHardLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileHardLink = "/tmp/symlinktest.flink"; + const string dirHardLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + CreateHardLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a temp file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create hard link to file + int ret1 = CreateHardLink(fileHardLink.c_str(), file); + EXPECT_EQ(ret1, 1); + + // Create hard link to directory - should fail + int ret2 = CreateHardLink(dirHardLink.c_str(), dir); + EXPECT_EQ(ret2, 0); + } + + ~CreateHardLinkTest() + { + int ret; + + ret = unlink(fileHardLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(CreateHardLinkTest, FilePathNameIsNull) +{ + int retVal = CreateHardLink(NULL, NULL); + EXPECT_EQ(retVal, 0); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(CreateHardLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + std::string invalidLink = "/tmp/symlinktest_invalidLink"; + + // make sure neither exists + unlink(invalidFile.c_str()); + unlink(invalidLink.c_str()); + + int retVal = CreateHardLink(invalidLink.c_str(), invalidFile.c_str()); + EXPECT_EQ(retVal, 0); +} + +TEST_F(CreateHardLinkTest, VerifyLinkCount) +{ + int count = 0; + int retVal = GetLinkCount(fileHardLink.c_str(), &count); + EXPECT_EQ(1, retVal); + EXPECT_EQ(2, count); +} + diff --git a/src/libpsl-native/test/test-createsymlink.cpp b/src/libpsl-native/test/test-createsymlink.cpp new file mode 100644 index 0000000000..abc0646c61 --- /dev/null +++ b/src/libpsl-native/test/test-createsymlink.cpp @@ -0,0 +1,117 @@ +//! @file test-createsymlink.cpp +//! @author George Fleming +//! @brief Implements test for CreateSymLink() and FollowSymLink() + +#include +#include +#include +#include "issymlink.h" +#include "createsymlink.h" +#include "followsymlink.h" + +using namespace std; + +class CreateSymLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileSymLink = "/tmp/symlinktest.flink"; + const string dirSymLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + CreateSymLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a temp file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create symbolic link to file + int ret1 = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_EQ(ret1, 1); + + // Create symbolic link to directory + int ret2 = CreateSymLink(dirSymLink.c_str(), dir); + EXPECT_EQ(ret2, 1); + } + + ~CreateSymLinkTest() + { + int ret; + + ret = unlink(fileSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(dirSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(CreateSymLinkTest, FilePathNameIsNull) +{ + int retVal = CreateSymLink(NULL, NULL); + EXPECT_EQ(retVal, 0); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(CreateSymLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + std::string invalidLink = "/tmp/symlinktest_invalidLink"; + + // make sure neither exists + unlink(invalidFile.c_str()); + unlink(invalidLink.c_str()); + + // Linux allows creation of symbolic link that points to an invalid file + int retVal = CreateSymLink(invalidLink.c_str(), invalidFile.c_str()); + EXPECT_EQ(retVal, 1); + + std::string target = FollowSymLink(invalidLink.c_str()); + EXPECT_EQ(target, invalidFile); + + unlink(invalidLink.c_str()); +} + +TEST_F(CreateSymLinkTest, SymLinkToFile) +{ + int retVal = IsSymLink(fileSymLink.c_str()); + EXPECT_EQ(1, retVal); + + std::string target = FollowSymLink(fileSymLink.c_str()); + EXPECT_EQ(target, file); +} + +TEST_F(CreateSymLinkTest, SymLinkToDirectory) +{ + int retVal = IsSymLink(dirSymLink.c_str()); + EXPECT_EQ(1, retVal); + + std::string target = FollowSymLink(dirSymLink.c_str()); + EXPECT_EQ(target, dir); +} + +TEST_F(CreateSymLinkTest, SymLinkAgain) +{ + int retVal = CreateSymLink(fileSymLink.c_str(), file); + EXPECT_EQ(0, retVal); + EXPECT_EQ(ERROR_FILE_EXISTS, errno); +} diff --git a/src/libpsl-native/test/test-getcomputername.cpp b/src/libpsl-native/test/test-getcomputername.cpp new file mode 100644 index 0000000000..9c1d676cbd --- /dev/null +++ b/src/libpsl-native/test/test-getcomputername.cpp @@ -0,0 +1,33 @@ +//! @file test-getcomputername.cpp +//! @author George Fleming +//! @brief Unit tests for GetComputerName + +#include +#include "getcomputername.h" + +//! Test fixture for GetComputerNameTest +class GetComputerNameTest : public ::testing::Test +{ +}; + +TEST_F(GetComputerNameTest, Success) +{ + char expectedComputerName[_POSIX_HOST_NAME_MAX]; + + // the gethostname system call gets the nodename from uname + FILE *fPtr = popen("uname -n", "r"); + ASSERT_TRUE(fPtr != NULL); + + char *linePtr = fgets(expectedComputerName, sizeof(expectedComputerName), fPtr); + ASSERT_TRUE(linePtr != NULL); + + // There's a tendency to have \n at end of fgets string, so remove it before compare + size_t sz = strlen(expectedComputerName); + if (sz > 0 && expectedComputerName[sz - 1] == '\n') + { + expectedComputerName[sz - 1] = '\0'; + } + pclose(fPtr); + + ASSERT_STREQ(GetComputerName(), expectedComputerName); +} diff --git a/src/libpsl-native/test/test-getcurrentprocessid.cpp b/src/libpsl-native/test/test-getcurrentprocessid.cpp new file mode 100644 index 0000000000..a1d10a0bee --- /dev/null +++ b/src/libpsl-native/test/test-getcurrentprocessid.cpp @@ -0,0 +1,16 @@ +#include +#include "getcurrentprocessorid.h" + +// This is a very simple test case to show how tests can be written +TEST(GetCurrentProcessId,simple) +{ + const int32_t currentProcessId = GetCurrentProcessId(); + const pid_t pid = getpid(); + + // first make sure that on this platform those types are of the same size + ASSERT_TRUE(sizeof(int32_t) >= sizeof(pid_t)); + + // now compare the actual values + ASSERT_EQ(currentProcessId,static_cast(pid)); +} + diff --git a/src/libpsl-native/test/test-getcurrentthreadid.cpp b/src/libpsl-native/test/test-getcurrentthreadid.cpp new file mode 100644 index 0000000000..7488d192dd --- /dev/null +++ b/src/libpsl-native/test/test-getcurrentthreadid.cpp @@ -0,0 +1,16 @@ +#include +#include "getcurrentthreadid.h" +#include + +TEST(GetCurrentThreadId,simple) +{ + const HANDLE currentThreadId = GetCurrentThreadId(); + const pid_t tid = pthread_self(); + + // first make sure that on this platform those types are of the same size + ASSERT_TRUE(sizeof(HANDLE) >= sizeof(pid_t)); + + // now compare the actual values + ASSERT_EQ(currentThreadId,reinterpret_cast(tid)); +} + diff --git a/src/libpsl-native/test/test-getfullyqualifiedname.cpp b/src/libpsl-native/test/test-getfullyqualifiedname.cpp new file mode 100644 index 0000000000..7c0353cd1d --- /dev/null +++ b/src/libpsl-native/test/test-getfullyqualifiedname.cpp @@ -0,0 +1,40 @@ +//! @file test-getfullyqualifiedname.cpp +//! @author George Fleming +//! @brief Unit tests for GetFullyQualifiedName + +#include +#include "getfullyqualifiedname.h" +#include +#include +#include +#include + +//! Test fixture for GetComputerNameTest +class GetFullyQualifiedNameTest : public ::testing::Test +{ +}; + +TEST_F(GetFullyQualifiedNameTest, ValidateLinuxGetFullyQualifiedDomainName) +{ + std::string actual(GetFullyQualifiedName()); + + std::string hostname(_POSIX_HOST_NAME_MAX, 0); + ASSERT_FALSE(gethostname(&hostname[0], hostname.length())); + // trim null characters from string + hostname = std::string(hostname.c_str()); + + struct addrinfo hints, *info; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + ASSERT_FALSE(getaddrinfo(hostname.c_str(), "http", &hints, &info)); + + // Compare hostname part of FQDN + ASSERT_EQ(hostname, actual.substr(0, hostname.length())); + + // Compare canonical name to FQDN + ASSERT_EQ(info->ai_canonname, actual); + + freeaddrinfo(info); +} diff --git a/src/libpsl-native/test/test-getlinkcount.cpp b/src/libpsl-native/test/test-getlinkcount.cpp new file mode 100644 index 0000000000..fbe1a001fc --- /dev/null +++ b/src/libpsl-native/test/test-getlinkcount.cpp @@ -0,0 +1,95 @@ +//! @file test-getlinkcount.cpp +//! @author George Fleming +//! @brief Implements test for getLinkCount() + +#include +#include +#include +#include +#include +#include +#include +#include "getlinkcount.h" + +class getLinkCountTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const std::string fileTemplate = "/tmp/createFile.XXXXXX"; + char fileTemplateBuf[bufSize]; + + int32_t count; + char *file; + + getLinkCountTest() + { + // since mkstemp modifies the template string, let's give it writable buffer + strcpy(fileTemplateBuf, fileTemplate.c_str()); + + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + } + + void createFileForTesting(const std::string &theFile) + { + std::ofstream ofs; + ofs.open(theFile, std::ofstream::out); + ofs << "hi there, ms ostc!"; + ofs.close(); + } + + std::string createHardLink(const std::string &origFile) + { + std::string newFile = origFile + "_link"; + int ret = link(origFile.c_str(), newFile.c_str()); + EXPECT_EQ(0, ret); + + return newFile; + } + + void removeFile(const std::string &fileName) + { + int ret = unlink(fileName.c_str()); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(getLinkCountTest, FilePathNameIsNull) +{ + int32_t retVal = GetLinkCount(NULL, &count ); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(getLinkCountTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/createFile"; + int32_t retVal = GetLinkCount(invalidFile.c_str(), &count); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(getLinkCountTest, LinkCountOfSinglyLinkedFile) +{ + createFileForTesting(file); + int32_t retVal = GetLinkCount(file, &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(1, count); + + removeFile(file); +} + +TEST_F(getLinkCountTest, LinkCountOfMultipliLinkedFile) +{ + createFileForTesting(file); + std::string newFile = createHardLink(file); + int32_t retVal = GetLinkCount(file, &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(2, count); + + removeFile(file); + removeFile(newFile); +} + diff --git a/src/libpsl-native/test/test-getusername.cpp b/src/libpsl-native/test/test-getusername.cpp new file mode 100644 index 0000000000..2ccb29f68a --- /dev/null +++ b/src/libpsl-native/test/test-getusername.cpp @@ -0,0 +1,17 @@ +//! @file test-getusername.cpp +//! @author Andrew Schwartzmeyer +//! @brief Unit tests for GetUserName + +#include +#include +#include +#include +#include +#include "getusername.h" + +TEST(GetUserName, Success) +{ + char* expected = getpwuid(geteuid())->pw_name; + ASSERT_TRUE(expected != NULL); + ASSERT_EQ(GetUserName(), std::string(expected)); +} diff --git a/src/libpsl-native/test/test-isexecutable.cpp b/src/libpsl-native/test/test-isexecutable.cpp new file mode 100644 index 0000000000..36955912ff --- /dev/null +++ b/src/libpsl-native/test/test-isexecutable.cpp @@ -0,0 +1,94 @@ +//! @file test-isexecutable.cpp +//! @author George Fleming +//! @brief Implements test for isexecutable() + +#include +#include +#include +#include +#include "isexecutable.h" + +using namespace std; + +class IsExecutableTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/isexecutabletest.fXXXXXXX"; + const mode_t mode_700 = S_IRUSR | S_IWUSR | S_IXUSR; + const mode_t mode_070 = S_IRGRP | S_IWGRP | S_IXGRP; + const mode_t mode_007 = S_IROTH | S_IWOTH | S_IXOTH; + const mode_t mode_777 = mode_700 | mode_070 | mode_007; + const mode_t mode_444 = S_IRUSR | S_IRGRP | S_IROTH; + + char *file; + char fileTemplateBuf[bufSize]; + + IsExecutableTest() + { + // since mkstemp modifies the template string, let's give it writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + + // First create a file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + } + + ~IsExecutableTest() + { + int ret; + + ret = unlink(file); + EXPECT_EQ(0, ret); + } + + void ChangeFilePermission(const char* file, mode_t mode) + { + int ret = chmod(file, mode); + EXPECT_EQ(ret, 0); + } +}; + +TEST_F(IsExecutableTest, FilePathNameIsNull) +{ + int32_t retVal = IsExecutable(NULL); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(IsExecutableTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/isexecutabletest_invalidFile"; + int32_t retVal = IsExecutable(invalidFile.c_str()); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(IsExecutableTest, NormalFileIsNotIsexecutable) +{ + int32_t retVal = IsExecutable(file); + EXPECT_EQ(0, retVal); + + ChangeFilePermission(file, mode_444); + + retVal = IsExecutable(file); + EXPECT_EQ(0, retVal); +} + +TEST_F(IsExecutableTest, FilePermission_700) +{ + ChangeFilePermission(file, mode_700); + + int32_t retVal = IsExecutable(file); + EXPECT_EQ(1, retVal); +} + +TEST_F(IsExecutableTest, FilePermission_777) +{ + ChangeFilePermission(file, mode_777); + + int32_t retVal = IsExecutable(file); + EXPECT_EQ(1, retVal); +} diff --git a/src/libpsl-native/test/test-issymlink.cpp b/src/libpsl-native/test/test-issymlink.cpp new file mode 100644 index 0000000000..4aaa3eb541 --- /dev/null +++ b/src/libpsl-native/test/test-issymlink.cpp @@ -0,0 +1,104 @@ +//! @file test-issymlink.cpp +//! @author George Fleming +//! @brief Implements test for isSymLink() + +#include +#include +#include +#include "issymlink.h" + +using namespace std; + +class isSymLinkTest : public ::testing::Test +{ +protected: + + static const int bufSize = 64; + const string fileTemplate = "/tmp/symlinktest.fXXXXXX"; + const string dirTemplate = "/tmp/symlinktest.dXXXXXX"; + const string fileSymLink = "/tmp/symlinktest.flink"; + const string dirSymLink = "/tmp/symlinktest.dlink"; + char *file, *dir; + char fileTemplateBuf[bufSize], dirTemplateBuf[bufSize]; + + isSymLinkTest() + { + // since mkstemp and mkdtemp modifies the template string, let's give them writable buffers + strcpy(fileTemplateBuf, fileTemplate.c_str()); + strcpy(dirTemplateBuf, dirTemplate.c_str()); + + // First create a file + int fd = mkstemp(fileTemplateBuf); + EXPECT_TRUE(fd != -1); + file = fileTemplateBuf; + + // Create a temp directory + dir = mkdtemp(dirTemplateBuf); + EXPECT_TRUE(dir != NULL); + + // Create symbolic link to file + int ret1 = symlink(file, fileSymLink.c_str()); + EXPECT_EQ(ret1, 0); + + // Create symbolic link to directory + int ret2 = symlink(dir, dirSymLink.c_str()); + EXPECT_EQ(ret2, 0); + } + + ~isSymLinkTest() + { + int ret; + + ret = unlink(fileSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(dirSymLink.c_str()); + EXPECT_EQ(0, ret); + + ret = unlink(file); + EXPECT_EQ(0, ret); + + ret = rmdir(dir); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(isSymLinkTest, FilePathNameIsNull) +{ + int retVal = IsSymLink(NULL); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(isSymLinkTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/symlinktest_invalidFile"; + int retVal = IsSymLink(invalidFile.c_str()); + EXPECT_EQ(retVal, -1); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(isSymLinkTest, NormalFileIsNotSymLink) +{ + int retVal = IsSymLink(file); + EXPECT_EQ(0, retVal); +} + +TEST_F(isSymLinkTest, SymLinkToFile) +{ + int retVal = IsSymLink(fileSymLink.c_str()); + EXPECT_EQ(1, retVal); +} + +TEST_F(isSymLinkTest, NormalDirectoryIsNotSymbLink) +{ + int retVal = IsSymLink(dir); + EXPECT_EQ(0, retVal); +} + +TEST_F(isSymLinkTest, SymLinkToDirectory) +{ + int retVal = IsSymLink(dirSymLink.c_str()); + EXPECT_EQ(1, retVal); +} + diff --git a/src/libpsl-native/test/test-locale.cpp b/src/libpsl-native/test/test-locale.cpp new file mode 100644 index 0000000000..043e98910e --- /dev/null +++ b/src/libpsl-native/test/test-locale.cpp @@ -0,0 +1,21 @@ +//! @file test-locale.cpp +//! @author Alex Jordan +//! @brief Unit tests for linux locale + +#include +#include +#include +#include +#include +//! Test fixture for LocaleTest + +class LocaleTest : public ::testing::Test +{ +}; + +TEST_F(LocaleTest, Success) +{ + setlocale (LC_ALL, ""); + ASSERT_FALSE (nl_langinfo(CODESET) == NULL); + ASSERT_TRUE(nl_langinfo(CODESET) == std::string("UTF-8")); +}