blob: 58097d017a23f3b049a2f786ffbb2c4bcbaa0b29 [file] [log] [blame]
//===--- Implementation of a platform independent file data structure -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "file.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/span.h"
#include "src/errno/libc_errno.h" // For error macros
#include <stdio.h>
#include <stdlib.h>
namespace LIBC_NAMESPACE {
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) { // unbuffered.
size_t ret_val =
write_unlocked_nbf(static_cast<const uint8_t *>(data), len);
flush_unlocked();
return ret_val;
} else if (bufmode == _IOFBF) { // fully buffered
return write_unlocked_fbf(static_cast<const uint8_t *>(data), len);
} else /*if (bufmode == _IOLBF) */ { // line buffered
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) { // If the buffer is not empty
// Flush the buffer
const size_t write_size = pos;
auto write_result = platform_write(this, buf, write_size);
pos = 0; // Buffer is now empty so reset pos to the beginning.
// If less bytes were written than expected, then an error occurred.
if (write_result < write_size) {
err = true;
// No bytes from data were written, so return 0.
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 data is too large to be buffered at all, then just write it unbuffered.
if (len > bufspace + bufsize)
return write_unlocked_nbf(data, len);
// we split |data| (conceptually) using the split point. Then we handle the
// two pieces separately.
const size_t split_point = len < bufspace ? len : bufspace;
// The primary piece is the piece of |data| we want to write to the buffer
// before flushing. It will always fit into the buffer, since the split point
// is defined as being min(len, bufspace), and it will always exist if len is
// non-zero.
cpp::span<const uint8_t> primary(data, split_point);
// The second piece is the remainder of |data|. It is written to the buffer if
// it fits, or written directly to the output if it doesn't. If the primary
// piece fits entirely in the buffer, the remainder may be nothing.
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);
// Copy the first piece into the buffer.
// TODO: Replace the for loop below with a call to internal memcpy.
for (size_t i = 0; i < primary.size(); ++i)
bufref[pos + i] = primary[i];
pos += primary.size();
// If there is no remainder, we can return early, since the first piece has
// fit completely into the buffer.
if (remainder.size() == 0)
return len;
// We need to flush the buffer now, since there is still data and the buffer
// is full.
const size_t write_size = pos;
auto buf_result = platform_write(this, buf, write_size);
size_t bytes_written = buf_result.value;
pos = 0; // Buffer is now empty so reset pos to the beginning.
// If less bytes were written than expected, then an error occurred. Return
// the number of bytes that have been written from |data|.
if (buf_result.has_error() || bytes_written < write_size) {
err = true;
return {bytes_written <= init_pos ? 0 : bytes_written - init_pos,
buf_result.error};
}
// The second piece is handled basically the same as the first, although we
// know that if the second piece has data in it then the buffer has been
// flushed, meaning that pos is always 0.
if (remainder.size() < bufsize) {
// TODO: Replace the for loop below with a call to internal memcpy.
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 less bytes were written than expected, then an error occurred. Return
// the number of bytes that have been written from |data|.
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 there is no newline, treat this as fully buffered.
if (last_newline == len) {
return write_unlocked_fbf(data, len);
}
// we split |data| (conceptually) using the split point. Then we handle the
// two pieces separately.
const size_t split_point = last_newline + 1;
// The primary piece is everything in |data| up to the newline. It's written
// unbuffered to the output.
cpp::span<const uint8_t> primary(data, split_point);
// The second piece is the remainder of |data|. It is written fully buffered,
// meaning it may stay in the buffer if it fits.
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);
// Because read_limit is always greater than equal to pos,
// available_data is never a wrapped around value.
size_t available_data = read_limit - pos;
if (len <= available_data) {
// TODO: Replace the for loop below with a call to internal memcpy.
for (size_t i = 0; i < len; ++i)
dataref[i] = bufref[i + pos];
pos += len;
return len;
}
// Copy all of the available data.
// TODO: Replace the for loop with a call to internal memcpy.
for (size_t i = 0; i < available_data; ++i)
dataref[i] = bufref[i + pos];
read_limit = pos = 0; // Reset the pointers.
// Update the dataref to reflect that fact that we have already
// copied |available_data| into |data|.
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;
}
// Fetch and buffer another buffer worth of data.
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) {
// There is no meaning to unget if:
// 1. You are trying to push back EOF.
// 2. Read operations are not allowed on this file.
// 3. The previous operation was a write operation.
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) {
// If |read_limit| is zero, it can mean three things:
// a. This file was just created.
// b. The previous operation was a seek operation.
// c. The previous operation was a read operation which emptied
// the buffer.
// For all the above cases, we simply write |c| at the beginning
// of the buffer and bump |read_limit|. Note that |pos| will also
// be zero in this case, so we don't need to adjust it.
bufref[0] = static_cast<unsigned char>(c);
++read_limit;
} else {
// If |read_limit| is non-zero, it means that there is data in the buffer
// from a previous read operation. Which would also mean that |pos| is not
// zero. So, we decrement |pos| and write |c| in to the buffer at the new
// |pos|. If too many ungetc operations are performed without reads, it
// can lead to (pos == 0 but read_limit != 0). We will just error out in
// such a case.
if (pos == 0)
return EOF;
--pos;
bufref[pos] = static_cast<unsigned char>(c);
}
eof = false; // There is atleast one character that can be read now.
err = false; // This operation was a success.
return c;
}
ErrorOr<int> File::seek(long 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) {
// More data could have been read out from the platform file than was
// required. So, we have to adjust the offset we pass to platform seek
// function. Note that read_limit >= pos is always true.
offset -= (read_limit - pos);
}
pos = read_limit = 0;
prev_op = FileOp::SEEK;
// Reset the eof flag as a seek might move the file positon to some place
// readable.
eof = false;
auto result = platform_seek(this, offset, whence);
if (!result.has_value())
return Error(result.error());
else
return 0;
}
ErrorOr<long> 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());
long platform_offset = result.value();
if (prev_op == FileOp::READ)
return platform_offset - (read_limit - pos);
else if (prev_op == FileOp::WRITE)
return platform_offset + pos;
else
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;
}
// TODO: Add POSIX behavior for input streams.
return 0;
}
int File::set_buffer(void *buffer, size_t size, int buffer_mode) {
// We do not need to lock the file as this method should be called before
// other operations are performed on the file.
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) {
// We exclude the case of buffer_mode == _IONBF in this branch
// because we don't need to allocate buffer in such a case.
if (own_buf) {
// This is one of the places where use a C allocation functon
// as C++ does not have an equivalent of realloc.
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;
// TODO: Handle allocation failures.
} else {
if (own_buf)
delete buf;
if (buffer_mode != _IONBF) {
buf = static_cast<uint8_t *>(buffer);
bufsize = size;
} else {
// We don't need any buffer.
buf = nullptr;
bufsize = 0;
}
own_buf = false;
}
bufmode = buffer_mode;
adjust_buf();
return 0;
}
File::ModeFlags File::mode_flags(const char *mode) {
// First character in |mode| should be 'a', 'r' or 'w'.
if (*mode != 'a' && *mode != 'r' && *mode != 'w')
return 0;
// There should be exaclty one main mode ('a', 'r' or 'w') character.
// If there are more than one main mode characters listed, then
// we will consider |mode| as incorrect and 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;
}
} // namespace LIBC_NAMESPACE