summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-27 09:04:05 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-27 09:04:05 -0500
commitb8345246dcc2121bcb6d1515a9341789de20199f (patch)
tree4a25857512a90ff38e8a40166c54694b74920216
parentf84b8259f6e980fed647d8e1ec0634f89ee59c06 (diff)
First crack at file objects.
-rw-r--r--CMakeLists.txt7
-rw-r--r--README.org26
-rw-r--r--include/fud_c_file.hpp11
-rw-r--r--include/fud_file.hpp168
-rw-r--r--include/fud_memory.hpp26
-rw-r--r--include/fud_status.hpp45
-rw-r--r--include/fud_string.hpp6
-rw-r--r--include/fud_string_view.hpp7
-rw-r--r--source/fud_assert.cpp32
-rw-r--r--source/fud_file.cpp201
-rw-r--r--source/fud_string.cpp30
-rw-r--r--test/CMakeLists.txt5
-rw-r--r--test/test_common.cpp60
-rw-r--r--test/test_common.hpp13
-rw-r--r--test/test_directory.cpp45
-rw-r--r--test/test_file.cpp97
16 files changed, 697 insertions, 82 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 88833ab..1676173 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,14 +14,14 @@ set(CXX_CPPCHECK "project=build/compile_commands.json;enable=information;force")
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
find_package(SQLite3 REQUIRED)
-
add_library(fud SHARED
source/libfud.cpp
source/fud_allocator.cpp
source/fud_assert.cpp
- source/fud_format.cpp
- source/fud_directory.cpp
source/fud_c_file.cpp
+ source/fud_directory.cpp
+ source/fud_file.cpp
+ source/fud_format.cpp
source/fud_memory.cpp
source/fud_sqlite.cpp
source/fud_string_view.cpp
@@ -102,6 +102,7 @@ set(FUD_HEADERS
"include/fud_c_file.hpp"
"include/fud_c_string.hpp"
"include/fud_directory.hpp"
+ "include/fud_file.hpp"
"include/fud_fixed_vector.hpp"
"include/fud_fud_type_traits.hpp"
"include/fud_memory.hpp"
diff --git a/README.org b/README.org
index 61795d7..2a600c4 100644
--- a/README.org
+++ b/README.org
@@ -1 +1,27 @@
* libfud
+
+** Design Principles
+
++ Full control over allocations, even the default allocator.
++ Zero exceptions, zero exceptions to zero exceptions.
++ Assertions in production release for invariants.
++ Configurable run-time assertions for indexing.
++ Safe API for users intolerant to run-time assertions for indexing.
++ Readable, understandable code.
++ Minimize undefined behavior in the API to a subset which is only decidable at
+ run-time.
++ Minimize runtime undefined behavior as much as possible.
+
+** Relatively mature features
+
++ Statically sized arrays
++ String Views
+
+** Unstable features
+
++ Customizable allocator model
++ Dynamically sized vectors taking an allocator
++ Dynamically sized strings taking an allocator
++ Unicode support with UTF8 encoding
++ Formatting à la =std::format=
++ Wrappers around C files
diff --git a/include/fud_c_file.hpp b/include/fud_c_file.hpp
index b54b044..197abd3 100644
--- a/include/fud_c_file.hpp
+++ b/include/fud_c_file.hpp
@@ -22,6 +22,7 @@
#include "fud_result.hpp"
#include "fud_status.hpp"
#include "fud_string.hpp"
+#include "fud_file.hpp"
#include <cstdint>
#include <cstdio>
@@ -82,16 +83,6 @@ constexpr const char* CTextFileModeFromFlags(CFileMode mode)
}
}
-struct [[nodiscard]] ReadResult {
- size_t bytesRead{0};
- FudStatus status{FudStatus::Success};
-};
-
-struct [[nodiscard]] WriteResult {
- size_t bytesWritten{0};
- FudStatus status{FudStatus::Success};
-};
-
namespace detail {
template <typename Derived>
diff --git a/include/fud_file.hpp b/include/fud_file.hpp
new file mode 100644
index 0000000..b468b4b
--- /dev/null
+++ b/include/fud_file.hpp
@@ -0,0 +1,168 @@
+/*
+ * libfud
+ * Copyright 2024 Dominick Allen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FUD_FILE_HPP
+#define FUD_FILE_HPP
+
+#include "fud_allocator.hpp"
+#include "fud_option.hpp"
+#include "fud_result.hpp"
+#include "fud_status.hpp"
+#include "fud_string_view.hpp"
+
+#include <fcntl.h>
+
+namespace fud {
+
+/** \brief Access Modes for File */
+enum class FileAccessMode : uint8_t
+{
+ Read = 0x01,
+ Write = 0x02,
+ ReadWrite = Read | Write
+};
+
+enum class OpenFlagEnum : uint32_t
+{
+ Append = O_APPEND,
+ Truncate = O_TRUNC,
+ CloseOnExec = O_CLOEXEC,
+ DataSync = O_DSYNC,
+ Direct = O_DIRECT,
+ NoAtime = O_NOATIME,
+ NonBlock = O_NONBLOCK,
+ FileSync = O_SYNC
+};
+
+class OpenFlags {
+ public:
+ using FlagType = std::underlying_type_t<OpenFlagEnum>;
+
+ constexpr OpenFlags() noexcept = default;
+ constexpr OpenFlags(const OpenFlags& rhs) noexcept = default;
+ constexpr OpenFlags(OpenFlags&& rhs) noexcept = default;
+ constexpr ~OpenFlags() noexcept = default;
+ constexpr OpenFlags& operator=(const OpenFlags& rhs) noexcept = default;
+ constexpr OpenFlags& operator=(OpenFlags&& rhs) noexcept = default;
+
+ constexpr OpenFlags(OpenFlagEnum mode) noexcept : m_mask{static_cast<FlagType>(mode)}
+ {
+ }
+
+ constexpr OpenFlags operator|(OpenFlags rhs) const noexcept
+ {
+ OpenFlags mode{*this};
+ mode.m_mask |= rhs.m_mask;
+ return mode;
+ }
+
+ constexpr OpenFlags& operator|=(OpenFlags rhs) noexcept
+ {
+ m_mask |= rhs.m_mask;
+ return *this;
+ }
+
+ constexpr OpenFlags& operator|=(OpenFlagEnum rhs) noexcept
+ {
+ m_mask |= static_cast<FlagType>(rhs);
+ return *this;
+ }
+
+ constexpr OpenFlags operator|(OpenFlagEnum rhs) const noexcept
+ {
+ OpenFlags mode{*this};
+ mode.m_mask |= static_cast<FlagType>(rhs);
+ return mode;
+ }
+
+ constexpr FlagType flags() const noexcept
+ {
+ return m_mask;
+ }
+
+ constexpr bool hasFlag(OpenFlagEnum flag) noexcept
+ {
+ return (m_mask & static_cast<FlagType>(flag)) != 0;
+ }
+
+ private:
+ FlagType m_mask{0};
+};
+
+constexpr OpenFlags operator|(OpenFlagEnum lhs, OpenFlagEnum rhs)
+{
+ OpenFlags mode{lhs};
+ return mode | rhs;
+}
+
+struct [[nodiscard]] ReadResult {
+ size_t bytesRead{0};
+ FudStatus status{FudStatus::Success};
+};
+
+struct [[nodiscard]] WriteResult {
+ size_t bytesWritten{0};
+ FudStatus status{FudStatus::Success};
+};
+
+class RegularFile;
+using FileResult = Result<RegularFile, FudStatus>;
+
+class RegularFile {
+ public:
+ static FileResult open(
+ StringView filename,
+ FileAccessMode mode,
+ OpenFlags flags,
+ Option<int> dirFdoption,
+ Allocator* allocator = &globalFudAllocator);
+
+ static FileResult create(
+ StringView filename,
+ FileAccessMode mode,
+ OpenFlags flags,
+ bool exclusive,
+ Option<int> dirFdOption,
+ Allocator* allocator = &globalFudAllocator);
+
+ FudStatus close();
+
+ private:
+ RegularFile() = default;
+
+ public:
+ RegularFile(const RegularFile& rhs) = delete;
+
+ RegularFile(RegularFile&& rhs) noexcept;
+
+ ~RegularFile();
+
+ RegularFile& operator=(const RegularFile& rhs) = delete;
+
+ RegularFile& operator=(RegularFile&& rhs) noexcept;
+
+ FudStatus take(RegularFile& rhs);
+
+ private:
+ int m_fd{-1};
+ FileAccessMode m_modeFlags{};
+ OpenFlags m_openFlags{};
+};
+
+} // namespace fud
+
+#endif
diff --git a/include/fud_memory.hpp b/include/fud_memory.hpp
index 6ce6312..6f6816f 100644
--- a/include/fud_memory.hpp
+++ b/include/fud_memory.hpp
@@ -73,6 +73,18 @@ constexpr void setMemory(Container<Size>& container, T&& value)
}
}
+template <typename T>
+constexpr void zeroObject(T& object)
+{
+ static_assert(std::is_standard_layout_v<T>);
+ static_assert(std::is_trivially_copyable_v<T>);
+
+ auto* objPtr = reinterpret_cast<uint8_t*>(&object);
+ for (size_t idx = 0; idx < sizeof(object); ++idx) {
+ objPtr[idx] = 0;
+ }
+}
+
template <size_t Count, typename T, typename U>
void copyMem(T& destination, const U& source)
{
@@ -80,11 +92,16 @@ void copyMem(T& destination, const U& source)
static_assert(Count <= sizeof(T));
static_assert(std::is_standard_layout_v<T>);
static_assert(std::is_standard_layout_v<U>);
+ static_assert(std::is_trivially_copyable_v<T>);
+ static_assert(std::is_trivially_copyable_v<U>);
+
+ // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
+ auto* destPtr = reinterpret_cast<char*>(&destination);
+ const auto* srcPtr = reinterpret_cast<const char*>(&source);
+ // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
for (size_t idx = 0; idx < Count; ++idx) {
- // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
- reinterpret_cast<char*>(&destination)[idx] = reinterpret_cast<const char*>(&source)[idx];
- // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
+ destPtr[idx] = srcPtr[idx];
}
}
@@ -126,8 +143,7 @@ int compareMem(const T& lhs, U&& rhs)
int difference = 0;
for (size_t idx = 0; idx < Count; ++idx) {
// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
- difference = reinterpret_cast<const uint8_t*>(&lhs)[idx] -
- reinterpret_cast<const uint8_t*>(&uRhs)[idx];
+ difference = reinterpret_cast<const uint8_t*>(&lhs)[idx] - reinterpret_cast<const uint8_t*>(&uRhs)[idx];
// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
if (difference != 0) {
break;
diff --git a/include/fud_status.hpp b/include/fud_status.hpp
index d57a9c5..f18c119 100644
--- a/include/fud_status.hpp
+++ b/include/fud_status.hpp
@@ -18,32 +18,59 @@
#ifndef FUD_STATUS_HPP
#define FUD_STATUS_HPP
+#include <cstdint>
+
namespace fud {
-enum class [[nodiscard]] FudStatus
+enum class [[nodiscard]] FudStatus : int32_t
{
+ /** \brief Indisputable success. */
Success = 0,
+ /** \brief Either partially succeeding, or failing in a recoverable way. */
Partial,
+ /** \brief Typically a major failure in an underlying system call. */
Failure,
+ /** \brief An argument passed in was a null pointer and is invalid. */
NullPointer,
+ /** \brief An argument passed in is in an invalid state or mismatches with other arguments. */
ArgumentInvalid,
- VariantInvalid,
+ /** \brief An handle to a managed object is not valid. */
+ HandleInvalid,
+ /** \brief An object is uninitialized or no longer valid. */
ObjectInvalid,
+ /** \brief A utf sequence in a string is invalid. */
Utf8Invalid,
+ /** \brief A string is not terminated or has a length field exceeding its capacity. */
StringInvalid,
+ /** \brief The requested operation on the object is not valid for its current state. */
OperationInvalid,
+ /** \brief An object which is to be initialized has already been initialized. */
AlreadyInitialized,
+ /** \brief A format string is invalid. */
FormatInvalid,
+ /** \brief A string being parsed as a number is out of range for the requested type. */
RangeError,
+ /** \brief The given index is not in range of a randomly accessible container. */
IndexInvalid,
+ /** \brief An object in a container already exists in the requested slot for a new object. */
Exists,
+ /** \brief An object in a container does not exist in the requested slot for an existing object. */
NotFound,
+ /** \brief A container is empty when something was expected to be present. */
Empty,
+ /** \brief A container is full when attempting to add something to it. */
Full,
+ /** \brief An operation requiring elevated permissions failed. */
+ PermissionDenied,
+ /** \brief Two or more objects overlap when they are expected to be distinct. */
Aliased,
+ /** \brief An requested allocation could not be completed. */
AllocFailure,
+ /** \brief An requested deallocation could not be completed. */
DeallocFailure,
+ /** \brief The function is not implemented, but is planned to be implemented. */
NotImplemented,
+ /** \brief The function or desired mode of action for a function is not supported. */
NotSupported
};
@@ -58,20 +85,20 @@ constexpr const char* FudStatusToString(FudStatus status)
return "Failure";
case FudStatus::NullPointer:
return "NullPointer";
- case FudStatus::StringInvalid:
- return "StringInvalid";
+ case FudStatus::ArgumentInvalid:
+ return "ArgumentInvalid";
+ case FudStatus::HandleInvalid:
+ return "HandleInvalid";
case FudStatus::ObjectInvalid:
return "ObjectInvalid";
+ case FudStatus::StringInvalid:
+ return "StringInvalid";
case FudStatus::OperationInvalid:
return "OperationInvalid";
- case FudStatus::ArgumentInvalid:
- return "ArgumentInvalid";
case FudStatus::Utf8Invalid:
return "Utf8Invalid";
case FudStatus::RangeError:
return "RangeError";
- case FudStatus::VariantInvalid:
- return "VariantInvalid";
case FudStatus::FormatInvalid:
return "FormatInvalid";
case FudStatus::AlreadyInitialized:
@@ -86,6 +113,8 @@ constexpr const char* FudStatusToString(FudStatus status)
return "Empty";
case FudStatus::Full:
return "Full";
+ case FudStatus::PermissionDenied:
+ return "PermissionDenied";
case FudStatus::Aliased:
return "Aliased";
case FudStatus::AllocFailure:
diff --git a/include/fud_string.hpp b/include/fud_string.hpp
index 0213524..df03ad9 100644
--- a/include/fud_string.hpp
+++ b/include/fud_string.hpp
@@ -90,7 +90,6 @@ class String {
}
String output{};
- auto* data = output.m_buffer.data();
output.m_length = totalLength;
output.m_allocator = allocator;
if (output.m_length >= output.m_capacity) {
@@ -103,10 +102,11 @@ class String {
output.m_data = static_cast<utf8*>(dataResult.getOkay());
}
+ auto* data = output.data();
size_t cumulativeLength = 0;
for (size_t idx = 0; idx < strPointers.size(); ++idx) {
const auto* cString = strPointers[idx];
- auto copyStatus = copyMem(data, output.m_capacity - cumulativeLength, cString, lengths[idx]);
+ auto copyStatus = copyMem(data + cumulativeLength, output.m_capacity - cumulativeLength, cString, lengths[idx]);
fudAssert(copyStatus == FudStatus::Success);
cumulativeLength += lengths[idx];
}
@@ -131,6 +131,8 @@ class String {
static StringResult from(const String& rhs);
+ static StringResult from(StringView view, Allocator* allocator = &globalFudAllocator);
+
FudStatus copy(const String& rhs);
[[nodiscard]] constexpr size_t length() const
diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp
index 718b3d7..972630a 100644
--- a/include/fud_string_view.hpp
+++ b/include/fud_string_view.hpp
@@ -61,6 +61,13 @@ struct StringView {
explicit StringView(const String& fudString) noexcept;
+ template <size_t N>
+ constexpr static StringView cStringView(const char (&input)[N])
+ {
+ static_assert(N > 0);
+ return StringView{N, reinterpret_cast<const utf8*>(input)};
+ }
+
[[nodiscard]] constexpr size_t length() const
{
return m_length;
diff --git a/source/fud_assert.cpp b/source/fud_assert.cpp
index 0c4d7cf..a7c9d76 100644
--- a/source/fud_assert.cpp
+++ b/source/fud_assert.cpp
@@ -1,11 +1,27 @@
+/*
+ * libfud
+ * Copyright 2024 Dominick Allen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#include "fud_assert.hpp"
#include "fud_format.hpp"
-#include "fud_string.hpp"
+#include "fud_string.hpp" // DrainResult
+#include "fud_string_view.hpp"
-#include <climits>
#include <cstdio>
-#include <exception>
namespace fud {
@@ -24,6 +40,7 @@ DrainResult BufferSink::drain(StringView source)
result.status = FudStatus::Success;
return result;
}
+ /* TODO: give users control over this functionality */
result.bytesWritten = fwrite(reinterpret_cast<const char*>(source.m_data), 1, source.m_length, stderr);
if (result.bytesWritten != source.m_length) {
result.status = FudStatus::Full;
@@ -43,7 +60,14 @@ void assertFailMessage(const char* assertion, const std::source_location sourceL
if (functionName == nullptr) {
functionName = "Unknown Function";
}
- format(sink, FormatCharMode::Unchecked, "{}:{}:{}: {}\n", fileName, functionName, sourceLocation.line(), assertion);
+ static_cast<void>(format(
+ sink,
+ FormatCharMode::Unchecked,
+ "{}:{}:{}: {}\n",
+ fileName,
+ functionName,
+ sourceLocation.line(),
+ assertion));
}
} // namespace impl
diff --git a/source/fud_file.cpp b/source/fud_file.cpp
new file mode 100644
index 0000000..e27a46d
--- /dev/null
+++ b/source/fud_file.cpp
@@ -0,0 +1,201 @@
+/*
+ * libfud
+ * Copyright 2024 Dominick Allen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fud_file.hpp"
+
+#include <cerrno>
+#include <fcntl.h>
+#include <linux/openat2.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+namespace fud {
+
+FileResult RegularFile::open(
+ StringView filename,
+ FileAccessMode mode,
+ OpenFlags flags,
+ Option<int> dirFdOption,
+ Allocator* allocator)
+{
+ if (allocator == nullptr) {
+ return FudStatus::NullPointer;
+ }
+
+ if (!filename.nullTerminated()) {
+ return FudStatus::ArgumentInvalid;
+ }
+
+ int dirFd = dirFdOption.valueOr(AT_FDCWD);
+
+ RegularFile file{};
+
+ uint32_t openFlags = 0;
+ switch (mode) {
+ case FileAccessMode::Read:
+ openFlags = O_RDONLY;
+ break;
+ case FileAccessMode::Write:
+ openFlags = O_WRONLY;
+ break;
+ case FileAccessMode::ReadWrite:
+ openFlags = O_RDWR;
+ break;
+ default:
+ return FudStatus::ArgumentInvalid;
+ }
+
+ if (flags.hasFlag(OpenFlagEnum::Append) && flags.hasFlag(OpenFlagEnum::Truncate)) {
+ return FudStatus::OperationInvalid;
+ }
+
+ openFlags |= flags.flags();
+
+ open_how openHow{};
+ zeroObject(openHow);
+ openHow.flags = openFlags;
+ openHow.resolve = RESOLVE_NO_SYMLINKS;
+
+ auto status = syscall(SYS_openat2, dirFd, filename.data(), &openHow, sizeof(openHow));
+ if (status == -1) {
+ if constexpr (EAGAIN != EWOULDBLOCK && status == EWOULDBLOCK) {
+ return FudStatus::Partial;
+ }
+ switch (errno) {
+ case ETXTBSY:
+ case EAGAIN:
+ return FudStatus::Partial;
+ case ENOENT:
+ return FudStatus::NotFound;
+ case EFBIG:
+ case EOVERFLOW:
+ case EINVAL:
+ case EISDIR:
+ case ENAMETOOLONG:
+ return FudStatus::ArgumentInvalid;
+ case EROFS:
+ case EACCES:
+ case EPERM:
+ return FudStatus::PermissionDenied;
+ case ELOOP:
+ case EXDEV:
+ case ENFILE:
+ case E2BIG:
+ default:
+ return FudStatus::Failure;
+ }
+ }
+ fudAssert(status <= std::numeric_limits<decltype(file.m_fd)>::max());
+ file.m_fd = static_cast<decltype(file.m_fd)>(status);
+
+ using Stat = struct stat;
+ Stat sBuffer{};
+ auto fStatus = fstat(file.m_fd, &sBuffer);
+ if (fStatus == -1) {
+ return FudStatus::Failure;
+ }
+
+ if ((sBuffer.st_mode & S_IFMT) != S_IFREG) {
+ return FudStatus::ObjectInvalid;
+ }
+
+ return file;
+}
+
+/*
+ static FileResult RegularFile::create(
+ StringView filename,
+ FileAccessMode mode,
+ OpenFlags flags,
+ bool exclusive,
+ Option<int> dirFdOption,
+ Allocator* allocator = &globalFudAllocator);
+{
+}
+*/
+
+RegularFile::~RegularFile()
+{
+ static_cast<void>(this->close());
+}
+
+RegularFile::RegularFile(RegularFile&& rhs) noexcept : m_fd{rhs.m_fd}, m_modeFlags{rhs.m_modeFlags}
+{
+ rhs.m_fd = -1;
+}
+
+RegularFile& RegularFile::operator=(RegularFile&& rhs) noexcept
+{
+ if (&rhs == this) {
+ return *this;
+ }
+
+ static_cast<void>(this->close());
+
+ m_fd = rhs.m_fd;
+ m_modeFlags = rhs.m_modeFlags;
+
+ rhs.m_fd = -1;
+ return *this;
+}
+
+FudStatus RegularFile::take(RegularFile& rhs)
+{
+ if (&rhs == this) {
+ return FudStatus::Success;
+ }
+
+ auto status = this->close();
+ if (status != FudStatus::Success) {
+ return status;
+ }
+
+ m_fd = rhs.m_fd;
+ m_modeFlags = rhs.m_modeFlags;
+
+ rhs.m_fd = -1;
+ return status;
+}
+
+FudStatus RegularFile::close()
+{
+ FudStatus status = FudStatus::Success;
+ if (m_fd != -1) {
+ auto closeStatus = ::close(m_fd);
+ if (closeStatus == -1) {
+ switch (errno) {
+ case EBADF:
+ status = FudStatus::HandleInvalid;
+ break;
+ case EINTR:
+ case EIO:
+ case ENOSPC:
+ case EDQUOT:
+ status = FudStatus::Partial;
+ break;
+ default:
+ status = FudStatus::Failure;
+ break;
+ }
+ }
+ m_fd = -1;
+ }
+ return status;
+}
+
+} // namespace fud
diff --git a/source/fud_string.cpp b/source/fud_string.cpp
index 048cc94..c444a74 100644
--- a/source/fud_string.cpp
+++ b/source/fud_string.cpp
@@ -84,6 +84,32 @@ StringResult String::from(const String& rhs)
return StringResult::okay(std::move(output));
}
+StringResult String::from(StringView view, Allocator* allocator)
+{
+ if (allocator == nullptr || view.m_data == nullptr) {
+ return StringResult::error(FudStatus::NullPointer);
+ }
+
+ String output{};
+ output.m_allocator = allocator;
+ output.m_length = view.m_length;
+
+ if (output.m_length >= output.m_capacity) {
+ output.m_capacity = output.m_length + 1;
+
+ output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.m_allocator->allocate(output.m_capacity)));
+ fudAssert(output.m_data != nullptr);
+ }
+
+ auto copyStatus = copyMem(output.data(), output.m_capacity, view.m_data, output.m_length);
+ fudAssert(copyStatus == FudStatus::Success);
+
+ auto terminateStatus = output.nullTerminate();
+ fudAssert(terminateStatus == FudStatus::Success);
+
+ return StringResult::okay(std::move(output));
+}
+
String::String(String&& rhs) noexcept : m_length{rhs.m_length}, m_capacity{rhs.m_capacity}, m_allocator{rhs.m_allocator}
{
if (rhs.isLarge()) {
@@ -131,6 +157,10 @@ FudStatus String::copy(const String& rhs)
String& String::operator=(String&& rhs) noexcept
{
+ if (&rhs == this) {
+ return *this;
+ }
+
cleanup();
m_length = rhs.m_length;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 788e4ba..aef8052 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -13,12 +13,16 @@ endif()
set(gtest_URL https://github.com/google/googletest.git)
set(gtest_TAG v1.14.0)
+# Keep this setting above the FetchContent_Declare for googletest
+set(INSTALL_GTEST OFF CACHE BOOL "Enable installation of googletest.")
+
FetchContent_Declare(
googletest
GIT_REPOSITORY ${gtest_URL}
GIT_TAG ${gtest_TAG}
)
FetchContent_MakeAvailable(googletest)
+
include(GoogleTest)
enable_testing()
@@ -61,6 +65,7 @@ fud_add_test(test_allocator SOURCES test_allocator.cpp)
fud_add_test(test_assert SOURCES test_assert.cpp)
# fud_add_test(test_c_file SOURCES test_c_file.cpp)
fud_add_test(test_directory SOURCES test_directory.cpp)
+fud_add_test(test_file SOURCES test_file.cpp)
fud_add_test(test_format SOURCES test_format.cpp)
fud_add_test(test_option SOURCES test_option.cpp)
fud_add_test(test_result SOURCES test_result.cpp)
diff --git a/test/test_common.cpp b/test/test_common.cpp
index d3e1704..784cb0d 100644
--- a/test/test_common.cpp
+++ b/test/test_common.cpp
@@ -16,7 +16,12 @@
*/
#include "test_common.hpp"
+
+#include "fud_string.hpp"
+
#include <cstdlib>
+#include <ftw.h>
+#include <gtest/gtest.h>
namespace fud {
@@ -34,22 +39,69 @@ MockFudAlloc globalDefaultMockAlloc{};
MockFudDealloc globalDefaultMockDealloc{};
-void* MockFudAllocator::allocate(size_t size) {
+void* MockFudAllocator::allocate(size_t size)
+{
return (*m_allocator)(size);
}
-void MockFudAllocator::deallocate(void* pointer) {
+void MockFudAllocator::deallocate(void* pointer)
+{
return (*m_deallocator)(pointer);
}
MockFudAllocator globalMockFudAlloc{};
-void* fudAlloc(size_t size) {
+void* fudAlloc(size_t size)
+{
return globalMockFudAlloc.allocate(size);
}
-void fudFree(void* ptr) {
+void fudFree(void* ptr)
+{
return globalMockFudAlloc.deallocate(ptr);
}
+int unlink_cb(const char* fpath, const struct stat* sb_unused, int typeflag, struct FTW* ftwbuf)
+{
+ static_cast<void>(sb_unused);
+ int retValue = remove(fpath);
+
+ EXPECT_EQ(retValue, 0);
+ if (retValue != 0) {
+ perror(fpath);
+ }
+
+ return retValue;
+}
+
+FudStatus removeRecursive(const String& path)
+{
+ if (!path.utf8Valid()) {
+ return FudStatus::Utf8Invalid;
+ }
+ if (path.length() < 5) {
+ return FudStatus::ArgumentInvalid;
+ }
+ auto prefix{String::makeFromCString("/tmp/").takeOkay()};
+ auto diffResult = compareMem(path.data(), path.length(), prefix.data(), prefix.length());
+ if (diffResult.isError()) {
+ return FudStatus::ArgumentInvalid;
+ }
+ auto diff = diffResult.getOkay();
+ if (diff != 0) {
+ return FudStatus::ArgumentInvalid;
+ }
+ constexpr int maxOpenFd = 64;
+ auto status = nftw(path.c_str(), unlink_cb, maxOpenFd, FTW_DEPTH | FTW_PHYS);
+ if (status == 0) {
+ return FudStatus::Success;
+ }
+
+ if (errno == ENOENT) {
+ return FudStatus::Success;
+ }
+
+ return FudStatus::Failure;
+}
+
} // namespace fud
diff --git a/test/test_common.hpp b/test/test_common.hpp
index f049fed..3d7ecba 100644
--- a/test/test_common.hpp
+++ b/test/test_common.hpp
@@ -18,9 +18,15 @@
#ifndef FUD_TEST_COMMON_HPP
#define FUD_TEST_COMMON_HPP
+#include "fud_status.hpp"
+
#include <cstddef>
#include <cstdlib>
+extern "C" {
+int unlink_cb(const char* fpath, const struct stat* sb_unused, int typeflag, struct FTW* ftwbuf);
+}
+
namespace fud {
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
@@ -70,11 +76,14 @@ struct MockFudAllocator {
void deallocate(void* pointer);
MockFudAlloc* m_allocator{&globalDefaultMockAlloc};
- MockFudDealloc* m_deallocator{&globalDefaultMockDealloc};;
+ MockFudDealloc* m_deallocator{&globalDefaultMockDealloc};
};
extern MockFudAllocator globalMockFudAlloc;
-} // namespace ext_lib
+class String;
+FudStatus removeRecursive(const String& path);
+
+} // namespace fud
#endif
diff --git a/test/test_directory.cpp b/test/test_directory.cpp
index 2f69dab..e30dafb 100644
--- a/test/test_directory.cpp
+++ b/test/test_directory.cpp
@@ -18,8 +18,8 @@
#include "fud_array.hpp"
#include "fud_c_file.hpp"
#include "fud_directory.hpp"
-#include "fud_memory.hpp"
#include "fud_string.hpp"
+#include "test_common.hpp"
#include "gtest/gtest.h"
#include <algorithm>
@@ -30,49 +30,6 @@
namespace fud {
-int unlink_cb(const char* fpath, const struct stat* sb_unused, int typeflag, struct FTW* ftwbuf)
-{
- static_cast<void>(sb_unused);
- int retValue = remove(fpath);
-
- EXPECT_EQ(retValue, 0);
- if (retValue != 0) {
- perror(fpath);
- }
-
- return retValue;
-}
-
-FudStatus removeRecursive(const String& path)
-{
- if (!path.utf8Valid()) {
- return FudStatus::Utf8Invalid;
- }
- if (path.length() < 5) {
- return FudStatus::ArgumentInvalid;
- }
- auto prefix{String::makeFromCString("/tmp/").takeOkay()};
- auto diffResult = compareMem(path.data(), path.length(), prefix.data(), prefix.length());
- if (diffResult.isError()) {
- return FudStatus::ArgumentInvalid;
- }
- auto diff = diffResult.getOkay();
- if (diff != 0) {
- return FudStatus::ArgumentInvalid;
- }
- constexpr int maxOpenFd = 64;
- auto status = nftw(path.c_str(), unlink_cb, maxOpenFd, FTW_DEPTH | FTW_PHYS);
- if (status == 0) {
- return FudStatus::Success;
- }
-
- if (errno == ENOENT) {
- return FudStatus::Success;
- }
-
- return FudStatus::Failure;
-}
-
TEST(FudDirectory, Basic)
{
const auto testDirName{String::makeFromCString("/tmp/fud_directory_test").takeOkay()};
diff --git a/test/test_file.cpp b/test/test_file.cpp
new file mode 100644
index 0000000..06e6bcc
--- /dev/null
+++ b/test/test_file.cpp
@@ -0,0 +1,97 @@
+/*
+ * libfud
+ * Copyright 2024 Dominick Allen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fud_file.hpp"
+#include "fud_string.hpp"
+#include "test_common.hpp"
+
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <cerrno>
+#include <fcntl.h>
+#include <ftw.h>
+#include <ranges>
+
+namespace fud {
+
+TEST(FudFile, Basic)
+{
+ constexpr const char* testDirCName = "/tmp/fud_directory_test";
+ const auto testDirName{String::makeFromCString(testDirCName).takeOkay()};
+ ASSERT_TRUE(testDirName.utf8Valid());
+ constexpr mode_t pathMode = 0777;
+
+ ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success);
+
+ auto mkdirResult = mkdir(testDirName.c_str(), pathMode);
+ EXPECT_EQ(mkdirResult, 0);
+ if (mkdirResult != 0) {
+ ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success);
+ return;
+ }
+
+ String testName1{String::makeFromCStrings(testDirCName, "/", "test1").takeOkay()};
+ String testName2{String::makeFromCStrings(testDirCName, "/", "test2").takeOkay()};
+
+ auto rmFile = [](const auto& filename) -> int {
+ auto result = unlink(filename.c_str());
+ if (result == -1) {
+ if (errno == ENOENT) {
+ return 0;
+ }
+ }
+ return result;
+ };
+ ASSERT_EQ(rmFile(testName1), 0);
+ ASSERT_EQ(rmFile(testName2), 0);
+
+ auto fileResult{RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt)};
+ EXPECT_EQ(fileResult.takeErrorOr(FudStatus::Success), FudStatus::NotFound);
+
+ /* The irony of the ability for TOCTOU bugs to be present in the test does
+ * not escape me. */
+
+ auto createFile = [](const auto& filename) -> int {
+ constexpr int allPerms = 0777;
+ auto handleResult = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, allPerms);
+ if (handleResult >= 0) {
+ close(handleResult);
+ }
+ return handleResult;
+ };
+ ASSERT_GE(createFile(testName1), 0);
+
+ fileResult = RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt);
+ ASSERT_TRUE(fileResult.isOkay());
+ auto file{fileResult.takeOkay()};
+ ASSERT_EQ(file.close(), FudStatus::Success);
+
+ ASSERT_EQ(rmFile(testName1), 0);
+ ASSERT_GE(createFile(testName2), 0);
+ ASSERT_EQ(symlink(testName2.c_str(), testName1.c_str()), 0);
+
+ fileResult = RegularFile::open(testName2.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt);
+ ASSERT_TRUE(fileResult.isOkay());
+ file = fileResult.takeOkay();
+ ASSERT_EQ(file.close(), FudStatus::Success);
+
+ fileResult = RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt);
+ ASSERT_TRUE(fileResult.isError());
+ EXPECT_EQ(fileResult.getError(), FudStatus::Failure);
+}
+
+} // namespace fud