/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "panic.h" #include "writev.h" #include "nested.h" #include "stdio-util.h" #include "verbose.h" unsigned int verbose = 0; typedef struct verbkey VERBKEY; typedef struct verbdest VERBDEST; typedef struct vfp VFP; struct vfp { unsigned int bits; } ; struct verbkey { const char *name; const char *desc; unsigned int bit; int ionly; } ; struct verbdest { VERBDEST *link; int type; #define VDT_NONE 1 #define VDT_FILE 2 #define VDT_FD 3 union { struct { char *name; int namelen; } file; int fdno; } ; unsigned int bits; int fd; } ; #define VERBKEY(s,d,b) { s,d,b,0 } #define IONLYKEY(s,b) { s,0,b,1 } static const VERBKEY verbkeys[] = { #include "verbose-table.inc" }; #define NVERBKEYS (sizeof(verbkeys)/sizeof(verbkeys[0])) static int verb_lineno; static int verb_newline; static VERBDEST *dests = 0; static FILE *verb_out = 0; unsigned int verb_dests = 0; static void enable_dest(unsigned int bits, const void *to, int tolen, const char *arg) { VERBDEST *d; VERBDEST n; if (tolen < 1) { fprintf(stderr,"%s: %s: missing destination\n",__progname,arg); exit(1); } verbose |= bits; if (((const char *)to)[0] == '-') { int fd; if (tolen == 1) { fd = 1; } else { char *t; char *e; long int v; t = malloc(tolen); bcopy(1+(const char *)to,t,tolen-1); t[tolen-1] = '\0'; v = strtol(t,&e,0); fd = v; if (*e || (e == t) || (v < 0) || (fd != v)) { fprintf(stderr,"%s: %s: invalid file descriptor spec `-%s'\n",__progname,arg,t); exit(1); } free(t); } for (d=dests;d;d=d->link) { if ((d->type == VDT_FD) && (d->fdno == fd)) { d->bits |= bits; return; } } n.type = VDT_FD; n.fdno = fd; n.fd = fd; } else { const char *oname; unsigned int oflags; for (d=dests;d;d=d->link) { if ((d->file.namelen == tolen) && !bcmp(d->file.name,to,tolen)) { d->bits |= bits; return; } } n.type = VDT_FILE; n.file.name = malloc(tolen+1); bcopy(to,n.file.name,tolen); n.file.name[tolen] = '\0'; n.file.namelen = tolen; if (n.file.name[0] == '+') { oname = n.file.name + 1; oflags = O_WRONLY | O_APPEND | O_CREAT; } else { oname = n.file.name; oflags = O_WRONLY | O_CREAT | O_TRUNC; } n.fd = open(oname,oflags,0666); if (n.fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,oname,strerror(errno)); exit(1); } } d = malloc(sizeof(VERBDEST)); *d = n; d->bits = bits; d->link = dests; dests = d; } static void enable_nodest(unsigned int bits) { VERBDEST *d; verbose |= bits; for (d=dests;d;d=d->link) { if (d->type == VDT_NONE) { d->bits |= bits; return; } } d = malloc(sizeof(VERBDEST)); d->type = VDT_NONE; d->bits = bits; d->fd = 2; d->link = dests; dests = d; } static void poll_writable(int fd) { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT | POLLWRNORM; poll(&pfd,1,INFTIM); } static void write_v(int fd, struct iovec *v, int n) { int o; int t; int w; while (1) { w = writev(fd,v,n); if (w < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: poll_writable(fd); continue; break; } } break; } o = w; while (1) { while ((n > 0) && (o >= v[0].iov_len)) { o -= v[0].iov_len; v ++; n --; } if (n < 1) break; t = v[0].iov_len - o; w = write(fd,((const char *)v[0].iov_base)+o,t); if (w < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: poll_writable(fd); continue; break; } return; } o += w; } } static int verb_w(void *cookie __attribute__((__unused__)), const char *data, int datalen) { void *c; const char *dp; const char *nl; NESTED void w(struct iovec *v, int n) { VERBDEST *d; for (d=dests;d;d=d->link) { if (d->bits & verb_dests) { switch (d->type) { default: panic("impossible VERBDEST type %d",d->type); break; case VDT_NONE: case VDT_FILE: case VDT_FD: write_v(d->fd,v,n); break; } } } } c = writev_open_cb(&w); dp = data; while (dp-data < datalen) { if (verb_newline) { char nbuf[32]; writev_copy(c,&nbuf[0],sprintf(&nbuf[0],"%-4d ",verb_lineno)); verb_lineno ++; verb_newline = 0; } nl = memchr(dp,'\n',datalen-(dp-data)); if (nl) { if (nl != dp) writev_point(c,dp,nl-dp); writev_point(c,"\n",1); dp = nl + 1; verb_newline = 1; } else { writev_point(c,dp,datalen-(dp-data)); break; } } writev_close(c); return(datalen); } static int verb_funopen_w(void *pv, const char *data, int len) { verb_dests = ((VFP *)pv)->bits; return(verb_w(0,data,len)); } static int verb_funopen_c(void *pv) { free(pv); return(0); } void verbosity_set(const char *s) { int i; int j; int l; int a; int b; unsigned int bits; unsigned int rv; if (!strcmp(s,"?")) { fprintf(stderr,"%s: -V usage:\n",__progname); fprintf(stderr,"\ -V argument is a whitespsace-separated list of `clause's.\n\ A `clause' is name,name,name[=destination], which turns on verbosity for\n\ all the listed `name's and directs the output to `destination'.\n\ Destinations are either a file name, or `-' for stdout, or `-N' for file\n\ descriptor N (eg, `-2' for stderr). A given name can be redirected to\n\ multiple destinations; its output goes to all of them. If a given filename\n\ is given as destination twice, it is equivalent to giving the same name\n\ once but with a merged list of names; if two different names naming the\n\ same file are given (even as similar as x=foo and x=./foo), they will be\n\ treated as two different files, probably with bad results (the output will\n\ be intermixed unpleasantly and possibly some output will overwrite other\n\ output). A filename destination may be prefixed with + to indicate that\n\ output is to be appended to the file, rather than overwriting it. If no\n\ destination is given, output goes to stderr; this is considered distinct\n\ from a destination of `-2'. Similarly, `-' and `-1' are distinct. (If an\n\ argument contains whitespace, most shells will require it be quoted, but\n\ you can also use multiple -V options instead.)\n\ Use `-V examples' for some examples, or `-V keys' for a keyword list.\n"); exit(0); } else if (!strcmp(s,"keys")) { fprintf(stderr,"Verbosity types:\n"); j = 0; for (i=0;i j) j = l; } for (i=0;i (s[a]) { b = a; while <"key"> (1) { switch (s[b]) { case '\0': case ',': case ' ': case '\t': case '=': break <"key">; } b ++; } if (b == a) { if (s[b]) { a = b + 1; continue <"nextkey">; } return; } for (i=NVERBKEYS-1;i>=0;i--) { l = strlen(verbkeys[i].name); if ((b-a == l) && !bcmp(verbkeys[i].name,s+a,l)) { bits |= verbkeys[i].bit; switch (s[b]) { default: panic("impossible terminator character 0x%02x",(unsigned char)s[b]); break; case ',': break; case '=': a = ++b; while <"dest"> (1) { switch (s[b]) { case '\0': case ' ': case '\t': break <"dest">; } b ++; } enable_dest(bits,&s[a],b-a,s); bits = 0; break; case '\0': case ' ': case '\t': enable_nodest(bits); bits = 0; break; } a = s[b] ? b+1 : b; continue <"nextkey">; } } fprintf(stderr,"%s: unknown -V keyword `%.*s'\n",__progname,b-a,s+a); exit(1); } } void pverb(unsigned int to, const char *fmt, ...) { va_list ap; if (verb_out == 0) { verb_newline = 1; verb_out = fwopen(0,&verb_w); verb_lineno = 1; } verb_dests = to; va_start(ap,fmt); vfprintf(verb_out,fmt,ap); va_end(ap); fflush(verb_out); } void verbosity_default(void) { verbosity_set("default"); } void verbose_startup(void) { VERBDEST *d; unsigned int m; int i; char *line; int len; FILE *f; for (d=dests;d;d=d->link) { if (d->bits == 0) panic("VERBDEST %p bits == 0",(void *)d); switch (d->type) { default: panic("VERBDEST %p bad type %d",(void *)d,d->type); break; case VDT_NONE: case VDT_FILE: case VDT_FD: break; } m = d->bits; f = fopen_alloc(&line,&len); fprintf(f,"Verbose:"); for (i=0;ifd,line,len); free(line); } } FILE *verb_fopen(unsigned int bits) { FILE *f; VFP *p; p = malloc(sizeof(VFP)); if (p == 0) return(0); p->bits = bits; f = funopen(p,0,&verb_funopen_w,0,&verb_funopen_c); if (f == 0) free(p); return(f); } void verb_data_block(unsigned int bits, const void *data, int len) #define cdata ((const unsigned char *)data) { int i; int o; for (o=0;o= 32) && (c <= 126)) || (c >= 160)) { pverb(bits,"%c",c); } else { pverb(bits,"·"); } } pverb(bits,"\n"); } } #undef cdata void verb_data_blocks(unsigned int bits, int nblocks, ...) { va_list ap; int blk; int o; int bl; const unsigned char *bp; unsigned char linetext[16]; int pmark; va_start(ap,nblocks); o = 0; pmark = 0; for (blk=nblocks;blk>0;blk--) { bp = va_arg(ap,const void *); bl = va_arg(ap,int); if (bl < 0) { switch (bl) { case -1: pmark = 1; break; default: panic("impossible negative block length"); break; } continue; } for (;bl>0;bl--,bp++) { if (! (o & 15)) pverb(bits,"%4x ",o); else if (! (o & 7)) pverb(bits," "); pverb(bits,"%c%02x",pmark?'*':' ',*bp); pmark = 0; if (((*bp >= 32) && (*bp <= 126)) || (*bp >= 160)) { linetext[o&15] = *bp; } else { linetext[o&15] = '·'; } o ++; if (! (o & 15)) pverb(bits," %.16s\n",&linetext[0]); } } if (o & 15) { pverb(bits,"%-*s%.*s\n",((16-(o&15))*3)+((o&8)?0:1)+2,pmark?"*":"",o&15,&linetext[0]); } } void verb_vis(unsigned int bits, const void *data, int len) #define dp ((const unsigned char *)data) { int i; for (i=0;i= 32) && (dp[i] <= 126)) || (dp[i] >= 160)) { pverb(bits,"%c",dp[i]); } else { switch (dp[i]) { case '\a': pverb(bits,"\\a"); break; case '\b': pverb(bits,"\\b"); break; case '\e': pverb(bits,"\\e"); break; case '\f': pverb(bits,"\\f"); break; case '\n': pverb(bits,"\\n"); break; case '\r': pverb(bits,"\\r"); break; case '\t': pverb(bits,"\\t"); break; case '\v': pverb(bits,"\\v"); break; default: pverb(bits,"\\%0*o",((i