diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -157,7 +157,7 @@ SimpleMacroDef<"_IONBF", "2">, SimpleMacroDef<"EOF", "-1">, ]; - let Types = ["size_t", "FILE", "cookie_io_functions_t"]; + let Types = ["off_t", "size_t", "FILE", "cookie_io_functions_t"]; } def StdlibAPI : PublicAPI<"stdlib.h"> { diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -385,6 +385,7 @@ libc.src.stdio.fread libc.src.stdio.fread_unlocked libc.src.stdio.fseek + libc.src.stdio.ftell libc.src.stdio.funlockfile libc.src.stdio.fwrite libc.src.stdio.fwrite_unlocked diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -162,6 +162,7 @@ .llvm-libc-macros.stdio_macros .llvm-libc-types.cookie_io_functions_t .llvm-libc-types.FILE + .llvm-libc-types.off_t .llvm-libc-types.size_t ) diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -998,7 +998,7 @@ HeaderSpec StdIO = HeaderSpec< "stdio.h", [], // Macros - [], // Types + [OffTType], // Types [], // Enumerations [ FunctionSpec< diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -563,6 +563,11 @@ [ArgSpec, ArgSpec] >, + FunctionSpec< + "ftell", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "putc", RetValSpec, 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 @@ -28,7 +28,9 @@ using WriteFunc = size_t(File *, const void *, size_t); using ReadFunc = size_t(File *, void *, size_t); - using SeekFunc = int(File *, long, int); + // The SeekFunc is expected to return the current offset of the external + // file position indicator. + using SeekFunc = long(File *, long, int); using CloseFunc = int(File *); using FlushFunc = int(File *); @@ -191,6 +193,8 @@ int seek(long offset, int whence); + long tell(); + // If buffer has data written to it, flush it out. Does nothing if the // buffer is currently being used as a read buffer. int flush() { @@ -283,6 +287,10 @@ // library. File *openfile(const char *path, const char *mode); +// The platform_file library should implement it if it relevant for that +// platform. +int get_fileno(File *f); + extern File *stdin; extern File *stdout; extern File *stderr; 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 @@ -294,7 +294,28 @@ // Reset the eof flag as a seek might move the file positon to some place // readable. eof = false; - return platform_seek(this, offset, whence); + long platform_pos = platform_seek(this, offset, whence); + if (platform_pos >= 0) + return 0; + else + return -1; +} + +long 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; + 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() { 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 @@ -22,7 +22,7 @@ size_t write_func(File *, const void *, size_t); size_t read_func(File *, void *, size_t); -int seek_func(File *, long, int); +long seek_func(File *, long, int); int close_func(File *); int flush_func(File *); @@ -71,10 +71,12 @@ return ret; } -int seek_func(File *f, long offset, int whence) { +long 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); + result = ret; #elif defined(SYS__llseek) long result; long ret = __llvm_libc::syscall_impl(SYS__llseek, lf->get_fd(), offset >> 32, @@ -87,7 +89,7 @@ errno = -ret; return -1; } - return 0; + return result; } int close_func(File *f) { @@ -164,6 +166,11 @@ return file; } +int get_fileno(File *f) { + auto *lf = reinterpret_cast(f); + return lf->get_fd(); +} + constexpr size_t STDIN_BUFFER_SIZE = 512; char stdin_buffer[STDIN_BUFFER_SIZE]; static LinuxFile StdIn(0, stdin_buffer, STDIN_BUFFER_SIZE, _IOFBF, false, 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 @@ -416,6 +416,18 @@ libc.src.stdio.printf_core.vfprintf_internal ) +add_entrypoint_object( + ftell + SRCS + ftell.cpp + HDRS + ftell.h + DEPENDS + libc.include.stdio + libc.src.__support.File.file + libc.src.__support.File.platform_file +) + add_entrypoint_object( remove ALIAS 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 @@ -39,14 +39,18 @@ reinterpret_cast(data), size); } -int seek_func(File *f, long offset, int whence) { +long seek_func(File *f, long offset, int whence) { auto cookie_file = reinterpret_cast(f); if (cookie_file->ops.seek == nullptr) { errno = EINVAL; return -1; } off64_t offset64 = offset; - return cookie_file->ops.seek(cookie_file->cookie, &offset64, whence); + int result = cookie_file->ops.seek(cookie_file->cookie, &offset64, whence); + if (result == 0) + return offset64; + else + return -1; } int close_func(File *f) { diff --git a/libc/src/stdio/ftell.h b/libc/src/stdio/ftell.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/ftell.h @@ -0,0 +1,20 @@ +//===-- Implementation header of ftell --------------------------*- 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_STDIO_FTELL_H +#define LLVM_LIBC_SRC_STDIO_FTELL_H + +#include + +namespace __llvm_libc { + +long ftell(::FILE *f); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_FTELL_H diff --git a/libc/src/stdio/ftell.cpp b/libc/src/stdio/ftell.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/ftell.cpp @@ -0,0 +1,20 @@ +//===-- Implementation of ftell -------------------------------------------===// +// +// 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 "src/stdio/ftell.h" +#include "src/__support/File/file.h" + +#include + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(long, ftell, (::FILE * stream)) { + return reinterpret_cast<__llvm_libc::File *>(stream)->tell(); +} + +} // namespace __llvm_libc 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 @@ -26,7 +26,7 @@ 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 int str_seek(__llvm_libc::File *f, long offset, int whence); + static long 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; } @@ -94,7 +94,7 @@ return i; } -int StringFile::str_seek(__llvm_libc::File *f, long offset, int whence) { +long StringFile::str_seek(__llvm_libc::File *f, long offset, int whence) { StringFile *sf = static_cast(f); if (whence == SEEK_SET) sf->pos = offset; @@ -102,7 +102,7 @@ sf->pos += offset; if (whence == SEEK_END) sf->pos = SIZE + offset; - return 0; + return sf->pos; } StringFile *new_string_file(char *buffer, size_t buflen, int bufmode, diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -235,6 +235,25 @@ libc.src.stdio.fwrite ) +add_libc_unittest( + ftell_test + SUITE + libc_stdio_unittests + SRCS + ftell_test.cpp + DEPENDS + libc.include.errno + libc.include.stdio + libc.src.stdio.fclose + libc.src.stdio.fflush + libc.src.stdio.fopen + libc.src.stdio.fread + libc.src.stdio.fseek + libc.src.stdio.ftell + libc.src.stdio.fwrite + libc.src.stdio.setvbuf +) + add_subdirectory(printf_core) add_subdirectory(scanf_core) add_subdirectory(testdata) diff --git a/libc/test/src/stdio/ftell_test.cpp b/libc/test/src/stdio/ftell_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/ftell_test.cpp @@ -0,0 +1,63 @@ +//===-- Unittests for ftell -----------------------------------------------===// +// +// 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 "src/stdio/fclose.h" +#include "src/stdio/fflush.h" +#include "src/stdio/fopen.h" +#include "src/stdio/fread.h" +#include "src/stdio/fseek.h" +#include "src/stdio/ftell.h" +#include "src/stdio/fwrite.h" +#include "src/stdio/setvbuf.h" +#include "utils/UnitTest/Test.h" + +#include + +class LlvmLibcFTellTest : public __llvm_libc::testing::Test { +protected: + void test_with_bufmode(int bufmode) { + constexpr char FILENAME[] = "testdata/ftell.test"; + // We will set a special buffer to the file so that we guarantee buffering. + constexpr size_t BUFFER_SIZE = 1024; + char buffer[BUFFER_SIZE]; + ::FILE *file = __llvm_libc::fopen(FILENAME, "w+"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(__llvm_libc::setvbuf(file, buffer, bufmode, BUFFER_SIZE), 0); + + // Include few '\n' chars to test when |bufmode| is _IOLBF. + constexpr char CONTENT[] = "12\n345\n6789"; + constexpr size_t WRITE_SIZE = sizeof(CONTENT) - 1; + ASSERT_EQ(WRITE_SIZE, __llvm_libc::fwrite(CONTENT, 1, WRITE_SIZE, file)); + // The above write should have buffered the written data and not have + // trasferred it to the underlying stream. But, ftell operation should + // still return the correct effective offset. + ASSERT_EQ(size_t(__llvm_libc::ftell(file)), WRITE_SIZE); + + long offset = 5; + ASSERT_EQ(0, __llvm_libc::fseek(file, offset, SEEK_SET)); + ASSERT_EQ(__llvm_libc::ftell(file), offset); + ASSERT_EQ(0, __llvm_libc::fseek(file, -offset, SEEK_END)); + ASSERT_EQ(size_t(__llvm_libc::ftell(file)), size_t(WRITE_SIZE - offset)); + + ASSERT_EQ(0, __llvm_libc::fseek(file, 0, SEEK_SET)); + constexpr size_t READ_SIZE = WRITE_SIZE / 2; + char data[READ_SIZE]; + // Reading a small amount will actually read out much more data and + // buffer it. But, ftell should return the correct effective offset. + ASSERT_EQ(READ_SIZE, __llvm_libc::fread(data, 1, READ_SIZE, file)); + ASSERT_EQ(size_t(__llvm_libc::ftell(file)), READ_SIZE); + + ASSERT_EQ(0, __llvm_libc::fclose(file)); + } +}; + +TEST_F(LlvmLibcFTellTest, TellWithFBF) { test_with_bufmode(_IOFBF); } + +TEST_F(LlvmLibcFTellTest, TellWithNBF) { test_with_bufmode(_IONBF); } + +TEST_F(LlvmLibcFTellTest, TellWithLBF) { test_with_bufmode(_IOLBF); }