/* * Predict when something will finish, based on progress values. * * The progress values can come from either of two sources. They can * come from a command which is run each time a value is needed, or * they can come from a long-running process to which a line of input * is sent when a value is needed. A typical example of the former is * * fstat -o -p 12345 * * A typical example of the latter is * * pidconn 12345 * * with something like "status" as the per-cycle line of input. There * is support for basic filtering of the output (to, for example, pick * out the relevant field from the relevant line). * * Options: * * -each CMD * Command to execute each time a value is needed (the * first paradigm, above). It gets /dev/null on stdin. * Either this or -one is required. * -one STRING CMD * Command (CMD) to execute as a long-running process and * a string (STRING) to send to it each time a value is * needed (the second paradigm, above). STRING gets a * newline appended to it when it's used. Either this or * -each is required. * -shell SH * Use SH as the shell for running the -each or -one CMD. * Default is /bin/sh (note, not $SHELL). * -grep REGEX * -fgrep STRING * An extended regular expression (-grep) or fixed string * (-fgrep) to search for in the output. This is normally * used only with -each. Lines not matching REGEX or not * containing STRING, as the case may be, are ignored. * The default is to do no filtering. * -field N * Picks out the Nth whitespace-delimited field of the * line as the number of interest. The first field is 1; * values less than 1 are equivalent to omitting -field. * The default is to consider the whole line as the value. * -time S * Request a value and report a response each S seconds. * The default is 60 (one minute). * -sync * Request values when time%S==0. The default is to * request them every S seconds from startup. * -target N * N is the value that the progress is to reach when it's * done. The progress values can be going either up or * down to the target. This must be given. * -scroll * Print each estimate as a separate line, scrolling past * estimates up. This is the default; see -over. * -over * Print each estimate over the previous, providing an * updated estimate without scrolling. The default is * -scroll (qv). * * There is a command line. Input commands: * * ? * help * Print basic help. * time S * Change the interval at which to report values. * sync ONOFF * Turn on or off synchronization of reports to the clock. * If on, reports happen when time%S is zero; if off, * every S seconds from startup, when S was last changed, * or when synchronization was turned off, whichever is * most recent. * target N * Change the target value. * scroll * Switch to -scroll mode. * over * Switch to -over mode. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #define REP4(x) (x), (x), (x), (x) #define REP16(x) REP4(x), REP4(x), REP4(x), REP4(x) #define REP64(x) REP16(x), REP16(x), REP16(x), REP16(x) #define REP256(x) REP64(x), REP64(x), REP64(x), REP64(x) static const char backspaces[256] = { REP256('\b') }; static const char spaces[256] = { REP256(' ') }; #undef REP4 #undef REP16 #undef REP64 #undef REP256 typedef long long int LLI; typedef unsigned long long int ULLI; static const char *cmd = 0; static const char *onestring = 0; static const char *shell = "/bin/sh"; static const char *grep = 0; static int grepflags = 0; static int field = 0; static int interval = 60; static int synch = 0; static LLI target; static int havetarget = 0; static int overmode = 0; typedef struct eline ELINE; typedef struct pendq PENDQ; typedef struct pendline PENDLINE; struct eline { unsigned char *buf; int alloc; int len; int curs; } ; struct pendq { PENDLINE *head; PENDLINE **tail; } ; struct pendline { PENDLINE *link; char *body; int len; } ; static int dnfd; static int timerfd; static ELINE ie; static unsigned int ieflags; #define IEF_LNEXT 0x00000001 #define IEF_ESC 0x00000002 static ES lastest; static ES laststamp; static int lastscrolled; static ELINE overdisp; static ES overes; static ELINE id; static ELINE pd; static PENDQ pending; static int tid; static int iid; static int oid; static AIO_OQ ooq; static AIO_OQ cmdiq; static int cmdifd; static int cmdiid; static int cmdofd; static int cmdoid; static int wantval; static ES cmdoline; static struct termios oldtio; static struct termios curtio; static int chldpipe[2]; static int chldid; static int updid; static int wantupd; static regex_t grepre; static ULLI time0; static LLI val0; #define Cisspace(x) isspace((unsigned char)(x)) static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-each")) { WANTARG(); cmd = av[skip]; onestring = 0; continue; } if (!strcmp(*av,"-one")) { WANTARG(); onestring = av[skip]; WANTARG(); cmd = av[skip]; continue; } if (!strcmp(*av,"-shell")) { WANTARG(); shell = av[skip]; continue; } if (!strcmp(*av,"-grep")) { WANTARG(); grep = av[skip]; grepflags = REG_EXTENDED; continue; } if (!strcmp(*av,"-fgrep")) { WANTARG(); grep = av[skip]; grepflags = REG_NOSPEC; continue; } if (!strcmp(*av,"-field")) { WANTARG(); field = atoi(av[skip]); continue; } if (!strcmp(*av,"-time")) { WANTARG(); interval = atoi(av[skip]); continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-target")) { WANTARG(); target = strtoll(av[skip],0,0); havetarget = 1; continue; } if (!strcmp(*av,"-scroll")) { overmode = 0; continue; } if (!strcmp(*av,"-over")) { overmode = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (! cmd) { fprintf(stderr,"%s: -each or -one must be given\n",__progname); errs = 1; } if (! havetarget) { fprintf(stderr,"%s: -target must be given\n",__progname); errs = 1; } if (errs) exit(1); } static void nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void clex(int fd) { fcntl(fd,F_SETFD,1); } static void dbg(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void dbg(const char *fmt, ...) { static int fd = -1; va_list ap; char *s; int l; switch (fd) { case -1: fd = open("predict.debug",O_WRONLY|O_TRUNC,0); if (fd < 0) { fd = -2; case -2: return; } break; } va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); write(fd,s,l); free(s); } static void reset_timer(void) { struct itimerval itv; itv.it_interval.tv_sec = interval; itv.it_interval.tv_usec = 0; if (synch) { struct timeval now; gettimeofday(&now,0); if (now.tv_usec == 0) now.tv_usec = 1; itv.it_value.tv_sec = interval - 1 - (now.tv_sec % interval); itv.it_value.tv_usec = 1000000 - now.tv_usec; } else { itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 10000; } write(timerfd,&itv,sizeof(itv)); } static int wr_oq(AIO_OQ *oq, int fd) { int nw; nw = aio_oq_writev(oq,fd,-1); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); break; } return(-1); } aio_oq_dropdata(oq,nw); return(0); } static int wtest_stdout(void *arg __attribute__((__unused__))) { return(aio_oq_nonempty(&ooq)); } static void wr_stdout(void *arg __attribute__((__unused__))) { if (wr_oq(&ooq,1) < 0) { fprintf(stderr,"%s: output write error: %s\n",__progname,strerror(errno)); exit(1); } } static void pendq_init(PENDQ *pq) { pq->head = 0; pq->tail = &pq->head; } static void pendq_append(PENDQ *q, char *s, int l) { PENDLINE *pl; pl = malloc(sizeof(PENDLINE)); pl->body = s; pl->len = l; pl->link = 0; *q->tail = pl; q->tail = &pl->link; } static PENDLINE *pendq_next(PENDQ *q) { PENDLINE *pl; pl = q->head; if (pl) { q->head = pl->link; if (! q->head) q->tail = &q->head; } return(pl); } static void scroll_output(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void scroll_output(const char *fmt, ...) { char *s; int l; va_list ap; va_start(ap,fmt); l = vasprintf(&s,fmt,ap); va_end(ap); pendq_append(&pending,s,l); wantupd = 1; } static int fork_child(const char *command, int ifd) { int xp[2]; int op[2]; pid_t kid; int e; int n; fflush(0); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (pipe(&op[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { dup2(ifd,0); close(ifd); dup2(op[1],1); close(op[1]); close(op[0]); clex(xp[0]); close(xp[1]); execlp(shell,shell,"-c",command,(char *)0); e = errno; write(xp[0],&e,sizeof(e)); _exit(1); } close(op[1]); close(xp[0]); while (1) { n = recv(xp[1],&e,sizeof(e),MSG_WAITALL); if (n < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: exec recv: %s\n",__progname,strerror(errno)); exit(1); } break; } close(xp[1]); switch (n) { case 0: break; case sizeof(e): fprintf(stderr,"%s: exec %s: %s\n",__progname,shell,strerror(e)); exit(1); break; default: fprintf(stderr,"%s: exec protocol error: read %d, wanted %d or 0\n",__progname,n,(int)sizeof(e)); exit(1); break; } return(op[0]); } static void reset_overdisp(void) { int curs; char *s; int l; es_clear(&overes); if (time0 == 0) { es_append_n(&overes,"[no data]",9); } else if (es_len(&lastest) == 0) { es_append_n(&overes,"[only one sample]",17); } if (overmode) { es_append_n(&overes,es_buf(&lastest),es_len(&lastest)); } curs = es_len(&overes); if (curs) es_append_n(&overes," ",2); es_append_printf(&overes,"[%.*s%s%d%s %s]",es_len(&laststamp),es_buf(&laststamp),es_len(&laststamp)?" ":"",interval,synch?" sync":"",overmode?"over":"scroll"); l = es_len(&overes); s = es_buf(&overes); if (l > overdisp.alloc) { free(overdisp.buf); overdisp.alloc = l; overdisp.buf = malloc(l); } if (l) bcopy(s,overdisp.buf,l); overdisp.len = l; overdisp.curs = curs; wantupd = 1; } static void have_value(LLI v) { struct timeval nowtv; ULLI now; ULLI when; time_t tt; struct tm *tm; gettimeofday(&nowtv,0); now = (nowtv.tv_sec * 1000000ULL) + nowtv.tv_usec; if (time0 == 0) { time0 = now; val0 = v; } else { if (v == val0) return; when = (((target - val0) * (double)(now - time0)) / (v - val0)) + time0; tt = when / 1000000ULL; tm = localtime(&tt); es_clear(&lastest); es_append_printf(&lastest,"%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); tt = now / 1000000ULL; tm = localtime(&tt); es_clear(&laststamp); es_append_printf(&laststamp,"%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } reset_overdisp(); if (overmode) { lastscrolled = 0; } else { if (es_len(&lastest)) scroll_output("%.*s\r\n",es_len(&lastest),es_buf(&lastest)); lastscrolled = 1; } } /* * This assumes body[len] is '\n', or at least is a terminator * character for strtoll and the like. */ static void command_output_line(const char *body, int len) { int f; LLI v; int x; int x0; if (grep) { regmatch_t pm; int es; char *em; int e; pm.rm_so = 0; pm.rm_eo = len; e = regexec(&grepre,body,0,&pm,REG_STARTEND); switch (e) { case 0: break; case REG_NOMATCH: return; break; default: es = regerror(e,&grepre,0,0); em = malloc(es); regerror(e,&grepre,em,es); fprintf(stderr,"%s: regexec: %s\n",__progname,em); exit(1); break; } } if (field < 1) { v = strtoll(body,0,0); wantval = 0; } else { x = 0; for (f=field;f>0;f--) { for (;(x= len) return; for (x0=x;(x 0) { es_append_n(&cmdoline,&rbuf[o],(nl-&rbuf[o])+1); command_output_line(es_buf(&cmdoline),es_len(&cmdoline)-1); es_clear(&cmdoline); } else { command_output_line(&rbuf[o],nl-&rbuf[o]); } o = (nl + 1) - &rbuf[0]; } else { es_append_n(&cmdoline,&rbuf[o],nr-o); break; } } } /* * If we get multiple timer events, either -time is tiny or we got * suspended or otherwise locked out of the CPU for longer than * expected. Either way, the sensiblist thing I can think of is to * consume all the events and act on just the last one. */ static void rd_timer(void *arg __attribute__((__unused__))) { struct timersock_event tse[64]; int nr; int any; any = 0; while <"reading"> (1) { nr = read(timerfd,&tse[0],sizeof(tse)); if (nr < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: break <"reading">; } fprintf(stderr,"%s: timer socket read: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { fprintf(stderr,"%s: timer socket read EOF\n",__progname); exit(1); } if (nr % sizeof(struct timersock_event)) { fprintf(stderr,"%s: timer socket read %d, not divisible by %d\n",__progname,nr,(int)sizeof(struct timersock_event)); exit(1); } any = 1; } if (! any) return; if (onestring) { aio_oq_queue_point(&cmdiq,onestring,AIO_STRLEN); aio_oq_queue_point(&cmdiq,"\n",1); } else { cmdofd = fork_child(cmd,dnfd); cmdoid = aio_add_poll(cmdofd,&aio_rwtest_always,&aio_rwtest_never,&rd_command,0,0); } es_clear(&cmdoline); wantval = 1; } static void set_over(int o) { if (o == overmode) return; if (overmode && !lastscrolled) { scroll_output("%.*s\r\n",es_len(&lastest),es_buf(&lastest)); lastscrolled = 1; } overmode = o; reset_overdisp(); } static void cmd_help(int x, const char *verb) { if (x >= ie.len) { scroll_output( "?, help\r\n" " This help.\r\n" "q, quit\r\n" " Quit.\r\n" "time S\r\n" " Set inter-estimate interval to S seconds.\r\n" "sync ONOFF\r\n" " Estimation time synchronization on/off.\r\n" "target N\r\n" " Change target value.\r\n" "scroll\r\n" " Scroll estimates.\r\n" "over\r\n" " Don't scroll estimates.\r\n" ); return; } scroll_output("%s: takes no argument\r\n",verb); } static void cmd_over(int x) { if (x < ie.len) { scroll_output("over: takes no argument\r\n"); return; } set_over(1); } static void cmd_quit(void) { printf("\r\n"); exit(0); } static void cmd_sync(int x) { if (x >= ie.len) { scroll_output("sync: needs an argument (on or off)\r\n"); return; } switch (ie.len-x) { case 2: if (! bcmp(ie.buf+x,"on",2)) { synch = 1; reset_timer(); return; } break; case 3: if (! bcmp(ie.buf+x,"off",3)) { synch = 0; reset_timer(); return; } break; } scroll_output("sync: argument must be `on' or `off'\r\n"); } static void cmd_time(int x) { int v; if (x >= ie.len) { scroll_output("time: needs an argument (a number of seconds)\r\n"); return; } v = strtol(ie.buf+x,0,0); if (v < 1) { scroll_output("time: argument bust be at least 1\r\n"); } else { interval = v; reset_timer(); reset_overdisp(); } } static void cmd_scroll(int x) { if (x < ie.len) { scroll_output("scroll: takes no argument\r\n"); return; } set_over(0); } static void cmd_target(int x) { if (x >= ie.len) { scroll_output("target: needs an argument (target value)\r\n"); return; } target = strtoll(ie.buf+x,0,0); } static void typed_line(void) { int x; int c0; int cl; if ((ie.len == 1) && (ie.buf[0] == 'Q')) { exit(0); } for (x=0;(x ie.len)) abort(); if (ie.len+1 > ie.alloc) { ie.alloc = ie.len + 32; ie.buf = realloc(ie.buf,ie.alloc); } if (at < ie.len) bcopy(ie.buf+at,ie.buf+at+1,ie.len-at); ie.buf[at] = c; ie.len ++; } static void ie_del(int at, int n) { if ((at < 0) || (at > ie.len) || (n < 0) || (n > ie.len) || (at+n > ie.len)) abort(); if (n < 1) return; if (at+n < ie.len) bcopy(ie.buf+at+n,ie.buf+at,ie.len-(at+n)); ie.len -= n; } static void ie_transpose(void) { unsigned char t; if (ie.curs < 2) abort(); t = ie.buf[ie.curs-2]; ie.buf[ie.curs-2] = ie.buf[ie.curs-1]; ie.buf[ie.curs-1] = t; } static void typed_char(unsigned char c) { if (ieflags & IEF_LNEXT) { ieflags &= ~IEF_LNEXT; ie_insert1(ie.curs++,c); return; } if (ieflags & IEF_ESC) { ieflags &= ~IEF_ESC; ie_beep(); } else { switch (c) { case 1: // ^A ie.curs = 0; break; case 2: // ^B if (ie.curs > 0) ie.curs --; break; case 4: // ^D if (ie.curs < ie.len) ie_del(ie.curs,1); break; case 5: // ^E ie.curs = ie.len; break; case 6: // ^F if (ie.curs < ie.len) ie.curs ++; break; case 8: // ^H case 127: // DEL if (ie.curs > 0) ie_del(--ie.curs,1); break; case 10: // ^J case 13: // ^M ie_insert1(ie.len,'\0'); ie.len --; typed_line(); ie.len = 0; ie.curs = 0; break; case 11: // ^K ie.len = ie.curs; break; case 12: // ^L aio_oq_queue_copy(&ooq,"\r",1); pd.len = 0; pd.curs = 0; break; case 20: // ^T if (ie.curs >= 2) ie_transpose(); break; case 21: // ^U // XXX prefix-argument? ie.len = 0; ie.curs = 0; break; case 22: // ^V ieflags |= IEF_LNEXT; break; case 27: // ESC ieflags |= IEF_ESC; break; default: if (((c >= 32) && (c <= 126)) || (c >= 160)) { ie_insert1(ie.curs++,c); } else { ie_beep(); } break; } } } static void rd_stdin(void *arg __attribute__((__unused__))) { unsigned char rbuf[4096]; int nr; int i; nr = read(0,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: stdin read: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { fprintf(stderr,"%s: stdin read EOF\n",__progname); exit(1); } for (i=0;i (1) { nr = read(chldpipe[0],&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: break <"reading">; } fprintf(stderr,"%s: stdin read: %s\n",__progname,strerror(errno)); exit(1); } if (nr > 0) any = 1; } if (! any) return; while <"wait"> (1) { dead = wait3(0,WNOHANG,0); if (dead < 0) { switch (errno) { case EINTR: continue; break; case ECHILD: break <"wait">; } fprintf(stderr,"%s: wait: %s\n",__progname,strerror(errno)); exit(1); } if (dead == 0) break; } } static void catch_sigchld(int sig __attribute__((__unused__))) { write(chldpipe[1],"",1); } static void gen_backspaces(int n) { int i; dbg("gen_backspaces(%d)\n",n); if (n < 0) abort(); while (n > 0) { i = n; if (i > sizeof(backspaces)) i = sizeof(backspaces); aio_oq_queue_point(&ooq,&backspaces[0],i); pd.curs -= i; n -= i; } } static void gen_spaces(int n) { int i; dbg("gen_spaces(%d)\n",n); if (n < 0) abort(); while (n > 0) { i = n; if (i > sizeof(spaces)) i = sizeof(spaces); aio_oq_queue_point(&ooq,&spaces[0],i); pd.curs += i; n -= i; } } static void update_to_match(const char *body, int len, int curs) { int s; int e; dbg(">>>> update_to_match entry\n"); dbg("cur %d «%.*s» curs %d\n",pd.len,pd.len,pd.buf,pd.curs); dbg("arg %d «%.*s» curs %d\n",len,len,body,curs); if (pd.alloc < len) { pd.alloc = len + 16; pd.buf = realloc(pd.buf,pd.alloc); dbg("grew pd to %d\n",pd.alloc); } for (s=0;(s= len) { dbg("chars identical\n"); if (curs < pd.curs) { gen_backspaces(pd.curs-curs); } else if (curs > pd.curs) { dbg("queue_copy %d at %d: «%.*s»\n",curs-pd.curs,pd.curs,curs-pd.curs,body+pd.curs); aio_oq_queue_copy(&ooq,body+pd.curs,curs-pd.curs); pd.curs = curs; } dbg("<<<< done\n"); return; } for (e=len-1;(e>=0)&&(body[e]==pd.buf[e]);e--) ; if (e < 0) abort(); e ++; dbg("e %d\n",e); if (s >= len) abort(); if (s < pd.curs) gen_backspaces(pd.curs-s); if (e <= pd.curs) abort(); dbg("queue_copy %d at %d: «%.*s»\n",e-pd.curs,pd.curs,e-pd.curs,body+pd.curs); aio_oq_queue_copy(&ooq,body+pd.curs,e-pd.curs); pd.curs = e; } else { dbg("lengths differ\n"); if (pd.curs > s) gen_backspaces(pd.curs-s); if (len > pd.len) { bcopy(body+pd.curs,pd.buf+pd.curs,len-pd.curs); dbg("copy and queue %d at %d: «%.*s»\n",len-pd.curs,pd.curs,len-pd.curs,body+pd.curs); aio_oq_queue_copy(&ooq,body+pd.curs,len-pd.curs); pd.len = len; pd.curs = len; } else // len < pd.len - can't be ==, tested above { bcopy(body+pd.curs,pd.buf+pd.curs,len-pd.curs); dbg("copy and queue %d at %d: «%.*s»\n",len-pd.curs,pd.curs,len-pd.curs,body+pd.curs); aio_oq_queue_copy(&ooq,body+pd.curs,len-pd.curs); gen_spaces(pd.len-len); gen_backspaces(pd.len-len); pd.len = len; pd.curs = len; } } dbg("cursor fixup, at %d want %d\n",pd.curs,curs); if (curs < pd.curs) { gen_backspaces(pd.curs-curs); } else if (curs > pd.curs) { aio_oq_queue_copy(&ooq,body+pd.curs,curs-pd.curs); } pd.curs = curs; dbg("<<<< done\n"); } static void idset(int x, unsigned char c) { if (x >= id.alloc) { id.alloc = x + 16; id.buf = realloc(id.buf,id.alloc); } if (x >= id.len) id.len = x + 1; id.buf[x] = c; } static void process_ie(void) { int i; id.curs = -1; id.len = 0; for (i=0;i 126) && (ie.buf[i] < 160))) { idset(id.len,'^'); idset(id.len+1,ie.buf[i]^64); } else { idset(id.len,ie.buf[i]); } } if (id.curs < 0) { if (ie.curs != ie.len) abort(); id.curs = id.len; } } static int maybe_update(void *arg __attribute__((__unused__))) { PENDLINE *pl; if (! wantupd) return(AIO_BLOCK_NIL); wantupd = 0; while ((pl = pendq_next(&pending))) { update_to_match("",0,0); aio_oq_queue_free(&ooq,pl->body,pl->len); free(pl); } if (ie.len) { process_ie(); update_to_match(id.buf,id.len,id.curs); } else { update_to_match(overdisp.buf,overdisp.len,overdisp.curs); } return(AIO_BLOCK_LOOP); } static void eline_init(ELINE *el) { el->buf = 0; el->alloc = 0; el->len = 0; el->curs = 0; } static void setup_grep(void) { int e; if (! grep) return; e = regcomp(&grepre,grep,grepflags|REG_NOSUB); if (e) { size_t es; char *em; es = regerror(e,&grepre,0,0); em = malloc(es); regerror(e,&grepre,em,es); fprintf(stderr,"%s: %s: %s\n",__progname,grep,em); exit(1); } } static int wtest_cmdin(void *arg __attribute__((__unused__))) { return(aio_oq_nonempty(&cmdiq)); } static void wr_command_in(void *arg __attribute__((__unused__))) { if (wr_oq(&cmdiq,cmdifd) < 0) { fprintf(stderr,"%s: command input write error: %s\n",__progname,strerror(errno)); exit(1); } } static void startup(void) { int p[2]; struct sigaction sa; int i; dnfd = open("/dev/null",O_RDWR,0); if (dnfd < 0) { fprintf(stderr,"%s: can't open /dev/null: %s\n",__progname,strerror(errno)); exit(1); } clex(dnfd); while (dnfd < 3) { i = dup(dnfd); if (i < 0) { fprintf(stderr,"%s: dup: %s\n",__progname,strerror(errno)); exit(1); } clex(i); dnfd = i; } if (tcgetattr(0,&oldtio) >= 0) { curtio = oldtio; curtio.c_iflag &= ~(INLCR|IGNCR|ICRNL|IXOFF); curtio.c_oflag &= ~OPOST; curtio.c_lflag &= ~(ECHOKE|ECHOE|ECHO|ECHONL|ECHOPRT|ECHOCTL|ICANON); curtio.c_cc[VMIN] = 1; curtio.c_cc[VTIME] = 0; // do tcsetattr later iid = aio_add_poll(0,&aio_rwtest_always,&aio_rwtest_never,&rd_stdin,0,0); } else { iid = AIO_NOID; } if (pipe(&chldpipe[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } sa.sa_handler = &catch_sigchld; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGCHLD,&sa,0); chldid = aio_add_poll(chldpipe[0],&aio_rwtest_always,&aio_rwtest_never,&rd_chld,0,0); nbio(chldpipe[0]); nbio(chldpipe[1]); timerfd = socket(AF_TIMER,SOCK_STREAM,0); nbio(timerfd); clex(timerfd); tid = aio_add_poll(timerfd,&aio_rwtest_always,&aio_rwtest_never,&rd_timer,0,0); aio_oq_init(&ooq); nbio(1); oid = aio_add_poll(1,&aio_rwtest_never,&wtest_stdout,0,&wr_stdout,0); if (onestring) { if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } cmdifd = p[1]; clex(cmdifd); cmdofd = fork_child(cmd,p[0]); close(p[0]); cmdoid = aio_add_poll(cmdofd,&aio_rwtest_always,&aio_rwtest_never,&rd_command,0,0); aio_oq_init(&cmdiq); cmdiid = aio_add_poll(cmdifd,&aio_rwtest_never,&wtest_cmdin,0,&wr_command_in,0); } else { cmdofd = -1; } es_init(&cmdoline); wantval = 0; setup_grep(); reset_timer(); eline_init(&ie); ieflags = 0; eline_init(&id); eline_init(&pd); eline_init(&overdisp); es_init(&overes); es_init(&lastest); lastscrolled = 1; es_init(&laststamp); pendq_init(&pending); wantupd = 1; updid = aio_add_block(&maybe_update,0); tcsetattr(0,TCSADRAIN,&curtio); nbio(0); time0 = 0; reset_overdisp(); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); startup(); aio_event_loop(); return(0); }