mirror of
https://github.com/nasa/fprime.git
synced 2025-12-11 04:35:25 -06:00
329 lines
14 KiB
C++
329 lines
14 KiB
C++
// ======================================================================
|
|
// \title Os/Posix/File.cpp
|
|
// \brief posix implementation for Os::File
|
|
// ======================================================================
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <cerrno>
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
#include <Fw/Types/Assert.hpp>
|
|
#include <Os/File.hpp>
|
|
#include <Os/Posix/File.hpp>
|
|
#include <Os/Posix/error.hpp>
|
|
|
|
namespace Os {
|
|
namespace Posix {
|
|
namespace File {
|
|
|
|
// Sets up the default file permission as user read + user write
|
|
// Some posix systems (e.g. Darwin) use the older S_IREAD and S_IWRITE flags while other systems (e.g. Linux) use the
|
|
// newer S_IRUSR and S_IWUSR flags, and some don't support these flags at all. Hence, we look if flags are defined then
|
|
// set USER_FLAGS to be the set of flags supported or 0 in the case neither is defined.
|
|
#if defined(S_IREAD) && defined(S_IWRITE)
|
|
#define USER_FLAGS (S_IREAD | S_IWRITE)
|
|
#elif defined(S_IRUSR) && defined(S_IWUSR)
|
|
#define USER_FLAGS (S_IRUSR | S_IWUSR)
|
|
#else
|
|
#define USER_FLAGS (0)
|
|
#endif
|
|
|
|
// O_SYNC is not defined on every system. This will set up the SYNC_FLAGS variable to be O_SYNC when defined and
|
|
// (0) when not defined. This allows OPEN_SYNC_WRITE to fall-back to OPEN_WRITE on those systems.
|
|
#if defined(O_SYNC)
|
|
#define SYNC_FLAGS O_SYNC
|
|
#else
|
|
#define SYNC_FLAGS (0)
|
|
#endif
|
|
|
|
// Create constants for the max limits of the signed types
|
|
// These constants are used for comparisons with complementary unsigned types to avoid sign-compare warning
|
|
using UnsignedOffT = std::make_unsigned<off_t>::type;
|
|
static const UnsignedOffT OFF_T_MAX_LIMIT = static_cast<UnsignedOffT>(std::numeric_limits<off_t>::max());
|
|
using UnsignedSSizeT = std::make_unsigned<ssize_t>::type;
|
|
static const UnsignedSSizeT SSIZE_T_MAX_LIMIT = static_cast<UnsignedSSizeT>(std::numeric_limits<ssize_t>::max());
|
|
|
|
// Ensure size of FwSizeType is large enough to fit eh necessary range
|
|
static_assert(sizeof(FwSignedSizeType) >= sizeof(off_t),
|
|
"FwSignedSizeType is not large enough to store values of type off_t");
|
|
static_assert(sizeof(FwSignedSizeType) >= sizeof(ssize_t),
|
|
"FwSignedSizeType is not large enough to store values of type ssize_t");
|
|
static_assert(sizeof(FwSizeType) >= sizeof(size_t), "FwSizeType is not large enough to store values of type size_t");
|
|
|
|
// Now check ranges of FwSizeType
|
|
static_assert(std::numeric_limits<FwSignedSizeType>::max() >= std::numeric_limits<off_t>::max(),
|
|
"Maximum value of FwSignedSizeType less than the maximum value of off_t. Configure a larger type.");
|
|
static_assert(std::numeric_limits<FwSizeType>::max() >= OFF_T_MAX_LIMIT,
|
|
"Maximum value of FwSizeType less than the maximum value of off_t. Configure a larger type.");
|
|
static_assert(std::numeric_limits<FwSignedSizeType>::max() >= std::numeric_limits<ssize_t>::max(),
|
|
"Maximum value of FwSignedSizeType less than the maximum value of ssize_t. Configure a larger type.");
|
|
static_assert(std::numeric_limits<FwSignedSizeType>::min() <= std::numeric_limits<off_t>::min(),
|
|
"Minimum value of FwSignedSizeType larger than the minimum value of off_t. Configure a larger type.");
|
|
static_assert(std::numeric_limits<FwSignedSizeType>::min() <= std::numeric_limits<ssize_t>::min(),
|
|
"Minimum value of FwSizeType larger than the minimum value of ssize_t. Configure a larger type.");
|
|
static_assert(std::numeric_limits<FwSizeType>::max() >= std::numeric_limits<size_t>::max(),
|
|
"Maximum value of FwSizeType less than the maximum value of size_t. Configure a larger type.");
|
|
|
|
//!\brief default copy constructor
|
|
PosixFile::PosixFile(const PosixFile& other) {
|
|
// Must properly duplicate the file handle
|
|
this->m_handle.m_file_descriptor = fcntl(other.m_handle.m_file_descriptor, F_DUPFD, 0);
|
|
}
|
|
|
|
PosixFile& PosixFile::operator=(const PosixFile& other) {
|
|
if (this != &other) {
|
|
this->m_handle.m_file_descriptor = fcntl(other.m_handle.m_file_descriptor, F_DUPFD, 0);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::open(const char* filepath,
|
|
PosixFile::Mode requested_mode,
|
|
PosixFile::OverwriteType overwrite) {
|
|
int mode_flags = 0;
|
|
Status status = OP_OK;
|
|
switch (requested_mode) {
|
|
case OPEN_READ:
|
|
mode_flags = O_RDONLY;
|
|
break;
|
|
case OPEN_WRITE:
|
|
mode_flags = O_WRONLY | O_CREAT;
|
|
break;
|
|
case OPEN_SYNC_WRITE:
|
|
mode_flags = O_WRONLY | O_CREAT | SYNC_FLAGS;
|
|
break;
|
|
case OPEN_CREATE:
|
|
mode_flags =
|
|
O_WRONLY | O_CREAT | O_TRUNC | ((overwrite == PosixFile::OverwriteType::OVERWRITE) ? 0 : O_EXCL);
|
|
break;
|
|
case OPEN_APPEND:
|
|
mode_flags = O_WRONLY | O_CREAT | O_APPEND;
|
|
break;
|
|
default:
|
|
FW_ASSERT(0, requested_mode);
|
|
break;
|
|
}
|
|
int descriptor = ::open(filepath, mode_flags, USER_FLAGS);
|
|
if (PosixFileHandle::INVALID_FILE_DESCRIPTOR == descriptor) {
|
|
int errno_store = errno;
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
}
|
|
this->m_handle.m_file_descriptor = descriptor;
|
|
return status;
|
|
}
|
|
|
|
void PosixFile::close() {
|
|
// Only close file handles that are not open
|
|
if (PosixFileHandle::INVALID_FILE_DESCRIPTOR != this->m_handle.m_file_descriptor) {
|
|
(void)::close(this->m_handle.m_file_descriptor);
|
|
this->m_handle.m_file_descriptor = PosixFileHandle::INVALID_FILE_DESCRIPTOR;
|
|
}
|
|
}
|
|
|
|
PosixFile::Status PosixFile::size(FwSizeType& size_result) {
|
|
FwSizeType current_position = 0;
|
|
Status status = this->position(current_position);
|
|
size_result = 0;
|
|
if (Os::File::Status::OP_OK == status) {
|
|
// Must be a coding error if current_position is larger than off_t max in Posix File
|
|
FW_ASSERT(current_position <= OFF_T_MAX_LIMIT);
|
|
// Seek to the end of the file to determine size
|
|
off_t end_of_file = ::lseek(this->m_handle.m_file_descriptor, 0, SEEK_END);
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == end_of_file) {
|
|
int errno_store = errno;
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
}
|
|
// Return to original position
|
|
(void)::lseek(this->m_handle.m_file_descriptor, static_cast<off_t>(current_position), SEEK_SET);
|
|
size_result = static_cast<FwSizeType>(end_of_file);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::position(FwSizeType& position_result) {
|
|
Status status = OP_OK;
|
|
position_result = 0;
|
|
off_t actual = ::lseek(this->m_handle.m_file_descriptor, 0, SEEK_CUR);
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == actual) {
|
|
int errno_store = errno;
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
}
|
|
// Protected by static assertion (FwSizeType >= off_t)
|
|
position_result = static_cast<FwSizeType>(actual);
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::preallocate(FwSizeType offset, FwSizeType length) {
|
|
PosixFile::Status status = Os::File::Status::NOT_SUPPORTED;
|
|
// Check for larger size than posix supports
|
|
if ((length > OFF_T_MAX_LIMIT) || (offset > OFF_T_MAX_LIMIT) ||
|
|
(std::numeric_limits<off_t>::max() - length) < offset) {
|
|
status = Os::File::Status::BAD_SIZE;
|
|
}
|
|
// posix_fallocate is only available with the posix C-API post version 200112L, however; it is not guaranteed that
|
|
// this call is properly implemented. This code starts with a status of "NOT_SUPPORTED". When the standard is met
|
|
// an attempt will be made to called posix_fallocate, and should that still return NOT_SUPPORTED then fallback
|
|
// code is engaged to synthesize this behavior.
|
|
#if _POSIX_C_SOURCE >= 200112L && !(defined(FPRIME_SYNTHETIC_FALLOCATE) && FPRIME_SYNTHETIC_FALLOCATE)
|
|
else {
|
|
int errno_status =
|
|
::posix_fallocate(this->m_handle.m_file_descriptor, static_cast<off_t>(offset), static_cast<off_t>(length));
|
|
status = Os::Posix::errno_to_file_status(errno_status);
|
|
}
|
|
#endif
|
|
// When the operation is not supported or posix-API is not sufficient, fallback to a slower algorithm
|
|
if (Os::File::Status::NOT_SUPPORTED == status) {
|
|
// Calculate size
|
|
FwSizeType file_size = 0;
|
|
status = this->size(file_size);
|
|
if (Os::File::Status::OP_OK == status) {
|
|
// Calculate current position
|
|
FwSizeType file_position = 0;
|
|
status = this->position(file_position);
|
|
// Check for overflow in seek calls
|
|
if (file_position > static_cast<FwSizeType>(std::numeric_limits<FwSignedSizeType>::max()) ||
|
|
file_size > static_cast<FwSizeType>(std::numeric_limits<FwSignedSizeType>::max())) {
|
|
status = Os::File::Status::BAD_SIZE;
|
|
}
|
|
// Only allocate when the file is smaller than the allocation
|
|
else if ((Os::File::Status::OP_OK == status) && (file_size < (offset + length))) {
|
|
const FwSizeType write_length = (offset + length) - file_size;
|
|
status = this->seek(static_cast<FwSignedSizeType>(file_size), PosixFile::SeekType::ABSOLUTE);
|
|
if (Os::File::Status::OP_OK == status) {
|
|
// Fill in zeros past size of file to ensure compatibility with fallocate
|
|
for (FwSizeType i = 0; i < write_length; i++) {
|
|
FwSizeType write_size = 1;
|
|
status =
|
|
this->write(reinterpret_cast<const U8*>("\0"), write_size, PosixFile::WaitType::NO_WAIT);
|
|
if (Status::OP_OK != status || write_size != 1) {
|
|
break;
|
|
}
|
|
}
|
|
// Return to original position
|
|
if (Os::File::Status::OP_OK == status) {
|
|
status =
|
|
this->seek(static_cast<FwSignedSizeType>(file_position), PosixFile::SeekType::ABSOLUTE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::seek(FwSignedSizeType offset, PosixFile::SeekType seekType) {
|
|
Status status = OP_OK;
|
|
if (offset > std::numeric_limits<off_t>::max()) {
|
|
status = BAD_SIZE;
|
|
} else {
|
|
off_t actual = ::lseek(this->m_handle.m_file_descriptor, static_cast<off_t>(offset),
|
|
(seekType == SeekType::ABSOLUTE) ? SEEK_SET : SEEK_CUR);
|
|
int errno_store = errno;
|
|
if (actual == PosixFileHandle::ERROR_RETURN_VALUE) {
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
} else if ((seekType == SeekType::ABSOLUTE) && (actual != offset)) {
|
|
status = Os::File::Status::OTHER_ERROR;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::flush() {
|
|
PosixFile::Status status = OP_OK;
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == ::fsync(this->m_handle.m_file_descriptor)) {
|
|
int errno_store = errno;
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::read(U8* buffer, FwSizeType& size, PosixFile::WaitType wait) {
|
|
Status status = OP_OK;
|
|
FwSizeType accumulated = 0;
|
|
// Loop up to 2 times for each by, bounded to prevent overflow
|
|
const FwSizeType maximum =
|
|
(size > (std::numeric_limits<FwSizeType>::max() / 2)) ? std::numeric_limits<FwSizeType>::max() : size * 2;
|
|
// POSIX APIs are implementation dependent when dealing with sizes larger than the signed return value
|
|
// thus we ensure a clear decision: BAD_SIZE
|
|
if (size > SSIZE_T_MAX_LIMIT) {
|
|
return BAD_SIZE;
|
|
}
|
|
|
|
for (FwSizeType i = 0; i < maximum && accumulated < size; i++) {
|
|
// char* for some posix implementations
|
|
ssize_t read_size = ::read(this->m_handle.m_file_descriptor, reinterpret_cast<CHAR*>(&buffer[accumulated]),
|
|
static_cast<size_t>(size - accumulated));
|
|
// Non-interrupt error
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == read_size) {
|
|
int errno_store = errno;
|
|
// Interrupted w/o read, try again
|
|
if (EINTR != errno_store) {
|
|
continue;
|
|
}
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
break;
|
|
}
|
|
// End-of-file
|
|
else if (read_size == 0) {
|
|
break;
|
|
}
|
|
accumulated += static_cast<FwSizeType>(read_size);
|
|
// Stop looping when we had a good read and are not waiting
|
|
if (not wait) {
|
|
break;
|
|
}
|
|
}
|
|
size = accumulated;
|
|
return status;
|
|
}
|
|
|
|
PosixFile::Status PosixFile::write(const U8* buffer, FwSizeType& size, PosixFile::WaitType wait) {
|
|
Status status = OP_OK;
|
|
FwSizeType accumulated = 0;
|
|
// Loop up to 2 times for each by, bounded to prevent overflow
|
|
const FwSizeType maximum =
|
|
(size > (std::numeric_limits<FwSizeType>::max() / 2)) ? std::numeric_limits<FwSizeType>::max() : size * 2;
|
|
// POSIX APIs are implementation dependent when dealing with sizes larger than the signed return value
|
|
// thus we ensure a clear decision: BAD_SIZE
|
|
if (size > SSIZE_T_MAX_LIMIT) {
|
|
return BAD_SIZE;
|
|
}
|
|
|
|
for (FwSizeType i = 0; i < maximum && accumulated < size; i++) {
|
|
// char* for some posix implementations
|
|
ssize_t write_size =
|
|
::write(this->m_handle.m_file_descriptor, reinterpret_cast<const CHAR*>(&buffer[accumulated]),
|
|
static_cast<size_t>(size - accumulated));
|
|
// Non-interrupt error
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == write_size || write_size < 0) {
|
|
int errno_store = errno;
|
|
// Interrupted w/o write, try again
|
|
if (EINTR != errno_store) {
|
|
continue;
|
|
}
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
break;
|
|
}
|
|
accumulated += static_cast<FwSizeType>(write_size);
|
|
}
|
|
size = accumulated;
|
|
// When waiting, sync to disk
|
|
if (wait) {
|
|
int fsync_return = ::fsync(this->m_handle.m_file_descriptor);
|
|
if (PosixFileHandle::ERROR_RETURN_VALUE == fsync_return) {
|
|
int errno_store = errno;
|
|
status = Os::Posix::errno_to_file_status(errno_store);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
FileHandle* PosixFile::getHandle() {
|
|
return &this->m_handle;
|
|
}
|
|
|
|
} // namespace File
|
|
} // namespace Posix
|
|
} // namespace Os
|