ref: 497eb997f7c9d494b7ecd26c52dcac3361ef93e1
dir: /llt/ios.c/
#include "llt.h" #define MOST_OF(x) ((x) - ((x)>>4)) ios_t *ios_stdin = nil; ios_t *ios_stdout = nil; ios_t *ios_stderr = nil; /* OS-level primitive wrappers */ void * llt_memrchr(const void *s, int c, size_t n) { const uint8_t *src = (const uint8_t*)s + n; uint8_t uc = c; while(--src >= (uint8_t*)s) if(*src == uc) return (void *)src; return nil; } #if !defined(__plan9__) static int _enonfatal(int err) { return err == EAGAIN || err == EINPROGRESS || err == EINTR || err == EWOULDBLOCK; } #define SLEEP_TIME 5//ms #endif // return error code, #bytes read in *nread // these wrappers retry operations until success or a fatal error static int _os_read(long fd, char *buf, size_t n, size_t *nread) { ssize_t r; while(1){ r = read((int)fd, buf, n); if(r > -1){ *nread = (size_t)r; break; } #if defined(__plan9__) return r; #else if(!_enonfatal(errno)){ *nread = 0; return errno; } sleep_ms(SLEEP_TIME); #endif } return 0; } static int _os_read_all(long fd, char *buf, size_t n, size_t *nread) { size_t got; *nread = 0; while(n > 0){ int err = _os_read(fd, buf, n, &got); n -= got; *nread += got; buf += got; if(err || got == 0) return err; } return 0; } static int _os_write(long fd, const void *buf, size_t n, size_t *nwritten) { ssize_t r; while(1){ r = write((int)fd, buf, n); if(r > -1){ *nwritten = (size_t)r; break; } #if defined(__plan9__) return r; #else if(!_enonfatal(errno)){ *nwritten = 0; return errno; } sleep_ms(SLEEP_TIME); #endif } return 0; } static int _os_write_all(long fd, const char *buf, size_t n, size_t *nwritten) { size_t wrote; *nwritten = 0; while(n > 0){ int err = _os_write(fd, buf, n, &wrote); n -= wrote; *nwritten += wrote; buf += wrote; if(err) return err; } return 0; } /* internal utility functions */ static char * _buf_realloc(ios_t *s, size_t sz) { char *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->ownbuf = 1; return s->buf; } if(sz <= s->maxsize) return s->buf; if(s->ownbuf && 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 = LLT_REALLOC(s->buf, sz); if(temp == nil) return nil; }else{ temp = LLT_ALLOC(sz); if(temp == nil) return nil; s->ownbuf = 1; if(s->size > 0) memmove(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. static size_t _write_grow(ios_t *s, const char *data, size_t n) { size_t amt; size_t newsize; if(n == 0) return 0; if(s->bpos + n > s->size){ if(s->bpos + n > s->maxsize){ /* TODO: here you might want to add a mechanism for limiting the growth of the stream. */ 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){ memmove(&s->buf[s->bpos], data, amt); } s->bpos += amt; s->size = s->maxsize; return amt; } } s->size = s->bpos + n; } memmove(s->buf + s->bpos, data, n); s->bpos += n; return n; } /* interface functions, low level */ static size_t _ios_read(ios_t *s, char *dest, size_t n, int all) { size_t tot = 0; size_t got, avail; while(n > 0){ avail = s->size - s->bpos; if(avail > 0){ size_t ncopy = avail >= n ? n : avail; memmove(dest, s->buf + s->bpos, ncopy); s->bpos += ncopy; if(ncopy >= n){ s->state = bst_rd; return tot+ncopy; } } if(s->bm == bm_mem || s->fd == -1){ // can't get any more data s->state = bst_rd; if(avail == 0) s->_eof = 1; return avail; } dest += avail; n -= avail; tot += avail; ios_flush(s); s->bpos = s->size = 0; s->state = bst_rd; s->fpos = -1; if(n > MOST_OF(s->maxsize)){ // doesn't fit comfortably in buffer; go direct if(all) _os_read_all(s->fd, dest, n, &got); else _os_read(s->fd, dest, n, &got); tot += got; if(got == 0) s->_eof = 1; return tot; }else{ // refill buffer if(_os_read(s->fd, s->buf, s->maxsize, &got)){ s->_eof = 1; return tot; } if(got == 0){ s->_eof = 1; return tot; } s->size = got; } } return tot; } size_t ios_read(ios_t *s, char *dest, size_t n) { return _ios_read(s, dest, n, 0); } size_t ios_readall(ios_t *s, char *dest, size_t n) { return _ios_read(s, dest, n, 1); } size_t ios_readprep(ios_t *s, size_t n) { if(s->state == bst_wr && s->bm != bm_mem){ ios_flush(s); s->bpos = s->size = 0; } size_t space = s->size - s->bpos; s->state = bst_rd; if(space >= n || s->bm == bm_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; } } size_t got; int result = _os_read(s->fd, s->buf+s->size, s->maxsize - s->size, &got); if(result) return space; s->size += got; return s->size - s->bpos; } static void _write_update_pos(ios_t *s) { if(s->bpos > s->ndirty) s->ndirty = s->bpos; if(s->bpos > s->size) s->size = s->bpos; } size_t ios_write(ios_t *s, const char *data, size_t n) { if(s->readonly) return 0; if(n == 0) return 0; size_t space; size_t wrote = 0; if(s->state == bst_none) s->state = bst_wr; if(s->state == bst_rd){ if(!s->rereadable){ s->size = 0; s->bpos = 0; } space = s->size - s->bpos; }else{ space = s->maxsize - s->bpos; } if(s->bm == bm_mem){ wrote = _write_grow(s, data, n); }else if(s->bm == bm_none){ s->fpos = -1; _os_write_all(s->fd, data, n, &wrote); return wrote; }else if(n <= space){ if(s->bm == bm_line){ char *nl; if((nl = llt_memrchr(data, '\n', n)) != nil){ size_t linesz = nl-data+1; s->bm = bm_block; wrote += ios_write(s, data, linesz); ios_flush(s); s->bm = bm_line; n -= linesz; data += linesz; } } memmove(s->buf + s->bpos, data, n); s->bpos += n; wrote += n; }else{ s->state = bst_wr; ios_flush(s); if(n > MOST_OF(s->maxsize)){ _os_write_all(s->fd, data, n, &wrote); return wrote; } return ios_write(s, data, n); } _write_update_pos(s); return wrote; } off_t ios_seek(ios_t *s, off_t pos) { s->_eof = 0; if(s->bm == bm_mem){ if((size_t)pos > s->size) return -1; s->bpos = pos; }else{ ios_flush(s); off_t fdpos = lseek(s->fd, pos, SEEK_SET); if(fdpos == (off_t)-1) return fdpos; s->bpos = s->size = 0; } return 0; } off_t ios_seek_end(ios_t *s) { s->_eof = 1; if(s->bm == bm_mem){ s->bpos = s->size; }else{ ios_flush(s); off_t fdpos = lseek(s->fd, 0, SEEK_END); if(fdpos == (off_t)-1) return fdpos; s->bpos = s->size = 0; } return 0; } off_t ios_skip(ios_t *s, off_t offs) { if(offs != 0){ if(offs > 0){ if(offs <= (off_t)(s->size-s->bpos)){ s->bpos += offs; return 0; }else if(s->bm == bm_mem){ // TODO: maybe grow buffer return -1; } }else if(offs < 0){ if(-offs <= (off_t)s->bpos){ s->bpos += offs; s->_eof = 0; return 0; }else if(s->bm == bm_mem){ return -1; } } ios_flush(s); if(s->state == bst_wr) offs += s->bpos; else if(s->state == bst_rd) offs -= s->size - s->bpos; off_t fdpos = lseek(s->fd, offs, SEEK_CUR); if(fdpos == (off_t)-1) return fdpos; s->bpos = s->size = 0; s->_eof = 0; } return 0; } off_t ios_pos(ios_t *s) { if(s->bm == bm_mem) return (off_t)s->bpos; off_t fdpos = s->fpos; if(fdpos == (off_t)-1){ fdpos = lseek(s->fd, 0, SEEK_CUR); if(fdpos == (off_t)-1) return fdpos; s->fpos = fdpos; } if(s->state == bst_wr) fdpos += s->bpos; else if(s->state == bst_rd) fdpos -= s->size - s->bpos; return fdpos; } size_t ios_trunc(ios_t *s, size_t size) { if(s->bm == bm_mem){ if(size == s->size) return s->size; if(size < s->size){ if(s->bpos > size) s->bpos = size; }else if(_buf_realloc(s, size) == nil) return s->size; s->size = size; return size; } //todo return 0; } int ios_eof(ios_t *s) { if(s->bm == bm_mem) return s->_eof; return s->fd == -1 || s->_eof; } int ios_flush(ios_t *s) { if(s->ndirty == 0 || s->bm == bm_mem || s->buf == nil) return 0; if(s->fd == -1) return -1; if(s->state == bst_rd){ if(lseek(s->fd, -(off_t)s->size, SEEK_CUR) == (off_t)-1){ // FIXME eh? } } size_t nw, ntowrite = s->ndirty; s->fpos = -1; int err = _os_write_all(s->fd, s->buf, ntowrite, &nw); // todo: try recovering from some kinds of errors (e.g. retry) if(s->state == bst_rd){ if(lseek(s->fd, s->size - nw, SEEK_CUR) == (off_t)-1){ // FIXME eh? } }else if(s->state == bst_wr){ if(s->bpos != nw && lseek(s->fd, (off_t)s->bpos - (off_t)nw, SEEK_CUR) == (off_t)-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(s->size > s->ndirty){ size_t delta = s->size - s->ndirty; memmove(s->buf, s->buf + s->ndirty, delta); } s->size -= s->ndirty; s->bpos = 0; } s->ndirty = 0; if(err) return err; if(nw < ntowrite) return -1; return 0; } void ios_close(ios_t *s) { ios_flush(s); if(s->fd != -1 && s->ownfd) close(s->fd); s->fd = -1; if(s->buf != nil && s->ownbuf && s->buf != &s->local[0]) LLT_FREE(s->buf); s->buf = nil; s->size = s->maxsize = s->bpos = 0; } static void _buf_init(ios_t *s, bufmode_t bm) { s->bm = bm; if(s->bm == bm_mem || s->bm == bm_none){ s->buf = &s->local[0]; s->maxsize = IOS_INLSIZE; }else{ s->buf = nil; _buf_realloc(s, IOS_BUFSIZE); } s->size = s->bpos = 0; } char * ios_takebuf(ios_t *s, size_t *psize) { char *buf; ios_flush(s); if(s->buf == &s->local[0] || s->buf == nil || (!s->ownbuf && s->size == s->maxsize)){ buf = LLT_ALLOC(s->size+1); if(buf == nil) return nil; if(s->size) memmove(buf, s->buf, s->size); }else if(s->size == s->maxsize){ buf = LLT_REALLOC(s->buf, s->size + 1); if(buf == nil) return nil; }else{ buf = s->buf; } buf[s->size] = '\0'; *psize = s->size + 1; /* empty stream and reinitialize */ _buf_init(s, s->bm); return buf; } int ios_setbuf(ios_t *s, char *buf, size_t size, int own) { ios_flush(s); size_t nvalid; nvalid = size < s->size ? size : s->size; if(nvalid > 0) memmove(buf, s->buf, nvalid); if(s->bpos > nvalid){ // truncated s->bpos = nvalid; } s->size = nvalid; if(s->buf != nil && s->ownbuf && s->buf != &s->local[0]) LLT_FREE(s->buf); s->buf = buf; s->maxsize = size; s->ownbuf = own; return 0; } int ios_bufmode(ios_t *s, bufmode_t mode) { // no fd; can only do mem-only buffering if(s->fd == -1 && mode != bm_mem) return -1; s->bm = mode; return 0; } void ios_set_readonly(ios_t *s) { if(s->readonly) return; ios_flush(s); s->state = bst_none; s->readonly = 1; } static size_t ios_copy_(ios_t *to, ios_t *from, size_t nbytes, int all) { size_t total = 0, avail; if(!ios_eof(from)){ do{ avail = ios_readprep(from, IOS_BUFSIZE/2); if(avail == 0){ from->_eof = 1; break; } size_t written, ntowrite; ntowrite = (avail <= nbytes || all) ? avail : nbytes; written = ios_write(to, from->buf+from->bpos, ntowrite); // TODO: should this be +=written instead? from->bpos += ntowrite; total += written; if(!all){ nbytes -= written; if(nbytes == 0) break; } if(written < ntowrite) break; }while(!ios_eof(from)); } return total; } size_t ios_copy(ios_t *to, ios_t *from, size_t nbytes) { return ios_copy_(to, from, nbytes, 0); } size_t ios_copyall(ios_t *to, ios_t *from) { return ios_copy_(to, from, 0, 1); } #define LINE_CHUNK_SIZE 160 size_t ios_copyuntil(ios_t *to, ios_t *from, char delim) { size_t total = 0, avail = from->size - from->bpos; int first = 1; if(!ios_eof(from)){ do{ if(avail == 0){ first = 0; avail = ios_readprep(from, LINE_CHUNK_SIZE); } size_t written; char *pd = memchr(from->buf+from->bpos, delim, avail); if(pd == nil){ written = ios_write(to, from->buf+from->bpos, avail); from->bpos += avail; total += written; avail = 0; }else{ size_t ntowrite = pd - (from->buf+from->bpos) + 1; written = ios_write(to, from->buf+from->bpos, ntowrite); from->bpos += ntowrite; total += written; return total; } }while(!ios_eof(from) && (first || avail >= LINE_CHUNK_SIZE)); } from->_eof = 1; return total; } static void _ios_init(ios_t *s) { // put all fields in a sane initial state memset(s, 0, sizeof(*s)); s->bm = bm_block; s->state = bst_none; s->fpos = -1; s->lineno = 1; s->fd = -1; s->ownbuf = 1; } /* stream object initializers. we do no allocation. */ ios_t * ios_file(ios_t *s, char *fname, int rd, int wr, int creat, int trunc) { int fd; if(!(rd || wr)) // must specify read and/or write goto open_file_err; int flags = wr ? (rd ? O_RDWR : O_WRONLY) : O_RDONLY; if(trunc) flags |= O_TRUNC; #if defined(__plan9__) fd = creat ? create(fname, flags, 0644) : open(fname, flags); #else if(creat) flags |= O_CREAT; fd = open(fname, flags, 0644); #endif s = ios_fd(s, fd, 1, 1); if(fd < 0) goto open_file_err; if(!wr) s->readonly = 1; return s; open_file_err: s->fd = -1; return nil; } ios_t * ios_mem(ios_t *s, size_t initsize) { _ios_init(s); s->bm = bm_mem; _buf_realloc(s, initsize); return s; } ios_t * ios_str(ios_t *s, char *str) { size_t n = strlen(str); if(ios_mem(s, n+1) == nil) return nil; ios_write(s, str, n+1); ios_seek(s, 0); return s; } ios_t * ios_static_buffer(ios_t *s, const char *buf, size_t sz) { ios_mem(s, 0); ios_setbuf(s, (char*)buf, sz, 0); s->size = sz; ios_set_readonly(s); return s; } ios_t * ios_fd(ios_t *s, long fd, int isfile, int own) { _ios_init(s); s->fd = fd; if(isfile) s->rereadable = 1; _buf_init(s, bm_block); s->ownfd = own; if(fd == STDERR_FILENO) s->bm = bm_none; return s; } void ios_init_stdstreams(void) { ios_stdin = LLT_ALLOC(sizeof(ios_t)); ios_fd(ios_stdin, STDIN_FILENO, 0, 0); ios_stdout = LLT_ALLOC(sizeof(ios_t)); ios_fd(ios_stdout, STDOUT_FILENO, 0, 0); ios_stdout->bm = bm_line; ios_stderr = LLT_ALLOC(sizeof(ios_t)); ios_fd(ios_stderr, STDERR_FILENO, 0, 0); ios_stderr->bm = bm_none; } /* higher level interface */ int ios_putc(int c, ios_t *s) { char ch = c; if(s->state == bst_wr && s->bpos < s->maxsize && s->bm != bm_none){ s->buf[s->bpos++] = ch; _write_update_pos(s); if(s->bm == bm_line && ch == '\n') ios_flush(s); return 1; } return ios_write(s, &ch, 1); } int ios_getc(ios_t *s) { char ch; if(s->state == bst_rd && s->bpos < s->size) ch = s->buf[s->bpos++]; else if(s->_eof) return IOS_EOF; else if(ios_read(s, &ch, 1) < 1) return IOS_EOF; if(ch == '\n') s->lineno++; return (uint8_t)ch; } int ios_peekc(ios_t *s) { if(s->bpos < s->size) return (uint8_t)s->buf[s->bpos]; if(s->_eof) return IOS_EOF; size_t n = ios_readprep(s, 1); if(n == 0) return IOS_EOF; return (uint8_t)s->buf[s->bpos]; } int ios_ungetc(int c, ios_t *s) { if(s->state == bst_wr) return IOS_EOF; if(c == '\n') s->lineno--; if(s->bpos > 0){ s->bpos--; if(s->buf[s->bpos] != (char)c) s->buf[s->bpos] = (char)c; s->_eof = 0; return c; } if(s->size == s->maxsize && _buf_realloc(s, s->maxsize*2) == nil) return IOS_EOF; memmove(s->buf + 1, s->buf, s->size); s->buf[0] = (char)c; s->size++; s->_eof = 0; return c; } int ios_getutf8(ios_t *s, uint32_t *pwc) { int c; size_t sz; char c0; char buf[8]; c = ios_peekc(s); if(c == IOS_EOF){ s->_eof = 1; return IOS_EOF; } c0 = (char)c; if((uint8_t)c0 < 0x80){ ios_getc(s); *pwc = (uint32_t)(uint8_t)c0; return 1; } sz = u8_seqlen(&c0)-1; if(!isutf(c0) || sz > 3) return 0; if(ios_readprep(s, sz) < sz){ // NOTE: this returns EOF even though some bytes are available // so we do not set s->_eof on this code path return IOS_EOF; } if(u8_isvalid(&s->buf[s->bpos], sz+1)){ size_t i = s->bpos; *pwc = u8_nextchar(s->buf, &i); ios_read(s, buf, sz+1); return 1; } return 0; } int ios_peekutf8(ios_t *s, uint32_t *pwc) { int c; size_t sz; char c0; c = ios_peekc(s); if(c == IOS_EOF) return IOS_EOF; c0 = (char)c; if((uint8_t)c0 < 0x80){ *pwc = (uint32_t)(uint8_t)c0; return 1; } sz = u8_seqlen(&c0)-1; if(!isutf(c0) || sz > 3) return 0; if(ios_readprep(s, sz) < sz) return IOS_EOF; if(u8_isvalid(&s->buf[s->bpos], sz+1)){ size_t i = s->bpos; *pwc = u8_nextchar(s->buf, &i); return 1; } return 0; } int ios_pututf8(ios_t *s, uint32_t wc) { char buf[8]; if(wc < 0x80) return ios_putc((int)wc, s); size_t n = u8_toutf8(buf, 8, &wc, 1); return ios_write(s, buf, n); } void ios_purge(ios_t *s) { if(s->state == bst_rd) s->bpos = s->size; } char * ios_readline(ios_t *s) { ios_t dest; ios_mem(&dest, 0); ios_copyuntil(&dest, s, '\n'); size_t n; return ios_takebuf(&dest, &n); } int ios_vprintf(ios_t *s, const char *format, va_list args) { char *str; int c; #if defined(__plan9__) str = vsmprint(format, args); if((c = strlen(str)) >= 0) ios_write(s, str, c); free(str); #else va_list al; va_copy(al, args); if(s->state == bst_wr && s->bpos < s->maxsize && s->bm != bm_none){ int avail = s->maxsize - s->bpos; char *start = s->buf + s->bpos; c = vsnprintf(start, avail, format, args); if(c < 0){ va_end(al); return c; } if(c < avail){ s->bpos += (size_t)c; _write_update_pos(s); // TODO: only works right if newline is at end if(s->bm == bm_line && llt_memrchr(start, '\n', (size_t)c)) ios_flush(s); va_end(al); return c; } } c = vasprintf(&str, format, al); if(c >= 0){ ios_write(s, str, c); LLT_FREE(str); } va_end(al); #endif return c; } int ios_printf(ios_t *s, const char *format, ...) { va_list args; int c; va_start(args, format); c = ios_vprintf(s, format, args); va_end(args); return c; }