Implement funopen64.

Bug: http://b/24807045
Change-Id: I161920978161389be34b707cc6ce8e05f760d552
diff --git a/libc/stdio/local.h b/libc/stdio/local.h
index 524e6de..7fe339a 100644
--- a/libc/stdio/local.h
+++ b/libc/stdio/local.h
@@ -72,7 +72,6 @@
 
 	// Function pointers used by `funopen`.
 	// Note that `_seek` is ignored if `_seek64` (in __sfileext) is set.
-	// TODO: implement `funopen64`.
 	// TODO: NetBSD has `funopen2` which corrects the `int`s to `size_t`s.
 	// TODO: glibc has `fopencookie` which passes the function pointers in a struct.
 	void* _cookie;	/* cookie passed to io functions */
diff --git a/libc/stdio/stdio.cpp b/libc/stdio/stdio.cpp
index 23b6971..2139621 100644
--- a/libc/stdio/stdio.cpp
+++ b/libc/stdio/stdio.cpp
@@ -538,3 +538,56 @@
   *pos = ftello64(fp);
   return (*pos == -1);
 }
+
+static FILE* __funopen(const void* cookie,
+                       int (*read_fn)(void*, char*, int),
+                       int (*write_fn)(void*, const char*, int),
+                       int (*close_fn)(void*)) {
+  if (read_fn == nullptr && write_fn == nullptr) {
+    errno = EINVAL;
+    return nullptr;
+  }
+
+  FILE* fp = __sfp();
+  if (fp == nullptr) return nullptr;
+
+  if (read_fn != nullptr && write_fn != nullptr) {
+    fp->_flags = __SRW;
+  } else if (read_fn != nullptr) {
+    fp->_flags = __SRD;
+  } else if (write_fn != nullptr) {
+    fp->_flags = __SWR;
+  }
+
+  fp->_file = -1;
+  fp->_cookie = const_cast<void*>(cookie); // The funopen(3) API is incoherent.
+  fp->_read = read_fn;
+  fp->_write = write_fn;
+  fp->_close = close_fn;
+
+  return fp;
+}
+
+FILE* funopen(const void* cookie,
+              int (*read_fn)(void*, char*, int),
+              int (*write_fn)(void*, const char*, int),
+              fpos_t (*seek_fn)(void*, fpos_t, int),
+              int (*close_fn)(void*)) {
+  FILE* fp = __funopen(cookie, read_fn, write_fn, close_fn);
+  if (fp != nullptr) {
+    fp->_seek = seek_fn;
+  }
+  return fp;
+}
+
+FILE* funopen64(const void* cookie,
+                int (*read_fn)(void*, char*, int),
+                int (*write_fn)(void*, const char*, int),
+                fpos64_t (*seek_fn)(void*, fpos64_t, int),
+                int (*close_fn)(void*)) {
+  FILE* fp = __funopen(cookie, read_fn, write_fn, close_fn);
+  if (fp != nullptr) {
+    _EXT(fp)->_seek64 = seek_fn;
+  }
+  return fp;
+}