From: Rich Felker Date: Mon, 28 Mar 2011 05:14:44 +0000 (-0400) Subject: major stdio overhaul, using readv/writev, plus other changes X-Git-Tag: v0.7.5~37 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=e3cd6c5c265cd481db6e0c5b529855d99f0bda30;p=oweals%2Fmusl.git major stdio overhaul, using readv/writev, plus other changes the biggest change in this commit is that stdio now uses readv to fill the caller's buffer and the FILE buffer with a single syscall, and likewise writev to flush the FILE buffer and write out the caller's buffer in a single syscall. making this change required fundamental architectural changes to stdio, so i also made a number of other improvements in the process: - the implementation no longer assumes that further io will fail following errors, and no longer blocks io when the error flag is set (though the latter could easily be changed back if desired) - unbuffered mode is no longer implemented as a one-byte buffer. as a consequence, scanf unreading has to use ungetc, to the unget buffer has been enlarged to hold at least 2 wide characters. - the FILE structure has been rearranged to maintain the locations of the fields that might be used in glibc getc/putc type macros, while shrinking the structure to save some space. - error cases for fflush, fseek, etc. should be more correct. - library-internal macros are used for getc_unlocked and putc_unlocked now, eliminating some ugly code duplication. __uflow and __overflow are no longer used anywhere but these macros. switch to read or write mode is also separated so the code can be better shared, e.g. with ungetc. - lots of other small things. --- diff --git a/src/exit/exit.c b/src/exit/exit.c index d0c1bfc1..bfdb3923 100644 --- a/src/exit/exit.c +++ b/src/exit/exit.c @@ -17,7 +17,7 @@ void exit(int code) /* Only do atexit & stdio flush if they were actually used */ if (__funcs_on_exit) __funcs_on_exit(); - if (__fflush_on_exit) __fflush_on_exit(0); + if (__fflush_on_exit) __fflush_on_exit((void *)0); /* Destructor s**t is kept separate from atexit to avoid bloat */ if (libc.fini) libc.fini(); diff --git a/src/internal/stdio_impl.h b/src/internal/stdio_impl.h index b977df68..90a8214b 100644 --- a/src/internal/stdio_impl.h +++ b/src/internal/stdio_impl.h @@ -18,10 +18,11 @@ #include #include #include +#include #include "syscall.h" #include "libc.h" -#define UNGET 4 +#define UNGET 8 #define FLOCK(f) ((libc.lockfile && (f)->lock>=0) ? (libc.lockfile((f)),0) : 0) #define FUNLOCK(f) ((f)->lockcount && (--(f)->lockcount || ((f)->lock=0))) @@ -31,14 +32,18 @@ #define F_NOWR 8 #define F_EOF 16 #define F_ERR 32 +#define F_SVB 64 struct __FILE_s { unsigned flags; - unsigned char *rpos, *rstop; - unsigned char *rend, *wend; - unsigned char *wpos, *wstop; + unsigned char *rpos, *rend; + int (*close)(FILE *); + unsigned char *wend, *wpos; + unsigned char *mustbezero_1; unsigned char *wbase; - unsigned char *dummy01[3]; + size_t (*read)(FILE *, unsigned char *, size_t); + size_t (*write)(FILE *, const unsigned char *, size_t); + off_t (*seek)(FILE *, off_t, int); unsigned char *buf; size_t buf_size; FILE *prev, *next; @@ -46,26 +51,25 @@ struct __FILE_s { int pipe_pid; long dummy2; short dummy3; - char dummy4; + signed char mode; signed char lbf; int lock; int lockcount; void *cookie; off_t off; int (*flush)(FILE *); - void **wide_data; /* must be NULL */ - size_t (*read)(FILE *, unsigned char *, size_t); - size_t (*write)(FILE *, const unsigned char *, size_t); - off_t (*seek)(FILE *, off_t, int); - int mode; - int (*close)(FILE *); + void *mustbezero_2; }; size_t __stdio_read(FILE *, unsigned char *, size_t); size_t __stdio_write(FILE *, const unsigned char *, size_t); +size_t __stdout_write(FILE *, const unsigned char *, size_t); off_t __stdio_seek(FILE *, off_t, int); int __stdio_close(FILE *); +int __toread(FILE *); +int __towrite(FILE *); + int __overflow(FILE *, int); int __oflow(FILE *); int __uflow(FILE *); @@ -87,6 +91,12 @@ FILE *__fdopen(int, const char *); #define feof(f) ((f)->flags & F_EOF) #define ferror(f) ((f)->flags & F_ERR) +#define getc_unlocked(f) \ + ( ((f)->rpos < (f)->rend) ? *(f)->rpos++ : __uflow((f)) ) + +#define putc_unlocked(c, f) ( ((c)!=(f)->lbf && (f)->wpos<(f)->wend) \ + ? *(f)->wpos++ = (c) : __overflow((f),(c)) ) + /* Caller-allocated FILE * operations */ FILE *__fopen_rb_ca(const char *, FILE *, unsigned char *, size_t); int __fclose_ca(FILE *); diff --git a/src/stdio/__overflow.c b/src/stdio/__overflow.c index e35104de..3bb37923 100644 --- a/src/stdio/__overflow.c +++ b/src/stdio/__overflow.c @@ -1,52 +1,10 @@ #include "stdio_impl.h" -static int overflow(FILE *f, int c) +int __overflow(FILE *f, int _c) { - /* Initialize if we're not already writing */ - if (!f->wend) { - /* Fail if we're in error state or unwritable. */ - if (f->flags & (F_ERR|F_NOWR)) return EOF; - - /* Set byte orientation -1,0=>-1; 1=>1 */ - f->mode |= f->mode-1; - - /* Clear read buffer (easier than summoning nasal demons) */ - f->rpos = f->rend = f->rstop = 0; - - /* Activate write through the buffer */ - f->wpos = f->wbase = f->buf; - f->wend = f->buf + f->buf_size; - f->wstop = (f->lbf < 0) ? f->wend - 1 : 0; - } - - /* Buffer can always hold at least 1 byte... */ - if (c != EOF) { - *f->wpos++ = c; - if (f->wpos <= f->wstop && c != f->lbf) return c; - } - /* ...since if the next call fails, buffer is empty. */ - if (f->write(f, f->wbase, f->wpos - f->wbase) < 0) { - f->flags |= F_ERR; - f->wpos = f->wbase = f->wend = f->wstop = 0; - return EOF; - } - - /* Buffer is empty so reset position to beginning */ - f->wpos = f->wbase; - + unsigned char c = _c; + if (!f->wend && __towrite(f)) return EOF; + if (f->wpos < f->wend && c != f->lbf) return *f->wpos++ = c; + if (f->write(f, &c, 1)!=1) return EOF; return c; } - -int __overflow(FILE *f, int c) -{ - return overflow(f, c & 0xff); -} - -int __oflow(FILE *f) -{ - overflow(f, EOF); - return (f->flags & F_ERR) ? EOF : 0; -} - -/* Link flush-on-exit code iff any stdio write functions are linked. */ -int (*const __fflush_on_exit)(FILE *) = fflush; diff --git a/src/stdio/__stdio_read.c b/src/stdio/__stdio_read.c index d9bb3269..a2e4cd62 100644 --- a/src/stdio/__stdio_read.c +++ b/src/stdio/__stdio_read.c @@ -2,5 +2,21 @@ size_t __stdio_read(FILE *f, unsigned char *buf, size_t len) { - return syscall(SYS_read, f->fd, buf, len); + struct iovec iov[2] = { + { .iov_base = buf, .iov_len = len }, + { .iov_base = f->buf, .iov_len = f->buf_size } + }; + ssize_t cnt; + + cnt = syscall(SYS_readv, f->fd, iov, 2); + if (cnt <= 0) { + f->flags |= F_EOF ^ ((F_ERR^F_EOF) & cnt); + f->rpos = f->rend = 0; + return cnt; + } + if (cnt <= len) return cnt; + cnt -= len; + f->rpos = f->buf; + f->rend = f->buf + cnt; + return len; } diff --git a/src/stdio/__stdio_seek.c b/src/stdio/__stdio_seek.c index c205ab82..35ae788c 100644 --- a/src/stdio/__stdio_seek.c +++ b/src/stdio/__stdio_seek.c @@ -2,6 +2,7 @@ static off_t retneg1(FILE *f, off_t off, int whence) { + errno = ESPIPE; return -1; } @@ -15,7 +16,6 @@ off_t __stdio_seek(FILE *f, off_t off, int whence) ret = syscall(SYS_lseek, f->fd, off, whence); #endif /* Detect unseekable files and optimize future failures out */ - if (ret < 0 && off == 0 && whence == SEEK_CUR) - f->seek = retneg1; + if (ret < 0 && errno == ESPIPE) f->seek = retneg1; return ret; } diff --git a/src/stdio/__stdio_write.c b/src/stdio/__stdio_write.c index d4264eff..63d9c858 100644 --- a/src/stdio/__stdio_write.c +++ b/src/stdio/__stdio_write.c @@ -2,8 +2,29 @@ size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len) { - const unsigned char *stop = buf+len; - ssize_t cnt = 1; - for (; buffd, buf, len))>0; buf+=cnt); - return len-(stop-buf); + struct iovec iovs[2] = { + { .iov_base = f->wbase, .iov_len = f->wpos-f->wbase }, + { .iov_base = (void *)buf, .iov_len = len } + }; + struct iovec *iov = iovs; + size_t rem = iov[0].iov_len + iov[1].iov_len; + int iovcnt = 2; + ssize_t cnt; + f->wpos = f->wbase; + for (;;) { + cnt = syscall(SYS_writev, f->fd, iov, iovcnt); + if (cnt == rem) return len; + if (cnt < 0) { + f->wpos = f->wbase = f->wend = 0; + f->flags |= F_ERR; + return iovcnt == 2 ? 0 : len-iov[0].iov_len; + } + rem -= cnt; + if (cnt > iov[0].iov_len) { + cnt -= iov[0].iov_len; + iov++; iovcnt--; + } + iov[0].iov_base = (char *)iov[0].iov_base + cnt; + iov[0].iov_len -= cnt; + } } diff --git a/src/stdio/__stdout_write.c b/src/stdio/__stdout_write.c new file mode 100644 index 00000000..4683ffc3 --- /dev/null +++ b/src/stdio/__stdout_write.c @@ -0,0 +1,10 @@ +#include "stdio_impl.h" + +size_t __stdout_write(FILE *f, const unsigned char *buf, size_t len) +{ + struct termios tio; + f->write = __stdio_write; + if (!(f->flags & F_SVB) && syscall(SYS_ioctl, f->fd, TCGETS, &tio)) + f->lbf = -1; + return __stdio_write(f, buf, len); +} diff --git a/src/stdio/__toread.c b/src/stdio/__toread.c new file mode 100644 index 00000000..f00cc467 --- /dev/null +++ b/src/stdio/__toread.c @@ -0,0 +1,14 @@ +#include + +int __toread(FILE *f) +{ + f->mode |= f->mode-1; + if (f->wpos > f->buf) f->write(f, 0, 0); + f->wpos = f->wbase = f->wend = 0; + if (f->flags & (F_EOF|F_NORD)) { + if (f->flags & F_NORD) f->flags |= F_ERR; + return EOF; + } + f->rpos = f->rend = f->buf; + return 0; +} diff --git a/src/stdio/__towrite.c b/src/stdio/__towrite.c new file mode 100644 index 00000000..b4587419 --- /dev/null +++ b/src/stdio/__towrite.c @@ -0,0 +1,21 @@ +#include "stdio_impl.h" + +int __towrite(FILE *f) +{ + f->mode |= f->mode-1; + if (f->flags & (F_NOWR)) { + f->flags |= F_ERR; + return EOF; + } + /* Clear read buffer (easier than summoning nasal demons) */ + f->rpos = f->rend = 0; + + /* Activate write through the buffer. */ + f->wpos = f->wbase = f->buf; + f->wend = f->buf + f->buf_size; + + return 0; +} + +/* Link flush-on-exit code iff any stdio write functions are linked. */ +int (*const __fflush_on_exit)(FILE *) = fflush; diff --git a/src/stdio/__uflow.c b/src/stdio/__uflow.c index 5a51d610..544dda98 100644 --- a/src/stdio/__uflow.c +++ b/src/stdio/__uflow.c @@ -1,7 +1,11 @@ #include "stdio_impl.h" +/* This function will never be called if there is already data + * buffered for reading. Thus we can get by with very few branches. */ + int __uflow(FILE *f) { - if (__underflow(f) < 0) return EOF; - else return *f->rpos++; + unsigned char c = EOF; + if (f->rend || !__toread(f)) f->read(f, &c, 1); + return c; } diff --git a/src/stdio/__underflow.c b/src/stdio/__underflow.c deleted file mode 100644 index b769f4e4..00000000 --- a/src/stdio/__underflow.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "stdio_impl.h" - -int __underflow(FILE *f) -{ - ssize_t cnt; - - /* Read from buffer (Do we ever get called when this is true??) */ - if (f->rpos < f->rstop) return *f->rpos; - - /* Initialize if we're not already reading */ - if (!f->rstop) { - /* Fail immediately if unreadable, eof, or error state. */ - if (f->flags & (F_EOF|F_ERR|F_NORD)) return EOF; - - /* Set byte orientation -1,0=>-1; 1=>1 */ - f->mode |= f->mode-1; - - /* Flush any unwritten output; fail on error. */ - if (f->wpos > f->buf && __oflow(f)) return EOF; - - /* Disallow writes to buffer. */ - f->wstop = 0; - } - - /* Perform the underlying read operation */ - if ((cnt=f->read(f, f->buf, f->buf_size)) + 1 <= 1) { - /* Set flags and leave read mode */ - f->flags |= F_EOF | (cnt & F_ERR); - f->rpos = f->rend = f->rstop = 0; - return EOF; - } - - /* Setup buffer pointers for reading from buffer */ - f->rpos = f->buf; - f->rend = f->rstop = f->buf + cnt; - - return *f->rpos; -} diff --git a/src/stdio/fflush.c b/src/stdio/fflush.c index cf3f5b0e..cdbd39bc 100644 --- a/src/stdio/fflush.c +++ b/src/stdio/fflush.c @@ -2,17 +2,23 @@ static int __fflush_unlocked(FILE *f) { - /* If writing, flush output. */ - if (f->wpos > f->buf && __oflow(f)) return -1; + /* If writing, flush output */ + if (f->wpos > f->wbase) { + f->write(f, 0, 0); + if (!f->wpos) return EOF; + } /* If reading, sync position, per POSIX */ if (f->rpos < f->rend) f->seek(f, f->rpos-f->rend, SEEK_CUR); - f->rpos = f->rend; + + /* Clear read and write modes */ + f->wpos = f->wbase = f->wend = 0; + f->rpos = f->rend = 0; /* Hook for special behavior on flush */ if (f->flush) f->flush(f); - return (f->flags & F_ERR) ? EOF : 0; + return 0; } /* stdout.c will override this if linked */ @@ -36,9 +42,9 @@ int fflush(FILE *f) OFLLOCK(); for (f=ofl_head; f; f=next) { FLOCK(f); - OFLUNLOCK(); + //OFLUNLOCK(); r |= __fflush_unlocked(f); - OFLLOCK(); + //OFLLOCK(); next = f->next; FUNLOCK(f); } diff --git a/src/stdio/fgetc.c b/src/stdio/fgetc.c index 3a7f1e30..da638684 100644 --- a/src/stdio/fgetc.c +++ b/src/stdio/fgetc.c @@ -4,7 +4,9 @@ int fgetc(FILE *f) { int c; FLOCK(f); - c = f->rpos < f->rstop ? *f->rpos++ : __uflow(f); + c = getc_unlocked(f); FUNLOCK(f); return c; } + +weak_alias(fgetc, getc); diff --git a/src/stdio/fgets.c b/src/stdio/fgets.c index 7939303e..3135a69a 100644 --- a/src/stdio/fgets.c +++ b/src/stdio/fgets.c @@ -7,12 +7,17 @@ char *fgets(char *s, int n, FILE *f) char *p = s; unsigned char *z; size_t k; + int c; - if (!n--) return 0; + if (n--<=1) { + if (n) return 0; + *s = 0; + return s; + } FLOCK(f); - while (n && !feof(f)) { + while (n) { z = memchr(f->rpos, '\n', f->rend - f->rpos); k = z ? z - f->rpos + 1 : f->rend - f->rpos; k = MIN(k, n); @@ -20,15 +25,19 @@ char *fgets(char *s, int n, FILE *f) f->rpos += k; p += k; n -= k; - if (z) break; - __underflow(f); + if (z || !n) break; + if ((c = getc_unlocked(f)) < 0) { + if (p==s || !feof(f)) s = 0; + break; + } + n--; + if ((*p++ = c) == '\n') break; } *p = 0; - if (ferror(f)) p = s; FUNLOCK(f); - return (p == s) ? 0 : s; + return s; } weak_alias(fgets, fgets_unlocked); diff --git a/src/stdio/fgetwc.c b/src/stdio/fgetwc.c index 77b30fd1..5e420594 100644 --- a/src/stdio/fgetwc.c +++ b/src/stdio/fgetwc.c @@ -23,9 +23,9 @@ wint_t __fgetwc_unlocked(FILE *f) } } else l = -2; - /* Convert character byte-by-byte from __uflow */ + /* Convert character byte-by-byte */ while (l == -2) { - b = c = __uflow(f); + b = c = getc_unlocked(f); if (c < 0) { if (!mbsinit(&st)) errno = EILSEQ; return WEOF; diff --git a/src/stdio/fputc.c b/src/stdio/fputc.c index ec859385..98d0a20a 100644 --- a/src/stdio/fputc.c +++ b/src/stdio/fputc.c @@ -3,8 +3,9 @@ int fputc(int c, FILE *f) { FLOCK(f); - if (c != f->lbf && f->wpos + 1 < f->wend) *f->wpos++ = c; - else c = __overflow(f, c); + c = putc_unlocked(c, f); FUNLOCK(f); return c; } + +weak_alias(fputc, putc); diff --git a/src/stdio/fputwc.c b/src/stdio/fputwc.c index ec49b5c6..292a53fb 100644 --- a/src/stdio/fputwc.c +++ b/src/stdio/fputwc.c @@ -8,8 +8,7 @@ wint_t __fputwc_unlocked(wchar_t c, FILE *f) f->mode |= f->mode+1; if (isascii(c)) { - if (c != f->lbf && f->wpos + 1 < f->wend) *f->wpos++ = c; - else c = __overflow(f, c); + c = putc_unlocked(c, f); } else if (f->wpos + MB_LEN_MAX < f->wend) { l = wctomb((void *)f->wpos, c); if (l < 0) c = WEOF; diff --git a/src/stdio/fread.c b/src/stdio/fread.c index 0fa0b2aa..8105fe99 100644 --- a/src/stdio/fread.c +++ b/src/stdio/fread.c @@ -12,38 +12,31 @@ size_t fread(void *destv, size_t size, size_t nmemb, FILE *f) FLOCK(f); - for (;;) { + if (f->rend - f->rpos > 0) { /* First exhaust the buffer. */ k = MIN(f->rend - f->rpos, l); memcpy(dest, f->rpos, k); f->rpos += k; dest += k; l -= k; - - /* Stop on EOF or errors */ - if (f->flags & (F_EOF|F_ERR|F_NORD)) goto eof; - - /* Done? Or going unbuffered? */ - if (!l || l > f->buf_size/2) break; - - /* Otherwise, refill & read thru buffer. */ - __underflow(f); + } + + if (!l) { + FUNLOCK(f); + return nmemb; } /* Read the remainder directly */ for (; l; l-=k, dest+=k) { k = f->read(f, dest, l); if (k+1<=1) { - f->flags |= F_EOF | (F_ERR & k); - goto eof; + FUNLOCK(f); + return (len-l)/size; } } FUNLOCK(f); return nmemb; -eof: - FUNLOCK(f); - return (len-l)/size; } weak_alias(fread, fread_unlocked); diff --git a/src/stdio/fseek.c b/src/stdio/fseek.c index bfaad375..8d9da440 100644 --- a/src/stdio/fseek.c +++ b/src/stdio/fseek.c @@ -5,17 +5,25 @@ int __fseeko_unlocked(FILE *f, off_t off, int whence) /* Adjust relative offset for unread data in buffer, if any. */ if (whence == SEEK_CUR) off -= f->rend - f->rpos; - /* If writing, flush output. */ - if (f->wpos > f->buf && __oflow(f)) return -1; + /* Flush write buffer, and report error on failure. */ + if (f->wpos > f->wbase) { + f->write(f, 0, 0); + if (!f->wpos) return -1; + } - /* Perform the underlying seek operation. */ - if (f->seek(f, off, whence) < 0) return -1; + /* Leave writing mode */ + f->wpos = f->wbase = f->wend = 0; + + /* Perform the underlying seek. */ + if (f->seek(f, off, whence) < 0) { + f->flags |= F_ERR; + return -1; + } /* If seek succeeded, file is seekable and we discard read buffer. */ - f->rpos = f->rend = f->rstop = 0; + f->rpos = f->rend = 0; f->flags &= ~F_EOF; - FUNLOCK(f); return 0; } diff --git a/src/stdio/fwrite.c b/src/stdio/fwrite.c index 23974fe1..02908c4b 100644 --- a/src/stdio/fwrite.c +++ b/src/stdio/fwrite.c @@ -2,50 +2,36 @@ size_t __fwritex(const unsigned char *s, size_t l, FILE *f) { - size_t i = 0; - size_t k = f->wend - f->wpos; + size_t i=0; + + if (!f->wend && __towrite(f)) return 0; + + if (l > f->wend - f->wpos) return f->write(f, s, l); - /* Handle line-buffered mode by breaking into 2 parts */ if (f->lbf >= 0) { /* Match /^(.*\n|)/ */ for (i=l; i && s[i-1] != '\n'; i--); if (i) { - f->lbf = EOF; - __fwritex(s, i, f); - f->lbf = '\n'; - __oflow(f); - return ferror(f) ? 0 : i + __fwritex(s+i, l-i, f); + if (f->write(f, s, i) < i) + return i; + s += i; + l -= i; } } - /* Buffer initial segment */ - if (k > l) k = l; - memcpy(f->wpos, s, k); - f->wpos += k; - if (f->wpos < f->wend) return l; - - /* If there's work left to do, flush buffer */ - __oflow(f); - if (ferror(f)) return 0; - - /* If the remainder will not fit in buffer, write it directly */ - if (l - k >= f->wend - f->wpos) - return k + f->write(f, s+k, l-k); - - /* Otherwise, buffer the remainder */ - memcpy(f->wpos, s+k, l-k); - f->wpos += l-k; - return l; + memcpy(f->wpos, s, l); + f->wpos += l; + return l+i; } size_t fwrite(const void *src, size_t size, size_t nmemb, FILE *f) { - size_t l = size*nmemb; + size_t k, l = size*nmemb; if (!l) return l; FLOCK(f); - l = __fwritex(src, l, f); + k = __fwritex(src, l, f); FUNLOCK(f); - return l/size; + return k==l ? nmemb : l/size; } weak_alias(fwrite, fwrite_unlocked); diff --git a/src/stdio/getc.c b/src/stdio/getc.c deleted file mode 100644 index b739b0a5..00000000 --- a/src/stdio/getc.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "stdio_impl.h" - -int getc(FILE *f) -{ - return fgetc(f); -} diff --git a/src/stdio/getc_unlocked.c b/src/stdio/getc_unlocked.c index 629223ea..203a1081 100644 --- a/src/stdio/getc_unlocked.c +++ b/src/stdio/getc_unlocked.c @@ -1,8 +1,8 @@ #include "stdio_impl.h" -int getc_unlocked(FILE *f) +int (getc_unlocked)(FILE *f) { - return f->rpos < f->rstop ? *f->rpos++ : __uflow(f); + return getc_unlocked(f); } weak_alias (getc_unlocked, fgetc_unlocked); diff --git a/src/stdio/getchar_unlocked.c b/src/stdio/getchar_unlocked.c index 299cb958..355ac318 100644 --- a/src/stdio/getchar_unlocked.c +++ b/src/stdio/getchar_unlocked.c @@ -2,5 +2,5 @@ int getchar_unlocked(void) { - return stdin->rpos < stdin->rstop ? *stdin->rpos++ : __uflow(stdin); + return getc_unlocked(stdin); } diff --git a/src/stdio/getdelim.c b/src/stdio/getdelim.c index f770d20b..20d345d1 100644 --- a/src/stdio/getdelim.c +++ b/src/stdio/getdelim.c @@ -8,6 +8,7 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) unsigned char *z; size_t k; size_t i=0; + int c; if (!n || !s) { errno = EINVAL; @@ -18,16 +19,16 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) FLOCK(f); - while (!feof(f)) { + for (;;) { z = memchr(f->rpos, delim, f->rend - f->rpos); k = z ? z - f->rpos + 1 : f->rend - f->rpos; if (i+k >= *n) { - if (k >= SIZE_MAX-i) goto oom; - *n = i+k+1; - if (*n < SIZE_MAX/2) *n *= 2; + if (k >= SIZE_MAX/2-i) goto oom; + *n = i+k+2; + if (*n < SIZE_MAX/4) *n *= 2; tmp = realloc(*s, *n); if (!tmp) { - *n = i+k+1; + *n = i+k+2; tmp = realloc(*s, *n); if (!tmp) goto oom; } @@ -37,23 +38,22 @@ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) f->rpos += k; i += k; if (z) break; - __underflow(f); + if ((c = getc_unlocked(f)) == EOF) { + if (!i || !feof(f)) { + FUNLOCK(f); + return -1; + } + break; + } + if (((*s)[i++] = c) == delim) break; } (*s)[i] = 0; - if (feof(f) || ferror(f)) { - FUNLOCK(f); - return -1; - } FUNLOCK(f); - if (i > SSIZE_MAX) { - errno = EOVERFLOW; - return -1; - } - return i; oom: + FUNLOCK(f); errno = ENOMEM; return -1; } diff --git a/src/stdio/putc.c b/src/stdio/putc.c deleted file mode 100644 index 3c9dc11e..00000000 --- a/src/stdio/putc.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "stdio_impl.h" - -int putc(int c, FILE *f) -{ - return fputc(c, f); -} - -weak_alias(putc, _IO_putc); diff --git a/src/stdio/putc_unlocked.c b/src/stdio/putc_unlocked.c index f01da717..b47876c9 100644 --- a/src/stdio/putc_unlocked.c +++ b/src/stdio/putc_unlocked.c @@ -1,8 +1,8 @@ #include "stdio_impl.h" -int putc_unlocked(int c, FILE *f) +int (putc_unlocked)(int c, FILE *f) { - return f->wpos < f->wstop ? (*f->wpos++ = c) : __overflow(f, c); + return putc_unlocked(c, f); } weak_alias(putc_unlocked, fputc_unlocked); diff --git a/src/stdio/putchar_unlocked.c b/src/stdio/putchar_unlocked.c index 72d47d15..8b5d0603 100644 --- a/src/stdio/putchar_unlocked.c +++ b/src/stdio/putchar_unlocked.c @@ -2,6 +2,5 @@ int putchar_unlocked(int c) { - return stdout->wpos < stdout->wstop ? - (*stdout->wpos++ = c) : __overflow(stdout, c); + return putc_unlocked(c, stdout); } diff --git a/src/stdio/setvbuf.c b/src/stdio/setvbuf.c index 2985d3f1..6dea0ebf 100644 --- a/src/stdio/setvbuf.c +++ b/src/stdio/setvbuf.c @@ -14,9 +14,11 @@ int setvbuf(FILE *f, char *buf, int type, size_t size) f->lbf = EOF; if (type == _IONBF) - f->buf_size = 1; + f->buf_size = 0; else if (type == _IOLBF) f->lbf = '\n'; + f->flags |= F_SVB; + return 0; } diff --git a/src/stdio/stderr.c b/src/stdio/stderr.c index 4a79d4e7..3bdaffbc 100644 --- a/src/stdio/stderr.c +++ b/src/stdio/stderr.c @@ -1,9 +1,9 @@ #include "stdio_impl.h" -static unsigned char buf[1+UNGET]; +static unsigned char buf[UNGET]; static FILE f = { .buf = buf+UNGET, - .buf_size = 1, + .buf_size = 0, .fd = 2, .flags = F_PERM | F_NORD, .write = __stdio_write, diff --git a/src/stdio/stdout.c b/src/stdio/stdout.c index bf6eea6c..552d729e 100644 --- a/src/stdio/stdout.c +++ b/src/stdio/stdout.c @@ -7,7 +7,7 @@ static FILE f = { .fd = 1, .flags = F_PERM | F_NORD, .lbf = '\n', - .write = __stdio_write, + .write = __stdout_write, .seek = __stdio_seek, .close = __stdio_close, }; diff --git a/src/stdio/ungetc.c b/src/stdio/ungetc.c index 07181684..7f56f8d5 100644 --- a/src/stdio/ungetc.c +++ b/src/stdio/ungetc.c @@ -6,25 +6,11 @@ int ungetc(int c, FILE *f) FLOCK(f); - /* Fail if unreadable or writing and unable to flush */ - if ((f->flags & (F_ERR|F_NORD)) || (f->wpos && __oflow(f))) { + if ((!f->rend && __toread(f)) || f->rpos <= f->buf - UNGET) { FUNLOCK(f); return EOF; } - /* Clear write mode */ - f->wbase = f->wpos = f->wstop = f->wend = 0; - - /* Put the file in read mode */ - if (!f->rpos) f->rpos = f->rend = f->buf; - - /* If unget buffer is already full, fail. */ - if (f->rpos <= f->buf - UNGET) { - FUNLOCK(f); - return EOF; - } - - /* Put a byte back into the buffer */ *--f->rpos = c; f->flags &= ~F_EOF; diff --git a/src/stdio/ungetwc.c b/src/stdio/ungetwc.c index 6871d034..5282fee1 100644 --- a/src/stdio/ungetwc.c +++ b/src/stdio/ungetwc.c @@ -15,29 +15,14 @@ wint_t ungetwc(wint_t c, FILE *f) f->mode |= f->mode+1; - /* Fail if unreadable or writing and unable to flush */ - if ((f->flags & (F_ERR|F_NORD)) || (f->wpos && __oflow(f))) { + if ((!f->rend && __toread(f)) || f->rpos < f->buf - UNGET + l) { FUNLOCK(f); return EOF; } - /* Clear write mode */ - f->wpos = f->wstop = f->wend = 0; - - /* Put the file in read mode */ - if (!f->rpos) f->rpos = f->rend = f->buf; - - /* If unget buffer is nonempty, fail. */ - if (f->rpos < f->buf) { - FUNLOCK(f); - return WEOF; - } - - /* Put character back into the buffer */ if (isascii(c)) *--f->rpos = c; else memcpy(f->rpos -= l, mbc, l); - /* Clear EOF */ f->flags &= ~F_EOF; FUNLOCK(f); diff --git a/src/stdio/vdprintf.c b/src/stdio/vdprintf.c index 68562e05..faf9536a 100644 --- a/src/stdio/vdprintf.c +++ b/src/stdio/vdprintf.c @@ -11,9 +11,9 @@ int vdprintf(int fd, const char *fmt, va_list ap) unsigned char buf[BUFSIZ]; FILE f = { .fd = fd, .lbf = EOF, .write = wrap_write, - .buf = buf+UNGET, .buf_size = sizeof buf - UNGET + .buf = buf+UNGET, .buf_size = sizeof buf - UNGET, + .lock = -1 }; r = vfprintf(&f, fmt, ap); - __oflow(&f); - return r; + return fflush(&f) ? EOF : r; } diff --git a/src/stdio/vfprintf.c b/src/stdio/vfprintf.c index b6bb3bcf..57878c03 100644 --- a/src/stdio/vfprintf.c +++ b/src/stdio/vfprintf.c @@ -434,7 +434,7 @@ static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, /* Update output count, end loop when fmt is exhausted */ if (cnt >= 0) { if (l > INT_MAX - cnt) { - if (!ferror(f)) errno = EOVERFLOW; + errno = EOVERFLOW; cnt = -1; } else cnt += l; } diff --git a/src/stdio/vfscanf.c b/src/stdio/vfscanf.c index 69f45081..414c2a3d 100644 --- a/src/stdio/vfscanf.c +++ b/src/stdio/vfscanf.c @@ -9,7 +9,7 @@ static void f_read(rctx_t *r) { FILE *f = r->opaque; - if ((r->c = __uflow(f)) >= 0) r->l++; + if ((r->c = getc_unlocked(f)) >= 0) r->l++; } int vfscanf(FILE *f, const char *fmt, va_list ap) @@ -28,15 +28,8 @@ int vfscanf(FILE *f, const char *fmt, va_list ap) result = __scanf(&r, fmt2, ap); - if (r.u && r.c >= 0) { - /* This code takes care of the case where the caller performs - * a nonmatching scanf to leave a character in the unscan - * buffer, followed by an unget, followed by a scanf that - * matches zero characters. In this case the final 'unread' - * character must be returned to the unget buffer rather than - * the unscan buffer. */ - f->rpos--; - } + if (r.u && r.c >= 0) + ungetc(r.c, f); FUNLOCK(f); return result; diff --git a/src/stdio/vsnprintf.c b/src/stdio/vsnprintf.c index 1f316ca4..ba17bd7d 100644 --- a/src/stdio/vsnprintf.c +++ b/src/stdio/vsnprintf.c @@ -2,33 +2,37 @@ static size_t sn_write(FILE *f, const unsigned char *s, size_t l) { - /* pretend to succeed, but discard data */ + size_t k = f->wend - f->wpos; + if (k > l) k = l; + memcpy(f->wpos, s, k); + f->wpos += k; + /* pretend to succeed, but discard extra data */ return l; } int vsnprintf(char *s, size_t n, const char *fmt, va_list ap) { int r; - FILE f; - unsigned char buf[1]; + char b; + FILE f = { .lbf = EOF, .write = sn_write, .lock = -1 }; - memset(&f, 0, sizeof(FILE)); - f.lbf = EOF; - f.write = sn_write; - f.buf_size = 1; - f.buf = buf; - f.lock = -1; - if (n > INT_MAX) { - errno = EOVERFLOW; - return -1; - } else if (n > 0) { - if (n > (char *)0+SIZE_MAX-s) n = (char *)0+SIZE_MAX-s; - f.wpos = (void *)s; - f.wbase = f.wend = (void *)(s+n-1); - f.wstop = f.wend - 1; + if (n-1 > INT_MAX-1) { + if (n) { + errno = EOVERFLOW; + return -1; + } + s = &b; + n = 1; } + + /* Ensure pointers don't wrap if "infinite" n is passed in */ + if (n > (char *)0+SIZE_MAX-s-1) n = (char *)0+SIZE_MAX-s-1; + f.buf_size = n; + f.buf = f.wpos = (void *)s; + f.wbase = f.wend = (void *)(s+n); r = vfprintf(&f, fmt, ap); - /* wpos points just after last byte written, or to s+n-1 (wbase) */ - *f.wpos = 0; + + /* Null-terminate, overwriting last char if dest buffer is full */ + if (n) f.wpos[-(f.wpos == f.wend)] = 0; return r; } diff --git a/src/stdio/vswprintf.c b/src/stdio/vswprintf.c index 2d9f2002..8e8f80ce 100644 --- a/src/stdio/vswprintf.c +++ b/src/stdio/vswprintf.c @@ -10,6 +10,8 @@ static size_t sw_write(FILE *f, const unsigned char *s, size_t l) size_t l0 = l; int i = 0; struct cookie *c = f->cookie; + if (s!=f->wbase && sw_write(f, f->wbase, f->wpos-f->wbase)==-1) + return -1; while (c->l && l && (i=mbtowc(c->ws, (void *)s, l))>=0) { s+=i; l-=i; @@ -41,6 +43,6 @@ int vswprintf(wchar_t *s, size_t n, const wchar_t *fmt, va_list ap) return -1; } r = vfwprintf(&f, fmt, ap); - __oflow(&f); + sw_write(&f, 0, 0); return r>=n ? -1 : r; }