Home | History | Annotate | Download | only in stdio
      1 /*	$OpenBSD: findfp.c,v 1.15 2013/12/17 16:33:27 deraadt Exp $ */
      2 /*-
      3  * Copyright (c) 1990, 1993
      4  *	The Regents of the University of California.  All rights reserved.
      5  *
      6  * This code is derived from software contributed to Berkeley by
      7  * Chris Torek.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted provided that the following conditions
     11  * are met:
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in the
     16  *    documentation and/or other materials provided with the distribution.
     17  * 3. Neither the name of the University nor the names of its contributors
     18  *    may be used to endorse or promote products derived from this software
     19  *    without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     31  * SUCH DAMAGE.
     32  */
     33 
     34 #include <stdio.h>
     35 
     36 #include <errno.h>
     37 #include <fcntl.h>
     38 #include <limits.h>
     39 #include <stdlib.h>
     40 #include <string.h>
     41 #include <sys/param.h>
     42 #include <sys/stat.h>
     43 #include <unistd.h>
     44 
     45 #include "local.h"
     46 #include "glue.h"
     47 #include "private/ErrnoRestorer.h"
     48 #include "private/thread_private.h"
     49 
     50 #define ALIGNBYTES (sizeof(uintptr_t) - 1)
     51 #define ALIGN(p) (((uintptr_t)(p) + ALIGNBYTES) &~ ALIGNBYTES)
     52 
     53 #define	NDYNAMIC 10		/* add ten more whenever necessary */
     54 
     55 #define std(flags, file) \
     56     {0,0,0,flags,file,{0,0},0,__sF+file,__sclose,__sread,nullptr,__swrite, \
     57     {(unsigned char *)(__sFext+file), 0},nullptr,0,{0},{0},{0,0},0,0}
     58 
     59 _THREAD_PRIVATE_MUTEX(__sfp_mutex);
     60 
     61 // TODO: when we no longer have to support both clang and GCC, we can simplify all this.
     62 #define SBUF_INIT {0,0}
     63 #if defined(__LP64__)
     64 #define MBSTATE_T_INIT {{0},{0}}
     65 #else
     66 #define MBSTATE_T_INIT {{0}}
     67 #endif
     68 #define WCHAR_IO_DATA_INIT {MBSTATE_T_INIT,MBSTATE_T_INIT,{0},0,0}
     69 
     70 static struct __sfileext __sFext[3] = {
     71   { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
     72   { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
     73   { SBUF_INIT, WCHAR_IO_DATA_INIT, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, false, __sseek64 },
     74 };
     75 
     76 // __sF is exported for backwards compatibility. Until M, we didn't have symbols
     77 // for stdin/stdout/stderr; they were macros accessing __sF.
     78 FILE __sF[3] = {
     79   std(__SRD, STDIN_FILENO),
     80   std(__SWR, STDOUT_FILENO),
     81   std(__SWR|__SNBF, STDERR_FILENO),
     82 };
     83 
     84 FILE* stdin = &__sF[0];
     85 FILE* stdout = &__sF[1];
     86 FILE* stderr = &__sF[2];
     87 
     88 struct glue __sglue = { NULL, 3, __sF };
     89 static struct glue* lastglue = &__sglue;
     90 
     91 class ScopedFileLock {
     92  public:
     93   ScopedFileLock(FILE* fp) : fp_(fp) {
     94     FLOCKFILE(fp_);
     95   }
     96   ~ScopedFileLock() {
     97     FUNLOCKFILE(fp_);
     98   }
     99 
    100  private:
    101   FILE* fp_;
    102 };
    103 
    104 static glue* moreglue(int n) {
    105   static FILE empty;
    106 
    107   char* data = new char[sizeof(glue) + ALIGNBYTES + n * sizeof(FILE) + n * sizeof(__sfileext)];
    108   if (data == nullptr) return nullptr;
    109 
    110   glue* g = reinterpret_cast<glue*>(data);
    111   FILE* p = reinterpret_cast<FILE*>(ALIGN(data + sizeof(*g)));
    112   __sfileext* pext = reinterpret_cast<__sfileext*>(ALIGN(data + sizeof(*g)) + n * sizeof(FILE));
    113   g->next = NULL;
    114   g->niobs = n;
    115   g->iobs = p;
    116   while (--n >= 0) {
    117     *p = empty;
    118     _FILEEXT_SETUP(p, pext);
    119     p++;
    120     pext++;
    121   }
    122   return g;
    123 }
    124 
    125 /*
    126  * Find a free FILE for fopen et al.
    127  */
    128 FILE* __sfp(void) {
    129 	FILE *fp;
    130 	int n;
    131 	struct glue *g;
    132 
    133 	_THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex);
    134 	for (g = &__sglue; g != NULL; g = g->next) {
    135 		for (fp = g->iobs, n = g->niobs; --n >= 0; fp++)
    136 			if (fp->_flags == 0)
    137 				goto found;
    138 	}
    139 
    140 	/* release lock while mallocing */
    141 	_THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex);
    142 	if ((g = moreglue(NDYNAMIC)) == NULL)
    143 		return (NULL);
    144 	_THREAD_PRIVATE_MUTEX_LOCK(__sfp_mutex);
    145 	lastglue->next = g;
    146 	lastglue = g;
    147 	fp = g->iobs;
    148 found:
    149 	fp->_flags = 1;		/* reserve this slot; caller sets real flags */
    150 	_THREAD_PRIVATE_MUTEX_UNLOCK(__sfp_mutex);
    151 	fp->_p = NULL;		/* no current pointer */
    152 	fp->_w = 0;		/* nothing to read or write */
    153 	fp->_r = 0;
    154 	fp->_bf._base = NULL;	/* no buffer */
    155 	fp->_bf._size = 0;
    156 	fp->_lbfsize = 0;	/* not line buffered */
    157 	fp->_file = -1;		/* no file */
    158 
    159 	fp->_lb._base = NULL;	/* no line buffer */
    160 	fp->_lb._size = 0;
    161 	_FILEEXT_INIT(fp);
    162 
    163 	// Caller sets cookie, _read/_write etc.
    164 	// We explicitly clear _seek and _seek64 to prevent subtle bugs.
    165 	fp->_seek = nullptr;
    166 	_EXT(fp)->_seek64 = nullptr;
    167 
    168 	return fp;
    169 }
    170 
    171 extern "C" __LIBC_HIDDEN__ void __libc_stdio_cleanup(void) {
    172   // Equivalent to fflush(nullptr), but without all the locking since we're shutting down anyway.
    173   _fwalk(__sflush);
    174 }
    175 
    176 static FILE* __fopen(int fd, int flags) {
    177 #if !defined(__LP64__)
    178   if (fd > SHRT_MAX) {
    179     errno = EMFILE;
    180     return nullptr;
    181   }
    182 #endif
    183 
    184   FILE* fp = __sfp();
    185   if (fp != nullptr) {
    186     fp->_file = fd;
    187     fp->_flags = flags;
    188     fp->_cookie = fp;
    189     fp->_read = __sread;
    190     fp->_write = __swrite;
    191     fp->_close = __sclose;
    192     _EXT(fp)->_seek64 = __sseek64;
    193   }
    194   return fp;
    195 }
    196 
    197 FILE* fopen(const char* file, const char* mode) {
    198   int oflags;
    199   int flags = __sflags(mode, &oflags);
    200   if (flags == 0) return nullptr;
    201 
    202   int fd = open(file, oflags, DEFFILEMODE);
    203   if (fd == -1) {
    204     return nullptr;
    205   }
    206 
    207   FILE* fp = __fopen(fd, flags);
    208   if (fp == nullptr) {
    209     ErrnoRestorer errno_restorer;
    210     close(fd);
    211     return nullptr;
    212   }
    213 
    214   // When opening in append mode, even though we use O_APPEND,
    215   // we need to seek to the end so that ftell() gets the right
    216   // answer.  If the user then alters the seek pointer, or
    217   // the file extends, this will fail, but there is not much
    218   // we can do about this.  (We could set __SAPP and check in
    219   // fseek and ftell.)
    220   // TODO: check in __sseek instead.
    221   if (oflags & O_APPEND) __sseek64(fp, 0, SEEK_END);
    222 
    223   return fp;
    224 }
    225 __strong_alias(fopen64, fopen);
    226 
    227 FILE* fdopen(int fd, const char* mode) {
    228   int oflags;
    229   int flags = __sflags(mode, &oflags);
    230   if (flags == 0) return nullptr;
    231 
    232   // Make sure the mode the user wants is a subset of the actual mode.
    233   int fdflags = fcntl(fd, F_GETFL, 0);
    234   if (fdflags < 0) return nullptr;
    235   int tmp = fdflags & O_ACCMODE;
    236   if (tmp != O_RDWR && (tmp != (oflags & O_ACCMODE))) {
    237     errno = EINVAL;
    238     return nullptr;
    239   }
    240 
    241   // If opened for appending, but underlying descriptor does not have
    242   // O_APPEND bit set, assert __SAPP so that __swrite() will lseek to
    243   // end before each write.
    244   // TODO: use fcntl(2) to set O_APPEND instead.
    245   if ((oflags & O_APPEND) && !(fdflags & O_APPEND)) flags |= __SAPP;
    246 
    247   // If close-on-exec was requested, then turn it on if not already.
    248   if ((oflags & O_CLOEXEC) && !((tmp = fcntl(fd, F_GETFD)) & FD_CLOEXEC)) {
    249     fcntl(fd, F_SETFD, tmp | FD_CLOEXEC);
    250   }
    251 
    252   return __fopen(fd, flags);
    253 }
    254 
    255 // Re-direct an existing, open (probably) file to some other file.
    256 // ANSI is written such that the original file gets closed if at
    257 // all possible, no matter what.
    258 // TODO: rewrite this mess completely.
    259 FILE* freopen(const char* file, const char* mode, FILE* fp) {
    260   int oflags;
    261   int flags = __sflags(mode, &oflags);
    262   if (flags == 0) {
    263     fclose(fp);
    264     return nullptr;
    265   }
    266 
    267   ScopedFileLock sfl(fp);
    268 
    269   // There are actually programs that depend on being able to "freopen"
    270   // descriptors that weren't originally open.  Keep this from breaking.
    271   // Remember whether the stream was open to begin with, and which file
    272   // descriptor (if any) was associated with it.  If it was attached to
    273   // a descriptor, defer closing it; freopen("/dev/stdin", "r", stdin)
    274   // should work.  This is unnecessary if it was not a Unix file.
    275   int isopen, wantfd;
    276   if (fp->_flags == 0) {
    277     fp->_flags = __SEOF; // Hold on to it.
    278     isopen = 0;
    279     wantfd = -1;
    280   } else {
    281     // Flush the stream; ANSI doesn't require this.
    282     if (fp->_flags & __SWR) __sflush(fp);
    283 
    284     // If close is NULL, closing is a no-op, hence pointless.
    285     isopen = fp->_close != NULL;
    286     if ((wantfd = fp->_file) < 0 && isopen) {
    287         (*fp->_close)(fp->_cookie);
    288         isopen = 0;
    289     }
    290   }
    291 
    292   // Get a new descriptor to refer to the new file.
    293   int fd = open(file, oflags, DEFFILEMODE);
    294   if (fd < 0 && isopen) {
    295     // If out of fd's close the old one and try again.
    296     if (errno == ENFILE || errno == EMFILE) {
    297       (*fp->_close)(fp->_cookie);
    298       isopen = 0;
    299       fd = open(file, oflags, DEFFILEMODE);
    300     }
    301   }
    302 
    303   int sverrno = errno;
    304 
    305   // Finish closing fp.  Even if the open succeeded above, we cannot
    306   // keep fp->_base: it may be the wrong size.  This loses the effect
    307   // of any setbuffer calls, but stdio has always done this before.
    308   if (isopen && fd != wantfd) (*fp->_close)(fp->_cookie);
    309   if (fp->_flags & __SMBF) free(fp->_bf._base);
    310   fp->_w = 0;
    311   fp->_r = 0;
    312   fp->_p = NULL;
    313   fp->_bf._base = NULL;
    314   fp->_bf._size = 0;
    315   fp->_lbfsize = 0;
    316   if (HASUB(fp)) FREEUB(fp);
    317   _UB(fp)._size = 0;
    318   WCIO_FREE(fp);
    319   if (HASLB(fp)) FREELB(fp);
    320   fp->_lb._size = 0;
    321 
    322   if (fd < 0) { // Did not get it after all.
    323     fp->_flags = 0; // Release.
    324     errno = sverrno; // Restore errno in case _close clobbered it.
    325     return nullptr;
    326   }
    327 
    328   // If reopening something that was open before on a real file, try
    329   // to maintain the descriptor.  Various C library routines (perror)
    330   // assume stderr is always fd STDERR_FILENO, even if being freopen'd.
    331   if (wantfd >= 0 && fd != wantfd) {
    332     if (dup3(fd, wantfd, oflags & O_CLOEXEC) >= 0) {
    333       close(fd);
    334       fd = wantfd;
    335     }
    336   }
    337 
    338   // _file is only a short.
    339   if (fd > SHRT_MAX) {
    340       fp->_flags = 0; // Release.
    341       errno = EMFILE;
    342       return nullptr;
    343   }
    344 
    345   fp->_flags = flags;
    346   fp->_file = fd;
    347   fp->_cookie = fp;
    348   fp->_read = __sread;
    349   fp->_write = __swrite;
    350   fp->_close = __sclose;
    351   _EXT(fp)->_seek64 = __sseek64;
    352 
    353   // When opening in append mode, even though we use O_APPEND,
    354   // we need to seek to the end so that ftell() gets the right
    355   // answer.  If the user then alters the seek pointer, or
    356   // the file extends, this will fail, but there is not much
    357   // we can do about this.  (We could set __SAPP and check in
    358   // fseek and ftell.)
    359   if (oflags & O_APPEND) __sseek64(fp, 0, SEEK_END);
    360   return fp;
    361 }
    362 __strong_alias(freopen64, freopen);
    363 
    364 int fclose(FILE* fp) {
    365   if (fp->_flags == 0) {
    366     // Already freed!
    367     errno = EBADF;
    368     return EOF;
    369   }
    370 
    371   ScopedFileLock sfl(fp);
    372   WCIO_FREE(fp);
    373   int r = fp->_flags & __SWR ? __sflush(fp) : 0;
    374   if (fp->_close != NULL && (*fp->_close)(fp->_cookie) < 0) {
    375     r = EOF;
    376   }
    377   if (fp->_flags & __SMBF) free(fp->_bf._base);
    378   if (HASUB(fp)) FREEUB(fp);
    379   if (HASLB(fp)) FREELB(fp);
    380 
    381   // Poison this FILE so accesses after fclose will be obvious.
    382   fp->_file = -1;
    383   fp->_r = fp->_w = 0;
    384 
    385   // Release this FILE for reuse.
    386   fp->_flags = 0;
    387   return r;
    388 }
    389 
    390 int fileno(FILE* fp) {
    391   ScopedFileLock sfl(fp);
    392   return fileno_unlocked(fp);
    393 }
    394 
    395 int __sread(void* cookie, char* buf, int n) {
    396   FILE* fp = reinterpret_cast<FILE*>(cookie);
    397   return TEMP_FAILURE_RETRY(read(fp->_file, buf, n));
    398 }
    399 
    400 int __swrite(void* cookie, const char* buf, int n) {
    401   FILE* fp = reinterpret_cast<FILE*>(cookie);
    402   if (fp->_flags & __SAPP) {
    403     // The FILE* is in append mode, but the underlying fd doesn't have O_APPEND set.
    404     // We need to seek manually.
    405     // TODO: use fcntl(2) to set O_APPEND in fdopen(3) instead?
    406     TEMP_FAILURE_RETRY(lseek64(fp->_file, 0, SEEK_END));
    407   }
    408   return TEMP_FAILURE_RETRY(write(fp->_file, buf, n));
    409 }
    410 
    411 fpos_t __sseek(void* cookie, fpos_t offset, int whence) {
    412   FILE* fp = reinterpret_cast<FILE*>(cookie);
    413   return TEMP_FAILURE_RETRY(lseek(fp->_file, offset, whence));
    414 }
    415 
    416 off64_t __sseek64(void* cookie, off64_t offset, int whence) {
    417   FILE* fp = reinterpret_cast<FILE*>(cookie);
    418   return TEMP_FAILURE_RETRY(lseek64(fp->_file, offset, whence));
    419 }
    420 
    421 int __sclose(void* cookie) {
    422   FILE* fp = reinterpret_cast<FILE*>(cookie);
    423   return close(fp->_file);
    424 }
    425 
    426 static off64_t __seek_unlocked(FILE* fp, off64_t offset, int whence) {
    427   // Use `_seek64` if set, but fall back to `_seek`.
    428   if (_EXT(fp)->_seek64 != nullptr) {
    429     return (*_EXT(fp)->_seek64)(fp->_cookie, offset, whence);
    430   } else if (fp->_seek != nullptr) {
    431     off64_t result = (*fp->_seek)(fp->_cookie, offset, whence);
    432 #if !defined(__LP64__)
    433     // Avoid sign extension if off64_t is larger than off_t.
    434     if (result != -1) result &= 0xffffffff;
    435 #endif
    436     return result;
    437   } else {
    438     errno = ESPIPE;
    439     return -1;
    440   }
    441 }
    442 
    443 static off64_t __ftello64_unlocked(FILE* fp) {
    444   // Find offset of underlying I/O object, then adjust for buffered bytes.
    445   __sflush(fp);  // May adjust seek offset on append stream.
    446   off64_t result = __seek_unlocked(fp, 0, SEEK_CUR);
    447   if (result == -1) {
    448     return -1;
    449   }
    450 
    451   if (fp->_flags & __SRD) {
    452     // Reading.  Any unread characters (including
    453     // those from ungetc) cause the position to be
    454     // smaller than that in the underlying object.
    455     result -= fp->_r;
    456     if (HASUB(fp)) result -= fp->_ur;
    457   } else if (fp->_flags & __SWR && fp->_p != NULL) {
    458     // Writing.  Any buffered characters cause the
    459     // position to be greater than that in the
    460     // underlying object.
    461     result += fp->_p - fp->_bf._base;
    462   }
    463   return result;
    464 }
    465 
    466 int __fseeko64(FILE* fp, off64_t offset, int whence, int off_t_bits) {
    467   ScopedFileLock sfl(fp);
    468 
    469   // Change any SEEK_CUR to SEEK_SET, and check `whence` argument.
    470   // After this, whence is either SEEK_SET or SEEK_END.
    471   if (whence == SEEK_CUR) {
    472     fpos64_t current_offset = __ftello64_unlocked(fp);
    473     if (current_offset == -1) {
    474       return -1;
    475     }
    476     offset += current_offset;
    477     whence = SEEK_SET;
    478   } else if (whence != SEEK_SET && whence != SEEK_END) {
    479     errno = EINVAL;
    480     return -1;
    481   }
    482 
    483   // If our caller has a 32-bit interface, refuse to go past a 32-bit file offset.
    484   if (off_t_bits == 32 && offset > LONG_MAX) {
    485     errno = EOVERFLOW;
    486     return -1;
    487   }
    488 
    489   if (fp->_bf._base == NULL) __smakebuf(fp);
    490 
    491   // Flush unwritten data and attempt the seek.
    492   if (__sflush(fp) || __seek_unlocked(fp, offset, whence) == -1) {
    493     return -1;
    494   }
    495 
    496   // Success: clear EOF indicator and discard ungetc() data.
    497   if (HASUB(fp)) FREEUB(fp);
    498   fp->_p = fp->_bf._base;
    499   fp->_r = 0;
    500   /* fp->_w = 0; */	/* unnecessary (I think...) */
    501   fp->_flags &= ~__SEOF;
    502   return 0;
    503 }
    504 
    505 int fseeko(FILE* fp, off_t offset, int whence) {
    506   static_assert(sizeof(off_t) == sizeof(long), "sizeof(off_t) != sizeof(long)");
    507   return __fseeko64(fp, offset, whence, 8*sizeof(off_t));
    508 }
    509 __strong_alias(fseek, fseeko);
    510 
    511 int fseeko64(FILE* fp, off64_t offset, int whence) {
    512   return __fseeko64(fp, offset, whence, 8*sizeof(off_t));
    513 }
    514 
    515 int fsetpos(FILE* fp, const fpos_t* pos) {
    516   return fseeko(fp, *pos, SEEK_SET);
    517 }
    518 
    519 int fsetpos64(FILE* fp, const fpos64_t* pos) {
    520   return fseeko64(fp, *pos, SEEK_SET);
    521 }
    522 
    523 off_t ftello(FILE* fp) {
    524   static_assert(sizeof(off_t) == sizeof(long), "sizeof(off_t) != sizeof(long)");
    525   off64_t result = ftello64(fp);
    526   if (result > LONG_MAX) {
    527     errno = EOVERFLOW;
    528     return -1;
    529   }
    530   return result;
    531 }
    532 __strong_alias(ftell, ftello);
    533 
    534 off64_t ftello64(FILE* fp) {
    535   ScopedFileLock sfl(fp);
    536   return __ftello64_unlocked(fp);
    537 }
    538 
    539 int fgetpos(FILE* fp, fpos_t* pos) {
    540   *pos = ftello(fp);
    541   return (*pos == -1) ? -1 : 0;
    542 }
    543 
    544 int fgetpos64(FILE* fp, fpos64_t* pos) {
    545   *pos = ftello64(fp);
    546   return (*pos == -1) ? -1 : 0;
    547 }
    548 
    549 static FILE* __funopen(const void* cookie,
    550                        int (*read_fn)(void*, char*, int),
    551                        int (*write_fn)(void*, const char*, int),
    552                        int (*close_fn)(void*)) {
    553   if (read_fn == nullptr && write_fn == nullptr) {
    554     errno = EINVAL;
    555     return nullptr;
    556   }
    557 
    558   FILE* fp = __sfp();
    559   if (fp == nullptr) return nullptr;
    560 
    561   if (read_fn != nullptr && write_fn != nullptr) {
    562     fp->_flags = __SRW;
    563   } else if (read_fn != nullptr) {
    564     fp->_flags = __SRD;
    565   } else if (write_fn != nullptr) {
    566     fp->_flags = __SWR;
    567   }
    568 
    569   fp->_file = -1;
    570   fp->_cookie = const_cast<void*>(cookie); // The funopen(3) API is incoherent.
    571   fp->_read = read_fn;
    572   fp->_write = write_fn;
    573   fp->_close = close_fn;
    574 
    575   return fp;
    576 }
    577 
    578 FILE* funopen(const void* cookie,
    579               int (*read_fn)(void*, char*, int),
    580               int (*write_fn)(void*, const char*, int),
    581               fpos_t (*seek_fn)(void*, fpos_t, int),
    582               int (*close_fn)(void*)) {
    583   FILE* fp = __funopen(cookie, read_fn, write_fn, close_fn);
    584   if (fp != nullptr) {
    585     fp->_seek = seek_fn;
    586   }
    587   return fp;
    588 }
    589 
    590 FILE* funopen64(const void* cookie,
    591                 int (*read_fn)(void*, char*, int),
    592                 int (*write_fn)(void*, const char*, int),
    593                 fpos64_t (*seek_fn)(void*, fpos64_t, int),
    594                 int (*close_fn)(void*)) {
    595   FILE* fp = __funopen(cookie, read_fn, write_fn, close_fn);
    596   if (fp != nullptr) {
    597     _EXT(fp)->_seek64 = seek_fn;
    598   }
    599   return fp;
    600 }
    601