diff --git a/libc/include/stdio.h b/libc/include/stdio.h index 98f0fd755..a227904a3 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -49,7 +49,8 @@ __BEGIN_DECLS -typedef off_t fpos_t; /* stdio file position type */ +typedef off_t fpos_t; +typedef off64_t fpos64_t; struct __sFILE; typedef struct __sFILE FILE; @@ -118,8 +119,6 @@ FILE *freopen(const char * __restrict, const char * __restrict, FILE * __restrict); int fscanf(FILE * __restrict, const char * __restrict, ...) __scanflike(2, 3); -int fseek(FILE *, long, int); -long ftell(FILE *); size_t fwrite(const void * __restrict, size_t, size_t, FILE * __restrict); int getc(FILE *); int getchar(void); @@ -166,25 +165,26 @@ char* tempnam(const char*, const char*) #endif #endif -extern int rename(const char*, const char*); -extern int renameat(int, const char*, int, const char*); +int rename(const char*, const char*); +int renameat(int, const char*, int, const char*); +int fseek(FILE*, long, int); +long ftell(FILE*); #if defined(__USE_FILE_OFFSET64) -/* Not possible. */ -int fgetpos(FILE * __restrict, fpos_t * __restrict) - __attribute__((__error__("not available with _FILE_OFFSET_BITS=64"))); -int fsetpos(FILE *, const fpos_t *) - __attribute__((__error__("not available with _FILE_OFFSET_BITS=64"))); -int fseeko(FILE *, off_t, int) - __attribute__((__error__("not available with _FILE_OFFSET_BITS=64"))); -off_t ftello(FILE *) - __attribute__((__error__("not available with _FILE_OFFSET_BITS=64"))); +int fgetpos(FILE*, fpos_t*) __RENAME(fgetpos64); +int fsetpos(FILE*, const fpos_t*) __RENAME(fsetpos64); +int fseeko(FILE*, off_t, int) __RENAME(fseeko64); +off_t ftello(FILE*) __RENAME(ftello64); #else -int fgetpos(FILE * __restrict, fpos_t * __restrict); -int fsetpos(FILE *, const fpos_t *); -int fseeko(FILE *, off_t, int); -off_t ftello(FILE *); +int fgetpos(FILE*, fpos_t*); +int fsetpos(FILE*, const fpos_t*); +int fseeko(FILE*, off_t, int); +off_t ftello(FILE*); #endif +int fgetpos64(FILE*, fpos64_t*); +int fsetpos64(FILE*, const fpos64_t*); +int fseeko64(FILE*, off64_t, int); +off64_t ftello64(FILE*); #if __ISO_C_VISIBLE >= 1999 || __BSD_VISIBLE int snprintf(char * __restrict, size_t, const char * __restrict, ...) @@ -256,6 +256,7 @@ int fileno_unlocked(FILE*); /* * Stdio function-access interface. + * TODO: __USE_FILE_OFFSET64 */ FILE *funopen(const void *, int (*)(void *, char *, int), diff --git a/libc/libc.arm.brillo.map b/libc/libc.arm.brillo.map index 4de7b2b9b..d8d503301 100644 --- a/libc/libc.arm.brillo.map +++ b/libc/libc.arm.brillo.map @@ -1228,8 +1228,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.arm.map b/libc/libc.arm.map index 1f26a605f..df8030f90 100644 --- a/libc/libc.arm.map +++ b/libc/libc.arm.map @@ -1228,8 +1228,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map index fbeaa0d13..838a97892 100644 --- a/libc/libc.arm64.map +++ b/libc/libc.arm64.map @@ -1152,8 +1152,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.map.txt b/libc/libc.map.txt index e5cae49e6..9317f81f5 100644 --- a/libc/libc.map.txt +++ b/libc/libc.map.txt @@ -1255,8 +1255,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.mips.brillo.map b/libc/libc.mips.brillo.map index 98ae6f889..683fb249e 100644 --- a/libc/libc.mips.brillo.map +++ b/libc/libc.mips.brillo.map @@ -1213,8 +1213,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.mips.map b/libc/libc.mips.map index 79bca0b83..f72d84fc1 100644 --- a/libc/libc.mips.map +++ b/libc/libc.mips.map @@ -1213,8 +1213,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map index fbeaa0d13..838a97892 100644 --- a/libc/libc.mips64.map +++ b/libc/libc.mips64.map @@ -1152,8 +1152,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.x86.brillo.map b/libc/libc.x86.brillo.map index 8792e6a7e..090d5c500 100644 --- a/libc/libc.x86.brillo.map +++ b/libc/libc.x86.brillo.map @@ -1212,8 +1212,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.x86.map b/libc/libc.x86.map index 4835a4e12..f3bda0b53 100644 --- a/libc/libc.x86.map +++ b/libc/libc.x86.map @@ -1212,8 +1212,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map index fbeaa0d13..838a97892 100644 --- a/libc/libc.x86_64.map +++ b/libc/libc.x86_64.map @@ -1152,8 +1152,11 @@ LIBC_N { __pwrite_chk; __pwrite64_chk; __write_chk; + fgetpos64; fileno_unlocked; freeifaddrs; + fseeko64; + fsetpos64; getgrgid_r; getgrnam_r; getifaddrs; diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp index 7749e0e45..23b69715a 100644 --- a/libc/stdio/stdio.cpp +++ b/libc/stdio/stdio.cpp @@ -433,11 +433,10 @@ static off64_t __seek_unlocked(FILE* fp, off64_t offset, int whence) { } } -// TODO: _FILE_OFFSET_BITS=64. -static off_t __ftello_unlocked(FILE* fp) { +static off64_t __ftello64_unlocked(FILE* fp) { // Find offset of underlying I/O object, then adjust for buffered bytes. __sflush(fp); // May adjust seek offset on append stream. - fpos_t result = __seek_unlocked(fp, 0, SEEK_CUR); + off64_t result = __seek_unlocked(fp, 0, SEEK_CUR); if (result == -1) { return -1; } @@ -457,29 +456,34 @@ static off_t __ftello_unlocked(FILE* fp) { return result; } -// TODO: _FILE_OFFSET_BITS=64. -int fseeko(FILE* fp, off_t offset, int whence) { +int __fseeko64(FILE* fp, off64_t offset, int whence, int off_t_bits) { ScopedFileLock sfl(fp); // Change any SEEK_CUR to SEEK_SET, and check `whence` argument. // After this, whence is either SEEK_SET or SEEK_END. if (whence == SEEK_CUR) { - fpos_t current_offset = __ftello_unlocked(fp); + fpos64_t current_offset = __ftello64_unlocked(fp); if (current_offset == -1) { - return EOF; + return -1; } offset += current_offset; whence = SEEK_SET; } else if (whence != SEEK_SET && whence != SEEK_END) { errno = EINVAL; - return EOF; + return -1; + } + + // If our caller has a 32-bit interface, refuse to go past a 32-bit file offset. + if (off_t_bits == 32 && offset > LONG_MAX) { + errno = EOVERFLOW; + return -1; } if (fp->_bf._base == NULL) __smakebuf(fp); // Flush unwritten data and attempt the seek. if (__sflush(fp) || __seek_unlocked(fp, offset, whence) == -1) { - return EOF; + return -1; } // Success: clear EOF indicator and discard ungetc() data. @@ -491,34 +495,46 @@ int fseeko(FILE* fp, off_t offset, int whence) { return 0; } -// TODO: _FILE_OFFSET_BITS=64. -int fseek(FILE* fp, long offset, int whence) { - return fseeko(fp, offset, whence); +int fseeko(FILE* fp, off_t offset, int whence) { + static_assert(sizeof(off_t) == sizeof(long), "sizeof(off_t) != sizeof(long)"); + return __fseeko64(fp, offset, whence, 8*sizeof(off_t)); +} +__strong_alias(fseek, fseeko); + +int fseeko64(FILE* fp, off64_t offset, int whence) { + return __fseeko64(fp, offset, whence, 8*sizeof(off_t)); +} + +int fsetpos(FILE* fp, const fpos_t* pos) { + return fseeko(fp, *pos, SEEK_SET); +} + +int fsetpos64(FILE* fp, const fpos64_t* pos) { + return fseeko64(fp, *pos, SEEK_SET); } -// TODO: _FILE_OFFSET_BITS=64. off_t ftello(FILE* fp) { - ScopedFileLock sfl(fp); - return __ftello_unlocked(fp); -} - -// TODO: _FILE_OFFSET_BITS=64 -long ftell(FILE* fp) { - off_t offset = ftello(fp); - if (offset > LONG_MAX) { + static_assert(sizeof(off_t) == sizeof(long), "sizeof(off_t) != sizeof(long)"); + off64_t result = ftello64(fp); + if (result > LONG_MAX) { errno = EOVERFLOW; return -1; } - return offset; + return result; +} +__strong_alias(ftell, ftello); + +off64_t ftello64(FILE* fp) { + ScopedFileLock sfl(fp); + return __ftello64_unlocked(fp); } -// TODO: _FILE_OFFSET_BITS=64 int fgetpos(FILE* fp, fpos_t* pos) { *pos = ftello(fp); return (*pos == -1); } -// TODO: _FILE_OFFSET_BITS=64 -int fsetpos(FILE* fp, const fpos_t* pos) { - return fseeko(fp, *pos, SEEK_SET); +int fgetpos64(FILE* fp, fpos64_t* pos) { + *pos = ftello64(fp); + return (*pos == -1); } diff --git a/tests/stdio_test.cpp b/tests/stdio_test.cpp index a7df78448..89bf04a8e 100644 --- a/tests/stdio_test.cpp +++ b/tests/stdio_test.cpp @@ -280,7 +280,7 @@ TEST(STDIO_TEST, snprintf_n) { EXPECT_EQ(1234, i); EXPECT_STREQ("a n b", buf); #else - GTEST_LOG_(INFO) << "This test does nothing.\n"; + GTEST_LOG_(INFO) << "This test does nothing on glibc.\n"; #endif } @@ -831,7 +831,7 @@ TEST(STDIO_TEST, open_memstream_EINVAL) { ASSERT_EQ(nullptr, open_memstream(&p, nullptr)); ASSERT_EQ(EINVAL, errno); #else - GTEST_LOG_(INFO) << "This test does nothing.\n"; + GTEST_LOG_(INFO) << "This test does nothing on glibc.\n"; #endif } @@ -962,7 +962,7 @@ static void test_fwrite_after_fread(size_t n) { // But hitting EOF doesn't prevent us from writing... errno = 0; - ASSERT_EQ(1U, fwrite("2", 1, 1, fp)) << errno; + ASSERT_EQ(1U, fwrite("2", 1, 1, fp)) << strerror(errno); // And if we rewind, everything's there. rewind(fp); @@ -1011,7 +1011,7 @@ TEST(STDIO_TEST, fread_after_fseek) { ASSERT_EQ(memcmp(file_data+cur_location, buffer, 8192), 0); // Small backwards seek to verify fseek does not reuse the internal buffer. - ASSERT_EQ(0, fseek(fp, -22, SEEK_CUR)); + ASSERT_EQ(0, fseek(fp, -22, SEEK_CUR)) << strerror(errno); cur_location = static_cast(ftell(fp)); ASSERT_EQ(22U, fread(buffer, 1, 22, fp)); ASSERT_EQ(memcmp(file_data+cur_location, buffer, 22), 0); @@ -1121,3 +1121,90 @@ TEST(STDIO_TEST, lots_of_concurrent_files) { delete tfs[i]; } } + +static void AssertFileOffsetAt(FILE* fp, off64_t offset) { + EXPECT_EQ(offset, ftell(fp)); + EXPECT_EQ(offset, ftello(fp)); + fpos_t pos; + fpos64_t pos64; + EXPECT_EQ(0, fgetpos(fp, &pos)); + EXPECT_EQ(0, fgetpos64(fp, &pos64)); +#if defined(__BIONIC__) + EXPECT_EQ(offset, static_cast(pos)); + EXPECT_EQ(offset, static_cast(pos64)); +#else + GTEST_LOG_(INFO) << "glibc's fpos_t is opaque.\n"; +#endif +} + +TEST(STDIO_TEST, seek_tell_family_smoke) { + TemporaryFile tf; + FILE* fp = fdopen(tf.fd, "w+"); + + // Initially we should be at 0. + AssertFileOffsetAt(fp, 0); + + // Seek to offset 8192. + ASSERT_EQ(0, fseek(fp, 8192, SEEK_SET)); + AssertFileOffsetAt(fp, 8192); + fpos_t eight_k_pos; + ASSERT_EQ(0, fgetpos(fp, &eight_k_pos)); + + // Seek forward another 8192... + ASSERT_EQ(0, fseek(fp, 8192, SEEK_CUR)); + AssertFileOffsetAt(fp, 8192 + 8192); + fpos64_t sixteen_k_pos64; + ASSERT_EQ(0, fgetpos64(fp, &sixteen_k_pos64)); + + // Seek back 8192... + ASSERT_EQ(0, fseek(fp, -8192, SEEK_CUR)); + AssertFileOffsetAt(fp, 8192); + + // Since we haven't written anything, the end is also at 0. + ASSERT_EQ(0, fseek(fp, 0, SEEK_END)); + AssertFileOffsetAt(fp, 0); + + // Check that our fpos64_t from 16KiB works... + ASSERT_EQ(0, fsetpos64(fp, &sixteen_k_pos64)); + AssertFileOffsetAt(fp, 8192 + 8192); + // ...as does our fpos_t from 8192. + ASSERT_EQ(0, fsetpos(fp, &eight_k_pos)); + AssertFileOffsetAt(fp, 8192); + + // Do fseeko and fseeko64 work too? + ASSERT_EQ(0, fseeko(fp, 1234, SEEK_SET)); + AssertFileOffsetAt(fp, 1234); + ASSERT_EQ(0, fseeko64(fp, 5678, SEEK_SET)); + AssertFileOffsetAt(fp, 5678); + + fclose(fp); +} + +TEST(STDIO_TEST, fseek_fseeko_EINVAL) { + TemporaryFile tf; + FILE* fp = fdopen(tf.fd, "w+"); + + // Bad whence. + errno = 0; + ASSERT_EQ(-1, fseek(fp, 0, 123)); + ASSERT_EQ(EINVAL, errno); + errno = 0; + ASSERT_EQ(-1, fseeko(fp, 0, 123)); + ASSERT_EQ(EINVAL, errno); + errno = 0; + ASSERT_EQ(-1, fseeko64(fp, 0, 123)); + ASSERT_EQ(EINVAL, errno); + + // Bad offset. + errno = 0; + ASSERT_EQ(-1, fseek(fp, -1, SEEK_SET)); + ASSERT_EQ(EINVAL, errno); + errno = 0; + ASSERT_EQ(-1, fseeko(fp, -1, SEEK_SET)); + ASSERT_EQ(EINVAL, errno); + errno = 0; + ASSERT_EQ(-1, fseeko64(fp, -1, SEEK_SET)); + ASSERT_EQ(EINVAL, errno); + + fclose(fp); +}