blob: 903c65393a4688eab7811304e297f76e249aa9f0 [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Implementation of fmemopen, a POSIX function.
///
//===----------------------------------------------------------------------===//
#include "src/stdio/fmemopen.h"
#include "hdr/errno_macros.h"
#include "hdr/stdint_proxy.h"
#include "hdr/stdio_macros.h"
#include "hdr/types/off_t.h"
#include "src/__support/CPP/algorithm.h"
#include "src/__support/CPP/new.h"
#include "src/__support/File/file.h"
#include "src/__support/File/file_mode.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/libc_errno.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/null_check.h"
#include "src/string/memory_utils/inline_memcpy.h"
namespace LIBC_NAMESPACE_DECL {
namespace {
class MemoryFile : public File {
// The stdio buffer is separate from the memory used as the file's contents.
// In particular, ungetc and setvbuf must not modify or free that memory.
uint8_t stream_buffer[DEFAULT_BUFFER_SIZE];
uint8_t *storage;
size_t capacity;
size_t position = 0;
size_t end = 0;
bool owns_storage;
bool append;
static FileIOResult memory_read(File *f, void *data, size_t size) {
auto *mf = reinterpret_cast<MemoryFile *>(f);
if (size == 0 || mf->position >= mf->end)
return 0;
size_t available = mf->end - mf->position;
size_t count = cpp::min(size, available);
inline_memcpy(data, mf->storage + mf->position, count);
mf->position += count;
return count;
}
static FileIOResult memory_write(File *f, const void *data, size_t size) {
auto *mf = reinterpret_cast<MemoryFile *>(f);
if (size == 0)
return 0;
size_t start = mf->append ? mf->end : mf->position;
size_t available = mf->capacity - start;
size_t count = cpp::min(size, available);
// POSIX fflush and fseek specify ENOSPC for a full fmemopen buffer.
if (count == 0)
return {0, ENOSPC};
inline_memcpy(mf->storage + start, data, count);
mf->position = start + count;
if (mf->position > mf->end) {
mf->end = mf->position;
// The terminator is not part of the file contents. All capacity bytes
// may hold data, in which case there is no room for a terminator.
if (mf->end < mf->capacity)
mf->storage[mf->end] = '\0';
}
return {count, count < size ? ENOSPC : 0};
}
static ErrorOr<off_t> memory_seek(File *f, off_t offset, int whence) {
auto *mf = reinterpret_cast<MemoryFile *>(f);
size_t base;
switch (whence) {
case SEEK_SET:
base = 0;
break;
case SEEK_CUR:
base = mf->position;
break;
case SEEK_END:
base = mf->end;
break;
default:
return Error(EINVAL);
}
// The bounds fit in off_t since no object is larger than PTRDIFF_MAX, so
// comparing in off_t needs no negation of offset or narrowing to size_t.
const off_t min_offset = -static_cast<off_t>(base);
const off_t max_offset = static_cast<off_t>(mf->capacity - base);
if (offset < min_offset || offset > max_offset)
return Error(EINVAL);
size_t next = base + static_cast<size_t>(offset);
mf->position = next;
return static_cast<off_t>(next);
}
static int memory_close(File *f) {
auto *mf = reinterpret_cast<MemoryFile *>(f);
File::remove_file(mf);
if (mf->owns_storage)
delete[] mf->storage;
delete mf;
return 0;
}
public:
MemoryFile(uint8_t *storage, size_t capacity, bool owns_storage,
FileMode mode)
: File(&memory_write, &memory_read, &memory_seek, &memory_close,
stream_buffer, sizeof(stream_buffer), _IOFBF, false, mode),
storage(storage), capacity(capacity), owns_storage(owns_storage),
append(mode.is_append()) {
if (mode.is_read()) {
end = capacity;
} else if (mode.is_write()) {
if (capacity != 0)
storage[0] = '\0';
} else if (!owns_storage) {
while (end < capacity && storage[end] != '\0')
++end;
position = end;
}
}
};
} // namespace
LLVM_LIBC_FUNCTION(::FILE *, fmemopen,
(void *__restrict buf, size_t max_size,
const char *__restrict mode)) {
LIBC_CRASH_ON_NULLPTR(mode);
const FileMode file_mode(mode);
if (!file_mode.is_valid()) {
libc_errno = EINVAL;
return nullptr;
}
auto *storage = static_cast<uint8_t *>(buf);
bool owns_storage = buf == nullptr;
if (owns_storage && max_size != 0) {
AllocChecker ac;
storage = new (ac) uint8_t[max_size];
if (!ac) {
libc_errno = ENOMEM;
return nullptr;
}
}
AllocChecker ac;
auto *file = new (ac) MemoryFile(storage, max_size, owns_storage, file_mode);
if (!ac) {
if (owns_storage)
delete[] storage;
libc_errno = ENOMEM;
return nullptr;
}
File::add_file(file);
return reinterpret_cast<::FILE *>(file);
}
} // namespace LIBC_NAMESPACE_DECL