fprime/Os/Posix/File.cpp

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