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());