ref: 3512e8bf86d1b5387bcd7543e85c50e147db4bf1
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;
if(s->state == bst_closed)
return 0;
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);
}
// ensure at least n bytes are buffered if possible. returns # available.
static 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 || s->state == bst_closed || 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)
{
if(s->state == bst_closed)
return 0;
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)
{
if(s->state == bst_closed)
return 0;
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(s->state == bst_closed)
return 0;
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->state == bst_closed)
return 0;
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->state == bst_closed)
return 0;
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;
}
// FIXME
return 0;
}
bool
ios_eof(ios_t *s)
{
if(s->state == bst_closed)
return true;
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)
{
if(s->state == bst_closed)
return;
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;
s->state = bst_closed;
}
void
ios_free(ios_t *s)
{
if(s->loc.filename != emptystr){
MEM_FREE(s->loc.filename);
s->loc.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->loc.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->loc.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->loc.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->loc.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->loc.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->loc.filename = MEM_STRDUP("*stderr*");
}
/* higher level interface */
int
ios_putc(ios_t *s, int c)
{
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);
}
static void
ios_loc(ios_t *s, uint8_t ch)
{
if(ch == '\n'){
s->loc.lineno++;
s->loc.colno = 0;
s->colnowait = 0;
}else if(s->colnowait > 0){
s->colnowait--;
}else{
s->loc.colno++;
if(ch & 0x80){
if((ch & 0xe0) == 0xc0)
s->colnowait = 1;
else if((ch & 0xf0) == 0xe0)
s->colnowait = 2;
else
s->colnowait = 3;
}
}
}
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;
ios_loc(s, ch);
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->loc.colno = 0;
else
s->loc.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){
for(; s->bpos < s->size; s->bpos++)
ios_loc(s, s->buf[s->bpos]);
}
}
int
ios_vprintf(ios_t *s, const char *format, va_list args)
{
char buf[256];
char *str;
int c;
if(s->state == bst_closed)
return -1;
/* 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;
}