fread: cope with >2GiB totals.

The FILE::_read function pointer takes an int rather than a size_t, so
we need to be careful to break large reads up for it.

fwrite() hasn't (yet) been optimized in this way, so it's immune for
now, but add the corresponding write test anyway.

Seeking already uses a off64_t function pointer where possible, so I
don't think there's anything more to be done there.

No other function pointers in FILE are relevant.

Bug: https://issuetracker.google.com/240139009
Test: treehugger
Change-Id: Ife2537e10f178bb0d980719592539f4b00b67031
diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp
index 08df2eb..27813a6 100644
--- a/libc/stdio/stdio.cpp
+++ b/libc/stdio/stdio.cpp
@@ -1129,7 +1129,9 @@
 
   // Read directly into the caller's buffer.
   while (total > 0) {
-    ssize_t bytes_read = (*fp->_read)(fp->_cookie, dst, total);
+    // The _read function pointer takes an int instead of a size_t.
+    int chunk_size = MIN(total, INT_MAX);
+    ssize_t bytes_read = (*fp->_read)(fp->_cookie, dst, chunk_size);
     if (bytes_read <= 0) {
       fp->_flags |= (bytes_read == 0) ? __SEOF : __SERR;
       break;
diff --git a/tests/stdio_test.cpp b/tests/stdio_test.cpp
index 87031f6..5bc6567 100644
--- a/tests/stdio_test.cpp
+++ b/tests/stdio_test.cpp
@@ -25,6 +25,7 @@
 #include <sys/cdefs.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/sysinfo.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <wchar.h>
@@ -2938,3 +2939,27 @@
 
   fclose(fp);
 }
+
+static int64_t GetTotalRamGiB() {
+  struct sysinfo si;
+  sysinfo(&si);
+  return (static_cast<int64_t>(si.totalram) * si.mem_unit) / 1024 / 1024 / 1024;
+}
+
+TEST(STDIO_TEST, fread_int_overflow) {
+  if (GetTotalRamGiB() <= 4) GTEST_SKIP() << "not enough memory";
+
+  const size_t too_big_for_an_int = 0x80000000ULL;
+  std::vector<char> buf(too_big_for_an_int);
+  std::unique_ptr<FILE, decltype(&fclose)> fp{fopen("/dev/zero", "re"), fclose};
+  ASSERT_EQ(too_big_for_an_int, fread(&buf[0], 1, too_big_for_an_int, fp.get()));
+}
+
+TEST(STDIO_TEST, fwrite_int_overflow) {
+  if (GetTotalRamGiB() <= 4) GTEST_SKIP() << "not enough memory";
+
+  const size_t too_big_for_an_int = 0x80000000ULL;
+  std::vector<char> buf(too_big_for_an_int);
+  std::unique_ptr<FILE, decltype(&fclose)> fp{fopen("/dev/null", "we"), fclose};
+  ASSERT_EQ(too_big_for_an_int, fwrite(&buf[0], 1, too_big_for_an_int, fp.get()));
+}