From da36e7e256fd03de61cbdf736befdf5d238b162a Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 26 Mar 2021 22:31:41 -0700 Subject: [PATCH] Make major improvements to stdio Buffering now has optimal performance, bugs have been fixed, and some missing apis have been introduced. This implementation is also now more production worthy since it's less brittle now in terms of system errors. That's going to help redbean since lua i/o is all based on stdio. See #97 --- libc/{time => calls}/usleep.c | 0 libc/dns/parsehoststxt.c | 19 +- libc/log/vflogf.c | 11 +- libc/runtime/ftrace.c | 4 +- libc/stdio/fdopen.c | 2 - libc/stdio/fflush.c | 21 +- libc/stdio/fgetc.c | 16 +- libc/stdio/fgetwc.c | 29 +- libc/stdio/fgetws.c | 2 +- libc/stdio/fmemopen.c | 14 +- libc/stdio/fputc.c | 33 +-- libc/stdio/fputs.c | 18 +- libc/stdio/fputwc.c | 9 +- libc/stdio/fread.c | 76 ++++- libc/stdio/fseek.c | 29 +- libc/stdio/fseeko.c | 83 ++++++ libc/stdio/ftell.c | 23 +- libc/stdio/ftello.S | 23 -- libc/stdio/ftello.c | 54 ++++ libc/stdio/fwrite.c | 59 +++- libc/stdio/getc.S | 24 -- libc/stdio/{fseterr.c => getc.c} | 15 +- libc/stdio/getchar.S | 26 -- libc/stdio/getchar.c | 27 ++ libc/stdio/getdelim.c | 75 ++--- libc/stdio/getwc.S | 23 -- libc/stdio/{fseterrno.c => getwc.c} | 11 +- libc/stdio/getwchar.S | 27 -- libc/stdio/{fseteof.c => getwchar.c} | 10 +- libc/stdio/internal.h | 16 +- libc/stdio/putc.S | 23 -- libc/stdio/{freadbuf.c => putc.c} | 19 +- libc/stdio/puts.c | 18 +- libc/stdio/putwc.S | 27 -- libc/stdio/{fseeko.S => putwc.c} | 17 +- libc/stdio/putwchar.c | 5 +- libc/stdio/rewind.c | 5 +- libc/stdio/setbuf.c | 6 +- libc/stdio/setbuffer.c | 14 +- libc/stdio/setlinebuf.c | 27 ++ libc/stdio/setvbuf.c | 13 +- libc/stdio/stdbuf.c | 4 +- libc/stdio/stderr-init.S | 3 - libc/stdio/stdin-init.S | 3 - libc/stdio/stdio.h | 43 +-- libc/stdio/stdout-init.S | 3 - libc/stdio/ungetc.c | 17 +- libc/stdio/{fwritebuf.c => ungetwc.c} | 38 ++- libc/str/tprecode8to16.c | 7 +- net/http/escapejsstringliteral.c | 153 ++++++++--- net/http/gethttpheader.gperf | 9 +- net/http/gethttpheader.inc | 259 ++++++++++-------- net/http/gethttpreason.c | 3 + net/http/http.h | 7 +- test/libc/dns/parsehoststxt_test.c | 2 +- test/libc/stdio/fgetwc_test.c | 12 +- test/libc/stdio/fputc_test.c | 62 +++++ test/libc/stdio/fputs_test.c | 31 ++- test/libc/stdio/ftell_test.c | 177 ++++++++++++ test/libc/stdio/fwrite_test.c | 161 +++++++++-- test/libc/stdio/getline_test.c | 43 +++ test/libc/stdio/ungetc_test.c | 69 +++++ test/net/http/escapehtml_test.c | 12 + test/net/http/escapejsstringliteral_test.c | 79 ++++++ .../http/escapeurlparam_test.c} | 39 ++- third_party/lua/lbaselib.c | 2 +- tool/net/.init.lua | 1 + tool/net/.reload.lua | 1 + tool/net/redbean.c | 137 ++++----- 69 files changed, 1595 insertions(+), 735 deletions(-) rename libc/{time => calls}/usleep.c (100%) create mode 100644 libc/stdio/fseeko.c delete mode 100644 libc/stdio/ftello.S create mode 100644 libc/stdio/ftello.c delete mode 100644 libc/stdio/getc.S rename libc/stdio/{fseterr.c => getc.c} (88%) delete mode 100644 libc/stdio/getchar.S create mode 100644 libc/stdio/getchar.c delete mode 100644 libc/stdio/getwc.S rename libc/stdio/{fseterrno.c => getwc.c} (91%) delete mode 100644 libc/stdio/getwchar.S rename libc/stdio/{fseteof.c => getwchar.c} (91%) delete mode 100644 libc/stdio/putc.S rename libc/stdio/{freadbuf.c => putc.c} (86%) delete mode 100644 libc/stdio/putwc.S rename libc/stdio/{fseeko.S => putwc.c} (83%) create mode 100644 libc/stdio/setlinebuf.c rename libc/stdio/{fwritebuf.c => ungetwc.c} (77%) create mode 100644 test/libc/stdio/fputc_test.c create mode 100644 test/libc/stdio/ftell_test.c create mode 100644 test/libc/stdio/ungetc_test.c create mode 100644 test/net/http/escapejsstringliteral_test.c rename test/{libc/stdio/fread_test.c => net/http/escapeurlparam_test.c} (71%) create mode 100644 tool/net/.init.lua create mode 100644 tool/net/.reload.lua diff --git a/libc/time/usleep.c b/libc/calls/usleep.c similarity index 100% rename from libc/time/usleep.c rename to libc/calls/usleep.c diff --git a/libc/dns/parsehoststxt.c b/libc/dns/parsehoststxt.c index a9455a9b..937ca326 100644 --- a/libc/dns/parsehoststxt.c +++ b/libc/dns/parsehoststxt.c @@ -19,6 +19,7 @@ #include "libc/alg/arraylist.internal.h" #include "libc/dns/dns.h" #include "libc/dns/hoststxt.h" +#include "libc/errno.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" @@ -43,28 +44,28 @@ * @see hoststxtsort() which is the logical next step */ int parsehoststxt(struct HostsTxt *ht, FILE *f) { - int rc; char *line; size_t linesize; - rc = 0; + struct HostsTxtEntry entry; + char *addr, *name, *tok, *comment; line = NULL; linesize = 0; while ((getline(&line, &linesize, f)) != -1) { - struct HostsTxtEntry entry; - char *addr, *name, *tok, *comment; if ((comment = strchr(line, '#'))) *comment = '\0'; if ((addr = strtok_r(line, " \t\r\n\v", &tok)) && inet_pton(AF_INET, addr, entry.ip) == 1) { entry.canon = ht->strings.i; while ((name = strtok_r(NULL, " \t\r\n\v", &tok))) { entry.name = ht->strings.i; - if (concat(&ht->strings, name, strnlen(name, DNS_NAME_MAX) + 1) == -1 || - append(&ht->entries, &entry) == -1) { - rc = -1; - } + concat(&ht->strings, name, strnlen(name, DNS_NAME_MAX) + 1); + append(&ht->entries, &entry); } } } free(line); - return rc | ferror(f); + if (ferror(f)) { + errno = ferror(f); + return -1; + } + return 0; } diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 78a9c05b..70f3537d 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -64,7 +64,7 @@ void vflogf_onfail(FILE *f) { struct stat st; if (IsTiny()) return; err = ferror(f); - if ((err == ENOSPC || err == EDQUOT || err == EFBIG) && + if (fileno(f) != -1 && (err == ENOSPC || err == EDQUOT || err == EFBIG) && (fstat(fileno(f), &st) == -1 || st.st_size > kNontrivialSize)) { ftruncate(fileno(f), 0); fseek(f, SEEK_SET, 0); @@ -91,6 +91,7 @@ void vflogf_onfail(FILE *f) { */ void(vflogf)(unsigned level, const char *file, int line, FILE *f, const char *fmt, va_list va) { + int bufmode; struct tm tm; long double t2; const char *prog; @@ -98,7 +99,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, char buf32[32], *buf32p; int64_t secs, nsec, dots; if (!f) f = __log_file; - if (fileno(f) == -1) return; + if (!f) return; t2 = nowl(); secs = t2; nsec = (t2 - secs) * 1e9L; @@ -114,6 +115,8 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, buf32p = "--------------------"; } prog = basename(program_invocation_name); + bufmode = f->bufmode; + if (bufmode == _IOLBF) f->bufmode = _IOFBF; if ((fprintf)(f, "%c%s%06ld:%s:%d:%.*s:%d] ", vflogf_loglevel2char(level), buf32p, rem1000000int64(div1000int64(dots)), file, line, strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { @@ -122,6 +125,10 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, (vfprintf)(f, fmt, va); va_end(va); fputs("\n", f); + if (bufmode == _IOLBF) { + f->bufmode = _IOLBF; + fflush(f); + } if (level == kLogFatal) { __start_fatal(file, line); strcpy(buf32, "unknown"); diff --git a/libc/runtime/ftrace.c b/libc/runtime/ftrace.c index 293b6b2f..5c190ca8 100644 --- a/libc/runtime/ftrace.c +++ b/libc/runtime/ftrace.c @@ -53,7 +53,7 @@ static char g_buf[512]; static const char *g_lastsymbol; static struct SymbolTable *g_symbols; -forceinline int GetNestingLevel(struct StackFrame *frame) { +static noasan int GetNestingLevel(struct StackFrame *frame) { int nesting = -2; while (frame) { ++nesting; @@ -69,7 +69,7 @@ forceinline int GetNestingLevel(struct StackFrame *frame) { * prologues of other functions. We assume those functions behave * according to the System Five NexGen32e ABI. */ -privileged void ftrace(void) { +privileged noasan void ftrace(void) { size_t i, j, nesting; const char *symbol; struct StackFrame *frame; diff --git a/libc/stdio/fdopen.c b/libc/stdio/fdopen.c index 3b05e83b..0f786fab 100644 --- a/libc/stdio/fdopen.c +++ b/libc/stdio/fdopen.c @@ -35,8 +35,6 @@ FILE *fdopen(int fd, const char *mode) { FILE *f; if ((f = calloc(1, sizeof(FILE)))) { f->fd = fd; - f->reader = __freadbuf; - f->writer = __fwritebuf; f->bufmode = ischardev(fd) ? _IOLBF : _IOFBF; f->iomode = fopenflags(mode); f->size = BUFSIZ; diff --git a/libc/stdio/fflush.c b/libc/stdio/fflush.c index 400e1427..99a758d9 100644 --- a/libc/stdio/fflush.c +++ b/libc/stdio/fflush.c @@ -20,6 +20,7 @@ #include "libc/bits/bits.h" #include "libc/bits/pushpop.h" #include "libc/calls/calls.h" +#include "libc/errno.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" @@ -36,28 +37,26 @@ */ int fflush(FILE *f) { size_t i; - int rc; - rc = 0; + ssize_t rc; if (!f) { for (i = __fflush.handles.i; i; --i) { if ((f = __fflush.handles.p[i - 1])) { - if (fflush(f) == -1) { - rc = -1; - break; - } + if (fflush(f) == -1) return -1; } } } else if (f->fd != -1) { - while (f->beg && !f->end) { - if (__fwritebuf(f) == -1) { - rc = -1; - break; + while (f->beg && !f->end && (f->iomode & O_ACCMODE) != O_RDONLY) { + if ((rc = write(f->fd, f->buf, f->beg)) == -1) { + f->state = errno; + return -1; } + if (rc != f->beg) abort(); + f->beg = 0; } } else if (f->beg && f->beg < f->size) { f->buf[f->beg] = 0; } - return rc; + return 0; } textstartup int __fflush_register(FILE *f) { diff --git a/libc/stdio/fgetc.c b/libc/stdio/fgetc.c index 4cc4d2f7..034b9678 100644 --- a/libc/stdio/fgetc.c +++ b/libc/stdio/fgetc.c @@ -16,22 +16,18 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" -static noinline int __fgetc(FILE *f) { - if (!f->reader) return __fseteof(f); - if (f->reader(f) == -1) return -1; - return f->buf[f->beg++]; -} - /** - * Reads uint8_t from stream. + * Reads byte from stream. + * @return byte in range 0..255, or -1 w/ errno */ int fgetc(FILE *f) { + unsigned char b; if (f->beg < f->end) { - return f->buf[f->beg++]; + return f->buf[f->beg++] & 0xff; } else { - return __fgetc(f); + if (!fread(&b, 1, 1, f)) return -1; + return b; } } diff --git a/libc/stdio/fgetwc.c b/libc/stdio/fgetwc.c index 605b4840..64112364 100644 --- a/libc/stdio/fgetwc.c +++ b/libc/stdio/fgetwc.c @@ -17,16 +17,35 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/stdio.h" +#include "libc/str/thompike.h" #include "libc/str/tpdecodecb.internal.h" /** * Reads UTF-8 character from stream. - * * @return wide character or -1 on EOF or error */ wint_t fgetwc(FILE *f) { - wint_t res; - res = -1; - tpdecodecb(&res, fgetc(f), (void *)fgetc, f); - return res; + int c, n; + wint_t b, x, y; + if (f->beg < f->end) { + b = f->buf[f->beg++] & 0xff; + } else if ((c = fgetc(f)) != -1) { + b = c; + } else { + return -1; + } + if (b < 0300) return b; + n = ThomPikeLen(b); + x = ThomPikeByte(b); + while (--n) { + if ((c = fgetc(f)) == -1) return -1; + y = c; + if (ThomPikeCont(y)) { + x = ThomPikeMerge(x, y); + } else { + ungetc(y, f); + return b; + } + } + return x; } diff --git a/libc/stdio/fgetws.c b/libc/stdio/fgetws.c index 6a67a525..3fdd3b3b 100644 --- a/libc/stdio/fgetws.c +++ b/libc/stdio/fgetws.c @@ -24,10 +24,10 @@ * Reads UTF-8 content from stream into UTF-32 buffer. */ wchar_t *fgetws(wchar_t *s, int size, FILE *f) { + wint_t c; wchar_t *p = s; if (size > 0) { while (--size > 0) { - wint_t c; if ((c = fgetwc(f)) == -1) { if (ferror(f) == EINTR) continue; break; diff --git a/libc/stdio/fmemopen.c b/libc/stdio/fmemopen.c index 56d2f5e7..6aac757f 100644 --- a/libc/stdio/fmemopen.c +++ b/libc/stdio/fmemopen.c @@ -32,32 +32,27 @@ FILE *fmemopen(void *buf, size_t size, const char *mode) { FILE *f; char *p; unsigned flags; - if (size && size > 0x7ffff000) { einval(); return NULL; } - if (!(f = calloc(1, sizeof(FILE)))) { return NULL; } - - if (!buf) { + if (buf) { + f->nofree = true; + } else { if (!size) size = BUFSIZ; if (!(buf = calloc(1, size))) { free(f); return NULL; } - } else { - f->nofree = true; } - f->fd = -1; f->buf = buf; - f->size = size; f->end = size; + f->size = size; f->iomode = fopenflags(mode); - if (f->iomode & O_APPEND) { if ((p = memchr(buf, '\0', size))) { f->beg = p - (char *)buf; @@ -65,6 +60,5 @@ FILE *fmemopen(void *buf, size_t size, const char *mode) { f->beg = f->end; } } - return f; } diff --git a/libc/stdio/fputc.c b/libc/stdio/fputc.c index 6fefbef8..635294fa 100644 --- a/libc/stdio/fputc.c +++ b/libc/stdio/fputc.c @@ -17,33 +17,22 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/errno.h" -#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" -#include "libc/sysv/consts/o.h" /** * Writes byte to stream. - * @return c (as unsigned char) if written or -1 w/ errno - * @see putc() if called within loop + * + * @param c is byte to buffer or write, which is masked + * @return c as unsigned char if written or -1 w/ errno */ -noinstrument int fputc(int c, FILE *f) { - if ((f->iomode & O_ACCMODE) != O_RDONLY) { - if (f->beg < f->size) { - f->buf[f->beg++] = c; - if (f->beg == f->size || f->bufmode == _IONBF || - (f->bufmode == _IOLBF && c == '\n')) { - if (f->writer) { - if (f->writer(f) == -1) return -1; - } else if (f->beg == f->size) { - f->beg = 0; - } - } - return c & 0xff; - } else { - return __fseteof(f); - } +int fputc(int c, FILE *f) { + unsigned char b; + if (c != '\n' && f->beg < f->size && f->bufmode != _IONBF) { + f->buf[f->beg++] = c; + return c & 0xff; } else { - return __fseterr(f, EBADF); + b = c; + if (!fwrite(&b, 1, 1, f)) return -1; + return b; } } diff --git a/libc/stdio/fputs.c b/libc/stdio/fputs.c index cfc89ab1..149b03c5 100644 --- a/libc/stdio/fputs.c +++ b/libc/stdio/fputs.c @@ -16,9 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" -#include "libc/macros.internal.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" @@ -31,17 +28,12 @@ * * @param s is a NUL-terminated string that's non-NULL * @param f is an open stream - * @return strlen(s) or -1 w/ errno on error + * @return bytes written, or -1 w/ errno */ int fputs(const char *s, FILE *f) { - int i, n, m; + size_t n, r; n = strlen(s); - for (i = 0; i < n; ++i) { - if (putc(s[i], f) == -1) { - if (ferror(f) == EINTR) continue; - if (feof(f)) errno = f->state = EPIPE; - return -1; - } - } - return n; + r = fwrite(s, 1, n, f); + if (!r && n) return -1; + return r; } diff --git a/libc/stdio/fputwc.c b/libc/stdio/fputwc.c index 10dbcce0..3bb23e72 100644 --- a/libc/stdio/fputwc.c +++ b/libc/stdio/fputwc.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/str/tpenc.h" @@ -30,14 +29,12 @@ wint_t fputwc(wchar_t wc, FILE *f) { if (wc != -1) { w = tpenc(wc); do { - if (fputc(w & 0xff, f) != -1) { - w >>= 8; - } else { + if (fputc(w, f) == -1) { return -1; } - } while (w); + } while ((w >>= 8)); return wc; } else { - return __fseteof(f); + return -1; } } diff --git a/libc/stdio/fread.c b/libc/stdio/fread.c index 35a3b36b..52fea4d9 100644 --- a/libc/stdio/fread.c +++ b/libc/stdio/fread.c @@ -16,12 +16,19 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/iovec.h" #include "libc/errno.h" #include "libc/fmt/conv.h" +#include "libc/macros.internal.h" #include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/str/internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/errfuns.h" /** * Reads data from stream. @@ -31,17 +38,62 @@ * @return count on success, [0,count) on eof, or 0 on error or count==0 */ size_t fread(void *buf, size_t stride, size_t count, FILE *f) { - int c; - size_t i, n; - unsigned char *p; - for (n = stride * count, p = buf, i = 0; i < n; ++i) { - if ((c = getc(f)) != -1) { - p[i] = c & 0xff; - } else if (!(i % stride)) { - return i / stride; - } else { - return __fseterr(f, EOVERFLOW); - } + char *p; + ssize_t rc; + size_t n, m; + struct iovec iov[2]; + if ((f->iomode & O_ACCMODE) == O_WRONLY) { + f->state = errno = EBADF; + return 0; + } + if (f->beg > f->end) { + f->state = errno = EINVAL; + return 0; + } + p = buf; + n = stride * count; + m = f->end - f->beg; + memcpy(p, f->buf + f->beg, MIN(n, m)); + if (n < m) { + f->beg += n; + return count; + } + if (n == m) { + f->beg = f->end = 0; + return count; + } + if (f->fd == -1) { + f->beg = 0; + f->end = 0; + f->state = -1; + return m / stride; + } + iov[0].iov_base = p + m; + iov[0].iov_len = n - m; + if (f->bufmode != _IONBF && n < f->size) { + iov[1].iov_base = f->buf; + if (f->size > PUSHBACK) { + iov[1].iov_len = f->size - PUSHBACK; + } else { + iov[1].iov_len = f->size; + } + } else { + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + } + if ((rc = readv(f->fd, iov, 2)) == -1) { + f->state = errno; + return 0; + } + n = rc; + f->beg = 0; + f->end = 0; + if (n > iov[0].iov_len) { + f->end += n - iov[0].iov_len; + return count; + } else { + n = (m + n) / stride; + if (n < count) f->state = -1; + return n; } - return count; } diff --git a/libc/stdio/fseek.c b/libc/stdio/fseek.c index 6ba6fe9b..f9c92c79 100644 --- a/libc/stdio/fseek.c +++ b/libc/stdio/fseek.c @@ -16,10 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" #include "libc/stdio/stdio.h" -#include "libc/sysv/consts/o.h" /** * Repositions open file stream. @@ -32,28 +29,8 @@ * @param f is a non-null stream handle * @param offset is the byte delta * @param whence can be SEET_SET, SEEK_CUR, or SEEK_END - * @returns new offset or -1 on error + * @returns 0 on success or -1 on error */ -long fseek(FILE *f, long offset, int whence) { - int64_t pos; - if (f->fd != -1) { - if (whence == SEEK_CUR && f->beg < f->end) { - offset -= f->end - f->beg; - } - if (f->beg && !f->end) { - f->writer(f); - } - if (lseek(f->fd, offset, whence) != -1) { - f->state = 0; - f->beg = 0; - f->end = 0; - return 0; - } else { - f->state = errno == ESPIPE ? EBADF : errno; - return -1; - } - } else { - f->beg = (offset & 0xffffffff) % f->size; - return -1; - } +int fseek(FILE *f, long offset, int whence) { + return fseeko(f, offset, whence); } diff --git a/libc/stdio/fseeko.c b/libc/stdio/fseeko.c new file mode 100644 index 00000000..67dd3308 --- /dev/null +++ b/libc/stdio/fseeko.c @@ -0,0 +1,83 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/o.h" + +/** + * Repositions open file stream. + * + * This function flushes the buffer (unless it's currently in the EOF + * state) and then calls lseek() on the underlying file. If the stream + * is in the EOF state, this function can be used to restore it without + * needing to reopen the file. + * + * @param f is a non-null stream handle + * @param offset is the byte delta + * @param whence can be SEET_SET, SEEK_CUR, or SEEK_END + * @returns 0 on success or -1 on error + */ +int fseeko(FILE *f, int64_t offset, int whence) { + ssize_t rc; + int64_t pos; + if (f->fd != -1) { + if (f->beg && !f->end && (f->iomode & O_ACCMODE) != O_RDONLY) { + if ((rc = write(f->fd, f->buf, f->beg)) == -1) { + f->state = errno; + return -1; + } + if (rc != f->beg) abort(); + f->beg = 0; + } + if (whence == SEEK_CUR && f->beg < f->end) { + offset -= f->end - f->beg; + } + if (lseek(f->fd, offset, whence) != -1) { + f->beg = 0; + f->end = 0; + return 0; + } else { + f->state = errno == ESPIPE ? EBADF : errno; + return -1; + } + } else { + switch (whence) { + case SEEK_SET: + pos = offset; + break; + case SEEK_CUR: + pos = f->beg + offset; + break; + case SEEK_END: + pos = f->end + offset; + break; + default: + pos = -1; + break; + } + if (0 <= pos && pos <= f->end) { + f->beg = pos; + return 0; + } else { + f->state = errno = EINVAL; + return -1; + } + } +} diff --git a/libc/stdio/ftell.c b/libc/stdio/ftell.c index 19207478..1f843eab 100644 --- a/libc/stdio/ftell.c +++ b/libc/stdio/ftell.c @@ -16,33 +16,14 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" #include "libc/stdio/stdio.h" /** * Returns current position of stream. * * @param stream is a non-null stream handle - * @returns current byte offset from beginning of file, or -1 + * @returns current byte offset from beginning, or -1 w/ errno */ long ftell(FILE *f) { - int64_t pos; - if (f->fd != -1) { - if (f->beg && !f->end) { - f->writer(f); - } - if ((pos = lseek(f->fd, 0, SEEK_CUR)) != -1) { - f->state = 0; - f->beg = 0; - f->end = 0; - return pos; - } else { - f->state = errno == ESPIPE ? EBADF : errno; - return -1; - } - } else { - errno = f->state; - return -1; - } + return ftello(f); } diff --git a/libc/stdio/ftello.S b/libc/stdio/ftello.S deleted file mode 100644 index d763fac1..00000000 --- a/libc/stdio/ftello.S +++ /dev/null @@ -1,23 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -ftello: jmp ftell - .endfn ftello,globl diff --git a/libc/stdio/ftello.c b/libc/stdio/ftello.c new file mode 100644 index 00000000..7c6148ac --- /dev/null +++ b/libc/stdio/ftello.c @@ -0,0 +1,54 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/o.h" + +/** + * Returns current position of stream. + * + * @param stream is a non-null stream handle + * @returns current byte offset from beginning, or -1 w/ errno + */ +int64_t ftello(FILE *f) { + ssize_t rc; + int64_t pos; + uint32_t skew; + if (f->fd != -1) { + if (f->beg && !f->end && (f->iomode & O_ACCMODE) != O_RDONLY) { + if ((rc = write(f->fd, f->buf, f->beg)) == -1) { + f->state = errno; + return -1; + } + if (rc != f->beg) abort(); + f->beg = 0; + } + if ((pos = lseek(f->fd, 0, SEEK_CUR)) != -1) { + if (f->beg < f->end) pos -= f->end - f->beg; + return pos; + } else { + f->state = errno == ESPIPE ? EBADF : errno; + return -1; + } + } else { + return f->beg; + } +} diff --git a/libc/stdio/fwrite.c b/libc/stdio/fwrite.c index 3dbc5da7..30f10003 100644 --- a/libc/stdio/fwrite.c +++ b/libc/stdio/fwrite.c @@ -16,10 +16,17 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/iovec.h" #include "libc/errno.h" +#include "libc/fmt/conv.h" +#include "libc/macros.internal.h" #include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" /** * Writes data to stream. @@ -29,10 +36,54 @@ * @return count on success, [0,count) on EOF, 0 on error or count==0 */ size_t fwrite(const void *data, size_t stride, size_t count, FILE *f) { - size_t i, n; - const unsigned char *p; - for (n = stride * count, p = data, i = 0; i < n; ++i) { - if (fputc(p[i], f) == -1) return -1; + ldiv_t d; + ssize_t rc; + size_t n, m; + const char *p; + struct iovec iov[2]; + if ((f->iomode & O_ACCMODE) == O_RDONLY) { + f->state = errno = EBADF; + return 0; } + n = stride * count; + m = f->size - f->beg; + if (n <= m && f->bufmode != _IONBF) { + memcpy(f->buf + f->beg, data, n); + f->beg += n; + if (f->fd != -1 && f->bufmode == _IOLBF && + (p = memrchr(f->buf, '\n', f->beg))) { + n = p + 1 - f->buf; + if ((rc = write(f->fd, f->buf, n)) == -1) { + if (errno == EINTR || errno == EAGAIN) return count; + f->state = errno; + return 0; + } + n = rc; + memmove(f->buf, f->buf + n, f->beg - n); + f->beg -= n; + } + return count; + } + if (f->fd == -1) { + n = MIN(n, m); + d = ldiv(n, stride); + n -= d.rem; + memcpy(f->buf + f->beg, data, n); + f->beg += n; + f->state = -1; + return d.quot; + } + iov[0].iov_base = f->buf; + iov[0].iov_len = f->beg; + iov[1].iov_base = data; + iov[1].iov_len = n; + n += f->beg; + if ((rc = writev(f->fd, iov, 2)) == -1) { + f->state = errno; + return 0; + } + m = rc; + if (n != m) abort(); + f->beg = 0; return count; } diff --git a/libc/stdio/getc.S b/libc/stdio/getc.S deleted file mode 100644 index a86ec2b7..00000000 --- a/libc/stdio/getc.S +++ /dev/null @@ -1,24 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -#include "libc/notice.inc" -.source __FILE__ - -getc: jmp fgetc - .endfn getc,globl diff --git a/libc/stdio/fseterr.c b/libc/stdio/getc.c similarity index 88% rename from libc/stdio/fseterr.c rename to libc/stdio/getc.c index 97aa1fc6..71082bce 100644 --- a/libc/stdio/fseterr.c +++ b/libc/stdio/getc.c @@ -16,13 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" -#include "libc/stdio/internal.h" +#include "libc/stdio/stdio.h" -long __fseterr(FILE *f, int err) { - if (!err) err = -1; - f->state = f->state <= 0 ? err : f->state; - if (err > 0) errno = err; - return -1; +/** + * Reads byte from stream. + * @return byte in range 0..255, or -1 w/ errno + */ +int(getc)(FILE *f) { + return fgetc(f); } diff --git a/libc/stdio/getchar.S b/libc/stdio/getchar.S deleted file mode 100644 index f65c831a..00000000 --- a/libc/stdio/getchar.S +++ /dev/null @@ -1,26 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -// Reads uint8_t from standard input. -// @return %al has result w/ rest of %rax cleared -getchar:mov stdin(%rip),%rdi - jmp fgetc - .endfn getchar,globl diff --git a/libc/stdio/getchar.c b/libc/stdio/getchar.c new file mode 100644 index 00000000..db56d6b0 --- /dev/null +++ b/libc/stdio/getchar.c @@ -0,0 +1,27 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/stdio/stdio.h" + +/** + * Reads byte from stream. + * @return byte in range 0..255, or -1 w/ errno + */ +int getchar(void) { + return fgetc(stdin); +} diff --git a/libc/stdio/getdelim.c b/libc/stdio/getdelim.c index 69de5843..dfdadf27 100644 --- a/libc/stdio/getdelim.c +++ b/libc/stdio/getdelim.c @@ -17,54 +17,63 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/calls.h" #include "libc/errno.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" -#include "libc/stdio/internal.h" +#include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" /** * Reads string from stream. * - * @param line is the caller's buffer (in/out) which is extended - * automatically. *line may be NULL but only if *n is 0; - * NUL-termination is guaranteed FTMP - * @param n is the capacity of line (in/out) + * @param s is the caller's buffer (in/out) which is extended or + * allocated automatically, also NUL-terminated is guaranteed + * @param n is the capacity of s (in/out) * @param delim is the stop char (and NUL is implicitly too) * @return number of bytes read, including delim, excluding NUL, or -1 * w/ errno on EOF or error; see ferror() and feof() * @note this function can't punt EINTR to caller * @see getline(), gettok_r() */ -ssize_t getdelim(char **line, size_t *n, int delim, FILE *f) { - assert((*line && *n) || (!*line && !*n)); - ssize_t rc = -1; - size_t i = 0; - char *p2; - size_t n2; - int c; - for (;;) { - if ((c = getc(f)) == -1) { - if (ferror(f) == EINTR) continue; - if (feof(f) && i) rc = i; - break; - } - if (i + 2 > *n) { - n2 = MAX(11, *n); - n2 += n2 >> 1; - if ((p2 = realloc(*line, n2))) { - *line = p2; - *n = n2; - } else { - __fseterrno(f); - break; - } - } - if (((*line)[i++] = c) == delim) { - rc = i; +ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { + char *p; + ssize_t rc; + size_t i, m; + if ((f->iomode & O_ACCMODE) == O_WRONLY) { + f->state = errno = EBADF; + return -1; + } + if (f->beg > f->end || f->bufmode == _IONBF) { + f->state = errno = EINVAL; + return -1; + } + if (!*s) *n = 0; + for (i = 0;; i += m) { + m = f->end - f->beg; + if ((p = memchr(f->buf + f->beg, delim, m))) m = p + 1 - (f->buf + f->beg); + if (i + m + 1 > *n && !(*s = realloc(*s, (*n = i + m + 1)))) abort(); + memcpy(*s + i, f->buf + f->beg, m); + (*s)[i + m] = '\0'; + if ((f->beg += m) == f->end) f->beg = f->end = 0; + if (p) { + return i + m; + } else if (f->fd == -1) { break; + } else if ((rc = read(f->fd, f->buf, f->size)) != -1) { + if (!rc) break; + f->end = rc; + } else if (errno != EINTR) { + f->state = errno; + return -1; } } - if (*line && i < *n) (*line)[i] = '\0'; - return rc; + f->state = -1; + if (i + m) { + return i + m; + } else { + return -1; + } } diff --git a/libc/stdio/getwc.S b/libc/stdio/getwc.S deleted file mode 100644 index 3f22a614..00000000 --- a/libc/stdio/getwc.S +++ /dev/null @@ -1,23 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -getwc: jmp fgetwc - .endfn getwc,globl diff --git a/libc/stdio/fseterrno.c b/libc/stdio/getwc.c similarity index 91% rename from libc/stdio/fseterrno.c rename to libc/stdio/getwc.c index 73162ddf..2ec4cc1b 100644 --- a/libc/stdio/fseterrno.c +++ b/libc/stdio/getwc.c @@ -16,9 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/errno.h" -#include "libc/stdio/internal.h" +#include "libc/stdio/stdio.h" -long __fseterrno(FILE *f) { - return __fseterr(f, errno); +/** + * Reads UTF-8 character from stream. + * @return wide character or -1 on EOF or error + */ +wint_t(getwc)(FILE *f) { + return fgetwc(f); } diff --git a/libc/stdio/getwchar.S b/libc/stdio/getwchar.S deleted file mode 100644 index a51d8f41..00000000 --- a/libc/stdio/getwchar.S +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -// Reads Thompson-Pike encoded varint from standard input. -// @return %eax has result w/ rest of %rax cleared -getwchar: - mov stdin(%rip),%rdi - jmp fgetwc - .endfn getwchar,globl diff --git a/libc/stdio/fseteof.c b/libc/stdio/getwchar.c similarity index 91% rename from libc/stdio/fseteof.c rename to libc/stdio/getwchar.c index 4e6cf82d..a455dbc1 100644 --- a/libc/stdio/fseteof.c +++ b/libc/stdio/getwchar.c @@ -16,8 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/internal.h" +#include "libc/stdio/stdio.h" -long __fseteof(FILE *f) { - return __fseterr(f, -1); +/** + * Reads UTF-8 character from stream. + * @return wide character or -1 on EOF or error + */ +wint_t getwchar(void) { + return fgetwc(stdin); } diff --git a/libc/stdio/internal.h b/libc/stdio/internal.h index d70e7e51..ba8de1ea 100644 --- a/libc/stdio/internal.h +++ b/libc/stdio/internal.h @@ -1,23 +1,19 @@ #ifndef COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ #define COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ #include "libc/stdio/stdio.h" + +#define PUSHBACK 12 + #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -extern unsigned char g_stdinbuf[BUFSIZ]; -extern unsigned char g_stdoutbuf[BUFSIZ]; -extern unsigned char g_stderrbuf[BUFSIZ]; +extern char g_stdinbuf[BUFSIZ]; +extern char g_stdoutbuf[BUFSIZ]; +extern char g_stderrbuf[BUFSIZ]; int __fflush_register(FILE *) hidden; void __fflush_unregister(FILE *) hidden; -int __freadbuf(FILE *) hidden; -int __fwritebuf(FILE *) hidden; -long __fseteof(FILE *) hidden; -long __fseterrno(FILE *) hidden; -long __fseterr(FILE *, int) hidden; -void __fclosepid(FILE *) hidden; - COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_STDIO_INTERNAL_H_ */ diff --git a/libc/stdio/putc.S b/libc/stdio/putc.S deleted file mode 100644 index 27fd1cd3..00000000 --- a/libc/stdio/putc.S +++ /dev/null @@ -1,23 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -putc: jmp fputc - .endfn putc,globl diff --git a/libc/stdio/freadbuf.c b/libc/stdio/putc.c similarity index 86% rename from libc/stdio/freadbuf.c rename to libc/stdio/putc.c index 2df78aaf..559519a3 100644 --- a/libc/stdio/freadbuf.c +++ b/libc/stdio/putc.c @@ -16,17 +16,14 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/runtime/runtime.h" -#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" -int __freadbuf(FILE *f) { - ssize_t got; - got = read(f->fd, f->buf, f->size); - if (got == -1) return __fseterrno(f); - if (got == 0) return __fseteof(f); - f->beg = 0; - f->end = got; - return got; +/** + * Writes byte to stream. + * + * @param c is byte to buffer or write, which is masked + * @return c as unsigned char if written or -1 w/ errno + */ +int(putc)(int c, FILE *f) { + return fputc(c, f); } diff --git a/libc/stdio/puts.c b/libc/stdio/puts.c index 1270988a..044420e2 100644 --- a/libc/stdio/puts.c +++ b/libc/stdio/puts.c @@ -22,9 +22,17 @@ * Writes string w/ trailing newline to stdout. */ int puts(const char *s) { - FILE *f = stdout; - int rc, res; - if ((res = rc = fputs(s, f)) == -1) return -1; - if ((rc = fputc('\n', f)) == -1) return -1; - return res + 1; + FILE *f; + size_t n, r; + f = stdout; + if ((n = strlen(s))) { + r = fwrite(s, 1, n, f); + if (!r) return -1; + if (r < n) return r; + } + if (fputc('\n', f) == -1) { + if (feof(f)) return n; + return -1; + } + return n + 1; } diff --git a/libc/stdio/putwc.S b/libc/stdio/putwc.S deleted file mode 100644 index de67ea0e..00000000 --- a/libc/stdio/putwc.S +++ /dev/null @@ -1,27 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ - -// Writes wide character to stream. -// @param %edi is the wide character -// @param %rsi is the FILE stream pointer -// @return %eax is set to %edi param or -1 on error -putwc: jmp fputwc - .endfn putwc,globl diff --git a/libc/stdio/fseeko.S b/libc/stdio/putwc.c similarity index 83% rename from libc/stdio/fseeko.S rename to libc/stdio/putwc.c index e9d0373e..fad85cad 100644 --- a/libc/stdio/fseeko.S +++ b/libc/stdio/putwc.c @@ -1,5 +1,5 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ @@ -16,8 +16,13 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.source __FILE__ +#include "libc/stdio/stdio.h" -fseeko: jmp fseek - .endfn fseeko,globl +/** + * Writes wide character to stream. + * + * @return wc if written or -1 w/ errno + */ +wint_t(putwc)(wchar_t wc, FILE *f) { + return fputwc(wc, f); +} diff --git a/libc/stdio/putwchar.c b/libc/stdio/putwchar.c index 302feca6..8fe5cf01 100644 --- a/libc/stdio/putwchar.c +++ b/libc/stdio/putwchar.c @@ -20,7 +20,8 @@ /** * Writes wide character to stdout. - * * @return wc if written or -1 w/ errno */ -wint_t putwchar(wchar_t wc) { return fputwc(wc, stdout); } +wint_t putwchar(wchar_t wc) { + return fputwc(wc, stdout); +} diff --git a/libc/stdio/rewind.c b/libc/stdio/rewind.c index 1dfc9da6..03aa3f5c 100644 --- a/libc/stdio/rewind.c +++ b/libc/stdio/rewind.c @@ -25,6 +25,7 @@ * Like fseek(), this function can be used to restore a stream from the * EOF state, without reopening it. */ -void rewind(FILE *stream) { - fseek(stream, 0, SEEK_SET); +void rewind(FILE *f) { + fseek(f, 0, SEEK_SET); + f->state = 0; } diff --git a/libc/stdio/setbuf.c b/libc/stdio/setbuf.c index 8d8f392a..4396f15a 100644 --- a/libc/stdio/setbuf.c +++ b/libc/stdio/setbuf.c @@ -16,10 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/stdio/stdio.h" -#include "libc/sysv/errfuns.h" /** * Sets buffer on stdio stream. */ -void setbuf(FILE *f, char *buf) { setbuffer(f, buf, BUFSIZ); } +void setbuf(FILE *f, char *buf) { + setvbuf(f, buf, buf ? _IOFBF : _IONBF, BUFSIZ); +} diff --git a/libc/stdio/setbuffer.c b/libc/stdio/setbuffer.c index cd404ef2..94220dc4 100644 --- a/libc/stdio/setbuffer.c +++ b/libc/stdio/setbuffer.c @@ -16,20 +16,18 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/sysv/errfuns.h" /** * Sets buffer on stdio stream. + * + * @param buf may optionally be non-NULL to set the stream's underlying + * buffer, which the stream will own, but won't free + * @param size is ignored if buf is NULL */ void setbuffer(FILE *f, char *buf, size_t size) { - if (buf && f->buf != (unsigned char *)buf) { - free_s(&f->buf); - if (!size) size = BUFSIZ; - f->buf = (unsigned char *)buf; - } - if (size) { - f->size = size; - } + setvbuf(f, buf, buf ? _IOFBF : _IONBF, size); } diff --git a/libc/stdio/setlinebuf.c b/libc/stdio/setlinebuf.c new file mode 100644 index 00000000..fa6bc643 --- /dev/null +++ b/libc/stdio/setlinebuf.c @@ -0,0 +1,27 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/stdio/stdio.h" + +/** + * Puts stream in line-buffering mode. + */ +void setlinebuf(FILE *f) { + setvbuf(f, NULL, _IOLBF, 0); +} diff --git a/libc/stdio/setvbuf.c b/libc/stdio/setvbuf.c index 8146a0ba..4149bdd3 100644 --- a/libc/stdio/setvbuf.c +++ b/libc/stdio/setvbuf.c @@ -24,12 +24,19 @@ * * @param mode may be _IOFBF, _IOLBF, or _IONBF * @param buf may optionally be non-NULL to set the stream's underlying - * buffer, which the stream will own, but won't free - * @param size must be a two power if buf is provided + * buffer, which the stream will own, but won't free, otherwise the + * existing buffer is used + * @param size is ignored if buf is NULL * @return 0 on success or -1 on error */ int setvbuf(FILE *f, char *buf, int mode, size_t size) { - setbuffer(f, buf, size); + if (buf) { + if (!size) size = BUFSIZ; + if (!f->nofree && f->buf != buf) free_s(&f->buf); + f->buf = buf; + f->size = size; + f->nofree = true; + } f->bufmode = mode; return 0; } diff --git a/libc/stdio/stdbuf.c b/libc/stdio/stdbuf.c index c1e4eab5..67d68cf0 100644 --- a/libc/stdio/stdbuf.c +++ b/libc/stdio/stdbuf.c @@ -18,5 +18,5 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/internal.h" -unsigned char g_stdoutbuf[BUFSIZ] hidden; -unsigned char g_stderrbuf[BUFSIZ] hidden; +char g_stdoutbuf[BUFSIZ] hidden; +char g_stderrbuf[BUFSIZ] hidden; diff --git a/libc/stdio/stderr-init.S b/libc/stdio/stderr-init.S index 373a98ab..30acd051 100644 --- a/libc/stdio/stderr-init.S +++ b/libc/stdio/stderr-init.S @@ -34,8 +34,5 @@ ezlea __stderr_buf,cx mov %rcx,24(%rax) #→ f.buf movl $BUFSIZ,32(%rax) #→ f.size - ezlea __fwritebuf,cx - mov %rcx,%rdx - mov %rdx,48(%rax) #→ f.writer mov %rax,stderr(%rip) .init.end 400,_init_stderr,globl,hidden diff --git a/libc/stdio/stdin-init.S b/libc/stdio/stdin-init.S index 8f05aa1f..46095379 100644 --- a/libc/stdio/stdin-init.S +++ b/libc/stdio/stdin-init.S @@ -30,8 +30,5 @@ ezlea __stdin_buf,cx mov %rcx,24(%rax) #→ f.buf movl $BUFSIZ,32(%rax) #→ f.size - ezlea __freadbuf,cx - mov %rcx,%rdx - mov %rdx,40(%rax) #→ f.reader mov %rax,stdin(%rip) .init.end 400,_init_stdin,globl,hidden diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h index c9b61255..268913be 100644 --- a/libc/stdio/stdio.h +++ b/libc/stdio/stdio.h @@ -13,19 +13,17 @@ COSMOPOLITAN_C_START_ ╚────────────────────────────────────────────────────────────────────────────│*/ typedef struct FILE { - uint8_t bufmode; /* 0x00 _IOFBF, etc. (ignored if fd=-1) */ - bool noclose; /* 0x01 for fake dup() */ - uint32_t iomode; /* 0x04 O_RDONLY, etc. (ignored if fd=-1) */ - int32_t state; /* 0x08 0=OK, -1=EOF, >0=errno */ - int fd; /* 0x0c ≥0=fd, -1=closed|buffer */ - uint32_t beg; /* 0x10 */ - uint32_t end; /* 0x14 */ - uint8_t *buf; /* 0x18 */ - uint32_t size; /* 0x20 */ - uint32_t nofree; /* 0x24 */ - int (*reader)(struct FILE *); /* 0x28 */ - int (*writer)(struct FILE *); /* 0x30 */ - int pid; /* 0x34 */ + uint8_t bufmode; /* 0x00 _IOFBF, etc. (ignored if fd=-1) */ + bool noclose; /* 0x01 for fake dup() */ + uint32_t iomode; /* 0x04 O_RDONLY, etc. (ignored if fd=-1) */ + int32_t state; /* 0x08 0=OK, -1=EOF, >0=errno */ + int fd; /* 0x0c ≥0=fd, -1=closed|buffer */ + uint32_t beg; /* 0x10 */ + uint32_t end; /* 0x14 */ + char *buf; /* 0x18 */ + uint32_t size; /* 0x20 */ + uint32_t nofree; /* 0x24 */ + int pid; /* 0x28 */ } FILE; extern FILE *stdin; @@ -46,10 +44,13 @@ int fputs(const char *, FILE *) paramsnonnull(); int fputws(const wchar_t *, FILE *) paramsnonnull(); char *fgets(char *, int, FILE *) paramsnonnull(); wchar_t *fgetws(wchar_t *, int, FILE *) paramsnonnull(); +wint_t putwc(wchar_t, FILE *) paramsnonnull(); wint_t fputwc(wchar_t, FILE *) paramsnonnull(); wint_t putwchar(wchar_t); wint_t getwchar(void); +wint_t getwc(FILE *) paramsnonnull(); wint_t fgetwc(FILE *) paramsnonnull(); +wint_t ungetwc(wint_t, FILE *) paramsnonnull(); int getchar(void); int putchar(int); int puts(const char *) paramsnonnull(); @@ -59,12 +60,14 @@ FILE *fopen(const char *, const char *) paramsnonnull() nodiscard; FILE *fdopen(int, const char *) paramsnonnull() nodiscard; FILE *fmemopen(void *, size_t, const char *) paramsnonnull((3)) nodiscard; FILE *freopen(const char *, const char *, FILE *) paramsnonnull((2, 3)); -size_t fread(void *, size_t, size_t, FILE *) paramsnonnull(); -size_t fwrite(const void *, size_t, size_t, FILE *) paramsnonnull(); +size_t fread(void *, size_t, size_t, FILE *) paramsnonnull((4)); +size_t fwrite(const void *, size_t, size_t, FILE *) paramsnonnull((4)); int fclose(FILE *); int fclose_s(FILE **) paramsnonnull(); -long fseek(FILE *, long, int) paramsnonnull(); +int fseek(FILE *, long, int) paramsnonnull(); long ftell(FILE *) paramsnonnull(); +int fseeko(FILE *, int64_t, int) paramsnonnull(); +int64_t ftello(FILE *) paramsnonnull(); void rewind(FILE *) paramsnonnull(); int fopenflags(const char *) paramsnonnull(); void setbuf(FILE *, char *); @@ -77,8 +80,6 @@ typedef uint64_t fpos_t; compatfn char *gets(char *) paramsnonnull(); compatfn int fgetpos(FILE *, fpos_t *) paramsnonnull(); compatfn int fsetpos(FILE *, const fpos_t *) paramsnonnull(); -compatfn int64_t fseeko(FILE *, long, int) paramsnonnull(); -compatfn int64_t ftello(FILE *) paramsnonnull(); int system(const char *); int systemexec(const char *); @@ -102,8 +103,10 @@ int vfscanf(FILE *, const char *, va_list); │ cosmopolitan § standard i/o » optimizations ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -#define getc(f) fgetc(f) -#define putc(c, f) fputc(c, f) +#define getc(f) fgetc(f) +#define getwc(f) fgetwc(f) +#define putc(c, f) fputc(c, f) +#define putwc(c, f) fputwc(c, f) #if defined(__GNUC__) && !defined(__STRICT_ANSI__) #define printf(FMT, ...) (printf)(PFLINK(FMT), ##__VA_ARGS__) diff --git a/libc/stdio/stdout-init.S b/libc/stdio/stdout-init.S index 39b1f5d1..da9efdbc 100644 --- a/libc/stdio/stdout-init.S +++ b/libc/stdio/stdout-init.S @@ -32,8 +32,5 @@ ezlea __stdout_buf,cx mov %rcx,24(%rax) #→ f.buf movl $BUFSIZ,32(%rax) #→ f.size - ezlea __fwritebuf,cx - mov %rcx,%rdx - mov %rdx,48(%rax) #→ f.writer mov %rax,stdout(%rip) .init.end 400,_init_stdout,globl,hidden diff --git a/libc/stdio/ungetc.c b/libc/stdio/ungetc.c index 2ef432cf..91a28e22 100644 --- a/libc/stdio/ungetc.c +++ b/libc/stdio/ungetc.c @@ -17,13 +17,20 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/stdio.h" +#include "libc/str/str.h" /** - * Pushes 𝑐 back to stream. + * Pushes byte back to stream. */ int ungetc(int c, FILE *f) { - uint32_t i; - if (c == -1) return c; - if (f->beg) f->buf[--f->beg] = c; - return c; + if (c == -1) return -1; + if (f->beg) { + f->buf[--f->beg] = c; + } else if (f->end < f->size) { + memmove(f->buf + 1, f->buf, f->end++); + f->buf[0] = c; + } else { + return -1; + } + return c & 0xff; } diff --git a/libc/stdio/fwritebuf.c b/libc/stdio/ungetwc.c similarity index 77% rename from libc/stdio/fwritebuf.c rename to libc/stdio/ungetwc.c index 5432a285..be1fce4f 100644 --- a/libc/stdio/fwritebuf.c +++ b/libc/stdio/ungetwc.c @@ -16,22 +16,32 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/errno.h" -#include "libc/stdio/internal.h" +#include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/str/tpenc.h" -int __fwritebuf(FILE *f) { - ssize_t wrote; - if ((wrote = write(f->fd, f->buf, f->beg)) == -1) { - if (errno == EINTR) return 0; - return __fseterrno(f); - } - if (wrote == f->beg) { - f->beg = 0; +/** + * Pushes wide character back to stream. + */ +wint_t ungetwc(wint_t c, FILE *f) { + char b[6]; + unsigned n; + uint64_t w; + if (c == -1) return -1; + n = 0; + w = tpenc(c); + do { + b[n++] = w; + } while ((w >>= 8)); + if (f->beg >= n) { + f->beg -= n; + memcpy(f->buf + f->beg, b, n); + } else if (f->beg + f->end + n <= f->size) { + memmove(f->buf + f->beg + n, f->buf + f->beg, f->end - f->beg); + memcpy(f->buf + f->beg, b, n); + f->end += n; } else { - memcpy(f->buf, f->buf + wrote, f->beg - wrote); - f->beg -= wrote; + return -1; } - return wrote; + return c; } diff --git a/libc/str/tprecode8to16.c b/libc/str/tprecode8to16.c index dc8eafb3..7d995665 100644 --- a/libc/str/tprecode8to16.c +++ b/libc/str/tprecode8to16.c @@ -60,9 +60,10 @@ axdx_t tprecode8to16(char16_t *dst, size_t dstsize, const char *src) { r.ax = 0; r.dx = 0; for (;;) { - if (!IsTiny() && !((uintptr_t)(src + r.dx) & 15)) { - tprecode8to16_sse2(dst, dstsize, src, r); - } + /* TODO(jart): Why is it now so much slower refactored? */ + /* if (!IsTiny() && !((uintptr_t)(src + r.dx) & 15)) { */ + /* tprecode8to16_sse2(dst, dstsize, src, r); */ + /* } */ x = src[r.dx++] & 0xff; if (ThomPikeCont(x)) continue; if (!isascii(x)) { diff --git a/net/http/escapejsstringliteral.c b/net/http/escapejsstringliteral.c index ad264806..5e3e7cda 100644 --- a/net/http/escapejsstringliteral.c +++ b/net/http/escapejsstringliteral.c @@ -27,44 +27,130 @@ * HTML entities and forward slash are escaped too for added safety. * * We assume the UTF-8 is well-formed and can be represented as UTF-16. - * Things that can't be decoded or encoded will be replaced with invalid - * code-point markers. This function is agnostic to numbers that have - * been used with malicious intent in the past under buggy software. - * Noncanonical encodings such as overlong NUL are canonicalized as NUL. + * Things that can't be decoded will fall back to binary. Things that + * can't be encoded will use invalid codepoint markers. This function is + * agnostic to numbers that have been used with malicious intent in the + * past under buggy software. Noncanonical encodings such as overlong + * NUL are canonicalized as NUL. */ struct EscapeResult EscapeJsStringLiteral(const char *data, size_t size) { char *p; - size_t i; - unsigned n; uint64_t w; - wint_t x, y; + unsigned i, n; + wint_t x, a, b; + const char *d, *e; struct EscapeResult r; + d = data; + e = data + size; p = r.data = xmalloc(size * 6 + 6 + 1); - for (i = 0; i < size;) { - x = data[i++] & 0xff; - if (x >= 0200) { - if (x >= 0300) { - n = ThomPikeLen(x); - x = ThomPikeByte(x); - while (--n) { - if (i < size) { - y = data[i++] & 0xff; - if (ThomPikeCont(y)) { - x = ThomPikeMerge(x, y); - } else { - x = 0xFFFD; - break; - } - } else { - x = 0xFFFD; + while (d < e) { + x = *d++ & 0xff; + if (x >= 0300) { + a = ThomPikeByte(x); + n = ThomPikeLen(x) - 1; + if (d + n <= e) { + for (i = 0;;) { + b = d[i] & 0xff; + if (!ThomPikeCont(b)) break; + a = ThomPikeMerge(a, b); + if (++i == n) { + x = a; + d += i; break; } } - } else { - x = 0xFFFD; } } switch (x) { + case ' ': + case '!': + case '#': + case '$': + case '%': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case ':': + case ';': + case '?': + case '@': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '[': + case ']': + case '^': + case '_': + case '`': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case '{': + case '|': + case '}': + case '~': + *p++ = x; + break; case '\t': p[0] = '\\'; p[1] = 't'; @@ -105,24 +191,19 @@ struct EscapeResult EscapeJsStringLiteral(const char *data, size_t size) { p[1] = '\''; p += 2; break; - default: - if (0x20 <= x && x < 0x7F) { - *p++ = x; - break; - } - /* fallthrough */ case '<': case '>': case '&': case '=': + default: w = EncodeUtf16(x); do { p[0] = '\\'; p[1] = 'u'; - p[2] = "0123456789ABCDEF"[(w & 0xF000) >> 014]; - p[3] = "0123456789ABCDEF"[(w & 0x0F00) >> 010]; - p[4] = "0123456789ABCDEF"[(w & 0x00F0) >> 004]; - p[5] = "0123456789ABCDEF"[(w & 0x000F) >> 000]; + p[2] = "0123456789abcdef"[(w & 0xF000) >> 014]; + p[3] = "0123456789abcdef"[(w & 0x0F00) >> 010]; + p[4] = "0123456789abcdef"[(w & 0x00F0) >> 004]; + p[5] = "0123456789abcdef"[(w & 0x000F) >> 000]; p += 6; } while ((w >>= 16)); break; diff --git a/net/http/gethttpheader.gperf b/net/http/gethttpheader.gperf index 6e554ab0..9993daec 100644 --- a/net/http/gethttpheader.gperf +++ b/net/http/gethttpheader.gperf @@ -31,7 +31,7 @@ Content-Md5, kHttpContentMd5 Content-Range, kHttpContentRange Content-Type, kHttpContentType Date, kHttpDate -Etag, kHttpEtag +ETag, kHttpEtag Expires, kHttpExpires From, kHttpFrom Host, kHttpHost @@ -58,5 +58,10 @@ Retry-After, kHttpRetryAfter Server, kHttpServer Vary, kHttpVary Warning, kHttpWarning -Www-Authenticate, kHttpWwwAuthenticate +WWW-Authenticate, kHttpWwwAuthenticate Last-Modified, kHttpLastModified +Cookie, kHttpCookie +Trailer, kHttpTrailer +TE, kHttpTe +DNT, kHttpDnt +Expect, kHttpExpect diff --git a/net/http/gethttpheader.inc b/net/http/gethttpheader.inc index 673dd103..c8f95e2a 100644 --- a/net/http/gethttpheader.inc +++ b/net/http/gethttpheader.inc @@ -1,6 +1,6 @@ /* ANSI-C code produced by gperf version 3.1 */ /* Command-line: gperf gethttpheader.gperf */ -/* Computed positions: -k'1,9,$' */ +/* Computed positions: -k'3-4,10' */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ @@ -37,12 +37,12 @@ #line 12 "gethttpheader.gperf" struct HttpHeaderSlot { char *name; char code; }; -#define TOTAL_KEYWORDS 49 -#define MIN_WORD_LENGTH 3 +#define TOTAL_KEYWORDS 54 +#define MIN_WORD_LENGTH 2 #define MAX_WORD_LENGTH 19 -#define MIN_HASH_VALUE 5 -#define MAX_HASH_VALUE 72 -/* maximum key range = 68, duplicates = 0 */ +#define MIN_HASH_VALUE 2 +#define MAX_HASH_VALUE 89 +/* maximum key range = 88, duplicates = 0 */ #ifndef GPERF_DOWNCASE #define GPERF_DOWNCASE 1 @@ -101,52 +101,55 @@ hash (register const char *str, register size_t len) { static const unsigned char asso_values[] = { - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 0, 73, 73, 73, 73, - 73, 73, 73, 0, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 5, 20, 0, 15, 0, - 0, 45, 20, 5, 73, 10, 35, 5, 5, 5, - 20, 73, 5, 30, 0, 0, 15, 20, 73, 15, - 73, 73, 73, 73, 73, 73, 73, 5, 20, 0, - 15, 0, 0, 45, 20, 5, 73, 10, 35, 5, - 5, 5, 20, 73, 5, 30, 0, 0, 15, 20, - 73, 15, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73 + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 30, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 5, 5, 30, 50, 0, + 35, 30, 0, 5, 90, 40, 0, 30, 0, 20, + 45, 90, 0, 5, 10, 5, 0, 15, 20, 25, + 90, 90, 90, 90, 90, 90, 90, 5, 5, 30, + 50, 0, 35, 30, 0, 5, 90, 40, 0, 30, + 0, 20, 45, 90, 0, 5, 10, 5, 0, 15, + 20, 25, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90 }; register unsigned int hval = len; switch (hval) { default: - hval += asso_values[(unsigned char)str[8]]; + hval += asso_values[(unsigned char)str[9]]; /*FALLTHROUGH*/ + case 9: case 8: case 7: case 6: case 5: case 4: + hval += asso_values[(unsigned char)str[3]]; + /*FALLTHROUGH*/ case 3: + hval += asso_values[(unsigned char)str[2]]; + /*FALLTHROUGH*/ case 2: - case 1: - hval += asso_values[(unsigned char)str[0]]; break; } - return hval + asso_values[(unsigned char)str[len - 1]]; + return hval; } const struct HttpHeaderSlot * @@ -154,116 +157,134 @@ LookupHttpHeader (register const char *str, register size_t len) { static const struct HttpHeaderSlot wordlist[] = { - {""}, {""}, {""}, {""}, {""}, -#line 23 "gethttpheader.gperf" - {"Close", kHttpClose}, - {""}, -#line 52 "gethttpheader.gperf" - {"Upgrade", kHttpUpgrade}, + {""}, {""}, +#line 65 "gethttpheader.gperf" + {"TE", kHttpTe}, #line 18 "gethttpheader.gperf" {"Age", kHttpAge}, -#line 36 "gethttpheader.gperf" - {"From", kHttpFrom}, + {""}, {""}, +#line 58 "gethttpheader.gperf" + {"Server", kHttpServer}, +#line 60 "gethttpheader.gperf" + {"Warning", kHttpWarning}, +#line 54 "gethttpheader.gperf" + {"Via", kHttpVia}, + {""}, +#line 24 "gethttpheader.gperf" + {"Connection", kHttpConnection}, +#line 56 "gethttpheader.gperf" + {"Public", kHttpPublic}, +#line 22 "gethttpheader.gperf" + {"Chunked", kHttpChunked}, +#line 66 "gethttpheader.gperf" + {"DNT", kHttpDnt}, +#line 33 "gethttpheader.gperf" + {"Date", kHttpDate}, + {""}, {""}, +#line 64 "gethttpheader.gperf" + {"Trailer", kHttpTrailer}, + {""}, +#line 37 "gethttpheader.gperf" + {"Host", kHttpHost}, +#line 53 "gethttpheader.gperf" + {"User-Agent", kHttpUserAgent}, +#line 57 "gethttpheader.gperf" + {"Retry-After", kHttpRetryAfter}, +#line 51 "gethttpheader.gperf" + {"Transfer-Encoding", kHttpTransferEncoding}, + {""}, +#line 28 "gethttpheader.gperf" + {"Content-Length", kHttpContentLength}, +#line 19 "gethttpheader.gperf" + {"Allow", kHttpAllow}, +#line 26 "gethttpheader.gperf" + {"Content-Encoding", kHttpContentEncoding}, +#line 25 "gethttpheader.gperf" + {"Content-Base", kHttpContentBase}, +#line 31 "gethttpheader.gperf" + {"Content-Range", kHttpContentRange}, +#line 59 "gethttpheader.gperf" + {"Vary", kHttpVary}, +#line 23 "gethttpheader.gperf" + {"Close", kHttpClose}, +#line 27 "gethttpheader.gperf" + {"Content-Language", kHttpContentLanguage}, + {""}, +#line 20 "gethttpheader.gperf" + {"Authorization", kHttpAuthorization}, + {""}, #line 49 "gethttpheader.gperf" {"Range", kHttpRange}, #line 14 "gethttpheader.gperf" {"Accept", kHttpAccept}, -#line 32 "gethttpheader.gperf" - {"Content-Type", kHttpContentType}, +#line 52 "gethttpheader.gperf" + {"Upgrade", kHttpUpgrade}, #line 41 "gethttpheader.gperf" {"If-Range", kHttpIfRange}, +#line 34 "gethttpheader.gperf" + {"ETag", kHttpEtag}, {""}, -#line 53 "gethttpheader.gperf" - {"User-Agent", kHttpUserAgent}, -#line 30 "gethttpheader.gperf" - {"Content-Md5", kHttpContentMd5}, -#line 50 "gethttpheader.gperf" - {"Referer", kHttpReferer}, -#line 31 "gethttpheader.gperf" - {"Content-Range", kHttpContentRange}, -#line 33 "gethttpheader.gperf" - {"Date", kHttpDate}, -#line 24 "gethttpheader.gperf" - {"Connection", kHttpConnection}, -#line 57 "gethttpheader.gperf" - {"Retry-After", kHttpRetryAfter}, -#line 22 "gethttpheader.gperf" - {"Chunked", kHttpChunked}, -#line 54 "gethttpheader.gperf" - {"Via", kHttpVia}, -#line 37 "gethttpheader.gperf" - {"Host", kHttpHost}, -#line 17 "gethttpheader.gperf" - {"Accept-Language", kHttpAcceptLanguage}, -#line 56 "gethttpheader.gperf" - {"Public", kHttpPublic}, -#line 39 "gethttpheader.gperf" - {"If-Modified-Since", kHttpIfModifiedSince}, -#line 20 "gethttpheader.gperf" - {"Authorization", kHttpAuthorization}, -#line 42 "gethttpheader.gperf" - {"If-Unmodified-Since", kHttpIfUnmodifiedSince}, -#line 19 "gethttpheader.gperf" - {"Allow", kHttpAllow}, #line 45 "gethttpheader.gperf" {"Pragma", kHttpPragma}, -#line 25 "gethttpheader.gperf" - {"Content-Base", kHttpContentBase}, -#line 38 "gethttpheader.gperf" - {"If-Match", kHttpIfMatch}, -#line 59 "gethttpheader.gperf" - {"Vary", kHttpVary}, +#line 50 "gethttpheader.gperf" + {"Referer", kHttpReferer}, +#line 55 "gethttpheader.gperf" + {"Location", kHttpLocation}, + {""}, +#line 17 "gethttpheader.gperf" + {"Accept-Language", kHttpAcceptLanguage}, +#line 29 "gethttpheader.gperf" + {"Content-Location", kHttpContentLocation}, +#line 32 "gethttpheader.gperf" + {"Content-Type", kHttpContentType}, +#line 40 "gethttpheader.gperf" + {"If-None-Match", kHttpIfNoneMatch}, +#line 15 "gethttpheader.gperf" + {"Accept-Charset", kHttpAcceptCharset}, + {""}, +#line 67 "gethttpheader.gperf" + {"Expect", kHttpExpect}, + {""}, +#line 21 "gethttpheader.gperf" + {"Cache-Control", kHttpCacheControl}, +#line 36 "gethttpheader.gperf" + {"From", kHttpFrom}, #line 43 "gethttpheader.gperf" {"Keep-Alive", kHttpKeepAlive}, -#line 61 "gethttpheader.gperf" - {"Www-Authenticate", kHttpWwwAuthenticate}, +#line 48 "gethttpheader.gperf" + {"Proxy-Connection", kHttpProxyConnection}, #line 35 "gethttpheader.gperf" {"Expires", kHttpExpires}, #line 46 "gethttpheader.gperf" {"Proxy-Authenticate", kHttpProxyAuthenticate}, -#line 15 "gethttpheader.gperf" - {"Accept-Charset", kHttpAcceptCharset}, - {""}, -#line 58 "gethttpheader.gperf" - {"Server", kHttpServer}, - {""}, -#line 40 "gethttpheader.gperf" - {"If-None-Match", kHttpIfNoneMatch}, #line 47 "gethttpheader.gperf" {"Proxy-Authorization", kHttpProxyAuthorization}, {""}, -#line 48 "gethttpheader.gperf" - {"Proxy-Connection", kHttpProxyConnection}, - {""}, -#line 55 "gethttpheader.gperf" - {"Location", kHttpLocation}, -#line 34 "gethttpheader.gperf" - {"Etag", kHttpEtag}, - {""}, -#line 27 "gethttpheader.gperf" - {"Content-Language", kHttpContentLanguage}, +#line 61 "gethttpheader.gperf" + {"WWW-Authenticate", kHttpWwwAuthenticate}, #line 44 "gethttpheader.gperf" {"Max-Forwards", kHttpMaxForwards}, -#line 21 "gethttpheader.gperf" - {"Cache-Control", kHttpCacheControl}, - {""}, {""}, -#line 29 "gethttpheader.gperf" - {"Content-Location", kHttpContentLocation}, - {""}, {""}, {""}, {""}, -#line 26 "gethttpheader.gperf" - {"Content-Encoding", kHttpContentEncoding}, -#line 51 "gethttpheader.gperf" - {"Transfer-Encoding", kHttpTransferEncoding}, - {""}, {""}, {""}, {""}, {""}, #line 62 "gethttpheader.gperf" {"Last-Modified", kHttpLastModified}, -#line 28 "gethttpheader.gperf" - {"Content-Length", kHttpContentLength}, + {""}, {""}, +#line 63 "gethttpheader.gperf" + {"Cookie", kHttpCookie}, + {""}, +#line 38 "gethttpheader.gperf" + {"If-Match", kHttpIfMatch}, + {""}, {""}, +#line 30 "gethttpheader.gperf" + {"Content-Md5", kHttpContentMd5}, + {""}, {""}, {""}, #line 16 "gethttpheader.gperf" {"Accept-Encoding", kHttpAcceptEncoding}, {""}, -#line 60 "gethttpheader.gperf" - {"Warning", kHttpWarning} +#line 39 "gethttpheader.gperf" + {"If-Modified-Since", kHttpIfModifiedSince}, + {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, + {""}, {""}, +#line 42 "gethttpheader.gperf" + {"If-Unmodified-Since", kHttpIfUnmodifiedSince} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) diff --git a/net/http/gethttpreason.c b/net/http/gethttpreason.c index 550a488c..ce10aaf5 100644 --- a/net/http/gethttpreason.c +++ b/net/http/gethttpreason.c @@ -91,6 +91,9 @@ static const struct HttpReason { /** * Returns string describing HTTP reason phrase. + * + * @see https://tools.ietf.org/html/rfc2616 + * @see https://tools.ietf.org/html/rfc6585 */ const char *GetHttpReason(int code) { int m, l, r; diff --git a/net/http/http.h b/net/http/http.h index e48421aa..21a67555 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -70,7 +70,12 @@ #define kHttpWarning 46 #define kHttpWwwAuthenticate 47 #define kHttpLastModified 48 -#define kHttpHeadersMax 49 +#define kHttpCookie 49 +#define kHttpTrailer 50 +#define kHttpTe 51 +#define kHttpDnt 52 +#define kHttpExpect 53 +#define kHttpHeadersMax 54 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/test/libc/dns/parsehoststxt_test.c b/test/libc/dns/parsehoststxt_test.c index 1cb52b7e..a0649c95 100644 --- a/test/libc/dns/parsehoststxt_test.c +++ b/test/libc/dns/parsehoststxt_test.c @@ -45,7 +45,7 @@ TEST(parsehoststxt, testCorrectlyTokenizesAndSorts) { "203.0.113.2 cat.example. cat\n"; struct HostsTxt *ht = calloc(1, sizeof(struct HostsTxt)); FILE *f = fmemopen(NULL, BUFSIZ, "r+"); - fwrite(kInput, 1, strlen(kInput), f); + ASSERT_EQ(1, fwrite(kInput, strlen(kInput), 1, f)); rewind(f); ASSERT_EQ(0, parsehoststxt(ht, f)); sorthoststxt(ht); diff --git a/test/libc/stdio/fgetwc_test.c b/test/libc/stdio/fgetwc_test.c index 8877f685..a8263e26 100644 --- a/test/libc/stdio/fgetwc_test.c +++ b/test/libc/stdio/fgetwc_test.c @@ -57,15 +57,21 @@ TEST(fgetwc, testUnicode_oneChar_writtenAsRawUtf8) { fclose(f); } -TEST(fgetwc, testUnicode_spuriousContChars_synchronizedBeforeRead) { +TEST(fgetwc, testUnicode_undecodableSequences_fallsBackToBinary) { FILE *f = fmemopen(NULL, BUFSIZ, "r+"); - EXPECT_EQ(0x90, fputc(0x90, f)); - EXPECT_EQ(0x90, fputc(0x90, f)); + EXPECT_EQ(0200, fputc(0200, f)); + EXPECT_EQ(0220, fputc(0220, f)); EXPECT_EQ(0xF0, fputc(0xF0, f)); EXPECT_EQ(0x90, fputc(0x90, f)); EXPECT_EQ(0x8C, fputc(0x8C, f)); EXPECT_EQ(0xB0, fputc(0xB0, f)); + EXPECT_EQ(0304, fputc(0304, f)); + EXPECT_EQ('a', fputc('a', f)); rewind(f); + EXPECT_EQ(0200, fgetwc(f)); + EXPECT_EQ(0220, fgetwc(f)); EXPECT_EQ(L'𐌰', fgetwc(f)); + EXPECT_EQ(0304, fgetwc(f)); + EXPECT_EQ('a', fgetwc(f)); fclose(f); } diff --git a/test/libc/stdio/fputc_test.c b/test/libc/stdio/fputc_test.c new file mode 100644 index 00000000..9c2d4788 --- /dev/null +++ b/test/libc/stdio/fputc_test.c @@ -0,0 +1,62 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/testlib.h" + +FILE *f; +char buf[512]; +char testlib_enable_tmp_setup_teardown; + +TEST(fputc, test) { + ASSERT_NE(NULL, (f = fopen("hog", "w+"))); + EXPECT_EQ('h', fputc('h', f)); + EXPECT_EQ(0xFF, fputc(-1, f)); + EXPECT_NE(-1, fseek(f, 0, SEEK_SET)); + EXPECT_EQ('h', fgetc(f)); + EXPECT_EQ(0, fread(NULL, 0, 0, f)); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(0xFF, fgetc(f)); + EXPECT_EQ(-1, fgetc(f)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(2, fread(buf, 1, 512, f)); + EXPECT_TRUE(!memcmp(buf, "h\377", 2)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fgetc, testUnbuffered) { + ASSERT_NE(NULL, (f = fopen("hog", "w+"))); + EXPECT_NE(-1, setvbuf(f, NULL, _IONBF, 0)); + EXPECT_EQ('h', fputc('h', f)); + EXPECT_EQ(0xFF, fputc(-1, f)); + EXPECT_NE(-1, fseek(f, 0, SEEK_SET)); + EXPECT_EQ('h', fgetc(f)); + EXPECT_EQ(0xFF, fgetc(f)); + EXPECT_EQ(-1, fgetc(f)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(2, fread(buf, 1, 512, f)); + EXPECT_TRUE(!memcmp(buf, "h\377", 2)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); +} diff --git a/test/libc/stdio/fputs_test.c b/test/libc/stdio/fputs_test.c index d4e7a86f..eb207a01 100644 --- a/test/libc/stdio/fputs_test.c +++ b/test/libc/stdio/fputs_test.c @@ -23,10 +23,35 @@ #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" +FILE *f; +char buf[512]; +char testlib_enable_tmp_setup_teardown; + +TEST(fputs, test) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(5, fread(buf, 1, 512, f)); + EXPECT_TRUE(!memcmp(buf, "hello", 5)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); +} + +TEST(puts, test) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ('\n', fputc('\n', f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(6, fread(buf, 1, 512, f)); + EXPECT_TRUE(!memcmp(buf, "hello\n", 6)); + EXPECT_TRUE(feof(f)); + EXPECT_NE(-1, fclose(f)); +} + BENCH(fputs, bench) { - FILE *f; - char *buf; - buf = gc(malloc(kHyperionSize)); + char *buf = gc(malloc(kHyperionSize)); EZBENCH2("fputs", f = fmemopen(buf, kHyperionSize, "r+"), fputs(kHyperion, f)); } diff --git a/test/libc/stdio/ftell_test.c b/test/libc/stdio/ftell_test.c new file mode 100644 index 00000000..58d55e85 --- /dev/null +++ b/test/libc/stdio/ftell_test.c @@ -0,0 +1,177 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/mem/mem.h" +#include "libc/runtime/gc.internal.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +FILE *f; +char buf[512]; +char testlib_enable_tmp_setup_teardown; + +TEST(ftell, test) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fread(buf, 1, 1, f)); + EXPECT_EQ(1, ftell(f)); + EXPECT_EQ('h', buf[0]); + EXPECT_EQ(2, fread(buf, 1, 2, f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ('e', buf[0]); + EXPECT_EQ('l', buf[1]); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(2, fread(buf, 1, 512, f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_TRUE(feof(f)); + EXPECT_EQ('l', buf[0]); + EXPECT_EQ('o', buf[1]); + EXPECT_NE(-1, fclose(f)); +} + +TEST(ftell, testSmallBuffer) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_NE(-1, setvbuf(f, gc(xmalloc(1)), _IOFBF, 1)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_NE(-1, setvbuf(f, gc(xmalloc(1)), _IOFBF, 1)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fread(buf, 1, 1, f)); + EXPECT_EQ(1, ftell(f)); + EXPECT_EQ('h', buf[0]); + EXPECT_EQ(2, fread(buf, 1, 2, f)); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ('e', buf[0]); + EXPECT_EQ('l', buf[1]); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(2, fread(buf, 1, 512, f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_TRUE(feof(f)); + EXPECT_EQ('l', buf[0]); + EXPECT_EQ('o', buf[1]); + EXPECT_NE(-1, fclose(f)); +} + +TEST(ftell, testNoBuffer) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_NE(-1, setvbuf(f, gc(xmalloc(0)), _IONBF, 0)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_NE(-1, setvbuf(f, gc(xmalloc(0)), _IONBF, 0)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fread(buf, 1, 1, f)); + EXPECT_EQ(1, ftell(f)); + EXPECT_EQ('h', buf[0]); + EXPECT_EQ(2, fread(buf, 1, 2, f)); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ('e', buf[0]); + EXPECT_EQ('l', buf[1]); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(2, fread(buf, 1, 512, f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_TRUE(feof(f)); + EXPECT_EQ('l', buf[0]); + EXPECT_EQ('o', buf[1]); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fseek, test) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, -3, SEEK_CUR)); + EXPECT_EQ(2, ftell(f)); + EXPECT_EQ(1, fputs("L", f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ(0, fseek(f, -1, SEEK_END)); + EXPECT_EQ(4, ftell(f)); + EXPECT_EQ(1, fputs("O", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, 0, SEEK_SET)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fputs("H", f)); + EXPECT_EQ(1, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(5, fread(buf, 1, 512, f)); + EXPECT_BINEQ(u"HeLlO", buf); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fseek, testMemory) { + ASSERT_NE(NULL, (f = fmemopen(gc(strdup("hello")), 5, "r+"))); + EXPECT_EQ(0, fseek(f, 0, SEEK_END)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, -3, SEEK_CUR)); + EXPECT_EQ(2, ftell(f)); + EXPECT_EQ(1, fputs("L", f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ(0, fseek(f, -1, SEEK_END)); + EXPECT_EQ(4, ftell(f)); + EXPECT_EQ(1, fputs("O", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, 0, SEEK_SET)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fputs("H", f)); + EXPECT_EQ(1, ftell(f)); + rewind(f); + EXPECT_EQ(5, fread(buf, 1, 512, f)); + EXPECT_BINEQ(u"HeLlO", buf); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fseek, testSmallBuffer) { + ASSERT_NE(NULL, (f = fopen("hog", "w"))); + EXPECT_NE(-1, setvbuf(f, gc(xmalloc(1)), _IOFBF, 1)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(5, fputs("hello", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, -3, SEEK_CUR)); + EXPECT_EQ(2, ftell(f)); + EXPECT_EQ(1, fputs("L", f)); + EXPECT_EQ(3, ftell(f)); + EXPECT_EQ(0, fseek(f, -1, SEEK_END)); + EXPECT_EQ(4, ftell(f)); + EXPECT_EQ(1, fputs("O", f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_EQ(0, fseek(f, 0, SEEK_SET)); + EXPECT_EQ(0, ftell(f)); + EXPECT_EQ(1, fputs("H", f)); + EXPECT_EQ(1, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(5, fread(buf, 1, 512, f)); + EXPECT_BINEQ(u"HeLlO", buf); + EXPECT_NE(-1, fclose(f)); +} diff --git a/test/libc/stdio/fwrite_test.c b/test/libc/stdio/fwrite_test.c index d8f3fcf6..6bb2f640 100644 --- a/test/libc/stdio/fwrite_test.c +++ b/test/libc/stdio/fwrite_test.c @@ -17,37 +17,164 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/mem/mem.h" +#include "libc/rand/rand.h" +#include "libc/runtime/gc.internal.h" +#include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" +#include "libc/time/time.h" +/* TODO(jart): O_APPEND on Windows */ + +#define PATH "hog" + +FILE *f; +char buf[512]; char testlib_enable_tmp_setup_teardown; TEST(fwrite, test) { - FILE *f; - char buf[512]; - - ASSERT_NE(NULL, (f = fopen("hog", "wb"))); + ASSERT_NE(NULL, (f = fopen(PATH, "wb"))); EXPECT_EQ(-1, fgetc(f)); EXPECT_EQ(5, fwrite("hello", 1, 5, f)); EXPECT_EQ(5, ftell(f)); EXPECT_NE(-1, fclose(f)); - - ASSERT_NE(NULL, (f = fopen("hog", "r"))); - EXPECT_EQ(-1, fwrite("hello", 1, 5, f)); + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + EXPECT_EQ(0, fwrite("hello", 1, 5, f)); EXPECT_EQ(EBADF, ferror(f)); EXPECT_NE(-1, fclose(f)); - - ASSERT_NE(NULL, (f = fopen("hog", "a+b"))); + ASSERT_NE(NULL, (f = fopen(PATH, "a+b"))); EXPECT_EQ(5, fwrite("hello", 1, 5, f)); EXPECT_NE(-1, fclose(f)); - - /* TODO(jart): O_APPEND on Windows */ - if (!IsWindows()) { - ASSERT_NE(NULL, (f = fopen("hog", "r"))); - EXPECT_EQ(10, fread(buf, 1, 10, f)); - EXPECT_TRUE(!memcmp(buf, "hellohello", 10)); - EXPECT_NE(-1, fclose(f)); - } + if (IsWindows()) return; + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + EXPECT_EQ(10, fread(buf, 1, 10, f)); + EXPECT_TRUE(!memcmp(buf, "hellohello", 10)); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fwrite, testSmallBuffer) { + ASSERT_NE(NULL, (f = fopen(PATH, "wb"))); + setbuffer(f, gc(malloc(1)), 1); + EXPECT_EQ(-1, fgetc(f)); + EXPECT_EQ(5, fwrite("hello", 1, 5, f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setbuffer(f, gc(malloc(1)), 1); + EXPECT_EQ(0, fwrite("hello", 1, 5, f)); + EXPECT_EQ(EBADF, ferror(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "a"))); + setbuffer(f, gc(malloc(1)), 1); + EXPECT_EQ(5, fwrite("hello", 1, 5, f)); + EXPECT_NE(-1, fclose(f)); + if (IsWindows()) return; + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setbuffer(f, gc(malloc(1)), 1); + EXPECT_EQ(10, fread(buf, 1, 10, f)); + EXPECT_TRUE(!memcmp(buf, "hellohello", 10)); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fwrite, testLineBuffer) { + ASSERT_NE(NULL, (f = fopen(PATH, "wb"))); + setvbuf(f, NULL, _IOLBF, 64); + EXPECT_EQ(-1, fgetc(f)); + EXPECT_EQ(5, fwrite("heyy\n", 1, 5, f)); + EXPECT_EQ(0, fread(buf, 0, 0, f)); + EXPECT_FALSE(feof(f)); + EXPECT_EQ(0, fread(buf, 1, 10, f)); + EXPECT_EQ(EBADF, ferror(f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setvbuf(f, NULL, _IOLBF, 64); + EXPECT_EQ(0, fwrite("heyy\n", 1, 5, f)); + EXPECT_EQ(EBADF, ferror(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "a"))); + setvbuf(f, NULL, _IOLBF, 64); + EXPECT_EQ(5, fwrite("heyy\n", 1, 5, f)); + EXPECT_NE(-1, fclose(f)); + if (IsWindows()) return; + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setvbuf(f, NULL, _IOLBF, 64); + EXPECT_EQ(10, fread(buf, 1, 10, f)); + EXPECT_TRUE(!memcmp(buf, "heyy\nheyy\n", 10)); + EXPECT_NE(-1, fclose(f)); +} + +TEST(fwrite, testNoBuffer) { + ASSERT_NE(NULL, (f = fopen(PATH, "wb"))); + setvbuf(f, NULL, _IONBF, 64); + EXPECT_EQ(-1, fgetc(f)); + EXPECT_EQ(5, fwrite("heyy\n", 1, 5, f)); + EXPECT_EQ(5, ftell(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setvbuf(f, NULL, _IONBF, 64); + EXPECT_EQ(0, fwrite("heyy\n", 1, 5, f)); + EXPECT_EQ(EBADF, ferror(f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "a"))); + setvbuf(f, NULL, _IONBF, 64); + EXPECT_EQ(5, fwrite("heyy\n", 1, 5, f)); + EXPECT_NE(-1, fclose(f)); + if (IsWindows()) return; + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setvbuf(f, NULL, _IONBF, 64); + EXPECT_EQ(10, fread(buf, 1, 10, f)); + EXPECT_TRUE(!memcmp(buf, "heyy\nheyy\n", 10)); + EXPECT_NE(-1, fclose(f)); +} + +void MeatyReadWriteTest(void) { + size_t n; + char *mem, *buf; + n = 8 * 1024 * 1024; + buf = gc(malloc(n)); + mem = rngset(gc(malloc(n)), n, rand64, -1); + ASSERT_NE(NULL, (f = fopen(PATH, "wb"))); + setbuffer(f, gc(malloc(4 * 1000 * 1000)), 4 * 1000 * 1000); + EXPECT_EQ(n, fwrite(mem, 1, n, f)); + EXPECT_NE(-1, fclose(f)); + ASSERT_NE(NULL, (f = fopen(PATH, "r"))); + setbuffer(f, gc(malloc(4 * 1000 * 1000)), 4 * 1000 * 1000); + EXPECT_EQ(n, fread(buf, 1, n, f)); + EXPECT_EQ(0, memcmp(buf, mem, n)); + EXPECT_NE(-1, fclose(f)); +} + +volatile bool gotsigint; +void OnSigInt(int sig) { + gotsigint = true; +} + +TEST(fwrite, signalStorm) { + if (IsWindows()) return; + int pid; + struct sigaction oldchld, oldint; + struct sigaction sachld = {.sa_handler = SIG_IGN}; + struct sigaction saint = {.sa_handler = OnSigInt}; + EXPECT_NE(-1, sigaction(SIGINT, &saint, &oldint)); + EXPECT_NE(-1, sigaction(SIGCHLD, &sachld, &oldchld)); + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + do { + ASSERT_NE(-1, kill(getppid(), SIGINT)); + usleep(1); + } while (!gotsigint); + _exit(0); + } + pause(); + MeatyReadWriteTest(); + EXPECT_NE(-1, kill(pid, SIGINT)); + while (wait(0) == -1 && errno == EINTR) donothing; + EXPECT_NE(-1, sigaction(SIGCHLD, &oldchld, NULL)); + EXPECT_NE(-1, sigaction(SIGINT, &oldint, NULL)); } diff --git a/test/libc/stdio/getline_test.c b/test/libc/stdio/getline_test.c index 73288765..8dfb714f 100644 --- a/test/libc/stdio/getline_test.c +++ b/test/libc/stdio/getline_test.c @@ -20,7 +20,12 @@ #include "libc/mem/mem.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +char testlib_enable_tmp_setup_teardown; TEST(getline, testEmpty) { FILE *f = fmemopen("", 0, "r+"); @@ -72,3 +77,41 @@ TEST(getline, testBinaryLine_countExcludesOnlyTheBonusNul) { fclose(f); free(line); } + +void WriteHyperionFile(void) { + FILE *f; + ASSERT_NE(NULL, (f = fopen("hyperion.txt", "w"))); + EXPECT_EQ(1, fwrite(kHyperion, kHyperionSize, 1, f)); + EXPECT_NE(-1, fclose(f)); +} + +void ReadHyperionLines(void) { + FILE *f; + ssize_t rc; + char *data = NULL; + size_t size = 0; + char *line = NULL; + size_t linesize = 0; + ASSERT_NE(NULL, (f = fopen("hyperion.txt", "r"))); + while ((rc = getline(&line, &linesize, f)) != -1) { + data = xrealloc(data, size + rc); + memcpy(data + size, line, rc); + size += rc; + } + EXPECT_TRUE(feof(f)); + ASSERT_EQ(kHyperionSize, size); + EXPECT_EQ(0, memcmp(kHyperion, data, size)); + EXPECT_NE(-1, fclose(f)); + free(line); + free(data); +} + +TEST(getline, lotsOfLines_roundTrips) { + WriteHyperionFile(); + ReadHyperionLines(); +} + +BENCH(getline, bench) { + WriteHyperionFile(); + EZBENCH2("getline", donothing, ReadHyperionLines()); +} diff --git a/test/libc/stdio/ungetc_test.c b/test/libc/stdio/ungetc_test.c new file mode 100644 index 00000000..dc887f7a --- /dev/null +++ b/test/libc/stdio/ungetc_test.c @@ -0,0 +1,69 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/stdio/stdio.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" + +FILE *f; +char buf[512]; +char testlib_enable_tmp_setup_teardown; + +TEST(ungetc, testGetChar_canBeUndoneWithinReason) { + ASSERT_NE(NULL, (f = fopen("hog", "wb"))); + EXPECT_EQ(12, fputs("hello world\n", f)); + EXPECT_NE(-1, fputs(kHyperion, f)); + EXPECT_EQ(0, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ('h', fgetc(f)); + EXPECT_EQ('e', fgetc(f)); + EXPECT_EQ('e', ungetc('e', f)); + EXPECT_EQ('h', ungetc('h', f)); + EXPECT_EQ(5, fread(buf, 1, 5, f)); + EXPECT_BINEQ(u"hello", buf); + EXPECT_EQ(0, fclose(f)); +} + +TEST(ungetc, testRead_canBeUndoneWithinReason) { + ASSERT_NE(NULL, (f = fopen("hog", "wb"))); + EXPECT_EQ(12, fputs("hello world\n", f)); + EXPECT_EQ(0, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(3, fread(buf, 1, 3, f)); + EXPECT_BINEQ(u"hel", buf); + EXPECT_EQ('l', ungetc(buf[2], f)); + EXPECT_EQ('e', ungetc(buf[1], f)); + EXPECT_EQ(4, fread(buf, 1, 4, f)); + EXPECT_BINEQ(u"ello", buf); + EXPECT_EQ(0, fclose(f)); +} + +TEST(ungetwc, testGetWideChar_canBeUndoneWithinReason) { + memset(buf, 0, sizeof(buf)); + ASSERT_NE(NULL, (f = fopen("hog", "wb"))); + EXPECT_NE(-1, fputs("𐌰𐌱\n", f)); + EXPECT_EQ(0, fclose(f)); + ASSERT_NE(NULL, (f = fopen("hog", "r"))); + EXPECT_EQ(L'𐌰', fgetwc(f)); + EXPECT_EQ(L'𐌱', fgetwc(f)); + EXPECT_EQ(L'𐌱', ungetwc(L'𐌱', f)); + EXPECT_EQ(L'𐌰', ungetwc(L'𐌰', f)); + EXPECT_NE(0, fread(buf, 1, sizeof(buf), f)); + EXPECT_STREQ("𐌰𐌱\n", buf); + EXPECT_EQ(0, fclose(f)); +} diff --git a/test/net/http/escapehtml_test.c b/test/net/http/escapehtml_test.c index 432d3a7b..963baba0 100644 --- a/test/net/http/escapehtml_test.c +++ b/test/net/http/escapehtml_test.c @@ -16,7 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" #include "libc/runtime/gc.internal.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" #include "net/http/escape.h" @@ -39,3 +42,12 @@ TEST(escapehtml, testLargeGrowth) { TEST(escapehtml, testEmpty) { EXPECT_STREQ("", gc(escapehtml(""))); } + +TEST(escapehtml, testAstralPlanes_doesNothing) { + EXPECT_STREQ("𐌰", escapehtml("𐌰")); +} + +BENCH(escapehtml, bench) { + EZBENCH2("escapehtml", donothing, + free(EscapeHtml(kHyperion, kHyperionSize).data)); +} diff --git a/test/net/http/escapejsstringliteral_test.c b/test/net/http/escapejsstringliteral_test.c new file mode 100644 index 00000000..98da2f6c --- /dev/null +++ b/test/net/http/escapejsstringliteral_test.c @@ -0,0 +1,79 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" +#include "net/http/escape.h" + +char *escapejs(const char *s) { + struct EscapeResult r; + r = EscapeJsStringLiteral(s, strlen(s)); + ASSERT_EQ(strlen(r.data), r.size); + return r.data; +} + +TEST(EscapeJsStringLiteral, test) { + EXPECT_STREQ("", escapejs("")); + EXPECT_STREQ("\\u00ff", escapejs("\377")); + EXPECT_STREQ("\\u00ff\\u0080\\u0080\\u0080\\u0080", + escapejs("\377\200\200\200\200")); + EXPECT_STREQ("\\u0001\\u0002\\u0003 \\u0026\\u003d\\u003c\\u003e\\/", + escapejs("\1\2\3 &=<>/")); +} + +TEST(EscapeJsStringLiteral, testUcs2) { + EXPECT_STREQ("\\u00d0\\u263b", escapejs("Ð☻")); +} + +TEST(EscapeJsStringLiteral, testAstralPlanes) { + EXPECT_STREQ("\\ud800\\udf30\\ud800\\udf30", escapejs("𐌰𐌰")); +} + +TEST(EscapeJsStringLiteral, testBrokenUnicode_sparesInnocentCharacters) { + EXPECT_STREQ("\\u00e1YO", escapejs("\xE1YO")); +} + +void makefile1(void) { + FILE *f; + struct EscapeResult r; + r = EscapeJsStringLiteral(kHyperion, kHyperionSize); + f = fopen("/tmp/a", "wb"); + fwrite(r.data, r.size, 1, f); + fclose(f); + free(r.data); +} + +void makefile2(void) { + int fd; + struct EscapeResult r; + r = EscapeJsStringLiteral(kHyperion, kHyperionSize); + fd = creat("/tmp/a", 0644); + write(fd, r.data, r.size); + close(fd); + free(r.data); +} + +BENCH(EscapeJsStringLiteral, bench) { + EZBENCH2("escapejs", donothing, + free(EscapeJsStringLiteral(kHyperion, kHyperionSize).data)); + EZBENCH2("makefile1", donothing, makefile1()); + EZBENCH2("makefile2", donothing, makefile2()); +} diff --git a/test/libc/stdio/fread_test.c b/test/net/http/escapeurlparam_test.c similarity index 71% rename from test/libc/stdio/fread_test.c rename to test/net/http/escapeurlparam_test.c index cb4d7a62..3538c3b3 100644 --- a/test/libc/stdio/fread_test.c +++ b/test/net/http/escapeurlparam_test.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ @@ -18,18 +18,35 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/mem/mem.h" #include "libc/runtime/gc.internal.h" -#include "libc/stdio/stdio.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" +#include "net/http/escape.h" -BENCH(fputs, bench) { - FILE *f; - char *buf = gc(malloc(kHyperionSize)); - char *buf2 = gc(malloc(kHyperionSize)); - buf2 = gc(malloc(kHyperionSize)); - f = fmemopen(buf, kHyperionSize, "r+"); - ASSERT_EQ(kHyperionSize, fread(buf2, 1, kHyperionSize, f)); - EZBENCH2("fread", f = fmemopen(buf, kHyperionSize, "r+"), - fread(buf2, 1, kHyperionSize, f)); +char *escapeparam(const char *s) { + struct EscapeResult r; + r = EscapeUrlParam(s, strlen(s)); + ASSERT_EQ(strlen(r.data), r.size); + return r.data; +} + +TEST(escapeparam, test) { + EXPECT_STREQ("abc+%26%3C%3E%22%27%01%02", gc(escapeparam("abc &<>\"'\1\2"))); +} + +TEST(escapeparam, testLargeGrowth) { + EXPECT_STREQ("%22%22%22", gc(escapeparam("\"\"\""))); +} + +TEST(escapeparam, testEmpty) { + EXPECT_STREQ("", gc(escapeparam(""))); +} + +TEST(escapeparam, testAstralPlanes_usesUtf8HexEncoding) { + EXPECT_STREQ("%F0%90%8C%B0", escapeparam("𐌰")); +} + +BENCH(escapeparam, bench) { + EZBENCH2("escapeparam", donothing, + free(EscapeUrlParam(kHyperion, kHyperionSize).data)); } diff --git a/third_party/lua/lbaselib.c b/third_party/lua/lbaselib.c index 35f007ff..fe58794e 100644 --- a/third_party/lua/lbaselib.c +++ b/third_party/lua/lbaselib.c @@ -479,7 +479,7 @@ static int luaB_tostring (lua_State *L) { static const luaL_Reg base_funcs[] = { {"assert", luaB_assert}, {"collectgarbage", luaB_collectgarbage}, - /* {"dofile", luaB_dofile}, */ + {"dofile", luaB_dofile}, {"error", luaB_error}, {"getmetatable", luaB_getmetatable}, {"ipairs", luaB_ipairs}, diff --git a/tool/net/.init.lua b/tool/net/.init.lua new file mode 100644 index 00000000..0c16759d --- /dev/null +++ b/tool/net/.init.lua @@ -0,0 +1 @@ +-- special script called by main redbean process at startup diff --git a/tool/net/.reload.lua b/tool/net/.reload.lua new file mode 100644 index 00000000..1298ca41 --- /dev/null +++ b/tool/net/.reload.lua @@ -0,0 +1 @@ +-- special script called by main redbean process on invalidate diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 2c31b5cf..70000941 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/alg/arraylist2.internal.h" #include "libc/bits/bits.h" #include "libc/bits/bswap.h" #include "libc/bits/safemacros.internal.h" @@ -137,16 +136,8 @@ USAGE\n\ You can have redbean run as a daemon by doing the following:\n\ \n\ redbean.com -vv -d -L redbean.log -P redbean.pid\n\ - kill -HUP $(cat redbean.pid)\n\ kill -TERM $(cat redbean.pid) # 1x: graceful shutdown\n\ kill -TERM $(cat redbean.pid) # 2x: forceful shutdown\n\ -\n\ - Each connection uses a point in time snapshot of your ZIP file.\n\ - If your ZIP is deleted then serving continues. If it's replaced\n\ - then issuing SIGUSR1 (or SIGHUP if daemon) will reindex the zip\n\ - for subsequent connections without interrupting active ones. If\n\ - Ctrl-C or SIGTERM is issued then a graceful shutdown is started\n\ - but if it's issued a second time, active connections are reset.\n\ \n\ redbean imposes a 32kb limit on requests to limit the memory of\n\ connection processes, which grow to whatever number your system\n\ @@ -328,7 +319,6 @@ static int64_t programtime; static size_t contentlength; static int64_t cacheseconds; static uint8_t gzip_footer[8]; -static const char *programfile; static const char *serverheader; static struct Buffer body; @@ -479,11 +469,6 @@ static const char *GetContentType(const char *path, size_t n) { return "application/octet-stream"; } -static wontreturn void PrintUsage(FILE *f, int rc) { - fprintf(f, "SYNOPSIS\n\n %s%s", program_invocation_name, USAGE); - exit(rc); -} - static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) { char *p = buf; const uint8_t *ip4 = (const uint8_t *)&addr->sin_addr.s_addr; @@ -497,7 +482,7 @@ static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) { static void SetDefaults(void) { cacheseconds = -1; - serverheader = "redbean/0.1"; + serverheader = "redbean/0.2"; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(DEFAULT_PORT); serveraddr.sin_addr.s_addr = INADDR_ANY; @@ -506,6 +491,11 @@ static void SetDefaults(void) { AddRedirect("/favicon.ico=/tool/net/redbean.ico"); } +static wontreturn void PrintUsage(FILE *f, int rc) { + fprintf(f, "SYNOPSIS\n\n %s%s", program_invocation_name, USAGE); + exit(rc); +} + static void GetOpts(int argc, char *argv[]) { int opt; while ((opt = getopt(argc, argv, "zhduvml:p:w:r:c:L:P:U:G:B:")) != -1) { @@ -550,7 +540,7 @@ static void GetOpts(int argc, char *argv[]) { daemonuid = atoi(optarg); break; case 'G': - daemonuid = atoi(optarg); + daemongid = atoi(optarg); break; case 'h': PrintUsage(stdout, EXIT_SUCCESS); @@ -580,6 +570,8 @@ static void Daemonize(void) { freopen("/dev/null", "r", stdin); freopen(logpath, "a", stdout); freopen(logpath, "a", stderr); + LOGIFNEG1(setuid(daemonuid)); + LOGIFNEG1(setgid(daemongid)); } static void OnWorkerExit(int pid, int ws) { @@ -1551,13 +1543,19 @@ static int LuaEscapeLiteral(lua_State *L) { return LuaEscaper(L, EscapeJsStringLiteral); } -static const luaL_Reg kLuaLibs[] = { - {"_G", luaopen_base}, // - {"table", luaopen_table}, // - {"string", luaopen_string}, // - {"math", luaopen_math}, // - {"utf8", luaopen_utf8}, // -}; +static void LuaRun(const char *path) { + struct Asset *a; + const char *code; + if ((a = LocateAsset(path, strlen(path)))) { + code = LoadAsset(a, NULL); + if (luaL_dostring(L, code) != LUA_OK) { + WARNF("%s %s", path, lua_tostring(L, -1)); + } + free(code); + } else { + DEBUGF("%s not found", path); + } +} static const luaL_Reg kLuaFuncs[] = { {"EscapeFragment", LuaEscapeFragment}, // @@ -1591,14 +1589,16 @@ static const luaL_Reg kLuaFuncs[] = { static void LuaInit(void) { size_t i; L = luaL_newstate(); - for (i = 0; i < ARRAYLEN(kLuaLibs); ++i) { - luaL_requiref(L, kLuaLibs[i].name, kLuaLibs[i].func, 1); - lua_pop(L, 1); - } + luaL_openlibs(L); for (i = 0; i < ARRAYLEN(kLuaFuncs); ++i) { lua_pushcfunction(L, kLuaFuncs[i].func); lua_setglobal(L, kLuaFuncs[i].name); } + LuaRun("/tool/net/.init.lua"); +} + +static void LuaReload(void) { + LuaRun("/tool/net/.reload.lua"); } static char *ServeLua(struct Asset *a) { @@ -1672,12 +1672,35 @@ static char *HandleRedirect(struct Redirect *r) { return p; } -static const char *DescribeClose(void) { - if (killed) return "killed"; - if (meltdown) return "meltdown"; - if (terminated) return "terminated"; - if (connectionclose) return "connectionclose"; - return "destroyed"; +static ssize_t SendString(const char *s) { + ssize_t rc; + for (;;) { + if ((rc = write(client, s, strlen(s))) != -1 || errno != EINTR) { + return rc; + } + } +} + +static ssize_t SendContinue(void) { + return SendString("\ +HTTP/1.1 100 Continue\r\n\ +\r\n"); +} + +static ssize_t SendTimeout(void) { + return SendString("\ +HTTP/1.1 408 Request Timeout\r\n\ +Connection: close\r\n\ +Content-Length: 0\r\n\ +\r\n"); +} + +static ssize_t SendServiceUnavailable(void) { + return SendString("\ +HTTP/1.1 503 Service Unavailable\r\n\ +Connection: close\r\n\ +Content-Length: 0\r\n\ +\r\n"); } static void LogClose(const char *reason) { @@ -1688,6 +1711,14 @@ static void LogClose(const char *reason) { } } +static const char *DescribeClose(void) { + if (killed) return "killed"; + if (meltdown) return "meltdown"; + if (terminated) return "terminated"; + if (connectionclose) return "connectionclose"; + return "destroyed"; +} + static char *HandleMessage(size_t hdrlen) { long r; ssize_t cl, rc; @@ -1703,6 +1734,10 @@ static char *HandleMessage(size_t hdrlen) { CompareHeaderValue(kHttpTransferEncoding, "identity"))) { return ServeError(501, "Not Implemented"); } + if (HasHeader(kHttpExpect) && + CompareHeaderValue(kHttpExpect, "100-continue")) { + return ServeError(417, "Expectation Failed"); + } if ((cl = ParseContentLength(inbuf.p + req.headers[kHttpContentLength].a, req.headers[kHttpContentLength].b - req.headers[kHttpContentLength].a)) == -1) { @@ -1719,6 +1754,9 @@ static char *HandleMessage(size_t hdrlen) { if (need > inbuf.n) { return ServeError(413, "Payload Too Large"); } + if (!CompareHeaderValue(kHttpExpect, "100-continue") && httpversion >= 101) { + SendContinue(); + } while (amtread < need) { if (++frags == 64) { LogClose("payload fragged"); @@ -1838,26 +1876,6 @@ static bool HandleRequest(void) { return true; } -static void SendString(const char *s) { - write(client, s, strlen(s)); -} - -static void SendTimeout(void) { - SendString("\ -HTTP/1.1 408 Request Timeout\r\n\ -Connection: close\r\n\ -Content-Length: 0\r\n\ -\r\n"); -} - -static void SendServiceUnavailable(void) { - SendString("\ -HTTP/1.1 503 Service Unavailable\r\n\ -Connection: close\r\n\ -Content-Length: 0\r\n\ -\r\n"); -} - static void InitRequest(void) { frags = 0; content = NULL; @@ -1968,8 +1986,7 @@ void RedBean(void) { if (IsWindows()) uniprocess = true; if (daemonize) Daemonize(); gmtoff = GetGmtOffset(); - programfile = (const char *)getauxval(AT_EXECFN); - CHECK(OpenZip(programfile)); + CHECK(OpenZip((const char *)getauxval(AT_EXECFN))); xsigaction(SIGINT, OnInt, 0, 0, 0); xsigaction(SIGHUP, OnHup, 0, 0, 0); xsigaction(SIGTERM, OnTerm, 0, 0, 0); @@ -2001,21 +2018,17 @@ void RedBean(void) { printf("%d\n", ntohs(serveraddr.sin_port)); fflush(stdout); } - LuaInit(); UpdateCurrentDate(nowl()); inbuf.n = 64 * 1024; inbuf.p = xvalloc(inbuf.n); hdrbuf.n = 4 * 1024; hdrbuf.p = xvalloc(hdrbuf.n); + LuaInit(); while (!terminated) { if (zombied) { ReapZombies(); } else if (invalidated) { - if (OpenZip(programfile)) { - LOGF("%s reindexed zip", serveraddrstr); - } else { - WARNF("%s reindexing failed", serveraddrstr); - } + LuaReload(); invalidated = false; } else if (heartbeat) { UpdateCurrentDate(nowl());