#include "inc/priv.h" #include "inc/str.h" #include "inc/time.h" #define MOST_OF(x) ((x) - ((x)>>4)) ios *ios_stdin = nil; ios *ios_stdout = nil; ios *ios_stderr = nil; /* OS-level primitive wrappers */ void * sl_memrchr(const void *s, int c, usize n) { const u8int *src = (const u8int*)s + n; u8int uc = c; while(--src >= (const u8int*)s) if(*src == uc) return (void *)src; return nil; } sl_fd_wr(1) sl_rd(2, 3) sl_wr(4) sl_nonnull(2, 4) static int write_all(int fd, const u8int *buf, usize n, usize *nwritten) { usize wrote; *nwritten = 0; while(n > 0){ wrote = 0; int err = sys_write(fd, buf, n, &wrote); n -= wrote; *nwritten += wrote; buf += wrote; if(err) return err; } return 0; } /* internal utility functions */ sl_nonnull(1) static u8int * buf_realloc(ios *s, usize sz) { u8int *temp; if((s->buf == nil || s->buf == &s->local[0]) && sz <= IOS_INLSIZE){ /* TODO: if we want to allow shrinking, see if the buffer shrank down to this size, in which case we need to copy. */ s->buf = &s->local[0]; s->maxsize = IOS_INLSIZE; s->flags |= IOS_FL_OWNBUF; return s->buf; } if(sz <= s->maxsize) return s->buf; if((s->flags & IOS_FL_OWNBUF) != 0 && s->buf != &s->local[0]){ // if we own the buffer we're free to resize it // always allocate 1 bigger in case user wants to add a NUL // terminator after taking over the buffer temp = MEM_REALLOC(s->buf, sz); if(temp == nil) return nil; }else{ temp = MEM_ALLOC(sz); if(temp == nil) return nil; s->flags |= IOS_FL_OWNBUF; if(s->buf != nil) memcpy(temp, s->buf, s->size); } s->buf = temp; s->maxsize = sz; return s->buf; } // write a block of data into the buffer at the current position, resizing // if necessary. returns # written. sl_nonnull(1, 2) sl_rd(2, 3) static usize write_grow(ios *s, const void *data, usize n) { usize amt; usize newsize; if(sl_unlikely(n == 0)) return 0; if(sl_likely(s->bpos + n > s->size)){ if(sl_unlikely(s->bpos + n > s->maxsize)){ /* TODO: here you might want to add a mechanism for limiting the growth of the io. */ newsize = s->maxsize ? s->maxsize * 2 : 8; while(s->bpos + n > newsize) newsize *= 2; if(buf_realloc(s, newsize) == nil){ /* no more space; write as much as we can */ amt = s->maxsize - s->bpos; if(amt > 0) memcpy(&s->buf[s->bpos], data, amt); s->bpos += amt; s->size = s->maxsize; return amt; } } s->size = s->bpos + n; } memcpy(s->buf + s->bpos, data, n); s->bpos += n; return n; } /* interface functions, low level */ usize ios_read(ios *s, void *dest, usize n) { usize tot = 0; usize got, avail; if(s->state == IOS_STATE_CLOSED || (s->flags & IOS_FL_WRONLY) != 0) return 0; while(n > 0){ avail = s->size - s->bpos; if(avail > 0){ usize ncopy = avail >= n ? n : avail; memcpy(dest, s->buf + s->bpos, ncopy); s->bpos += ncopy; if(ncopy >= n){ s->state = IOS_STATE_RD; return tot+ncopy; } } if(s->bm == IOS_BUF_MEM || s->fd == -1){ // can't get any more data s->state = IOS_STATE_RD; if(avail == 0) s->flags |= IOS_FL_EOF; return avail; } dest = (u8int*)dest + avail; n -= avail; tot += avail; ios_flush(s); s->bpos = s->size = 0; s->state = IOS_STATE_RD; got = 0; if(n > MOST_OF(s->maxsize)){ // doesn't fit comfortably in buffer; go direct sys_read(s->fd, dest, n, &got); tot += got; if(got == 0) s->flags |= IOS_FL_EOF; return tot; } // refill buffer if(sys_read(s->fd, s->buf, s->maxsize, &got) != 0 || got == 0){ s->flags |= IOS_FL_EOF; return tot; } s->size = got; } return tot; } // ensure at least n bytes are buffered if possible. returns # available. usize ios_readprep(ios *s, usize n) { if((s->flags & IOS_FL_WRONLY) != 0) return 0; if(s->state == IOS_STATE_WR && s->bm != IOS_BUF_MEM){ ios_flush(s); s->bpos = s->size = 0; } usize space = s->size - s->bpos; s->state = IOS_STATE_RD; if(space >= n || s->bm == IOS_BUF_MEM || s->fd == -1) return space; if(s->maxsize < s->bpos+n){ // it won't fit. grow buffer or move data back. if(n <= s->maxsize && space <= ((s->maxsize)>>2)){ if(space) memmove(s->buf, s->buf+s->bpos, space); s->size -= s->bpos; s->bpos = 0; } else { if(buf_realloc(s, s->bpos + n) == nil) return space; } } usize got; int result = sys_read(s->fd, s->buf+s->size, s->maxsize - s->size, &got); if(result != 0) return space; s->size += got; return s->size - s->bpos; } sl_nonnull(1) static void write_update_pos(ios *s) { if(sl_likely(s->bpos > s->ndirty)) s->ndirty = s->bpos; if(s->bpos > s->size) s->size = s->bpos; } usize ios_write(ios *s, const void *data, usize n) { if(sl_unlikely((s->flags & IOS_FL_RDONLY) != 0 || s->state == IOS_STATE_CLOSED || n == 0)) return 0; usize space, wrote = 0; if(sl_unlikely(s->state == IOS_STATE_NONE)) s->state = IOS_STATE_WR; if(sl_unlikely(s->state == IOS_STATE_RD)){ if((s->flags & IOS_FL_REREADABLE) == 0){ s->size = 0; s->bpos = 0; } space = s->size - s->bpos; }else{ space = s->maxsize - s->bpos; } if(sl_likely(s->bm == IOS_BUF_MEM)){ wrote = write_grow(s, data, n); }else if(sl_unlikely(s->bm == IOS_BUF_NONE)){ write_all(s->fd, data, n, &wrote); return wrote; }else if(sl_likely(n <= space)){ if(s->bm == IOS_BUF_LINE){ const char *nl; if(sl_unlikely((nl = sl_memrchr(data, '\n', n)) != nil)){ usize linesz = nl-(const char*)data+1; s->bm = IOS_BUF_BLOCK; wrote += ios_write(s, data, linesz); ios_flush(s); s->bm = IOS_BUF_LINE; n -= linesz; data = (const char*)data + linesz; } } memcpy(s->buf + s->bpos, data, n); s->bpos += n; wrote += n; }else{ s->state = IOS_STATE_WR; ios_flush(s); if(n > MOST_OF(s->maxsize)){ write_all(s->fd, data, n, &wrote); return wrote; } return ios_write(s, data, n); } write_update_pos(s); return wrote; } soffset ios_seek(ios *s, soffset pos) { if(sl_unlikely(s->state == IOS_STATE_CLOSED || pos < 0)) return -1; s->flags &= ~IOS_FL_EOF; if(sl_likely(s->bm == IOS_BUF_MEM)){ if((usize)pos >= s->size) pos = s->size; s->bpos = pos; }else{ ios_flush(s); s->bpos = s->size = 0; soffset fdpos = sys_seek(s->fd, pos, SEEK_SET); if(fdpos < 0) return -1; pos = fdpos; } s->loc.lineno = 1; s->loc.colno = 0; return pos; } soffset ios_seek_end(ios *s) { if(sl_unlikely(s->state == IOS_STATE_CLOSED)) return -1; s->flags |= IOS_FL_EOF; if(s->bm == IOS_BUF_MEM) s->bpos = s->size; else{ ios_flush(s); soffset fdpos = sys_seek(s->fd, 0, SEEK_END); if(fdpos == (soffset)-1) return fdpos; s->bpos = s->size = 0; } s->loc.lineno = 1; s->loc.colno = 0; return 0; } soffset ios_skip(ios *s, soffset offs, bool end) { if(sl_unlikely(s->state == IOS_STATE_CLOSED)) return -1; if(sl_unlikely(end && ios_seek_end(s) < 0)) return -1; s->flags &= ~IOS_FL_EOF; if(s->bm == IOS_BUF_MEM){ if(offs > 0){ if(offs <= (soffset)(s->size-s->bpos)) s->bpos += offs; else s->bpos = s->size; }else if(offs < 0){ if((soffset)s->bpos+offs >= 0) s->bpos += offs; else s->bpos = 0; } return s->bpos; } ios_flush(s); s->bpos = s->size = 0; return sys_seek(s->fd, offs, SEEK_CUR); } int ios_trunc(ios *s, soffset size) { if(sl_unlikely(s->state == IOS_STATE_CLOSED || (s->flags & IOS_FL_RDONLY) != 0 || size < 0)) return -1; if(s->bm == IOS_BUF_MEM){ if(sl_unlikely((usize)size == s->size)) return 0; if(sl_likely((usize)size < s->size)){ if(s->bpos > (usize)size) s->bpos = size; }else if(sl_unlikely(buf_realloc(s, size) == nil)) return -1; s->size = size; return 0; } return sl_likely(ios_flush(s) == 0) ? ftruncate(s->fd, size) : -1; } int ios_flush(ios *s) { if(s->ndirty == 0 || s->bm == IOS_BUF_MEM || s->buf == nil) return 0; if(sl_unlikely(s->fd == -1)) return -1; if(sl_unlikely(s->state == IOS_STATE_RD)){ if(sys_seek(s->fd, -(soffset)s->size, SEEK_CUR) == (soffset)-1){ // FIXME eh? } return -1; } usize nw, ntowrite = s->ndirty; int err = write_all(s->fd, s->buf, ntowrite, &nw); // todo: try recovering from some kinds of errors (e.g. retry) if(sl_unlikely(s->state == IOS_STATE_RD)){ if(sys_seek(s->fd, s->size - nw, SEEK_CUR) == (soffset)-1){ // FIXME eh? } return -1; }else if(s->state == IOS_STATE_WR){ if(s->bpos != nw && sys_seek(s->fd, (soffset)s->bpos - (soffset)nw, SEEK_CUR) == (soffset)-1){ // FIXME eh? } // now preserve the invariant that data to write // begins at the beginning of the buffer, and s->size refers // to how much valid file data is stored in the buffer. if(sl_unlikely(s->size > s->ndirty)){ usize delta = s->size - s->ndirty; memmove(s->buf, s->buf + s->ndirty, delta); } s->size -= s->ndirty; s->bpos = 0; } s->ndirty = 0; if(sl_unlikely(err)) return err; if(sl_unlikely(nw < ntowrite)) return -1; return 0; } void ios_close(ios *s) { if(sl_unlikely(s->state == IOS_STATE_CLOSED)) return; ios_flush(s); if(sl_unlikely(s->fd != -1 && (s->flags & IOS_FL_OWNFD) != 0)) sys_close(s->fd); s->fd = -1; s->data = sl_nil; if((s->flags & IOS_FL_OWNBUF) != 0 && s->buf != &s->local[0]) MEM_FREE(s->buf); s->buf = nil; s->size = s->maxsize = s->bpos = 0; s->state = IOS_STATE_CLOSED; } void ios_free(ios *s) { MEM_FREE(s->filename); s->filename = nil; } sl_nonnull(1) static void buf_init(ios *s, ios_buf_mode bm) { s->bm = bm; if(s->bm == IOS_BUF_MEM || s->bm == IOS_BUF_NONE){ s->buf = &s->local[0]; s->maxsize = IOS_INLSIZE; }else{ s->buf = nil; buf_realloc(s, IOS_BUFSIZE); } s->size = s->bpos = 0; } u8int * ios_takebuf(ios *s, usize *psize) { u8int *buf; ios_flush(s); if(sl_unlikely(s->buf == &s->local[0] || s->buf == nil || ((s->flags & IOS_FL_OWNBUF) == 0 && s->size == s->maxsize))){ buf = MEM_ALLOC(s->size+1); if(buf == nil) return nil; if(s->buf != nil) memcpy(buf, s->buf, s->size); }else if(sl_unlikely(s->size == s->maxsize)){ buf = MEM_REALLOC(s->buf, s->size + 1); if(sl_unlikely(buf == nil)) return nil; }else{ buf = s->buf; } buf[s->size] = '\0'; *psize = s->size + 1; /* empty io and reinitialize */ buf_init(s, s->bm); return buf; } int ios_setbuf(ios *s, u8int *buf, usize size, bool own) { ios_flush(s); usize nvalid = size < s->size ? size : s->size; if(nvalid > 0) memcpy(buf, s->buf, nvalid); if(s->bpos > nvalid){ // truncated s->bpos = nvalid; } s->size = nvalid; if((s->flags & IOS_FL_OWNBUF) != 0 && s->buf != &s->local[0]) MEM_FREE(s->buf); s->buf = buf; s->maxsize = size; if(own) s->flags |= IOS_FL_OWNBUF; else s->flags &= ~IOS_FL_OWNBUF; return 0; } int ios_bufmode(ios *s, ios_buf_mode mode) { // no fd; can only do mem-only buffering - ignore if((s->fd == -1) != (mode == IOS_BUF_MEM)) return 0; s->bm = mode; return 0; } void ios_set_readonly(ios *s) { if((s->flags & IOS_FL_RDONLY) != 0) return; ios_flush(s); s->state = IOS_STATE_NONE; s->flags |= IOS_FL_RDONLY; } sl_nonnull(1) static void ios_move(ios *s, u8int ch) { if(sl_unlikely(ch == '\n')){ s->loc.lineno++; s->loc.colno = 0; s->colnowait = 0; }else if(sl_unlikely(s->colnowait > 0)){ s->colnowait--; }else{ s->loc.colno++; if(sl_unlikely(ch & 0x80)){ if((ch & 0xe0) == 0xc0) s->colnowait = 1; else if((ch & 0xf0) == 0xe0) s->colnowait = 2; else s->colnowait = 3; } } } sl_nonnull(1, 2) static usize ios_copy_(ios *to, ios *from, usize nbytes, bool all) { // FIXME(sigrid): ios doesn't really care about errors // this needs a big fix everywhere if((to->flags & IOS_FL_RDONLY) != 0 || (from->flags & IOS_FL_WRONLY) != 0) return 0; usize total = 0, avail; if((from->flags & IOS_FL_EOF) == 0){ do{ avail = ios_readprep(from, IOS_BUFSIZE/2); if(avail == 0){ from->flags |= IOS_FL_EOF; break; } usize written, ntowrite; ntowrite = (avail <= nbytes || all) ? avail : nbytes; written = ios_write(to, from->buf+from->bpos, ntowrite); for(usize i = 0; i < written; i++){ u8int ch = from->buf[from->bpos+i]; ios_move(from, ch); ios_move(to, ch); } from->bpos += written; total += written; if(!all){ nbytes -= written; if(nbytes == 0) break; } if(written < ntowrite) break; }while((from->flags & IOS_FL_EOF) == 0); } return total; } usize ios_copy(ios *to, ios *from, usize nbytes) { return ios_copy_(to, from, nbytes, 0); } usize ios_copyall(ios *to, ios *from) { return ios_copy_(to, from, 0, true); } #define LINE_CHUNK_SIZE 160 usize ios_copyuntil(ios *to, ios *from, Rune delim) { usize total = 0, avail = from->size - from->bpos; bool first = true; if((from->flags & IOS_FL_EOF) == 0){ do{ if(avail == 0){ first = false; avail = ios_readprep(from, LINE_CHUNK_SIZE); } usize written; usize junk; u8int *pd = u8_memchr(from->buf+from->bpos, delim, avail, &junk); if(pd == nil){ written = ios_write(to, from->buf+from->bpos, avail); for(usize i = 0; i < written; i++){ u8int ch = from->buf[from->bpos+i]; ios_move(from, ch); ios_move(to, ch); } from->bpos += written; total += written; avail = 0; }else{ usize ntowrite = pd - (from->buf+from->bpos) + runelen(delim); written = ios_write(to, from->buf+from->bpos, ntowrite); for(usize i = 0; i < written; i++){ u8int ch = from->buf[from->bpos+i]; ios_move(from, ch); ios_move(to, ch); } from->bpos += written; total += written; return total; } }while((from->flags & IOS_FL_EOF) == 0 && (first || avail >= LINE_CHUNK_SIZE)); } from->flags |= IOS_FL_EOF; return total; } sl_nonnull(1) static void ios_init(ios *s) { // put all fields in a sane initial state memset(s, 0, sl_offsetof(ios, local[0])); s->bm = IOS_BUF_BLOCK; s->state = IOS_STATE_NONE; s->fd = -1; s->data = sl_nil; s->flags |= IOS_FL_OWNBUF; s->loc.lineno = 1; } /* io object initializers. we do no allocation. */ ios * ios_file(ios *s, const char *fname, ios_flags flags, usize mode) { int fd; if((flags & IOS_RDWR) == 0 || (fd = sys_open(fname, flags, mode)) < 0){ s->fd = -1; return nil; } s = ios_fd(s, fd, fname, true, true); if((flags & IOS_WRITE) == 0) s->flags |= IOS_FL_RDONLY; else if((flags & IOS_READ) == 0) s->flags |= IOS_FL_WRONLY; if(flags & IOS_APPEND){ s->flags &= ~IOS_FL_REREADABLE; ios_seek_end(s); } return s; } ios * ios_mem(ios *s, usize initsize) { ios_init(s); s->bm = IOS_BUF_MEM; s->flags |= IOS_FL_REREADABLE; s->filename = MEM_STRDUP(""); buf_realloc(s, initsize); return s; } ios * ios_pipe(ios *s, ios_flags flags, int *otherside) { int fd[2]; if(sys_pipe(fd, (flags & IOS_WRITE) != 0, (flags & IOS_READ) != 0) != 0){ *otherside = -1; return s; } s = ios_fd(s, fd[0], "", false, true); s->bm = IOS_BUF_NONE; *otherside = fd[1]; if((flags & IOS_READ) == 0) s->flags |= IOS_FL_RDONLY; else if((flags & IOS_WRITE) == 0) s->flags |= IOS_FL_WRONLY; return s; } ios * ios_static_buffer(ios *s, const u8int *buf, usize sz) { ios_mem(s, 0); ios_setbuf(s, (u8int*)buf, sz, 0); s->size = sz; s->flags |= IOS_FL_REREADABLE; ios_set_readonly(s); return s; } ios * ios_fd(ios *s, int fd, const char *name, bool isfile, bool own) { sys_fd_setup(fd); ios_init(s); s->fd = fd; if(isfile) s->flags |= IOS_FL_REREADABLE; buf_init(s, IOS_BUF_BLOCK); if(own) s->flags |= IOS_FL_OWNFD; else s->flags &= ~IOS_FL_OWNFD; s->filename = MEM_STRDUP(name); return s; } void ios_init_std(void) { ios_stdin = MEM_ALLOC(sizeof(ios)); ios_fd(ios_stdin, STDIN_FILENO, "*stdin*", false, false); ios_stdout = MEM_ALLOC(sizeof(ios)); ios_fd(ios_stdout, STDOUT_FILENO, "*stdout*", false, false); ios_stdout->bm = IOS_BUF_LINE; ios_stderr = MEM_ALLOC(sizeof(ios)); ios_fd(ios_stderr, STDERR_FILENO, "*stderr*", false, false); ios_stderr->bm = IOS_BUF_NONE; } /* higher level interface */ int ios_putc(ios *s, int c) { u8int ch = c; if(s->state == IOS_STATE_WR && s->bpos < s->maxsize && s->bm != IOS_BUF_NONE){ s->buf[s->bpos++] = ch; write_update_pos(s); if(sl_unlikely(s->bm == IOS_BUF_LINE && ch == '\n')) ios_flush(s); return 1; } return ios_write(s, &ch, 1); } int ios_getc(ios *s) { u8int ch; if(sl_likely(s->state == IOS_STATE_RD && s->bpos < s->size)) ch = s->buf[s->bpos++]; else if(sl_unlikely((s->flags & IOS_FL_EOF) != 0 || ios_read(s, &ch, 1) < 1)) return IOS_EOF; ios_move(s, ch); return ch; } usize ios_peek(ios *s, void *dest, usize n) { usize r; if((s->flags & IOS_FL_EOF) != 0 || (r = ios_readprep(s, n)) == 0) return 0; if(n > r) n = r; memcpy(dest, s->buf+s->bpos, n); return n; } int ios_peekc(ios *s) { if(sl_likely(s->bpos < s->size)) return s->buf[s->bpos]; if(sl_unlikely((s->flags & IOS_FL_EOF) != 0)) return IOS_EOF; usize n = ios_readprep(s, 1); if(sl_unlikely(n == 0)) return IOS_EOF; return s->buf[s->bpos]; } int ios_peekrune(ios *s, Rune *r) { int n = s->size - s->bpos; if(!fullrune((char*)s->buf+s->bpos, n)){ n = ios_readprep(s, UTFmax); if(!fullrune((char*)s->buf + s->bpos, n)) return n == 0 ? IOS_EOF : 0; } chartorune(r, (char*)s->buf + s->bpos); return *r == Runeerror ? 0 : 1; } int ios_getrune(ios *s, Rune *r) { usize i; char buf[UTFmax]; for(i = 0; i < sizeof(buf); i++){ u8int c; if(sl_likely(s->state == IOS_STATE_RD && s->bpos < s->size)) c = s->buf[s->bpos++]; else if(sl_unlikely((s->flags & IOS_FL_EOF) != 0 || ios_read(s, &c, 1) < 1)) return IOS_EOF; buf[i] = c; if(fullrune(buf, i+1)) break; } chartorune(r, buf); if(*r == Runeerror) return 0; if(*r == '\n') s->loc.colno = 0; else s->loc.colno += sl_wcwidth(*r); return 1; } int ios_putrune(ios *s, Rune r) { char buf[UTFmax]; return ios_write(s, buf, runetochar(buf, &r)); } void ios_purge(ios *s) { if(s->state == IOS_STATE_RD){ for(; s->bpos < s->size; s->bpos++) ios_move(s, s->buf[s->bpos]); } } int ios_vprintf(ios *s, const char *format, va_list args) { char buf[256]; char *str; int c; if(sl_unlikely(s->state == IOS_STATE_CLOSED)) return -1; /* skip allocations if no buffering needed */ if(sl_unlikely(s->bm == IOS_BUF_NONE && (s->state == IOS_STATE_NONE || s->state == IOS_STATE_WR))) return vdprintf(s->fd, format, args); if(sl_likely((c = vsnprintf(buf, sizeof(buf), format, args)) < nelem(buf))) str = buf; else if(sl_unlikely(s->state == IOS_STATE_NONE || s->state == IOS_STATE_WR)){ /* doesn't fit? prefer no allocations */ ios_flush(s); return vdprintf(s->fd, format, args); }else{ str = MEM_ALLOC(c+1); c = vsnprintf(str, c+1, format, args); } if(sl_likely(c > 0)) c = ios_write(s, str, c); if(sl_unlikely(str != buf)) MEM_FREE(str); return c; } int ios_printf(ios *s, const char *format, ...) { va_list args; int c; va_start(args, format); c = ios_vprintf(s, format, args); va_end(args); return c; }