diff --git a/Os/CMakeLists.txt b/Os/CMakeLists.txt index db695c4d985..0fb5a57d4b4 100644 --- a/Os/CMakeLists.txt +++ b/Os/CMakeLists.txt @@ -135,8 +135,61 @@ register_fprime_module( Fw_Types ) -add_named_os_module(Console Fw_Logger) -add_named_os_module("File;FileSystem;Directory" Utils_Hash Fw_DataStructures) +# Console: Console.hpp is a thin public alias header (no .cpp); the interface and +# link-time delegate live in ConsoleInterface.hpp and DelegateConsole.{hpp,cpp}. +# Registered explicitly because add_named_os_module assumes a matching +# .cpp per header. +register_fprime_module( + "Os_Console" + REQUIRES_IMPLEMENTATIONS + "Os_Console" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/ConsoleInterface.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateConsole.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/Console.hpp" + "${CMAKE_CURRENT_LIST_DIR}/ConsoleInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateConsole.hpp" + DEPENDS + Fw_Time + Fw_Types + Fw_Logger +) +fprime_target_dependencies(Os PUBLIC "Os_Console") + +# File: File.hpp is a thin public alias header; the interface and link-time +# delegate live in FileInterface.hpp and DelegateFile.{hpp,cpp}. FileSystem +# and Directory follow the same pattern. Registered explicitly because +# add_named_os_module assumes a matching .cpp per header. +register_fprime_module( + "Os_File" + REQUIRES_IMPLEMENTATIONS + "Os_File" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/FileInterface.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateFile.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateFileSystem.cpp" + "${CMAKE_CURRENT_LIST_DIR}/FileSystemInterface.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DirectoryInterface.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateDirectory.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/File.hpp" + "${CMAKE_CURRENT_LIST_DIR}/FileInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateFile.hpp" + "${CMAKE_CURRENT_LIST_DIR}/FileSystem.hpp" + "${CMAKE_CURRENT_LIST_DIR}/FileSystemInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateFileSystem.hpp" + "${CMAKE_CURRENT_LIST_DIR}/Directory.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DirectoryInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateDirectory.hpp" + DEPENDS + Fw_Time + Fw_Types + Utils_Hash + Fw_DataStructures +) +fprime_target_dependencies(Os PUBLIC "Os_File") + add_named_os_module(Task ${CMAKE_THREAD_LIBS_INIT}) # Mutex: Mutex.hpp is a thin public alias header (no Mutex.cpp); the interface and # link-time delegate live in MutexInterface.hpp / DelegateMutex.{hpp,cpp}. Registered @@ -164,10 +217,67 @@ register_fprime_module( Fw_Types ) fprime_target_dependencies(Os PUBLIC "Os_Mutex") -add_named_os_module(CountingSemaphore) +# CountingSemaphore: CountingSemaphore.hpp is a thin public alias header (no +# .cpp); the interface and link-time delegate live in +# CountingSemaphoreInterface.hpp and DelegateCountingSemaphore.{hpp,cpp}. +# Registered explicitly because add_named_os_module assumes a matching +# .cpp per header. +register_fprime_module( + "Os_CountingSemaphore" + REQUIRES_IMPLEMENTATIONS + "Os_CountingSemaphore" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/DelegateCountingSemaphore.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/CountingSemaphore.hpp" + "${CMAKE_CURRENT_LIST_DIR}/CountingSemaphoreInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateCountingSemaphore.hpp" + DEPENDS + Fw_Time + Fw_Types +) +fprime_target_dependencies(Os PUBLIC "Os_CountingSemaphore") add_named_os_module(Queue) -add_named_os_module(Cpu) -add_named_os_module(Memory) +# Cpu: Cpu.hpp is a thin public alias header (no .cpp); the interface and +# link-time delegate live in CpuInterface.hpp and DelegateCpu.{hpp,cpp}. +# Registered explicitly because add_named_os_module assumes a matching +# .cpp per header. +register_fprime_module( + "Os_Cpu" + REQUIRES_IMPLEMENTATIONS + "Os_Cpu" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/DelegateCpu.cpp" + "${CMAKE_CURRENT_LIST_DIR}/CpuInterface.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/Cpu.hpp" + "${CMAKE_CURRENT_LIST_DIR}/CpuInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateCpu.hpp" + DEPENDS + Fw_Time + Fw_Types +) +fprime_target_dependencies(Os PUBLIC "Os_Cpu") +# Memory: Memory.hpp is a thin public alias header (no .cpp); the interface and +# link-time delegate live in MemoryInterface.hpp and DelegateMemory.{hpp,cpp}. +# Registered explicitly because add_named_os_module assumes a matching +# .cpp per header. +register_fprime_module( + "Os_Memory" + REQUIRES_IMPLEMENTATIONS + "Os_Memory" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/DelegateMemory.cpp" + "${CMAKE_CURRENT_LIST_DIR}/MemoryInterface.cpp" + HEADERS + "${CMAKE_CURRENT_LIST_DIR}/Memory.hpp" + "${CMAKE_CURRENT_LIST_DIR}/MemoryInterface.hpp" + "${CMAKE_CURRENT_LIST_DIR}/DelegateMemory.hpp" + DEPENDS + Fw_Time + Fw_Types +) +fprime_target_dependencies(Os PUBLIC "Os_Memory") # RawTime: RawTime.hpp is a thin public alias header (no .cpp); the interface and # link-time delegate live in DelegateRawTime.{hpp,cpp}. Registered explicitly # because add_named_os_module assumes a matching .cpp per header. diff --git a/Os/Console.cpp b/Os/Console.cpp deleted file mode 100644 index 51fa774924a..00000000000 --- a/Os/Console.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// ====================================================================== -// \title Os/Console.cpp -// \brief common function implementation for Os::Console -// ====================================================================== -#include -#include - -namespace Os { -Console::Console() - : ConsoleInterface(), - Fw::Logger(), - m_handle_storage(), - m_delegate(*ConsoleInterface::getDelegate(m_handle_storage)) {} - -Console::~Console() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - m_delegate.~ConsoleInterface(); -} - -Console::Console(const Console& other) - : m_handle_storage(), m_delegate(*Console::getDelegate(m_handle_storage, &other.m_delegate)) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); -} - -Console& Console::operator=(const Console& other) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (this != &other) { - this->m_delegate = *ConsoleInterface::getDelegate(m_handle_storage, &other.m_delegate); - } - return *this; -} - -void Console::writeMessage(const CHAR* message, const FwSizeType size) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage)); - FW_ASSERT(message != nullptr || size == 0); - this->m_delegate.writeMessage(message, size); -} - -void Console::writeMessage(const Fw::ConstStringBase& message) { - this->writeMessage(message.toChar(), message.length()); -} - -ConsoleHandle* Console::getHandle() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage)); - return this->m_delegate.getHandle(); -} - -void Console::write(const CHAR* message, const FwSizeType size) { - Console::getSingleton().writeMessage(message, size); -} - -void Console::write(const Fw::ConstStringBase& message) { - Console::getSingleton().writeMessage(message.toChar(), message.length()); -} - -void Console::init() { - // Force trigger on the fly singleton setup - (void)Console::getSingleton(); -} - -Console& Console::getSingleton() { - static Console s_singleton; - // Registration happens once: re-registering on every access would silently replace a - // logger the project registered after the console singleton was first used - static bool s_registered = false; - if (not s_registered) { - s_registered = true; - Fw::Logger::registerLogger(&s_singleton); - } - return s_singleton; -} -} // namespace Os diff --git a/Os/Console.hpp b/Os/Console.hpp index f0a19cf1aa0..035d96c1648 100644 --- a/Os/Console.hpp +++ b/Os/Console.hpp @@ -1,139 +1,45 @@ // ====================================================================== // \title Os/Console.hpp -// \brief common function definitions for Os::Console +// \brief public Os::Console interface and alias +// +// This header aggregates all definitions needed to use Os::Console: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::Console type alias by +// forward-declaring a link-time delegate (e.g. DelegateConsole) or directly +// aliasing a concrete implementation (e.g. Va416x0::GdsConsole). +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/ConsoleInterface.hpp, which this header includes first. +// +// 2. Os/ConsoleInterface.hpp (IF) includes CFG first (so the Os::Console alias +// is available), then defines ConsoleHandle and ConsoleInterface. +// +// 3. OS_CONSOLE_HEADER (IMPL) is defined by CFG and points to the concrete +// implementation header. If using delegation, this points to +// Os/DelegateConsole.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation (e.g. GdsConsole.hpp). +// +// ConsoleInterface.hpp must precede OS_CONSOLE_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== #ifndef Os_Console_hpp_ #define Os_Console_hpp_ -#include -#include -#include -#include +#include "Os/ConsoleInterface.hpp" +// A project config/OsSelection.hpp that predates the Console selection defines neither the Os::Console +// alias nor OS_CONSOLE_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_CONSOLE_HEADER +#include "Os/DelegateConsole.hpp" namespace Os { -//! \brief Base class for storing implementation specific handle information -struct ConsoleHandle {}; - -// \brief Interface defining the properties of the console -class ConsoleInterface { - public: - //! \brief Default constructor - ConsoleInterface() = default; - - //! \brief Default destructor - virtual ~ConsoleInterface() = default; - - //! \brief write message to console - //! - //! Write a message to the console with a bounded size. - //! - //! \param message: raw message to write - //! \param size: size of the message to write to the console - virtual void writeMessage(const CHAR* message, const FwSizeType size) = 0; - - //! \brief returns the raw console handle - //! - //! Gets the raw console handle from the implementation. Note: users must include the implementation specific - //! header to make any real use of this handle. Otherwise it will be as an opaque type. - //! - //! \return raw console handle - //! - virtual ConsoleHandle* getHandle() = 0; - - //! \brief provide a pointer to a console delegate object - //! - //! This function must return a pointer to a `ConsoleInterface` object that contains the real implementation of - //! the console functions as defined by the implementor. This function must do several things to be considered - //! correctly implemented: - //! - //! 1. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. - //! e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage, - //! "FW_HANDLE_MAX_SIZE too small");` - //! 2. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. - //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");` - //! 3. If to_copy is null, placement new their implementation into `aligned_placement_new_memory` - //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;` - //! 4. If to_copy is non-null, placement new using copy constructor their implementation into - //! `aligned_placement_new_memory` - //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);` - //! 5. Return the result of the placement new - //! e.g. `return interface;` - //! - //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` - //! - static ConsoleInterface* getDelegate(ConsoleHandleStorage& aligned_placement_new_memory, - const ConsoleInterface* to_copy = nullptr); -}; - -class Console : public ConsoleInterface, public Fw::Logger { - public: - //! \brief Default constructor - Console(); - - //! \brief Default destructor - ~Console(); - - //! \brief copy constructor that copies the internal representation - Console(const Console& other); - - //! \brief assignment operator that copies the internal representation - Console& operator=(const Console& other); - - //! \brief write message to console - //! - //! Write a message to the console with a bounded size. This will delegate to the implementation defined write - //! method. - //! - //! \param message: raw message to write - //! \param size: size of the message to write to the console - void writeMessage(const CHAR* message, const FwSizeType size) override; - - //! \brief write message to console - //! - //! Write a message to the console as stored as a ConstStringBase type - //! - //! \param message: raw message to write (ConstStringBase) - void writeMessage(const Fw::ConstStringBase& message) override; - - //! \brief returns the raw console handle - //! - //! Gets the raw console handle from the implementation. Note: users must include the implementation specific - //! header to make any real use of this handle. Otherwise it will be as an opaque type. - //! - //! \return raw console handle - //! - ConsoleHandle* getHandle() override; - - //! \brief write message to console - //! - //! Write a message to the console as stored as a ConstStringBase type - //! - //! \param message: raw message to write (ConstStringBase) - static void write(const Fw::ConstStringBase& message); - - //! \brief write message to the global console - //! - //! Write a message to the console with a bounded size. This will delegate to the global singleton - //! implementation. - //! - //! \param message: raw message to write - //! \param size: size of the message to write to the console - static void write(const CHAR* message, const FwSizeType size); - - //! \brief initialize singleton - static void init(); - - //! \brief get a reference to singleton - //! \return reference to singleton - static Console& getSingleton(); - - private: - // This section is used to store the implementation-defined console handle. To Os::Console and fprime, this type - // is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle - // in the byte-array here and set `handle` to that address for storage. - alignas(FW_HANDLE_ALIGNMENT) ConsoleHandleStorage m_handle_storage; // Storage for the delegate - ConsoleInterface& m_delegate; //!< Delegate for the real implementation -}; -} // namespace Os +using Console = DelegateConsole; +} +#else +#include OS_CONSOLE_HEADER +#endif #endif diff --git a/Os/ConsoleInterface.cpp b/Os/ConsoleInterface.cpp new file mode 100644 index 00000000000..9ce89d42f24 --- /dev/null +++ b/Os/ConsoleInterface.cpp @@ -0,0 +1,13 @@ +// ====================================================================== +// \title Os/ConsoleInterface.cpp +// \brief common function implementation for Os::ConsoleInterface +// ====================================================================== +#include + +namespace Os { + +void ConsoleInterface::writeMessage(const Fw::ConstStringBase& message) { + this->writeMessage(message.toChar(), message.length()); +} + +} // namespace Os diff --git a/Os/ConsoleInterface.hpp b/Os/ConsoleInterface.hpp new file mode 100644 index 00000000000..ee38cfea3b9 --- /dev/null +++ b/Os/ConsoleInterface.hpp @@ -0,0 +1,81 @@ +// ====================================================================== +// \title Os/ConsoleInterface.hpp +// \brief Os::ConsoleHandle and Os::ConsoleInterface definitions +// ====================================================================== +#ifndef OS_CONSOLEINTERFACE_HPP_ +#define OS_CONSOLEINTERFACE_HPP_ + +#include +#include +#include +#include "config/OsSelection.hpp" + +namespace Os { +//! \brief Base class for storing implementation specific handle information +struct ConsoleHandle {}; + +// \brief Interface defining the properties of the console +class ConsoleInterface : public Fw::Logger { + public: + //! \brief Default constructor + ConsoleInterface() = default; + + //! \brief Default destructor + virtual ~ConsoleInterface() = default; + + //! \brief write message to console + //! + //! Write a message to the console with a bounded size. + //! + //! \param message: raw message to write + //! \param size: size of the message to write to the console + virtual void writeMessage(const CHAR* message, const FwSizeType size) = 0; + + //! \brief returns the raw console handle + //! + //! Gets the raw console handle from the implementation. Note: users must include the implementation specific + //! header to make any real use of this handle. Otherwise it will be as an opaque type. + //! + //! \return raw console handle + //! + virtual ConsoleHandle* getHandle() = 0; + + //! \brief provide a pointer to a console delegate object + //! + //! This function must return a pointer to a `ConsoleInterface` object that contains the real implementation of + //! the console functions as defined by the implementor. This function must do several things to be considered + //! correctly implemented: + //! + //! 1. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. + //! e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage, + //! "FW_HANDLE_MAX_SIZE too small");` + //! 2. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. + //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");` + //! 3. If to_copy is null, placement new their implementation into `aligned_placement_new_memory` + //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;` + //! 4. If to_copy is non-null, placement new using copy constructor their implementation into + //! `aligned_placement_new_memory` + //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);` + //! 5. Return the result of the placement new + //! e.g. `return interface;` + //! + //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` + //! + static ConsoleInterface* getDelegate(ConsoleHandleStorage& aligned_placement_new_memory, + const ConsoleInterface* to_copy = nullptr); + + // ------------------------------------------------------------------ + // Common virtual functions built on top of OS-specific functions + // ------------------------------------------------------------------ + + //! \brief write message to console + //! + //! Write a message to the console as stored as a ConstStringBase type. Satisfies Fw::Logger's writeMessage + //! contract by forwarding to the CHAR*-based console primitive. + //! + //! \param message: raw message to write (ConstStringBase) + void writeMessage(const Fw::ConstStringBase& message) override; +}; +} // namespace Os + +#endif // OS_CONSOLEINTERFACE_HPP_ diff --git a/Os/CountingSemaphore.cpp b/Os/CountingSemaphore.cpp deleted file mode 100644 index c4ef1a4ddc3..00000000000 --- a/Os/CountingSemaphore.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Os/CountingSemaphore.hpp" -#include "Fw/Types/Assert.hpp" - -namespace Os { -CountingSemaphore::CountingSemaphore(U32 initial_count) - : m_delegate(*CountingSemaphoreInterface::getDelegate(m_handle_storage, initial_count)) {} - -CountingSemaphore::~CountingSemaphore() { - m_delegate.~CountingSemaphoreInterface(); -} - -CountingSemaphore::Status CountingSemaphore::wait() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.wait(); -} - -CountingSemaphore::Status CountingSemaphore::waitTimeout(const Fw::TimeInterval& interval) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.waitTimeout(interval); -} - -CountingSemaphore::Status CountingSemaphore::tryWait() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.tryWait(); -} - -CountingSemaphore::Status CountingSemaphore::post() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.post(); -} - -CountingSemaphoreHandle* CountingSemaphore::getHandle() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.getHandle(); -} - -} // namespace Os diff --git a/Os/CountingSemaphore.hpp b/Os/CountingSemaphore.hpp index 6d4eb506690..8e56182d876 100644 --- a/Os/CountingSemaphore.hpp +++ b/Os/CountingSemaphore.hpp @@ -1,91 +1,48 @@ // ====================================================================== // \title Os/CountingSemaphore.hpp -// \brief common function definitions for Os::CountingSemaphore +// \brief public Os::CountingSemaphore interface and alias +// +// This header aggregates all definitions needed to use Os::CountingSemaphore: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the +// Os::CountingSemaphore type alias by forward-declaring a link-time +// delegate (e.g. DelegateCountingSemaphore) or directly aliasing a +// concrete implementation. +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/CountingSemaphoreInterface.hpp, which this header includes first. +// +// 2. Os/CountingSemaphoreInterface.hpp (IF) includes CFG first (so the +// Os::CountingSemaphore alias is available), then defines +// CountingSemaphoreHandle and CountingSemaphoreInterface. +// +// 3. OS_COUNTING_SEMAPHORE_HEADER (IMPL) is defined by CFG and points to +// the concrete implementation header. If using delegation, this points +// to Os/DelegateCountingSemaphore.hpp. If using compile-time selection, +// it points directly to a platform-specific implementation. +// +// CountingSemaphoreInterface.hpp must precede OS_COUNTING_SEMAPHORE_HEADER +// here, and CFG must never include either of them (that would form a cycle). // ====================================================================== #ifndef OS_COUNTING_SEMAPHORE_HPP_ #define OS_COUNTING_SEMAPHORE_HPP_ -#include "Fw/Time/TimeInterval.hpp" -#include "Os/Os.hpp" -namespace Os { - -class CountingSemaphoreHandle {}; - -class CountingSemaphoreInterface { - public: - enum Status { - OP_OK, //!< Operation was successful - ERROR_TIMEOUT, //!< Timeout occurred during wait - ERROR_INVALID, //!< Invalid semaphore or argument - ERROR_NOT_IMPLEMENTED, //!< Feature not implemented - NOT_SUPPORTED, //!< CountingSemaphore does not support operation - ERROR_OTHER //!< All other errors - }; - - //! \brief default constructor - CountingSemaphoreInterface() = default; - - //! \brief default virtual destructor - virtual ~CountingSemaphoreInterface() = default; - - //! \brief copy constructor is forbidden - CountingSemaphoreInterface(const CountingSemaphoreInterface& other) = delete; - - //! \brief assignment operator is forbidden - CountingSemaphoreInterface& operator=(const CountingSemaphoreInterface& other) = delete; - - //! \brief wait (decrement) the semaphore, blocking if count is zero - //! \return status of the operation - virtual Status wait() = 0; - - //! \brief wait on the semaphore with a timeout - //! \param interval maximum time to wait - //! \return OP_OK on success, ERROR_TIMEOUT if the interval elapsed - virtual Status waitTimeout(const Fw::TimeInterval& interval) = 0; - - //! \brief non-blocking attempt to decrement the semaphore - //! \return OP_OK if decremented, ERROR_TIMEOUT if count was zero - virtual Status tryWait() = 0; - - //! \brief post (increment) the semaphore, potentially waking a waiting thread - //! \return status of the operation - virtual Status post() = 0; +#include "Os/CountingSemaphoreInterface.hpp" - //! \brief return the underlying semaphore handle (implementation specific) - //! \return internal semaphore handle representation - virtual CountingSemaphoreHandle* getHandle() = 0; - - //! \brief provide a pointer to a CountingSemaphore delegate object - static CountingSemaphoreInterface* getDelegate(CountingSemaphoreHandleStorage& aligned_new_memory, - U32 initial_count); -}; - -class CountingSemaphore final : public CountingSemaphoreInterface { - public: - explicit CountingSemaphore(U32 initial_count); //!< Constructor with initial count - - ~CountingSemaphore() final; //!< Destructor - - CountingSemaphore(const CountingSemaphoreInterface& other) = delete; - - CountingSemaphore(const CountingSemaphore& other) = delete; - CountingSemaphore& operator=(const CountingSemaphore& other) = delete; - CountingSemaphore(CountingSemaphore&& other) = delete; - CountingSemaphore& operator=(CountingSemaphore&& other) = delete; - - Status wait() override; //!< wait (decrement), blocking if count is zero - - Status waitTimeout(const Fw::TimeInterval& interval) override; //!< wait with timeout - - Status tryWait() override; - - Status post() override; - - CountingSemaphoreHandle* getHandle() override; +// A project config/OsSelection.hpp that predates the CountingSemaphore selection defines neither the +// Os::CountingSemaphore alias nor OS_COUNTING_SEMAPHORE_HEADER; fall back to the link-time delegate so such projects +// keep building. +#ifndef OS_COUNTING_SEMAPHORE_HEADER +#include "Os/DelegateCountingSemaphore.hpp" +namespace Os { +using CountingSemaphore = DelegateCountingSemaphore; +} +#else +#include OS_COUNTING_SEMAPHORE_HEADER +#endif - private: - alignas(FW_HANDLE_ALIGNMENT) CountingSemaphoreHandleStorage m_handle_storage; - CountingSemaphoreInterface& m_delegate; -}; -} // namespace Os #endif // OS_COUNTING_SEMAPHORE_HPP_ diff --git a/Os/CountingSemaphoreInterface.hpp b/Os/CountingSemaphoreInterface.hpp new file mode 100644 index 00000000000..37c6b3e0baf --- /dev/null +++ b/Os/CountingSemaphoreInterface.hpp @@ -0,0 +1,64 @@ +// ====================================================================== +// \title Os/CountingSemaphoreInterface.hpp +// \brief Os::CountingSemaphoreHandle and Os::CountingSemaphoreInterface definitions +// ====================================================================== +#ifndef OS_COUNTINGSEMAPHOREINTERFACE_HPP_ +#define OS_COUNTINGSEMAPHOREINTERFACE_HPP_ +#include "Fw/Time/TimeInterval.hpp" +#include "Os/Os.hpp" +#include "config/OsSelection.hpp" + +namespace Os { + +class CountingSemaphoreHandle {}; + +class CountingSemaphoreInterface { + public: + enum Status { + OP_OK, //!< Operation was successful + ERROR_TIMEOUT, //!< Timeout occurred during wait + ERROR_INVALID, //!< Invalid semaphore or argument + ERROR_NOT_IMPLEMENTED, //!< Feature not implemented + NOT_SUPPORTED, //!< CountingSemaphore does not support operation + ERROR_OTHER //!< All other errors + }; + + //! \brief default constructor + CountingSemaphoreInterface() = default; + + //! \brief default virtual destructor + virtual ~CountingSemaphoreInterface() = default; + + //! \brief copy constructor is forbidden + CountingSemaphoreInterface(const CountingSemaphoreInterface& other) = delete; + + //! \brief assignment operator is forbidden + CountingSemaphoreInterface& operator=(const CountingSemaphoreInterface& other) = delete; + + //! \brief wait (decrement) the semaphore, blocking if count is zero + //! \return status of the operation + virtual Status wait() = 0; + + //! \brief wait on the semaphore with a timeout + //! \param interval maximum time to wait + //! \return OP_OK on success, ERROR_TIMEOUT if the interval elapsed + virtual Status waitTimeout(const Fw::TimeInterval& interval) = 0; + + //! \brief non-blocking attempt to decrement the semaphore + //! \return OP_OK if decremented, ERROR_TIMEOUT if count was zero + virtual Status tryWait() = 0; + + //! \brief post (increment) the semaphore, potentially waking a waiting thread + //! \return status of the operation + virtual Status post() = 0; + + //! \brief return the underlying semaphore handle (implementation specific) + //! \return internal semaphore handle representation + virtual CountingSemaphoreHandle* getHandle() = 0; + + //! \brief provide a pointer to a CountingSemaphore delegate object + static CountingSemaphoreInterface* getDelegate(CountingSemaphoreHandleStorage& aligned_new_memory, + U32 initial_count); +}; +} // namespace Os +#endif // OS_COUNTINGSEMAPHOREINTERFACE_HPP_ diff --git a/Os/Cpu.hpp b/Os/Cpu.hpp index d9f174bf537..ab1a00d5cbf 100644 --- a/Os/Cpu.hpp +++ b/Os/Cpu.hpp @@ -1,151 +1,45 @@ // ====================================================================== // \title Os/Cpu.hpp -// \brief common function definitions for Os::Cpu +// \brief public Os::Cpu interface and alias +// +// This header aggregates all definitions needed to use Os::Cpu: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::Cpu type alias by +// forward-declaring a link-time delegate (e.g. DelegateCpu) or directly +// aliasing a concrete implementation. +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/CpuInterface.hpp, which this header includes first. +// +// 2. Os/CpuInterface.hpp (IF) includes CFG first (so the Os::Cpu alias is +// available), then defines CpuHandle and CpuInterface. +// +// 3. OS_CPU_HEADER (IMPL) is defined by CFG and points to the concrete +// implementation header. If using delegation, this points to +// Os/DelegateCpu.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation. +// +// CpuInterface.hpp must precede OS_CPU_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== -#include "Os/Os.hpp" +#ifndef Os_Cpu_hpp +#define Os_Cpu_hpp -#ifndef OS_CPU_HPP_ -#define OS_CPU_HPP_ +#include "Os/CpuInterface.hpp" +// A project config/OsSelection.hpp that predates the Cpu selection defines neither the Os::Cpu +// alias nor OS_CPU_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_CPU_HEADER +#include "Os/DelegateCpu.hpp" namespace Os { +using Cpu = DelegateCpu; +} +#else +#include OS_CPU_HEADER +#endif -//! \brief Cpu variable handle parent -class CpuHandle {}; - -//! \brief interface for cpu implementation -class CpuInterface { - public: - using Status = Os::Generic::Status; - using Ticks = Os::Generic::UsedTotal; - - //! Default constructor - CpuInterface() = default; - //! Default destructor - virtual ~CpuInterface() = default; - - //! \brief copy constructor is forbidden - CpuInterface(const CpuInterface& other) = delete; - - //! \brief assignment operator is forbidden - virtual CpuInterface& operator=(const CpuInterface& other) = delete; // NO_CODESONAR (cpp:S3657) - - //! \brief Request the count of the CPUs detected by the system - //! - //! \param cpu_count: (output) filled with CPU count on system - //! \return: OP_OK with valid CPU count, ERROR when error occurs - //! - virtual Status _getCount(FwSizeType& cpu_count) = 0; - - //! \brief Get the CPU tick information for a given CPU - //! - //! CPU ticks represent a small time slice of processor time. This will retrieve the used CPU ticks and total - //! ticks for a given CPU. This information in a running accumulation and thus a sample-to-sample - //! differencing is needed to see the 'realtime' changing load. This shall be done by the caller. - //! - //! \param ticks: (output) filled with the tick information for the given CPU - //! \param cpu_index: index for CPU to read. Default: 0 - //! \return: ERROR when error occurs, OK otherwise. - //! - virtual Status _getTicks(Ticks& ticks, FwSizeType cpu_index) = 0; - - //! \brief return the underlying cpu handle (implementation specific). - //! \return internal task handle representation - virtual CpuHandle* getHandle() = 0; - - //! \brief provide a pointer to a Mutex delegate object - static CpuInterface* getDelegate(CpuHandleStorage& aligned_new_memory); -}; - -//! \brief cpu implementation -class Cpu final : public CpuInterface { - public: - //! \brief default constructor - Cpu(); - - //! \brief default virtual destructor - ~Cpu() final; - - //! \brief copy constructor is forbidden - Cpu(const CpuInterface& other) = delete; - - //! \brief copy constructor is forbidden - Cpu(const CpuInterface* other) = delete; - - //! \brief assignment operator is forbidden - CpuInterface& operator=(const CpuInterface& other) override = delete; - - //----------------------------------------------------------------------------- - // Interface methods - //----------------------------------------------------------------------------- - - //! \brief initialize the singleton - static void init(); - - //! \brief return singleton - static Cpu& getSingleton(); - - //----------------------------------------------------------------------------- - // Delegating methods - //----------------------------------------------------------------------------- - - //! \brief Request the count of the CPUs detected by the system - //! - //! This method wraps delegates to the underlying implementation. - //! - //! \param cpu_count: (output) filled with CPU count on system - //! \return: OP_OK with valid CPU count, ERROR when error occurs - //! - Status _getCount(FwSizeType& cpu_count) override; - - //! \brief Get the CPU tick information for a given CPU - //! - //! CPU ticks represent a small time slice of processor time. This will retrieve the used CPU ticks and total - //! ticks for a given CPU. This information in a running accumulation and thus a sample-to-sample - //! differencing is needed to see the 'realtime' changing load. This shall be done by the caller. This method wraps - //! delegates to the underlying implementation. - //! - //! \param ticks: (output) filled with the tick information for the given CPU - //! \param cpu_index: index for CPU to read. Default: 0 - //! \return: ERROR when error occurs, OK otherwise. - //! - Status _getTicks(Ticks& ticks, FwSizeType cpu_index) override; - - //! \brief return the underlying cpu handle (implementation specific). - //! \return internal task handle representation - CpuHandle* getHandle() override; - - //----------------------------------------------------------------------------- - // Static interface (singleton) methods - //----------------------------------------------------------------------------- - - //! \brief Request the count of the CPUs detected by the system - //! - //! This method wraps a singleton implementation. - //! - //! \param cpu_count: (output) filled with CPU count on system - //! \return: OP_OK with valid CPU count, ERROR when error occurs - //! - static Status getCount(FwSizeType& cpu_count); - - //! \brief Get the CPU tick information for a given CPU - //! - //! CPU ticks represent a small time slice of processor time. This will retrieve the used CPU ticks and total - //! ticks for a given CPU. This information in a running accumulation and thus a sample-to-sample - //! differencing is needed to see the 'realtime' changing load. This shall be done by the caller. This method wraps - //! a singleton implementation. - //! - //! \param ticks: (output) filled with the tick information for the given CPU - //! \param cpu_index: index for CPU to read. Default: 0 - //! \return: ERROR when error occurs, OK otherwise. - //! - static Status getTicks(Ticks& ticks, FwSizeType cpu_index); - - private: - // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is - // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in - // the byte-array here and set `handle` to that address for storage. - alignas(FW_HANDLE_ALIGNMENT) CpuHandleStorage m_handle_storage; //!< Storage for aligned data - CpuInterface& m_delegate; //!< Delegate for the real implementation -}; -} // namespace Os -#endif // OS_CONDITION_HPP_ +#endif // Os_Cpu_hpp diff --git a/Os/CpuInterface.cpp b/Os/CpuInterface.cpp new file mode 100644 index 00000000000..f1be8af1ff4 --- /dev/null +++ b/Os/CpuInterface.cpp @@ -0,0 +1,17 @@ +// ====================================================================== +// \title Os/CpuInterface.cpp +// \brief common convenience-wrapper implementation for Os::Cpu +// ====================================================================== +#include + +namespace Os { + +CpuInterface::Status CpuInterface::getCount(FwSizeType& cpu_count) { + return Os::Cpu::getSingleton()._getCount(cpu_count); +} + +CpuInterface::Status CpuInterface::getTicks(Ticks& ticks, FwSizeType cpu_index) { + return Os::Cpu::getSingleton()._getTicks(ticks, cpu_index); +} + +} // namespace Os diff --git a/Os/CpuInterface.hpp b/Os/CpuInterface.hpp new file mode 100644 index 00000000000..97256382ef7 --- /dev/null +++ b/Os/CpuInterface.hpp @@ -0,0 +1,83 @@ +// ====================================================================== +// \title Os/CpuInterface.hpp +// \brief Os::CpuHandle and Os::CpuInterface definitions +// ====================================================================== +#ifndef OS_CPUINTERFACE_HPP_ +#define OS_CPUINTERFACE_HPP_ + +#include "Os/Os.hpp" +#include "config/OsSelection.hpp" + +namespace Os { + +//! \brief Cpu variable handle parent +class CpuHandle {}; + +//! \brief interface for cpu implementation +class CpuInterface { + public: + using Status = Os::Generic::Status; + using Ticks = Os::Generic::UsedTotal; + + //! Default constructor + CpuInterface() = default; + //! Default destructor + virtual ~CpuInterface() = default; + + //! \brief copy constructor is forbidden + CpuInterface(const CpuInterface& other) = delete; + + //! \brief assignment operator is forbidden + virtual CpuInterface& operator=(const CpuInterface& other) = delete; // NO_CODESONAR (cpp:S3657) + + //! \brief Request the count of the CPUs detected by the system + //! + //! \param cpu_count: (output) filled with CPU count on system + //! \return: OP_OK with valid CPU count, ERROR when error occurs + //! + virtual Status _getCount(FwSizeType& cpu_count) = 0; + + //! \brief Get the CPU tick information for a given CPU + //! + //! CPU ticks represent a small time slice of processor time. This will retrieve the used CPU ticks and total + //! ticks for a given CPU. This information in a running accumulation and thus a sample-to-sample + //! differencing is needed to see the 'realtime' changing load. This shall be done by the caller. + //! + //! \param ticks: (output) filled with the tick information for the given CPU + //! \param cpu_index: index for CPU to read. Default: 0 + //! \return: ERROR when error occurs, OK otherwise. + //! + virtual Status _getTicks(Ticks& ticks, FwSizeType cpu_index) = 0; + + //! \brief return the underlying cpu handle (implementation specific). + //! \return internal task handle representation + virtual CpuHandle* getHandle() = 0; + + //! \brief provide a pointer to a Cpu delegate object + static CpuInterface* getDelegate(CpuHandleStorage& aligned_new_memory); + + // ------------------------------------------------------------------ + // Convenience static wrappers + // ------------------------------------------------------------------ + // These are implemented in terms of the virtual instance functions above, + // and are inherited (via ordinary class-scope lookup) by every concrete + // Os::Cpu backend, regardless of whether that backend is a link-time + // delegate or a direct compile-time alias. + + //! \brief Request the count of the CPUs detected by the system + //! + //! \param cpu_count: (output) filled with CPU count on system + //! \return: OP_OK with valid CPU count, ERROR when error occurs + //! + static Status getCount(FwSizeType& cpu_count); + + //! \brief Get the CPU tick information for a given CPU + //! + //! \param ticks: (output) filled with the tick information for the given CPU + //! \param cpu_index: index for CPU to read. Default: 0 + //! \return: ERROR when error occurs, OK otherwise. + //! + static Status getTicks(Ticks& ticks, FwSizeType cpu_index); +}; +} // namespace Os +#endif // OS_CPUINTERFACE_HPP_ diff --git a/Os/DelegateConsole.cpp b/Os/DelegateConsole.cpp new file mode 100644 index 00000000000..137491c6ad1 --- /dev/null +++ b/Os/DelegateConsole.cpp @@ -0,0 +1,66 @@ +// ====================================================================== +// \title Os/DelegateConsole.cpp +// \brief common function implementation for Os::Console +// ====================================================================== +#include +#include +#include + +namespace Os { +DelegateConsole::DelegateConsole() + : ConsoleInterface(), m_handle_storage(), m_delegate(*ConsoleInterface::getDelegate(m_handle_storage)) {} + +DelegateConsole::~DelegateConsole() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + m_delegate.~ConsoleInterface(); +} + +DelegateConsole::DelegateConsole(const DelegateConsole& other) + : m_handle_storage(), m_delegate(*ConsoleInterface::getDelegate(m_handle_storage, &other.m_delegate)) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +DelegateConsole& DelegateConsole::operator=(const DelegateConsole& other) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (this != &other) { + this->m_delegate = *ConsoleInterface::getDelegate(m_handle_storage, &other.m_delegate); + } + return *this; +} + +void DelegateConsole::writeMessage(const CHAR* message, const FwSizeType size) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(message != nullptr || size == 0); + this->m_delegate.writeMessage(message, size); +} + +ConsoleHandle* DelegateConsole::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +void DelegateConsole::write(const CHAR* message, const FwSizeType size) { + DelegateConsole::getSingleton().writeMessage(message, size); +} + +void DelegateConsole::write(const Fw::ConstStringBase& message) { + DelegateConsole::getSingleton().writeMessage(message.toChar(), message.length()); +} + +void DelegateConsole::init() { + // Force trigger on the fly singleton setup + (void)DelegateConsole::getSingleton(); +} + +DelegateConsole& DelegateConsole::getSingleton() { + static DelegateConsole s_singleton; + // Registration happens once: re-registering on every access would silently replace a + // logger the project registered after the console singleton was first used + static bool s_registered = false; + if (not s_registered) { + s_registered = true; + Fw::Logger::registerLogger(&s_singleton); + } + return s_singleton; +} +} // namespace Os diff --git a/Os/DelegateConsole.hpp b/Os/DelegateConsole.hpp new file mode 100644 index 00000000000..d5b6593cf3f --- /dev/null +++ b/Os/DelegateConsole.hpp @@ -0,0 +1,79 @@ +// ====================================================================== +// \title Os/DelegateConsole.hpp +// \brief Define the Os::DelegateConsole class +// ====================================================================== +#ifndef OS_DELEGATECONSOLE_HPP_ +#define OS_DELEGATECONSOLE_HPP_ + +#include "Os/ConsoleInterface.hpp" + +namespace Os { + +class DelegateConsole final : public ConsoleInterface { + public: + //! \brief Default constructor + DelegateConsole(); + + //! \brief Default destructor + ~DelegateConsole(); + + //! \brief copy constructor that copies the internal representation + DelegateConsole(const DelegateConsole& other); + + //! \brief assignment operator that copies the internal representation + DelegateConsole& operator=(const DelegateConsole& other); + + // Bring the base class's writeMessage(const Fw::ConstStringBase&) overload back into scope + using ConsoleInterface::writeMessage; + + //! \brief write message to console + //! + //! Write a message to the console with a bounded size. This will delegate to the implementation defined write + //! method. + //! + //! \param message: raw message to write + //! \param size: size of the message to write to the console + void writeMessage(const CHAR* message, const FwSizeType size) override; + + //! \brief returns the raw console handle + //! + //! Gets the raw console handle from the implementation. Note: users must include the implementation specific + //! header to make any real use of this handle. Otherwise it will be as an opaque type. + //! + //! \return raw console handle + //! + ConsoleHandle* getHandle() override; + + //! \brief write message to console + //! + //! Write a message to the console as stored as a ConstStringBase type + //! + //! \param message: raw message to write (ConstStringBase) + static void write(const Fw::ConstStringBase& message); + + //! \brief write message to the global console + //! + //! Write a message to the console with a bounded size. This will delegate to the global singleton + //! implementation. + //! + //! \param message: raw message to write + //! \param size: size of the message to write to the console + static void write(const CHAR* message, const FwSizeType size); + + //! \brief initialize singleton + static void init(); + + //! \brief get a reference to singleton + //! \return reference to singleton + static DelegateConsole& getSingleton(); + + private: + // This section is used to store the implementation-defined console handle. To Os::Console and fprime, this type + // is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle + // in the byte-array here and set `handle` to that address for storage. + alignas(FW_HANDLE_ALIGNMENT) ConsoleHandleStorage m_handle_storage; // Storage for the delegate + ConsoleInterface& m_delegate; //!< Delegate for the real implementation +}; +} // namespace Os + +#endif // OS_DELEGATECONSOLE_HPP_ diff --git a/Os/DelegateCountingSemaphore.cpp b/Os/DelegateCountingSemaphore.cpp new file mode 100644 index 00000000000..b12fe7a6e97 --- /dev/null +++ b/Os/DelegateCountingSemaphore.cpp @@ -0,0 +1,41 @@ +// ====================================================================== +// \title Os/DelegateCountingSemaphore.cpp +// \brief common function implementation for Os::CountingSemaphore +// ====================================================================== +#include "Os/DelegateCountingSemaphore.hpp" +#include "Fw/Types/Assert.hpp" + +namespace Os { +DelegateCountingSemaphore::DelegateCountingSemaphore(U32 initial_count) + : m_delegate(*CountingSemaphoreInterface::getDelegate(m_handle_storage, initial_count)) {} + +DelegateCountingSemaphore::~DelegateCountingSemaphore() { + m_delegate.~CountingSemaphoreInterface(); +} + +DelegateCountingSemaphore::Status DelegateCountingSemaphore::wait() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.wait(); +} + +DelegateCountingSemaphore::Status DelegateCountingSemaphore::waitTimeout(const Fw::TimeInterval& interval) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.waitTimeout(interval); +} + +DelegateCountingSemaphore::Status DelegateCountingSemaphore::tryWait() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.tryWait(); +} + +DelegateCountingSemaphore::Status DelegateCountingSemaphore::post() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.post(); +} + +CountingSemaphoreHandle* DelegateCountingSemaphore::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +} // namespace Os diff --git a/Os/DelegateCountingSemaphore.hpp b/Os/DelegateCountingSemaphore.hpp new file mode 100644 index 00000000000..5af0c96f450 --- /dev/null +++ b/Os/DelegateCountingSemaphore.hpp @@ -0,0 +1,40 @@ +// ====================================================================== +// \title Os/DelegateCountingSemaphore.hpp +// \brief defines a delegate-based implementation of CountingSemaphoreInterface +// ====================================================================== +#ifndef OS_DELEGATECOUNTINGSEMAPHORE_HPP_ +#define OS_DELEGATECOUNTINGSEMAPHORE_HPP_ + +#include + +namespace Os { + +class DelegateCountingSemaphore final : public CountingSemaphoreInterface { + public: + explicit DelegateCountingSemaphore(U32 initial_count); //!< Constructor with initial count + + ~DelegateCountingSemaphore() final; //!< Destructor + + DelegateCountingSemaphore(const CountingSemaphoreInterface& other) = delete; + + DelegateCountingSemaphore(const DelegateCountingSemaphore& other) = delete; + DelegateCountingSemaphore& operator=(const DelegateCountingSemaphore& other) = delete; + DelegateCountingSemaphore(DelegateCountingSemaphore&& other) = delete; + DelegateCountingSemaphore& operator=(DelegateCountingSemaphore&& other) = delete; + + Status wait() override; //!< wait (decrement), blocking if count is zero + + Status waitTimeout(const Fw::TimeInterval& interval) override; //!< wait with timeout + + Status tryWait() override; + + Status post() override; + + CountingSemaphoreHandle* getHandle() override; + + private: + alignas(FW_HANDLE_ALIGNMENT) CountingSemaphoreHandleStorage m_handle_storage; + CountingSemaphoreInterface& m_delegate; +}; +} // namespace Os +#endif // OS_DELEGATECOUNTINGSEMAPHORE_HPP_ diff --git a/Os/Cpu.cpp b/Os/DelegateCpu.cpp similarity index 52% rename from Os/Cpu.cpp rename to Os/DelegateCpu.cpp index b061fe0f935..481c8795b33 100644 --- a/Os/Cpu.cpp +++ b/Os/DelegateCpu.cpp @@ -1,47 +1,39 @@ // ====================================================================== -// \title Os/Cpu.hpp -// \brief common function implementations for Os::Cpu +// \title Os/DelegateCpu.cpp +// \brief common function implementations for Os::DelegateCpu // ====================================================================== -#include "Os/Cpu.hpp" +#include "Os/DelegateCpu.hpp" #include "Fw/Types/Assert.hpp" namespace Os { -Cpu::Cpu() : m_delegate(*CpuInterface::getDelegate(m_handle_storage)) {} +DelegateCpu::DelegateCpu() : m_handle_storage(), m_delegate(*CpuInterface::getDelegate(m_handle_storage)) {} -Cpu::~Cpu() { +DelegateCpu::~DelegateCpu() { m_delegate.~CpuInterface(); } -void Cpu::init() { - (void)Cpu::getSingleton(); +void DelegateCpu::init() { + (void)DelegateCpu::getSingleton(); } -Cpu& Cpu::getSingleton() { - static Cpu _singleton; +DelegateCpu& DelegateCpu::getSingleton() { + static DelegateCpu _singleton; return _singleton; } -Cpu::Status Cpu::_getCount(FwSizeType& cpu_count) { +DelegateCpu::Status DelegateCpu::_getCount(FwSizeType& cpu_count) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate._getCount(cpu_count); } -Cpu::Status Cpu::_getTicks(Ticks& ticks, FwSizeType cpu_index) { +DelegateCpu::Status DelegateCpu::_getTicks(Ticks& ticks, FwSizeType cpu_index) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate._getTicks(ticks, cpu_index); } -CpuHandle* Cpu::getHandle() { +CpuHandle* DelegateCpu::getHandle() { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate.getHandle(); } - -Cpu::Status Cpu::getCount(FwSizeType& cpu_count) { - return Cpu::getSingleton()._getCount(cpu_count); -} - -Cpu::Status Cpu::getTicks(Ticks& ticks, FwSizeType cpu_index) { - return Cpu::getSingleton()._getTicks(ticks, cpu_index); -} } // namespace Os diff --git a/Os/DelegateCpu.hpp b/Os/DelegateCpu.hpp new file mode 100644 index 00000000000..df0f265d553 --- /dev/null +++ b/Os/DelegateCpu.hpp @@ -0,0 +1,78 @@ +// ====================================================================== +// \title Os/DelegateCpu.hpp +// \brief defines a delegate-based implementation of CpuInterface +// ====================================================================== +#ifndef OS_DELEGATECPU_HPP_ +#define OS_DELEGATECPU_HPP_ + +#include + +namespace Os { + +//! \brief cpu delegate implementation +class DelegateCpu final : public CpuInterface { + public: + //! \brief default constructor + DelegateCpu(); + + //! \brief default virtual destructor + ~DelegateCpu() final; + + //! \brief copy constructor is forbidden + DelegateCpu(const CpuInterface& other) = delete; + + //! \brief copy constructor is forbidden + DelegateCpu(const CpuInterface* other) = delete; + + //! \brief assignment operator is forbidden + CpuInterface& operator=(const CpuInterface& other) override = delete; + + //----------------------------------------------------------------------------- + // Interface methods + //----------------------------------------------------------------------------- + + //! \brief initialize the singleton + static void init(); + + //! \brief return singleton + static DelegateCpu& getSingleton(); + + //----------------------------------------------------------------------------- + // Delegating methods + //----------------------------------------------------------------------------- + + //! \brief Request the count of the CPUs detected by the system + //! + //! This method wraps delegates to the underlying implementation. + //! + //! \param cpu_count: (output) filled with CPU count on system + //! \return: OP_OK with valid CPU count, ERROR when error occurs + //! + Status _getCount(FwSizeType& cpu_count) override; + + //! \brief Get the CPU tick information for a given CPU + //! + //! CPU ticks represent a small time slice of processor time. This will retrieve the used CPU ticks and total + //! ticks for a given CPU. This information in a running accumulation and thus a sample-to-sample + //! differencing is needed to see the 'realtime' changing load. This shall be done by the caller. This method wraps + //! delegates to the underlying implementation. + //! + //! \param ticks: (output) filled with the tick information for the given CPU + //! \param cpu_index: index for CPU to read. Default: 0 + //! \return: ERROR when error occurs, OK otherwise. + //! + Status _getTicks(Ticks& ticks, FwSizeType cpu_index) override; + + //! \brief return the underlying cpu handle (implementation specific). + //! \return internal task handle representation + CpuHandle* getHandle() override; + + private: + // This section is used to store the implementation-defined cpu handle. To Os::Cpu and fprime, this type is + // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in + // the byte-array here and set `handle` to that address for storage. + alignas(FW_HANDLE_ALIGNMENT) CpuHandleStorage m_handle_storage; //!< Storage for aligned data + CpuInterface& m_delegate; //!< Delegate for the real implementation +}; +} // namespace Os +#endif // OS_DELEGATECPU_HPP_ diff --git a/Os/DelegateDirectory.cpp b/Os/DelegateDirectory.cpp new file mode 100644 index 00000000000..ac263813e92 --- /dev/null +++ b/Os/DelegateDirectory.cpp @@ -0,0 +1,65 @@ +// ====================================================================== +// \title Os/DelegateDirectory.cpp +// \brief common function implementation for Os::DelegateDirectory +// ====================================================================== +#include +#include + +namespace Os { + +DelegateDirectory::DelegateDirectory() + : m_handle_storage(), m_delegate(*DirectoryInterface::getDelegate(m_handle_storage)) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +DelegateDirectory::~DelegateDirectory() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (this->isOpen()) { + this->close(); + } + this->m_delegate.~DirectoryInterface(); +} + +DirectoryHandle* DelegateDirectory::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +DelegateDirectory::Status DelegateDirectory::open(const char* path, OpenMode mode) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + FW_ASSERT(mode >= 0 and mode < OpenMode::MAX_OPEN_MODE); + Status status = this->m_delegate.open(path, mode); + if (status == Status::OP_OK) { + this->setOpen(true); + } + return status; +} + +DelegateDirectory::Status DelegateDirectory::rewind() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (not this->isOpen()) { + return Status::NOT_OPENED; + } + return this->m_delegate.rewind(); +} + +DelegateDirectory::Status DelegateDirectory::read(char* fileNameBuffer, FwSizeType bufSize) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (not this->isOpen()) { + return Status::NOT_OPENED; + } + FW_ASSERT(fileNameBuffer != nullptr); + FW_ASSERT(bufSize > 0); + Status status = this->m_delegate.read(fileNameBuffer, bufSize); + fileNameBuffer[bufSize - 1] = '\0'; // Guarantee null-termination + return status; +} + +void DelegateDirectory::close() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + this->setOpen(false); + return this->m_delegate.close(); +} + +} // namespace Os diff --git a/Os/DelegateDirectory.hpp b/Os/DelegateDirectory.hpp new file mode 100644 index 00000000000..2c18c3d2e12 --- /dev/null +++ b/Os/DelegateDirectory.hpp @@ -0,0 +1,86 @@ +// ====================================================================== +// \title Os/DelegateDirectory.hpp +// \brief defines a delegate-based implementation of DirectoryInterface +// ====================================================================== +#ifndef OS_DELEGATEDIRECTORY_HPP_ +#define OS_DELEGATEDIRECTORY_HPP_ + +#include + +namespace Os { + +//! \brief Directory delegate class +//! +//! This class provides a common interface for directory operations, such as reading files in a directory +//! and getting the number of files in a directory. +class DelegateDirectory final : public DirectoryInterface { + public: + //! \brief Constructor + DelegateDirectory(); + + //! \brief Destructor + //! + //! Destructor will close the Directory if it is open + ~DelegateDirectory() final; + + //! \brief return the underlying Directory handle (implementation specific) + //! \return internal Directory handle representation + DirectoryHandle* getHandle() override; + + // ------------------------------------------------------------ + // Implementation-specific Directory member functions + // ------------------------------------------------------------ + + // Bring the DirectoryInterface convenience overloads into scope; overriding a + // single `read` overload below would otherwise hide all of them. + using DirectoryInterface::read; + + //! \brief Open or create a directory + //! + //! Using the path provided, this function will open or create a directory. + //! Use OpenMode::READ to open an existing directory and error if the directory is not found + //! Use OpenMode::CREATE_IF_MISSING to open a directory, creating the directory if it doesn't exist + //! Use OpenMode::CREATE_EXCLUSIVE to open a directory, creating the directory and erroring if it already exists + //! + //! It is invalid to pass `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! + //! \param path: path of directory to open + //! \param mode: enum (READ, CREATE_IF_MISSING, CREATE_EXCLUSIVE). See notes above for more information + //! \return status of the operation + Status open(const char* path, OpenMode mode) override; + + //! \brief Rewind directory stream + //! + //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. + //! + //! \return status of the operation + Status rewind() override; + + //! \brief Get next filename from directory stream + //! + //! Write at most buffSize characters of the file name to fileNameBuffer and guarantee null-termination. + //! This function skips the current directory (.) and parent directory (..) entries. + //! Returns NO_MORE_FILES if there are no more files to read from the buffer. + //! + //! It is invalid to pass `nullptr` as fileNameBuffer. + //! + //! \param fileNameBuffer: buffer to store filename + //! \param buffSize: size of fileNameBuffer + //! \return status of the operation + Status read(char* fileNameBuffer, FwSizeType buffSize) override; + + //! \brief Close directory + void close() override; + + private: + // This section is used to store the implementation-defined Directory handle. To Os::Directory and fprime, this type + // is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in + // the byte-array here and set `handle` to that address for storage. + alignas(FW_HANDLE_ALIGNMENT) DirectoryHandleStorage m_handle_storage; //!< Directory handle storage + DirectoryInterface& m_delegate; +}; + +} // namespace Os + +#endif // OS_DELEGATEDIRECTORY_HPP_ diff --git a/Os/DelegateFile.cpp b/Os/DelegateFile.cpp new file mode 100644 index 00000000000..4a730363069 --- /dev/null +++ b/Os/DelegateFile.cpp @@ -0,0 +1,210 @@ +// ====================================================================== +// \title Os/DelegateFile.cpp +// \brief link-time delegate implementation for Os::DelegateFile +// ====================================================================== +#include +#include +#include +#include + +namespace Os { + +DelegateFile::DelegateFile() + : FileInterface(), m_handle_storage(), m_delegate(*FileInterface::getDelegate(m_handle_storage)), m_crc_buffer() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +DelegateFile::~DelegateFile() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (this->isOpen()) { + this->close(); + } + m_delegate.~FileInterface(); +} + +DelegateFile::DelegateFile(const DelegateFile& other) + : FileInterface(other), + m_handle_storage(), + m_delegate(*FileInterface::getDelegate(m_handle_storage, &other.m_delegate)), + m_hash(other.m_hash), + m_crc_buffer() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +DelegateFile& DelegateFile::operator=(const DelegateFile& other) { + if (this != &other) { + // The delegate below is constructed over the storage of the existing one. Any file this + // object currently holds must be closed first or its handle is orphaned permanently. + if (this->isOpen()) { + this->close(); + } + this->m_delegate.~FileInterface(); + FileInterface::operator=(other); + this->m_hash = other.m_hash; + (void)FileInterface::getDelegate(m_handle_storage, &other.m_delegate); + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + } + return *this; +} + +DelegateFile::Status DelegateFile::open(const char* filepath, + DelegateFile::Mode requested_mode, + DelegateFile::OverwriteType overwrite) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(nullptr != filepath); + FW_ASSERT(DelegateFile::Mode::OPEN_NO_MODE < requested_mode && DelegateFile::Mode::MAX_OPEN_MODE > requested_mode); + FW_ASSERT((0 <= overwrite) && (overwrite < OverwriteType::MAX_OVERWRITE_TYPE)); + // Check for already opened file + if (this->isOpen()) { + return DelegateFile::Status::INVALID_MODE; + } + DelegateFile::Status status = this->m_delegate.open(filepath, requested_mode, overwrite); + if (status == DelegateFile::Status::OP_OK) { + this->setMode(requested_mode); + // Reset any open CRC calculation to match the freshly-opened file + this->m_hash.init(); + } + return status; +} + +void DelegateFile::close() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + this->m_delegate.close(); + this->setMode(Mode::OPEN_NO_MODE); +} + +DelegateFile::Status DelegateFile::size(FwSizeType& size_result) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + if (not this->isOpen()) { + return DelegateFile::Status::NOT_OPENED; + } + return this->m_delegate.size(size_result); +} + +DelegateFile::Status DelegateFile::position(FwSizeType& position_result) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + return DelegateFile::Status::NOT_OPENED; + } + return this->m_delegate.position(position_result); +} + +DelegateFile::Status DelegateFile::preallocate(FwSizeType offset, FwSizeType length) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + return DelegateFile::Status::NOT_OPENED; + } else if (this->getMode() == FileInterface::Mode::OPEN_READ) { + return DelegateFile::Status::INVALID_MODE; + } + return this->m_delegate.preallocate(offset, length); +} + +DelegateFile::Status DelegateFile::seek(FwSignedSizeType offset, DelegateFile::SeekType seekType) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT((0 <= seekType) && (seekType < SeekType::MAX_SEEK_TYPE)); + // Cannot do a seek with a negative offset in absolute mode + FW_ASSERT((seekType == DelegateFile::SeekType::RELATIVE) || (offset >= 0)); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + return DelegateFile::Status::NOT_OPENED; + } + return this->m_delegate.seek(offset, seekType); +} + +DelegateFile::Status DelegateFile::flush() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + return DelegateFile::Status::NOT_OPENED; + } else if (this->getMode() == FileInterface::Mode::OPEN_READ) { + return DelegateFile::Status::INVALID_MODE; + } + return this->m_delegate.flush(); +} + +DelegateFile::Status DelegateFile::read(U8* buffer, FwSizeType& size, DelegateFile::WaitType wait) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(buffer != nullptr); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + size = 0; + return DelegateFile::Status::NOT_OPENED; + } else if (this->getMode() != FileInterface::Mode::OPEN_READ) { + size = 0; + return DelegateFile::Status::INVALID_MODE; + } + return this->m_delegate.read(buffer, size, wait); +} + +DelegateFile::Status DelegateFile::write(const U8* buffer, FwSizeType& size, DelegateFile::WaitType wait) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(buffer != nullptr); + // Check that the file is open before attempting operation + if (not this->isOpen()) { + size = 0; + return DelegateFile::Status::NOT_OPENED; + } else if (this->getMode() == FileInterface::Mode::OPEN_READ) { + size = 0; + return DelegateFile::Status::INVALID_MODE; + } + return this->m_delegate.write(buffer, size, wait); +} + +FileHandle* DelegateFile::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +// ---------------------------------------------------------------------- +// CRC implementation -- the scratch state lives here, not on FileInterface; +// see the design note in Os/FileInterface.hpp. +// ---------------------------------------------------------------------- + +DelegateFile::Status DelegateFile::calculateCrc(U32& crc) { + DelegateFile::Status status = DelegateFile::Status::OP_OK; + FwSizeType size = FW_FILE_CHUNK_SIZE; + crc = 0; + for (FwSizeType i = 0; i < std::numeric_limits::max(); i++) { + status = this->incrementalCrc(size); + // Break on eof or error + if ((size != FW_FILE_CHUNK_SIZE) || (status != DelegateFile::OP_OK)) { + break; + } + } + // When successful, finalize the CRC + if (status == DelegateFile::OP_OK) { + status = this->finalizeCrc(crc); + } + return status; +} + +DelegateFile::Status DelegateFile::incrementalCrc(FwSizeType& size) { + DelegateFile::Status status = DelegateFile::Status::OP_OK; + FW_ASSERT(size <= FW_FILE_CHUNK_SIZE, FwAssertArgType(size)); + if (not this->isOpen()) { + status = DelegateFile::Status::NOT_OPENED; + } else if (OPEN_READ != this->getMode()) { + status = DelegateFile::Status::INVALID_MODE; + } else { + // Read data without waiting for additional data to be available + status = this->read(this->m_crc_buffer, size, DelegateFile::WaitType::NO_WAIT); + if (OP_OK == status) { + FW_ASSERT(size <= FW_FILE_CHUNK_SIZE, FwAssertArgType(size)); + this->m_hash.update(this->m_crc_buffer, size); + } + } + return status; +} + +DelegateFile::Status DelegateFile::finalizeCrc(U32& crc) { + this->m_hash.finalize(crc); + // Historically, the CRC calculation in File omitted the final 1's complement step. Utils::Hash performs that step + // and as such, we must undo it before returning the value to ensure backwards compatibility. + crc = ~crc; + this->m_hash.init(); + return DelegateFile::Status::OP_OK; +} + +} // namespace Os diff --git a/Os/DelegateFile.hpp b/Os/DelegateFile.hpp new file mode 100644 index 00000000000..ded973ba44e --- /dev/null +++ b/Os/DelegateFile.hpp @@ -0,0 +1,201 @@ +// ====================================================================== +// \title Os/DelegateFile.hpp +// \brief Define the Os::DelegateFile class +// ====================================================================== +#ifndef OS_DELEGATEFILE_HPP_ +#define OS_DELEGATEFILE_HPP_ + +#include +#include "Os/FileInterface.hpp" + +namespace Os { + +class DelegateFile final : public FileInterface { + // The file unit-test harness reaches into the CRC scratch state (`m_hash`) to check the running + // accumulator against its shadow model. That state moved here from `FileInterface`, so the tester + // must befriend this wrapper too (it is already a friend of `FileInterface`). + friend struct Os::Test::FileTest::Tester; + + public: + //! \brief constructor + //! + DelegateFile(); + + //! \brief destructor + //! + //! Destructor closes the file if it is open + ~DelegateFile() final; + + //! \brief copy constructor that copies the internal representation + DelegateFile(const DelegateFile& other); + + //! \brief assignment operator that copies the internal representation + DelegateFile& operator=(const DelegateFile& other); + + // ------------------------------------ + // Functions overrides + // ------------------------------------ + + // Bring the FileInterface convenience overloads into scope; overriding a + // single `open` overload below would otherwise hide all of them. + using FileInterface::open; + using FileInterface::read; + using FileInterface::write; + + //! \brief open file with supplied path and mode + //! + //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in + //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. + //! The status of the open request is returned from the function call. Delegates to the chosen + //! implementation's `open` function. + //! + //! It is invalid to send `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! It is invalid to supply `overwrite` as a non-enumerated value. + //! + //! \param path: c-string of path to open + //! \param mode: file operation mode + //! \param overwrite: overwrite existing file on create + //! \return: status of the open + //! + Os::FileInterface::Status open(const char* path, Mode mode, OverwriteType overwrite) override; + + //! \brief close the file, if not opened then do nothing + //! + //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's + //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`. + //! + void close() override; + + //! \brief get size of currently open file + //! + //! Get the size of the currently open file and fill the size parameter. Return status of the operation. + //! \param size: output parameter for size. + //! \return OP_OK on success otherwise error status + //! + Status size(FwSizeType& size_result) override; + + //! \brief get file pointer position of the currently open file + //! + //! Get the current position of the read/write pointer of the open file. + //! \param position: output parameter for size. + //! \return OP_OK on success otherwise error status + //! + Status position(FwSizeType& position_result) override; + + //! \brief pre-allocate file storage + //! + //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations + //! that cannot pre-allocate. + //! + //! It is invalid to pass a negative `offset`. + //! It is invalid to pass a negative `length`. + //! + //! \param offset: offset into file + //! \param length: length after offset to preallocate + //! \return OP_OK on success otherwise error status + //! + Status preallocate(FwSizeType offset, FwSizeType length) override; + + //! \brief seek the file pointer to the given offset + //! + //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated + //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position. + //! + //! \param offset: offset to seek to + //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position. + //! \return OP_OK on success otherwise error status + //! + Status seek(FwSignedSizeType offset, SeekType seekType) override; + + //! \brief flush file contents to storage + //! + //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations + //! that do not support flushing. + //! + //! \return OP_OK on success otherwise error status + //! + Status flush() override; + + //! \brief read data from this file into supplied buffer bounded by size + //! + //! Read data from this file up to the `size` and store it in `buffer`. When `wait` is set to `WAIT`, this + //! will block until the requested size has been read successfully read or the end of the file has been + //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available. + //! + //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of + //! the read operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! It is invalid to supply wait as a non-enumerated value. + //! + //! \param buffer: memory location to store data read from file + //! \param size: size of data to read + //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available + //! \return OP_OK on success otherwise error status + //! + Status read(U8* buffer, FwSizeType& size, WaitType wait) override; + + //! \brief read data from this file into supplied buffer bounded by size + //! + //! Write data to this file up to the `size` from the `buffer`. When `wait` is set to `WAIT`, this + //! will block until the requested size has been written successfully to disk. When `wait` is set to + //! `NO_WAIT` it will return once the data is sent to the OS. + //! + //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of + //! the read operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! It is invalid to supply wait as a non-enumerated value. + //! + //! \param buffer: memory location to store data read from file + //! \param size: size of data to read + //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available + //! \return OP_OK on success otherwise error status + //! + Status write(const U8* buffer, FwSizeType& size, WaitType wait) override; + + //! \brief returns the raw file handle + //! + //! Gets the raw file handle from the implementation. Note: users must include the implementation specific + //! header to make any real use of this handle. Otherwise it//!must* be passed as an opaque type. + //! + //! \return raw file handle + //! + FileHandle* getHandle() override; + + // ------------------------------------ + // CRC overrides + // + // The CRC scratch state (`m_hash` accumulator + `m_crc_buffer` read + // buffer) and the working algorithm live here on the wrapper rather than + // on `FileInterface`. See the design note in Os/FileInterface.hpp. + // ------------------------------------ + + //! \brief calculate the CRC32 of the entire file. See FileInterface::calculateCrc. + Status calculateCrc(U32& crc) override; + + //! \brief calculate the CRC32 of the next section of data. See FileInterface::incrementalCrc. + Status incrementalCrc(FwSizeType& size) override; + + //! \brief finalize and retrieve the CRC value. See FileInterface::finalizeCrc. + Status finalizeCrc(U32& crc) override; + + private: + // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is + // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in + // the byte-array here and set `handle` to that address for storage. + // + alignas(FW_HANDLE_ALIGNMENT) FileHandleStorage m_handle_storage; //!< Storage for aligned FileHandle data + FileInterface& m_delegate; //!< Delegate for the real implementation + + // CRC scratch state -- see the "CRC overrides" note above. Kept off the + // handle-size-constrained delegate and on this wrapper instead. + Utils::Hash m_hash; //!< Hash object for incremental CRC calculation + U8 m_crc_buffer[FW_FILE_CHUNK_SIZE]; //!< Read buffer for incremental CRC calculation +}; +} // namespace Os + +#endif // OS_DELEGATEFILE_HPP_ diff --git a/Os/DelegateFileSystem.cpp b/Os/DelegateFileSystem.cpp new file mode 100644 index 00000000000..68205ec762d --- /dev/null +++ b/Os/DelegateFileSystem.cpp @@ -0,0 +1,82 @@ +// ====================================================================== +// \title Os/DelegateFileSystem.cpp +// \brief common function implementation for Os::FileSystem +// ====================================================================== +#include +#include +#include + +namespace Os { + +DelegateFileSystem::DelegateFileSystem() + : m_handle_storage(), m_delegate(*FileSystemInterface::getDelegate(m_handle_storage)) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); +} + +DelegateFileSystem::~DelegateFileSystem() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + m_delegate.~FileSystemInterface(); +} + +FileSystemHandle* DelegateFileSystem::getHandle() { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + return this->m_delegate.getHandle(); +} + +DelegateFileSystem::Status DelegateFileSystem::_removeDirectory(const char* path) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + return this->m_delegate._removeDirectory(path); +} + +DelegateFileSystem::Status DelegateFileSystem::_removeFile(const char* path) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + return this->m_delegate._removeFile(path); +} + +DelegateFileSystem::Status DelegateFileSystem::_rename(const char* sourcePath, const char* destPath) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(sourcePath != nullptr); + FW_ASSERT(destPath != nullptr); + return this->m_delegate._rename(sourcePath, destPath); +} + +DelegateFileSystem::Status DelegateFileSystem::_getPathType(const char* path, PathType& pathType) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + return this->m_delegate._getPathType(path, pathType); +} + +DelegateFileSystem::Status DelegateFileSystem::_getWorkingDirectory(char* path, FwSizeType bufferSize) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + FW_ASSERT(bufferSize > 0); // because bufferSize=0 would trigger a malloc in some implementations (e.g. Posix) + return this->m_delegate._getWorkingDirectory(path, bufferSize); +} + +DelegateFileSystem::Status DelegateFileSystem::_changeWorkingDirectory(const char* path) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + return this->m_delegate._changeWorkingDirectory(path); +} + +DelegateFileSystem::Status DelegateFileSystem::_getFreeSpace(const char* path, + FwSizeType& totalBytes, + FwSizeType& freeBytes) { + FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); + FW_ASSERT(path != nullptr); + return this->m_delegate._getFreeSpace(path, totalBytes, freeBytes); +} + +void DelegateFileSystem::init() { + // Force trigger on the fly singleton setup + (void)DelegateFileSystem::getSingleton(); +} + +DelegateFileSystem& DelegateFileSystem::getSingleton() { + static DelegateFileSystem s_singleton; + return s_singleton; +} + +} // namespace Os diff --git a/Os/DelegateFileSystem.hpp b/Os/DelegateFileSystem.hpp new file mode 100644 index 00000000000..241de526af6 --- /dev/null +++ b/Os/DelegateFileSystem.hpp @@ -0,0 +1,123 @@ +// ====================================================================== +// \title Os/DelegateFileSystem.hpp +// \brief Define the Os::DelegateFileSystem class +// ====================================================================== + +#ifndef OS_DELEGATEFILESYSTEM_HPP_ +#define OS_DELEGATEFILESYSTEM_HPP_ + +#include + +namespace Os { + +//! \brief FileSystem class +//! +//! This class provides a common interface for file system operations. +//! This class uses the singleton pattern and should be accessed through +//! its static functions, for example using `Os::FileSystem::removeFile(path)`. +class DelegateFileSystem final : public FileSystemInterface { + private: + DelegateFileSystem(); //!< Constructor (private because singleton pattern) + public: + ~DelegateFileSystem() final; //!< Destructor + + //! \brief return the underlying FileSystem handle (implementation specific) + //! \return internal FileSystem handle representation + FileSystemHandle* getHandle() override; + + // ------------------------------------------------------------ + // Implementation-specific FileSystem member functions + // ------------------------------------------------------------ + + //! \brief Remove a directory at the specified path + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path of the directory to remove + //! \return Status of the operation + Status _removeDirectory(const char* path) override; + + //! \brief Remove a file at the specified path + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path of the file to remove + //! \return Status of the operation + Status _removeFile(const char* path) override; + + //! \brief Rename a file from source to destination + //! + //! If the rename fails due to a cross-device operation, this function should return EXDEV_ERROR + //! and moveFile can be used instead to force a copy-and-remove. + //! + //! It is invalid to pass `nullptr` as sourcePath or destPath. + //! + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \return Status of the operation + Status _rename(const char* sourcePath, const char* destPath) override; + + //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path on the filesystem to query + //! \param totalBytes Reference to store the total bytes on the filesystem + //! \param freeBytes Reference to store the free bytes on the filesystem + //! \return Status of the operation + Status _getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) override; + + //! \brief Get the current working directory + //! + //! Writes the current working directory path to the provided buffer of size bufferSize. + //! If the buffer is too small to hold the full path, the function will return BUFFER_TOO_SMALL. + //! + //! It is invalid to pass `nullptr` as the path. + //! It is invalid to pass a bufferSize of 0. + //! + //! \param path Buffer to store the current working directory path + //! \param bufferSize Size of the buffer + //! \return Status of the operation + Status _getWorkingDirectory(char* path, FwSizeType bufferSize) override; + + //! \brief Change the current working directory to the specified path + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path of the new working directory + //! \return Status of the operation + Status _changeWorkingDirectory(const char* path) override; + + //! \brief Get the type of the path (file, directory, etc.) + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path to check + //! \param pathType Reference to store the path type + //! \return Status of the operation + Status _getPathType(const char* path, PathType& pathType) override; + + // Convenience static wrappers (removeDirectory, removeFile, rename, getWorkingDirectory, + // changeWorkingDirectory, getFreeSpace, exists, getPathType, touch, createDirectory, appendFile, + // copyFile, moveFile, getFileSize) are inherited from Os::FileSystemInterface. + + public: + //! \brief initialize singleton + static void init(); + + //! \brief get a reference to singleton + //! \return reference to singleton + static DelegateFileSystem& getSingleton(); + + private: + // This section is used to store the implementation-defined FileSystem handle. To Os::FileSystem and fprime, this + // type is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle + // in the byte-array here and set `handle` to that address for storage. + + alignas(FW_HANDLE_ALIGNMENT) FileSystemHandleStorage m_handle_storage; //!< FileSystem handle storage + FileSystemInterface& m_delegate; +}; + +} // namespace Os + +#endif // OS_DELEGATEFILESYSTEM_HPP_ diff --git a/Os/Memory.cpp b/Os/DelegateMemory.cpp similarity index 51% rename from Os/Memory.cpp rename to Os/DelegateMemory.cpp index e5a34421108..9216106e568 100644 --- a/Os/Memory.cpp +++ b/Os/DelegateMemory.cpp @@ -1,37 +1,33 @@ // ====================================================================== -// \title Os/Memory.hpp -// \brief common function implementations for Os::Memory +// \title Os/DelegateMemory.cpp +// \brief common function implementations for Os::DelegateMemory // ====================================================================== -#include "Os/Memory.hpp" +#include "Os/DelegateMemory.hpp" #include "Fw/Types/Assert.hpp" namespace Os { -Memory::Memory() : m_delegate(*MemoryInterface::getDelegate(m_handle_storage)) {} +DelegateMemory::DelegateMemory() : m_handle_storage(), m_delegate(*MemoryInterface::getDelegate(m_handle_storage)) {} -Memory::~Memory() { +DelegateMemory::~DelegateMemory() { m_delegate.~MemoryInterface(); } -void Memory::init() { - (void)Memory::getSingleton(); +void DelegateMemory::init() { + (void)DelegateMemory::getSingleton(); } -Memory& Memory::getSingleton() { - static Memory _singleton; +DelegateMemory& DelegateMemory::getSingleton() { + static DelegateMemory _singleton; return _singleton; } -Memory::Status Memory::_getUsage(Os::Memory::Usage& memory_usage) { +DelegateMemory::Status DelegateMemory::_getUsage(Usage& memory_usage) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate._getUsage(memory_usage); } -Memory::Status Memory::getUsage(Os::Memory::Usage& memory_usage) { - return Memory::getSingleton()._getUsage(memory_usage); -} - -MemoryHandle* Memory::getHandle() { +MemoryHandle* DelegateMemory::getHandle() { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate.getHandle(); } diff --git a/Os/DelegateMemory.hpp b/Os/DelegateMemory.hpp new file mode 100644 index 00000000000..e11c8d1c516 --- /dev/null +++ b/Os/DelegateMemory.hpp @@ -0,0 +1,64 @@ +// ====================================================================== +// \title Os/DelegateMemory.hpp +// \brief defines a delegate-based implementation of MemoryInterface +// ====================================================================== +#ifndef OS_DELEGATEMEMORY_HPP_ +#define OS_DELEGATEMEMORY_HPP_ + +#include + +namespace Os { + +//! \brief memory delegate implementation +class DelegateMemory final : public MemoryInterface { + public: + //! \brief default constructor + DelegateMemory(); + + //! \brief default virtual destructor + ~DelegateMemory() final; + + //! \brief copy constructor is forbidden + DelegateMemory(const MemoryInterface& other) = delete; + + //! \brief copy constructor is forbidden + DelegateMemory(const MemoryInterface* other) = delete; + + //! \brief assignment operator is forbidden + MemoryInterface& operator=(const MemoryInterface& other) override = delete; + + //----------------------------------------------------------------------------- + // Interface methods + //----------------------------------------------------------------------------- + public: + //! \brief initialize the singleton + static void init(); + + //! \brief return singleton + static DelegateMemory& getSingleton(); + + //----------------------------------------------------------------------------- + // Delegating methods + //----------------------------------------------------------------------------- + + //! \brief get system memory usage + //! + //! This method delegates to the underlying implementation. + //! + //! \param memory_usage: (output) data structure used to store memory usage + //! \return: ERROR when error occurs, OK otherwise. + Status _getUsage(Usage& memory_usage) override; + + //! \brief return the underlying memory handle (implementation specific). + //! \return internal task handle representation + MemoryHandle* getHandle() override; + + private: + // This section is used to store the implementation-defined memory handle. To Os::Memory and fprime, this type is + // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in + // the byte-array here and set `handle` to that address for storage. + alignas(FW_HANDLE_ALIGNMENT) MemoryHandleStorage m_handle_storage; //!< Storage for aligned data + MemoryInterface& m_delegate; //!< Delegate for the real implementation +}; +} // namespace Os +#endif // OS_DELEGATEMEMORY_HPP_ diff --git a/Os/Directory.cpp b/Os/Directory.cpp deleted file mode 100644 index 910d197a8a9..00000000000 --- a/Os/Directory.cpp +++ /dev/null @@ -1,154 +0,0 @@ -// ====================================================================== -// \title Os/Directory.cpp -// \brief common function implementation for Os::Directory -// ====================================================================== -#include -#include - -namespace Os { - -Directory::Directory() - : m_is_open(false), m_handle_storage(), m_delegate(*DirectoryInterface::getDelegate(m_handle_storage)) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); -} - -Directory::~Directory() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (this->m_is_open) { - this->close(); - } - this->m_delegate.~DirectoryInterface(); -} - -// ------------------------------------------------------------ -// Directory operations delegating to implementation -// ------------------------------------------------------------ -DirectoryHandle* Directory::getHandle() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.getHandle(); -} - -Directory::Status Directory::open(const char* path, OpenMode mode) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - FW_ASSERT(mode >= 0 and mode < OpenMode::MAX_OPEN_MODE); - Status status = this->m_delegate.open(path, mode); - if (status == Status::OP_OK) { - this->m_is_open = true; - } - return status; -} - -bool Directory::isOpen() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_is_open; -} -Directory::Status Directory::rewind() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (not this->m_is_open) { - return Status::NOT_OPENED; - } - return this->m_delegate.rewind(); -} - -Directory::Status Directory::read(char* fileNameBuffer, FwSizeType bufSize) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (not this->m_is_open) { - return Status::NOT_OPENED; - } - FW_ASSERT(fileNameBuffer != nullptr); - FW_ASSERT(bufSize > 0); - Status status = this->m_delegate.read(fileNameBuffer, bufSize); - fileNameBuffer[bufSize - 1] = '\0'; // Guarantee null-termination - return status; -} - -Directory::Status Directory::read(Fw::StringBase& filename) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (not this->m_is_open) { - return Status::NOT_OPENED; - } - return this->m_delegate.read(const_cast(filename.toChar()), filename.getCapacity()); -} - -void Directory::close() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - this->m_is_open = false; - return this->m_delegate.close(); -} - -// ------------------------------------------------------------ -// Common functions built on top of OS-specific functions -// ------------------------------------------------------------ - -Directory::Status Directory::getFileCount(FwSizeType& fileCount) { - if (not this->m_is_open) { - return Status::NOT_OPENED; - } - // Rewind to ensure we start from the beginning of the stream - if (this->rewind() != Status::OP_OK) { - return Status::OTHER_ERROR; - } - const FwSizeType loopLimit = std::numeric_limits::max(); - FwSizeType count = 0; - char unusedBuffer[1]; // buffer must have size but is unused - Status readStatus = Status::OP_OK; - fileCount = 0; - // Count files by reading each file entry until there is NO_MORE_FILES - for (FwSizeType iter = 0; iter < loopLimit; ++iter) { - readStatus = this->read(unusedBuffer, sizeof(unusedBuffer)); - if (readStatus == Status::NO_MORE_FILES) { - break; - } else if (readStatus != Status::OP_OK) { - return Status::OTHER_ERROR; - } - count++; - } - fileCount = count; - if (this->rewind() != Status::OP_OK) { - return Status::OTHER_ERROR; - } - return Status::OP_OK; -} - -Directory::Status Directory::readDirectory(Fw::ExternalArray& filenameArray, FwSizeType& filenameCount) { - FW_ASSERT(filenameArray.getElements() != nullptr); - FW_ASSERT(filenameArray.getSize() > 0); - if (not this->m_is_open) { - return Status::NOT_OPENED; - } - // Rewind to ensure we start reading from the beginning of the stream - if (this->rewind() != Status::OP_OK) { - return Status::OTHER_ERROR; - } - - Status readStatus = Status::OP_OK; - Status returnStatus = Status::OP_OK; - FwSizeType index; - filenameCount = 0; - // Iterate through the directory and read the filenames into the array - for (index = 0; index < filenameArray.getSize(); index++) { - readStatus = this->read(filenameArray[index]); - if (readStatus == Status::NO_MORE_FILES) { - break; - } else if (readStatus != Status::OP_OK) { - return Status::OTHER_ERROR; - } - } - filenameCount = index; - - if (this->rewind() != Status::OP_OK) { - return Status::OTHER_ERROR; - } - - return returnStatus; -} - -Directory::Status Directory::readDirectory(Fw::String filenameArray[], - const FwSizeType filenameArraySize, - FwSizeType& filenameCount) { - Fw::ExternalArray array(filenameArray, filenameArraySize); - return this->readDirectory(array, filenameCount); -} - -} // namespace Os diff --git a/Os/Directory.hpp b/Os/Directory.hpp index a7fe12e3b25..977a09f65d2 100644 --- a/Os/Directory.hpp +++ b/Os/Directory.hpp @@ -1,234 +1,45 @@ // ====================================================================== // \title Os/Directory.hpp -// \brief Os::Directory interface definition +// \brief public Os::Directory interface and alias +// +// This header aggregates all definitions needed to use Os::Directory: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::Directory type alias by +// forward-declaring a link-time delegate (e.g. DelegateDirectory) or directly +// aliasing a concrete implementation. +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/DirectoryInterface.hpp, which this header includes first. +// +// 2. Os/DirectoryInterface.hpp (IF) includes CFG first (so the Os::Directory alias is +// available), then defines DirectoryHandle and DirectoryInterface. +// +// 3. OS_DIRECTORY_HEADER (IMPL) is defined by CFG and points to the concrete +// implementation header. If using delegation, this points to +// Os/DelegateDirectory.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation. +// +// DirectoryInterface.hpp must precede OS_DIRECTORY_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== +#ifndef Os_Directory_hpp +#define Os_Directory_hpp -#ifndef _OS_DIRECTORY_HPP_ -#define _OS_DIRECTORY_HPP_ - -#include -#include -#include -#include -#include +#include "Os/DirectoryInterface.hpp" +// A project config/OsSelection.hpp that predates the Directory selection defines neither the Os::Directory +// alias nor OS_DIRECTORY_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_DIRECTORY_HEADER +#include "Os/DelegateDirectory.hpp" namespace Os { - -struct DirectoryHandle {}; - -class DirectoryInterface { - public: - enum Status { - OP_OK, //!< Operation was successful - DOESNT_EXIST, //!< Directory doesn't exist - NO_PERMISSION, //!< No permission to read directory - NOT_OPENED, //!< Directory hasn't been opened yet - NOT_DIR, //!< Path is not a directory - NO_MORE_FILES, //!< Directory stream has no more files - FILE_LIMIT, //!< Directory has more files than can be read - BAD_DESCRIPTOR, //!< Directory stream descriptor is invalid - ALREADY_EXISTS, //!< Directory already exists - NOT_SUPPORTED, //!< Operation is not supported by the current implementation - OTHER_ERROR, //!< A catch-all for other errors. Have to look in implementation-specific code - }; - - enum OpenMode { - READ, //!< Error if directory doesn't exist - CREATE_IF_MISSING, //!< Create directory if it doesn't exist - CREATE_EXCLUSIVE, //!< Create directory and error if it already exists - MAX_OPEN_MODE //!< Maximum value of OpenMode - }; - - //! \brief default constructor - DirectoryInterface() = default; - - //! \brief default virtual destructor - virtual ~DirectoryInterface() = default; - - //! \brief copy constructor is forbidden - DirectoryInterface(const DirectoryInterface& other) = delete; - - //! \brief assignment operator is forbidden - DirectoryInterface& operator=(const DirectoryInterface& other) = delete; - - //! \brief return the underlying Directory handle (implementation specific) - //! \return internal Directory handle representation - virtual DirectoryHandle* getHandle() = 0; - - //! \brief provide a pointer to a Directory delegate object - static DirectoryInterface* getDelegate(DirectoryHandleStorage& aligned_new_memory); - - // ----------------------------------------------------------------- - // Directory operations to be implemented by an OSAL implementation - // ----------------------------------------------------------------- - // These functions are to be overridden in each OS implementation - // See an example in in Os/Posix/Directory.hpp - - //! \brief Open or create a directory - //! - //! Using the path provided, this function will open or create a directory. - //! Use OpenMode::READ to open an existing directory and error if the directory is not found - //! Use OpenMode::CREATE_IF_MISSING to open a directory, creating the directory if it doesn't exist - //! Use OpenMode::CREATE_EXCLUSIVE to open a directory, creating the directory and erroring if it already exists - //! - //! It is invalid to pass `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! - //! \param path: path of directory to open - //! \param mode: enum (READ, CREATE_IF_MISSING, CREATE_EXCLUSIVE). See notes above for more information - //! \return status of the operation - virtual Status open(const char* path, OpenMode mode) = 0; - - //! \brief Rewind directory stream - //! - //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. - //! - //! \return status of the operation - virtual Status rewind() = 0; - - //! \brief Get next filename from directory stream - //! - //! Write at most buffSize characters of the file name to fileNameBuffer and guarantee null-termination. - //! This function skips the current directory (.) and parent directory (..) entries. - //! Returns NO_MORE_FILES if there are no more files to read from the buffer. - //! - //! It is invalid to pass `nullptr` as fileNameBuffer. - //! - //! \param fileNameBuffer: buffer to store filename - //! \param buffSize: size of fileNameBuffer - //! \return status of the operation - virtual Status read(char* fileNameBuffer, FwSizeType buffSize) = 0; - - //! \brief Get next filename from directory stream and write it to a Fw::StringBase object - //! - //! \param filename: Fw::StringBase (or derived) object to store filename in - //! \return status of the operation - // virtual Status read(Fw::StringBase& filename) = 0; - - //! \brief Close directory - virtual void close() = 0; -}; - -//! \brief Directory class -//! -//! This class provides a common interface for directory operations, such as reading files in a directory -//! and getting the number of files in a directory. -class Directory final : public DirectoryInterface { - public: - //! \brief Constructor - Directory(); - - //! \brief Destructor - //! - //! Destructor will close the Directory if it is open - ~Directory() final; - - //! \brief return the underlying Directory handle (implementation specific) - //! \return internal Directory handle representation - DirectoryHandle* getHandle() override; - - // ------------------------------------------------------------ - // Implementation-specific Directory member functions - // ------------------------------------------------------------ - // These functions are overridden in each OS implementation (e.g. in Os/Posix/Directory.hpp) - - //! \brief Open or create a directory - //! - //! Using the path provided, this function will open or create a directory. - //! Use OpenMode::READ to open an existing directory and error if the directory is not found - //! Use OpenMode::CREATE_IF_MISSING to open a directory, creating the directory if it doesn't exist - //! Use OpenMode::CREATE_EXCLUSIVE to open a directory, creating the directory and erroring if it already exists - //! - //! It is invalid to pass `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! - //! \param path: path of directory to open - //! \param mode: enum (READ, CREATE_IF_MISSING, CREATE_EXCLUSIVE). See notes above for more information - //! \return status of the operation - Status open(const char* path, OpenMode mode) override; - - //! \brief Check if Directory is open or not - //! \return true if Directory is open, false otherwise - bool isOpen(); - - //! \brief Rewind directory stream - //! - //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. - //! - //! \return status of the operation - Status rewind() override; - - //! \brief Get next filename from directory stream - //! - //! Write at most buffSize characters of the file name to fileNameBuffer and guarantee null-termination. - //! This function skips the current directory (.) and parent directory (..) entries. - //! Returns NO_MORE_FILES if there are no more files to read from the buffer. - //! - //! It is invalid to pass `nullptr` as fileNameBuffer. - //! - //! \param fileNameBuffer: buffer to store filename - //! \param buffSize: size of fileNameBuffer - //! \return status of the operation - Status read(char* fileNameBuffer, FwSizeType buffSize) override; - - //! \brief Close directory - void close() override; - - // ------------------------------------------------------------ - // Common functions built on top of OS-specific functions - // ------------------------------------------------------------ - - //! \brief Get next filename from directory stream and write it to a Fw::StringBase object - //! - //! \param filename: Fw::StringBase (or derived) object to store filename in - //! \return status of the operation - Status read(Fw::StringBase& filename); - - //! \brief Read the contents of the directory and store filenames in the supplied array. - //! - //! Reads at most filenameArray.getSize() filenames. - //! The function first rewinds the directory stream to ensure reading starts from the beginning. - //! After reading, it rewinds the directory stream again, resetting seek position to beginning. - //! - //! \param filenameArray: array to store filenames - //! \param filenameCount: number of filenames written to filenameArray (output) - //! \return status of the operation - Status readDirectory(Fw::ExternalArray& filenameArray, FwSizeType& filenameCount); - - //! \brief Read the contents of the directory and store filenames in filenameArray of size arraySize. - //! - //! The function first rewinds the directory stream to ensure reading starts from the beginning. - //! After reading, it rewinds the directory stream again, resetting seek position to beginning. - //! - //! \param filenameArray: array to store filenames - //! \param arraySize: size of filenameArray - //! \param filenameCount: number of filenames written to filenameArray (output) - //! \return status of the operation - DEPRECATED(Status readDirectory(Fw::String filenameArray[], const FwSizeType arraySize, FwSizeType& filenameCount), - "Use readDirectory(Fw::ExternalArray& filenameArray, FwSizeType& filenameCount) instead"); - - //! \brief Get the number of files in the directory. - //! - //! Counts the number of files in the directory by reading each file entry and writing the count to fileCount. - //! - //! The function first rewinds the directory stream to ensure counting starts from the beginning. - //! After counting, it rewinds the directory stream again, resetting seek position to beginning. - //! - //! \param fileCount Reference to a variable where the file count will be stored. - //! \return Status indicating the result of the operation. - Status getFileCount(FwSizeType& fileCount); - - private: - bool m_is_open; //!< Flag indicating if the directory has been open - - private: - // This section is used to store the implementation-defined Directory handle. To Os::Directory and fprime, this type - // is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in - // the byte-array here and set `handle` to that address for storage. - alignas(FW_HANDLE_ALIGNMENT) DirectoryHandleStorage m_handle_storage; //!< Directory handle storage - DirectoryInterface& m_delegate; -}; - -} // namespace Os - +using Directory = DelegateDirectory; +} +#else +#include OS_DIRECTORY_HEADER #endif + +#endif // Os_Directory_hpp diff --git a/Os/DirectoryInterface.cpp b/Os/DirectoryInterface.cpp new file mode 100644 index 00000000000..8109671947c --- /dev/null +++ b/Os/DirectoryInterface.cpp @@ -0,0 +1,97 @@ +// ====================================================================== +// \title Os/DirectoryInterface.cpp +// \brief common function implementation for Os::DirectoryInterface +// ====================================================================== +#include +#include +#include + +namespace Os { + +bool DirectoryInterface::isOpen() const { + return this->m_is_open; +} + +void DirectoryInterface::setOpen(bool is_open) { + this->m_is_open = is_open; +} + +DirectoryInterface::Status DirectoryInterface::read(Fw::StringBase& filename) { + if (not this->isOpen()) { + return Status::NOT_OPENED; + } + return this->read(const_cast(filename.toChar()), filename.getCapacity()); +} + +DirectoryInterface::Status DirectoryInterface::readDirectory(Fw::ExternalArray& filenameArray, + FwSizeType& filenameCount) { + FW_ASSERT(filenameArray.getElements() != nullptr); + FW_ASSERT(filenameArray.getSize() > 0); + if (not this->isOpen()) { + return Status::NOT_OPENED; + } + // Rewind to ensure we start reading from the beginning of the stream + if (this->rewind() != Status::OP_OK) { + return Status::OTHER_ERROR; + } + + Status readStatus = Status::OP_OK; + Status returnStatus = Status::OP_OK; + FwSizeType index; + filenameCount = 0; + // Iterate through the directory and read the filenames into the array + for (index = 0; index < filenameArray.getSize(); index++) { + readStatus = this->read(filenameArray[index]); + if (readStatus == Status::NO_MORE_FILES) { + break; + } else if (readStatus != Status::OP_OK) { + return Status::OTHER_ERROR; + } + } + filenameCount = index; + + if (this->rewind() != Status::OP_OK) { + return Status::OTHER_ERROR; + } + + return returnStatus; +} + +DirectoryInterface::Status DirectoryInterface::readDirectory(Fw::String filenameArray[], + const FwSizeType filenameArraySize, + FwSizeType& filenameCount) { + Fw::ExternalArray array(filenameArray, filenameArraySize); + return this->readDirectory(array, filenameCount); +} + +DirectoryInterface::Status DirectoryInterface::getFileCount(FwSizeType& fileCount) { + if (not this->isOpen()) { + return Status::NOT_OPENED; + } + // Rewind to ensure we start from the beginning of the stream + if (this->rewind() != Status::OP_OK) { + return Status::OTHER_ERROR; + } + const FwSizeType loopLimit = std::numeric_limits::max(); + FwSizeType count = 0; + char unusedBuffer[1]; // buffer must have size but is unused + Status readStatus = Status::OP_OK; + fileCount = 0; + // Count files by reading each file entry until there is NO_MORE_FILES + for (FwSizeType iter = 0; iter < loopLimit; ++iter) { + readStatus = this->read(unusedBuffer, sizeof(unusedBuffer)); + if (readStatus == Status::NO_MORE_FILES) { + break; + } else if (readStatus != Status::OP_OK) { + return Status::OTHER_ERROR; + } + count++; + } + fileCount = count; + if (this->rewind() != Status::OP_OK) { + return Status::OTHER_ERROR; + } + return Status::OP_OK; +} + +} // namespace Os diff --git a/Os/DirectoryInterface.hpp b/Os/DirectoryInterface.hpp new file mode 100644 index 00000000000..08ff26ea72e --- /dev/null +++ b/Os/DirectoryInterface.hpp @@ -0,0 +1,169 @@ +// ====================================================================== +// \title Os/DirectoryInterface.hpp +// \brief Os::DirectoryHandle and Os::DirectoryInterface definitions +// ====================================================================== + +#ifndef OS_DIRECTORYINTERFACE_HPP_ +#define OS_DIRECTORYINTERFACE_HPP_ + +#include +#include +#include +#include +#include +#include "config/OsSelection.hpp" + +namespace Os { + +struct DirectoryHandle {}; + +class DirectoryInterface { + public: + enum Status { + OP_OK, //!< Operation was successful + DOESNT_EXIST, //!< Directory doesn't exist + NO_PERMISSION, //!< No permission to read directory + NOT_OPENED, //!< Directory hasn't been opened yet + NOT_DIR, //!< Path is not a directory + NO_MORE_FILES, //!< Directory stream has no more files + FILE_LIMIT, //!< Directory has more files than can be read + BAD_DESCRIPTOR, //!< Directory stream descriptor is invalid + ALREADY_EXISTS, //!< Directory already exists + NOT_SUPPORTED, //!< Operation is not supported by the current implementation + OTHER_ERROR, //!< A catch-all for other errors. Have to look in implementation-specific code + }; + + enum OpenMode { + READ, //!< Error if directory doesn't exist + CREATE_IF_MISSING, //!< Create directory if it doesn't exist + CREATE_EXCLUSIVE, //!< Create directory and error if it already exists + MAX_OPEN_MODE //!< Maximum value of OpenMode + }; + + //! \brief default constructor + DirectoryInterface() = default; + + //! \brief default virtual destructor + virtual ~DirectoryInterface() = default; + + //! \brief copy constructor is forbidden + DirectoryInterface(const DirectoryInterface& other) = delete; + + //! \brief assignment operator is forbidden + DirectoryInterface& operator=(const DirectoryInterface& other) = delete; + + //! \brief Check if Directory is open or not + //! \return true if Directory is open, false otherwise + bool isOpen() const; + + //! \brief return the underlying Directory handle (implementation specific) + //! \return internal Directory handle representation + virtual DirectoryHandle* getHandle() = 0; + + //! \brief provide a pointer to a Directory delegate object + static DirectoryInterface* getDelegate(DirectoryHandleStorage& aligned_new_memory); + + // ----------------------------------------------------------------- + // Directory operations to be implemented by an OSAL implementation + // ----------------------------------------------------------------- + // These functions are to be overridden in each OS implementation + // See an example in in Os/Posix/Directory.hpp + + //! \brief Open or create a directory + //! + //! Using the path provided, this function will open or create a directory. + //! Use OpenMode::READ to open an existing directory and error if the directory is not found + //! Use OpenMode::CREATE_IF_MISSING to open a directory, creating the directory if it doesn't exist + //! Use OpenMode::CREATE_EXCLUSIVE to open a directory, creating the directory and erroring if it already exists + //! + //! It is invalid to pass `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! + //! \param path: path of directory to open + //! \param mode: enum (READ, CREATE_IF_MISSING, CREATE_EXCLUSIVE). See notes above for more information + //! \return status of the operation + virtual Status open(const char* path, OpenMode mode) = 0; + + //! \brief Rewind directory stream + //! + //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. + //! + //! \return status of the operation + virtual Status rewind() = 0; + + //! \brief Get next filename from directory stream + //! + //! Write at most buffSize characters of the file name to fileNameBuffer and guarantee null-termination. + //! This function skips the current directory (.) and parent directory (..) entries. + //! Returns NO_MORE_FILES if there are no more files to read from the buffer. + //! + //! It is invalid to pass `nullptr` as fileNameBuffer. + //! + //! \param fileNameBuffer: buffer to store filename + //! \param buffSize: size of fileNameBuffer + //! \return status of the operation + virtual Status read(char* fileNameBuffer, FwSizeType buffSize) = 0; + + //! \brief Close directory + virtual void close() = 0; + + // ------------------------------------------------------------------ + // Common functions built on top of OS-specific functions + // ------------------------------------------------------------------ + + //! \brief Get next filename from directory stream and write it to a Fw::StringBase object + //! + //! \param filename: Fw::StringBase (or derived) object to store filename in + //! \return status of the operation + Status read(Fw::StringBase& filename); + + //! \brief Read the contents of the directory and store filenames in the supplied array. + //! + //! Reads at most filenameArray.getSize() filenames. + //! The function first rewinds the directory stream to ensure reading starts from the beginning. + //! After reading, it rewinds the directory stream again, resetting seek position to beginning. + //! + //! \param filenameArray: array to store filenames + //! \param filenameCount: number of filenames written to filenameArray (output) + //! \return status of the operation + Status readDirectory(Fw::ExternalArray& filenameArray, FwSizeType& filenameCount); + + //! \brief Read the contents of the directory and store filenames in filenameArray of size arraySize. + //! + //! The function first rewinds the directory stream to ensure reading starts from the beginning. + //! After reading, it rewinds the directory stream again, resetting seek position to beginning. + //! + //! \param filenameArray: array to store filenames + //! \param arraySize: size of filenameArray + //! \param filenameCount: number of filenames written to filenameArray (output) + //! \return status of the operation + DEPRECATED(Status readDirectory(Fw::String filenameArray[], const FwSizeType arraySize, FwSizeType& filenameCount), + "Use readDirectory(Fw::ExternalArray& filenameArray, FwSizeType& filenameCount) instead"); + + //! \brief Get the number of files in the directory. + //! + //! Counts the number of files in the directory by reading each file entry and writing the count to fileCount. + //! + //! The function first rewinds the directory stream to ensure counting starts from the beginning. + //! After counting, it rewinds the directory stream again, resetting seek position to beginning. + //! + //! \param fileCount Reference to a variable where the file count will be stored. + //! \return Status indicating the result of the operation. + Status getFileCount(FwSizeType& fileCount); + + protected: + //! \brief set whether the directory is currently tracked as open + //! + //! Provided for subclasses (e.g. link-time delegates) whose `open`/`close` overrides must update the open + //! state tracked by this base class. + //! + //! \param is_open: new open state to track + //! + void setOpen(bool is_open); + + private: + bool m_is_open = false; //!< Flag indicating if the directory has been open +}; +} // namespace Os + +#endif // OS_DIRECTORYINTERFACE_HPP_ diff --git a/Os/File.cpp b/Os/File.cpp deleted file mode 100644 index e3edb5de0da..00000000000 --- a/Os/File.cpp +++ /dev/null @@ -1,341 +0,0 @@ -// ====================================================================== -// \title Os/File.cpp -// \brief common function implementation for Os::File -// ====================================================================== -#include -#include -#include -#include -#include - -namespace Os { - -File::File() : m_crc_buffer(), m_handle_storage(), m_delegate(*FileInterface::getDelegate(m_handle_storage)) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); -} - -File::~File() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - if (this->m_mode != OPEN_NO_MODE) { - this->close(); - } - m_delegate.~FileInterface(); -} - -File::File(const File& other) - : m_mode(other.m_mode), - m_hash(other.m_hash), - m_crc_buffer(), - m_handle_storage(), - m_delegate(*FileInterface::getDelegate(m_handle_storage, &other.m_delegate)) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); -} - -File& File::operator=(const File& other) { - if (this != &other) { - // The delegate below is constructed over the storage of the existing one. Any file this - // object currently holds must be closed first or its handle is orphaned permanently. - if (this->m_mode != OPEN_NO_MODE) { - this->close(); - } - this->m_delegate.~FileInterface(); - this->m_mode = other.m_mode; - this->m_hash = other.m_hash; - (void)FileInterface::getDelegate(m_handle_storage, &other.m_delegate); - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - } - return *this; -} - -File::Status File::open(const CHAR* filepath, File::Mode requested_mode) { - return this->open(filepath, requested_mode, OverwriteType::NO_OVERWRITE); -} - -File::Status File::open(const CHAR* filepath, File::Mode requested_mode, File::OverwriteType overwrite) { - FW_ASSERT(nullptr != filepath); - return this->open(filepath, static_cast(FileNameStringSize + 1), requested_mode, overwrite); -} - -File::Status File::open(const CHAR* filepath, FwSizeType length, File::Mode requested_mode) { - return this->open(filepath, length, requested_mode, OverwriteType::NO_OVERWRITE); -} - -File::Status File::open(const CHAR* filepath, - FwSizeType length, - File::Mode requested_mode, - File::OverwriteType overwrite) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(nullptr != filepath); - const FwSizeType string_len = static_cast(Fw::StringUtils::string_length(filepath, length)); - FW_ASSERT(string_len < length, static_cast(string_len), static_cast(length)); - FW_ASSERT(File::Mode::OPEN_NO_MODE < requested_mode && File::Mode::MAX_OPEN_MODE > requested_mode); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - FW_ASSERT((0 <= overwrite) && (overwrite < OverwriteType::MAX_OVERWRITE_TYPE)); - // Check for already opened file - if (this->isOpen()) { - return File::Status::INVALID_MODE; - } - File::Status status = this->m_delegate.open(filepath, requested_mode, overwrite); - if (status == File::Status::OP_OK) { - this->m_mode = requested_mode; - // Reset any open CRC calculations - this->m_hash.init(); - } - - return status; -} - -File::Status File::open(const Fw::ConstStringBase& path, File::Mode requested_mode) { - return this->open(path.toChar(), static_cast(path.getCapacity()), requested_mode, - OverwriteType::NO_OVERWRITE); -} - -File::Status File::open(const Fw::ConstStringBase& path, File::Mode requested_mode, File::OverwriteType overwrite) { - return this->open(path.toChar(), static_cast(path.getCapacity()), requested_mode, overwrite); -} - -void File::close() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - this->m_delegate.close(); - this->m_mode = Mode::OPEN_NO_MODE; -} - -bool File::isOpen() const { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - return this->m_mode != Mode::OPEN_NO_MODE; -} - -File::Status File::size(FwSizeType& size_result) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - if (OPEN_NO_MODE == this->m_mode) { - return File::Status::NOT_OPENED; - } - return this->m_delegate.size(size_result); -} - -File::Status File::position(FwSizeType& position_result) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - return File::Status::NOT_OPENED; - } - return this->m_delegate.position(position_result); -} - -File::Status File::preallocate(FwSizeType offset, FwSizeType length) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - return File::Status::NOT_OPENED; - } else if (OPEN_READ == this->m_mode) { - return File::Status::INVALID_MODE; - } - return this->m_delegate.preallocate(offset, length); -} - -File::Status File::seek(FwSignedSizeType offset, File::SeekType seekType) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT((0 <= seekType) && (seekType < SeekType::MAX_SEEK_TYPE)); - // Cannot do a seek with a negative offset in absolute mode - FW_ASSERT((seekType == File::SeekType::RELATIVE) || (offset >= 0)); - FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - return File::Status::NOT_OPENED; - } - return this->m_delegate.seek(offset, seekType); -} - -File::Status File::seek_absolute(FwSizeType offset) { - Os::File::Status status = File::Status::OTHER_ERROR; - // If the offset can be represented by a signed value, then we can perform a single seek - if (static_cast(std::numeric_limits::max()) >= offset) { - // Check that the bounding above is correct - FW_ASSERT(static_cast(offset) >= 0); - status = this->seek(static_cast(offset), File::SeekType::ABSOLUTE); - } - // Otherwise, a full seek to any value represented by FwSizeType can be performed - // by at most 3 seeks of a FwSignedSizeType. Two half seeks (rounded down) that are - // strictly bounded by std::numeric_limits::max() and one seek of - // a possibile "odd" byte to ensure odds offsets do not introduce an off-by-one-error. - // Thus we perform 3 seeks to guarantee that we can reach any position. - else { - FwSignedSizeType half_offset = static_cast(offset >> 1); - bool is_odd = (offset % 2) == 1; - status = this->seek(half_offset, File::SeekType::ABSOLUTE); - if (status == File::Status::OP_OK) { - status = this->seek(half_offset, File::SeekType::RELATIVE); - } - if (status == File::Status::OP_OK) { - status = this->seek((is_odd) ? 1 : 0, File::SeekType::RELATIVE); - } - } - return status; -} - -File::Status File::flush() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - return File::Status::NOT_OPENED; - } else if (OPEN_READ == this->m_mode) { - return File::Status::INVALID_MODE; - } - return this->m_delegate.flush(); -} - -File::Status File::read(U8* buffer, FwSizeType& size) { - return this->read(buffer, size, WaitType::WAIT); -} - -File::Status File::read(U8* buffer, FwSizeType& size, File::WaitType wait) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(buffer != nullptr); - FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - size = 0; - return File::Status::NOT_OPENED; - } else if (OPEN_READ != this->m_mode) { - size = 0; - return File::Status::INVALID_MODE; - } - return this->m_delegate.read(buffer, size, wait); -} - -File::Status File::write(const U8* buffer, FwSizeType& size) { - return this->write(buffer, size, WaitType::WAIT); -} - -File::Status File::write(const U8* buffer, FwSizeType& size, File::WaitType wait) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(buffer != nullptr); - FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - size = 0; - return File::Status::NOT_OPENED; - } else if (OPEN_READ == this->m_mode) { - size = 0; - return File::Status::INVALID_MODE; - } - return this->m_delegate.write(buffer, size, wait); -} - -FileHandle* File::getHandle() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.getHandle(); -} - -File::Status File::calculateCrc(U32& crc) { - File::Status status = File::Status::OP_OK; - FwSizeType size = FW_FILE_CHUNK_SIZE; - crc = 0; - for (FwSizeType i = 0; i < std::numeric_limits::max(); i++) { - status = this->incrementalCrc(size); - // Break on eof or error - if ((size != FW_FILE_CHUNK_SIZE) || (status != File::OP_OK)) { - break; - } - } - // When successful, finalize the CRC - if (status == File::OP_OK) { - status = this->finalizeCrc(crc); - } - return status; -} - -File::Status File::incrementalCrc(FwSizeType& size) { - File::Status status = File::Status::OP_OK; - FW_ASSERT(size <= FW_FILE_CHUNK_SIZE, FwAssertArgType(size)); - if (OPEN_NO_MODE == this->m_mode) { - status = File::Status::NOT_OPENED; - } else if (OPEN_READ != this->m_mode) { - status = File::Status::INVALID_MODE; - } else { - // Read data without waiting for additional data to be available - status = this->read(this->m_crc_buffer, size, File::WaitType::NO_WAIT); - if (OP_OK == status) { - FW_ASSERT(size <= FW_FILE_CHUNK_SIZE, FwAssertArgType(size)); - this->m_hash.update(this->m_crc_buffer, size); - } - } - return status; -} - -File::Status File::finalizeCrc(U32& crc) { - File::Status status = File::Status::OP_OK; - this->m_hash.finalize(crc); - // Historically, the CRC calculation in File omitted the final 1's complement step. Utils::Hash performs that step - // and as such, we must undo it before returning the value to ensure backwards compatibility. - crc = ~crc; - this->m_hash.init(); - return status; -} - -File::Status File::readline(U8* buffer, FwSizeType& size, File::WaitType wait) { - const FwSizeType requested_size = size; - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(buffer != nullptr); - FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); - // Check that the file is open before attempting operation - if (OPEN_NO_MODE == this->m_mode) { - size = 0; - return File::Status::NOT_OPENED; - } else if (OPEN_READ != this->m_mode) { - size = 0; - return File::Status::INVALID_MODE; - } - FwSizeType original_location = 0; - File::Status status = this->position(original_location); - if (status != Os::File::Status::OP_OK) { - size = 0; - (void)this->seek_absolute(original_location); - return status; - } - FwSizeType read = 0; - // Loop reading chunk by chunk - for (FwSizeType i = 0; i < size; i += read) { - // read in chunks to avoid large buffer allocations - FwSizeType current_chunk_size = std::min(size - i, static_cast(FW_FILE_CHUNK_SIZE)); - read = current_chunk_size; - status = this->read(buffer + i, read, wait); - if (status != File::Status::OP_OK) { - // Contract: on error, seek back to the original location - size = 0; - (void)this->seek_absolute(original_location); - return status; - } - // EOF break out now - if (read == 0) { - size = i; - return Os::File::Status::OP_OK; - } - // Loop from i to i + current_chunk_size looking for `\n` - const FwSizeType chunk_end = i + read; - for (FwSizeType j = i; j < chunk_end; j++) { - // Newline seek back to after it, return the size read - if (buffer[j] == '\n') { - size = j + 1; - // Ensure that the computation worked and there is not overflow - FW_ASSERT(size <= requested_size); - FW_ASSERT(std::numeric_limits::max() - size >= original_location); - (void)this->seek_absolute(original_location + j + 1); - return Os::File::Status::OP_OK; - } - } - } - // Failed to find newline within data available - // Contract: on error, seek back to the original location - size = 0; - (void)this->seek_absolute(original_location); - return Os::File::Status::OTHER_ERROR; -} -} // namespace Os diff --git a/Os/File.hpp b/Os/File.hpp index 2a70188666d..cf844ba975e 100644 --- a/Os/File.hpp +++ b/Os/File.hpp @@ -1,602 +1,45 @@ // ====================================================================== // \title Os/File.hpp -// \brief common function definitions for Os::File +// \brief public Os::File interface and alias +// +// This header aggregates all definitions needed to use Os::File: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::File type alias by +// forward-declaring a link-time delegate (e.g. DelegateFile) or directly +// aliasing a concrete implementation (e.g. Va416x0Os::SomeFile). +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/FileInterface.hpp, which this header includes first. +// +// 2. Os/FileInterface.hpp (IF) includes CFG first (so the Os::File alias is +// available), then defines FileHandle and FileInterface. +// +// 3. OS_FILE_HEADER (IMPL) is defined by CFG and points to the concrete +// implementation header. If using delegation, this points to +// Os/DelegateFile.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation. +// +// FileInterface.hpp must precede OS_FILE_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== #ifndef Os_File_hpp_ #define Os_File_hpp_ -#include -#include -#include -#include +#include "Os/FileInterface.hpp" -// Forward declaration for UTs +// A project config/OsSelection.hpp that predates the File selection defines neither the Os::File +// alias nor OS_FILE_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_FILE_HEADER +#include "Os/DelegateFile.hpp" namespace Os { -namespace Test { -namespace FileTest { -struct Tester; +using File = DelegateFile; } -} // namespace Test -} // namespace Os - -namespace Os { - -//! \brief base implementation of FileHandle -//! -struct FileHandle {}; - -// This class encapsulates a very simple file interface that has the most often-used features -class FileInterface { - public: - enum Mode { - OPEN_NO_MODE, //!< File mode not yet selected - OPEN_READ, //!< Open file for reading - OPEN_CREATE, //!< Open file for writing and truncates file if it exists, ie same flags as creat() - OPEN_WRITE, //!< Open file for writing - OPEN_SYNC_WRITE, //!< Open file for writing; writes don't return until data is on disk - OPEN_APPEND, //!< Open file for appending - MAX_OPEN_MODE //!< Maximum value of mode - }; - - enum Status { - OP_OK, //!< Operation was successful - DOESNT_EXIST, //!< File doesn't exist (for read) - NO_SPACE, //!< No space left - NO_PERMISSION, //!< No permission to read/write file - BAD_SIZE, //!< Invalid size parameter - NOT_OPENED, //!< file hasn't been opened yet - FILE_EXISTS, //!< file already exist (for CREATE with O_EXCL enabled) - NOT_SUPPORTED, //!< Kernel or file system does not support operation - INVALID_MODE, //!< Mode for file access is invalid for current operation - INVALID_ARGUMENT, //!< Invalid argument passed in - NO_MORE_RESOURCES, //!< No more available resources - OTHER_ERROR, //!< A catch-all for other errors. Have to look in implementation-specific code - OUTSIDE_SANDBOX, //!< Path falls outside the configured sandbox directory - MAX_STATUS //!< Maximum value of status - }; - - enum OverwriteType { - NO_OVERWRITE, //!< Do NOT overwrite existing files - OVERWRITE, //!< Overwrite file when it exists and creation was requested - MAX_OVERWRITE_TYPE - }; - - enum SeekType { - RELATIVE, //!< Relative seek from current file offset - ABSOLUTE, //!< Absolute seek from beginning of file - MAX_SEEK_TYPE - }; - - enum WaitType { - NO_WAIT, //!< Do not wait for read/write operation to finish - WAIT, //!< Do wait for read/write operation to finish - MAX_WAIT_TYPE - }; - - virtual ~FileInterface() = default; - - //! \brief open file with supplied path and mode - //! - //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in - //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. - //! The status of the open request is returned from the function call. Delegates to the chosen - //! implementation's `open` function. - //! - //! It is invalid to send `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! It is invalid to supply `overwrite` as a non-enumerated value. - //! - //! \param path: c-string of path to open - //! \param mode: file operation mode - //! \param overwrite: overwrite existing file on create - //! \return: status of the open - //! - virtual Status open(const char* path, Mode mode, OverwriteType overwrite) = 0; - - //! \brief close the file, if not opened then do nothing - //! - //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's - //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`. - //! - virtual void close() = 0; - - //! \brief get size of currently open file - //! - //! Get the size of the currently open file and fill the size parameter. Return status of the operation. - //! \param size: output parameter for size. - //! \return OP_OK on success otherwise error status - //! - virtual Status size(FwSizeType& size_result) = 0; - - //! \brief get file pointer position of the currently open file - //! - //! Get the current position of the read/write pointer of the open file. - //! \param position: output parameter for size. - //! \return OP_OK on success otherwise error status - //! - virtual Status position(FwSizeType& position_result) = 0; - - //! \brief pre-allocate file storage - //! - //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations - //! that cannot pre-allocate. - //! - //! It is invalid to pass a negative `offset`. - //! It is invalid to pass a negative `length`. - //! - //! \param offset: offset into file - //! \param length: length after offset to preallocate - //! \return OP_OK on success otherwise error status - //! - virtual Status preallocate(FwSizeType offset, FwSizeType length) = 0; - - //! \brief seek the file pointer to the given offset - //! - //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated - //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position. - //! - //! \param offset: offset to seek to - //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position. - //! \return OP_OK on success otherwise error status - //! - virtual Status seek(FwSignedSizeType offset, SeekType seekType) = 0; - - //! \brief flush file contents to storage - //! - //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations - //! that do not support flushing. - //! - //! \return OP_OK on success otherwise error status - //! - virtual Status flush() = 0; - - //! \brief read data from this file into supplied buffer bounded by size - //! - //! Read data from this file up to the `size` and store it in `buffer`. When `wait` is set to `WAIT`, this - //! will block until the requested size has been read successfully read or the end of the file has been - //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available. - //! - //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of - //! the read operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! It is invalid to supply wait as a non-enumerated value. - //! - //! \param buffer: memory location to store data read from file - //! \param size: size of data to read - //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available - //! \return OP_OK on success otherwise error status - //! - virtual Status read(U8* buffer, FwSizeType& size, WaitType wait) = 0; - - //! \brief read data from this file into supplied buffer bounded by size - //! - //! Write data to this file up to the `size` from the `buffer`. When `wait` is set to `WAIT`, this - //! will block until the requested size has been written successfully to disk. When `wait` is set to - //! `NO_WAIT` it will return once the data is sent to the OS. - //! - //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of - //! the read operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! It is invalid to supply wait as a non-enumerated value. - //! - //! \param buffer: memory location to store data read from file - //! \param size: size of data to read - //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available - //! \return OP_OK on success otherwise error status - //! - virtual Status write(const U8* buffer, FwSizeType& size, WaitType wait) = 0; - - //! \brief returns the raw file handle - //! - //! Gets the raw file handle from the implementation. Note: users must include the implementation specific - //! header to make any real use of this handle. Otherwise it will be as an opaque type. - //! - //! \return raw file handle - //! - virtual FileHandle* getHandle() = 0; - - //! \brief provide a pointer to a file delegate object - //! - //! This function must return a pointer to a `FileInterface` object that contains the real implementation of the - //! file functions as defined by the implementor. This function must do several things to be considered correctly - //! implemented: - //! - //! 1. Assert that the supplied memory is non-null. e.g `FW_ASSERT(aligned_placement_new_memory != NULL);` - //! 2. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. - //! e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage, - //! "FW_HANDLE_MAX_SIZE too small");` - //! 3. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. - //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");` - //! 4. If to_copy is null, placement new their implementation into `aligned_placement_new_memory` - //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;` - //! 5. If to_copy is non-null, placement new using copy constructor their implementation into - //! `aligned_placement_new_memory` - //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);` - //! 6. Return the result of the placement new - //! e.g. `return interface;` - //! - //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` - //! - static FileInterface* getDelegate(FileHandleStorage& aligned_placement_new_memory, - const FileInterface* to_copy = nullptr); -}; - -class File final : public FileInterface { - friend struct Os::Test::FileTest::Tester; - - public: - //! \brief constructor - //! - File(); - //! \brief destructor - //! - //! Destructor closes the file if it is open - ~File() final; - - //! \brief copy constructor that copies the internal representation - File(const File& other); - - //! \brief assignment operator that copies the internal representation - File& operator=(const File& other); - - //! \brief determine if the file is open - //! \return true if file is open, false otherwise - //! - bool isOpen() const; - - // ------------------------------------ - // Functions supplying default values - // ------------------------------------ - - //! \brief open file with supplied path and mode - //! - //! Open the file passed in with the given mode. Opening files with `OPEN_CREATE` mode will not clobber existing - //! files. Use other `open` method to set overwrite flag and clobber existing files. The status of the open - //! request is returned from the function call. Delegates to the chosen implementation's `open` function. - //! - //! It is invalid to send `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! - //! \param path: c-string of path to open - //! \param mode: file operation mode - //! \return: status of the open - //! - Os::FileInterface::Status open(const char* path, Mode mode); - - //! \brief open file with supplied path, bounded length, and mode - //! - //! Open the file passed in with the given mode. The path length is bounded by `length`. - //! Opening files with `OPEN_CREATE` mode will not clobber existing files. Use the overload - //! accepting `OverwriteType` to set overwrite flag and clobber existing files. - //! - //! It is invalid to send `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! It is invalid for the path to not be null-terminated within `length` characters. - //! - //! \param path: c-string of path to open - //! \param length: bound on the path buffer size - //! \param mode: file operation mode - //! \return: status of the open - //! - Os::FileInterface::Status open(const char* path, FwSizeType length, Mode mode); - - //! \brief open file with supplied string path and mode - //! - //! Open the file passed in with the given mode. Opening files with `OPEN_CREATE` mode will not clobber existing - //! files. Use the overload accepting `OverwriteType` to set overwrite flag and clobber existing files. - //! - //! It is invalid to supply `mode` as a non-enumerated value. - //! - //! \param path: ConstStringBase reference of path to open - //! \param mode: file operation mode - //! \return: status of the open - //! - Os::FileInterface::Status open(const Fw::ConstStringBase& path, Mode mode); - - //! \brief open file with supplied string path, mode, and overwrite type - //! - //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in - //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. - //! - //! It is invalid to supply `mode` as a non-enumerated value. - //! It is invalid to supply `overwrite` as a non-enumerated value. - //! - //! \param path: ConstStringBase reference of path to open - //! \param mode: file operation mode - //! \param overwrite: overwrite existing file on create - //! \return: status of the open - //! - Os::FileInterface::Status open(const Fw::ConstStringBase& path, Mode mode, OverwriteType overwrite); - - //! \brief read data from this file into supplied buffer bounded by size - //! - //! Read data from this file up to the `size` and store it in `buffer`. This version will - //! will block until the requested size has been read successfully read or the end of the file has been - //! reached. - //! - //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of - //! the read operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! - //! \param buffer: memory location to store data read from file - //! \param size: size of data to read - //! \return OP_OK on success otherwise error status - //! - Status read(U8* buffer, FwSizeType& size); - - //! \brief write data to this file from the supplied buffer bounded by size - //! - //! Write data from `buffer` up to the `size` and store it in this file. This call - //! will block until the requested size has been written. Otherwise, this call will write without blocking. - //! - //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of - //! the write operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! - //! \param buffer: memory location of data to write to file - //! \param size: size of data to write - //! \return OP_OK on success otherwise error status - //! - Status write(const U8* buffer, FwSizeType& size); - - // ------------------------------------ - // Functions overrides - // ------------------------------------ - - //! \brief open file with supplied path and mode - //! - //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in - //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. - //! The status of the open request is returned from the function call. Delegates to the chosen - //! implementation's `open` function. - //! - //! It is invalid to send `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! It is invalid to supply `overwrite` as a non-enumerated value. - //! - //! \param path: c-string of path to open - //! \param mode: file operation mode - //! \param overwrite: overwrite existing file on create - //! \return: status of the open - //! - Os::FileInterface::Status open(const char* path, Mode mode, OverwriteType overwrite) override; - - //! \brief open file with supplied path, bounded length, mode, and overwrite type - //! - //! Open the file passed in with the given mode. The path length is bounded by `length`. - //! If overwrite is set to OVERWRITE, then opening files in OPEN_CREATE mode will clobber - //! existing files. Set overwrite to NO_OVERWRITE to preserve existing files. This is the - //! core open implementation to which all other open overloads delegate. - //! - //! It is invalid to send `nullptr` as the path. - //! It is invalid to supply `mode` as a non-enumerated value. - //! It is invalid to supply `overwrite` as a non-enumerated value. - //! It is invalid for the path to not be null-terminated within `length` characters. - //! - //! \param path: c-string of path to open - //! \param length: bound on the path buffer size - //! \param mode: file operation mode - //! \param overwrite: overwrite existing file on create - //! \return: status of the open - //! - Os::FileInterface::Status open(const char* path, FwSizeType length, Mode mode, OverwriteType overwrite); - - //! \brief close the file, if not opened then do nothing - //! - //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's - //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`. - //! - void close() override; - - //! \brief get size of currently open file - //! - //! Get the size of the currently open file and fill the size parameter. Return status of the operation. - //! \param size: output parameter for size. - //! \return OP_OK on success otherwise error status - //! - Status size(FwSizeType& size_result) override; - - //! \brief get file pointer position of the currently open file - //! - //! Get the current position of the read/write pointer of the open file. - //! \param position: output parameter for size. - //! \return OP_OK on success otherwise error status - //! - Status position(FwSizeType& position_result) override; - - //! \brief pre-allocate file storage - //! - //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations - //! that cannot pre-allocate. - //! - //! It is invalid to pass a negative `offset`. - //! It is invalid to pass a negative `length`. - //! - //! \param offset: offset into file - //! \param length: length after offset to preallocate - //! \return OP_OK on success otherwise error status - //! - Status preallocate(FwSizeType offset, FwSizeType length) override; - - //! \brief seek the file pointer to the given offset - //! - //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated - //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position. - //! - //! \param offset: offset to seek to - //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position. - //! \return OP_OK on success otherwise error status - //! - Status seek(FwSignedSizeType offset, SeekType seekType) override; - - //! \brief seek the file pointer to the given offset absolutely with the full range - //! - //! Seek the file pointer to the given `offset` absolutely from the beginning of the file. This function is - //! equivalent to calling `seek` with `ABSOLUTE` as the `seekType` with the exception that it can handle the - //! full range of `FwSizeType` values as returned by `size` and `position` calls. - //! - //! Internally, it will perform multiple seeks to reach the desired offset while never exceeding the signed - //! limit of the basic `seek` function. - //! - //! \param offset_unsigned: offset to absolutely seek to - //! \return OP_OK on success otherwise error status - Status seek_absolute(FwSizeType offset_unsigned); - - //! \brief flush file contents to storage - //! - //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations - //! that do not support flushing. - //! - //! \return OP_OK on success otherwise error status - //! - Status flush() override; - - //! \brief read data from this file into supplied buffer bounded by size - //! - //! Read data from this file up to the `size` and store it in `buffer`. When `wait` is set to `WAIT`, this - //! will block until the requested size has been read successfully read or the end of the file has been - //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available. - //! - //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of - //! the read operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! It is invalid to supply wait as a non-enumerated value. - //! - //! \param buffer: memory location to store data read from file - //! \param size: size of data to read - //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available - - //! - Status read(U8* buffer, FwSizeType& size, WaitType wait) override; - - //! \brief read a line from the file using `\n` as the delimiter - //! - //! Reads a single line from the file including the terminating '\n'. This will return an error if no line is - //! found within the specified buffer size. In the case of EOF, the line is read without the terminating '\n'. - //! - //! In the case of an error, this function will seek to the original location in the file. Otherwise, the - //! pointer will point to the first character after the `\n` or EOF in the case of no `\n`. - //! - //! It is invalid to send a null buffer. - //! It is invalid to send a size less than 0. - //! It is an error if the file is not opened for reading. - //! - //! \param buffer: memory location to store data read from file - //! \param size: maximum size of buffer to store the new line - //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available - //! \return OP_OK on success otherwise error status - Status readline(U8* buffer, FwSizeType& size, WaitType wait); - - //! \brief read data from this file into supplied buffer bounded by size - //! - //! Write data to this file up to the `size` from the `buffer`. When `wait` is set to `WAIT`, this - //! will block until the requested size has been written successfully to disk. When `wait` is set to - //! `NO_WAIT` it will return once the data is sent to the OS. - //! - //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of - //! the read operation. - //! - //! It is invalid to pass `nullptr` to this function call. - //! It is invalid to pass a negative `size`. - //! It is invalid to supply wait as a non-enumerated value. - //! - //! \param buffer: memory location to store data read from file - //! \param size: size of data to read - //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available - //! \return OP_OK on success otherwise error status - //! - Status write(const U8* buffer, FwSizeType& size, WaitType wait) override; - - //! \brief returns the raw file handle - //! - //! Gets the raw file handle from the implementation. Note: users must include the implementation specific - //! header to make any real use of this handle. Otherwise it//!must* be passed as an opaque type. - //! - //! \return raw file handle - //! - FileHandle* getHandle() override; - - //! \brief calculate the CRC32 of the entire file - //! - //! Calculates the CRC32 of the file's contents. The `crc` parameter will be updated to contain the CRC or 0 on - //! failure. Status will represent failure conditions. This call will be decomposed into calculations on - //! sections of the file `FW_FILE_CHUNK_SIZE` bytes long. - //! - //! This function requires that the file already be opened for "READ" mode. - //! - //! On error crc will be set to 0. - //! - //! \note: the file pointer will be positioned at the end of the file after this call. - //! - //! This function is equivalent to the following pseudo-code: - //! - //! ``` - //! U32 crc; - //! do { - //! size = FW_FILE_CHUNK_SIZE; - //! m_file.incrementalCrc(size); - //! while (size == FW_FILE_CHUNK_SIZE); - //! m_file.finalize(crc); - //! ``` - //! \param crc: U32 bit value to fill with CRC - //! \return OP_OK on success otherwise error status - //! - Status calculateCrc(U32& crc); - - //! \brief calculate the CRC32 of the next section of data - //! - //! Starting at the current file pointer, this will add `size` bytes of data to the currently calculated CRC. - //! Call `finalizeCrc` to retrieve the CRC or `calculateCrc` to perform a CRC on the entire file. This call will - //! not block waiting for data on the underlying read, nor will it reset the file position pointer. On error, - //! the current CRC results should be discarded by reopening the file or calling `finalizeCrc` and - //! discarding its result. `size` will be updated with the `size` actually read and used in the CRC calculation. - //! - //! This function requires that the file already be opened for "READ" mode. - //! - //! It is illegal for size to be less than or equal to 0 or greater than FW_FILE_CHUNK_SIZE. - //! - //! \param size: size of data to read for CRC - //! \return: status of the CRC calculation - //! - Status incrementalCrc(FwSizeType& size); - - //! \brief finalize and retrieve the CRC value - //! - //! Finalizes the CRC computation and returns the CRC value. The `crc` value will be modified to contain the - //! crc or 0 on error. Note: this will reset any active CRC calculation and effectively re-initializes any - //! `incrementalCrc` calculation. - //! - //! On error crc will be set to 0. - //! - //! \param crc: value to fill - //! \return status of the CRC calculation - //! - Status finalizeCrc(U32& crc); - - private: - static const U32 INITIAL_CRC = 0xFFFFFFFF; //!< Initial value for CRC calculation - - Mode m_mode = Mode::OPEN_NO_MODE; //!< Stores mode for error checking - - Utils::Hash m_hash; //!< Hash object for incremental CRC calculation - U8 m_crc_buffer[FW_FILE_CHUNK_SIZE]; - - // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is - // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in - // the byte-array here and set `handle` to that address for storage. - // - alignas(FW_HANDLE_ALIGNMENT) FileHandleStorage m_handle_storage; //!< Storage for aligned FileHandle data - FileInterface& m_delegate; //!< Delegate for the real implementation -}; -} // namespace Os +#else +#include OS_FILE_HEADER #endif + +#endif // Os_File_hpp_ diff --git a/Os/FileInterface.cpp b/Os/FileInterface.cpp new file mode 100644 index 00000000000..75f6006fd77 --- /dev/null +++ b/Os/FileInterface.cpp @@ -0,0 +1,182 @@ +// ====================================================================== +// \title Os/FileInterface.cpp +// \brief common function implementation for Os::FileInterface +// ====================================================================== +#include +#include +#include +#include +#include + +namespace Os { + +FileInterface::FileInterface(const FileInterface& other) : m_mode(other.m_mode) {} + +FileInterface& FileInterface::operator=(const FileInterface& other) { + if (this != &other) { + this->m_mode = other.m_mode; + } + return *this; +} + +FileInterface::Status FileInterface::open(const char* filepath, FileInterface::Mode requested_mode) { + return this->open(filepath, static_cast(FileNameStringSize + 1), requested_mode, + OverwriteType::NO_OVERWRITE); +} + +FileInterface::Status FileInterface::open(const char* filepath, FwSizeType length, FileInterface::Mode requested_mode) { + return this->open(filepath, length, requested_mode, OverwriteType::NO_OVERWRITE); +} + +FileInterface::Status FileInterface::open(const char* filepath, + FwSizeType length, + FileInterface::Mode requested_mode, + FileInterface::OverwriteType overwrite) { + FW_ASSERT(nullptr != filepath); + const FwSizeType string_len = static_cast(Fw::StringUtils::string_length(filepath, length)); + FW_ASSERT(string_len < length, static_cast(string_len), static_cast(length)); + FW_ASSERT(FileInterface::Mode::OPEN_NO_MODE < requested_mode && + FileInterface::Mode::MAX_OPEN_MODE > requested_mode); + FW_ASSERT((0 <= overwrite) && (overwrite < OverwriteType::MAX_OVERWRITE_TYPE)); + return this->open(filepath, requested_mode, overwrite); +} + +FileInterface::Status FileInterface::open(const Fw::ConstStringBase& path, FileInterface::Mode requested_mode) { + return this->open(path.toChar(), static_cast(path.getCapacity()), requested_mode, + OverwriteType::NO_OVERWRITE); +} + +FileInterface::Status FileInterface::open(const Fw::ConstStringBase& path, + FileInterface::Mode requested_mode, + FileInterface::OverwriteType overwrite) { + return this->open(path.toChar(), static_cast(path.getCapacity()), requested_mode, overwrite); +} + +bool FileInterface::isOpen() const { + FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); + return this->m_mode != Mode::OPEN_NO_MODE; +} + +FileInterface::Mode FileInterface::getMode() const { + FW_ASSERT((0 <= this->m_mode) && (this->m_mode < Mode::MAX_OPEN_MODE)); + return this->m_mode; +} + +void FileInterface::setMode(FileInterface::Mode mode) { + FW_ASSERT((0 <= mode) && (mode < Mode::MAX_OPEN_MODE)); + this->m_mode = mode; +} + +FileInterface::Status FileInterface::seek_absolute(FwSizeType offset) { + Os::FileInterface::Status status = FileInterface::Status::OTHER_ERROR; + // If the offset can be represented by a signed value, then we can perform a single seek + if (static_cast(std::numeric_limits::max()) >= offset) { + // Check that the bounding above is correct + FW_ASSERT(static_cast(offset) >= 0); + status = this->seek(static_cast(offset), FileInterface::SeekType::ABSOLUTE); + } + // Otherwise, a full seek to any value represented by FwSizeType can be performed + // by at most 3 seeks of a FwSignedSizeType. Two half seeks (rounded down) that are + // strictly bounded by std::numeric_limits::max() and one seek of + // a possibile "odd" byte to ensure odds offsets do not introduce an off-by-one-error. + // Thus we perform 3 seeks to guarantee that we can reach any position. + else { + FwSignedSizeType half_offset = static_cast(offset >> 1); + bool is_odd = (offset % 2) == 1; + status = this->seek(half_offset, FileInterface::SeekType::ABSOLUTE); + if (status == FileInterface::Status::OP_OK) { + status = this->seek(half_offset, FileInterface::SeekType::RELATIVE); + } + if (status == FileInterface::Status::OP_OK) { + status = this->seek((is_odd) ? 1 : 0, FileInterface::SeekType::RELATIVE); + } + } + return status; +} + +FileInterface::Status FileInterface::read(U8* buffer, FwSizeType& size) { + return this->read(buffer, size, WaitType::WAIT); +} + +FileInterface::Status FileInterface::write(const U8* buffer, FwSizeType& size) { + return this->write(buffer, size, WaitType::WAIT); +} + +// The CRC scratch state (`Utils::Hash` accumulator + `FW_FILE_CHUNK_SIZE` +// read buffer) and the working algorithm deliberately do not live on this +// base class -- see the design note in Os/FileInterface.hpp. These base +// implementations report the capability is unavailable; `Os::DelegateFile` +// (which `Os::File` aliases) overrides all three with the real logic. +FileInterface::Status FileInterface::calculateCrc(U32& crc) { + crc = 0; + return FileInterface::Status::NOT_SUPPORTED; +} + +FileInterface::Status FileInterface::incrementalCrc(FwSizeType& size) { + (void)size; + return FileInterface::Status::NOT_SUPPORTED; +} + +FileInterface::Status FileInterface::finalizeCrc(U32& crc) { + crc = 0; + return FileInterface::Status::NOT_SUPPORTED; +} + +FileInterface::Status FileInterface::readline(U8* buffer, FwSizeType& size, FileInterface::WaitType wait) { + const FwSizeType requested_size = size; + FW_ASSERT(buffer != nullptr); + FW_ASSERT(this->m_mode < Mode::MAX_OPEN_MODE); + // Check that the file is open before attempting operation + if (OPEN_NO_MODE == this->m_mode) { + size = 0; + return FileInterface::Status::NOT_OPENED; + } else if (OPEN_READ != this->m_mode) { + size = 0; + return FileInterface::Status::INVALID_MODE; + } + FwSizeType original_location = 0; + FileInterface::Status status = this->position(original_location); + if (status != Os::FileInterface::Status::OP_OK) { + size = 0; + (void)this->seek_absolute(original_location); + return status; + } + FwSizeType read = 0; + // Loop reading chunk by chunk + for (FwSizeType i = 0; i < size; i += read) { + // read in chunks to avoid large buffer allocations + FwSizeType current_chunk_size = std::min(size - i, static_cast(FW_FILE_CHUNK_SIZE)); + read = current_chunk_size; + status = this->read(buffer + i, read, wait); + if (status != FileInterface::Status::OP_OK) { + // Contract: on error, seek back to the original location + size = 0; + (void)this->seek_absolute(original_location); + return status; + } + // EOF break out now + if (read == 0) { + size = i; + return Os::FileInterface::Status::OP_OK; + } + // Loop from i to i + current_chunk_size looking for `\n` + const FwSizeType chunk_end = i + read; + for (FwSizeType j = i; j < chunk_end; j++) { + // Newline seek back to after it, return the size read + if (buffer[j] == '\n') { + size = j + 1; + // Ensure that the computation worked and there is not overflow + FW_ASSERT(size <= requested_size); + FW_ASSERT(std::numeric_limits::max() - size >= original_location); + (void)this->seek_absolute(original_location + j + 1); + return Os::FileInterface::Status::OP_OK; + } + } + } + // Failed to find newline within data available + // Contract: on error, seek back to the original location + size = 0; + (void)this->seek_absolute(original_location); + return Os::FileInterface::Status::OTHER_ERROR; +} +} // namespace Os diff --git a/Os/FileInterface.hpp b/Os/FileInterface.hpp new file mode 100644 index 00000000000..b4b39c28ed3 --- /dev/null +++ b/Os/FileInterface.hpp @@ -0,0 +1,523 @@ +// ====================================================================== +// \title Os/FileInterface.hpp +// \brief Os::FileHandle and Os::FileInterface definitions +// ====================================================================== +#ifndef OS_FILEINTERFACE_HPP_ +#define OS_FILEINTERFACE_HPP_ + +#include +#include +#include +#include "config/OsSelection.hpp" + +// Forward declaration for UTs +namespace Os { +namespace Test { +namespace FileTest { +struct Tester; +} +} // namespace Test +} // namespace Os + +namespace Os { + +//! \brief base implementation of FileHandle +//! +struct FileHandle {}; + +// This class encapsulates a very simple file interface that has the most often-used features +class FileInterface { + friend struct Os::Test::FileTest::Tester; + + public: + enum Mode { + OPEN_NO_MODE, //!< File mode not yet selected + OPEN_READ, //!< Open file for reading + OPEN_CREATE, //!< Open file for writing and truncates file if it exists, ie same flags as creat() + OPEN_WRITE, //!< Open file for writing + OPEN_SYNC_WRITE, //!< Open file for writing; writes don't return until data is on disk + OPEN_APPEND, //!< Open file for appending + MAX_OPEN_MODE //!< Maximum value of mode + }; + + enum Status { + OP_OK, //!< Operation was successful + DOESNT_EXIST, //!< File doesn't exist (for read) + NO_SPACE, //!< No space left + NO_PERMISSION, //!< No permission to read/write file + BAD_SIZE, //!< Invalid size parameter + NOT_OPENED, //!< file hasn't been opened yet + FILE_EXISTS, //!< file already exist (for CREATE with O_EXCL enabled) + NOT_SUPPORTED, //!< Kernel or file system does not support operation + INVALID_MODE, //!< Mode for file access is invalid for current operation + INVALID_ARGUMENT, //!< Invalid argument passed in + NO_MORE_RESOURCES, //!< No more available resources + OTHER_ERROR, //!< A catch-all for other errors. Have to look in implementation-specific code + OUTSIDE_SANDBOX, //!< Path falls outside the configured sandbox directory + MAX_STATUS //!< Maximum value of status + }; + + enum OverwriteType { + NO_OVERWRITE, //!< Do NOT overwrite existing files + OVERWRITE, //!< Overwrite file when it exists and creation was requested + MAX_OVERWRITE_TYPE + }; + + enum SeekType { + RELATIVE, //!< Relative seek from current file offset + ABSOLUTE, //!< Absolute seek from beginning of file + MAX_SEEK_TYPE + }; + + enum WaitType { + NO_WAIT, //!< Do not wait for read/write operation to finish + WAIT, //!< Do wait for read/write operation to finish + MAX_WAIT_TYPE + }; + + //! \brief default constructor + //! + FileInterface() = default; + + //! \brief default virtual destructor + //! + virtual ~FileInterface() = default; + + //! \brief copy constructor that copies the mode state tracked by this base class + FileInterface(const FileInterface& other); + + //! \brief assignment operator that copies the mode state tracked by this base class + FileInterface& operator=(const FileInterface& other); + + //! \brief determine if the file is open + //! \return true if file is open, false otherwise + //! + bool isOpen() const; + + // ------------------------------------ + // Functions supplying default values + // ------------------------------------ + + //! \brief open file with supplied path and mode + //! + //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in + //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. + //! The status of the open request is returned from the function call. Delegates to the chosen + //! implementation's `open` function. + //! + //! It is invalid to send `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! It is invalid to supply `overwrite` as a non-enumerated value. + //! + //! This overload is also the implementation hook and applies no length bound to `path`, which must be + //! null-terminated. Callers holding a buffer that may be unterminated or longer than `FileNameStringSize` + //! should use an overload that takes an explicit `length`. + //! + //! \param path: c-string of path to open + //! \param mode: file operation mode + //! \param overwrite: overwrite existing file on create + //! \return: status of the open + //! + virtual Status open(const char* path, Mode mode, OverwriteType overwrite) = 0; + + //! \brief open file with supplied path and mode + //! + //! Open the file passed in with the given mode. Opening files with `OPEN_CREATE` mode will not clobber existing + //! files. Use other `open` method to set overwrite flag and clobber existing files. The status of the open + //! request is returned from the function call. Delegates to the chosen implementation's `open` function. + //! + //! It is invalid to send `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! + //! \param path: c-string of path to open + //! \param mode: file operation mode + //! \return: status of the open + //! + Status open(const char* path, Mode mode); + + //! \brief open file with supplied path, bounded length, and mode + //! + //! Open the file passed in with the given mode. The path length is bounded by `length`. + //! Opening files with `OPEN_CREATE` mode will not clobber existing files. Use the overload + //! accepting `OverwriteType` to set overwrite flag and clobber existing files. + //! + //! It is invalid to send `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! It is invalid for the path to not be null-terminated within `length` characters. + //! + //! \param path: c-string of path to open + //! \param length: bound on the path buffer size + //! \param mode: file operation mode + //! \return: status of the open + //! + Status open(const char* path, FwSizeType length, Mode mode); + + //! \brief open file with supplied path, bounded length, mode, and overwrite type + //! + //! Open the file passed in with the given mode. The path length is bounded by `length`. + //! If overwrite is set to OVERWRITE, then opening files in OPEN_CREATE mode will clobber + //! existing files. Set overwrite to NO_OVERWRITE to preserve existing files. This overload + //! validates its arguments and then delegates to the 3-argument open() implementation hook. + //! + //! It is invalid to send `nullptr` as the path. + //! It is invalid to supply `mode` as a non-enumerated value. + //! It is invalid to supply `overwrite` as a non-enumerated value. + //! It is invalid for the path to not be null-terminated within `length` characters. + //! + //! \param path: c-string of path to open + //! \param length: bound on the path buffer size + //! \param mode: file operation mode + //! \param overwrite: overwrite existing file on create + //! \return: status of the open + //! + Status open(const char* path, FwSizeType length, Mode mode, OverwriteType overwrite); + + //! \brief open file with supplied string path and mode + //! + //! Open the file passed in with the given mode. Opening files with `OPEN_CREATE` mode will not clobber existing + //! files. Use the overload accepting `OverwriteType` to set overwrite flag and clobber existing files. + //! + //! It is invalid to supply `mode` as a non-enumerated value. + //! + //! \param path: ConstStringBase reference of path to open + //! \param mode: file operation mode + //! \return: status of the open + //! + Status open(const Fw::ConstStringBase& path, Mode mode); + + //! \brief open file with supplied string path, mode, and overwrite type + //! + //! Open the file passed in with the given mode. If overwrite is set to OVERWRITE, then opening files in + //! OPEN_CREATE mode will clobber existing files. Set overwrite to NO_OVERWRITE to preserve existing files. + //! + //! It is invalid to supply `mode` as a non-enumerated value. + //! It is invalid to supply `overwrite` as a non-enumerated value. + //! + //! \param path: ConstStringBase reference of path to open + //! \param mode: file operation mode + //! \param overwrite: overwrite existing file on create + //! \return: status of the open + //! + Status open(const Fw::ConstStringBase& path, Mode mode, OverwriteType overwrite); + + //! \brief close the file, if not opened then do nothing + //! + //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's + //! `closeInternal` function. `mode` is set to `OPEN_NO_MODE`. + //! + virtual void close() = 0; + + //! \brief get size of currently open file + //! + //! Get the size of the currently open file and fill the size parameter. Return status of the operation. + //! \param size: output parameter for size. + //! \return OP_OK on success otherwise error status + //! + virtual Status size(FwSizeType& size_result) = 0; + + //! \brief get file pointer position of the currently open file + //! + //! Get the current position of the read/write pointer of the open file. + //! \param position: output parameter for size. + //! \return OP_OK on success otherwise error status + //! + virtual Status position(FwSizeType& position_result) = 0; + + //! \brief pre-allocate file storage + //! + //! Pre-allocates file storage with at least `length` storage starting at `offset`. No-op on implementations + //! that cannot pre-allocate. + //! + //! It is invalid to pass a negative `offset`. + //! It is invalid to pass a negative `length`. + //! + //! \param offset: offset into file + //! \param length: length after offset to preallocate + //! \return OP_OK on success otherwise error status + //! + virtual Status preallocate(FwSizeType offset, FwSizeType length) = 0; + + //! \brief seek the file pointer to the given offset + //! + //! Seek the file pointer to the given `offset`. If `seekType` is set to `ABSOLUTE` then the offset is calculated + //! from the start of the file, and if it is set to `RELATIVE` it is calculated from the current position. + //! + //! \param offset: offset to seek to + //! \param seekType: `ABSOLUTE` for seeking from beginning of file, `RELATIVE` to use current position. + //! \return OP_OK on success otherwise error status + //! + virtual Status seek(FwSignedSizeType offset, SeekType seekType) = 0; + + //! \brief seek the file pointer to the given offset absolutely with the full range + //! + //! Seek the file pointer to the given `offset` absolutely from the beginning of the file. This function is + //! equivalent to calling `seek` with `ABSOLUTE` as the `seekType` with the exception that it can handle the + //! full range of `FwSizeType` values as returned by `size` and `position` calls. + //! + //! Internally, it will perform multiple seeks to reach the desired offset while never exceeding the signed + //! limit of the basic `seek` function. + //! + //! \param offset_unsigned: offset to absolutely seek to + //! \return OP_OK on success otherwise error status + Status seek_absolute(FwSizeType offset_unsigned); + + //! \brief flush file contents to storage + //! + //! Flushes the file contents to storage (i.e. out of the OS cache to disk). Does nothing in implementations + //! that do not support flushing. + //! + //! \return OP_OK on success otherwise error status + //! + virtual Status flush() = 0; + + //! \brief read data from this file into supplied buffer bounded by size + //! + //! Read data from this file up to the `size` and store it in `buffer`. When `wait` is set to `WAIT`, this + //! will block until the requested size has been read successfully read or the end of the file has been + //! reached. When `wait` is set to `NO_WAIT` it will return whatever data is currently available. + //! + //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of + //! the read operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! It is invalid to supply wait as a non-enumerated value. + //! + //! \param buffer: memory location to store data read from file + //! \param size: size of data to read + //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available + //! \return OP_OK on success otherwise error status + //! + virtual Status read(U8* buffer, FwSizeType& size, WaitType wait) = 0; + + //! \brief read data from this file into supplied buffer bounded by size + //! + //! Read data from this file up to the `size` and store it in `buffer`. This version will + //! will block until the requested size has been read successfully read or the end of the file has been + //! reached. + //! + //! `size` will be updated to the count of bytes actually read. Status will reflect the success/failure of + //! the read operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! + //! \param buffer: memory location to store data read from file + //! \param size: size of data to read + //! \return OP_OK on success otherwise error status + //! + Status read(U8* buffer, FwSizeType& size); + + //! \brief read a line from the file using `\n` as the delimiter + //! + //! Reads a single line from the file including the terminating '\n'. This will return an error if no line is + //! found within the specified buffer size. In the case of EOF, the line is read without the terminating '\n'. + //! + //! In the case of an error, this function will seek to the original location in the file. Otherwise, the + //! pointer will point to the first character after the `\n` or EOF in the case of no `\n`. + //! + //! It is invalid to send a null buffer. + //! It is invalid to send a size less than 0. + //! It is an error if the file is not opened for reading. + //! + //! \param buffer: memory location to store data read from file + //! \param size: maximum size of buffer to store the new line + //! \param wait: `WAIT` to wait for data, `NO_WAIT` to return what is currently available + //! \return OP_OK on success otherwise error status + Status readline(U8* buffer, FwSizeType& size, WaitType wait); + + //! \brief read data from this file into supplied buffer bounded by size + //! + //! Write data to this file up to the `size` from the `buffer`. When `wait` is set to `WAIT`, this + //! will block until the requested size has been written successfully to disk. When `wait` is set to + //! `NO_WAIT` it will return once the data is sent to the OS. + //! + //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of + //! the read operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! It is invalid to supply wait as a non-enumerated value. + //! + //! \param buffer: memory location to store data read from file + //! \param size: size of data to read + //! \param wait: `WAIT` to wait for data to write to disk, `NO_WAIT` to return what is currently available + //! \return OP_OK on success otherwise error status + //! + virtual Status write(const U8* buffer, FwSizeType& size, WaitType wait) = 0; + + //! \brief write data to this file from the supplied buffer bounded by size + //! + //! Write data from `buffer` up to the `size` and store it in this file. This call + //! will block until the requested size has been written. Otherwise, this call will write without blocking. + //! + //! `size` will be updated to the count of bytes actually written. Status will reflect the success/failure of + //! the write operation. + //! + //! It is invalid to pass `nullptr` to this function call. + //! It is invalid to pass a negative `size`. + //! + //! \param buffer: memory location of data to write to file + //! \param size: size of data to write + //! \return OP_OK on success otherwise error status + //! + Status write(const U8* buffer, FwSizeType& size); + + //! \brief returns the raw file handle + //! + //! Gets the raw file handle from the implementation. Note: users must include the implementation specific + //! header to make any real use of this handle. Otherwise it will be as an opaque type. + //! + //! \return raw file handle + //! + virtual FileHandle* getHandle() = 0; + + // ---------------------------------------------------------------------- + // CRC interface + // + // Design note (authoritative; other CRC comments refer back to this one): + // The CRC computation needs cross-call scratch state -- an incremental + // `Utils::Hash` accumulator and a `FW_FILE_CHUNK_SIZE`-byte read buffer. + // That state deliberately does *not* live here on `FileInterface`. + // Concrete `FileInterface` implementations (`Os::Posix::File::PosixFile`, + // `Os::Stub::File::StubFile`, ...) are placement-new'd into a fixed-size + // (`FW_FILE_HANDLE_MAX_SIZE`) byte array by `Os::Delegate::makeDelegate`; + // embedding the ~512-byte CRC buffer in that state would blow the + // handle-size budget (and be duplicated into every implementation). + // Instead the CRC state and the real algorithm live on the wrapper + // (`Os::DelegateFile`, which is what `Os::File` aliases), whose storage is + // *not* handle-size-constrained. These methods are therefore `virtual` + // with a base implementation that reports the capability is unavailable; + // `DelegateFile` overrides them with the working implementation, driving + // I/O through the (virtual) `read()` so it transparently reaches the + // selected delegate. `m_mode` stays on this base class because it guards + // many non-CRC operations (`isOpen`, `readline`, seek checks) and is + // shared with the delegate mode-tracking contract. + // ---------------------------------------------------------------------- + + //! \brief calculate the CRC32 of the entire file + //! + //! Calculates the CRC32 of the file's contents. The `crc` parameter will be updated to contain the CRC or 0 on + //! failure. Status will represent failure conditions. This call will be decomposed into calculations on + //! sections of the file `FW_FILE_CHUNK_SIZE` bytes long. + //! + //! This function requires that the file already be opened for "READ" mode. + //! + //! On error crc will be set to 0. + //! + //! \note: the file pointer will be positioned at the end of the file after this call. + //! + //! This function is equivalent to the following pseudo-code: + //! + //! ``` + //! U32 crc; + //! do { + //! size = FW_FILE_CHUNK_SIZE; + //! m_file.incrementalCrc(size); + //! while (size == FW_FILE_CHUNK_SIZE); + //! m_file.finalize(crc); + //! ``` + //! + //! \note The base-class implementation returns `NOT_SUPPORTED`; the CRC + //! scratch state and algorithm live on `Os::DelegateFile` (see the design + //! note above), which overrides this method. + //! + //! \param crc: U32 bit value to fill with CRC + //! \return OP_OK on success otherwise error status + //! + virtual Status calculateCrc(U32& crc); + + //! \brief calculate the CRC32 of the next section of data + //! + //! Starting at the current file pointer, this will add `size` bytes of data to the currently calculated CRC. + //! Call `finalizeCrc` to retrieve the CRC or `calculateCrc` to perform a CRC on the entire file. This call will + //! not block waiting for data on the underlying read, nor will it reset the file position pointer. On error, + //! the current CRC results should be discarded by reopening the file or calling `finalizeCrc` and + //! discarding its result. `size` will be updated with the `size` actually read and used in the CRC calculation. + //! + //! This function requires that the file already be opened for "READ" mode. + //! + //! It is illegal for size to be less than or equal to 0 or greater than FW_FILE_CHUNK_SIZE. + //! + //! \note The base-class implementation returns `NOT_SUPPORTED`; the CRC + //! scratch state and algorithm live on `Os::DelegateFile` (see the design + //! note above), which overrides this method. + //! + //! \param size: size of data to read for CRC + //! \return: status of the CRC calculation + //! + virtual Status incrementalCrc(FwSizeType& size); + + //! \brief finalize and retrieve the CRC value + //! + //! Finalizes the CRC computation and returns the CRC value. The `crc` value will be modified to contain the + //! crc or 0 on error. Note: this will reset any active CRC calculation and effectively re-initializes any + //! `incrementalCrc` calculation. + //! + //! On error crc will be set to 0. + //! + //! \note The base-class implementation returns `NOT_SUPPORTED`; the CRC + //! scratch state and algorithm live on `Os::DelegateFile` (see the design + //! note above), which overrides this method. + //! + //! \param crc: value to fill + //! \return status of the CRC calculation + //! + virtual Status finalizeCrc(U32& crc); + + //! \brief provide a pointer to a file delegate object + //! + //! This function must return a pointer to a `FileInterface` object that contains the real implementation of the + //! file functions as defined by the implementor. This function must do several things to be considered correctly + //! implemented: + //! + //! 1. Assert that the supplied memory is non-null. e.g `FW_ASSERT(aligned_placement_new_memory != NULL);` + //! 2. Assert that their implementation fits within FW_HANDLE_MAX_SIZE. + //! e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage, + //! "FW_HANDLE_MAX_SIZE too small");` + //! 3. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT. + //! e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");` + //! 4. If to_copy is null, placement new their implementation into `aligned_placement_new_memory` + //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;` + //! 5. If to_copy is non-null, placement new using copy constructor their implementation into + //! `aligned_placement_new_memory` + //! e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);` + //! 6. Return the result of the placement new + //! e.g. `return interface;` + //! + //! \return result of placement new, must be equivalent to `aligned_placement_new_memory` + //! + static FileInterface* getDelegate(FileHandleStorage& aligned_placement_new_memory, + const FileInterface* to_copy = nullptr); + + protected: + //! \brief get the currently tracked open mode + //! + //! Provided for subclasses (e.g. link-time delegates) that need to replicate the mode-based guards this base + //! class already applies in its own convenience methods. + //! + //! \return the mode most recently passed to a successful `open` call, or `OPEN_NO_MODE` if not open + //! + Mode getMode() const; + + //! \brief set the tracked open mode + //! + //! Provided for subclasses (e.g. link-time delegates) whose `close` override must reset the mode tracked by + //! this base class back to `OPEN_NO_MODE`. + //! + //! \param mode: new mode to track + //! + void setMode(Mode mode); + + private: + //! \brief seed value for a fresh CRC-32 calculation + //! + //! Part of the CRC contract shared with the shadow model in the unit tests. This is a compile-time + //! constant with no per-instance storage cost, so it stays on the interface even though the CRC scratch + //! state and algorithm live on `Os::DelegateFile` (see the CRC design note above). + static const U32 INITIAL_CRC = 0xFFFFFFFF; //!< Initial value for CRC calculation + + Mode m_mode = Mode::OPEN_NO_MODE; //!< Stores mode for error checking +}; +} // namespace Os +#endif // OS_FILEINTERFACE_HPP_ diff --git a/Os/FileSystem.cpp b/Os/FileSystem.cpp deleted file mode 100644 index d82ad246911..00000000000 --- a/Os/FileSystem.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// ====================================================================== -// \title Os/FileSystem.cpp -// \brief common function implementation for Os::FileSystem -// ====================================================================== -#include -#include -#include - -namespace Os { - -FileSystem::FileSystem() : m_handle_storage(), m_delegate(*FileSystemInterface::getDelegate(m_handle_storage)) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); -} - -FileSystem::~FileSystem() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - m_delegate.~FileSystemInterface(); -} - -FileSystemHandle* FileSystem::getHandle() { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - return this->m_delegate.getHandle(); -} - -FileSystem::Status FileSystem::_removeDirectory(const char* path) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - return this->m_delegate._removeDirectory(path); -} - -FileSystem::Status FileSystem::_removeFile(const char* path) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - return this->m_delegate._removeFile(path); -} - -FileSystem::Status FileSystem::_rename(const char* sourcePath, const char* destPath) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(sourcePath != nullptr); - FW_ASSERT(destPath != nullptr); - return this->m_delegate._rename(sourcePath, destPath); -} - -FileSystem::Status FileSystem::_getPathType(const char* path, PathType& pathType) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - return this->m_delegate._getPathType(path, pathType); -} - -FileSystem::Status FileSystem::_getWorkingDirectory(char* path, FwSizeType bufferSize) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - FW_ASSERT(bufferSize > 0); // because bufferSize=0 would trigger a malloc in some implementations (e.g. Posix) - return this->m_delegate._getWorkingDirectory(path, bufferSize); -} - -FileSystem::Status FileSystem::_changeWorkingDirectory(const char* path) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - return this->m_delegate._changeWorkingDirectory(path); -} - -FileSystem::Status FileSystem::_getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) { - FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); - FW_ASSERT(path != nullptr); - return this->m_delegate._getFreeSpace(path, totalBytes, freeBytes); -} - -void FileSystem::init() { - // Force trigger on the fly singleton setup - (void)FileSystem::getSingleton(); -} - -FileSystem& FileSystem::getSingleton() { - static FileSystem s_singleton; - return s_singleton; -} - -// ------------------------------------------------------------ -// Static functions calling implementation-specific operations -// ------------------------------------------------------------ - -FileSystem::Status FileSystem::removeDirectory(const char* path) { - return FileSystem::getSingleton()._removeDirectory(path); -} - -FileSystem::Status FileSystem::removeFile(const char* path) { - return FileSystem::getSingleton()._removeFile(path); -} - -FileSystem::Status FileSystem::rename(const char* sourcePath, const char* destPath) { - return FileSystem::getSingleton()._rename(sourcePath, destPath); -} - -FileSystem::Status FileSystem::getWorkingDirectory(char* path, FwSizeType bufferSize) { - return FileSystem::getSingleton()._getWorkingDirectory(path, bufferSize); -} - -FileSystem::Status FileSystem::changeWorkingDirectory(const char* path) { - return FileSystem::getSingleton()._changeWorkingDirectory(path); -} - -FileSystem::Status FileSystem::getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) { - return FileSystem::getSingleton()._getFreeSpace(path, totalBytes, freeBytes); -} - -// ------------------------------------------------------------ -// Additional functions built on top of OS-specific operations -// ------------------------------------------------------------ - -FileSystem::Status FileSystem::createDirectory(const char* path, bool errorIfAlreadyExists) { - FW_ASSERT(path != nullptr); - Status status = Status::OP_OK; - Os::Directory dir; - // If errorIfAlreadyExists is true, use CREATE_EXCLUSIVE mode, otherwise use CREATE_IF_MISSING - Directory::OpenMode mode = - errorIfAlreadyExists ? Directory::OpenMode::CREATE_EXCLUSIVE : Directory::OpenMode::CREATE_IF_MISSING; - Directory::Status dirStatus = dir.open(path, mode); - dir.close(); - if (dirStatus != Directory::OP_OK) { - return FileSystem::handleDirectoryError(dirStatus); - } - return status; -} - -FileSystem::Status FileSystem::touch(const char* path) { - FW_ASSERT(path != nullptr); - Status status = Status::OP_OK; - Os::File file; - File::Status file_status = file.open(path, Os::File::OPEN_WRITE); - file.close(); - if (file_status != File::OP_OK) { - status = FileSystem::handleFileError(file_status); - } - return status; -} - -FileSystem::PathType FileSystem::getPathType(const char* path) { - FW_ASSERT(path != nullptr); - PathType pathType; - Status status = getSingleton()._getPathType(path, pathType); - if (status != Status::OP_OK) { - return PathType::NOT_EXIST; - } - return pathType; -} // end getPathType - -bool FileSystem::exists(const char* path) { - return FileSystem::getPathType(path) != PathType::NOT_EXIST; -} // end exists - -FileSystem::Status FileSystem::copyFile(const char* sourcePath, const char* destPath) { - FW_ASSERT(sourcePath != nullptr); - FW_ASSERT(destPath != nullptr); - Os::File source; - Os::File destination; - Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); - if (fileStatus != Os::File::OP_OK) { - return FileSystem::handleFileError(fileStatus); - } - fileStatus = destination.open(destPath, Os::File::OPEN_CREATE, Os::File::OverwriteType::OVERWRITE); - if (fileStatus != Os::File::OP_OK) { - return FileSystem::handleFileError(fileStatus); - } - - FwSizeType sourceFileSize = 0; - FileSystem::Status fs_status = FileSystem::getFileSize(sourcePath, sourceFileSize); - if (fs_status != FileSystem::Status::OP_OK) { - return fs_status; - } - - fs_status = FileSystem::copyFileData(source, destination, sourceFileSize); - - return fs_status; -} // end copyFile - -FileSystem::Status FileSystem::appendFile(const char* sourcePath, const char* destPath, bool createMissingDest) { - Os::File source; - Os::File destination; - - // If requested, check if destination file exists and exit if does not exist - const bool destExists = FileSystem::exists(destPath); - if (not createMissingDest and not destExists) { - return Status::DOESNT_EXIST; - } - - Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); - if (fileStatus != Os::File::OP_OK) { - return FileSystem::handleFileError(fileStatus); - } - fileStatus = destination.open(destPath, Os::File::OPEN_APPEND); - if (fileStatus != Os::File::OP_OK) { - return FileSystem::handleFileError(fileStatus); - } - - FileSystem::Status fs_status = FileSystem::OP_OK; - - FwSizeType sourceFileSize = 0; - fs_status = FileSystem::getFileSize(sourcePath, sourceFileSize); - if (fs_status != FileSystem::Status::OP_OK) { - return fs_status; - } - - fs_status = FileSystem::copyFileData(source, destination, sourceFileSize); - - return fs_status; -} // end appendFile - -FileSystem::Status FileSystem::moveFile(const char* source, const char* destination) { - Status status = Status::OP_OK; - - // Try to rename the file - status = FileSystem::rename(source, destination); - - // If rename fails because of cross-device rename, attempt to copy and remove instead - if (status == Status::EXDEV_ERROR) { - status = FileSystem::copyFile(source, destination); - if (status != Status::OP_OK) { - return status; - } - status = FileSystem::removeFile(source); - } - - return status; -} - -FileSystem::Status FileSystem::getFileSize(const char* path, FwSizeType& size) { - Os::File file; - Os::File::Status status = file.open(path, Os::File::OPEN_READ); - if (status != File::Status::OP_OK) { - return FileSystem::handleFileError(status); - } - status = file.size(size); - if (status != File::Status::OP_OK) { - return FileSystem::handleFileError(status); - } - return FileSystem::OP_OK; -} - -// ------------------------------------------------------------ -// Internal helper functions -// ------------------------------------------------------------ - -FileSystem::Status FileSystem::handleFileError(File::Status fileStatus) { - FileSystem::Status status = FileSystem::OTHER_ERROR; - - switch (fileStatus) { - case File::NO_SPACE: - status = FileSystem::NO_SPACE; - break; - case File::NO_PERMISSION: - status = FileSystem::NO_PERMISSION; - break; - case File::DOESNT_EXIST: - status = FileSystem::DOESNT_EXIST; - break; - default: - status = FileSystem::OTHER_ERROR; - } - return status; -} // end handleFileError - -FileSystem::Status FileSystem::handleDirectoryError(Directory::Status dirStatus) { - FileSystem::Status status = FileSystem::OTHER_ERROR; - - switch (dirStatus) { - case Directory::DOESNT_EXIST: - status = FileSystem::DOESNT_EXIST; - break; - case Directory::NO_PERMISSION: - status = FileSystem::NO_PERMISSION; - break; - case Directory::ALREADY_EXISTS: - status = FileSystem::ALREADY_EXISTS; - break; - case Directory::NOT_SUPPORTED: - status = FileSystem::NOT_SUPPORTED; - break; - default: - status = FileSystem::OTHER_ERROR; - } - return status; -} // end handleFileError - -FileSystem::Status FileSystem::copyFileData(File& source, File& destination, FwSizeType size) { - static_assert(FILE_SYSTEM_FILE_CHUNK_SIZE != 0, "FILE_SYSTEM_FILE_CHUNK_SIZE must be >0"); - U8 fileBuffer[FILE_SYSTEM_FILE_CHUNK_SIZE]; - File::Status file_status; - - FwSizeType copiedSize = 0; - FwSizeType chunkSize = FILE_SYSTEM_FILE_CHUNK_SIZE; - - // Loop up to 2 times for each by, bounded to prevent infinite loop - const FwSizeType maximum = - (size > (std::numeric_limits::max() / 2)) ? std::numeric_limits::max() : size * 2; - - // Copy the file in chunks - loop until all data is copied - FwSizeType i = 0; - for (copiedSize = 0; (copiedSize < size) && (i < maximum); i++) { - // chunkSize is FILE_SYSTEM_FILE_CHUNK_SIZE unless size-copiedSize is less than that - // in which case chunkSize is size-copiedSize, ensuring the last chunk reads the remaining data - // static_cast needed to avoid ODR linker issue with static constexpr passed by reference to std::min - chunkSize = std::min(static_cast(FILE_SYSTEM_FILE_CHUNK_SIZE), size - copiedSize); - FwSizeType readSize = chunkSize; - file_status = source.read(fileBuffer, readSize, Os::File::WaitType::WAIT); - if (file_status != File::OP_OK) { - return FileSystem::handleFileError(file_status); - } - // A zero-byte read means the source ended before the expected size (e.g. truncated mid-copy) - if (readSize == 0) { - break; - } - FwSizeType writeSize = readSize; - file_status = destination.write(fileBuffer, writeSize, Os::File::WaitType::WAIT); - if (file_status != File::OP_OK) { - return FileSystem::handleFileError(file_status); - } - // A short write means the destination did not receive all the data (e.g. device full) - if (writeSize != readSize) { - return FileSystem::OTHER_ERROR; - } - copiedSize += writeSize; - } - - // A copy that did not transfer the full expected size must not report success - if (copiedSize != size) { - return FileSystem::OTHER_ERROR; - } - - return FileSystem::OP_OK; -} // end copyFileData - -} // namespace Os diff --git a/Os/FileSystem.hpp b/Os/FileSystem.hpp index 581bbadee48..546db8bb11d 100644 --- a/Os/FileSystem.hpp +++ b/Os/FileSystem.hpp @@ -1,399 +1,49 @@ // ====================================================================== // \title Os/FileSystem.hpp -// \brief Os::FileSystem interface definition +// \brief public Os::FileSystem interface and alias +// +// This header aggregates all definitions needed to use Os::FileSystem: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::FileSystem type +// alias by forward-declaring a link-time delegate (e.g. DelegateFileSystem) +// or directly aliasing a concrete implementation. +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/FileSystemInterface.hpp, which this header includes first. +// +// 2. Os/FileSystemInterface.hpp (IF) includes CFG first (so the +// Os::FileSystem alias is available), then defines FileSystemHandle +// and FileSystemInterface. +// +// 3. OS_FILE_SYSTEM_HEADER (IMPL) is defined by CFG and points to the +// concrete implementation header. If using delegation, this points to +// Os/DelegateFileSystem.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation. +// +// FileSystemInterface.hpp must precede OS_FILE_SYSTEM_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== #ifndef _OS_FILESYSTEM_HPP_ #define _OS_FILESYSTEM_HPP_ -#include -#include -#include -#include +#include "Os/Directory.hpp" +#include "Os/File.hpp" +#include "Os/FileSystemInterface.hpp" +// A project config/OsSelection.hpp that predates the FileSystem selection defines neither the Os::FileSystem +// alias nor OS_FILE_SYSTEM_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_FILE_SYSTEM_HEADER +#include "Os/DelegateFileSystem.hpp" namespace Os { - -struct FileSystemHandle {}; - -class FileSystemInterface { - public: - // Size of file chunks to use for file system operations (e.g. copyFile) - static constexpr FwSizeType FILE_SYSTEM_FILE_CHUNK_SIZE = FW_FILE_CHUNK_SIZE; //!< Size of file system chunk - - enum Status { - OP_OK, //!< Operation was successful - ALREADY_EXISTS, //!< File already exists - NO_SPACE, //!< No space left - NO_PERMISSION, //!< No permission to write - NOT_DIR, //!< Path is not a directory - IS_DIR, //!< Path is a directory - NOT_EMPTY, //!< directory is not empty - INVALID_PATH, //!< Path is too long, too many sym links, etc. - DOESNT_EXIST, //!< Path doesn't exist - FILE_LIMIT, //!< Too many files or links - BUSY, //!< Operand is in use by the system or by a process - NO_MORE_FILES, //!< Directory stream has no more files - BUFFER_TOO_SMALL, //!< Buffer size is too small to hold full path (for getWorkingDirectory) - EXDEV_ERROR, // Operation not supported across devices (e.g. rename) - OVERFLOW_ERROR, // Operation failed due to overflow in calculation of the result - NOT_SUPPORTED, //!< Operation is not supported by the current implementation - OTHER_ERROR, //!< other OS-specific error - }; - - enum PathType { - FILE, //!< Path is a file - DIRECTORY, //!< Path is a directory - OTHER, //!< Path is not a file or directory, e.g. a socket - NOT_EXIST, //!< Path does not exist - }; - - //! \brief default constructor - FileSystemInterface() = default; - - //! \brief default virtual destructor - virtual ~FileSystemInterface() = default; - - //! \brief copy constructor is forbidden - FileSystemInterface(const FileSystemInterface& other) = delete; - - //! \brief assignment operator is forbidden - FileSystemInterface& operator=(const FileSystemInterface& other) = delete; // NO_CODESONAR (cpp:S3657) - - //! \brief return the underlying FileSystem handle (implementation specific) - //! \return internal FileSystem handle representation - virtual FileSystemHandle* getHandle() = 0; - - //! \brief provide a pointer to a FileSystem delegate object - static FileSystemInterface* getDelegate(FileSystemHandleStorage& aligned_new_memory); - - // ------------------------------------------------------------------ - // FileSystem operations to be implemented by an OSAL implementation - // ------------------------------------------------------------------ - // These functions are to be overridden in each OS implementation - // See an example in in Os/Posix/FileSystem.hpp - - //! \brief Remove a directory at the specified path - //! \param path The path of the directory to remove - //! \return Status of the operation - virtual Status _removeDirectory(const char* path) = 0; - - //! \brief Remove a file at the specified path - //! \param path The path of the file to remove - //! \return Status of the operation - virtual Status _removeFile(const char* path) = 0; - - //! \brief Rename (or move) a file from source to destination - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \return Status of the operation - virtual Status _rename(const char* sourcePath, const char* destPath) = 0; - - //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path - //! \param path The path on the filesystem to query - //! \param totalBytes Reference to store the total bytes on the filesystem - //! \param freeBytes Reference to store the free bytes on the filesystem - //! \return Status of the operation - virtual Status _getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) = 0; - - //! \brief Get the type of the path (file, directory, etc.) - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path to check - //! \param pathType Reference to store the path type - //! \return Status of the operation - virtual Status _getPathType(const char* path, PathType& pathType) = 0; - - //! \brief Get the current working directory - //! \param path Buffer to store the current working directory path - //! \param bufferSize Size of the buffer - //! \return Status of the operation - virtual Status _getWorkingDirectory(char* path, FwSizeType bufferSize) = 0; - - //! \brief Change the current working directory to the specified path - //! \param path The path of the new working directory - //! \return Status of the operation - virtual Status _changeWorkingDirectory(const char* path) = 0; -}; - -//! \brief FileSystem class -//! -//! This class provides a common interface for file system operations. -//! This class uses the singleton pattern and should be accessed through -//! its static functions, for example using `Os::FileSystem::removeFile(path)`. -class FileSystem final : public FileSystemInterface { - private: - FileSystem(); //!< Constructor (private because singleton pattern) - public: - ~FileSystem() final; //!< Destructor - - //! \brief return the underlying FileSystem handle (implementation specific) - //! \return internal FileSystem handle representation - FileSystemHandle* getHandle() override; - - // ------------------------------------------------------------ - // Implementation-specific FileSystem member functions - // ------------------------------------------------------------ - - //! \brief Remove a directory at the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the directory to remove - //! \return Status of the operation - Status _removeDirectory(const char* path) override; - - //! \brief Remove a file at the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the file to remove - //! \return Status of the operation - Status _removeFile(const char* path) override; - - //! \brief Rename a file from source to destination - //! - //! If the rename fails due to a cross-device operation, this function should return EXDEV_ERROR - //! and moveFile can be used instead to force a copy-and-remove. - //! - //! It is invalid to pass `nullptr` as sourcePath or destPath. - //! - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \return Status of the operation - Status _rename(const char* sourcePath, const char* destPath) override; - - //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path on the filesystem to query - //! \param totalBytes Reference to store the total bytes on the filesystem - //! \param freeBytes Reference to store the free bytes on the filesystem - //! \return Status of the operation - Status _getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) override; - - //! \brief Get the current working directory - //! - //! Writes the current working directory path to the provided buffer of size bufferSize. - //! If the buffer is too small to hold the full path, the function will return BUFFER_TOO_SMALL. - //! - //! It is invalid to pass `nullptr` as the path. - //! It is invalid to pass a bufferSize of 0. - //! - //! \param path Buffer to store the current working directory path - //! \param bufferSize Size of the buffer - //! \return Status of the operation - Status _getWorkingDirectory(char* path, FwSizeType bufferSize) override; - - //! \brief Change the current working directory to the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the new working directory - //! \return Status of the operation - Status _changeWorkingDirectory(const char* path) override; - - //! \brief Get the type of the path (file, directory, etc.) - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path to check - //! \param pathType Reference to store the path type - //! \return Status of the operation - Status _getPathType(const char* path, PathType& pathType) override; - - // ------------------------------------------------------------ - // Implementation-specific FileSystem static functions - // ------------------------------------------------------------ - // These are static variants that are exposed to the user, and call the above member functions - - //! \brief Remove a directory at the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the directory to remove - //! \return Status of the operation - static Status removeDirectory(const char* path); - - //! \brief Remove a file at the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the file to remove - //! \return Status of the operation - static Status removeFile(const char* path); - - //! \brief Rename a file from source to destination - //! - //! If the rename fails due to a cross-device operation, this function should return EXDEV_ERROR - //! and moveFile can be used instead to force a copy-and-remove. - //! - //! It is invalid to pass `nullptr` as sourcePath or destPath. - //! - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \return Status of the operation - static Status rename(const char* sourcePath, const char* destPath); - - //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path on the filesystem to query - //! \param totalBytes Reference to store the total bytes on the filesystem - //! \param freeBytes Reference to store the free bytes on the filesystem - //! \return Status of the operation - static Status getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes); - - //! \brief Get the current working directory - //! - //! Writes the current working directory path to the provided buffer of size bufferSize. - //! If the buffer is too small to hold the full path, the function will return BUFFER_TOO_SMALL. - //! - //! It is invalid to pass `nullptr` as the path. - //! It is invalid to pass a bufferSize of 0. - //! - //! \param path Buffer to store the current working directory path - //! \param bufferSize Size of the buffer - //! \return Status of the operation - static Status getWorkingDirectory(char* path, FwSizeType bufferSize); - - //! \brief Change the current working directory to the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the new working directory - //! \return Status of the operation - static Status changeWorkingDirectory(const char* path); - - // ------------------------------------------------------------ - // Additional functions built on top of OS-specific operations - // ------------------------------------------------------------ - - //! \brief Return true if the path exists, false otherwise - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path to check for existence - //! \return True if the path exists, false otherwise - static bool exists(const char* path); - - //! \brief Return the type of the path (file, directory, or doesn't exist) - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path to check for existence - //! \return PathType enum representing the type of the path (FILE, DIRECTORY, NOT_EXIST) - static PathType getPathType(const char* path); - - //! \brief Touch a file at the specified path, creating it if it doesn't exist - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the file to touch - //! \return Status of the operation - static Status touch(const char* path); - - //! \brief Create a new directory at the specified path. - //! - //! The optional errorIfAlreadyExists (default=false) parameter can be set to true - //! to return an error status if the directory already exists. - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path where the new directory will be created - //! \param errorIfAlreadyExists If true, returns an error if the directory already exists - //! \return Status of the operation - static Status createDirectory(const char* path, bool errorIfAlreadyExists = false); - - //! \brief Append the source file to the destination file - //! - //! This function opens both files, and iteratively reads the source by chunks and writes - //! chunks to the destination. - //! If the destination file does not exist and createMissingDest is true, a new file is created. - //! - //! It is invalid to pass `nullptr` as either the source or destination path. - //! - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \param createMissingDest If true, creates a new file if the destination doesn't exist - //! \return Status of the operation - static Status appendFile(const char* sourcePath, const char* destPath, bool createMissingDest = false); - - //! \brief Copy a file from the source path to the destination path - //! - //! This function opens both files, and iteratively reads the source by chunks and writes - //! chunks to the destination. - //! - //! It is invalid to pass `nullptr` as either the source or destination path. - //! - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \return Status of the operation - static Status copyFile(const char* sourcePath, const char* destPath); - - //! \brief Move a file from sourcePath to destPath - //! - //! This is done by first trying to rename, and if renaming fails, - //! copy it and then remove the original - //! - //! It is invalid to pass `nullptr` as either the source or destination path. - //! - //! \param sourcePath The path of the source file - //! \param destPath The path of the destination file - //! \return Status of the operation - static Status moveFile(const char* sourcePath, const char* destPath); - - //! \brief Get the size of the file (in bytes) at the specified path - //! - //! It is invalid to pass `nullptr` as the path. - //! - //! \param path The path of the file - //! \param size Reference to store the size of the file - //! \return Status of the operation - static Status getFileSize(const char* path, FwSizeType& size); - - public: - //! \brief initialize singleton - static void init(); - - //! \brief get a reference to singleton - //! \return reference to singleton - static FileSystem& getSingleton(); - - private: - // ------------------------------------------------------------ - // Internal helper functions - // ------------------------------------------------------------ - - //! \brief Convert a File::Status to a FileSystem::Status - static Status handleFileError(File::Status fileStatus); - - //! \brief Convert a Directory::Status to a FileSystem::Status - static Status handleDirectoryError(Directory::Status dirStatus); - - //! \brief A helper function that writes all the file information in the source - //! file to the destination file (replaces/appends to end/etc. depending - //! on destination file mode). - //! - //! Files must already be open and will remain open after this function - //! completes. - //! - //! @param source File to copy data from - //! @param destination File to copy data to - //! @param size The number of bytes to copy - static Status copyFileData(File& source, File& destination, FwSizeType size); - - private: - // This section is used to store the implementation-defined FileSystem handle. To Os::FileSystem and fprime, this - // type is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle - // in the byte-array here and set `handle` to that address for storage. - - alignas(FW_HANDLE_ALIGNMENT) FileSystemHandleStorage m_handle_storage; //!< FileSystem handle storage - FileSystemInterface& m_delegate; -}; - -} // namespace Os +using FileSystem = DelegateFileSystem; +} +#else +#include OS_FILE_SYSTEM_HEADER +#endif #endif diff --git a/Os/FileSystemInterface.cpp b/Os/FileSystemInterface.cpp new file mode 100644 index 00000000000..eceb24a1999 --- /dev/null +++ b/Os/FileSystemInterface.cpp @@ -0,0 +1,272 @@ +// ====================================================================== +// \title Os/FileSystemInterface.cpp +// \brief common convenience-wrapper implementation for Os::FileSystem +// ====================================================================== +#include +#include +#include +#include +#include + +namespace Os { + +namespace { + +FileSystemInterface::Status handleFileError(File::Status fileStatus) { + FileSystemInterface::Status status = FileSystemInterface::OTHER_ERROR; + + switch (fileStatus) { + case File::NO_SPACE: + status = FileSystemInterface::NO_SPACE; + break; + case File::NO_PERMISSION: + status = FileSystemInterface::NO_PERMISSION; + break; + case File::DOESNT_EXIST: + status = FileSystemInterface::DOESNT_EXIST; + break; + default: + status = FileSystemInterface::OTHER_ERROR; + } + return status; +} // end handleFileError + +FileSystemInterface::Status handleDirectoryError(Directory::Status dirStatus) { + FileSystemInterface::Status status = FileSystemInterface::OTHER_ERROR; + + switch (dirStatus) { + case Directory::DOESNT_EXIST: + status = FileSystemInterface::DOESNT_EXIST; + break; + case Directory::NO_PERMISSION: + status = FileSystemInterface::NO_PERMISSION; + break; + case Directory::ALREADY_EXISTS: + status = FileSystemInterface::ALREADY_EXISTS; + break; + case Directory::NOT_SUPPORTED: + status = FileSystemInterface::NOT_SUPPORTED; + break; + default: + status = FileSystemInterface::OTHER_ERROR; + } + return status; +} // end handleDirectoryError + +FileSystemInterface::Status copyFileData(File& source, File& destination, FwSizeType size) { + static_assert(FileSystemInterface::FILE_SYSTEM_FILE_CHUNK_SIZE != 0, "FILE_SYSTEM_FILE_CHUNK_SIZE must be >0"); + U8 fileBuffer[FileSystemInterface::FILE_SYSTEM_FILE_CHUNK_SIZE]; + File::Status file_status; + + FwSizeType copiedSize = 0; + FwSizeType chunkSize = FileSystemInterface::FILE_SYSTEM_FILE_CHUNK_SIZE; + + // Loop up to 2 times for each by, bounded to prevent infinite loop + const FwSizeType maximum = + (size > (std::numeric_limits::max() / 2)) ? std::numeric_limits::max() : size * 2; + + // Copy the file in chunks - loop until all data is copied + FwSizeType i = 0; + for (copiedSize = 0; (copiedSize < size) && (i < maximum); i++) { + // chunkSize is FILE_SYSTEM_FILE_CHUNK_SIZE unless size-copiedSize is less than that + // in which case chunkSize is size-copiedSize, ensuring the last chunk reads the remaining data + // static_cast needed to avoid ODR linker issue with static constexpr passed by reference to std::min + chunkSize = + std::min(static_cast(FileSystemInterface::FILE_SYSTEM_FILE_CHUNK_SIZE), size - copiedSize); + FwSizeType readSize = chunkSize; + file_status = source.read(fileBuffer, readSize, Os::File::WaitType::WAIT); + if (file_status != File::OP_OK) { + return handleFileError(file_status); + } + // A zero-byte read means the source ended before the expected size (e.g. truncated mid-copy) + if (readSize == 0) { + break; + } + FwSizeType writeSize = readSize; + file_status = destination.write(fileBuffer, writeSize, Os::File::WaitType::WAIT); + if (file_status != File::OP_OK) { + return handleFileError(file_status); + } + // A short write means the destination did not receive all the data (e.g. device full) + if (writeSize != readSize) { + return FileSystemInterface::OTHER_ERROR; + } + copiedSize += writeSize; + } + + // A copy that did not transfer the full expected size must not report success + if (copiedSize != size) { + return FileSystemInterface::OTHER_ERROR; + } + + return FileSystemInterface::OP_OK; +} // end copyFileData + +} // namespace + +// ------------------------------------------------------------ +// Convenience static wrappers built directly on the virtual instance API +// ------------------------------------------------------------ + +FileSystemInterface::Status FileSystemInterface::removeDirectory(const char* path) { + return Os::FileSystem::getSingleton()._removeDirectory(path); +} + +FileSystemInterface::Status FileSystemInterface::removeFile(const char* path) { + return Os::FileSystem::getSingleton()._removeFile(path); +} + +FileSystemInterface::Status FileSystemInterface::rename(const char* sourcePath, const char* destPath) { + return Os::FileSystem::getSingleton()._rename(sourcePath, destPath); +} + +FileSystemInterface::Status FileSystemInterface::getWorkingDirectory(char* path, FwSizeType bufferSize) { + return Os::FileSystem::getSingleton()._getWorkingDirectory(path, bufferSize); +} + +FileSystemInterface::Status FileSystemInterface::changeWorkingDirectory(const char* path) { + return Os::FileSystem::getSingleton()._changeWorkingDirectory(path); +} + +FileSystemInterface::Status FileSystemInterface::getFreeSpace(const char* path, + FwSizeType& totalBytes, + FwSizeType& freeBytes) { + return Os::FileSystem::getSingleton()._getFreeSpace(path, totalBytes, freeBytes); +} + +// ------------------------------------------------------------ +// Additional functions built on top of OS-specific operations +// ------------------------------------------------------------ + +FileSystemInterface::Status FileSystemInterface::createDirectory(const char* path, bool errorIfAlreadyExists) { + FW_ASSERT(path != nullptr); + Status status = Status::OP_OK; + Os::Directory dir; + // If errorIfAlreadyExists is true, use CREATE_EXCLUSIVE mode, otherwise use CREATE_IF_MISSING + Directory::OpenMode mode = + errorIfAlreadyExists ? Directory::OpenMode::CREATE_EXCLUSIVE : Directory::OpenMode::CREATE_IF_MISSING; + Directory::Status dirStatus = dir.open(path, mode); + dir.close(); + if (dirStatus != Directory::OP_OK) { + return handleDirectoryError(dirStatus); + } + return status; +} + +FileSystemInterface::Status FileSystemInterface::touch(const char* path) { + FW_ASSERT(path != nullptr); + Status status = Status::OP_OK; + Os::File file; + File::Status file_status = file.open(path, Os::File::OPEN_WRITE); + file.close(); + if (file_status != File::OP_OK) { + status = handleFileError(file_status); + } + return status; +} + +FileSystemInterface::PathType FileSystemInterface::getPathType(const char* path) { + FW_ASSERT(path != nullptr); + PathType pathType; + Status status = Os::FileSystem::getSingleton()._getPathType(path, pathType); + if (status != Status::OP_OK) { + return PathType::NOT_EXIST; + } + return pathType; +} // end getPathType + +bool FileSystemInterface::exists(const char* path) { + return FileSystemInterface::getPathType(path) != PathType::NOT_EXIST; +} // end exists + +FileSystemInterface::Status FileSystemInterface::copyFile(const char* sourcePath, const char* destPath) { + FW_ASSERT(sourcePath != nullptr); + FW_ASSERT(destPath != nullptr); + Os::File source; + Os::File destination; + Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); + if (fileStatus != Os::File::OP_OK) { + return handleFileError(fileStatus); + } + fileStatus = destination.open(destPath, Os::File::OPEN_CREATE, Os::File::OverwriteType::OVERWRITE); + if (fileStatus != Os::File::OP_OK) { + return handleFileError(fileStatus); + } + + FwSizeType sourceFileSize = 0; + FileSystemInterface::Status fs_status = FileSystemInterface::getFileSize(sourcePath, sourceFileSize); + if (fs_status != FileSystemInterface::Status::OP_OK) { + return fs_status; + } + + fs_status = copyFileData(source, destination, sourceFileSize); + + return fs_status; +} // end copyFile + +FileSystemInterface::Status FileSystemInterface::appendFile(const char* sourcePath, + const char* destPath, + bool createMissingDest) { + Os::File source; + Os::File destination; + + // If requested, check if destination file exists and exit if does not exist + const bool destExists = FileSystemInterface::exists(destPath); + if (not createMissingDest and not destExists) { + return Status::DOESNT_EXIST; + } + + Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); + if (fileStatus != Os::File::OP_OK) { + return handleFileError(fileStatus); + } + fileStatus = destination.open(destPath, Os::File::OPEN_APPEND); + if (fileStatus != Os::File::OP_OK) { + return handleFileError(fileStatus); + } + + FileSystemInterface::Status fs_status = FileSystemInterface::OP_OK; + + FwSizeType sourceFileSize = 0; + fs_status = FileSystemInterface::getFileSize(sourcePath, sourceFileSize); + if (fs_status != FileSystemInterface::Status::OP_OK) { + return fs_status; + } + + fs_status = copyFileData(source, destination, sourceFileSize); + + return fs_status; +} // end appendFile + +FileSystemInterface::Status FileSystemInterface::moveFile(const char* source, const char* destination) { + Status status = Status::OP_OK; + + // Try to rename the file + status = FileSystemInterface::rename(source, destination); + + // If rename fails because of cross-device rename, attempt to copy and remove instead + if (status == Status::EXDEV_ERROR) { + status = FileSystemInterface::copyFile(source, destination); + if (status != Status::OP_OK) { + return status; + } + status = FileSystemInterface::removeFile(source); + } + + return status; +} + +FileSystemInterface::Status FileSystemInterface::getFileSize(const char* path, FwSizeType& size) { + Os::File file; + Os::File::Status status = file.open(path, Os::File::OPEN_READ); + if (status != File::Status::OP_OK) { + return handleFileError(status); + } + status = file.size(size); + if (status != File::Status::OP_OK) { + return handleFileError(status); + } + return FileSystemInterface::OP_OK; +} + +} // namespace Os diff --git a/Os/FileSystemInterface.hpp b/Os/FileSystemInterface.hpp new file mode 100644 index 00000000000..4fc9b5d0663 --- /dev/null +++ b/Os/FileSystemInterface.hpp @@ -0,0 +1,226 @@ +// ====================================================================== +// \title Os/FileSystemInterface.hpp +// \brief Os::FileSystemHandle and Os::FileSystemInterface definitions +// ====================================================================== + +#ifndef OS_FILESYSTEMINTERFACE_HPP_ +#define OS_FILESYSTEMINTERFACE_HPP_ + +#include +#include +#include "config/OsSelection.hpp" + +namespace Os { + +struct FileSystemHandle {}; + +class FileSystemInterface { + public: + // Size of file chunks to use for file system operations (e.g. copyFile) + static constexpr FwSizeType FILE_SYSTEM_FILE_CHUNK_SIZE = FW_FILE_CHUNK_SIZE; //!< Size of file system chunk + + enum Status { + OP_OK, //!< Operation was successful + ALREADY_EXISTS, //!< File already exists + NO_SPACE, //!< No space left + NO_PERMISSION, //!< No permission to write + NOT_DIR, //!< Path is not a directory + IS_DIR, //!< Path is a directory + NOT_EMPTY, //!< directory is not empty + INVALID_PATH, //!< Path is too long, too many sym links, etc. + DOESNT_EXIST, //!< Path doesn't exist + FILE_LIMIT, //!< Too many files or links + BUSY, //!< Operand is in use by the system or by a process + NO_MORE_FILES, //!< Directory stream has no more files + BUFFER_TOO_SMALL, //!< Buffer size is too small to hold full path (for getWorkingDirectory) + EXDEV_ERROR, // Operation not supported across devices (e.g. rename) + OVERFLOW_ERROR, // Operation failed due to overflow in calculation of the result + NOT_SUPPORTED, //!< Operation is not supported by the current implementation + OTHER_ERROR, //!< other OS-specific error + }; + + enum PathType { + FILE, //!< Path is a file + DIRECTORY, //!< Path is a directory + OTHER, //!< Path is not a file or directory, e.g. a socket + NOT_EXIST, //!< Path does not exist + }; + + //! \brief default constructor + FileSystemInterface() = default; + + //! \brief default virtual destructor + virtual ~FileSystemInterface() = default; + + //! \brief copy constructor is forbidden + FileSystemInterface(const FileSystemInterface& other) = delete; + + //! \brief assignment operator is forbidden + FileSystemInterface& operator=(const FileSystemInterface& other) = delete; // NO_CODESONAR (cpp:S3657) + + //! \brief return the underlying FileSystem handle (implementation specific) + //! \return internal FileSystem handle representation + virtual FileSystemHandle* getHandle() = 0; + + //! \brief provide a pointer to a FileSystem delegate object + static FileSystemInterface* getDelegate(FileSystemHandleStorage& aligned_new_memory); + + // ------------------------------------------------------------------ + // FileSystem operations to be implemented by an OSAL implementation + // ------------------------------------------------------------------ + // These functions are to be overridden in each OS implementation + // See an example in in Os/Posix/FileSystem.hpp + + //! \brief Remove a directory at the specified path + //! \param path The path of the directory to remove + //! \return Status of the operation + virtual Status _removeDirectory(const char* path) = 0; + + //! \brief Remove a file at the specified path + //! \param path The path of the file to remove + //! \return Status of the operation + virtual Status _removeFile(const char* path) = 0; + + //! \brief Rename (or move) a file from source to destination + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \return Status of the operation + virtual Status _rename(const char* sourcePath, const char* destPath) = 0; + + //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path + //! \param path The path on the filesystem to query + //! \param totalBytes Reference to store the total bytes on the filesystem + //! \param freeBytes Reference to store the free bytes on the filesystem + //! \return Status of the operation + virtual Status _getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) = 0; + + //! \brief Get the type of the path (file, directory, etc.) + //! + //! It is invalid to pass `nullptr` as the path. + //! + //! \param path The path to check + //! \param pathType Reference to store the path type + //! \return Status of the operation + virtual Status _getPathType(const char* path, PathType& pathType) = 0; + + //! \brief Get the current working directory + //! \param path Buffer to store the current working directory path + //! \param bufferSize Size of the buffer + //! \return Status of the operation + virtual Status _getWorkingDirectory(char* path, FwSizeType bufferSize) = 0; + + //! \brief Change the current working directory to the specified path + //! \param path The path of the new working directory + //! \return Status of the operation + virtual Status _changeWorkingDirectory(const char* path) = 0; + + // ------------------------------------------------------------------ + // Convenience static wrappers + // ------------------------------------------------------------------ + // These are implemented in terms of the virtual instance functions above, + // and are inherited (via ordinary class-scope lookup) by every concrete + // Os::FileSystem backend (e.g. Os::DelegateFileSystem, Os::Stub::FileSystem::StubFileSystem), + // regardless of whether that backend is a link-time delegate or a direct + // compile-time alias. + + //! \brief Remove a directory at the specified path + //! \param path The path of the directory to remove + //! \return Status of the operation + static Status removeDirectory(const char* path); + + //! \brief Remove a file at the specified path + //! \param path The path of the file to remove + //! \return Status of the operation + static Status removeFile(const char* path); + + //! \brief Rename (or move) a file from source to destination + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \return Status of the operation + static Status rename(const char* sourcePath, const char* destPath); + + //! \brief Get filesystem free and total space in bytes on the filesystem containing the specified path + //! \param path The path on the filesystem to query + //! \param totalBytes Reference to store the total bytes on the filesystem + //! \param freeBytes Reference to store the free bytes on the filesystem + //! \return Status of the operation + static Status getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes); + + //! \brief Get the current working directory + //! \param path Buffer to store the current working directory path + //! \param bufferSize Size of the buffer + //! \return Status of the operation + static Status getWorkingDirectory(char* path, FwSizeType bufferSize); + + //! \brief Change the current working directory to the specified path + //! \param path The path of the new working directory + //! \return Status of the operation + static Status changeWorkingDirectory(const char* path); + + //! \brief Return true if the path exists, false otherwise + //! \param path The path to check for existence + //! \return True if the path exists, false otherwise + static bool exists(const char* path); + + //! \brief Return the type of the path (file, directory, or doesn't exist) + //! \param path The path to check for existence + //! \return PathType enum representing the type of the path (FILE, DIRECTORY, NOT_EXIST) + static PathType getPathType(const char* path); + + //! \brief Touch a file at the specified path, creating it if it doesn't exist + //! \param path The path of the file to touch + //! \return Status of the operation + static Status touch(const char* path); + + //! \brief Create a new directory at the specified path. + //! + //! The optional errorIfAlreadyExists (default=false) parameter can be set to true + //! to return an error status if the directory already exists. + //! + //! \param path The path where the new directory will be created + //! \param errorIfAlreadyExists If true, returns an error if the directory already exists + //! \return Status of the operation + static Status createDirectory(const char* path, bool errorIfAlreadyExists = false); + + //! \brief Append the source file to the destination file + //! + //! This function opens both files, and iteratively reads the source by chunks and writes + //! chunks to the destination. + //! If the destination file does not exist and createMissingDest is true, a new file is created. + //! + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \param createMissingDest If true, creates a new file if the destination doesn't exist + //! \return Status of the operation + static Status appendFile(const char* sourcePath, const char* destPath, bool createMissingDest = false); + + //! \brief Copy a file from the source path to the destination path + //! + //! This function opens both files, and iteratively reads the source by chunks and writes + //! chunks to the destination. + //! + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \return Status of the operation + static Status copyFile(const char* sourcePath, const char* destPath); + + //! \brief Move a file from sourcePath to destPath + //! + //! This is done by first trying to rename, and if renaming fails, + //! copy it and then remove the original + //! + //! \param sourcePath The path of the source file + //! \param destPath The path of the destination file + //! \return Status of the operation + static Status moveFile(const char* sourcePath, const char* destPath); + + //! \brief Get the size of the file (in bytes) at the specified path + //! \param path The path of the file + //! \param size Reference to store the size of the file + //! \return Status of the operation + static Status getFileSize(const char* path, FwSizeType& size); +}; + +} // namespace Os + +#endif // OS_FILESYSTEMINTERFACE_HPP_ diff --git a/Os/Generic/PriorityMemQueue.cpp b/Os/Generic/PriorityMemQueue.cpp index 11cbdde7d54..aac2117f695 100644 --- a/Os/Generic/PriorityMemQueue.cpp +++ b/Os/Generic/PriorityMemQueue.cpp @@ -76,7 +76,7 @@ void PriorityMemQueueHandle::init() { // Initialize the not-empty semaphore with count 0 (queue starts empty) if (this->m_notEmptySem != nullptr) { - this->m_notEmptySem->~CountingSemaphore(); + this->m_notEmptySem->Os::CountingSemaphore::~CountingSemaphore(); this->m_notEmptySem = nullptr; } @@ -556,7 +556,7 @@ void PriorityMemQueue::teardownInternal() { if (this->m_handle.m_notEmptySem != nullptr) { Fw::MemAllocator& allocator = this->getAllocator(); FW_ASSERT(this->m_handle.m_allocatorId != 0 || this->m_handle.m_notEmptySem != nullptr, this->m_handle.m_id); - this->m_handle.m_notEmptySem->~CountingSemaphore(); + this->m_handle.m_notEmptySem->Os::CountingSemaphore::~CountingSemaphore(); allocator.deallocate(this->m_handle.m_allocatorId, this->m_handle.m_notEmptySem); this->m_handle.m_notEmptySem = nullptr; } diff --git a/Os/Generic/Types/AtomicQueue.cpp b/Os/Generic/Types/AtomicQueue.cpp index 12322ec1ee4..f4bb2eefad8 100644 --- a/Os/Generic/Types/AtomicQueue.cpp +++ b/Os/Generic/Types/AtomicQueue.cpp @@ -111,7 +111,7 @@ void AtomicQueue::teardown() { FW_ASSERT(this->m_allocator != nullptr, 0); // Call destructor - this->m_notFullSem->~CountingSemaphore(); + this->m_notFullSem->Os::CountingSemaphore::~CountingSemaphore(); // Deallocate memory this->m_allocator->deallocate(this->m_allocatorId, this->m_notFullSem); diff --git a/Os/Memory.hpp b/Os/Memory.hpp index ce54c86955a..e009c19ada9 100644 --- a/Os/Memory.hpp +++ b/Os/Memory.hpp @@ -1,110 +1,45 @@ // ====================================================================== // \title Os/Memory.hpp -// \brief common function definitions for Os::Memory +// \brief public Os::Memory interface and alias +// +// This header aggregates all definitions needed to use Os::Memory: +// the interface, the configured alias, and the concrete delegate type. +// +// WARNING — include order is load-bearing. Do not reorder. +// +// The dependency constraints are: +// +// 1. config/OsSelection.hpp (CFG) defines the Os::Memory type alias by +// forward-declaring a link-time delegate (e.g. DelegateMemory) or directly +// aliasing a concrete implementation. +// Must not include Os OSAL headers (they aren't yet defined). +// Included by Os/MemoryInterface.hpp, which this header includes first. +// +// 2. Os/MemoryInterface.hpp (IF) includes CFG first (so the Os::Memory alias is +// available), then defines MemoryHandle and MemoryInterface. +// +// 3. OS_MEMORY_HEADER (IMPL) is defined by CFG and points to the concrete +// implementation header. If using delegation, this points to +// Os/DelegateMemory.hpp. If using compile-time selection, it points +// directly to a platform-specific implementation. +// +// MemoryInterface.hpp must precede OS_MEMORY_HEADER here, +// and CFG must never include either of them (that would form a cycle). // ====================================================================== -#include "Os/Os.hpp" +#ifndef Os_Memory_hpp +#define Os_Memory_hpp -#ifndef OS_MEMORY_HPP_ -#define OS_MEMORY_HPP_ +#include "Os/MemoryInterface.hpp" +// A project config/OsSelection.hpp that predates the Memory selection defines neither the Os::Memory +// alias nor OS_MEMORY_HEADER; fall back to the link-time delegate so such projects keep building. +#ifndef OS_MEMORY_HEADER +#include "Os/DelegateMemory.hpp" namespace Os { +using Memory = DelegateMemory; +} +#else +#include OS_MEMORY_HEADER +#endif -//! \brief Memory variable handle parent -class MemoryHandle {}; - -//! \brief interface for memory implementation -class MemoryInterface { - public: - using Status = Os::Generic::Status; - using Usage = Os::Generic::UsedTotal; - - //! Default constructor - MemoryInterface() = default; - //! Default destructor - virtual ~MemoryInterface() = default; - - //! \brief copy constructor is forbidden - MemoryInterface(const MemoryInterface& other) = delete; // NO_CODESONAR (cpp:S3657) - - //! \brief assignment operator is forbidden - virtual MemoryInterface& operator=(const MemoryInterface& other) = delete; - - //! \brief get system memory usage - //! - //! \param memory_usage: (output) data structure used to store memory usage - //! \return: ERROR when error occurs, OK otherwise. - virtual Status _getUsage(Usage& memory_usage) = 0; - - //! \brief return the underlying memory handle (implementation specific). - //! \return internal task handle representation - virtual MemoryHandle* getHandle() = 0; - - //! \brief provide a pointer to a Mutex delegate object - static MemoryInterface* getDelegate(MemoryHandleStorage& aligned_new_memory); -}; - -//! \brief memory implementation -class Memory final : public MemoryInterface { - public: - //! \brief default constructor - Memory(); - - //! \brief default virtual destructor - ~Memory() final; - - //! \brief copy constructor is forbidden - Memory(const MemoryInterface& other) = delete; - - //! \brief copy constructor is forbidden - Memory(const MemoryInterface* other) = delete; - - //! \brief assignment operator is forbidden - MemoryInterface& operator=(const MemoryInterface& other) override = delete; - - //----------------------------------------------------------------------------- - // Interface methods - //----------------------------------------------------------------------------- - public: - //! \brief initialize the singleton - static void init(); - - //! \brief return singleton - static Memory& getSingleton(); - - //----------------------------------------------------------------------------- - // Delegating methods - //----------------------------------------------------------------------------- - - //! \brief get system memory usage - //! - //! This method delegates to the underlying implementation. - //! - //! \param memory_usage: (output) data structure used to store memory usage - //! \return: ERROR when error occurs, OK otherwise. - Status _getUsage(Usage& memory_usage) override; - - //! \brief return the underlying memory handle (implementation specific). - //! \return internal task handle representation - MemoryHandle* getHandle() override; - - //----------------------------------------------------------------------------- - // Static interface (singleton) methods - //----------------------------------------------------------------------------- - - //! \brief get system memory usage - //! - //! This method wraps delegates to the underlying implementation. - //! - //! \param memory_usage: (output) data structure used to store memory usage - //! \return: ERROR when error occurs, OK otherwise. - static Status getUsage(Usage& memory); - - private: - // This section is used to store the implementation-defined file handle. To Os::File and fprime, this type is - // opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle in - // the byte-array here and set `handle` to that address for storage. - alignas(FW_HANDLE_ALIGNMENT) MemoryHandleStorage m_handle_storage; //!< Storage for aligned data - MemoryInterface& m_delegate; //!< Delegate for the real implementation -}; -} // namespace Os -#endif // OS_CONDITION_HPP_ +#endif // Os_Memory_hpp diff --git a/Os/MemoryInterface.cpp b/Os/MemoryInterface.cpp new file mode 100644 index 00000000000..e966cb66be7 --- /dev/null +++ b/Os/MemoryInterface.cpp @@ -0,0 +1,13 @@ +// ====================================================================== +// \title Os/MemoryInterface.cpp +// \brief common convenience-wrapper implementation for Os::Memory +// ====================================================================== +#include + +namespace Os { + +MemoryInterface::Status MemoryInterface::getUsage(Usage& memory) { + return Os::Memory::getSingleton()._getUsage(memory); +} + +} // namespace Os diff --git a/Os/MemoryInterface.hpp b/Os/MemoryInterface.hpp new file mode 100644 index 00000000000..d618042dda8 --- /dev/null +++ b/Os/MemoryInterface.hpp @@ -0,0 +1,61 @@ +// ====================================================================== +// \title Os/MemoryInterface.hpp +// \brief Os::MemoryHandle and Os::MemoryInterface definitions +// ====================================================================== +#ifndef OS_MEMORYINTERFACE_HPP_ +#define OS_MEMORYINTERFACE_HPP_ + +#include "Os/Os.hpp" +#include "config/OsSelection.hpp" + +namespace Os { + +//! \brief Memory variable handle parent +class MemoryHandle {}; + +//! \brief interface for memory implementation +class MemoryInterface { + public: + using Status = Os::Generic::Status; + using Usage = Os::Generic::UsedTotal; + + //! Default constructor + MemoryInterface() = default; + //! Default destructor + virtual ~MemoryInterface() = default; + + //! \brief copy constructor is forbidden + MemoryInterface(const MemoryInterface& other) = delete; // NO_CODESONAR (cpp:S3657) + + //! \brief assignment operator is forbidden + virtual MemoryInterface& operator=(const MemoryInterface& other) = delete; + + //! \brief get system memory usage + //! + //! \param memory_usage: (output) data structure used to store memory usage + //! \return: ERROR when error occurs, OK otherwise. + virtual Status _getUsage(Usage& memory_usage) = 0; + + //! \brief return the underlying memory handle (implementation specific). + //! \return internal task handle representation + virtual MemoryHandle* getHandle() = 0; + + //! \brief provide a pointer to a Memory delegate object + static MemoryInterface* getDelegate(MemoryHandleStorage& aligned_new_memory); + + // ------------------------------------------------------------------ + // Convenience static wrappers + // ------------------------------------------------------------------ + // Implemented in terms of the virtual instance function above, and inherited + // (via ordinary class-scope lookup) by every concrete Os::Memory backend, + // regardless of whether that backend is a link-time delegate or a direct + // compile-time alias. + + //! \brief get system memory usage + //! + //! \param memory_usage: (output) data structure used to store memory usage + //! \return: ERROR when error occurs, OK otherwise. + static Status getUsage(Usage& memory); +}; +} // namespace Os +#endif // OS_MEMORYINTERFACE_HPP_ diff --git a/Os/Posix/Console.hpp b/Os/Posix/Console.hpp index c16d5e91b24..8990e1f45e5 100644 --- a/Os/Posix/Console.hpp +++ b/Os/Posix/Console.hpp @@ -49,6 +49,9 @@ class PosixConsole : public ConsoleInterface { // Functions overrides // ------------------------------------ + // Bring the base class's writeMessage(const Fw::ConstStringBase&) overload back into scope + using ConsoleInterface::writeMessage; + //! \brief write message to console //! //! Write a message to the console with a bounded size. This will use the active file descriptor as the output diff --git a/Os/Posix/Directory.cpp b/Os/Posix/Directory.cpp index c6bc23cc4bf..207343b97fa 100644 --- a/Os/Posix/Directory.cpp +++ b/Os/Posix/Directory.cpp @@ -46,6 +46,9 @@ PosixDirectory::Status PosixDirectory::open(const char* path, OpenMode mode) { } this->m_handle.m_dir_descriptor = dir; + // Maintain the open state tracked by DirectoryInterface so that this class behaves correctly + // when it is used directly as the Os::Directory alias (compile-time selection) + this->setOpen(dir != nullptr); return status; } @@ -94,6 +97,7 @@ void PosixDirectory::close() { (void)::closedir(this->m_handle.m_dir_descriptor); } this->m_handle.m_dir_descriptor = nullptr; + this->setOpen(false); } } // namespace Directory diff --git a/Os/Posix/Directory.hpp b/Os/Posix/Directory.hpp index 50a53e2f308..a744f0b0639 100644 --- a/Os/Posix/Directory.hpp +++ b/Os/Posix/Directory.hpp @@ -49,10 +49,6 @@ class PosixDirectory : public DirectoryInterface { //! \return status of the operation Status open(const char* path, OpenMode mode) override; - //! \brief Check if Directory is open or not - //! \return true if Directory is open, false otherwise - bool isOpen(); - //! \brief Rewind directory stream //! //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. @@ -60,6 +56,10 @@ class PosixDirectory : public DirectoryInterface { //! \return status of the operation Status rewind() override; + // Bring the base class's read(Fw::StringBase&) overload back into scope; it would + // otherwise be hidden by the read(char*, FwSizeType) override declared below. + using DirectoryInterface::read; + //! \brief Get next filename from directory stream //! //! Writes at most buffSize characters of the file name to fileNameBuffer. diff --git a/Os/Posix/File.cpp b/Os/Posix/File.cpp index 2d266427c21..cc7d181ee85 100644 --- a/Os/Posix/File.cpp +++ b/Os/Posix/File.cpp @@ -54,13 +54,14 @@ static_assert(std::numeric_limits::max() >= std::numeric_limitsm_handle.m_file_descriptor = fcntl(other.m_handle.m_file_descriptor, F_DUPFD, 0); } PosixFile& PosixFile::operator=(const PosixFile& other) { if (this != &other) { + FileInterface::operator=(other); this->m_handle.m_file_descriptor = fcntl(other.m_handle.m_file_descriptor, F_DUPFD, 0); } return *this; @@ -130,6 +131,9 @@ PosixFile::Status PosixFile::open(const char* filepath, status = Os::Posix::errno_to_file_status(errno_store); } this->m_handle.m_file_descriptor = descriptor; + // Maintain the mode state tracked by FileInterface so that this class behaves correctly when it + // is used directly as the Os::File alias (compile-time selection) + this->setMode((status == OP_OK) ? requested_mode : Mode::OPEN_NO_MODE); return status; } @@ -139,6 +143,7 @@ void PosixFile::close() { (void)::close(this->m_handle.m_file_descriptor); this->m_handle.m_file_descriptor = PosixFileHandle::INVALID_FILE_DESCRIPTOR; } + this->setMode(Mode::OPEN_NO_MODE); } PosixFile::Status PosixFile::size(FwSizeType& size_result) { diff --git a/Os/Posix/File.hpp b/Os/Posix/File.hpp index 5863c6c5cb7..2566450d6c1 100644 --- a/Os/Posix/File.hpp +++ b/Os/Posix/File.hpp @@ -66,6 +66,13 @@ class PosixFile : public FileInterface { //! Os::FileInterface::Status open(const char* path, Mode mode, OverwriteType overwrite) override; + // Bring the base class's open(const char*, Mode) convenience overload, and the read/write overloads + // without a WaitType parameter, back into scope; they would otherwise be hidden by the + // open/read/write overrides declared here and below. + using FileInterface::open; + using FileInterface::read; + using FileInterface::write; + //! \brief close the file, if not opened then do nothing //! //! Closes the file, if open. Otherwise this function does nothing. Delegates to the chosen implementation's diff --git a/Os/Stub/Console.hpp b/Os/Stub/Console.hpp index 82d395f9959..a990bd8f264 100644 --- a/Os/Stub/Console.hpp +++ b/Os/Stub/Console.hpp @@ -39,6 +39,9 @@ class StubConsole : public ConsoleInterface { // Functions overrides // ------------------------------------ + // Bring the base class's writeMessage(const Fw::ConstStringBase&) overload back into scope + using ConsoleInterface::writeMessage; + //! \brief write message to console //! //! Write a message to the console with a bounded size. This will use the active file descriptor as the output diff --git a/Os/Stub/Cpu.cpp b/Os/Stub/Cpu.cpp index 8efed21139b..39adebf32dc 100644 --- a/Os/Stub/Cpu.cpp +++ b/Os/Stub/Cpu.cpp @@ -8,6 +8,11 @@ namespace Os { namespace Stub { namespace Cpu { +StubCpu& StubCpu::getSingleton() { + static StubCpu s_singleton; + return s_singleton; +} + CpuInterface::Status StubCpu::_getCount(FwSizeType& cpu_count) { cpu_count = 0; return Status::ERROR; diff --git a/Os/Stub/Cpu.hpp b/Os/Stub/Cpu.hpp index fb428e01f70..66846bf7969 100644 --- a/Os/Stub/Cpu.hpp +++ b/Os/Stub/Cpu.hpp @@ -35,6 +35,13 @@ class StubCpu : public CpuInterface { //! ~StubCpu() override = default; + //! \brief initialize singleton + static void init() {} + + //! \brief get a reference to singleton + //! \return reference to singleton + static StubCpu& getSingleton(); + // ------------------------------------ // Functions overrides // ------------------------------------ diff --git a/Os/Stub/Directory.hpp b/Os/Stub/Directory.hpp index 099bbbef54f..b367068178e 100644 --- a/Os/Stub/Directory.hpp +++ b/Os/Stub/Directory.hpp @@ -28,6 +28,10 @@ class StubDirectory : public DirectoryInterface { //! \return internal Directory handle representation DirectoryHandle* getHandle() override; + // Bring the base class's read(Fw::StringBase&) overload back into scope; it would + // otherwise be hidden by the read(char*, FwSizeType) override declared below. + using DirectoryInterface::read; + // ------------------------------------------------------------ // Implementation-specific Directory member functions // ------------------------------------------------------------ @@ -47,10 +51,6 @@ class StubDirectory : public DirectoryInterface { //! \return status of the operation Status open(const char* path, OpenMode mode) override; - //! \brief Check if Directory is open or not - //! \return true if Directory is open, false otherwise - bool isOpen(); - //! \brief Rewind directory stream //! //! Each read operation moves the seek position forward. This function resets the seek position to the beginning. diff --git a/Os/Stub/File.hpp b/Os/Stub/File.hpp index 2411bfc9d36..d39e04b9223 100644 --- a/Os/Stub/File.hpp +++ b/Os/Stub/File.hpp @@ -45,6 +45,13 @@ class StubFile : public FileInterface { //! Os::FileInterface::Status open(const char* path, Mode mode, OverwriteType overwrite) override; + // Bring the base class's open(const char*, Mode) convenience overload, and the read/write overloads + // without a WaitType parameter, back into scope; they would otherwise be hidden by the + // open/read/write overrides declared here and below. + using FileInterface::open; + using FileInterface::read; + using FileInterface::write; + //! \brief close the file, if not opened then do nothing //! //! This implementation does nothing. diff --git a/Os/Stub/FileSystem.cpp b/Os/Stub/FileSystem.cpp index 7e25d71954d..25618ffd3ab 100644 --- a/Os/Stub/FileSystem.cpp +++ b/Os/Stub/FileSystem.cpp @@ -8,6 +8,11 @@ namespace Os { namespace Stub { namespace FileSystem { +StubFileSystem& StubFileSystem::getSingleton() { + static StubFileSystem s_singleton; + return s_singleton; +} + StubFileSystem::Status StubFileSystem::_removeDirectory(const char* path) { return Status::NOT_SUPPORTED; } diff --git a/Os/Stub/FileSystem.hpp b/Os/Stub/FileSystem.hpp index e688eed01a9..47854026018 100644 --- a/Os/Stub/FileSystem.hpp +++ b/Os/Stub/FileSystem.hpp @@ -15,7 +15,8 @@ struct StubFileSystemHandle : public FileSystemHandle {}; //! \brief stub implementation of Os::FileSystem //! -//! Stub implementation of `FileSystemInterface` for use as a delegate class handling error-only fileSystem operations. +//! Stub implementation of `FileSystemInterface` for use as a delegate class or direct compile-time alias handling +//! error-only fileSystem operations. //! class StubFileSystem : public FileSystemInterface { public: @@ -25,6 +26,13 @@ class StubFileSystem : public FileSystemInterface { //! \brief destructor ~StubFileSystem() override = default; + //! \brief initialize singleton + static void init() {} + + //! \brief get a reference to singleton + //! \return reference to singleton + static StubFileSystem& getSingleton(); + // ------------------------------------------------------------ // Implementation-specific FileSystem member functions // ------------------------------------------------------------ diff --git a/Os/Stub/Memory.cpp b/Os/Stub/Memory.cpp index 769d00afca3..17cfd23b589 100644 --- a/Os/Stub/Memory.cpp +++ b/Os/Stub/Memory.cpp @@ -8,6 +8,11 @@ namespace Os { namespace Stub { namespace Memory { +StubMemory& StubMemory::getSingleton() { + static StubMemory s_singleton; + return s_singleton; +} + MemoryInterface::Status StubMemory::_getUsage(Os::Memory::Usage& memory_usage) { memory_usage.used = 0; memory_usage.total = 0; diff --git a/Os/Stub/Memory.hpp b/Os/Stub/Memory.hpp index 936e310eb2e..167d4dc4976 100644 --- a/Os/Stub/Memory.hpp +++ b/Os/Stub/Memory.hpp @@ -35,6 +35,13 @@ class StubMemory : public MemoryInterface { //! ~StubMemory() override = default; + //! \brief initialize singleton + static void init() {} + + //! \brief get a reference to singleton + //! \return reference to singleton + static StubMemory& getSingleton(); + // ------------------------------------ // Functions overrides // ------------------------------------ diff --git a/Os/docs/sdd.md b/Os/docs/sdd.md index c3f88d0d870..58e3554b9ec 100644 --- a/Os/docs/sdd.md +++ b/Os/docs/sdd.md @@ -74,13 +74,15 @@ Every OSAL _core service_ (e.g. File, Task, Mutex, etc.) follows a uniform three | Layer | Example | Description | |---|---|---| -| Interface | [`Os::FileInterface`](../File.hpp#L27) | A pure-virtual base class that defines the contract for a given OS service. | -| Wrapper | [`Os::File`](../File.hpp#L225) | A `final` concrete class that holds a reference to the interface delegate constructed inside it. Application code interacts exclusively with the wrapper. | +| Interface | [`Os::FileInterface`](../FileInterface.hpp) | A pure-virtual base class that defines the contract for a given OS service. | +| Wrapper | [`Os::DelegateFile`](../DelegateFile.hpp) | A `final` concrete class that holds a reference to the interface delegate constructed inside it. Application code interacts exclusively with `Os::File`, which is the configured alias for the wrapper (see [§5.2](#52-selection-mechanisms-link-time-vs-compile-time)). | | Implementation | [`Os::Posix::File`](../Posix/File.hpp) | A concrete class that implements the interface. This is what needs to be implemented to support a new OS on F Prime. | +For services that follow this pattern, the public header (e.g. `Os/File.hpp`) is a thin aggregator: it includes `Os/Interface.hpp` (which in turn pulls in the `config/OsSelection.hpp` alias definition) and then the configured implementation header. The pure-virtual contract lives in `Os/Interface.hpp`. + This pattern allows for the selection of the implementation used for a given build to be performed through the build system at link-time: CMake chooses which `Default*.cpp` file to link, and that file provides the `getDelegate` factory function for a given OSAL implementation. -The following diagram (simplified for brevity) illustrates the relationship between these layers for the File service as an illustration. The same pattern applies to all other services. In the final `Os::File` wrapper, the OS functions (e.g. `open()`, `read()`, `close()`) are forwarded to its `m_delegate` which is a reference to an implementation of the `Os::FileInterface`, which is implemented by the platform-specific implementation class. +The following diagram (simplified for brevity) illustrates the relationship between these layers for the File service as an illustration. The same pattern applies to all other services. In the `Os::DelegateFile` wrapper, the OS functions (e.g. `open()`, `read()`, `close()`) are forwarded to its `m_delegate` which is a reference to an implementation of the `Os::FileInterface`, which is implemented by the platform-specific implementation class. ```mermaid classDiagram @@ -90,7 +92,7 @@ classDiagram <> +read() } - class File { + class DelegateFile { <> -FileInterface& m_delegate +read() @@ -104,10 +106,10 @@ classDiagram } - File *-- FileInterface + DelegateFile *-- FileInterface FileInterface <|-- PosixFile FileInterface <|-- FreeRTOSFile - FileInterface <|-- File + FileInterface <|-- DelegateFile ``` @@ -153,9 +155,23 @@ For performance-critical services, the OSAL supports **compile-time selection** - More complex build configuration **Currently Supported Services:** -- **RawTime** (`config/OsSelection.hpp`, `OS_RAW_TIME_HEADER`) -- **Mutex** (`config/OsSelection.hpp`, `OS_MUTEX_HEADER`) -- **ConditionVariable** (`config/OsSelection.hpp`, `OS_CONDITION_VARIABLE_HEADER`) — configured in the same header as Mutex; the two must be overridden together (a `static_assert` in `Os/ConditionVariableInterface.hpp` rejects aliasing only one of them), and the selected ConditionVariable must accept the handle of the selected `Os::Mutex` (implementations such as Posix cast it to their own handle type; `Os::Stub::Mutex::StubConditionVariable` accepts any mutex). Note that `ERROR_DIFFERENT_MUTEX` is reported only by the link-time `DelegateConditionVariable`; a directly aliased implementation does not track which mutex it was first used with. + +The default configuration header `default/config/OsSelection.hpp` defines the `Os::` alias and an `OS__HEADER` macro naming the implementation header to include for each service below: + +| Service | Alias | Implementation-header macro | +|---|---|---| +| RawTime | `Os::RawTime` | `OS_RAW_TIME_HEADER` | +| File | `Os::File` | `OS_FILE_HEADER` | +| FileSystem | `Os::FileSystem` | `OS_FILE_SYSTEM_HEADER` | +| Directory | `Os::Directory` | `OS_DIRECTORY_HEADER` | +| Mutex | `Os::Mutex` | `OS_MUTEX_HEADER` | +| ConditionVariable | `Os::ConditionVariable` | `OS_CONDITION_VARIABLE_HEADER` | +| Console | `Os::Console` | `OS_CONSOLE_HEADER` | +| CountingSemaphore | `Os::CountingSemaphore` | `OS_COUNTING_SEMAPHORE_HEADER` | +| Cpu | `Os::Cpu` | `OS_CPU_HEADER` | +| Memory | `Os::Memory` | `OS_MEMORY_HEADER` | + +Mutex and ConditionVariable are configured together in the same header because a condition variable operates on the handle of the configured `Os::Mutex`; the two must be overridden together (a `static_assert` in `Os/ConditionVariableInterface.hpp` rejects aliasing only one of them), and the selected ConditionVariable must accept the handle of the selected `Os::Mutex` (implementations such as Posix cast it to their own handle type; `Os::Stub::Mutex::StubConditionVariable` accepts any mutex). Note that `ERROR_DIFFERENT_MUTEX` is reported only by the link-time `DelegateConditionVariable`; a directly aliased implementation does not track which mutex it was first used with. The configuration header mechanism allows projects to opt into compile-time selection while maintaining link-time selection as the default for backward compatibility. @@ -171,7 +187,7 @@ namespace Os { #define OS_RAW_TIME_HEADER ``` -Projects can override this header to enable compile-time selection: +Projects can override this header to enable compile-time selection. The override replaces the whole `config/OsSelection.hpp`, so it must define every alias and `OS_*_HEADER` macro in the table above, not just the service being changed (see the complete example in [the OSAL how-to](../../docs/how-to/integrate/implement-osal.md#step-25--optional-override-for-compile-time-selection-performance-optimization)): ```c++ namespace MyPlatform { @@ -189,6 +205,38 @@ namespace Os { > - The concrete implementation MUST inherit from the corresponding interface class > - The configuration header MUST NOT include Os OSAL headers (to avoid circular dependencies) +**Additional requirements for singleton services.** `Console`, `FileSystem`, `Cpu` and `Memory` expose +static convenience wrappers that resolve through `Os::::getSingleton()`, and `Os::init()` +(`Os/Os.cpp`) calls `Os::::init()`. The `FileSystem`, `Cpu` and `Memory` static wrappers are +declared on the interface class (`Console`'s `write()` statics are not, see below), and `init()` and +`getSingleton()` are declared on no interface, so a compile-time alias for one of these services MUST +additionally provide: + +```c++ +static void init(); //!< called by Os::init() +static & getSingleton(); //!< backing instance for the static convenience API +``` + +`Os/Stub/FileSystem.hpp` shows the minimal shape. `Console` additionally needs `static void +write(const CHAR*, FwSizeType)` / `static void write(const Fw::ConstStringBase&)`, and its +`getSingleton()` is responsible for the one-time `Fw::Logger::registerLogger()` call — omitting it +silently drops all `Fw::Logger` output. Without these, the build fails to link (or, for the logger, +loses output at runtime) rather than failing at compile time. + +**Additional requirements for `Os::File`.** `FileInterface` tracks open mode in a non-virtual +`m_mode` member maintained by `DelegateFile`, and its CRC methods (`calculateCrc`, +`incrementalCrc`, `finalizeCrc`) are virtual stubs returning `NOT_SUPPORTED` whose real +implementation lives on `DelegateFile` (see the design note in `Os/FileInterface.hpp`). A +compile-time `Os::File` alias MUST therefore call `setMode()` on open/close so that `isOpen()`, +`readline()` and the mode checks in the `open(...)` overloads behave, and MUST override the three +CRC methods — otherwise `Svc::PrmDb::readParamFile` fails on every boot and +`Svc::FileManager`'s `CALCULATE_CRC` command always returns `EXECUTION_ERROR`. It MUST also replicate the +per-call guards that live only on `DelegateFile`: `open()` on an already-open file returns `INVALID_MODE` +and every other operation on a closed file returns `NOT_OPENED`. `PosixFile` alone does neither (a +re-open silently overwrites, and leaks, the previous descriptor), and callers such as +`Svc::ActiveTextLogger` rely on those statuses. The same `setMode` +style obligation applies to `Os::Directory` (`setOpen()`). + ### 5.3 API usage Application code interacts exclusively with the wrapper classes (e.g., `Os::File`) and never directly with the interfaces or implementations. The wrapper forwards calls to the delegate interface, which is implemented by the platform-specific implementation class. diff --git a/Os/test/ut/file/RulesHeaders.hpp b/Os/test/ut/file/RulesHeaders.hpp index 45f9ebb3d1b..6ebafd88c3e 100644 --- a/Os/test/ut/file/RulesHeaders.hpp +++ b/Os/test/ut/file/RulesHeaders.hpp @@ -27,8 +27,8 @@ struct Tester { FwSizeType position = std::numeric_limits::max(); }; - //! Assert in File.cpp for searching death text - static constexpr const char* ASSERT_IN_FILE_CPP = "Assert: \".*/?Os/File\\.cpp:[0-9]+\""; + //! Assert in FileInterface.cpp or DelegateFile.cpp for searching death text + static constexpr const char* ASSERT_IN_FILE_CPP = "Assert: \".*/?Os/(FileInterface|DelegateFile)\\.cpp:[0-9]+\""; // Constructors that ensures the file is always valid Tester(); diff --git a/default/config/OsSelection.hpp b/default/config/OsSelection.hpp index 56b6a887365..29343fd75ef 100644 --- a/default/config/OsSelection.hpp +++ b/default/config/OsSelection.hpp @@ -2,8 +2,9 @@ // \title config/OsSelection.hpp // \brief configured selection of Os OSAL implementations // -// This header configures how the Os services below (RawTime, Mutex, and -// ConditionVariable) resolve to concrete implementations. Two mechanisms are +// This header configures how the Os services below (RawTime, Mutex, +// ConditionVariable, File, FileSystem, Directory, Console, CountingSemaphore, +// Cpu, and Memory) resolve to concrete implementations. Two mechanisms are // available: // // 1. Link-time selection (default): each service aliases to an Os::Delegate* @@ -87,6 +88,22 @@ // } // namespace Os // #define OS_MUTEX_HEADER "Va416x0/Os/AtomicMutex/AtomicMutex.hpp" // #define OS_CONDITION_VARIABLE_HEADER "Os/Stub/ConditionVariable.hpp" +// +// ---------------------------------------------------------------------- +// File, FileSystem, Directory, Console, CountingSemaphore, Cpu, and Memory +// +// Each of these services is selected independently. A compile-time selection +// MUST derive from the matching Os::Interface. The aliased class must +// still be in the link (see the Mutex note above). Singleton services (Console, +// FileSystem, Cpu, Memory) must also provide static init()/getSingleton(), and a +// direct Os::File alias must implement the CRC methods and the open/closed guards +// that DelegateFile otherwise provides; see Os/docs/sdd.md "Compile-time selection". +// +// Example compile-time selection override: +// +// namespace SomePlatform { class PlatformFile; } +// namespace Os { using File = SomePlatform::PlatformFile; } +// #define OS_FILE_HEADER "SomePlatform/Os/File/PlatformFile.hpp" // ====================================================================== #ifndef CONFIG_OSSELECTION_HPP #define CONFIG_OSSELECTION_HPP @@ -104,10 +121,38 @@ using Mutex = DelegateMutex; class DelegateConditionVariable; using ConditionVariable = DelegateConditionVariable; +class DelegateFile; +using File = DelegateFile; + +class DelegateFileSystem; +using FileSystem = DelegateFileSystem; + +class DelegateDirectory; +using Directory = DelegateDirectory; + +class DelegateConsole; +using Console = DelegateConsole; + +class DelegateCountingSemaphore; +using CountingSemaphore = DelegateCountingSemaphore; + +class DelegateCpu; +using Cpu = DelegateCpu; + +class DelegateMemory; +using Memory = DelegateMemory; + } // namespace Os #define OS_RAW_TIME_HEADER #define OS_MUTEX_HEADER #define OS_CONDITION_VARIABLE_HEADER +#define OS_FILE_HEADER +#define OS_FILE_SYSTEM_HEADER +#define OS_DIRECTORY_HEADER +#define OS_CONSOLE_HEADER +#define OS_COUNTING_SEMAPHORE_HEADER +#define OS_CPU_HEADER +#define OS_MEMORY_HEADER #endif // CONFIG_OSSELECTION_HPP diff --git a/default/config/PlatformCfg.fpp b/default/config/PlatformCfg.fpp index 94318c122bd..7d762de9309 100644 --- a/default/config/PlatformCfg.fpp +++ b/default/config/PlatformCfg.fpp @@ -9,7 +9,12 @@ constant FW_CONSOLE_HANDLE_MAX_SIZE = 24 constant FW_TASK_HANDLE_MAX_SIZE = 40 @ Maximum size of a handle for Os::File -constant FW_FILE_HANDLE_MAX_SIZE = 16 +@ Posix: vtable pointer (8) + FileInterface::m_mode (4) + m_file_descriptor (int, 4) = 16 bytes; +@ implementations with a pointer-sized handle (e.g. Zephyr) = 24 bytes on a 64-bit platform. The +@ open mode moved from the Os::File wrapper onto FileInterface so that it is available to +@ compile-time-selected implementations, which pushed those implementations past the previous +@ 16-byte budget. +constant FW_FILE_HANDLE_MAX_SIZE = 24 @ Maximum size of a handle for Os::Mutex constant FW_MUTEX_HANDLE_MAX_SIZE = 72 @@ -18,7 +23,14 @@ constant FW_MUTEX_HANDLE_MAX_SIZE = 72 constant FW_QUEUE_HANDLE_MAX_SIZE = 368 @ Maximum size of a handle for Os::Directory -constant FW_DIRECTORY_HANDLE_MAX_SIZE = 16 +@ +@ Driven by Os::Posix::Directory::PosixDirectory, whose placement-new footprint is +@ vtable pointer (8) + DirectoryInterface::m_is_open (1, padded) + PosixDirectoryHandle:: +@ m_dir_descriptor (DIR*, 8) = 24 bytes on a 64-bit platform. The open-state flag moved +@ from the Os::Directory wrapper onto DirectoryInterface so that it is available to +@ compile-time-selected implementations, which pushed the delegate past the previous +@ 16-byte budget. Rounded up to 32 to keep alignment margin. +constant FW_DIRECTORY_HANDLE_MAX_SIZE = 32 @ Maximum size of a handle for Os::FileSystem constant FW_FILESYSTEM_HANDLE_MAX_SIZE = 16 diff --git a/docs/how-to/integrate/implement-osal.md b/docs/how-to/integrate/implement-osal.md index ebe37632617..d92cdfcb524 100644 --- a/docs/how-to/integrate/implement-osal.md +++ b/docs/how-to/integrate/implement-osal.md @@ -98,7 +98,7 @@ class MyOsMutex : public MutexInterface { ``` > [!TIP] -> Look at each interface header in `Os/` (e.g., `Os/MutexInterface.hpp`, `Os/File.hpp`, `Os/Task.hpp`) to see the exact set of pure virtual methods that need to be implemented for each. Each interface also defines a `Status` enum — your implementation must return the appropriate [status values](../../../Os/docs/sdd.md#24-error-handling). Note: for services that support compile-time selection, the pure-virtual contract lives in the `*Interface.hpp` header (e.g. `Os/MutexInterface.hpp`, `Os/RawTimeInterface.hpp`), while `Os/Mutex.hpp` / `Os/RawTime.hpp` are thin alias/aggregation headers. +> Look at each interface header in `Os/` (e.g., `Os/MutexInterface.hpp`, `Os/FileInterface.hpp`, `Os/Task.hpp`) to see the exact set of pure virtual methods that need to be implemented for each. Each interface also defines a `Status` enum — your implementation must return the appropriate [status values](../../../Os/docs/sdd.md#24-error-handling). Note: for services that support compile-time selection, the pure-virtual contract lives in the `*Interface.hpp` header (e.g. `Os/MutexInterface.hpp`, `Os/RawTimeInterface.hpp`), while `Os/Mutex.hpp` / `Os/RawTime.hpp` are thin alias/aggregation headers. ### 2.2 — Implement the Methods @@ -191,22 +191,44 @@ namespace MyPlatform { } namespace Os { - class DelegateMutex; // Forward-declare the link-time delegates - class DelegateConditionVariable; // kept for Mutex and ConditionVariable + // Forward-declare the link-time delegates kept for every other service + class DelegateMutex; + class DelegateConditionVariable; + class DelegateFile; + class DelegateFileSystem; + class DelegateDirectory; + class DelegateConsole; + class DelegateCountingSemaphore; + class DelegateCpu; + class DelegateMemory; // Alias Os::RawTime directly to your implementation (no delegate wrapper) using RawTime = MyPlatform::MyRawTime; // The override file replaces the whole default, so every selection must be - // present: keep Mutex and ConditionVariable on the link-time delegates + // present: keep the remaining services on the link-time delegates using Mutex = DelegateMutex; using ConditionVariable = DelegateConditionVariable; + using File = DelegateFile; + using FileSystem = DelegateFileSystem; + using Directory = DelegateDirectory; + using Console = DelegateConsole; + using CountingSemaphore = DelegateCountingSemaphore; + using Cpu = DelegateCpu; + using Memory = DelegateMemory; } // Point to your implementation header (will be included after RawTimeInterface.hpp) #define OS_RAW_TIME_HEADER "MyPlatform/Os/RawTime.hpp" #define OS_MUTEX_HEADER #define OS_CONDITION_VARIABLE_HEADER +#define OS_FILE_HEADER +#define OS_FILE_SYSTEM_HEADER +#define OS_DIRECTORY_HEADER +#define OS_CONSOLE_HEADER +#define OS_COUNTING_SEMAPHORE_HEADER +#define OS_CPU_HEADER +#define OS_MEMORY_HEADER #endif // CONFIG_OSSELECTION_HPP ``` @@ -362,7 +384,20 @@ The full set of [OSAL modules](../../../Os/docs/sdd.md#2-core-services) that can | **Cpu** | `CpuInterface` | `getCount()`, `getTicks()` | | **Memory** | `MemoryInterface` | `getUsage()` | -Refer to each interface header in `Os/` for the full method signatures and status enums. +Refer to each `Os/Interface.hpp` header for the full method signatures and status enums. +For the services that have been split into an alias header plus an interface header (`File`, +`FileSystem`, `Directory`, `Mutex`, `ConditionVariable`, `Console`, `CountingSemaphore`, `Cpu`, `Memory`, +`RawTime`), `Os/.hpp` is a thin aggregator and the pure-virtual contract is in +`Os/Interface.hpp`. + +> [!NOTE] +> The steps above cover *link-time* selection, where your class is reached through the generated +> `getDelegate()` factory. If instead you alias `Os::` directly to your class +> (*compile-time* selection, see [`Os/docs/sdd.md` §5.2.2](../../../Os/docs/sdd.md#522-compile-time-selection-performance-optimization)), +> inheriting from the interface is not sufficient: singleton services (`Console`, `FileSystem`, +> `Cpu`, `Memory`) must also supply `static void init()` and `static & getSingleton()`, and +> `Os::File`/`Os::Directory` must maintain the base class's mode/open state and, for `File`, +> override the CRC methods. See the "Additional requirements" notes in `Os/docs/sdd.md` §5.2.2. ---