#include "file.h"
#include "hdr/stdio_macros.h"
#include "hdr/types/off_t.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/span.h"
#include "src/__support/macros/config.h"
#include "src/errno/libc_errno.h"
namespace LIBC_NAMESPACE_DECL {
FileIOResult File::write_unlocked(const void *data, size_t len) {
if (!write_allowed()) {
err = true;
return {0, EBADF};
}
prev_op = FileOp::WRITE;
if (bufmode == _IONBF) {
size_t ret_val =
write_unlocked_nbf(static_cast<const uint8_t *>(data), len);
flush_unlocked();
return ret_val;
} else if (bufmode == _IOFBF) {
return write_unlocked_fbf(static_cast<const uint8_t *>(data), len);
} else {
return write_unlocked_lbf(static_cast<const uint8_t *>(data), len);
}
}
FileIOResult File::write_unlocked_nbf(const uint8_t *data, size_t len) {
if (pos > 0) {
const size_t write_size = pos;
auto write_result = platform_write(this, buf, write_size);
pos = 0;
if (write_result < write_size) {
err = true;
return {0, write_result.error};
}
}
auto write_result = platform_write(this, data, len);
if (write_result < len)
err = true;
return write_result;
}
FileIOResult File::write_unlocked_fbf(const uint8_t *data, size_t len) {
const size_t init_pos = pos;
const size_t bufspace = bufsize - pos;
if (len > bufspace + bufsize)
return write_unlocked_nbf(data, len);
const size_t split_point = len < bufspace ? len : bufspace;
cpp::span<const uint8_t> primary(data, split_point);
cpp::span<const uint8_t> remainder(
static_cast<const uint8_t *>(data) + split_point, len - split_point);
cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);
for (size_t i = 0; i < primary.size(); ++i)
bufref[pos + i] = primary[i];
pos += primary.size();
if (remainder.size() == 0)
return len;
const size_t write_size = pos;
auto buf_result = platform_write(this, buf, write_size);
size_t bytes_written = buf_result.value;
pos = 0;
if (buf_result.has_error() || bytes_written < write_size) {
err = true;
return {bytes_written <= init_pos ? 0 : bytes_written - init_pos,
buf_result.error};
}
if (remainder.size() < bufsize) {
for (size_t i = 0; i < remainder.size(); ++i)
bufref[i] = remainder[i];
pos = remainder.size();
} else {
auto result = platform_write(this, remainder.data(), remainder.size());
size_t bytes_written = buf_result.value;
if (result.has_error() || bytes_written < remainder.size()) {
err = true;
return {primary.size() + bytes_written, result.error};
}
}
return len;
}
FileIOResult File::write_unlocked_lbf(const uint8_t *data, size_t len) {
constexpr uint8_t NEWLINE_CHAR = '\n';
size_t last_newline = len;
for (size_t i = len; i >= 1; --i) {
if (data[i - 1] == NEWLINE_CHAR) {
last_newline = i - 1;
break;
}
}
if (last_newline == len) {
return write_unlocked_fbf(data, len);
}
const size_t split_point = last_newline + 1;
cpp::span<const uint8_t> primary(data, split_point);
cpp::span<const uint8_t> remainder(
static_cast<const uint8_t *>(data) + split_point, len - split_point);
size_t written = 0;
written = write_unlocked_nbf(primary.data(), primary.size());
if (written < primary.size()) {
err = true;
return written;
}
flush_unlocked();
written += write_unlocked_fbf(remainder.data(), remainder.size());
if (written < len) {
err = true;
return written;
}
return len;
}
FileIOResult File::read_unlocked(void *data, size_t len) {
if (!read_allowed()) {
err = true;
return {0, EBADF};
}
prev_op = FileOp::READ;
cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);
cpp::span<uint8_t> dataref(static_cast<uint8_t *>(data), len);
size_t available_data = read_limit - pos;
if (len <= available_data) {
for (size_t i = 0; i < len; ++i)
dataref[i] = bufref[i + pos];
pos += len;
return len;
}
for (size_t i = 0; i < available_data; ++i)
dataref[i] = bufref[i + pos];
read_limit = pos = 0;
dataref = cpp::span<uint8_t>(dataref.data() + available_data,
dataref.size() - available_data);
size_t to_fetch = len - available_data;
if (to_fetch > bufsize) {
auto result = platform_read(this, dataref.data(), to_fetch);
size_t fetched_size = result.value;
if (result.has_error() || fetched_size < to_fetch) {
if (!result.has_error())
eof = true;
else
err = true;
return {available_data + fetched_size, result.has_error()};
}
return len;
}
auto result = platform_read(this, buf, bufsize);
size_t fetched_size = result.value;
read_limit += fetched_size;
size_t transfer_size = fetched_size >= to_fetch ? to_fetch : fetched_size;
for (size_t i = 0; i < transfer_size; ++i)
dataref[i] = bufref[i];
pos += transfer_size;
if (result.has_error() || fetched_size < to_fetch) {
if (!result.has_error())
eof = true;
else
err = true;
}
return {transfer_size + available_data, result.error};
}
int File::ungetc_unlocked(int c) {
if (c == EOF || !read_allowed() || (prev_op == FileOp::WRITE))
return EOF;
cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);
if (read_limit == 0) {
bufref[0] = static_cast<unsigned char>(c);
++read_limit;
} else {
if (pos == 0)
return EOF;
--pos;
bufref[pos] = static_cast<unsigned char>(c);
}
eof = false;
err = false;
return c;
}
ErrorOr<int> File::seek(off_t offset, int whence) {
FileLock lock(this);
if (prev_op == FileOp::WRITE && pos > 0) {
auto buf_result = platform_write(this, buf, pos);
if (buf_result.has_error() || buf_result.value < pos) {
err = true;
return Error(buf_result.error);
}
} else if (prev_op == FileOp::READ && whence == SEEK_CUR) {
offset -= (read_limit - pos);
}
pos = read_limit = 0;
prev_op = FileOp::SEEK;
eof = false;
auto result = platform_seek(this, offset, whence);
if (!result.has_value())
return Error(result.error());
return 0;
}
ErrorOr<off_t> File::tell() {
FileLock lock(this);
auto seek_target = eof ? SEEK_END : SEEK_CUR;
auto result = platform_seek(this, 0, seek_target);
if (!result.has_value() || result.value() < 0)
return Error(result.error());
off_t platform_offset = result.value();
if (prev_op == FileOp::READ)
return platform_offset - (read_limit - pos);
if (prev_op == FileOp::WRITE)
return platform_offset + pos;
return platform_offset;
}
int File::flush_unlocked() {
if (prev_op == FileOp::WRITE && pos > 0) {
auto buf_result = platform_write(this, buf, pos);
if (buf_result.has_error() || buf_result.value < pos) {
err = true;
return buf_result.error;
}
pos = 0;
}
return 0;
}
int File::set_buffer(void *buffer, size_t size, int buffer_mode) {
if (buffer != nullptr && size == 0)
return EINVAL;
switch (buffer_mode) {
case _IOFBF:
case _IOLBF:
case _IONBF:
break;
default:
return EINVAL;
}
if (buffer == nullptr && size != 0 && buffer_mode != _IONBF) {
if (own_buf) {
buf = reinterpret_cast<uint8_t *>(realloc(buf, size));
if (buf == nullptr)
return ENOMEM;
} else {
AllocChecker ac;
buf = new (ac) uint8_t[size];
if (!ac)
return ENOMEM;
own_buf = true;
}
bufsize = size;
} else {
if (own_buf)
delete buf;
if (buffer_mode != _IONBF) {
buf = static_cast<uint8_t *>(buffer);
bufsize = size;
} else {
buf = nullptr;
bufsize = 0;
}
own_buf = false;
}
bufmode = buffer_mode;
adjust_buf();
return 0;
}
File::ModeFlags File::mode_flags(const char *mode) {
if (*mode != 'a' && *mode != 'r' && *mode != 'w')
return 0;
int main_mode_count = 0;
ModeFlags flags = 0;
for (; *mode != '\0'; ++mode) {
switch (*mode) {
case 'r':
flags |= static_cast<ModeFlags>(OpenMode::READ);
++main_mode_count;
break;
case 'w':
flags |= static_cast<ModeFlags>(OpenMode::WRITE);
++main_mode_count;
break;
case '+':
flags |= static_cast<ModeFlags>(OpenMode::PLUS);
break;
case 'b':
flags |= static_cast<ModeFlags>(ContentType::BINARY);
break;
case 'a':
flags |= static_cast<ModeFlags>(OpenMode::APPEND);
++main_mode_count;
break;
case 'x':
flags |= static_cast<ModeFlags>(CreateType::EXCLUSIVE);
break;
default:
return 0;
}
}
if (main_mode_count != 1)
return 0;
return flags;
}
}