diff --git a/impl/CMakeLists.txt b/impl/CMakeLists.txt index 0eeb2e8fa..5b9c123fb 100644 --- a/impl/CMakeLists.txt +++ b/impl/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(ps SHARED getcurrentprocessorid.cpp getusername.cpp getcomputername.cpp + getlinkcount.cpp terminal.cpp) target_link_libraries(ps icuuc) diff --git a/impl/getlinkcount.cpp b/impl/getlinkcount.cpp new file mode 100644 index 000000000..50e7263a8 --- /dev/null +++ b/impl/getlinkcount.cpp @@ -0,0 +1,118 @@ +//! @file getlinkcount.cpp +//! @author George FLeming +//! @brief Retrieve link count of a file + +#include +#include +#include +#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_BAD_ENVIRONMENT: locale is not UTF-8 +//! - 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 TRUE If the function succeeds, the return value is a nonzero +//! value, and the variable pointed to by buffer contains infomation about the files +//! @retval FALSE If the function fails, the return value is zero. To get +//! extended error information, call GetLastError. +//! + +BOOL GetLinkCount(char* fileName, LPDWORD count) +{ + errno = 0; + + // Check parameters + if (!fileName) + { + 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; + } + + 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 FALSE; + } + + *count = statBuf.st_nlink; + return TRUE; + +} diff --git a/impl/getlinkcount.h b/impl/getlinkcount.h new file mode 100644 index 000000000..738a606c6 --- /dev/null +++ b/impl/getlinkcount.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pal.h" + +PAL_BEGIN_EXTERNC + +BOOL GetLinkCount(char* fileName, LPDWORD count); + +PAL_END_EXTERNC diff --git a/impl/pal.h b/impl/pal.h index 944336ab0..c53c985a8 100644 --- a/impl/pal.h +++ b/impl/pal.h @@ -82,6 +82,13 @@ #define MAX_PATH 0x00000104 #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 + typedef unsigned long long uint64; #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 396e121c5..4ff914ae0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,10 +1,11 @@ add_subdirectory(googletest) add_executable(monad_native - main.cpp test-getcurrentprocessid.cpp test-getusername.cpp - test-getcomputername.cpp) + test-getcomputername.cpp + test-getlinkcount.cpp + main.cpp) target_include_directories(monad_native PRIVATE ${gtest_SOURCE_DIR}/include) diff --git a/tests/test-getlinkcount.cpp b/tests/test-getlinkcount.cpp new file mode 100644 index 000000000..b01ddbbee --- /dev/null +++ b/tests/test-getlinkcount.cpp @@ -0,0 +1,90 @@ +//! @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: + + std::string file; + const std::string fileTemplate = "/tmp/createFile.XXXXXXX"; + ulong count; + + getLinkCountTest() + { + file = fileTemplate; + file = mktemp(const_cast(file.c_str())); + std::ifstream fileCondition(file); + EXPECT_EQ(0, fileCondition.good()); + } + + void createFileForTesting(std::string theFile) + { + std::ofstream ofs; + ofs.open(theFile, std::ofstream::out); + ofs << "hi there, ms ostc!"; + ofs.close(); + } + + std::string createHardLink(std::string origFile) + { + std::string newFile = origFile + "_link"; + int ret = link(const_cast(origFile.c_str()), const_cast(newFile.c_str())); + EXPECT_EQ(0, ret); + + return newFile; + } + + void removeFile(std::string fileName) + { + int ret = unlink(const_cast(fileName.c_str())); + EXPECT_EQ(0, ret); + } +}; + +TEST_F(getLinkCountTest, FilePathNameIsNull) +{ + bool retVal = GetLinkCount(NULL, &count ); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_INVALID_PARAMETER, errno); +} + +TEST_F(getLinkCountTest, FilePathNameDoesNotExist) +{ + std::string invalidFile = "/tmp/createFile"; + bool retVal = GetLinkCount(const_cast(invalidFile.c_str()), &count); + ASSERT_FALSE(retVal); + EXPECT_EQ(ERROR_FILE_NOT_FOUND, errno); +} + +TEST_F(getLinkCountTest, LinkCountOfSinglyLinkedFile) +{ + createFileForTesting(file); + bool retVal = GetLinkCount(const_cast(file.c_str()), &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(1, count); + + removeFile(file); +} + +TEST_F(getLinkCountTest, LinkCountOfMultipliLinkedFile) +{ + createFileForTesting(file); + std::string newFile = createHardLink(file); + bool retVal = GetLinkCount(const_cast(file.c_str()), &count); + ASSERT_TRUE(retVal); + EXPECT_EQ(2, count); + + removeFile(file); + removeFile(newFile); +} +