Path: blob/main/contrib/llvm-project/libc/src/__support/File/file.cpp
213799 views
//===--- Implementation of a platform independent file data structure -----===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "file.h"910#include "hdr/func/realloc.h"11#include "hdr/stdio_macros.h"12#include "hdr/types/off_t.h"13#include "src/__support/CPP/new.h"14#include "src/__support/CPP/span.h"15#include "src/__support/libc_errno.h" // For error macros16#include "src/__support/macros/config.h"1718namespace LIBC_NAMESPACE_DECL {1920FileIOResult File::write_unlocked(const void *data, size_t len) {21if (!write_allowed()) {22err = true;23return {0, EBADF};24}2526prev_op = FileOp::WRITE;2728if (bufmode == _IONBF) { // unbuffered.29size_t ret_val =30write_unlocked_nbf(static_cast<const uint8_t *>(data), len);31flush_unlocked();32return ret_val;33} else if (bufmode == _IOFBF) { // fully buffered34return write_unlocked_fbf(static_cast<const uint8_t *>(data), len);35} else /*if (bufmode == _IOLBF) */ { // line buffered36return write_unlocked_lbf(static_cast<const uint8_t *>(data), len);37}38}3940FileIOResult File::write_unlocked_nbf(const uint8_t *data, size_t len) {41if (pos > 0) { // If the buffer is not empty42// Flush the buffer43const size_t write_size = pos;44FileIOResult write_result = platform_write(this, buf, write_size);45pos = 0; // Buffer is now empty so reset pos to the beginning.46// If less bytes were written than expected, then an error occurred.47if (write_result < write_size) {48err = true;49// No bytes from data were written, so return 0.50return {0, write_result.error};51}52}5354FileIOResult write_result = platform_write(this, data, len);55if (write_result < len)56err = true;57return write_result;58}5960FileIOResult File::write_unlocked_fbf(const uint8_t *data, size_t len) {61const size_t init_pos = pos;62const size_t bufspace = bufsize - pos;6364// If data is too large to be buffered at all, then just write it unbuffered.65if (len > bufspace + bufsize)66return write_unlocked_nbf(data, len);6768// we split |data| (conceptually) using the split point. Then we handle the69// two pieces separately.70const size_t split_point = len < bufspace ? len : bufspace;7172// The primary piece is the piece of |data| we want to write to the buffer73// before flushing. It will always fit into the buffer, since the split point74// is defined as being min(len, bufspace), and it will always exist if len is75// non-zero.76cpp::span<const uint8_t> primary(data, split_point);7778// The second piece is the remainder of |data|. It is written to the buffer if79// it fits, or written directly to the output if it doesn't. If the primary80// piece fits entirely in the buffer, the remainder may be nothing.81cpp::span<const uint8_t> remainder(82static_cast<const uint8_t *>(data) + split_point, len - split_point);8384cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);8586// Copy the first piece into the buffer.87// TODO: Replace the for loop below with a call to internal memcpy.88for (size_t i = 0; i < primary.size(); ++i)89bufref[pos + i] = primary[i];90pos += primary.size();9192// If there is no remainder, we can return early, since the first piece has93// fit completely into the buffer.94if (remainder.size() == 0)95return len;9697// We need to flush the buffer now, since there is still data and the buffer98// is full.99const size_t write_size = pos;100101FileIOResult buf_result = platform_write(this, buf, write_size);102size_t bytes_written = buf_result.value;103104pos = 0; // Buffer is now empty so reset pos to the beginning.105// If less bytes were written than expected, then an error occurred. Return106// the number of bytes that have been written from |data|.107if (buf_result.has_error() || bytes_written < write_size) {108err = true;109return {bytes_written <= init_pos ? 0 : bytes_written - init_pos,110buf_result.error};111}112113// The second piece is handled basically the same as the first, although we114// know that if the second piece has data in it then the buffer has been115// flushed, meaning that pos is always 0.116if (remainder.size() < bufsize) {117// TODO: Replace the for loop below with a call to internal memcpy.118for (size_t i = 0; i < remainder.size(); ++i)119bufref[i] = remainder[i];120pos = remainder.size();121} else {122123FileIOResult result =124platform_write(this, remainder.data(), remainder.size());125size_t bytes_written = buf_result.value;126127// If less bytes were written than expected, then an error occurred. Return128// the number of bytes that have been written from |data|.129if (result.has_error() || bytes_written < remainder.size()) {130err = true;131return {primary.size() + bytes_written, result.error};132}133}134135return len;136}137138FileIOResult File::write_unlocked_lbf(const uint8_t *data, size_t len) {139constexpr uint8_t NEWLINE_CHAR = '\n';140size_t last_newline = len;141for (size_t i = len; i >= 1; --i) {142if (data[i - 1] == NEWLINE_CHAR) {143last_newline = i - 1;144break;145}146}147148// If there is no newline, treat this as fully buffered.149if (last_newline == len) {150return write_unlocked_fbf(data, len);151}152153// we split |data| (conceptually) using the split point. Then we handle the154// two pieces separately.155const size_t split_point = last_newline + 1;156157// The primary piece is everything in |data| up to the newline. It's written158// unbuffered to the output.159cpp::span<const uint8_t> primary(data, split_point);160161// The second piece is the remainder of |data|. It is written fully buffered,162// meaning it may stay in the buffer if it fits.163cpp::span<const uint8_t> remainder(164static_cast<const uint8_t *>(data) + split_point, len - split_point);165166size_t written = 0;167168written = write_unlocked_nbf(primary.data(), primary.size());169if (written < primary.size()) {170err = true;171return written;172}173174flush_unlocked();175176written += write_unlocked_fbf(remainder.data(), remainder.size());177if (written < len) {178err = true;179return written;180}181182return len;183}184185FileIOResult File::read_unlocked(void *data, size_t len) {186if (!read_allowed()) {187err = true;188return {0, EBADF};189}190191prev_op = FileOp::READ;192193if (bufmode == _IONBF) { // unbuffered.194return read_unlocked_nbf(static_cast<uint8_t *>(data), len);195} else if (bufmode == _IOFBF) { // fully buffered196return read_unlocked_fbf(static_cast<uint8_t *>(data), len);197} else /*if (bufmode == _IOLBF) */ { // line buffered198// There is no line buffered mode for read. Use fully buffered instead.199return read_unlocked_fbf(static_cast<uint8_t *>(data), len);200}201}202203size_t File::copy_data_from_buf(uint8_t *data, size_t len) {204cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);205cpp::span<uint8_t> dataref(static_cast<uint8_t *>(data), len);206207// Because read_limit is always greater than equal to pos,208// available_data is never a wrapped around value.209size_t available_data = read_limit - pos;210if (len <= available_data) {211// TODO: Replace the for loop below with a call to internal memcpy.212for (size_t i = 0; i < len; ++i)213dataref[i] = bufref[i + pos];214pos += len;215return len;216}217218// Copy all of the available data.219// TODO: Replace the for loop with a call to internal memcpy.220for (size_t i = 0; i < available_data; ++i)221dataref[i] = bufref[i + pos];222read_limit = pos = 0; // Reset the pointers.223224return available_data;225}226227FileIOResult File::read_unlocked_fbf(uint8_t *data, size_t len) {228// Read data from the buffer first.229size_t available_data = copy_data_from_buf(data, len);230if (available_data == len)231return available_data;232233// Update the dataref to reflect that fact that we have already234// copied |available_data| into |data|.235size_t to_fetch = len - available_data;236cpp::span<uint8_t> dataref(static_cast<uint8_t *>(data) + available_data,237to_fetch);238239if (to_fetch > bufsize) {240FileIOResult result = platform_read(this, dataref.data(), to_fetch);241size_t fetched_size = result.value;242if (result.has_error() || fetched_size < to_fetch) {243if (!result.has_error())244eof = true;245else246err = true;247return {available_data + fetched_size, result.error};248}249return len;250}251252// Fetch and buffer another buffer worth of data.253FileIOResult result = platform_read(this, buf, bufsize);254size_t fetched_size = result.value;255read_limit += fetched_size;256size_t transfer_size = fetched_size >= to_fetch ? to_fetch : fetched_size;257for (size_t i = 0; i < transfer_size; ++i)258dataref[i] = buf[i];259pos += transfer_size;260if (result.has_error() || fetched_size < to_fetch) {261if (!result.has_error())262eof = true;263else264err = true;265}266return {transfer_size + available_data, result.error};267}268269FileIOResult File::read_unlocked_nbf(uint8_t *data, size_t len) {270// Check whether there is a character in the ungetc buffer.271size_t available_data = copy_data_from_buf(data, len);272if (available_data == len)273return available_data;274275// Directly copy the data into |data|.276cpp::span<uint8_t> dataref(static_cast<uint8_t *>(data) + available_data,277len - available_data);278FileIOResult result = platform_read(this, dataref.data(), dataref.size());279280if (result.has_error() || result < dataref.size()) {281if (!result.has_error())282eof = true;283else284err = true;285}286return {result + available_data, result.error};287}288289int File::ungetc_unlocked(int c) {290// There is no meaning to unget if:291// 1. You are trying to push back EOF.292// 2. Read operations are not allowed on this file.293// 3. The previous operation was a write operation.294if (c == EOF || !read_allowed() || (prev_op == FileOp::WRITE))295return EOF;296297cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);298if (read_limit == 0) {299// If |read_limit| is zero, it can mean three things:300// a. This file was just created.301// b. The previous operation was a seek operation.302// c. The previous operation was a read operation which emptied303// the buffer.304// For all the above cases, we simply write |c| at the beginning305// of the buffer and bump |read_limit|. Note that |pos| will also306// be zero in this case, so we don't need to adjust it.307bufref[0] = static_cast<unsigned char>(c);308++read_limit;309} else {310// If |read_limit| is non-zero, it means that there is data in the buffer311// from a previous read operation. Which would also mean that |pos| is not312// zero. So, we decrement |pos| and write |c| in to the buffer at the new313// |pos|. If too many ungetc operations are performed without reads, it314// can lead to (pos == 0 but read_limit != 0). We will just error out in315// such a case.316if (pos == 0)317return EOF;318--pos;319bufref[pos] = static_cast<unsigned char>(c);320}321322eof = false; // There is atleast one character that can be read now.323err = false; // This operation was a success.324return c;325}326327ErrorOr<int> File::seek(off_t offset, int whence) {328FileLock lock(this);329if (prev_op == FileOp::WRITE && pos > 0) {330331FileIOResult buf_result = platform_write(this, buf, pos);332if (buf_result.has_error() || buf_result.value < pos) {333err = true;334return Error(buf_result.error);335}336} else if (prev_op == FileOp::READ && whence == SEEK_CUR) {337// More data could have been read out from the platform file than was338// required. So, we have to adjust the offset we pass to platform seek339// function. Note that read_limit >= pos is always true.340offset -= (read_limit - pos);341}342pos = read_limit = 0;343prev_op = FileOp::SEEK;344// Reset the eof flag as a seek might move the file positon to some place345// readable.346eof = false;347auto result = platform_seek(this, offset, whence);348if (!result.has_value())349return Error(result.error());350return 0;351}352353ErrorOr<off_t> File::tell() {354FileLock lock(this);355auto seek_target = eof ? SEEK_END : SEEK_CUR;356auto result = platform_seek(this, 0, seek_target);357if (!result.has_value() || result.value() < 0)358return Error(result.error());359off_t platform_offset = result.value();360if (prev_op == FileOp::READ)361return platform_offset - (read_limit - pos);362if (prev_op == FileOp::WRITE)363return platform_offset + pos;364return platform_offset;365}366367int File::flush_unlocked() {368if (prev_op == FileOp::WRITE && pos > 0) {369FileIOResult buf_result = platform_write(this, buf, pos);370if (buf_result.has_error() || buf_result.value < pos) {371err = true;372return buf_result.error;373}374pos = 0;375}376// TODO: Add POSIX behavior for input streams.377return 0;378}379380int File::set_buffer(void *buffer, size_t size, int buffer_mode) {381// We do not need to lock the file as this method should be called before382// other operations are performed on the file.383if (buffer != nullptr && size == 0)384return EINVAL;385386switch (buffer_mode) {387case _IOFBF:388case _IOLBF:389case _IONBF:390break;391default:392return EINVAL;393}394395if (buffer == nullptr && size != 0 && buffer_mode != _IONBF) {396// We exclude the case of buffer_mode == _IONBF in this branch397// because we don't need to allocate buffer in such a case.398if (own_buf) {399// This is one of the places where use a C allocation functon400// as C++ does not have an equivalent of realloc.401buf = reinterpret_cast<uint8_t *>(realloc(buf, size));402if (buf == nullptr)403return ENOMEM;404} else {405AllocChecker ac;406buf = new (ac) uint8_t[size];407if (!ac)408return ENOMEM;409own_buf = true;410}411bufsize = size;412// TODO: Handle allocation failures.413} else {414if (own_buf)415delete buf;416if (buffer_mode != _IONBF) {417buf = static_cast<uint8_t *>(buffer);418bufsize = size;419} else {420// We don't need any buffer.421buf = nullptr;422bufsize = 0;423}424own_buf = false;425}426bufmode = buffer_mode;427adjust_buf();428return 0;429}430431File::ModeFlags File::mode_flags(const char *mode) {432// First character in |mode| should be 'a', 'r' or 'w'.433if (*mode != 'a' && *mode != 'r' && *mode != 'w')434return 0;435436// There should be exaclty one main mode ('a', 'r' or 'w') character.437// If there are more than one main mode characters listed, then438// we will consider |mode| as incorrect and return 0;439int main_mode_count = 0;440441ModeFlags flags = 0;442for (; *mode != '\0'; ++mode) {443switch (*mode) {444case 'r':445flags |= static_cast<ModeFlags>(OpenMode::READ);446++main_mode_count;447break;448case 'w':449flags |= static_cast<ModeFlags>(OpenMode::WRITE);450++main_mode_count;451break;452case '+':453flags |= static_cast<ModeFlags>(OpenMode::PLUS);454break;455case 'b':456flags |= static_cast<ModeFlags>(ContentType::BINARY);457break;458case 'a':459flags |= static_cast<ModeFlags>(OpenMode::APPEND);460++main_mode_count;461break;462case 'x':463flags |= static_cast<ModeFlags>(CreateType::EXCLUSIVE);464break;465default:466return 0;467}468}469470if (main_mode_count != 1)471return 0;472473return flags;474}475476} // namespace LIBC_NAMESPACE_DECL477478479