/* (C) Copyright 2000, 2001, 2002, 2003, 2004, 2005 Stijn van Dongen * (C) Copyright 2006, 2007 Stijn van Dongen * * This file is part of tingea. You can redistribute and/or modify tingea * under the terms of the GNU General Public License; either version 3 of the * License or (at your option) any later version. You should have received a * copy of the GPL along with tingea, in the file COPYING. */ /* TODO * very few routines should be allowed to touch buffer. * create/free routines, and step and stepBack. * currently many routines ignore the buffer (but warn * when doing so). */ #include #include #include #include #include #include #include #include #include "io.h" #include "types.h" #include "ting.h" #include "ding.h" #include "err.h" #include "alloc.h" #include "compile.h" #include "getpagesize.h" #define buffaro(xf) (xf->buffer_consumed < xf->buffer->len) static void buffer_empty ( mcxIO* xf ) { mcxTingEmpty(xf->buffer, getpagesize()) ; xf->buffer_consumed = 0 ; } static void buffer_spout ( mcxIO* xf , const char* me ) { mcxErr ( "mcxIO" , "warning: reader %s in file <%s> discards buffered input <%.*s>" , me , xf->fn->str , (int) (xf->buffer->len - xf->buffer_consumed) , xf->buffer->str+xf->buffer_consumed ) ; buffer_empty(xf) ; } int begets_stdio ( const char* name , const char* mode ) { if ( ( strstr(mode, "r") && !strcmp(name, "-") ) || ( (strstr(mode, "w") || strstr(mode, "a")) && !strcmp(name, "-") ) || (!strcmp(name, "stderr")) ) return 1 ; return 0 ; } static int mcxIOwarnOpenfp ( mcxIO* xf , const char* who ) { if (xf->fp && !xf->stdio) { mcxIOerr(xf, who, "has open file pointer") ; return 1 ; } return 0 ; } mcxstatus mcxIOclose ( mcxIO* xf ) { fflush(xf->fp) ; if (xf->fp && !xf->stdio) { fclose(xf->fp) ; xf->fp = NULL ; } else if (xf->fp && xf->stdio) { int fe = ferror(xf->fp) /* fixme why not in branch above? */ ; if (fe) mcxErr("mcxIOclose", "error [%d] for [%s] stdio", fe, xf->mode) ; if (xf->ateof || feof(xf->fp)) clearerr(xf->fp) ; } return mcxIOreset(xf) ; } /* note: does not touch all members, notably * usr * fn * mode * fp */ mcxstatus mcxIOreset ( mcxIO* xf ) { xf->lc = 0 ; xf->lo = 0 ; xf->lo_ = 0 ; xf->bc = 0 ; xf->ateof = 0 /* regardless of read/write */ ; buffer_empty(xf) /* xf->fp not touched; promote user care */ ; if (xf->usr && xf->usr_reset) return xf->usr_reset(xf->usr) ; return STATUS_OK ; } void mcxIOerr ( mcxIO* xf , const char *complainer , const char *complaint ) { if (!xf) return ; mcxErr ( complainer , "%s stream <%s> %s" , xf->mode , xf->fn->str , complaint ) ; } mcxIO* mcxIOnew ( const char* str , const char* mode ) { if (!str || !mode) { mcxErr("mcxIOnew PBD", "void string or mode argument") ; return NULL ; } return mcxIOrenew(NULL, str, mode) ; } /* fixme: the code below is very muddy. * The ->stdio decision (for new streams) might best be made * at open time? */ mcxIO* mcxIOrenew ( mcxIO* xf , const char* name , const char* mode ) { mcxbool twas_stdio = xf && xf->stdio ; if ( mode && !strstr(mode, "w") && !strstr(mode, "r") && !strstr(mode, "a") ) { mcxErr ("mcxIOrenew PBD", "unsupported open mode <%s>", mode) ; return NULL ; } if (!xf) { if (!name || !mode) { mcxErr("mcxIOrenew PBD", "too few arguments") ; return NULL ; } if (!(xf = (mcxIO*) mcxAlloc(sizeof(mcxIO), RETURN_ON_FAIL))) return NULL ; if (!(xf->fn = mcxTingEmpty(NULL, 20))) { mcxFree(xf) ; return NULL ; } ; if (!(xf->buffer = mcxTingEmpty(NULL, getpagesize()))) { mcxFree(xf) ; return NULL ; } xf->fp = NULL ; xf->mode = NULL ; xf->usr = NULL ; xf->usr_reset = NULL ; xf->buffer_consumed = 0 ; } else if (xf->stdio) NOTHING ; else if (mcxIOwarnOpenfp(xf, "mcxIOrenew")) mcxIOclose(xf) ; mcxIOreset(xf) ; if (name && !mcxTingWrite(xf->fn, name)) return NULL /* fixme; shd clean up */ ; if (mode) { if (xf->mode) mcxFree(xf->mode) ; xf->mode = mcxStrDup(mode) ; } xf->stdio = begets_stdio(xf->fn->str, xf->mode) /* fixme: no longer necessary? */ ; if (twas_stdio && !xf->stdio) xf->fp = NULL ; return xf ; } mcxstatus mcxIOopen ( mcxIO* xf , mcxOnFail ON_FAIL ) { const char* fname = xf->fn->str ; if (!xf) { mcxErr("mcxIOnew PBD", "received void object") ; if (ON_FAIL == RETURN_ON_FAIL) return STATUS_FAIL ; exit(1) ; } if (mcxIOwarnOpenfp(xf, "mcxIOopen PBD")) return STATUS_OK ; if (!strcmp(fname, "-")) { if (strchr(xf->mode, 'r')) xf->fp = stdin ; else if (strchr(xf->mode, 'w') || strchr(xf->mode, 'a')) xf->fp = stdout ; } else if ( !strcmp(fname, "stderr") && (strchr(xf->mode, 'w') || strchr(xf->mode, 'a')) ) xf->fp = stderr ; else if ((xf->fp = fopen(fname, xf->mode)) == NULL) { if (ON_FAIL == RETURN_ON_FAIL) return STATUS_FAIL ; mcxIOerr(xf, "mcxIOopen", "can not be opened") ; mcxExit(1) ; } return STATUS_OK ; } mcxstatus mcxIOtestOpen ( mcxIO* xf , mcxOnFail ON_FAIL ) { if (!xf->fp && mcxIOopen(xf, ON_FAIL) != STATUS_OK) { mcxErr ("mcxIO", "cannot open file <%s> in mode %s", xf->fn->str, xf->mode) ; return STATUS_FAIL ; } return STATUS_OK ; } void mcxIOrelease ( mcxIO* xf ) { if (xf) { mcxIOclose(xf) ; if (xf->fn) mcxTingFree(&(xf->fn)) ; if (xf->mode) mcxFree(xf->mode) ; } } mcxstatus mcxIOappendName ( mcxIO* xf , const char* suffix ) { if (xf->fp && !xf->stdio) mcxErr ( "mcxIOappendName PBD" , "stream open while request for name change from <%s> to <%s>" , xf->fn->str , suffix ) ; else if (!mcxTingAppend(xf->fn, suffix)) return STATUS_FAIL ; xf->stdio = begets_stdio(xf->fn->str, "-") ; return STATUS_OK ; } mcxstatus mcxIOnewName ( mcxIO* xf , const char* newname ) { if (!mcxTingEmpty(xf->fn, 0)) return STATUS_FAIL ; return mcxIOappendName(xf, newname) ; } int mcxIOstepback ( int c , mcxIO* xf ) { if (c == EOF) return EOF ; else { if (buffaro(xf) && xf->buffer_consumed > 0) c = xf->buffer->str[--xf->buffer_consumed] ; else if (ungetc(c, xf->fp) == EOF) { mcxErr ( "mcxIOstepback" , "failed to push back <%d> on stream <%s>\n" , c , xf->fn->str ) ; return EOF ; } xf->bc-- ; if (c == '\n') xf->lc-- , xf->lo = xf->lo_ , xf->lo_ = 0 ; else xf->lo-- ; } return c ; } int mcxIOstep ( mcxIO* xf ) { int c #if 0 ;if (xf->buffer) fprintf(stderr, "buffer [%s]\n", xf->buffer->str) ;else fprintf(stderr, "nobuffer\n") #endif ; if (xf->ateof) c = EOF ; else if (buffaro(xf)) { c = xf->buffer->str[xf->buffer_consumed++] ; if (!buffaro(xf)) buffer_empty(xf) ; } else c = fgetc(xf->fp) ; switch(c) { case '\n' : xf->lc++ ; xf->bc++ ; xf->lo_ = xf->lo ; xf->lo = 0 ; break ; case EOF : xf->ateof = 1 ; break ; default : xf->bc++ ; xf->lo++ ; break ; } return c ; } /* fixme todo * * look at bc; substract it from sz (we might have read a part already). * * support growing files. and look at other items in the grep source code. */ mcxstatus mcxIOreadFile ( mcxIO *xf , mcxTing *filetxt ) { struct stat mystat ; size_t sz = 4096 ; ssize_t r ; const char* me = "mcxIOreadFile" ; mcxTingEmpty(filetxt, 0) ; if (buffaro(xf)) buffer_spout(xf, me) ; if (!xf->stdio) { if (stat(xf->fn->str, &mystat)) mcxIOerr(xf, me, "can not stat file") ; else sz = mystat.st_size ; } if (!xf->fp && mcxIOopen(xf, RETURN_ON_FAIL)) { mcxIOerr(xf, me, "can not open file") ; return STATUS_FAIL ; } if (xf->ateof) return STATUS_OK /* fixme; ting count overflow */ ; if (!(filetxt = mcxTingEmpty(filetxt, sz))) return STATUS_NOMEM ; while ((r = mcxIOappendChunk(xf, filetxt, sz, 0)) > 0 && !xf->ateof) ; if (r <0) return STATUS_FAIL /* fixme; look closer at error */ ; return STATUS_OK ; } static dim mcxIO__rl_fillbuf__ ( mcxIO* xf , char* buf , dim size , int* last ) { int a = 0 ; dim ct = 0 ; while(ctateof = 1 ; return z #undef MCX_IORL_BSZ ; } dim mcxIOdiscardLine ( mcxIO *xf ) { int a ; dim ct = 0 ; if (!xf->fp) { mcxIOerr(xf, "mcxIOdiscardLine", "is not open") ; return 0 /* fixme; set errno? */ ; } while(((a = mcxIOstep(xf)) != '\n') && a != EOF) ct++ ; if (buffaro(xf)) /* fixme/design check buffer for line */ buffer_spout(xf, "mcxIOdiscardLine") ; return ct ; } ofs mcxIOappendChunk ( mcxIO *xf , mcxTing *dst , dim sz , mcxbits flags cpl__unused ) { unsigned long psz = getpagesize() ; dim k = sz / psz /* fixme: size checks? */ ; dim rem = sz % psz ; ofs r = 1 /* pretend in case k == 0 */ /* mcxbool account = flags & MCX_CHUNK_ACCOUNT ? TRUE : FALSE */ ; dim offset = dst->len ; char* p ; if (!dst || !xf->fp || !mcxTingEnsure(dst, dst->len + sz)) return -1 /* fixme set some (new) errno */ ; if (k) while (k-- > 0 && (r = read(fileno(xf->fp), dst->str+dst->len, psz)) > 0) dst->len += r /* careful with unsignedness */ ; if ( r > 0 && rem > 0 && (r = read(fileno(xf->fp), dst->str+dst->len, rem)) > 0 ) dst->len += r ; dst->str[dst->len] = '\0' ; xf->bc += dst->len - offset ; for (p = dst->str+offset; pstr+dst->len; p++) { if (*p == '\n') { xf->lc++ ; xf->lo_ = xf->lo ; xf->lo = 0 ; } else xf->lo++ ; } /* fixme; what if k == 0, rem == 0 ? */ if (!r) /* fixme; other possibilities? */ xf->ateof = 1 ; return dst->len ; } mcxstatus mcxIOreadLine ( mcxIO *xf , mcxTing *dst , mcxbits flags ) { int a ; dim ll ; mcxbool chomp = flags & MCX_READLINE_CHOMP ? TRUE : FALSE ; mcxbool skip = flags & MCX_READLINE_SKIP_EMPTY ? TRUE : FALSE ; mcxbool par = flags & MCX_READLINE_PAR ? TRUE : FALSE ; mcxbool dot = flags & MCX_READLINE_DOT ? TRUE : FALSE ; mcxbool bsc = flags & MCX_READLINE_BSC ? TRUE : FALSE ; mcxbool repeat = dot || par || bsc ? TRUE : FALSE ; mcxbool continuation = FALSE ; mcxTing* line ; mcxstatus stat = STATUS_OK ; if (!xf->fp && mcxIOopen(xf, RETURN_ON_FAIL)) { mcxIOerr(xf, "mcxIOreadLine", "is not open") ; return STATUS_FAIL ; } if (xf->ateof) return STATUS_DONE ; if (!dst || !mcxTingEmpty(dst, 1)) return STATUS_NOMEM ; if (skip || par) { while((a = mcxIOstep(xf)) == '\n') NOTHING ; if (xf->ateof) return STATUS_DONE ; else mcxIOstepback(a, xf) ; } if (!(line = repeat ? mcxTingEmpty(NULL, 1) : dst)) return STATUS_NOMEM ; while (1) { ofs d = mcxIO__rl_rl__(xf, line) ; if (IO_MEM_ERROR == d) { stat = STATUS_NOMEM /* fixme grainify error/status */ ; break ; } ll = line->len ; if (!repeat) break ; else /* must append line to dst */ { if ( dot && !continuation && line->str[0] == '.' && ( ll == 2 || (ll == 3 && line->str[1] == '\r') ) /* fixme still not fully covering */ ) break /* do not attach the single-dot-line */ ; if (par && !continuation && ll == 1) break /* do not attach the second newline */ ; if (!mcxTingNAppend(dst, line->str, line->len)) { stat = STATUS_NOMEM ; break ; } continuation = bsc && (ll > 1 && *(line->str+ll-2) == '\\') ; if (continuation) mcxTingShrink(dst, -2) ; if (!par && !dot && (bsc && !continuation)) break ; if (xf->ateof) break ; } } if (repeat) mcxTingFree(&line) ; if (stat) return stat /* fixme; should we not check chomp first ? */ /* fixme _: \n\r ? */ ; if (chomp && dst->len && *(dst->str+dst->len-1) == '\n') mcxTingShrink(dst, -1) ; if (xf->ateof && !dst->len) return STATUS_DONE ; return STATUS_OK ; } void mcxIOlistParmodes ( void ) { fprintf ( stdout , "%5d vanilla mode, fetch next line\n" , MCX_READLINE_DEFAULT ) ; fprintf(stdout, "%5d chomp trailing newline\n", MCX_READLINE_CHOMP) ; fprintf(stdout, "%5d skip empty lines\n", MCX_READLINE_SKIP_EMPTY) ; fprintf(stdout, "%5d paragraph mode\n", MCX_READLINE_PAR) ; fprintf(stdout, "%5d backslash escapes newline\n", MCX_READLINE_BSC) ; fprintf ( stdout , "%5d section mode, ended by singly dot on a single line\n" , MCX_READLINE_DOT ) ; } void mcxIOpos ( mcxIO* xf , FILE* channel ) { const char* ateof = xf->ateof ? "at EOF in " : "" ; fprintf ( channel , "[mcxIO] %sstream <%s>, line <%ld>, character <%ld>\n" , ateof , xf->fn->str , (long) xf->lc , (long) xf->lo ) ; } void mcxIOfree_v ( void* xfpp ) { mcxIOfree((mcxIO**) xfpp) ; } void mcxIOfree ( mcxIO** xfpp ) { if (*xfpp) { mcxIO* xf = *xfpp ; mcxIOrelease(xf) ; mcxTingFree(&(xf->buffer)) ; if (xf->usr && xf->usr_free) xf->usr_free(xf->usr) ; mcxFree(xf) ; *xfpp = NULL ; } } mcxstatus mcxIOexpectReal ( mcxIO* xf , double* dblp , mcxOnFail ON_FAIL ) { int n_read = 0 ; int n_conv = 0 ; if (buffaro(xf)) buffer_spout(xf, "mcxIOexpectReal") ; mcxIOskipSpace(xf) /* keeps accounting correct */ ; n_conv = fscanf(xf->fp, " %lf%n", dblp, &n_read) ; xf->bc += n_read /* fixme do fscanf error handling */ ; xf->lo += n_read ; if (1 != n_conv) { if (ON_FAIL == EXIT_ON_FAIL) { mcxIOpos(xf, stderr) ; mcxErr("parseReal", "parse error: expected to find real") ; mcxExit(1) ; } return STATUS_FAIL ; } return STATUS_OK ; } mcxstatus mcxIOexpectNum ( mcxIO* xf , long* lngp , mcxOnFail ON_FAIL ) { int n_read = 0 ; int n_conv = 0 ; mcxstatus status = STATUS_OK ; if (buffaro(xf)) buffer_spout(xf, "mcxIOexpectNum") ; mcxIOskipSpace(xf) /* keeps accounting correct */ ; n_conv = fscanf(xf->fp, "%ld%n", lngp, &n_read) ; xf->bc += n_read /* fixme do fscanf error handling */ ; xf->lo += n_read ; if (1 != n_conv) mcxErr("mcxIOexpectNum", "parse error: expected to find integer") , status = STATUS_FAIL ; else if (errno == ERANGE) mcxErr("mcxIOexpectNum", "range error: not in allowable range") , status = STATUS_FAIL ; if (status) { mcxIOpos(xf, stderr) ; if (ON_FAIL == EXIT_ON_FAIL) mcxExit(1) ; } return status ; } int mcxIOskipSpace ( mcxIO* xf ) { int c ; while ((c = mcxIOstep(xf)) != EOF && isspace(c)) ; return mcxIOstepback(c, xf) ; } mcxbool mcxIOtryCookie ( mcxIO* xf , const unsigned char abcd[4] ) { unsigned char efgh[5] ; int n_read = fread(efgh, sizeof efgh[0], 4, xf->fp) ; int error = ferror(xf->fp) ; dim i = 0 ; if (n_read == 4) for (i=0; i<4 && abcd[i] == efgh[i]; i++) NOTHING #if 0 ;fprintf(stderr, "IN %d %d %d %d %d %d\n", i, n_read, (int) efgh[0], (int) efgh[1], (int) efgh[2], (int) efgh[3]) ;fprintf(stderr, "IN %d %d %d %d %d %d\n", i, n_read, (int) abcd[0], (int) abcd[1], (int) abcd[2], (int) abcd[3]) #endif ; if (i == 4) { xf->bc += 4 ; return TRUE ; } if (!fseek(xf->fp, -n_read, SEEK_CUR)) xf->bc += (4-n_read) ; else { mcxTingNAppend(xf->buffer, (char*) efgh, n_read) ; xf->bc += n_read ; if (!error) clearerr(xf->fp) ; } return FALSE ; } mcxbool mcxIOwriteCookie ( mcxIO* xf , const unsigned char abcd[4] ) { dim n_written = fwrite(abcd, sizeof abcd[0], 4, xf->fp) ; if (n_written != 4) { mcxErr("mcxIOwriteCookie", "failed to write <%.4s>", abcd) ; return FALSE ; } return TRUE ; } /* fixme: newlines in str thrash correct lc/lo counting */ int mcxIOexpect ( mcxIO *xf , const char *str , mcxOnFail ON_FAIL ) { const char* s = str ; int c = 0 ; int d = 0 ; int n_trailing /* * no functional behaviour yet attached to this state change * fixme: semantics for STDIN agree between stdlib and us? */ ; while ( c = (unsigned char) s[0] , ( c && ( d = mcxIOstep(xf) , c == d ) ) ) s++ ; n_trailing = strlen(s) /* truncintok */ ; if (c && ON_FAIL == EXIT_ON_FAIL) { mcxErr("mcxIOexpect", "parse error: expected to see <%s>", str) ; mcxIOpos(xf, stderr) ; mcxExit(1) ; } return n_trailing ; } typedef struct { int tbl[256] ; int* circle /* circular buffer */ ; int circle_last /* circle bumper */ ; const char* pat ; int patlen ; } mcxIOpat ; static void mcxio_newpat ( mcxIOpat* md , const char* pattern ) { int i ; int *tbl = md->tbl ; const char* pat ; int patlen = strlen(pattern) /* truncintok */ ; md->circle = mcxAlloc(patlen * sizeof(int), EXIT_ON_FAIL) ; md->pat = pattern ; md->patlen = patlen ; pat = md->pat /* initialize */ ; for (i = 0; i < 256; i ++) tbl[i] = patlen ; for (i = 0; i < patlen-1; i++) tbl[(unsigned char) pat[i]] = patlen -i -1 #if 0 ; for (i=0; icircle_last = patlen -1 ; } static void mcxIOcleanpat ( mcxIOpat* md ) { mcxFree(md->circle) ; } static int fillpatbuf ( mcxIO* xfin , int shift , mcxIOpat* md ) { int c = 0 ; int z = 0 ; int patlen = md->patlen ; while (z < shift && (c = mcxIOstep(xfin)) != EOF) { int q = (md->circle_last+z+1) % patlen ; md->circle[q] = c ; z++ ; } md->circle_last = (md->circle_last+shift) % patlen ; return c ; } mcxstatus mcxIOfind ( mcxIO* xfin , const char* pat , mcxOnFail ON_FAIL ) { int j, k ; int shift, patlen ; int* tbl ; int* circle ; mcxIOpat md ; int found = 0 ; mcxio_newpat(&md, pat) ; patlen = md.patlen ; tbl = md.tbl ; circle = md.circle ; shift = patlen /* * hum. This means that empty pattern matches on empty string .. * Need to fix fillpatbuf if this should be reversed. */ ; if (!patlen) found = 1 ; else do { if (EOF == fillpatbuf(xfin, shift, &md)) break ; for ( j=md.circle_last+patlen, k=patlen-1 ; j>md.circle_last && circle[j%patlen] == (unsigned char) pat[k] ; j--, k-- ) ; ; if (j == md.circle_last) { found = 1 ; break ; } /* * if more matches are needed, do something in this branch * and then simply continue. */ shift = tbl[circle[md.circle_last % patlen]] #if 0 ; fprintf ( stderr , "___ last[%d] index[%d] pivot[%d] shift[%d]\n" , (int) md.circle_last , (int) md.circle_last % patlen , (int) circle[md.circle_last % patlen] , (int) shift ) #endif ; } while (1) ; mcxIOcleanpat(&md) ; if (!found && ON_FAIL == RETURN_ON_FAIL) return STATUS_FAIL ; else if (!found) exit(EXIT_FAILURE) ; return STATUS_OK ; } dim mcxIOdiscard ( mcxIO* xf , dim amount ) { dim bsz = xf->buffer->mxl ; char* buf = xf->buffer->str ; dim n_read = 0 ; dim n_chunk = amount / bsz ; dim rem = amount - bsz * n_chunk ; dim i, n ; if (buffaro(xf)) buffer_spout(xf, "mcxIOdiscard") ; for (i=0;ifp) ; n_read += n ; xf->bc += n ; if (n != bsz) break ; } if (i < n_chunk) return n_read ; if (rem) n = fread(buf, 1, rem, xf->fp) , n_read += n , xf->bc += n ; return n_read ; }