Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  stdio.cpp

  Sprache: C
 

/*-
 * Copyright (c) 1990, 1993
 * The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#define __BIONIC_NO_STDIO_FORTIFY
#include <stdio.h>

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include <android/fdsan.h>

#include <async_safe/log.h>

#include "local.h"
#include "private/ErrnoRestorer.h"
#include "private/FdPath.h"
#include "private/__bionic_get_shell_path.h"
#include "private/bionic_fortify.h"

// Check a FILE* isn't nullptr, so we can emit a clear diagnostic message
// instead of just crashing with SIGSEGV.
#define CHECK_FP(fp) \
  if (fp == nullptr) __fortify_fatal("%s: null FILE*", __FUNCTION__)

#define NDYNAMIC 10  /* add ten more whenever necessary */

#define PRINTF_IMPL(expr) \
    va_list ap; \
    va_start(ap, fmt); \
    int result = (expr); \
    va_end(ap); \
    return result;

#define MAKE_STD_STREAM(flags, fd)                                          \
  {                                                                         \
    ._flags = flags, ._file = fd, ._cookie = __sF + fd, ._close = __sclose, \
    ._read = __sread, ._write = __swrite, ._ext = {                         \
      ._base = reinterpret_cast<uint8_t*>(__sFext + fd)                     \
    }                                                                       \
  }

static struct __sfileext __sFext[3] = {
    {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
     ._caller_handles_locking = false,
     ._seek64 = __sseek64,
     ._popen_pid = 0},
    {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
     ._caller_handles_locking = false,
     ._seek64 = __sseek64,
     ._popen_pid = 0},
    {._lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
     ._caller_handles_locking = false,
     ._seek64 = __sseek64,
     ._popen_pid = 0},
};

// __sF is exported for backwards compatibility. Until M, we didn't have symbols
// for stdin/stdout/stderr; they were macros accessing __sF.
FILE __sF[3] = {
    MAKE_STD_STREAM(__SRD, STDIN_FILENO),
    MAKE_STD_STREAM(__SWR, STDOUT_FILENO),
    MAKE_STD_STREAM(__SWR|__SNBF, STDERR_FILENO),
};

FILE* stdin = &__sF[0];
FILE* stdout = &__sF[1];
FILE* stderr = &__sF[2];

static uint64_t __get_file_tag(FILE* fp) {
  // Don't use a tag for the standard streams.
  // They don't really own their file descriptors, because the values are well-known, and you're
  // allowed to do things like `close(STDIN_FILENO); open("foo", O_RDONLY)` when single-threaded.
  if (fp == stdin || fp == stderr || fp == stdout) {
    return 0;
  }

  return android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_FILE,
                                        reinterpret_cast<uint64_t>(fp));
}

// The first few FILEs are statically allocated; others are dynamically
// allocated and linked in via this glue structure.
// TODO: replace this with an intrusive doubly-linked list of the FILE*s (via _EXT())
struct glue {
  struct glue* next;
  int niobs;
  FILE* iobs;
};
static pthread_mutex_t __sglue_mutex = PTHREAD_MUTEX_INITIALIZER;
struct glue __sglue = { nullptr, 3, __sF };
static struct glue* lastglue = &__sglue;

class ScopedFileLock {
 public:
  explicit ScopedFileLock(FILE* fp) : fp_(fp) {
    FLOCKFILE(fp_);
  }
  ~ScopedFileLock() {
    FUNLOCKFILE(fp_);
  }

 private:
  FILE* fp_;
};

static glue* moreglue(int n) {
  char* data = new char[sizeof(glue) +
                        alignof(FILE) + n * sizeof(FILE) +
                        alignof(__sfileext) + n * sizeof(__sfileext)];
  if (data == nullptr) return nullptr;

  glue* g = reinterpret_cast<glue*>(data);
  FILE* p = reinterpret_cast<FILE*>(__builtin_align_up(g + 1, alignof(FILE)));
  __sfileext* pext = reinterpret_cast<__sfileext*>(__builtin_align_up(p + n, alignof(__sfileext)));
  g->next = nullptr;
  g->niobs = n;
  g->iobs = p;
  while (--n >= 0) {
    *p = {};
    _FILEEXT_SETUP(p, pext);
    p++;
    pext++;
  }
  return g;
}

static inline void free_fgetln_buffer(FILE* fp) {
  if (__predict_false(fp->fgetln_buffer._base != nullptr)) {
    free(fp->fgetln_buffer._base);
    fp->fgetln_buffer._base = nullptr;
    fp->fgetln_buffer._size = 0;
  }
}

/*
 * Find a free FILE for fopen et al.
 */

FILE* __sfp(void) {
  FILE *fp;
  int n;
  struct glue *g;

  pthread_mutex_lock(&__sglue_mutex);
  for (g = &__sglue; g != nullptr; g = g->next) {
    for (fp = g->iobs, n = g->niobs; --n >= 0; fp++) {
      if (fp->_flags == 0goto found;
    }
  }

  /* release lock while mallocing */
  pthread_mutex_unlock(&__sglue_mutex);
  if ((g = moreglue(NDYNAMIC)) == nullptr) return nullptr;
  pthread_mutex_lock(&__sglue_mutex);
  lastglue->next = g;
  lastglue = g;
  fp = g->iobs;
found:
  fp->_flags = 1;  /* reserve this slot; caller sets real flags */
  pthread_mutex_unlock(&__sglue_mutex);
  fp->_p = nullptr;  /* no current pointer */
  fp->_w = 0;  /* nothing to read or write */
  fp->_r = 0;
  fp->_bf._base = nullptr;  /* no buffer */
  fp->_bf._size = 0;
  fp->_lbfsize = 0;  /* not line buffered */
  fp->_file = -1;  /* no file */

  fp->fgetln_buffer = {};

  memset(_EXT(fp), 0sizeof(struct __sfileext));
  _EXT(fp)->_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
  _EXT(fp)->_caller_handles_locking = false;

  // Caller sets cookie, _read/_write etc.
  // We explicitly clear _seek and _seek64 to prevent subtle bugs.
  fp->_seek = nullptr;
  _EXT(fp)->_seek64 = nullptr;

  return fp;
}

int _fwalk(int (*callback)(FILE*)) {
  pthread_mutex_lock(&__sglue_mutex);
  int result = 0;
  for (glue* g = &__sglue; g != nullptr; g = g->next) {
    FILE* fp = g->iobs;
    for (int n = g->niobs; --n >= 0; ++fp) {
      ScopedFileLock sfl(fp);
      if (fp->_flags != 0) {
        result |= (*callback)(fp);
      }
    }
  }
  pthread_mutex_unlock(&__sglue_mutex);
  return result;
}

extern "C" __LIBC_HIDDEN__ void __libc_stdio_cleanup() {
  // This matches our historical behavior, but what about code that writes after this runs?
  // glibc sets all streams to unbuffered (so future writes go straight out).
  // musl takes the locks but keeps them held (so future writes deadlock).
  fflush(nullptr);
}

/*
 * Refill a stdio buffer.
 * Return EOF on eof or error, 0 otherwise.
 */

int __srefill(FILE* fp) {
  fp->_r = 0/* largely a convenience for callers */

  /* if not already reading, have to be reading and writing */
  if ((fp->_flags & __SRD) == 0) {
    if ((fp->_flags & __SRW) == 0) {
      errno = EBADF;
      fp->_flags |= __SERR;
      return EOF;
    }
    /* switch to reading */
    if (fp->_flags & __SWR) {
      if (__sflush(fp)) return EOF;
      fp->_flags &= ~__SWR;
      fp->_w = 0;
      fp->_lbfsize = 0;
    }
    fp->_flags |= __SRD;
  } else {
    /*
     * We were reading.  If there is an ungetc buffer,
     * we must have been reading from that.  Drop it,
     * restoring the previous buffer (if any).  If there
     * is anything in that buffer, return.
     */

    if (HASUB(fp)) {
      FREEUB(fp);
      if ((fp->_r = fp->_ur) != 0) {
        fp->_p = fp->_up;
        return 0;
      }
    }
  }

  if (fp->_bf._base == NULL) __smakebuf(fp);

  // If we're about to read from an unbuffered or line-buffered stream, what do we need to do?
  //
  // Everyone points to the ISO C standard, but disagree about how to interpret it.
  // The two most important sentences are C23 5.1.2.3 paragraph 6 bullet 3 which says that
  // "the intent ... is that unbuffered or line-buffered output appear as soon as possible,
  // to ensure that prompting messages appear prior to a program waiting for input",
  // and C23 7.23.3 paragraph 3 which qualifies the extended description of these behaviors with
  // "Support for these characteristics is implementation-defined".
  //
  // In practice, BSD flushes all streams, glibc only flushes stdout, and musl flushes no streams.
  //
  // Being BSD-derived, bionic historically flushed all streams. This was never implemented in a
  // completely thread-safe manner, and all attempts to fix that failed by introducing deadlocks.
  // Upstream BSD even adds a __SIGN ("ignore") flag as a partial workaround, but that only covers
  // the per-file locks' deadlocks, not deadlocks that would be caused by adding the missing locking
  // of the _list_ of streams.
  //
  // Long term I think we should move to the musl model, but the simple safe choice is to behave
  // like glibc and just flush stdout. (A transition glibc made in 2004, having had inherited BSD
  // behavior before then.)
  if (fp->_flags & (__SLBF|__SNBF)) {
    // We use fflush() rather than __sflush() for stdout since we don't hold the stdout lock.
    fflush(stdout);

    // Now flush _this_ file without locking it (because our caller should have either taken the
    // lock or know it's in a context -- such as fread_unlocked() -- where the lock isn't needed).
    if ((fp->_flags & (__SLBF|__SWR)) == (__SLBF|__SWR)) __sflush(fp);
  }
  fp->_p = fp->_bf._base;
  fp->_r = (*fp->_read)(fp->_cookie, reinterpret_cast<char*>(fp->_p), fp->_bf._size);
  if (fp->_r <= 0) {
    if (fp->_r == 0) {
      fp->_flags |= __SEOF;
    } else {
      fp->_r = 0;
      fp->_flags |= __SERR;
    }
    return EOF;
  }
  return 0;
}

// Handle getc() when the buffer ran out: refill then return the first new byte buffer.
int __srget(FILE* fp) {
  _SET_ORIENTATION(fp, -1);
  if (__srefill(fp) == 0) {
    fp->_r--;
    return (*fp->_p++);
  }
  return EOF;
}

/*
 * Allocate a file buffer, or switch to unbuffered I/O.
 * Per the ANSI C standard, ALL tty devices default to line buffered.
 */

void __smakebuf(FILE* fp) {
  unsigned char *p;
  int flags = 0;
  size_t size;
  int couldbetty;

  if (fp->_flags & __SNBF) {
    fp->_bf._base = fp->_p = fp->_nbuf;
    fp->_bf._size = 1;
    return;
  }
  __swhatbuf(fp, &size, &couldbetty);
  if ((p = static_cast<unsigned char*>(malloc(size))) == NULL) {
    fp->_flags |= __SNBF;
    fp->_bf._base = fp->_p = fp->_nbuf;
    fp->_bf._size = 1;
    return;
  }
  flags |= __SMBF;
  fp->_bf._base = fp->_p = p;
  fp->_bf._size = size;
  if (couldbetty && isatty(fp->_file)) flags |= __SLBF;
  fp->_flags |= flags;
}

/*
 * Internal routine to determine `proper' buffering for a file.
 */

void __swhatbuf(FILE* fp, size_t* bufsize, int* couldbetty) {
  struct stat st;
  if (fp->_file < 0 || fstat(fp->_file, &st) == -1) {
    *couldbetty = 0;
    *bufsize = BUFSIZ;
    return;
  }

  /* could be a tty iff it is a character device */
  *couldbetty = S_ISCHR(st.st_mode);
  if (st.st_blksize == 0) {
    *bufsize = BUFSIZ;
    return;
  }

  *bufsize = st.st_blksize;
}

static FILE* __FILE_init(FILE* fp, int fd, int flags) {
  if (fp == nullptr) return nullptr;

#if !defined(__LP64__)
  if (fd > SHRT_MAX) __fortify_fatal("stdio: fd %d > SHRT_MAX", fd);
#endif

  fp->_file = fd;
  android_fdsan_exchange_owner_tag(fd, 0, __get_file_tag(fp));
  fp->_flags = flags;
  fp->_cookie = fp;
  fp->_read = __sread;
  fp->_write = __swrite;
  fp->_close = __sclose;
  _EXT(fp)->_seek64 = __sseek64;
  return fp;
}

FILE* fopen(const char* file, const char* mode) {
  int mode_flags;
  int flags = __sflags(mode, &mode_flags);
  if (flags == 0return nullptr;

  int fd = open(file, mode_flags, DEFFILEMODE);
  if (fd == -1) {
    return nullptr;
  }

  FILE* fp = __FILE_init(__sfp(), fd, flags);
  if (fp == nullptr) {
    ErrnoRestorer errno_restorer;
    close(fd);
    return nullptr;
  }

  // For append mode, O_APPEND sets the write position for free, but we need to
  // set the read position manually.
  if ((mode_flags & O_APPEND) != 0) __sseek64(fp, 0, SEEK_END);
  return fp;
}
__strong_alias(fopen64, fopen);

FILE* fdopen(int fd, const char* mode) {
  int mode_flags;
  int flags = __sflags(mode, &mode_flags);
  if (flags == 0return nullptr;

  // Make sure the mode the user wants is a subset of the actual mode.
  int fd_flags = fcntl(fd, F_GETFL, 0);
  if (fd_flags == -1return nullptr;
  int tmp = fd_flags & O_ACCMODE;
  if (tmp != O_RDWR && (tmp != (mode_flags & O_ACCMODE))) {
    errno = EINVAL;
    return nullptr;
  }

  // Make sure O_APPEND is set on the underlying fd if our mode has 'a'.
  // POSIX says we just take the current offset of the underlying fd.
  if ((mode_flags & O_APPEND) && !(fd_flags & O_APPEND)) {
    if (fcntl(fd, F_SETFL, fd_flags | O_APPEND) == -1return nullptr;
  }

  // Make sure O_CLOEXEC is set on the underlying fd if our mode has 'e'.
  if ((mode_flags & O_CLOEXEC) && !((tmp = fcntl(fd, F_GETFD)) & FD_CLOEXEC)) {
    fcntl(fd, F_SETFD, tmp | FD_CLOEXEC);
  }

  return __FILE_init(__sfp(), fd, flags);
}

FILE* freopen(const char* file, const char* mode, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);

  // POSIX says: "If pathname is a null pointer, the freopen() function shall
  // attempt to change the mode of the stream to that specified by mode, as if
  // the name of the file currently associated with the stream had been used. In
  // this case, the file descriptor associated with the stream need not be
  // closed if the call to freopen() succeeds. It is implementation-defined
  // which changes of mode are permitted (if any), and under what
  // circumstances."
  //
  // Linux is quite restrictive about what changes you can make with F_SETFL,
  // and in particular won't let you touch the access bits. It's easiest and
  // most effective to just rely on /proc/self/fd/...
  FdPath fd_path(fp->_file);
  if (file == nullptr) file = fd_path.c_str();

  int mode_flags;
  int flags = __sflags(mode, &mode_flags);
  if (flags == 0) {
    fclose(fp);
    return nullptr;
  }

  // TODO: rewrite this mess completely.

  // There are actually programs that depend on being able to "freopen"
  // descriptors that weren't originally open.  Keep this from breaking.
  // Remember whether the stream was open to begin with, and which file
  // descriptor (if any) was associated with it.  If it was attached to
  // a descriptor, defer closing it; freopen("/dev/stdin", "r", stdin)
  // should work.  This is unnecessary if it was not a Unix file.
  int isopen, wantfd;
  if (fp->_flags == 0) {
    fp->_flags = __SEOF; // Hold on to it.
    isopen = 0;
    wantfd = -1;
  } else {
    // Flush the stream; ANSI doesn't require this.
    if (fp->_flags & __SWR) __sflush(fp);

    // If close is null, closing is a no-op, hence pointless.
    isopen = (fp->_close != nullptr);
    if ((wantfd = fp->_file) < 0 && isopen) {
        (*fp->_close)(fp->_cookie);
        isopen = 0;
    }
  }

  // Get a new descriptor to refer to the new file.
  int fd = open(file, mode_flags, DEFFILEMODE);
  if (fd < 0 && isopen) {
    // If out of fd's close the old one and try again.
    if (errno == ENFILE || errno == EMFILE) {
      (*fp->_close)(fp->_cookie);
      isopen = 0;
      fd = open(file, mode_flags, DEFFILEMODE);
    }
  }

  int sverrno = errno;

  // Finish closing fp.  Even if the open succeeded above, we cannot
  // keep fp->_base: it may be the wrong size.  This loses the effect
  // of any setbuffer calls, but stdio has always done this before.
  if (isopen && fd != wantfd) (*fp->_close)(fp->_cookie);
  if (fp->_flags & __SMBF) free(fp->_bf._base);
  fp->_w = 0;
  fp->_r = 0;
  fp->_p = nullptr;
  fp->_bf._base = nullptr;
  fp->_bf._size = 0;
  fp->_lbfsize = 0;
  if (HASUB(fp)) FREEUB(fp);
  _UB(fp)._size = 0;
  WCIO_FREE(fp);
  free_fgetln_buffer(fp);

  if (fd < 0) { // Did not get it after all.
    fp->_flags = 0// Release.
    errno = sverrno; // Restore errno in case _close clobbered it.
    return nullptr;
  }

  // If reopening something that was open before on a real file, try
  // to maintain the descriptor.  Various C library routines (perror)
  // assume stderr is always fd STDERR_FILENO, even if being freopen'd.
  if (wantfd >= 0 && fd != wantfd) {
    if (dup3(fd, wantfd, mode_flags & O_CLOEXEC) >= 0) {
      close(fd);
      fd = wantfd;
    }
  }

  __FILE_init(fp, fd, flags);

  // For append mode, O_APPEND sets the write position for free, but we need to
  // set the read position manually.
  if ((mode_flags & O_APPEND) != 0) __sseek64(fp, 0, SEEK_END);

  return fp;
}
__strong_alias(freopen64, freopen);

int fclose(FILE* fp) {
  CHECK_FP(fp);

  ScopedFileLock sfl(fp);
  WCIO_FREE(fp);
  int r = fp->_flags & __SWR ? __sflush(fp) : 0;
  if (fp->_close != nullptr && (*fp->_close)(fp->_cookie) < 0) {
    r = EOF;
  }
  if (fp->_flags & __SMBF) free(fp->_bf._base);
  if (HASUB(fp)) FREEUB(fp);
  free_fgetln_buffer(fp);

  // If we were created by popen(3), wait for the child.
  pid_t pid = _EXT(fp)->_popen_pid;
  if (pid > 0) {
    int status;
    if (TEMP_FAILURE_RETRY(wait4(pid, &status, 0, nullptr)) != -1) {
      r = status;
    }
  }
  _EXT(fp)->_popen_pid = 0;

  // Poison this FILE so accesses after fclose will be obvious.
  fp->_file = -1;
  fp->_r = fp->_w = 0;

  // Release this FILE for reuse.
  fp->_flags = 0;
  return r;
}
__strong_alias(pclose, fclose);

int fileno_unlocked(FILE* fp) {
  CHECK_FP(fp);
  int fd = fp->_file;
  if (fd == -1) {
    errno = EBADF;
    return -1;
  }
  return fd;
}

int fileno(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return fileno_unlocked(fp);
}

void clearerr_unlocked(FILE* fp) {
  CHECK_FP(fp);
  return __sclearerr(fp);
}

void clearerr(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  clearerr_unlocked(fp);
}

int feof_unlocked(FILE* fp) {
  CHECK_FP(fp);
  return ((fp->_flags & __SEOF) != 0);
}

int feof(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return feof_unlocked(fp);
}

int ferror_unlocked(FILE* fp) {
  CHECK_FP(fp);
  return __sferror(fp);
}

int ferror(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return ferror_unlocked(fp);
}

int __sflush(FILE* fp) {
  // Flushing is a no-op if we're not currently writing.
  if ((fp->_flags & __SWR) == 0return 0;

  // Flushing a file without a buffer is a no-op.
  unsigned char* p = fp->_bf._base;
  if (p == nullptr) return 0;

  // Set these immediately to avoid problems with longjmp and to allow
  // exchange buffering (via setvbuf) in user write function.
  int n = fp->_p - p;
  fp->_p = p;
  fp->_w = (fp->_flags & (__SLBF|__SNBF)) ? 0 : fp->_bf._size;

  while (n > 0) {
    int written = (*fp->_write)(fp->_cookie, reinterpret_cast<char*>(p), n);
    if (written <= 0) {
      fp->_flags |= __SERR;
      return EOF;
    }
    n -= written, p += written;
  }
  return 0;
}

int __sread(void* cookie, char* buf, int n) {
  FILE* fp = reinterpret_cast<FILE*>(cookie);
  return TEMP_FAILURE_RETRY(read(fp->_file, buf, n));
}

int __swrite(void* cookie, const char* buf, int n) {
  FILE* fp = reinterpret_cast<FILE*>(cookie);
  return TEMP_FAILURE_RETRY(write(fp->_file, buf, n));
}

off64_t __sseek64(void* cookie, off64_t offset, int whence) {
  FILE* fp = reinterpret_cast<FILE*>(cookie);
  return TEMP_FAILURE_RETRY(lseek64(fp->_file, offset, whence));
}

int __sclose(void* cookie) {
  FILE* fp = reinterpret_cast<FILE*>(cookie);
  return android_fdsan_close_with_tag(fp->_file, __get_file_tag(fp));
}

static off64_t __seek_unlocked(FILE* fp, off64_t offset, int whence) {
  // Use `_seek64` if set, but fall back to `_seek`.
  if (_EXT(fp)->_seek64 != nullptr) {
    return (*_EXT(fp)->_seek64)(fp->_cookie, offset, whence);
  } else if (fp->_seek != nullptr) {
    off64_t result = (*fp->_seek)(fp->_cookie, offset, whence);
#if !defined(__LP64__)
    // Avoid sign extension if off64_t is larger than off_t.
    if (result != -1) result &= 0xffffffff;
#endif
    return result;
  } else {
    errno = ESPIPE;
    return -1;
  }
}

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.

  off64_t result = __seek_unlocked(fp, 0, SEEK_CUR);
  if (result == -1) {
    return -1;
  }

  if (fp->_flags & __SRD) {
    // Reading.  Any unread characters (including
    // those from ungetc) cause the position to be
    // smaller than that in the underlying object.
    result -= fp->_r;
    if (HASUB(fp)) result -= fp->_ur;
  } else if (fp->_flags & __SWR && fp->_p != nullptr) {
    // Writing.  Any buffered characters cause the
    // position to be greater than that in the
    // underlying object.
    result += fp->_p - fp->_bf._base;
  }
  return result;
}

static 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) {
    fpos64_t current_offset = __ftello64_unlocked(fp);
    if (current_offset == -1) {
      return -1;
    }
    offset += current_offset;
    whence = SEEK_SET;
  } else if (whence != SEEK_SET && whence != SEEK_END) {
    errno = EINVAL;
    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 == nullptr) __smakebuf(fp);

  // Flush unwritten data and attempt the seek.
  if (__sflush(fp) || __seek_unlocked(fp, offset, whence) == -1) {
    return -1;
  }

  // Success: clear EOF indicator and discard ungetc() data.
  if (HASUB(fp)) FREEUB(fp);
  fp->_p = fp->_bf._base;
  fp->_r = 0;
  /* fp->_w = 0; */    /* unnecessary (I think...) */
  fp->_flags &= ~__SEOF;
  return 0;
}

int fseeko(FILE* fp, off_t offset, int whence) {
  CHECK_FP(fp);
  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) {
  CHECK_FP(fp);
  return __fseeko64(fp, offset, whence, 8*sizeof(off64_t));
}

int fsetpos(FILE* fp, const fpos_t* pos) {
  CHECK_FP(fp);
  return fseeko(fp, *pos, SEEK_SET);
}

int fsetpos64(FILE* fp, const fpos64_t* pos) {
  CHECK_FP(fp);
  return fseeko64(fp, *pos, SEEK_SET);
}

off_t ftello(FILE* fp) {
  CHECK_FP(fp);
  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 result;
}
__strong_alias(ftell, ftello);

off64_t ftello64(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return __ftello64_unlocked(fp);
}

int fgetpos(FILE* fp, fpos_t* pos) {
  CHECK_FP(fp);
  *pos = ftello(fp);
  return (*pos == -1) ? -1 : 0;
}

int fgetpos64(FILE* fp, fpos64_t* pos) {
  CHECK_FP(fp);
  *pos = ftello64(fp);
  return (*pos == -1) ? -1 : 0;
}

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;
}

int asprintf(char** s, const char* fmt, ...) {
  PRINTF_IMPL(vasprintf(s, fmt, ap));
}

// Traditional implementations required support for dynamically growing the
// buffer pointed to by a FILE*, and would then trim over-large cases.
// FreeBSD (and thus iOS) used linear 128 byte growth, NetBSD doubled, and
// OpenBSD (and thus Android) had a page-based compromise that reduced the
// number of realloc() calls for long strings, but made it more likely that
// you'd need one final pass to shrink back to size.
// Rather than get involved in all that, we solve the 99% case with a stack
// buffer and a single exact-size allocation+memcpy(), and handle the 1% case
// by using the required size returned by the first vsnprintf() to do a single
// exact-size allocation followed by another call to vsnprintf().
int vasprintf(char** s, const char* fmt, va_list ap) {
  va_list ap2;
  va_copy(ap2, ap);

  char buf[BUFSIZ] __attribute__((__uninitialized__));
  int n = vsnprintf(buf, sizeof(buf), fmt, ap);
  if (n == -1return -1;

  char* result = static_cast<char*>(malloc(n + 1));
  if (result == nullptr) return -1;

  if (n < static_cast<int>(sizeof(buf))) {
    memcpy(result, buf, n + 1);
  } else {
    vsnprintf(result, n + 1, fmt, ap2);
  }

  *s = result;
  return n;
}

char* ctermid(char* s) {
  return s ? strcpy(s, _PATH_TTY) : const_cast<char*>(_PATH_TTY);
}

int dprintf(int fd, const char* fmt, ...) {
  PRINTF_IMPL(vdprintf(fd, fmt, ap));
}

int fprintf(FILE* fp, const char* fmt, ...) {
  CHECK_FP(fp);
  PRINTF_IMPL(vfprintf(fp, fmt, ap));
}

int fgetc(FILE* fp) {
  CHECK_FP(fp);
  return getc(fp);
}

int fgetc_unlocked(FILE* fp) {
  CHECK_FP(fp);
  return getc_unlocked(fp);
}

char* fgetln(FILE* fp, size_t* length_ptr) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  // Implementing fgetln() in terms of getdelim() means lines are actually always NUL terminated.
  // We could explicitly overwrite the NUL to be "bug compatible", but that seems silly?
  ssize_t n = getdelim(reinterpret_cast<char**>(&fp->fgetln_buffer._base),
                       &fp->fgetln_buffer._size, '\n', fp);
  if (n <= 0return nullptr;
  *length_ptr = n;
  return reinterpret_cast<char*>(fp->fgetln_buffer._base);
}

char* fgets(char* buf, int n, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return fgets_unlocked(buf, n, fp);
}

// Reads at most n-1 characters from the given file.
// Stops when a newline has been read, or the count runs out.
// Returns first argument, or nullptr if no characters were read.
// Does not return nullptr if n == 1.
char* fgets_unlocked(char* buf, int n, FILE* fp) {
  if (n <= 0) __fortify_fatal("fgets: buffer size %d <= 0", n);

  _SET_ORIENTATION(fp, ORIENT_BYTES);

  char* s = buf;
  n--; // Leave space for NUL.
  while (n != 0) {
    // If the buffer is empty, refill it.
    if (fp->_r <= 0) {
      if (__srefill(fp)) {
        // EOF/error: stop with partial or no line.
        if (s == buf) return nullptr;
        break;
      }
    }

    // Scan through at most n bytes of the current buffer, looking for '\n'.
    size_t len = fp->_r;
    unsigned char* p = fp->_p;
    if (len > static_cast<size_t>(n)) len = n;
    unsigned char* t = static_cast<unsigned char*>(memchr(p, '\n', len));
    // If found, copy up to and including newline and stop.
    if (t != nullptr) {
      len = ++t - p;
      fp->_r -= len;
      fp->_p = t;
      memcpy(s, p, len);
      s[len] = '\0';
      return buf;
    }
    // Otherwise, copy entire chunk and loop.
    fp->_r -= len;
    fp->_p += len;
    memcpy(s, p, len);
    s += len;
    n -= len;
  }
  *s = '\0';
  return buf;
}

int fputc(int c, FILE* fp) {
  CHECK_FP(fp);
  return putc(c, fp);
}

int fputc_unlocked(int c, FILE* fp) {
  CHECK_FP(fp);
  return putc_unlocked(c, fp);
}

int fputs(const char* s, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return fputs_unlocked(s, fp);
}

int fputs_unlocked(const char* s, FILE* fp) {
  CHECK_FP(fp);
  size_t length = strlen(s);
  return (fwrite_unlocked(s, 1, length, fp) == length) ? 0 : EOF;
}

int fscanf(FILE* fp, const char* fmt, ...) {
  CHECK_FP(fp);
  PRINTF_IMPL(vfscanf(fp, fmt, ap));
}

int fwprintf(FILE* fp, const wchar_t* fmt, ...) {
  CHECK_FP(fp);
  PRINTF_IMPL(vfwprintf(fp, fmt, ap));
}

int fwscanf(FILE* fp, const wchar_t* fmt, ...) {
  CHECK_FP(fp);
  PRINTF_IMPL(vfwscanf(fp, fmt, ap));
}

int getc(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return getc_unlocked(fp);
}

int getc_unlocked(FILE* fp) {
  CHECK_FP(fp);
  return __sgetc(fp);
}

int getchar_unlocked() {
  return getc_unlocked(stdin);
}

int getchar() {
  return getc(stdin);
}

ssize_t getline(char** buf, size_t* len, FILE* fp) {
  CHECK_FP(fp);
  return getdelim(buf, len, '\n', fp);
}

wint_t getwc(FILE* fp) {
  CHECK_FP(fp);
  return fgetwc(fp);
}

wint_t getwchar() {
  return fgetwc(stdin);
}

void perror(const char* msg) {
  if (msg == nullptr) msg = "";
  fprintf(stderr, "%s%s%m\n", msg, (*msg == '\0') ? "" : ": ");
}

int printf(const char* fmt, ...) {
  PRINTF_IMPL(vfprintf(stdout, fmt, ap));
}

int putc(int c, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return putc_unlocked(c, fp);
}

int putc_unlocked(int c, FILE* fp) {
  CHECK_FP(fp);
  if (cantwrite(fp)) {
    errno = EBADF;
    return EOF;
  }
  _SET_ORIENTATION(fp, ORIENT_BYTES);
  if (--fp->_w >= 0 || (fp->_w >= fp->_lbfsize && c != '\n')) {
    return (*fp->_p++ = c);
  }
  return (__swbuf(c, fp));
}

int putchar(int c) {
  return putc(c, stdout);
}

int putchar_unlocked(int c) {
  return putc_unlocked(c, stdout);
}

int puts(const char* s) {
  size_t length = strlen(s);
  ScopedFileLock sfl(stdout);
  return (fwrite_unlocked(s, 1, length, stdout) == length &&
          putc_unlocked('\n', stdout) != EOF) ? 0 : EOF;
}

wint_t putwc(wchar_t wc, FILE* fp) {
  CHECK_FP(fp);
  return fputwc(wc, fp);
}

wint_t putwchar(wchar_t wc) {
  return fputwc(wc, stdout);
}

int remove(const char* path) {
  if (unlink(path) != -1return 0;
  if (errno != EISDIR) return -1;
  return rmdir(path);
}

void rewind(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  fseek(fp, 0, SEEK_SET);
  clearerr_unlocked(fp);
}

int scanf(const char* fmt, ...) {
  PRINTF_IMPL(vfscanf(stdin, fmt, ap));
}

void setbuf(FILE* fp, char* buf) {
  CHECK_FP(fp);
  setbuffer(fp, buf, BUFSIZ);
}

void setbuffer(FILE* fp, char* buf, int size) {
  CHECK_FP(fp);
  setvbuf(fp, buf, buf ? _IOFBF : _IONBF, size);
}

int setlinebuf(FILE* fp) {
  CHECK_FP(fp);
  return setvbuf(fp, nullptr, _IOLBF, 0);
}

/*
 * Set one of the three kinds of buffering, optionally including
 * a buffer.
 */

int setvbuf(FILE* fp, char* buf, int mode, size_t size) {
  int ret, flags;
  size_t iosize;
  int ignored;

  /*
   * Verify arguments.  The `int' limit on `size' is due to this
   * particular implementation.  Note, buf and size are ignored
   * when setting _IONBF.
   */

  if (mode != _IONBF)
    if ((mode != _IOFBF && mode != _IOLBF) || size > INT_MAX)
      return (EOF);

  /*
   * Write current buffer, if any.  Discard unread input (including
   * ungetc data), cancel line buffering, and free old buffer if
   * malloc()ed.  We also clear any eof condition, as if this were
   * a seek.
   */

  FLOCKFILE(fp);
  ret = 0;
  (void)__sflush(fp);
  if (HASUB(fp)) FREEUB(fp);
  WCIO_FREE(fp);
  fp->_r = fp->_lbfsize = 0;
  flags = fp->_flags;
  if (flags & __SMBF) free(fp->_bf._base);
    flags &= ~(__SLBF | __SNBF | __SMBF | __SEOF);

  /* If setting unbuffered mode, skip all the hard work. */
  if (mode == _IONBF) goto nbf;

  /*
   * Note that size == 0 is unspecified behavior:
   *
   * musl returns an error,
   * glibc interprets it as "unbuffered",
   * macOS' man page says it interprets it as "defer allocation" --
   * the default if you hadn't called setvbuf() --
   * but it actually seems to have the same BSD behavior we currently see here.
   *
   * TODO: investigate whether this whole "i/o size" thing is actually useful.
   */

  __swhatbuf(fp, &iosize, &ignored);
  if (size == 0) {
    buf = NULL; /* force local allocation */
    size = iosize;
  }

  /* Allocate buffer if needed. */
  if (buf == NULL) {
    if ((buf = static_cast<char*>(malloc(size))) == NULL) {
      /*
       * Unable to honor user's request.  We will return
       * failure, but try again with file system size.
       */

      ret = EOF;
      if (size != iosize) {
        size = iosize;
        buf = static_cast<char*>(malloc(size));
      }
    }
    if (buf == NULL) {
      /* No luck; switch to unbuffered I/O. */
nbf:
      fp->_flags = flags | __SNBF;
      fp->_w = 0;
      fp->_bf._base = fp->_p = fp->_nbuf;
      fp->_bf._size = 1;
      FUNLOCKFILE(fp);
      return (ret);
    }
    flags |= __SMBF;
  }

  /*
   * Fix up the FILE fields, and set __cleanup for output flush on
   * exit (since we are buffered in some way).
   */

  if (mode == _IOLBF) flags |= __SLBF;
  fp->_flags = flags;
  fp->_bf._base = fp->_p = reinterpret_cast<unsigned char*>(buf);
  fp->_bf._size = size;
  /* fp->_lbfsize is still 0 */
  if (flags & __SWR) {
    /*
     * Begin or continue writing: see __swsetup().  Note
     * that __SNBF is impossible (it was handled earlier).
     */

    if (flags & __SLBF) {
      fp->_w = 0;
      fp->_lbfsize = -fp->_bf._size;
    } else {
      fp->_w = size;
    }
  } else {
    /* begin/continue reading, or stay in intermediate state */
    fp->_w = 0;
  }
  FUNLOCKFILE(fp);

  return (ret);
}

int snprintf(char* s, size_t n, const char* fmt, ...) {
  PRINTF_IMPL(vsnprintf(s, n, fmt, ap));
}

int sprintf(char* s, const char* fmt, ...) {
  PRINTF_IMPL(vsprintf(s, fmt, ap));
}

int sscanf(const char* s, const char* fmt, ...) {
  PRINTF_IMPL(vsscanf(s, fmt, ap));
}

int swprintf(wchar_t* s, size_t n, const wchar_t* fmt, ...) {
  PRINTF_IMPL(vswprintf(s, n, fmt, ap));
}

int swscanf(const wchar_t* s, const wchar_t* fmt, ...) {
  PRINTF_IMPL(vswscanf(s, fmt, ap));
}

int vdprintf(int fd, const char* fmt, va_list ap) {
  unsigned char buf[BUFSIZ] __attribute__((__uninitialized__));

  FILE f;
  struct __sfileext fext;
  _FILEEXT_SETUP(&f, &fext);
  f._bf._base = f._p = buf;
  f._bf._size = f._w = sizeof(buf);
  f._flags = __SWR;
  f._file = -1;
  f._cookie = &fd;
  f._write = [](void* cookie, const char* buf, int n) -> int {
    int* fd_ptr = static_cast<int*>(cookie);
    return TEMP_FAILURE_RETRY(write(*fd_ptr, buf, n));
  };

  int byte_count = __vfprintf(&f, fmt, ap);
  if (byte_count >= 0 && __sflush(&f)) return EOF;
  return byte_count;
}

int vfprintf(FILE* fp, const char* fmt, va_list ap) {
  ScopedFileLock sfl(fp);
  return __vfprintf(fp, fmt, ap);
}

int vfscanf(FILE* fp, const char* fmt, va_list ap) {
  ScopedFileLock sfl(fp);
  return __svfscanf(fp, fmt, ap);
}

int vfwprintf(FILE* fp, const wchar_t* fmt, va_list ap) {
  ScopedFileLock sfl(fp);
  return __vfwprintf(fp, fmt, ap);
}

int vfwscanf(FILE* fp, const wchar_t* fmt, va_list ap) {
  ScopedFileLock sfl(fp);
  return __vfwscanf(fp, fmt, ap);
}

int vprintf(const char* fmt, va_list ap) {
  return vfprintf(stdout, fmt, ap);
}

int vscanf(const char* fmt, va_list ap) {
  return vfscanf(stdin, fmt, ap);
}

int vsnprintf(char* s, size_t n, const char* fmt, va_list ap) {
  // stdio internals use int rather than size_t.
  static_assert(INT_MAX <= SSIZE_MAX, "SSIZE_MAX too large to fit in int");

  __check_count("vsnprintf""size", n);

  // Stdio internals do not deal correctly with zero length buffer.
  char one_byte_buffer[1];
  if (n == 0) {
    s = one_byte_buffer;
    n = 1;
  }

  FILE f;
  __sfileext fext;
  _FILEEXT_SETUP(&f, &fext);
  f._file = -1;
  f._flags = __SWR | __SSTR;
  f._bf._base = f._p = reinterpret_cast<unsigned char*>(s);
  f._bf._size = f._w = n - 1;

  int result = __vfprintf(&f, fmt, ap);
  *f._p = '\0';
  return result;
}

int vsprintf(char* s, const char* fmt, va_list ap) {
  return vsnprintf(s, SSIZE_MAX, fmt, ap);
}

int vsscanf(const char* s, const char* fmt, va_list ap) {
  FILE f;
  __sfileext fext;
  _FILEEXT_SETUP(&f, &fext);
  f._flags = __SRD;
  f._bf._base = f._p = reinterpret_cast<unsigned char*>(const_cast<char*>(s));
  f._bf._size = f._r = strlen(s);
  f._read = [](void*, char*, int) { return 0; };
  return __svfscanf(&f, fmt, ap);
}

int vswscanf(const wchar_t* str, const wchar_t* fmt, va_list ap) {
  // We convert the wide character string to multibyte, which __vfwscanf() will convert back to
  // wide characters, but no-one really cares about the wchar_t stuff so this isn't worth improving.
  size_t len = wcslen(str) * MB_CUR_MAX;
  char* mbstr = static_cast<char*>(malloc(len + 1));
  if (mbstr == nullptr) return EOF;

  const wchar_t* strp = str;
  mbstate_t mbs = {};
  size_t mlen = wcsrtombs(mbstr, &strp, len, &mbs);
  if (mlen == static_cast<size_t>(-1)) {
    free(mbstr);
    return EOF;
  }
  if (mlen == len) mbstr[len] = '\0';

  FILE f;
  struct __sfileext fext;
  _FILEEXT_SETUP(&f, &fext);
  f._flags = __SRD;
  f._bf._base = f._p = reinterpret_cast<unsigned char*>(mbstr);
  f._bf._size = f._r = mlen;
  f._read = [](void*, char*, int) { return 0; };
  int r = __vfwscanf(&f, fmt, ap);
  free(mbstr);
  return r;
}

int vwprintf(const wchar_t* fmt, va_list ap) {
  return vfwprintf(stdout, fmt, ap);
}

int vwscanf(const wchar_t* fmt, va_list ap) {
  return vfwscanf(stdin, fmt, ap);
}

int wprintf(const wchar_t* fmt, ...) {
  PRINTF_IMPL(vfwprintf(stdout, fmt, ap));
}

int wscanf(const wchar_t* fmt, ...) {
  PRINTF_IMPL(vfwscanf(stdin, fmt, ap));
}

static int fflush_all() {
  return _fwalk(__sflush);
}

int fflush(FILE* fp) {
  if (fp == nullptr) return fflush_all();
  ScopedFileLock sfl(fp);
  return __sflush(fp);
}

int fflush_unlocked(FILE* fp) {
  if (fp == nullptr) return fflush_all();
  return __sflush(fp);
}

int fpurge(FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  if (HASUB(fp)) FREEUB(fp);
  WCIO_FREE(fp);
  fp->_p = fp->_bf._base;
  fp->_r = 0;
  fp->_w = fp->_flags & (__SLBF | __SNBF) ? 0 : fp->_bf._size;
  return 0;
}
__strong_alias(__fpurge, fpurge);

size_t fread(void* buf, size_t size, size_t count, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return fread_unlocked(buf, size, count, fp);
}

size_t fread_unlocked(void* buf, size_t size, size_t count, FILE* fp) {
  CHECK_FP(fp);

  size_t desired_total;
  if (__builtin_mul_overflow(size, count, &desired_total)) {
    errno = EOVERFLOW;
    fp->_flags |= __SERR;
    return 0;
  }

  size_t total = desired_total;
  if (total == 0return 0;

  _SET_ORIENTATION(fp, ORIENT_BYTES);

  // TODO: how can this ever happen?!
  if (fp->_r < 0) fp->_r = 0;

  // Ensure _bf._size is valid.
  if (fp->_bf._base == nullptr) __smakebuf(fp);

  char* dst = static_cast<char*>(buf);

  while (total > 0) {
    // Copy data out of the buffer.
    size_t buffered_bytes = MIN(static_cast<size_t>(fp->_r), total);
    memcpy(dst, fp->_p, buffered_bytes);
    fp->_p += buffered_bytes;
    fp->_r -= buffered_bytes;
    dst += buffered_bytes;
    total -= buffered_bytes;

    // Are we done?
    if (total == 0goto out;

    // Do we have so much more to read that we should avoid copying it through the buffer?
    if (total > static_cast<size_t>(fp->_bf._size)) break;

    // Less than a buffer to go, so refill the buffer and go around the loop again.
    if (__srefill(fp)) goto out;
  }

  // Read directly into the caller's buffer.
  while (total > 0) {
    // 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;
    }
    dst += bytes_read;
    total -= bytes_read;
  }

out:
  return ((desired_total - total) / size);
}

size_t fwrite(const void* buf, size_t size, size_t count, FILE* fp) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  return fwrite_unlocked(buf, size, count, fp);
}

size_t fwrite_unlocked(const void* buf, size_t size, size_t count, FILE* fp) {
  CHECK_FP(fp);

  size_t n;
  if (__builtin_mul_overflow(size, count, &n)) {
    errno = EOVERFLOW;
    fp->_flags |= __SERR;
    return 0;
  }

  if (n == 0return 0;

  __siov iov = { .iov_base = const_cast<void*>(buf), .iov_len = n };
  __suio uio = { .uio_iov = &iov, .uio_iovcnt = 1, .uio_resid = n };

  _SET_ORIENTATION(fp, ORIENT_BYTES);

  // The usual case is success (__sfvwrite returns 0); skip the divide if this happens,
  // since divides are generally slow.
  return (__sfvwrite(fp, &uio) == 0) ? count : ((n - uio.uio_resid) / size);
}

static FILE* __popen_fail(int fds[2]) {
  ErrnoRestorer errno_restorer;
  close(fds[0]);
  close(fds[1]);
  return nullptr;
}

FILE* popen(const char* cmd, const char* mode) {
  // Was the request for a socketpair or just a pipe?
  int fds[2];
  bool bidirectional = false;
  if (strchr(mode, '+') != nullptr) {
    if (socketpair(AF_LOCAL, SOCK_CLOEXEC | SOCK_STREAM, 0, fds) == -1return nullptr;
    bidirectional = true;
    mode = "r+";
  } else {
    if (pipe2(fds, O_CLOEXEC) == -1return nullptr;
    mode = strrchr(mode, 'r') ? "r" : "w";
  }

  // If the parent wants to read, the child's fd needs to be stdout.
  int parent, child, desired_child_fd;
  if (*mode == 'r') {
    parent = 0;
    child = 1;
    desired_child_fd = STDOUT_FILENO;
  } else {
    parent = 1;
    child = 0;
    desired_child_fd = STDIN_FILENO;
  }

  // Ensure that the child fd isn't the desired child fd.
  if (fds[child] == desired_child_fd) {
    int new_fd = fcntl(fds[child], F_DUPFD_CLOEXEC, 0);
    if (new_fd == -1return __popen_fail(fds);
    close(fds[child]);
    fds[child] = new_fd;
  }

  pid_t pid = vfork();
  if (pid == -1return __popen_fail(fds);

  if (pid == 0) {
    close(fds[parent]);
    // dup2 so that the child fd isn't closed on exec.
    if (dup2(fds[child], desired_child_fd) == -1) _exit(127);
    close(fds[child]);
    if (bidirectional) dup2(STDOUT_FILENO, STDIN_FILENO);
    execl(__bionic_get_shell_path(), "sh""-c""--", cmd, nullptr);
    _exit(127);
  }

  FILE* fp = fdopen(fds[parent], mode);
  if (fp == nullptr) return __popen_fail(fds);

  close(fds[child]);

  _EXT(fp)->_popen_pid = pid;
  return fp;
}

void flockfile(FILE* fp) {
  CHECK_FP(fp);
  pthread_mutex_lock(&_EXT(fp)->_lock);
}

int ftrylockfile(FILE* fp) {
  CHECK_FP(fp);
  // The specification for ftrylockfile() says it returns 0 on success,
  // or non-zero on error. We don't bother canonicalizing to 0/-1...
  return pthread_mutex_trylock(&_EXT(fp)->_lock);
}

void funlockfile(FILE* fp) {
  CHECK_FP(fp);
  pthread_mutex_unlock(&_EXT(fp)->_lock);
}

int fwide(FILE* fp, int mode) {
  CHECK_FP(fp);
  ScopedFileLock sfl(fp);
  if (mode != 0) _SET_ORIENTATION(fp, mode);
  return WCIO_GET(fp)->orientation;
}

namespace {

namespace phony {
#include <bits/struct_file.h>
}

static_assert(sizeof(::__sFILE) == sizeof(phony::__sFILE),
              "size mismatch between `struct __sFILE` implementation and public stub");
static_assert(alignof(::__sFILE) == alignof(phony::__sFILE),
              "alignment mismatch between `struct __sFILE` implementation and public stub");

}

Messung V0.5 in Prozent
C=87 H=97 G=91

¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet am  2026-06-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik