| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 |