ref: 22ae0e366f8678ac2df9104ba37c2e2ab9c0190d
dir: /ios.c/
#include "flisp.h" #include "timefuncs.h" #define MOST_OF(x) ((x) - ((x)>>4)) static char emptystr[] = ""; 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 == 0 || 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(int fd, uint8_t *buf, size_t n, size_t *nread) { ssize_t r; while(1){ #if !defined(__plan9__) errno = 0; #endif r = read(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(int fd, uint8_t *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(int fd, const void *buf, size_t n, size_t *nwritten) { ssize_t r; while(1){ #if !defined(__plan9__) errno = 0; #endif r = write(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(int fd, const uint8_t *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 uint8_t * _buf_realloc(ios_t *s, size_t sz) { uint8_t *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 = MEM_REALLOC(s->buf, sz); if(temp == nil) return nil; }else{ temp = MEM_ALLOC(sz); if(temp == nil) return nil; s->ownbuf = 1; if(s->size > 0) 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. static size_t _write_grow(ios_t *s, const void *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) 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 */ static size_t _ios_read(ios_t *s, uint8_t *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; memcpy(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, void *dest, size_t n) { return _ios_read(s, dest, n, 0); } 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 void *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-(char*)data+1; s->bm = bm_block; wrote += ios_write(s, data, linesz); ios_flush(s); s->bm = bm_line; n -= linesz; data = (char*)data + linesz; } } memcpy(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]) MEM_FREE(s->buf); s->buf = nil; s->size = s->maxsize = s->bpos = 0; if(s->filename != emptystr){ MEM_FREE(s->filename); s->filename = emptystr; } } 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; } uint8_t * ios_takebuf(ios_t *s, size_t *psize) { uint8_t *buf; ios_flush(s); if(s->buf == &s->local[0] || s->buf == nil || (!s->ownbuf && s->size == s->maxsize)){ buf = MEM_ALLOC(s->size+1); if(buf == nil) return nil; if(s->size) memcpy(buf, s->buf, s->size); }else if(s->size == s->maxsize){ buf = MEM_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, uint8_t *buf, size_t size, int own) { ios_flush(s); size_t nvalid; 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->buf != nil && s->ownbuf && s->buf != &s->local[0]) MEM_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, uint8_t 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; uint8_t *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->fd = -1; s->ownbuf = 1; s->lineno = 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; s->filename = MEM_STRDUP(fname); 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; s->filename = emptystr; _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 uint8_t *buf, size_t sz) { ios_mem(s, 0); ios_setbuf(s, (uint8_t*)buf, sz, 0); s->size = sz; ios_set_readonly(s); return s; } ios_t * ios_fd(ios_t *s, int 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 = MEM_ALLOC(sizeof(ios_t)); ios_fd(ios_stdin, STDIN_FILENO, 0, 0); ios_stdin->filename = MEM_STRDUP("*stdin*"); ios_stdout = MEM_ALLOC(sizeof(ios_t)); ios_fd(ios_stdout, STDOUT_FILENO, 0, 0); ios_stdout->bm = bm_line; ios_stdout->filename = MEM_STRDUP("*stdout*"); ios_stderr = MEM_ALLOC(sizeof(ios_t)); ios_fd(ios_stderr, STDERR_FILENO, 0, 0); ios_stderr->bm = bm_none; ios_stderr->filename = MEM_STRDUP("*stderr*"); } /* 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) { uint8_t ch; if(s->state == bst_rd && s->bpos < s->size) ch = s->buf[s->bpos++]; else if(s->_eof || ios_read(s, &ch, 1) < 1) return IOS_EOF; if(ch == '\n'){ s->lineno++; s->colno = 0; s->colnowait = 0; }else if(s->colnowait > 0){ s->colnowait--; }else{ s->colno++; if(ch & 0x80){ if((ch & 0xe0) == 0xc0) s->colnowait = 1; else if((ch & 0xf0) == 0xe0) s->colnowait = 2; else s->colnowait = 3; } } return 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_wait(ios_t *s, double ws) { if(s->bpos < s->size) return 1; if(s->_eof) return IOS_EOF; #if defined(__plan9__) USED(ws); return 1; // FIXME(sigrid): wait for input, but not too much #else struct timeval t, *pt = nil; if(ws >= 0){ t.tv_sec = ws; t.tv_usec = (long)((ws - t.tv_sec) * 1000000.0) % 1000000; pt = &t; } fd_set f; FD_ZERO(&f); FD_SET(s->fd, &f); int r; while((r = select(s->fd+1, &f, nil, nil, pt)) == EINTR); return r && FD_ISSET(s->fd, &f); #endif } int ios_getutf8(ios_t *s, Rune *r) { int c; size_t i; char buf[UTFmax]; for(i = 0; i < sizeof(buf); i++){ if((c = ios_getc(s)) == IOS_EOF){ s->_eof = 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->colno = 0; else s->colno++; return 1; } int ios_pututf8(ios_t *s, Rune r) { char buf[UTFmax]; return ios_write(s, buf, runetochar(buf, &r)); } void ios_purge(ios_t *s) { if(s->state == bst_rd) s->bpos = s->size; } int ios_vprintf(ios_t *s, const char *format, va_list args) { char buf[256]; char *str; int c; /* skip allocations if no buffering needed */ if(s->bm == bm_none && (s->state == bst_none || s->state == bst_wr)) return vdprintf(s->fd, format, args); if((c = vsnprintf(buf, sizeof(buf), format, args)) < nelem(buf)) str = buf; else if(s->state == bst_none || s->state == bst_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, sizeof(c+1), format, args); } if(c > 0) c = ios_write(s, str, c); if(str != buf) MEM_FREE(str); 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; }