/*+++* * title: tcap-ti * abstract: translate termcap to terminfo * author: T.R.Hageman, Groningen, The Netherlands. * created: january 1990 * modified: * description: *---*/ char *version = "v1.1 2-Jul-93 (c) Tom Hageman"; #include #include #include #include #include "portdefs.h" #if _ANSI_HEADERS_ # include # include #else extern void *malloc(); #endif #endif #if _STDARG_ # include #else # include #endif #include "tinfocap.h" #ifndef ARCANE # define ARCANE 1 /* we want full translation capabilities */ #endif #ifndef EXTENSION # define EXTENSION 1 /* ...with the Gnu extensions. */ #endif #define DEF_WIDTH 72 int verbose, width = DEF_WIDTH, resolve_tc, uncomment, pass_line_comments = TRUE; int err; /* program status */ const char *term, *cap; #define VERBOSE(level, x) {if (verbose > level) eprintf x;} void DEFVARG( eprintf, (const char *fmt, ...), (fmt, va_alist) const char *fmt; ) { va_list ap; VA_START(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } void DEFVARG( warn, (const char *fmt, ...), (fmt, va_alist) const char *fmt; ) { va_list ap; if (cap) fprintf(stderr, "capability <%.2s> ", cap); VA_START(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, " (TERM=%s)\n", term); } /* * Translate Termcap %x sequences to Terminfo. * `s' is the (de-escaped) source string * `d' is a pointer to a result buffer (assumed to be big enough); * The updated result pointer is returned. */ void translate __(( const char *_(s), char *_(d) )); void translate(s, d) register const char *s; register char *d; { static const char premature[]=": premature end of %%%c sequence", inefficient[]=": translation of %%%c may not be optimal"; register int c, i = '0', /* current parameter number */ onstack = 0; register const char *t; #define push() (onstack++, *d++ = 'p', *d++ = ++i, *d++ = '%') #define app(S) {for (t = S; *d++ = *t++; ); --d;} #define check(x) {if (!(x)) { warn(premature, c); return; }} #define slowww() warn(inefficient, c) while (*d = *s++) { if (*d++ != '%') continue; switch (c = *s++) { case '\0': c = '_'; check(0); default: /* including %i %% */ *d++ = c; continue; case 'r': /* %r -> reverse args (1,2) */ if (!onstack) { push(), push(); /* so arg 2 at top of stack */ --d; /* discard trailing '%' */ } else { slowww(); if (onstack == 1) { app("Pz%"); push(); *d++ = 'g', *d++ = 'z'; } else { /* swap top 2 stack elements */ app("Pz%Py%gz%gy"); } } continue; case '>': /* %>cd -> %pi%?%pi%'c'%>%t%'d'%+%; */ if (!onstack) { push(); app("?%p"); *d++ = i; } else { /* or %Pz%gz%?%gz%'c'%>%t%'d'%+%; */ app("Pz%gz%?%gz"); } *d++ = '%', *d++ = '\''; check(*d++ = *s++); app("'%>%t%'"); check(*d++ = *s++); app("'%+%;"); continue; #if ARCANE case 'n': /* %n xor (1,2) with 0140 */ if (!onstack) { app("p2%'\140'%^%p1%'\140'%^"); } else { slowww(); app("'\140'%^%Pz%"); if (onstack == 1) push(); app("'\140'%^%gz"); } continue; #if EXTENSION case 'm': /* %m xor (1,2) with 0177 */ if (!onstack) { app("p2%'\177'%^%p1%'\177'%^"); } else { slowww(); app("'\177'%^%Pz"); if (onstack == 1) push(); app("'\177'%^%gz"); } continue; #endif/*EXTENSION*/ case 'B': /* %B -> BCD 16*(v/10)+v%10 */ if (!onstack) { push(); app("{10}%/%{16}%*%p"); *d++ = i; } else { slowww(); app("Pz%gz%{10}%/%{16}%*%gz"); } app("%{10}%m%+"); continue; case 'D': /* %D -> (v - 2*(v%16)) */ if (!onstack) { push(); *d++ = 'p', *d++ = i; } else { slowww(); app("Pz%gz%gz"); } app("%{16}%m%{2}%*%-"); continue; #endif/*ARCANE*/ case '2': /* %2 -> [%pi]%02d */ case '3': /* %3 -> [%pi]%03d */ if (!onstack) push(); *d++ = '0', *d++ = c, *d++ = 'd'; break; case 'd': /* %d -> [%pi]%d */ #if EXTENSION case 's': /* %s -> [%pi]%s */ #endif if (!onstack) push(); *d++ = c; break; #if ARCANE && EXTENSION case 'f': /* %f discard */ ++i; continue; case 'b': /* %b backup/reuse */ --i; continue; case 'a': /* arithmetic */ if (*s == '=') { if (!onstack) ++i; else { slowww(); app("Pz%"); } } else { check(*s == '-' || *s == '+'); if (!onstack) push(); } s++; check(*s == 'p' || *s == 'c'); s++; *d++ = '\''; check(*d++ = *s++); *d++ = '\\'; if (s[-2] == 'p') { slowww(); app("%'\100'%-"); } if (s[-3] != '=') *d++ = '%', *d++ = s[-3]; continue; case 'C': /* %C -> C100 v%96,then %+ */ if (!onstack) push(); app("'\140'%m%"); #endif case '+': /* %+c -> [%pi]%'c'%+%c */ if (!onstack) push(); *d++ = '\''; check(*d++ = *s++); app("'%+%c"); break; case '.': /* %. -> [%pi]%c */ if (!onstack) push(); *d++ = 'c'; break; } /* * Only get here when a parameter has been output. * so, prepare for new one. */ onstack--; } } /* * expand string `s' in buffer `d'. * the updated destination pointer is returned. */ char * expand __(( const char *_(s), char *_(d) )); char * expand(s, d) register const char *s; register char *d; { register const char *t; register int c; while (c = *s++) { if ((c &= 0377) <= 040) { /* special escapes */ for (t = "\bb\nn\rr\33E\tt\ff s"; *t && (*t++ != c); t++) ; if (*t) *d++ = '\\', *d++ = *t; else *d++ = '^', *d++ = c + '@'; } else if (c >= 0177) { sprintf(d, "\\%03o", c); d += 4; } else switch (c) { case ':': case '^': case ',': case '\\': case '\'': *d++ = '\\'; default: *d++ = c; } } *d = '\0'; return d; } /* * handle escaped characters. * returns updated source string pointer */ #define isoctal(c) ((unsigned)((c) - '0') < 8) const char *do_esc __(( char *_(d), const char *_(s) )); const char * do_esc(d,s) char *d; register const char *s; { register char c = *s++; if (isoctal(c)) { register int count = 3; c &= 7; while (isoctal(*s) && --count) c = (c << 3) + (*s++ & 7); if (c == '\0') c = '\200'; } else { register const char *t = "E\033b\bf\fn\nr\rt\tv\vs e\033"; do { if (*t++ == c) { c = *t; break; } } while (*t++); } *d = c; return(s); } /* * de-escape string `s' into buffer `d'. * returns updated source pointer. */ const char *deescape __(( const char *_(s), char *_(d) )); const char * deescape(s, d) register const char *s; register char *d; { while (*s && *s != ':') { switch (*d = *s++) { case '\\': s = do_esc(d, s); break; case '^': *d = *s++ & 037; break; default: break; } d++; } *d = '\0'; return (s); } /* the definition table entry looks like this */ typedef struct { const short id; /* termcap id */ /*const short offset; /* in `term' struct */ const char name[6]; /* terminfo name */ const char pad_parm; /* padding/parametrization info */ char comment; /* commented out? */ char *translation; /* pointer to translation string */ } Where; #define NPARM(w) (w->pad_parm & PARMASK) #define HASPAD(w) (w->pad_parm & PAD) /* Define an entry in the table. */ #define ENTRY(i,d, ti_name, pad_parm, ti_variable) { \ TC_ID(i,d), \ /*(short) TI_TO_OFFSET(ti_variable),*/ \ name, pad_parm \ }, /* (Note the trailing comma.) */ /* Determine size of table at compile-time. (don't count zero-termination) */ #define TABLESIZE(tab) (sizeof(tab) / sizeof(tab[0]) -1) /* * do a binary search for capability `id' in the table. * (assumes table is lexically ordered on termcap id) */ Where *FindCap __(( const char *_(id), const Where *_(table), int _(size) )); Where * FindCap(id, table, size) const char *id; const Where *table; int size; { register const Where *med_p; register int top = 0, end = size, med; register short key = TC_ID(id[0],id[1]); while (top < end) { med_p = &table[med = (top + end) >> 1]; if (key < med_p->id) { end = med; continue; } if (key > med_p->id) { top = med + 1; continue; } return (Where *) med_p; } return NULL; } /* This re-initializes a table for a new run. */ void ClearTable __(( Where *_(table), int _(size) )); void ClearTable(table, size) register Where *table; register int size; { for (; --size >= 0; table++) table->translation = NULL; } /* These includes define the tables proper. */ Where FlagTab[] = { #define TABLE FLAGS #include "tinfocap.h" {0} }; Where NumTab[] = { #define TABLE NUMBERS #include "tinfocap.h" {0} }; Where StrTab[] = { #define TABLE STRINGS #include "tinfocap.h" {0} }; Where Specials[] = { { ident('k','o'), 0, ".ko" }, /* obsolete "vi" stuff (NOT YET SUPPORTED) */ { ident('m','a'), 0, ".ma" }, { ident('t','c'), 32766, "use" } { 0 } }; #define SPC_KO 0 #define SPC_MA 1 #define SPC_USE 2 /* these pointer arrays define the output order of the capabilities */ Where *sortFlagTab[TABLESIZE(FlagTab)], *sortNumTab[TABLESIZE(NumTab)], *sortStrTab[TABLESIZE(StrTab)], *sortSpecials[TABLESIZE(Specials)]; typedef struct { Where **sorted; Where *unsorted; int size; } Master; #define S_ENTRY(t) { _ANSI_CAT_(sort, t), t, TABLESIZE(t) } Master Tables[] = { S_ENTRY(FlagTab), S_ENTRY(NumTab), S_ENTRY(StrTab), S_ENTRY(Specials) }; /* this sets up a sorted pointer array */ private int cmp(p1, p2) Where **p1, **p2; { return strncmp((*p1)->name, (*p2)->name, sizeof(*p1)->name); } void InitTable __(( Where *_(sort_table)[], Where *_(table), int _(size) )); void InitTable(sort_table, table, size) Where *sort_table[], table[]; int size; { register Where **sp, *tp; register int i; /* setup pointer array... */ for (sp = sort_table, tp = table, i = size; --i >= 0; tp++) *sp++ = tp; /* and sort it. */ qsort(sort_table, size, sizeof *sort_table, cmp); } /* process a termcap entry */ #define TGETFLAG(cap) FindCap(cap, FlagTab, TABLESIZE(FlagTab)) #define TGETNUM(cap) FindCap(cap, NumTab, TABLESIZE(NumTab)) #define TGETSTR(cap) FindCap(cap, StrTab, TABLESIZE(StrTab)) #define TGETSPEC(cap) FindCap(cap, Specials, TABLESIZE(Specials)) char translbuf[4096]; /* main memory for translated entries */ char work[512]; /* work buffer for individual string entries */ const char *fullname; /* * Convert termcap entry to internal representation. * `tentry' should be terminated with 2 null bytes. * Destination pointer `d' is assumed to point in `translbuf'. * returns updated destination pointer. */ char *Input __(( const char *_(s), char *_(d) )); char * Input(s, d) register const char *s; register char *d; { register Where *w; register const char *t; const char *captype; VERBOSE(1, ("%s", s)); /* find name of termcap entry; discard old V6 2-character name (if any) */ cap = NULL; fullname = d; while (*d = *s++) if (*d++ == ':') { --d; break; } *d++ = '\0'; if (d > fullname + 4 && fullname[2] == '|') fullname += 3; for (term = d, t = fullname; *d = *t++; ) if (*d++ == '|') { --d; break; } *d++ = '\0'; if (*s == '\0') { warn("empty termcap entry"); return d; } VERBOSE(0, ("TERM=%s\n", term)); --s; for (;;) { do { /* find next entry. */ if (*s == '\\') { /* for escaped ':'s. */ if (*s++ == '\0') break; } } while (*s && *s++ != ':'); if (isspace(*s)) /* junk; skip to next entry. */ continue; if (*s == '.') /* commented out entry. */ s++; cap = s; if (*s++ == '\0' || *s++ == '\0') break; VERBOSE(1, ("%.2s ", cap)); if (d >= &translbuf[sizeof translbuf]) { warn("translation buffer overflow", NULL); break; } switch(*s) { case '@': if ((w = TGETFLAG(cap)) || (w = TGETNUM(cap)) || (w = TGETSTR(cap)) || (w = TGETSPEC(cap))) { if (w->translation == NULL || w->comment == '.') { VERBOSE(1, ("-> %s@\n", w->name)); w->translation = "@"; w->comment = cap[-1]; } continue; } captype = NULL; break; case '\0': case ':': if (w = TGETFLAG(cap)) { if (w->translation == NULL || w->comment == '.') { VERBOSE(1, ("-> %s\n", w->name)); w->translation = ""; w->comment = cap[-1]; } continue; } captype = "flag"; break; case '#': if (w = TGETNUM(cap)) { if (w->translation == NULL || w->comment == '.') { w->translation = d; do *d++ = *s++; while (isdigit(*s)); *d++ = '\0'; VERBOSE(1, ("-> %s%s\n", w->name, w->translation)); w->comment = cap[-1]; } continue; } captype = "numeric"; break; case '=': s = deescape(s + 1, work); if ((w = TGETSTR(cap)) || (w = TGETSPEC(cap))) { if (w->translation == NULL || w->comment == '.') { if (work[0] == '\0') { warn("null string capability -- use cap@ to turn it off"); break; } w->translation = d; *d++ = '='; /* * Copy entry literally if commented * and `uncomment' option is enabled. */ if (cap[-1] == '.' && uncomment) { strcpy(d, work); while (*d++); w->comment = '\0'; continue; } /* * take care of padding delay. */ t = work; if (HASPAD(w)) { while (isdigit(*t)) ++t; if (*t == '.') { ++t; if (isdigit(*t)) ++t; } if (*t == '*') ++t; } /* * Translate parametrized strings; * just copy others. */ if (NPARM(w) > 0) translate(t, d); else strcpy(d, t); while (*d++); /* skip over */ if (t != work) { d[-1] = '$', *d++ = '<', strncpy(d, work, (int)(t - work)); d += t - work; *d++ = '>'; *d++ = '\0'; } VERBOSE(1, ("-> %s%s\n", w->name, (expand(w->translation, work), work))); w->comment = cap[-1]; } continue; } captype = "string"; break; default: warn("bad capability type"); continue; } /* only get here on unrecognized entries */ if (captype) { if (TGETFLAG(cap)) t = "flag"; else if (TGETNUM(cap)) t = "numeric"; else if (TGETSTR(cap) || TGETSPEC(cap)) t = "string"; else captype = NULL; } if (captype) warn("%s found when %s expected", captype, t); else warn("not supported or unknown"); } return d; } /* fudge in some defaults and other amusing garbage */ const char *StrDefaults[] = { "bl=\7", "cr=\r", "do=\n", "sf=\n" }; void Fudge __(( void )); void Fudge() { register Where *w; register const char **d; /* * Provide some defaults, but only if it does not depend on another * description. */ if (Specials[SPC_USE].translation == NULL) { w = TGETFLAG("xo"); if (w->translation == NULL) w->translation = ""; for (d = StrDefaults; d < &StrDefaults[TABLESIZE(StrDefaults)]; d++) { w = TGETSTR(*d); if (w->translation == NULL) w->translation = *d + 2; } } /* * replace explicit pad chars in the middle of `flash' * with a 50 ms pad request. */ w = TGETSTR("vb"); if (w->translation) { register char *d = w->translation, *t; while (*d) { if (*d++ == (char)'\200') { t = d - 1; while (*d++ == (char)'\200') ; --d; if (d - t >= 5) { strncpy(t, "$<50>", 5); t += 5; if (t != d) /* shift tail */ while (*t++ = *d++); } break; } } } } /* output a termcap entry */ void Output __(( void )); void Output() { register Master *m; register Where *w, **ws, **es; register int i, len; fputs(fullname, stdout); for (m = Tables; m < &Tables[TABLESIZE(Tables)]; m++) { i = width; for (ws = m->sorted, es = &ws[m->size]; ws < es; ) { w = *ws++; if (w->translation == NULL) continue; if (w->comment != '.') w->comment = '\0'; sprintf(work, "%.1s%s", &w->comment, w->name); expand(w->translation, work + strlen(work)); if ((i += 2 + (len = strlen(work))) >= width) fputs(",\n\t", stdout), i = len + 8; else fputs(", ", stdout); fputs(work, stdout); } } fputs(",\n", stdout); } /* name matching */ int wildmat __(( const char *_(name), const char *_(pattern) )); int Match __(( const char *_(name), const char *_(ent) )); int Match(name, ent) const char *name, *ent; { register const char *s = ent; register char *d = work; for (s = ent; *s && *s != ':'; ) { for (d = work; (*d = *s++); ) { if (*d == '|') break; if (*d++ == ':') { --d, --s; break; } } /* ignore 2-character name (if any) */ if (d == ent + 2 && *d == '|') continue; *d = '\0'; if (wildmat(work, name)) return TRUE; } return FALSE; } int MatchVec __(( char *_(vec)[], const char *_(ent) )); int MatchVec(vec, ent) register char *vec[]; const char *ent; { if (*vec == NULL) /* empty list matches anything */ return TRUE; do { if (Match(*vec++, ent)) return TRUE; } while (*vec); return FALSE; } /* ** Do shell-style pattern matching for ?, \, [], and * characters. ** Might not be robust in face of malformed patterns; e.g., "foo[a-" ** could cause a segmentation violation. ** ** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. ** Modified 05-Mar-89 by Tom Hageman, make it robust; ** use [^...] and [!...] as character set negations; ** treat ']' and '-' as ordinary characters if they appear as the ** first character of a set (following eventual negation), ** for example []...], [!-...]; ** also treat '-' as a normal character if it follows a range [..x-y-..] ** or it appears as the last character [...-] in a set. ** Modified 05-Feb-90 TRH/GCThW fix bug: matched should be initialized ** (to FALSE) before range match. */ int wildmat(s, p) register const char *s; register const char *p; { register char c; register int matched; register int reverse; while (c = *p++) switch (c) { case '\\': /* Literal match with following character; fall through. */ if ((c = *p++) == '\0') return(FALSE); default: if (*s++ != c) return(FALSE); continue; case '?': /* Match anything. */ if (*s++ == '\0') return(FALSE); continue; case '*': if (*p) { /* Trailing star matches everything. */ while (wildmat(s, p) == FALSE) if (*++s == '\0') return FALSE; } return (TRUE); case '[': matched = FALSE; /* [^...] or [!...] means inverse character class. */ if (reverse = (*p == '!' || *p == '^')) p++; /* ']' is treated as normal character it it's the first one */ if (c = *p++) do { if (*p == '-') { if (*++p && *p != ']') { if (*s <= *p++ && c <= *s) matched = TRUE; continue; } --p; } if (*s == c) matched = TRUE; } while ((c = *p++) && c != ']'); if (matched == reverse || c == '\0') return(FALSE); ++s; continue; } return(*s == '\0'); } char inbuf[2048]; /* read a "logical record" from a file; handles escaped newlines */ char *fgetlr __(( char *_(buf), int _(bufsize), FILE *_(fp) )); char * fgetlr (bp, bpsize, fp) char *bp; int bpsize; register FILE *fp; { register int c; register char *cp = bp, *ep = &cp[bpsize - 1]; while (cp < ep && (c = getc(fp)) != EOF) { if ((*cp++ = c) == '\n') { if (--cp > bp && cp[-1] == '\\') { cp[-1] = *cp; /* i.e. newline */ } else { cp++; break; } } } *cp = '\0'; return (cp == bp) ? NULL : bp; } /* fgetlr */ #define DUMP 1 int getent __(( FILE *_(f), int _(dump) )); int getent(f, dump) register FILE *f; { register const char *s; while (s = fgetlr(inbuf, sizeof inbuf, f)) { if (*s == '#' || isspace(*s)) { if (dump) fputs(s, stdout); continue; } if (*(s += strlen(s) - 1) != '\n') { if (dump) { fprintf(stderr, "warning: termcap entry \"%.30s...\" truncated\n", s); err++; } /* ignore remainder... it's the best we can do */ while (getc(f) != '\n' && !feof(f)) ; } return 0; } return EOF; } /* store an entry in memory, if there is room */ typedef struct store store; struct store { store *next; /* link */ char text[1]; /* placeholder for entry */ }; store *StoreHead; void Store __(( const char *_(ent) )); void Store(ent) const char *ent; { register store *new = malloc(strlen(ent) + 1 + sizeof(store)); if (new) { VERBOSE(2, ("Storing entry \"%.30s\"\n", ent)); strcpy(new->text, ent); new->next = StoreHead; StoreHead = new; } else fprintf(stderr, "[cannot store entry \"%.30s\"]\n", ent); } /* * Get a (possibly previously stored) entry. * returns NULL if not found. */ const char *Get __(( FILE *_(f), const char *_(name) )); const char * Get(f, name) FILE *f; const char *name; { register store *p; register long filepos; /* first check in-memory storage */ for (p = StoreHead; p; p = p->next) { if (Match(name, p->text)) { VERBOSE(2, ("Got entry \"%.30s\" (in store)\n", p->text)); return p->text; } } /* then try to read ahead in the file--if we can fseek it */ if (isatty(fileno(f)) || (filepos = ftell(f)) < 0) { fprintf(stderr, "[cannot seek in this file]"); return NULL; } while (getent(f, !DUMP) == 0) if (Match(name, inbuf)) { fseek(f, filepos, 0); VERBOSE(2, ("Got entry \"%.30s\" (in file)\n", inbuf)); return inbuf; } fseek(f, filepos, 0); /* restore old position */ return NULL; } /* process a termcap file */ void process __(( FILE *_(f), char *_(matchlist)[] )); void process(f, matchlist) register FILE *f; char *matchlist[]; { register const char *s; register char *d; register Master *m; while (getent(f, pass_line_comments) == 0) { if (resolve_tc) Store(inbuf); if (!MatchVec(matchlist, inbuf)) /* did we want it? */ continue; for (m = Tables; m < &Tables[TABLESIZE(Tables)]; m++) ClearTable(m->unsorted, m->size); d = Input(inbuf, translbuf); if (resolve_tc) { const char *save_fullname = fullname, *save_term = term; int level = 10; while (Specials[SPC_USE].translation) { if (--level < 0) { warn("too many \"tc\" directives (cyclical?)"); err++; break; } if ((s = Get(f, Specials[SPC_USE].translation + 1)) == NULL) { warn("cannot resolve \"tc%s\"", Specials[SPC_USE].translation); err++; break; } Specials[SPC_USE].translation = NULL; d = Input(s, d); } /* restore globals */ fullname = save_fullname; term = save_term; } Fudge(); Output(); } } /* command line options */ const char *_pname; void usage __(( void )); void usage() { fprintf(stderr, "usage: %s -[? 1 c t x v w#] termcap-file [entries]\n", _pname); fprintf(stderr, "options:\n\ -? display version\n\ -1 single-column\n\ -c suppress # comments\n\ -t uncomment capabilities commented with '.' (and don't translate)\n\ -v increase verbosity (for debugging)\n\ -x resolve \"tc=xxx\" dependencies\n\ -wN set line width to N columns (D=%d)\n", width); exit(2); } void vsn __(( void )); void vsn() { fprintf(stderr, "%s: %s\n", _pname, version); exit(2); } main(argc, argv) int argc; char *argv[]; { register const char *arg; register FILE *inf; register int i; register int err = 0; _pname = *argv++; /* get command line options */ while ((arg = *argv++) && *arg == '-' && arg[1]) { ++arg; while (*arg) { switch (*arg++) { case '1': width = 0; continue; case 'c': pass_line_comments = FALSE; continue; case 't': uncomment++; continue; case 'w': if ((width = atoi(arg)) < 20) width = DEF_WIDTH; break; case 'v': verbose++; continue; case 'x': resolve_tc++; continue; case '?': vsn(); default: usage(); } break; } } for (i = TABLESIZE(Tables); --i >= 0; ) InitTable(Tables[i].sorted, Tables[i].unsorted, Tables[i].size); if (arg == NULL) { if (isatty(0)) usage(); inf = stdin; --argv; } else if (strcmp(arg, "-") == 0) { inf = stdin; } else if ((inf = fopen(arg, "r")) == NULL) { fprintf(stderr, "%s: cannot open \"%s\"\n", _pname, arg); return 1; } process(inf, argv); return err; }