diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt --- a/libc/src/__support/CMakeLists.txt +++ b/libc/src/__support/CMakeLists.txt @@ -30,6 +30,14 @@ sanitizer.h ) +add_header_library( + error_or + HDRS + error_or.h + DEPENDS + libc.src.__support.CPP.expected +) + add_header_library( ctype_utils HDRS diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt --- a/libc/src/__support/CPP/CMakeLists.txt +++ b/libc/src/__support/CPP/CMakeLists.txt @@ -94,7 +94,7 @@ ) add_header_library( - error + expected HDRS - error.h + expected.h ) diff --git a/libc/src/__support/CPP/error.h b/libc/src/__support/CPP/error.h deleted file mode 100644 --- a/libc/src/__support/CPP/error.h +++ /dev/null @@ -1,51 +0,0 @@ -//===-- A simple classes to manage error return vals ----------*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIBC_SRC_SUPPORT_CPP_ERROR_H -#define LLVM_LIBC_SRC_SUPPORT_CPP_ERROR_H - -namespace __llvm_libc { -namespace cpp { -// Many C functions return an error val and/or the actual result of the -// evaluation/operation performed by the function. This file defines a simple -// convenience data structure to encapsulate the error and the actual val in -// a single place. - -struct Error { - int error_code; -}; - -// This class is implemented in a simple fashion as the intention is it add -// more generality as required. Currently, it only supports simple copyable -// types for T. -template class ErrorOr { - bool is_error; - - union { - T val; - Error error; - }; - -public: - ErrorOr(const T &value) : is_error(false), val(value) {} - - ErrorOr(const Error &error) : is_error(true), error(error) {} - - operator bool() { return !is_error; } - - operator T &() { return val; } - - T &value() { return val; } - - int error_code() { return is_error ? error.error_code : 0; } -}; - -} // namespace cpp -} // namespace __llvm_libc - -#endif // LLVM_LIBC_SRC_SUPPORT_CPP_ERROR_H diff --git a/libc/src/__support/CPP/expected.h b/libc/src/__support/CPP/expected.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/CPP/expected.h @@ -0,0 +1,52 @@ +//===-- Holds an expected or unexpected value -------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SUPPORT_CPP_EXPECTED_H +#define LLVM_LIBC_SUPPORT_CPP_EXPECTED_H + +namespace __llvm_libc::cpp { + +// This is used to hold an unexpected value so that a different constructor is +// selected. +template class unexpected { + T value; + +public: + constexpr explicit unexpected(T value) : value(value) {} + constexpr T error() { return value; } +}; + +template class expected { + union { + T exp; + E unexp; + }; + bool is_expected; + +public: + constexpr expected(T exp) : exp(exp), is_expected(true) {} + constexpr expected(unexpected unexp) + : unexp(unexp.error()), is_expected(false) {} + + constexpr bool has_value() { return is_expected; } + + constexpr T value() { return exp; } + constexpr E error() { return unexp; } + + constexpr operator T() { return exp; } + constexpr operator bool() { return is_expected; } + + constexpr T &operator*() { return exp; } + constexpr const T &operator*() const { return exp; } + constexpr T *operator->() { return &exp; } + constexpr const T *operator->() const { return &exp; } +}; + +} // namespace __llvm_libc::cpp + +#endif // LLVM_LIBC_SUPPORT_CPP_EXPECTED_H diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt --- a/libc/src/__support/File/CMakeLists.txt +++ b/libc/src/__support/File/CMakeLists.txt @@ -14,7 +14,7 @@ libc.include.errno libc.src.__support.CPP.span libc.src.__support.threads.mutex - libc.src.errno.errno + libc.src.__support.error_or ) add_object_library( @@ -41,6 +41,7 @@ libc.include.sys_syscall libc.src.__support.OSUtil.osutil libc.src.errno.errno + libc.src.__support.error_or ) endif() diff --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h --- a/libc/src/__support/File/file.h +++ b/libc/src/__support/File/file.h @@ -9,6 +9,7 @@ #ifndef LLVM_LIBC_SRC_SUPPORT_OSUTIL_FILE_H #define LLVM_LIBC_SRC_SUPPORT_OSUTIL_FILE_H +#include "src/__support/error_or.h" #include "src/__support/threads/mutex.h" #include @@ -16,6 +17,18 @@ namespace __llvm_libc { +struct FileIOResult { + size_t value; + int error; + + constexpr FileIOResult(size_t val) : value(val), error(0) {} + constexpr FileIOResult(size_t val, int error) : value(val), error(error) {} + + constexpr bool has_error() { return error != 0; } + + constexpr operator size_t() { return value; } +}; + // This a generic base class to encapsulate a platform independent file data // structure. Platform specific specializations should create a subclass as // suitable for their platform. @@ -26,11 +39,11 @@ using LockFunc = void(File *); using UnlockFunc = void(File *); - using WriteFunc = size_t(File *, const void *, size_t); - using ReadFunc = size_t(File *, void *, size_t); + using WriteFunc = FileIOResult(File *, const void *, size_t); + using ReadFunc = FileIOResult(File *, void *, size_t); // The SeekFunc is expected to return the current offset of the external // file position indicator. - using SeekFunc = long(File *, long, int); + using SeekFunc = ErrorOr(File *, long, int); using CloseFunc = int(File *); using FlushFunc = int(File *); @@ -174,26 +187,26 @@ } // Buffered write of |len| bytes from |data| without the file lock. - size_t write_unlocked(const void *data, size_t len); + FileIOResult write_unlocked(const void *data, size_t len); // Buffered write of |len| bytes from |data| under the file lock. - size_t write(const void *data, size_t len) { + FileIOResult write(const void *data, size_t len) { FileLock l(this); return write_unlocked(data, len); } // Buffered read of |len| bytes into |data| without the file lock. - size_t read_unlocked(void *data, size_t len); + FileIOResult read_unlocked(void *data, size_t len); // Buffered read of |len| bytes into |data| under the file lock. - size_t read(void *data, size_t len) { + FileIOResult read(void *data, size_t len) { FileLock l(this); return read_unlocked(data, len); } - int seek(long offset, int whence); + ErrorOr seek(long offset, int whence); - long tell(); + ErrorOr tell(); // If buffer has data written to it, flush it out. Does nothing if the // buffer is currently being used as a read buffer. @@ -256,9 +269,9 @@ static ModeFlags mode_flags(const char *mode); private: - size_t write_unlocked_lbf(const uint8_t *data, size_t len); - size_t write_unlocked_fbf(const uint8_t *data, size_t len); - size_t write_unlocked_nbf(const uint8_t *data, size_t len); + FileIOResult write_unlocked_lbf(const uint8_t *data, size_t len); + FileIOResult write_unlocked_fbf(const uint8_t *data, size_t len); + FileIOResult write_unlocked_nbf(const uint8_t *data, size_t len); constexpr void adjust_buf() { if (read_allowed() && (buf == nullptr || bufsize == 0)) { @@ -285,7 +298,7 @@ // The implementaiton of this function is provided by the platfrom_file // library. -File *openfile(const char *path, const char *mode); +ErrorOr openfile(const char *path, const char *mode); // The platform_file library should implement it if it relevant for that // platform. diff --git a/libc/src/__support/File/file.cpp b/libc/src/__support/File/file.cpp --- a/libc/src/__support/File/file.cpp +++ b/libc/src/__support/File/file.cpp @@ -10,17 +10,16 @@ #include "src/__support/CPP/span.h" -#include +#include // For error macros #include #include namespace __llvm_libc { -size_t File::write_unlocked(const void *data, size_t len) { +FileIOResult File::write_unlocked(const void *data, size_t len) { if (!write_allowed()) { - errno = EBADF; err = true; - return 0; + return {0, EBADF}; } prev_op = FileOp::WRITE; @@ -37,26 +36,27 @@ } } -size_t File::write_unlocked_nbf(const uint8_t *data, size_t 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; - size_t bytes_written = platform_write(this, buf, write_size); + 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 (bytes_written < write_size) { + if (write_result < write_size) { err = true; - return 0; // No bytes from data were written, so return 0. + // No bytes from data were written, so return 0. + return {0, write_result.error}; } } - size_t written = platform_write(this, data, len); - if (written < len) + auto write_result = platform_write(this, data, len); + if (write_result < len) err = true; - return written; + return write_result; } -size_t File::write_unlocked_fbf(const uint8_t *data, size_t len) { +FileIOResult File::write_unlocked_fbf(const uint8_t *data, size_t len) { const size_t init_pos = pos; const size_t bufspace = bufsize - pos; @@ -96,13 +96,17 @@ // We need to flush the buffer now, since there is still data and the buffer // is full. const size_t write_size = pos; - size_t bytes_written = platform_write(this, buf, write_size); + + 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 (bytes_written < write_size) { + if (buf_result.has_error() || bytes_written < write_size) { err = true; - return bytes_written <= init_pos ? 0 : bytes_written - init_pos; + 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 @@ -114,21 +118,22 @@ bufref[i] = remainder[i]; pos = remainder.size(); } else { - size_t bytes_written = - platform_write(this, remainder.data(), remainder.size()); + + 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 (bytes_written < remainder.size()) { + if (result.has_error() || bytes_written < remainder.size()) { err = true; - return primary.size() + bytes_written; + return {primary.size() + bytes_written, result.error}; } } return len; } -size_t File::write_unlocked_lbf(const uint8_t *data, size_t 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) { @@ -175,11 +180,10 @@ return len; } -size_t File::read_unlocked(void *data, size_t len) { +FileIOResult File::read_unlocked(void *data, size_t len) { if (!read_allowed()) { - errno = EBADF; err = true; - return 0; + return {0, EBADF}; } prev_op = FileOp::READ; @@ -210,31 +214,33 @@ size_t to_fetch = len - available_data; if (to_fetch > bufsize) { - size_t fetched_size = platform_read(this, dataref.data(), to_fetch); - if (fetched_size < to_fetch) { - if (errno == 0) + 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; + return {available_data + fetched_size, result.has_error()}; } return len; } // Fetch and buffer another buffer worth of data. - size_t fetched_size = platform_read(this, buf, bufsize); + 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 (fetched_size < to_fetch) { - if (errno == 0) + if (result.has_error() || fetched_size < to_fetch) { + if (!result.has_error()) eof = true; else err = true; } - return transfer_size + available_data; + return {transfer_size + available_data, result.error}; } int File::ungetc_unlocked(int c) { @@ -275,13 +281,14 @@ return c; } -int File::seek(long offset, int whence) { +ErrorOr File::seek(long offset, int whence) { FileLock lock(this); if (prev_op == FileOp::WRITE && pos > 0) { - size_t transferred_size = platform_write(this, buf, pos); - if (transferred_size < pos) { + + auto buf_result = platform_write(this, buf, pos); + if (buf_result.has_error() || buf_result.value < pos) { err = true; - return -1; + 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 @@ -294,22 +301,20 @@ // Reset the eof flag as a seek might move the file positon to some place // readable. eof = false; - long platform_pos = platform_seek(this, offset, whence); - if (platform_pos >= 0) - return 0; + auto result = platform_seek(this, offset, whence); + if (!result.has_value()) + return Error(result.error()); else - return -1; + return 0; } -long File::tell() { +ErrorOr File::tell() { FileLock lock(this); - long platform_offset; - if (eof) - platform_offset = platform_seek(this, 0, SEEK_END); - else - platform_offset = platform_seek(this, 0, SEEK_CUR); - if (platform_offset < 0) - return -1; + 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) @@ -320,10 +325,10 @@ int File::flush_unlocked() { if (prev_op == FileOp::WRITE && pos > 0) { - size_t transferred_size = platform_write(this, buf, pos); - if (transferred_size < pos) { + auto buf_result = platform_write(this, buf, pos); + if (buf_result.has_error() || buf_result.value < pos) { err = true; - return -1; + return buf_result.error; } pos = 0; return platform_flush(this); @@ -336,14 +341,15 @@ { FileLock lock(this); if (prev_op == FileOp::WRITE && pos > 0) { - size_t transferred_size = platform_write(this, buf, pos); - if (transferred_size < pos) { + auto buf_result = platform_write(this, buf, pos); + if (buf_result.has_error() || buf_result.value < pos) { err = true; - return -1; + return buf_result.error; } } - if (platform_close(this) != 0) - return -1; + int result = platform_close(this); + if (result != 0) + return result; if (own_buf) free(buf); } diff --git a/libc/src/__support/File/linux_file.cpp b/libc/src/__support/File/linux_file.cpp --- a/libc/src/__support/File/linux_file.cpp +++ b/libc/src/__support/File/linux_file.cpp @@ -10,7 +10,7 @@ #include "src/__support/OSUtil/syscall.h" // For internal syscall function. -#include +#include // For error macros #include // For mode_t and other flags to the open syscall #include #include // For malloc @@ -20,9 +20,9 @@ namespace { -size_t write_func(File *, const void *, size_t); -size_t read_func(File *, void *, size_t); -long seek_func(File *, long, int); +FileIOResult write_func(File *, const void *, size_t); +FileIOResult read_func(File *, void *, size_t); +ErrorOr seek_func(File *, long, int); int close_func(File *); int flush_func(File *); @@ -51,75 +51,70 @@ namespace { -size_t write_func(File *f, const void *data, size_t size) { +FileIOResult write_func(File *f, const void *data, size_t size) { auto *lf = reinterpret_cast(f); - long ret = __llvm_libc::syscall_impl(SYS_write, lf->get_fd(), data, size); + int ret = __llvm_libc::syscall_impl(SYS_write, lf->get_fd(), data, size); if (ret < 0) { - errno = -ret; - return 0; + return {0, -ret}; } return ret; } -size_t read_func(File *f, void *buf, size_t size) { +FileIOResult read_func(File *f, void *buf, size_t size) { auto *lf = reinterpret_cast(f); - long ret = __llvm_libc::syscall_impl(SYS_read, lf->get_fd(), buf, size); + int ret = __llvm_libc::syscall_impl(SYS_read, lf->get_fd(), buf, size); if (ret < 0) { - errno = -ret; - return 0; + return {0, -ret}; } return ret; } -long seek_func(File *f, long offset, int whence) { +ErrorOr seek_func(File *f, long offset, int whence) { auto *lf = reinterpret_cast(f); long result; #ifdef SYS_lseek - long ret = __llvm_libc::syscall_impl(SYS_lseek, lf->get_fd(), offset, whence); + int ret = __llvm_libc::syscall_impl(SYS_lseek, lf->get_fd(), offset, whence); result = ret; #elif defined(SYS__llseek) long result; - long ret = __llvm_libc::syscall_impl(SYS__llseek, lf->get_fd(), offset >> 32, - offset, &result, whence); + int ret = __llvm_libc::syscall_impl(SYS__llseek, lf->get_fd(), offset >> 32, + offset, &result, whence); #else #error "lseek and _llseek syscalls not available to perform a seek operation." #endif - if (ret < 0) { - errno = -ret; - return -1; - } + if (ret < 0) + return Error(-ret); + return result; } int close_func(File *f) { auto *lf = reinterpret_cast(f); - long ret = __llvm_libc::syscall_impl(SYS_close, lf->get_fd()); + int ret = __llvm_libc::syscall_impl(SYS_close, lf->get_fd()); if (ret < 0) { - errno = -ret; - return -1; + return -ret; } return 0; } int flush_func(File *f) { auto *lf = reinterpret_cast(f); - long ret = __llvm_libc::syscall_impl(SYS_fsync, lf->get_fd()); + int ret = __llvm_libc::syscall_impl(SYS_fsync, lf->get_fd()); if (ret < 0) { - errno = -ret; - return -1; + return -ret; } return 0; } } // anonymous namespace -File *openfile(const char *path, const char *mode) { +ErrorOr openfile(const char *path, const char *mode) { using ModeFlags = File::ModeFlags; auto modeflags = File::mode_flags(mode); if (modeflags == 0) { - errno = EINVAL; - return nullptr; + // return {nullptr, EINVAL}; + return Error(EINVAL); } long open_flags = 0; if (modeflags & ModeFlags(File::OpenMode::APPEND)) { @@ -155,8 +150,8 @@ #endif if (fd < 0) { - errno = -fd; - return nullptr; + return Error(-fd); + // return {nullptr, -fd}; } void *buffer = malloc(File::DEFAULT_BUFFER_SIZE); diff --git a/libc/src/__support/error_or.h b/libc/src/__support/error_or.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/error_or.h @@ -0,0 +1,39 @@ +//===-- A data structure for returning an error or a value ------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SUPPORT_ERROR_OR_RESULT_H +#define LLVM_LIBC_SUPPORT_ERROR_OR_RESULT_H + +#include "src/__support/CPP/expected.h" + +namespace __llvm_libc { + +template using ErrorOr = cpp::expected; + +using Error = cpp::unexpected; + +// template struct ErrorOr { +// union { +// T value; +// int error; +// }; +// bool is_error; + +// constexpr ErrorOr(T value) : value(value), is_error(false) {} +// constexpr ErrorOr(int error, bool is_error) +// : error(error), is_error(is_error) {} + +// constexpr bool has_error() { return is_error; } + +// constexpr operator bool() { return is_error; } +// constexpr operator T() { return value; } +// }; + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SUPPORT_ERROR_OR_RESULT_H diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt --- a/libc/src/__support/threads/linux/CMakeLists.txt +++ b/libc/src/__support/threads/linux/CMakeLists.txt @@ -29,7 +29,7 @@ libc.config.linux.app_h libc.include.sys_syscall libc.src.__support.CPP.atomic - libc.src.__support.CPP.error + libc.src.__support.error_or libc.src.__support.CPP.stringstream libc.src.__support.CPP.string_view libc.src.__support.threads.thread_common diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp --- a/libc/src/__support/threads/linux/thread.cpp +++ b/libc/src/__support/threads/linux/thread.cpp @@ -8,11 +8,11 @@ #include "src/__support/threads/thread.h" #include "config/linux/app.h" -#include "src/__support/CPP/string_view.h" #include "src/__support/CPP/atomic.h" -#include "src/__support/CPP/error.h" +#include "src/__support/CPP/string_view.h" #include "src/__support/CPP/stringstream.h" -#include "src/__support/OSUtil/syscall.h" // For syscall functions. +#include "src/__support/OSUtil/syscall.h" // For syscall functions. +#include "src/__support/error_or.h" #include "src/__support/threads/linux/futex_word.h" // For FutexWordType #ifdef LLVM_LIBC_ARCH_AARCH64 @@ -54,7 +54,7 @@ // wake the joining thread. | CLONE_SETTLS; // Setup the thread pointer of the new thread. -static inline cpp::ErrorOr alloc_stack(size_t size) { +static inline ErrorOr alloc_stack(size_t size) { long mmap_result = __llvm_libc::syscall_impl(MMAP_SYSCALL_NUMBER, 0, // No special address @@ -65,7 +65,7 @@ 0 // No offset ); if (mmap_result < 0 && (uintptr_t(mmap_result) >= UINTPTR_MAX - size)) - return cpp::Error{int(-mmap_result)}; + return Error{int(-mmap_result)}; return reinterpret_cast(mmap_result); } @@ -113,8 +113,7 @@ #endif } -__attribute__((noinline)) -static void start_thread() { +__attribute__((noinline)) static void start_thread() { auto *start_args = reinterpret_cast(get_start_args_addr()); auto *attrib = start_args->thread_attrib; self.attrib = attrib; @@ -141,7 +140,7 @@ size = DEFAULT_STACK_SIZE; auto alloc = alloc_stack(size); if (!alloc) - return alloc.error_code(); + return alloc.error(); else stack = alloc.value(); owned_stack = true; @@ -374,7 +373,7 @@ // These callbacks could be the ones registered by the language runtimes, // for example, the destructors of thread local objects. They can also // be destructors of the TSS objects set using API like pthread_setspecific. - // NOTE: We cannot call the atexit callbacks as part of the + // NOTE: We cannot call the atexit callbacks as part of the // cleanup_thread_resources function as that function can be called from a // different thread. The destructors of thread local and TSS objects should // be called by the thread which owns them. diff --git a/libc/src/pthread/pthread_setname_np.cpp b/libc/src/pthread/pthread_setname_np.cpp --- a/libc/src/pthread/pthread_setname_np.cpp +++ b/libc/src/pthread/pthread_setname_np.cpp @@ -9,7 +9,6 @@ #include "pthread_setname_np.h" #include "src/__support/CPP/string_view.h" -#include "src/__support/CPP/error.h" #include "src/__support/common.h" #include "src/__support/threads/thread.h" diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -25,6 +25,7 @@ fclose.h DEPENDS libc.include.stdio + libc.include.errno libc.src.__support.File.file libc.src.__support.File.platform_file ) @@ -108,6 +109,7 @@ HDRS fgetc.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -120,6 +122,7 @@ HDRS fgetc_unlocked.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -132,6 +135,7 @@ HDRS getc.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -144,6 +148,7 @@ HDRS getc_unlocked.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -156,6 +161,7 @@ HDRS fgets.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -168,6 +174,7 @@ HDRS fflush.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -204,6 +211,7 @@ HDRS fread_unlocked.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -216,6 +224,7 @@ HDRS fread.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -228,6 +237,7 @@ HDRS fwrite_unlocked.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -240,6 +250,7 @@ HDRS fwrite.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -252,6 +263,7 @@ HDRS fputc.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -264,6 +276,7 @@ HDRS putc.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -276,6 +289,7 @@ HDRS putchar.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -288,6 +302,7 @@ HDRS fputs.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -301,6 +316,7 @@ HDRS puts.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -313,6 +329,7 @@ HDRS fseek.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file @@ -494,6 +511,7 @@ HDRS ftell.h DEPENDS + libc.include.errno libc.include.stdio libc.src.__support.File.file libc.src.__support.File.platform_file diff --git a/libc/src/stdio/fclose.cpp b/libc/src/stdio/fclose.cpp --- a/libc/src/stdio/fclose.cpp +++ b/libc/src/stdio/fclose.cpp @@ -9,12 +9,18 @@ #include "src/stdio/fclose.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fclose, (::FILE * stream)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->close(); + int result = reinterpret_cast<__llvm_libc::File *>(stream)->close(); + if (result != 0) { + errno = result; + return EOF; + } + return 0; } } // namespace __llvm_libc diff --git a/libc/src/stdio/fflush.cpp b/libc/src/stdio/fflush.cpp --- a/libc/src/stdio/fflush.cpp +++ b/libc/src/stdio/fflush.cpp @@ -9,12 +9,18 @@ #include "src/stdio/fflush.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->flush(); + int result = reinterpret_cast<__llvm_libc::File *>(stream)->flush(); + if (result != 0) { + errno = result; + return EOF; + } + return 0; } } // namespace __llvm_libc diff --git a/libc/src/stdio/fgetc.cpp b/libc/src/stdio/fgetc.cpp --- a/libc/src/stdio/fgetc.cpp +++ b/libc/src/stdio/fgetc.cpp @@ -9,13 +9,18 @@ #include "src/stdio/fgetc.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fgetc, (::FILE * stream)) { unsigned char c; - size_t r = reinterpret_cast<__llvm_libc::File *>(stream)->read(&c, 1); + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->read(&c, 1); + size_t r = result.value; + if (result.has_error()) + errno = result.error; + if (r != 1) return EOF; return c; diff --git a/libc/src/stdio/fgetc_unlocked.cpp b/libc/src/stdio/fgetc_unlocked.cpp --- a/libc/src/stdio/fgetc_unlocked.cpp +++ b/libc/src/stdio/fgetc_unlocked.cpp @@ -9,14 +9,18 @@ #include "src/stdio/fgetc_unlocked.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fgetc_unlocked, (::FILE * stream)) { unsigned char c; - size_t r = + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->read_unlocked(&c, 1); + size_t r = result.value; + if (result.has_error()) + errno = result.error; if (r != 1) return EOF; return c; diff --git a/libc/src/stdio/fgets.cpp b/libc/src/stdio/fgets.cpp --- a/libc/src/stdio/fgets.cpp +++ b/libc/src/stdio/fgets.cpp @@ -9,6 +9,7 @@ #include "src/stdio/fgets.h" #include "src/__support/File/file.h" +#include #include #include @@ -28,7 +29,11 @@ int i = 0; for (; i < (count - 1) && c != '\n'; ++i) { - size_t r = stream->read_unlocked(&c, 1); + auto result = stream->read_unlocked(&c, 1); + size_t r = result.value; + if (result.has_error()) + errno = result.error; + if (r != 1) break; str[i] = c; diff --git a/libc/src/stdio/fopen.cpp b/libc/src/stdio/fopen.cpp --- a/libc/src/stdio/fopen.cpp +++ b/libc/src/stdio/fopen.cpp @@ -9,13 +9,19 @@ #include "src/stdio/fopen.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(::FILE *, fopen, (const char *__restrict name, const char *__restrict mode)) { - return reinterpret_cast<::FILE *>(__llvm_libc::openfile(name, mode)); + auto result = __llvm_libc::openfile(name, mode); + if (!result.has_value()) { + errno = result.error(); + return nullptr; + } + return reinterpret_cast<::FILE *>(result.value()); } } // namespace __llvm_libc diff --git a/libc/src/stdio/fopencookie.cpp b/libc/src/stdio/fopencookie.cpp --- a/libc/src/stdio/fopencookie.cpp +++ b/libc/src/stdio/fopencookie.cpp @@ -23,7 +23,7 @@ cookie_io_functions_t ops; }; -size_t write_func(File *f, const void *data, size_t size) { +FileIOResult write_func(File *f, const void *data, size_t size) { auto cookie_file = reinterpret_cast(f); if (cookie_file->ops.write == nullptr) return 0; @@ -31,7 +31,7 @@ reinterpret_cast(data), size); } -size_t read_func(File *f, void *data, size_t size) { +FileIOResult read_func(File *f, void *data, size_t size) { auto cookie_file = reinterpret_cast(f); if (cookie_file->ops.read == nullptr) return 0; @@ -39,11 +39,10 @@ reinterpret_cast(data), size); } -long seek_func(File *f, long offset, int whence) { +ErrorOr seek_func(File *f, long offset, int whence) { auto cookie_file = reinterpret_cast(f); if (cookie_file->ops.seek == nullptr) { - errno = EINVAL; - return -1; + return Error(EINVAL); } off64_t offset64 = offset; int result = cookie_file->ops.seek(cookie_file->cookie, &offset64, whence); diff --git a/libc/src/stdio/fputc.cpp b/libc/src/stdio/fputc.cpp --- a/libc/src/stdio/fputc.cpp +++ b/libc/src/stdio/fputc.cpp @@ -9,13 +9,19 @@ #include "src/stdio/fputc.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fputc, (int c, ::FILE *stream)) { unsigned char uc = static_cast(c); - size_t written = reinterpret_cast<__llvm_libc::File *>(stream)->write(&uc, 1); + + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->write(&uc, 1); + if (result.has_error()) + errno = result.error; + size_t written = result.value; + if (1 != written) { // The stream should be in an error state in this case. return EOF; diff --git a/libc/src/stdio/fputs.cpp b/libc/src/stdio/fputs.cpp --- a/libc/src/stdio/fputs.cpp +++ b/libc/src/stdio/fputs.cpp @@ -10,6 +10,7 @@ #include "src/__support/CPP/string_view.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { @@ -18,8 +19,12 @@ (const char *__restrict str, ::FILE *__restrict stream)) { cpp::string_view str_view(str); - size_t written = reinterpret_cast<__llvm_libc::File *>(stream)->write( + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->write( str, str_view.size()); + if (result.has_error()) + errno = result.error; + size_t written = result.value; + if (str_view.size() != written) { // The stream should be in an error state in this case. return EOF; diff --git a/libc/src/stdio/fread.cpp b/libc/src/stdio/fread.cpp --- a/libc/src/stdio/fread.cpp +++ b/libc/src/stdio/fread.cpp @@ -9,6 +9,7 @@ #include "src/stdio/fread.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { @@ -18,9 +19,11 @@ ::FILE *stream)) { if (size == 0 || nmemb == 0) return 0; - return reinterpret_cast<__llvm_libc::File *>(stream)->read(buffer, - size * nmemb) / - size; + auto result = + reinterpret_cast<__llvm_libc::File *>(stream)->read(buffer, size * nmemb); + if (result.has_error()) + errno = result.error; + return result.value / size; } } // namespace __llvm_libc diff --git a/libc/src/stdio/fread_unlocked.cpp b/libc/src/stdio/fread_unlocked.cpp --- a/libc/src/stdio/fread_unlocked.cpp +++ b/libc/src/stdio/fread_unlocked.cpp @@ -9,6 +9,7 @@ #include "src/stdio/fread_unlocked.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { @@ -16,8 +17,13 @@ LLVM_LIBC_FUNCTION(size_t, fread_unlocked, (void *__restrict buffer, size_t size, size_t nmemb, ::FILE *stream)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->read_unlocked( + if (size == 0 || nmemb == 0) + return 0; + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->read_unlocked( buffer, size * nmemb); + if (result.has_error()) + errno = result.error; + return result.value / size; } } // namespace __llvm_libc diff --git a/libc/src/stdio/fseek.cpp b/libc/src/stdio/fseek.cpp --- a/libc/src/stdio/fseek.cpp +++ b/libc/src/stdio/fseek.cpp @@ -9,12 +9,19 @@ #include "src/stdio/fseek.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, fseek, (::FILE * stream, long offset, int whence)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->seek(offset, whence); + auto result = + reinterpret_cast<__llvm_libc::File *>(stream)->seek(offset, whence); + if (!result.has_value()) { + errno = result.error(); + return -1; + } + return 0; } } // namespace __llvm_libc diff --git a/libc/src/stdio/ftell.cpp b/libc/src/stdio/ftell.cpp --- a/libc/src/stdio/ftell.cpp +++ b/libc/src/stdio/ftell.cpp @@ -9,12 +9,18 @@ #include "src/stdio/ftell.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(long, ftell, (::FILE * stream)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->tell(); + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->tell(); + if (!result.has_value()) { + errno = result.error(); + return -1; + } + return result.value(); } } // namespace __llvm_libc diff --git a/libc/src/stdio/fwrite.cpp b/libc/src/stdio/fwrite.cpp --- a/libc/src/stdio/fwrite.cpp +++ b/libc/src/stdio/fwrite.cpp @@ -9,6 +9,7 @@ #include "src/stdio/fwrite.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { @@ -18,9 +19,12 @@ ::FILE *stream)) { if (size == 0 || nmemb == 0) return 0; - return reinterpret_cast<__llvm_libc::File *>(stream)->write(buffer, - size * nmemb) / - size; + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->write( + buffer, size * nmemb); + if (result.has_error()) + errno = result.error; + + return result.value / size; } } // namespace __llvm_libc diff --git a/libc/src/stdio/fwrite_unlocked.cpp b/libc/src/stdio/fwrite_unlocked.cpp --- a/libc/src/stdio/fwrite_unlocked.cpp +++ b/libc/src/stdio/fwrite_unlocked.cpp @@ -9,6 +9,7 @@ #include "src/stdio/fwrite_unlocked.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { @@ -16,8 +17,15 @@ LLVM_LIBC_FUNCTION(size_t, fwrite_unlocked, (const void *__restrict buffer, size_t size, size_t nmemb, ::FILE *stream)) { - return reinterpret_cast<__llvm_libc::File *>(stream)->write_unlocked( + + if (size == 0 || nmemb == 0) + return 0; + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->write_unlocked( buffer, size * nmemb); + if (result.has_error()) + errno = result.error; + + return result.value / size; } } // namespace __llvm_libc diff --git a/libc/src/stdio/getc.cpp b/libc/src/stdio/getc.cpp --- a/libc/src/stdio/getc.cpp +++ b/libc/src/stdio/getc.cpp @@ -9,13 +9,18 @@ #include "src/stdio/getc.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, getc, (::FILE * stream)) { unsigned char c; - size_t r = reinterpret_cast<__llvm_libc::File *>(stream)->read(&c, 1); + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->read(&c, 1); + size_t r = result.value; + if (result.has_error()) + errno = result.error; + if (r != 1) return EOF; return c; diff --git a/libc/src/stdio/getc_unlocked.cpp b/libc/src/stdio/getc_unlocked.cpp --- a/libc/src/stdio/getc_unlocked.cpp +++ b/libc/src/stdio/getc_unlocked.cpp @@ -9,14 +9,19 @@ #include "src/stdio/getc_unlocked.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, getc_unlocked, (::FILE * stream)) { unsigned char c; - size_t r = + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->read_unlocked(&c, 1); + size_t r = result.value; + if (result.has_error()) + errno = result.error; + if (r != 1) return EOF; return c; diff --git a/libc/src/stdio/printf_core/file_writer.cpp b/libc/src/stdio/printf_core/file_writer.cpp --- a/libc/src/stdio/printf_core/file_writer.cpp +++ b/libc/src/stdio/printf_core/file_writer.cpp @@ -16,8 +16,9 @@ namespace printf_core { int FileWriter::write(const char *__restrict to_write, size_t len) { - int written = file->write_unlocked(to_write, len); - if (written != static_cast(len)) + auto result = file->write_unlocked(to_write, len); + int written = result.value; + if (written != static_cast(len) || result.has_error()) written = FILE_WRITE_ERROR; if (file->error_unlocked()) written = FILE_STATUS_ERROR; diff --git a/libc/src/stdio/putc.cpp b/libc/src/stdio/putc.cpp --- a/libc/src/stdio/putc.cpp +++ b/libc/src/stdio/putc.cpp @@ -9,13 +9,19 @@ #include "src/stdio/putc.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, putc, (int c, ::FILE *stream)) { unsigned char uc = static_cast(c); - size_t written = reinterpret_cast<__llvm_libc::File *>(stream)->write(&uc, 1); + + auto result = reinterpret_cast<__llvm_libc::File *>(stream)->write(&uc, 1); + if (result.has_error()) + errno = result.error; + size_t written = result.value; + if (1 != written) { // The stream should be in an error state in this case. return EOF; diff --git a/libc/src/stdio/putchar.cpp b/libc/src/stdio/putchar.cpp --- a/libc/src/stdio/putchar.cpp +++ b/libc/src/stdio/putchar.cpp @@ -9,13 +9,19 @@ #include "src/stdio/putchar.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, putchar, (int c)) { unsigned char uc = static_cast(c); - size_t written = __llvm_libc::stdout->write(&uc, 1); + + auto result = __llvm_libc::stdout->write(&uc, 1); + if (result.has_error()) + errno = result.error; + size_t written = result.value; + if (1 != written) { // The stream should be in an error state in this case. return EOF; diff --git a/libc/src/stdio/puts.cpp b/libc/src/stdio/puts.cpp --- a/libc/src/stdio/puts.cpp +++ b/libc/src/stdio/puts.cpp @@ -10,18 +10,25 @@ #include "src/__support/CPP/string_view.h" #include "src/__support/File/file.h" +#include #include namespace __llvm_libc { LLVM_LIBC_FUNCTION(int, puts, (const char *__restrict str)) { cpp::string_view str_view(str); - size_t written = __llvm_libc::stdout->write(str, str_view.size()); + auto result = __llvm_libc::stdout->write(str, str_view.size()); + if (result.has_error()) + errno = result.error; + size_t written = result.value; if (str_view.size() != written) { // The stream should be in an error state in this case. return EOF; } - written = __llvm_libc::stdout->write("\n", 1); + result = __llvm_libc::stdout->write("\n", 1); + if (result.has_error()) + errno = result.error; + written = result.value; if (1 != written) { // The stream should be in an error state in this case. return EOF; diff --git a/libc/src/stdio/scanf_core/file_reader.cpp b/libc/src/stdio/scanf_core/file_reader.cpp --- a/libc/src/stdio/scanf_core/file_reader.cpp +++ b/libc/src/stdio/scanf_core/file_reader.cpp @@ -15,7 +15,8 @@ char FileReader::get_char() { char tiny_buff = 0; - if (file->read_unlocked(&tiny_buff, 1) != 1) + auto result = file->read_unlocked(&tiny_buff, 1); + if (result.value != 1 || result.has_error()) return 0; return tiny_buff; } diff --git a/libc/test/src/__support/File/file_test.cpp b/libc/test/src/__support/File/file_test.cpp --- a/libc/test/src/__support/File/file_test.cpp +++ b/libc/test/src/__support/File/file_test.cpp @@ -7,15 +7,17 @@ //===----------------------------------------------------------------------===// #include "src/__support/File/file.h" +#include "src/__support/error_or.h" #include "utils/UnitTest/MemoryMatcher.h" #include "utils/UnitTest/Test.h" -#include #include #include using ModeFlags = __llvm_libc::File::ModeFlags; using MemoryView = __llvm_libc::memory::testing::MemoryView; +using __llvm_libc::ErrorOr; +using __llvm_libc::FileIOResult; class StringFile : public __llvm_libc::File { static constexpr size_t SIZE = 512; @@ -24,9 +26,10 @@ size_t eof_marker; bool write_append; - static size_t str_read(__llvm_libc::File *f, void *data, size_t len); - static size_t str_write(__llvm_libc::File *f, const void *data, size_t len); - static long str_seek(__llvm_libc::File *f, long offset, int whence); + static FileIOResult str_read(__llvm_libc::File *f, void *data, size_t len); + static FileIOResult str_write(__llvm_libc::File *f, const void *data, + size_t len); + static ErrorOr str_seek(__llvm_libc::File *f, long offset, int whence); static int str_close(__llvm_libc::File *f) { return 0; } static int str_flush(__llvm_libc::File *f) { return 0; } @@ -67,7 +70,8 @@ } }; -size_t StringFile::str_read(__llvm_libc::File *f, void *data, size_t len) { +FileIOResult StringFile::str_read(__llvm_libc::File *f, void *data, + size_t len) { StringFile *sf = static_cast(f); if (sf->pos >= sf->eof_marker) return 0; @@ -78,8 +82,8 @@ return i; } -size_t StringFile::str_write(__llvm_libc::File *f, const void *data, - size_t len) { +FileIOResult StringFile::str_write(__llvm_libc::File *f, const void *data, + size_t len) { StringFile *sf = static_cast(f); if (sf->write_append) sf->pos = sf->eof_marker; @@ -94,7 +98,8 @@ return i; } -long StringFile::str_seek(__llvm_libc::File *f, long offset, int whence) { +ErrorOr StringFile::str_seek(__llvm_libc::File *f, long offset, + int whence) { StringFile *sf = static_cast(f); if (whence == SEEK_SET) sf->pos = offset; @@ -119,7 +124,7 @@ StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w"); - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream ASSERT_EQ(f->flush(), 0); EXPECT_EQ(f->get_pos(), sizeof(data)); // Data should now be available @@ -127,10 +132,10 @@ f->reset(); ASSERT_EQ(f->get_pos(), size_t(0)); - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream // The second write should trigger a buffer flush. - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_GE(f->get_pos(), size_t(0)); ASSERT_EQ(f->flush(), 0); EXPECT_EQ(f->get_pos(), 2 * sizeof(data)); @@ -139,11 +144,13 @@ EXPECT_MEM_EQ(src1, dst1); char read_data[sizeof(data)]; - // This is not a readable file. - EXPECT_EQ(f->read(read_data, sizeof(data)), size_t(0)); - EXPECT_TRUE(f->error()); - EXPECT_NE(errno, 0); - errno = 0; + { + // This is not a readable file. + auto result = f->read(read_data, sizeof(data)); + EXPECT_EQ(result.value, size_t(0)); + EXPECT_TRUE(f->error()); + EXPECT_TRUE(result.has_error()); + } ASSERT_EQ(f->close(), 0); } @@ -162,8 +169,8 @@ StringFile *f_full = new_string_file(file_buffer_full, FILE_BUFFER_SIZE, _IOFBF, false, "w"); - ASSERT_EQ(sizeof(data), f_line->write(data, sizeof(data))); - ASSERT_EQ(sizeof(data), f_full->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f_line->write(data, sizeof(data)).value); + ASSERT_EQ(sizeof(data), f_full->write(data, sizeof(data)).value); EXPECT_EQ(f_line->get_pos(), size_t(6)); // buffer after the newline EXPECT_EQ(f_full->get_pos(), size_t(0)); // buffer all of data @@ -175,12 +182,12 @@ const char data2[] = "longer for an \n overflow"; - ASSERT_EQ(sizeof(data2), f_line->write(data2, sizeof(data2))); + ASSERT_EQ(sizeof(data2), f_line->write(data2, sizeof(data2)).value); // The line buffer's initial contents should be " file\0" // Writing data2 should write up until the newline, even though that doesn't // all fit in the buffer. - ASSERT_EQ(sizeof(data2), f_full->write(data2, sizeof(data2))); + ASSERT_EQ(sizeof(data2), f_full->write(data2, sizeof(data2)).value); // The full buffer's initial contents should be "hello\n file\0" // Writing data2 should cause a flush of the buffer, as well as the remainder // to be written directly since it doesn't fit in the buffer. @@ -215,7 +222,7 @@ StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, _IONBF, false, "w"); - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_EQ(f->get_pos(), sizeof(data)); // no buffering means this is written immediately. EXPECT_STREQ(f->get_str(), data); @@ -233,7 +240,7 @@ constexpr size_t READ_SIZE = sizeof(initial_content) / 2; char read_data[READ_SIZE]; - ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); EXPECT_FALSE(f->iseof()); // Reading less than file buffer worth will still read one // full buffer worth of data. @@ -246,14 +253,14 @@ // Reading another buffer worth should read out everything in // the file. - ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); EXPECT_FALSE(f->iseof()); MemoryView src2(initial_content + READ_SIZE, READ_SIZE), dst2(read_data, READ_SIZE); EXPECT_MEM_EQ(src2, dst2); // Another read should trigger an EOF. - ASSERT_GT(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_GT(READ_SIZE, f->read(read_data, READ_SIZE).value); EXPECT_TRUE(f->iseof()); // Reset the pos to the beginning of the file which should allow @@ -261,15 +268,17 @@ for (size_t i = 0; i < READ_SIZE; ++i) read_data[i] = 0; f->seek(0, SEEK_SET); - ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); MemoryView src3(initial_content, READ_SIZE), dst3(read_data, READ_SIZE); EXPECT_MEM_EQ(src3, dst3); - // This is not a writable file. - EXPECT_EQ(f->write(initial_content, sizeof(initial_content)), size_t(0)); - EXPECT_TRUE(f->error()); - EXPECT_NE(errno, 0); - errno = 0; + { + // This is not a writable file. + auto result = f->write(initial_content, sizeof(initial_content)); + EXPECT_EQ(result.value, size_t(0)); + EXPECT_TRUE(f->error()); + EXPECT_TRUE(result.has_error()); + } ASSERT_EQ(f->close(), 0); } @@ -285,13 +294,13 @@ constexpr size_t READ_SIZE = 5; char data[READ_SIZE]; data[READ_SIZE - 1] = '\0'; - ASSERT_EQ(f->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "1234"); - ASSERT_EQ(f->seek(5, SEEK_CUR), 0); - ASSERT_EQ(f->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(f->seek(5, SEEK_CUR).value(), 0); + ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "0987"); - ASSERT_EQ(f->seek(-5, SEEK_CUR), 0); - ASSERT_EQ(f->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(f->seek(-5, SEEK_CUR).value(), 0); + ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "9098"); ASSERT_EQ(f->close(), 0); } @@ -307,14 +316,17 @@ constexpr size_t READ_SIZE = 5; char read_data[READ_SIZE]; - // This is not a readable file. - ASSERT_EQ(f->read(read_data, READ_SIZE), size_t(0)); - EXPECT_TRUE(f->error()); - EXPECT_NE(errno, 0); - errno = 0; + + { + // This is not a readable file. + auto result = f->read(read_data, READ_SIZE); + EXPECT_EQ(result.value, size_t(0)); + EXPECT_TRUE(f->error()); + EXPECT_TRUE(result.has_error()); + } // Write should succeed but will be buffered in the file stream. - ASSERT_EQ(f->write(write_data, sizeof(write_data)), sizeof(write_data)); + ASSERT_EQ(f->write(write_data, sizeof(write_data)).value, sizeof(write_data)); EXPECT_EQ(f->get_pos(), size_t(0)); // Flushing will write to the file. EXPECT_EQ(f->flush(), int(0)); @@ -330,14 +342,14 @@ StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream - ASSERT_EQ(f->seek(0, SEEK_SET), 0); + ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); // Seek flushes the stream buffer so we can read the previously written data. char read_data[sizeof(data)]; - ASSERT_EQ(f->read(read_data, sizeof(data)), sizeof(data)); + ASSERT_EQ(f->read(read_data, sizeof(data)).value, sizeof(data)); EXPECT_STREQ(read_data, data); ASSERT_EQ(f->close(), 0); @@ -353,7 +365,7 @@ constexpr size_t READ_SIZE = sizeof(initial_content) / 2; char read_data[READ_SIZE]; - ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); EXPECT_FALSE(f->iseof()); // Reading less than file buffer worth will still read one // full buffer worth of data. @@ -364,9 +376,9 @@ MemoryView src1(initial_content, READ_SIZE), dst1(read_data, READ_SIZE); EXPECT_MEM_EQ(src1, dst1); - ASSERT_EQ(f->seek(0, SEEK_SET), 0); + ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); const char write_data[] = "hello, file"; - ASSERT_EQ(sizeof(write_data), f->write(write_data, sizeof(write_data))); + ASSERT_EQ(sizeof(write_data), f->write(write_data, sizeof(write_data)).value); EXPECT_STREQ(file_buffer, write_data); ASSERT_EQ(f->flush(), 0); MemoryView dst2(f->get_str(), sizeof(write_data)), @@ -385,16 +397,16 @@ new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "a+"); f->reset_and_fill(initial_content, sizeof(initial_content)); - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream ASSERT_EQ(f->flush(), 0); // The flush should write |data| to the endof the file. EXPECT_EQ(f->get_pos(), sizeof(data) + sizeof(initial_content)); - ASSERT_EQ(f->seek(0, SEEK_SET), 0); + ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); // Seeking to the beginning of the file should not affect the place // where write happens. - ASSERT_EQ(sizeof(data), f->write(data, sizeof(data))); + ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); ASSERT_EQ(f->flush(), 0); EXPECT_EQ(f->get_pos(), sizeof(data) * 2 + sizeof(initial_content)); MemoryView src1(initial_content, sizeof(initial_content)), @@ -408,10 +420,10 @@ EXPECT_MEM_EQ(src3, dst3); // Reads can happen from any point. - ASSERT_EQ(f->seek(0, SEEK_SET), 0); + ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); constexpr size_t READ_SIZE = 10; char read_data[READ_SIZE]; - ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE)); + ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); MemoryView src4(initial_content, READ_SIZE), dst4(read_data, READ_SIZE); EXPECT_MEM_EQ(src4, dst4); @@ -426,7 +438,7 @@ StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w"); - ASSERT_EQ(WRITE_SIZE, f->write(WRITE_DATA, WRITE_SIZE)); + ASSERT_EQ(WRITE_SIZE, f->write(WRITE_DATA, WRITE_SIZE).value); // Since data much larger than the buffer is being written, all of it should // be available in the file without a flush operation. EXPECT_EQ(f->get_pos(), sizeof(WRITE_DATA)); @@ -442,9 +454,9 @@ StringFile *f_lbf = new_string_file(nullptr, 0, _IOLBF, true, "w"); StringFile *f_nbf = new_string_file(nullptr, 0, _IONBF, true, "w"); - ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE)); - ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE)); - ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE)); + ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE).value); + ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE).value); + ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE).value); // Since there is no buffer space, all of the written data should // be available in the file without a flush operation. EXPECT_EQ(f_fbf->get_pos(), sizeof(WRITE_DATA)); @@ -473,9 +485,9 @@ StringFile *f_nbf = new_string_file(file_buffer_nbf, FILE_BUFFER_SIZE, _IONBF, false, "w"); - ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE)); - ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE)); - ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE)); + ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE).value); + ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE).value); + ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE).value); ASSERT_FALSE(f_fbf->error_unlocked()); ASSERT_FALSE(f_lbf->error_unlocked()); diff --git a/libc/test/src/__support/File/platform_file_test.cpp b/libc/test/src/__support/File/platform_file_test.cpp --- a/libc/test/src/__support/File/platform_file_test.cpp +++ b/libc/test/src/__support/File/platform_file_test.cpp @@ -19,18 +19,18 @@ constexpr char FILENAME[] = "testdata/create_write_close_and_readback.test"; File *file = __llvm_libc::openfile(FILENAME, "w"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE).value, TEXT_SIZE); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "r"); ASSERT_FALSE(file == nullptr); char data[sizeof(TEXT)]; - ASSERT_EQ(file->read(data, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->read(data, TEXT_SIZE).value, TEXT_SIZE); data[TEXT_SIZE] = '\0'; ASSERT_STREQ(data, TEXT); // Reading more data should trigger EOF. - ASSERT_EQ(file->read(data, TEXT_SIZE), size_t(0)); + ASSERT_EQ(file->read(data, TEXT_SIZE).value, size_t(0)); ASSERT_TRUE(file->iseof()); ASSERT_EQ(file->close(), 0); @@ -40,17 +40,17 @@ constexpr char FILENAME[] = "testdata/create_write_seek_and_readback.test"; File *file = __llvm_libc::openfile(FILENAME, "w+"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE).value, TEXT_SIZE); - ASSERT_EQ(file->seek(0, SEEK_SET), 0); + ASSERT_EQ(file->seek(0, SEEK_SET).value(), 0); char data[sizeof(TEXT)]; - ASSERT_EQ(file->read(data, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->read(data, TEXT_SIZE).value, TEXT_SIZE); data[TEXT_SIZE] = '\0'; ASSERT_STREQ(data, TEXT); // Reading more data should trigger EOF. - ASSERT_EQ(file->read(data, TEXT_SIZE), size_t(0)); + ASSERT_EQ(file->read(data, TEXT_SIZE).value, size_t(0)); ASSERT_TRUE(file->iseof()); ASSERT_EQ(file->close(), 0); @@ -60,26 +60,26 @@ constexpr char FILENAME[] = "testdata/create_append_close_and_readback.test"; File *file = __llvm_libc::openfile(FILENAME, "w"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE).value, TEXT_SIZE); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "a"); ASSERT_FALSE(file == nullptr); constexpr char APPEND_TEXT[] = " Append Text"; constexpr size_t APPEND_TEXT_SIZE = sizeof(APPEND_TEXT) - 1; - ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE).value, APPEND_TEXT_SIZE); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "r"); ASSERT_FALSE(file == nullptr); constexpr size_t READ_SIZE = TEXT_SIZE + APPEND_TEXT_SIZE; char data[READ_SIZE + 1]; - ASSERT_EQ(file->read(data, READ_SIZE), READ_SIZE); + ASSERT_EQ(file->read(data, READ_SIZE).value, READ_SIZE); data[READ_SIZE] = '\0'; ASSERT_STREQ(data, "Hello, File Append Text"); // Reading more data should trigger EOF. - ASSERT_EQ(file->read(data, READ_SIZE), size_t(0)); + ASSERT_EQ(file->read(data, READ_SIZE).value, size_t(0)); ASSERT_TRUE(file->iseof()); ASSERT_EQ(file->close(), 0); @@ -89,23 +89,23 @@ constexpr char FILENAME[] = "testdata/create_append_seek_and_readback.test"; File *file = __llvm_libc::openfile(FILENAME, "w"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE).value, TEXT_SIZE); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "a+"); ASSERT_FALSE(file == nullptr); constexpr char APPEND_TEXT[] = " Append Text"; constexpr size_t APPEND_TEXT_SIZE = sizeof(APPEND_TEXT) - 1; - ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE).value, APPEND_TEXT_SIZE); - ASSERT_EQ(file->seek(-APPEND_TEXT_SIZE, SEEK_END), 0); + ASSERT_EQ(file->seek(-APPEND_TEXT_SIZE, SEEK_END).value(), 0); char data[APPEND_TEXT_SIZE + 1]; - ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE).value, APPEND_TEXT_SIZE); data[APPEND_TEXT_SIZE] = '\0'; ASSERT_STREQ(data, APPEND_TEXT); // Reading more data should trigger EOF. - ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE), size_t(0)); + ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE).value, size_t(0)); ASSERT_TRUE(file->iseof()); ASSERT_EQ(file->close(), 0); @@ -124,7 +124,7 @@ constexpr int REPEAT = 5; for (int i = 0; i < REPEAT; ++i) { - ASSERT_EQ(file->write(write_data, DATA_SIZE), DATA_SIZE); + ASSERT_EQ(file->write(write_data, DATA_SIZE).value, DATA_SIZE); } ASSERT_EQ(file->close(), 0); @@ -132,13 +132,13 @@ ASSERT_FALSE(file == nullptr); constexpr size_t READ_SIZE = DATA_SIZE * REPEAT; char data[READ_SIZE] = {0}; - ASSERT_EQ(file->read(data, READ_SIZE), READ_SIZE); + ASSERT_EQ(file->read(data, READ_SIZE).value, READ_SIZE); for (size_t i = 0; i < READ_SIZE; ++i) ASSERT_EQ(data[i], BYTE); // Reading more data should trigger EOF. - ASSERT_EQ(file->read(data, 1), size_t(0)); + ASSERT_EQ(file->read(data, 1).value, size_t(0)); ASSERT_TRUE(file->iseof()); ASSERT_EQ(file->close(), 0); @@ -149,7 +149,8 @@ File *file = __llvm_libc::openfile(FILENAME, "w"); ASSERT_FALSE(file == nullptr); constexpr char CONTENT[] = "1234567890987654321"; - ASSERT_EQ(sizeof(CONTENT) - 1, file->write(CONTENT, sizeof(CONTENT) - 1)); + ASSERT_EQ(sizeof(CONTENT) - 1, + file->write(CONTENT, sizeof(CONTENT) - 1).value); ASSERT_EQ(0, file->close()); file = __llvm_libc::openfile(FILENAME, "r"); @@ -158,13 +159,13 @@ constexpr size_t READ_SIZE = 5; char data[READ_SIZE]; data[READ_SIZE - 1] = '\0'; - ASSERT_EQ(file->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(file->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "1234"); - ASSERT_EQ(file->seek(5, SEEK_CUR), 0); - ASSERT_EQ(file->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(file->seek(5, SEEK_CUR).value(), 0); + ASSERT_EQ(file->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "0987"); - ASSERT_EQ(file->seek(-5, SEEK_CUR), 0); - ASSERT_EQ(file->read(data, READ_SIZE - 1), READ_SIZE - 1); + ASSERT_EQ(file->seek(-5, SEEK_CUR).value(), 0); + ASSERT_EQ(file->read(data, READ_SIZE - 1).value, READ_SIZE - 1); ASSERT_STREQ(data, "9098"); ASSERT_EQ(file->close(), 0); @@ -176,21 +177,21 @@ File *file = __llvm_libc::openfile(FILENAME, "w"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->read(data, 1), size_t(0)); // Cannot read + ASSERT_EQ(file->read(data, 1).value, size_t(0)); // Cannot read ASSERT_FALSE(file->iseof()); ASSERT_TRUE(file->error()); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "r"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->write(data, 1), size_t(0)); // Cannot write + ASSERT_EQ(file->write(data, 1).value, size_t(0)); // Cannot write ASSERT_FALSE(file->iseof()); ASSERT_TRUE(file->error()); ASSERT_EQ(file->close(), 0); file = __llvm_libc::openfile(FILENAME, "a"); ASSERT_FALSE(file == nullptr); - ASSERT_EQ(file->read(data, 1), size_t(0)); // Cannot read + ASSERT_EQ(file->read(data, 1).value, size_t(0)); // Cannot read ASSERT_FALSE(file->iseof()); ASSERT_TRUE(file->error()); ASSERT_EQ(file->close(), 0); diff --git a/libc/test/src/stdio/fileop_test.cpp b/libc/test/src/stdio/fileop_test.cpp --- a/libc/test/src/stdio/fileop_test.cpp +++ b/libc/test/src/stdio/fileop_test.cpp @@ -78,6 +78,11 @@ __llvm_libc::clearerr(file); ASSERT_EQ(__llvm_libc::ferror(file), 0); + errno = 0; + ASSERT_EQ(__llvm_libc::fwrite("nothing", 1, 1, file), size_t(0)); + ASSERT_NE(errno, 0); + errno = 0; + ASSERT_EQ(__llvm_libc::fclose(file), 0); // Now try puts. @@ -90,6 +95,12 @@ __llvm_libc::clearerr(file); ASSERT_EQ(__llvm_libc::ferror(file), 0); + // This is not a readable file. + errno = 0; + ASSERT_EQ(__llvm_libc::fread(data, 1, 1, file), size_t(0)); + ASSERT_NE(errno, 0); + errno = 0; + ASSERT_EQ(0, __llvm_libc::fclose(file)); file = __llvm_libc::fopen(FILENAME, "r"); @@ -100,6 +111,21 @@ read_data[sizeof(CONTENT) - 1] = '\0'; ASSERT_STREQ(read_data, CONTENT); ASSERT_EQ(__llvm_libc::fclose(file), 0); + + // Check that the other functions correctly set errno. + + errno = 0; + ASSERT_NE(__llvm_libc::fseek(file, 0, SEEK_SET), 0); + EXPECT_NE(errno, 0); + + errno = 0; + ASSERT_NE(__llvm_libc::fclose(file), 0); + EXPECT_NE(errno, 0); + + errno = 0; + ASSERT_EQ(__llvm_libc::fopen("INVALID FILE NAME", "r"), + static_cast(nullptr)); + EXPECT_NE(errno, 0); } TEST(LlvmLibcFILETest, FFlush) { @@ -132,6 +158,7 @@ constexpr size_t WRITE_NMEMB = sizeof(WRITE_DATA) / sizeof(MyStruct); constexpr char FILENAME[] = "testdata/fread_fwrite.test"; + errno = 0; FILE *file = __llvm_libc::fopen(FILENAME, "w"); ASSERT_FALSE(file == nullptr); ASSERT_EQ(size_t(0), __llvm_libc::fwrite(WRITE_DATA, 0, 1, file)); diff --git a/libc/test/src/stdio/ftell_test.cpp b/libc/test/src/stdio/ftell_test.cpp --- a/libc/test/src/stdio/ftell_test.cpp +++ b/libc/test/src/stdio/ftell_test.cpp @@ -16,6 +16,7 @@ #include "src/stdio/setvbuf.h" #include "utils/UnitTest/Test.h" +#include #include class LlvmLibcFTellTest : public __llvm_libc::testing::Test { @@ -53,6 +54,10 @@ ASSERT_EQ(size_t(__llvm_libc::ftell(file)), READ_SIZE); ASSERT_EQ(0, __llvm_libc::fclose(file)); + + errno = 0; + ASSERT_EQ(__llvm_libc::ftell(file), long(-1)); + ASSERT_NE(errno, 0); } };