#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "oq.h" #include "pollloop.h" #include "stdio-util.h" extern const char *__progname; typedef enum { DEST_NICK = 1, DEST_CHANNEL, } DESTTYPE; typedef enum { BIND_NIL = 1, BIND_KEYMAP, BIND_FUNCTION, } BINDTYPE; typedef enum { CT_NONE = 1, CT_TYPE, CT_CONN, CT_FROM, CT_CONTENT, CT_USE, CT_FAIL, } CLAUSETYPE; typedef enum { CDK_NORMAL = 1, CDK_STANDOUT, CDK_TAB, } CHARDISPKIND; typedef struct sconn SCONN; typedef struct ctx CTX; typedef struct dest DEST; typedef struct filter FILTER; typedef struct fmatch FMATCH; typedef struct fclause FCLAUSE; typedef struct binding BINDING; typedef struct keymap KEYMAP; typedef struct keyfn KEYFN; typedef struct priminit PRIMINIT; typedef struct bindinit BINDINIT; typedef struct cf CF; typedef struct mlmsg MLMSG; typedef struct line_ops LINE_OPS; typedef struct restr RESTR; typedef struct hline HLINE; typedef struct psconn PSCONN; typedef struct cfb CFB; typedef struct chardisp CHARDISP; typedef struct bal BAL; typedef struct configparse CONFIGPARSE; typedef struct cpcleanup CPCLEANUP; typedef struct listen LISTEN; typedef struct ircline IRCLINE; struct ircline { char *conn; char *type; char *prefix; char *body; } ; struct bal { unsigned char *b; int a; int l; } ; struct cpcleanup { CPCLEANUP *link; unsigned int when; #define CPCU_NORMAL 0x00000001 #define CPCU_ERROR 0x00000002 void (*fn)(void *); void *arg; } ; struct configparse { const char *lp; int x; BAL token; FILE *ef; void (*throw)(void); CPCLEANUP *cleanup; FILTER *curfilter; CTX *curctx; FMATCH **matchtree; FMATCH ***matchtreet; int amatchtree; int treedepth; LISTEN **clt; char *use_context; } ; struct chardisp { CHARDISPKIND kind; char *s; int l; } ; struct cfb { CF *b; int a; int n; } ; struct psconn { SCONN *sc; const char **portnum; struct addrinfo *ai0; struct addrinfo *ai; char *aitext; int s; int id; } ; struct hline { HLINE *flink; HLINE *blink; char *content; int dcols; int ndlines; CFB *dlines; } ; struct restr { char *pattern; regex_t re; } ; struct line_ops { void (*finish)(const char *); void (*abort)(void); } ; #define LINE_OPS_INIT(name) { &name##_finish, &name##_abort } struct mlmsg { MLMSG *link; char *msg; int xfd; int xid; struct itimerval itv; } ; struct cf { unsigned char c; unsigned char f; #define CF_STANDOUT 0x01 } ; struct priminit { const char *name; void (*impl)(unsigned char); } ; struct keyfn { KEYFN *link; const char *name; void (*impl)(unsigned char); } ; struct bindinit { const char *seq; const char *fxn; } ; struct binding { BINDTYPE type; union { /* nothing for BIND_NIL */ KEYMAP *keymap; KEYFN *function; } ; } ; struct keymap { KEYFN *fxns; BINDING map[256]; } ; struct ctx { CTX *link; char *name; DEST *tell; LISTEN *listen; } ; struct listen { LISTEN *link; char *filter; char *output; } ; struct filter { FILTER *link; char *name; int accept; unsigned int flags; #define FF_BUSY 0x00000001 FMATCH *match; } ; struct fmatch { FMATCH *link; int accept; FCLAUSE *clauses; FMATCH *except; } ; struct fclause { FCLAUSE *link; CLAUSETYPE t; union { RESTR type; RESTR conn; RESTR from; RESTR content; char *use; char *fail; } ; } ; struct dest { DEST *link; char *conn; char *what; } ; struct sconn { SCONN *link; char *name; char *host; int fd; char *nick; OQ oq; int id; BAL input; char *myuser; char *myhost; char *myname; } ; static char *myuser; static char *myhost; static char *myname; static SCONN *sconns; static CTX *contexts; static CTX *curctx; static BAL iline; static int icursor; static int imark; static CFB dline; static int dlineo; static int dcursor; static const char *iprompt; static int ipromptlen; static WINDOW *w_output; static WINDOW *w_modeline; static WINDOW *w_input; static unsigned int redraw; #define REDRAW_INPUT 0x00000001 #define REDRAW_ICURSOR 0x00000002 #define REDRAW_OUTPUT 0x00000004 #define REDRAW_MODELINE 0x00000008 #define REDRAW_FULL 0x00000010 static const LINE_OPS *line_ops; static void *line_priv; static KEYMAP *rootmap; static KEYMAP *lnextmap; static KEYMAP *curmap; static int line_is_cmd; static int input_id; static int update_id; static MLMSG *mlmsgs; static MLMSG **mlmsgst; static HLINE *hhead; static HLINE *htail; static CHARDISP chardisp[256]; static CHARDISP markdisp; static FILTER *filters; static const LINE_OPS default_line_ops; // forward #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) #define HL(x) (((x)*2)+1) #define HR(x) (((x)+1)*2) #define HU(x) (((x)-1)/2) #define Cisspace(x) isspace((unsigned char)(x)) #define Cisalnum(x) isalnum((unsigned char)(x)) #define Cisdigit(x) isdigit((unsigned char)(x)) /* Designed to be used as a if it were a "case ..." */ #if defined(EAGAIN) # if defined(EWOULDBLOCK) # if EAGAIN == EWOULDBLOCK # define NONBLOCK_CASES case EWOULDBLOCK # else # define NONBLOCK_CASES case EWOULDBLOCK: case EAGAIN # endif # else # define NONBLOCK_CASES case EAGAIN # endif #else # if defined(EWOULDBLOCK) # define NONBLOCK_CASES case EWOULDBLOCK # else # error "Need either EWOULDBLOCK or EAGAIN" # endif #endif #define CHARDISP_INIT_NORMAL 0 #define CHARDISP_INIT_STANDOUT 1 #define CHARDISP_INIT_TAB 2 #define CHARDISP_INIT_HACK2(x) #x #define CHARDISP_INIT_HACK(x) CHARDISP_INIT_HACK2(x) #define NM CHARDISP_INIT_HACK(\CHARDISP_INIT_NORMAL) #define SO CHARDISP_INIT_HACK(\CHARDISP_INIT_STANDOUT) #define TB CHARDISP_INIT_HACK(\CHARDISP_INIT_TAB) static const unsigned char *chardisp_init[256] = { SO"^\100", SO"^\101", SO"^\102", SO"^\103", SO"^\104", SO"^\105", SO"^\106", SO"^\107", SO"^\110", TB, SO"^\112", SO"^\113", SO"^\114", SO"^\115", SO"^\116", SO"^\117", SO"^\120", SO"^\121", SO"^\122", SO"^\123", SO"^\124", SO"^\125", SO"^\126", SO"^\127", SO"^\130", SO"^\131", SO"^\132", SO"^\133", SO"^\134", SO"^\135", SO"^\136", SO"^\137", NM"\040", NM"\041", NM"\042", NM"\043", NM"\044", NM"\045", NM"\046", NM"\047", NM"\050", NM"\051", NM"\052", NM"\053", NM"\054", NM"\055", NM"\056", NM"\057", NM"\060", NM"\061", NM"\062", NM"\063", NM"\064", NM"\065", NM"\066", NM"\067", NM"\070", NM"\071", NM"\072", NM"\073", NM"\074", NM"\075", NM"\076", NM"\077", NM"\100", NM"\101", NM"\102", NM"\103", NM"\104", NM"\105", NM"\106", NM"\107", NM"\110", NM"\111", NM"\112", NM"\113", NM"\114", NM"\115", NM"\116", NM"\117", NM"\120", NM"\121", NM"\122", NM"\123", NM"\124", NM"\125", NM"\126", NM"\127", NM"\130", NM"\131", NM"\132", NM"\133", NM"\134", NM"\135", NM"\136", NM"\137", NM"\140", NM"\141", NM"\142", NM"\143", NM"\144", NM"\145", NM"\146", NM"\147", NM"\150", NM"\151", NM"\152", NM"\153", NM"\154", NM"\155", NM"\156", NM"\157", NM"\160", NM"\161", NM"\162", NM"\163", NM"\164", NM"\165", NM"\166", NM"\167", NM"\170", NM"\171", NM"\172", NM"\173", NM"\174", NM"\175", NM"\176", SO"^\077", SO"^\340", SO"^\301", SO"^\302", SO"^\303", SO"^\304", SO"^\305", SO"^\306", SO"^\307", SO"^\310", SO"^\311", SO"^\312", SO"^\313", SO"^\314", SO"^\315", SO"^\316", SO"^\317", SO"^\320", SO"^\321", SO"^\322", SO"^\323", SO"^\324", SO"^\325", SO"^\326", SO"^\327", SO"^\330", SO"^\331", SO"^\332", SO"^\333", SO"^\334", SO"^\335", SO"^\336", SO"^\337", NM"\240", NM"\241", NM"\242", NM"\243", NM"\244", NM"\245", NM"\246", NM"\247", NM"\250", NM"\251", NM"\252", NM"\253", NM"\254", NM"\255", NM"\256", NM"\257", NM"\260", NM"\261", NM"\262", NM"\263", NM"\264", NM"\265", NM"\266", NM"\267", NM"\270", NM"\271", NM"\272", NM"\273", NM"\274", NM"\275", NM"\276", NM"\277", NM"\300", NM"\301", NM"\302", NM"\303", NM"\304", NM"\305", NM"\306", NM"\307", NM"\310", NM"\311", NM"\312", NM"\313", NM"\314", NM"\315", NM"\316", NM"\317", NM"\320", NM"\321", NM"\322", NM"\323", NM"\324", NM"\325", NM"\326", NM"\327", NM"\330", NM"\331", NM"\332", NM"\333", NM"\334", NM"\335", NM"\336", NM"\337", NM"\340", NM"\341", NM"\342", NM"\343", NM"\344", NM"\345", NM"\346", NM"\347", NM"\350", NM"\351", NM"\352", NM"\353", NM"\354", NM"\355", NM"\356", NM"\357", NM"\360", NM"\361", NM"\362", NM"\363", NM"\364", NM"\365", NM"\366", NM"\367", NM"\370", NM"\371", NM"\372", NM"\373", NM"\374", NM"\375", NM"\376", NM"\377" }; #undef NM #undef SO #undef TB static void bal_init(BAL *b) { b->b = 0; b->a = 0; b->l = 0; } static void bal_space(BAL *b, int n) { if (n > b->a) b->b = realloc(b->b,b->a=n+16); } static void bal_room(BAL *b, int n) { bal_space(b,b->l+n); } static void bal_grow(BAL *b, int n) { b->l += n; } static void bal_shrink(BAL *b, int n) { b->l -= n; } static void bal_save(BAL *b, unsigned char c) { bal_room(b,1); b->b[b->l++] = c; } static void bal_save_block(BAL *b, const char *s, int l) { if (l < 1) return; bal_room(b,l); bcopy(s,b->b+b->l,l); b->l += l; } static void bal_save_string(BAL *b, const char *s) { bal_save_block(b,s,strlen(s)); } static int bal_len(BAL *b) { return(b->l); } static void *bal_buf(BAL *b) { return(b->b); } static void bal_reset(BAL *b) { b->l = 0; } static void bal_done(BAL *b) { free(b->b); bal_init(b); } typedef enum { AK_LIBERAL = 1, AK_GENERAL, AK_NICKNAME, AK_HOSTNAME, } ARGKIND; static int badarg(const char *s, ARGKIND kind) { int i; for (i=0;s[i];i++) { switch (kind) { default: abort(); break; case AK_LIBERAL: switch (s[i]) { case '\r': case '\n': return(1); break; } break; case AK_GENERAL: switch (s[i]) { case '\r': case '\n': case ':': case ' ': return(1); break; } break; case AK_NICKNAME: switch (s[i]) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '[': case ']': case '\\': case '^': case '_': case '`': case '{': case '|': case '}': case '-': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (i == 0) return(1); break; default: return(1); break; } break; case AK_HOSTNAME: switch (s[i]) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '_': case '-': case '.': break; default: return(1); break; } break; } } return(0); } static FILTER *new_filter(char *name, int act) { FILTER *f; f = malloc(sizeof(FILTER)); f->name = name; f->accept = act; f->flags = 0; f->match = 0; f->link = filters; filters = f; return(f); } static void add_builtin_filters(void) { new_filter(strdup("ALWAYS"),1); new_filter(strdup("NEVER"),0); } static void init(void) { int uid; struct passwd *pw; char *user; int l; sconns = 0; contexts = 0; curctx = 0; uid = getuid(); user = getenv("USER"); myuser = strdup(user); l = 8; while (1) { myhost = malloc(l); gethostname(myhost,l-1); myhost[l-1] = '\0'; if (strlen(myhost) < l-1) break; free(myhost); l <<= 1; } pw = getpwnam(user); if (!pw || (pw->pw_uid != uid)) { pw = getpwuid(uid); } if (pw) { char *comma; myname = strdup(pw->pw_gecos); comma = index(myname,','); if (comma) *comma = '\0'; } else { // XXX should allow config file to supply it fprintf(stderr,"%s: who are you?\n",__progname); exit(1); } if (badarg(myuser,AK_GENERAL)) { // XXX should allow config file to override it fprintf(stderr,"%s: user name %s is unacceptable for IRC\n",__progname,myuser); exit(1); } if (badarg(myhost,AK_GENERAL)) { // XXX should allow config file to override it fprintf(stderr,"%s: host name %s is unacceptable for IRC\n",__progname,myhost); exit(1); } if (badarg(myname,AK_LIBERAL)) { // XXX should allow config file to override it fprintf(stderr,"%s: `real' name %s is unacceptable for IRC\n",__progname,myname); exit(1); } hhead = 0; htail = 0; filters = 0; add_builtin_filters(); } static void configparse_cleanup(CONFIGPARSE *cp, unsigned int when) { CPCLEANUP *cu; while ((cu = cp->cleanup)) { cp->cleanup = cu->link; if (cu->when & when) (*cu->fn)(cu->arg); free(cu); } } static void configparse_error(CONFIGPARSE *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void configparse_error(CONFIGPARSE *cp, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(cp->ef,"config file error: %s\nline: %s\n",s,cp->lp); free(s); configparse_cleanup(cp,CPCU_ERROR); (*cp->throw)(); abort(); } static const char *configparse_token(CONFIGPARSE *cp) { char q; int b; char ch; while (Cisspace(cp->lp[cp->x])) cp->x ++; if (! cp->lp[cp->x]) return(0); bal_reset(&cp->token); q = 0; b = 0; while (1) { ch = cp->lp[cp->x++]; if (! ch) { cp->x --; if (b) { configparse_error(cp,"nothing following \\"); } if (q) { configparse_error(cp,"unclosed %c",q); } if (b || q) return(0); bal_save(&cp->token,'\0'); return(bal_buf(&cp->token)); } if (b) { bal_save(&cp->token,ch); b = 0; } else if (ch == '\\') { b = 1; } else if (q) { if (ch == q) { q = 0; } else { bal_save(&cp->token,ch); } } else { switch (ch) { case '\\': b = 1; break; case '"': case '\'': q = ch; break; default: if (Cisspace(ch)) { bal_save(&cp->token,'\0'); return(bal_buf(&cp->token)); } bal_save(&cp->token,ch); break; } } } return(0); } static const char *configparse_trailingtext(CONFIGPARSE *cp) { while (Cisspace(cp->lp[cp->x])) cp->x ++; return(cp->lp+cp->x); } static void configparse_init(CONFIGPARSE *cp) { cp->lp = 0; cp->x = 0; bal_init(&cp->token); cp->ef = 0; cp->throw = 0; cp->cleanup = 0; cp->curfilter = 0; cp->curctx = 0; cp->matchtree = 0; cp->matchtreet = 0; cp->amatchtree = 0; cp->treedepth = -1; cp->clt = 0; cp->use_context = 0; } static void configparse_done(CONFIGPARSE *cp) { bal_done(&cp->token); free(cp->matchtree); free(cp->matchtreet); configparse_cleanup(cp,0); } static void configparse_addcleanup(CONFIGPARSE *cp, void (*fn)(void *), void *arg, unsigned int when) { CPCLEANUP *cu; cu = malloc(sizeof(CPCLEANUP)); cu->when = when; cu->fn = fn; cu->arg = arg; cu->link = cp->cleanup; cp->cleanup = cu; } static void cfbinit(CFB *b) { b->b = 0; b->a = 0; b->n = 0; } static void cfbroom(CFB *b, int n) { if (b->n+n > b->a) { b->b = realloc(b->b,(b->a=b->n+n+8)*sizeof(*b->b)); } } static void cfbappend(CFB *b, unsigned char ch, unsigned char fl) { if (b->n >= b->a) abort(); b->b[b->n++] = (CF){.c=ch,.f=fl}; } static void cfbappend_chardisp(CFB *b, CHARDISP *cd, unsigned char fl) { int i; switch (cd->kind) { case CDK_STANDOUT: fl ^= CF_STANDOUT; /* fall through */ case CDK_NORMAL: cfbroom(b,cd->l); for (i=0;il;i++) cfbappend(b,cd->s[i],fl); break; case CDK_TAB: cfbroom(b,8); do cfbappend(b,' ',fl^CF_STANDOUT); while (b->n % 8); break; default: abort(); break; } } static void format_history_for_display(HLINE *h, int cols) { CFB *b; int i; CHARDISP *cd; int l0; h->dcols = cols; h->ndlines = 1; h->dlines = malloc(sizeof(CFB)); b = &h->dlines[0]; cfbinit(b); for (i=0;h->content[i];i++) { cd = &chardisp[(unsigned char)h->content[i]]; l0 = b->n; cfbappend_chardisp(b,cd,0); if (b->n < cols) continue; if ((b->n > cols) && (cd->kind != CDK_TAB) && (l0 > 0)) { i --; b->n = l0; } h->ndlines ++; h->dlines = realloc(h->dlines,h->ndlines*sizeof(*h->dlines)); b = &h->dlines[h->ndlines-1]; cfbinit(b); } } static void printcf(WINDOW *w, CF *d, int l) { unsigned int pf; pf = CF_STANDOUT + 1; /* neither 0 nor CF_STANDOUT */ for (;l>0;l--,d++) { if (d->f & CF_STANDOUT) wstandout(w); else wstandend(w); waddch(w,d->c); } wstandend(w); } static void scroll_onto_output(HLINE *h) { int i; int nl; if (h->dcols != COLS) format_history_for_display(h,COLS); nl = h->ndlines; if (nl > LINES-2) nl = LINES - 2; wscrl(w_output,nl); for (i=h->ndlines-nl;indlines;i++) { wmove(w_output,LINES-2-h->ndlines+i,0); printcf(w_output,h->dlines[i].b,h->dlines[i].n); wclrtoeol(w_output); } redraw |= REDRAW_OUTPUT; } static void add_history(const char *s) { int l; HLINE *h; l = strlen(s); h = malloc(sizeof(HLINE)+l+1); bcopy(s,h+1,l+1); h->content = (void *)(h+1); h->dcols = -1; h->ndlines = 0; h->dlines = 0; h->flink = 0; h->blink = htail; htail = h; if (h->blink) h->blink->flink = h; else hhead = h; h->dcols = -1; scroll_onto_output(h); } static void sprintf_output(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void sprintf_output(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); add_history(s); } static void sconn_destroy(SCONN *sc) { free(sc->name); free(sc->host); if (sc->fd >= 0) close(sc->fd); free(sc->nick); oq_flush(&sc->oq); bal_done(&sc->input); free(sc->myuser); free(sc->myhost); free(sc->myname); free(sc); } static int sconn_destroy_block(void *scv) { remove_block_id(((SCONN *)scv)->id); sconn_destroy(scv); return(BLOCK_LOOP); } static char *copy_blk_to_str(const void *data, int len) { char *rv; rv = malloc(len+1); bcopy(data,rv,len); rv[len] = '\0'; return(rv); } static FILTER *find_filter_by_name(const char *name) { FILTER *f; for (f=filters;f;f=f->link) if (! strcmp(f->name,name)) return(f); return(0); } static int restr_match(const RESTR *r, const char *txt, const char *fname) { int e; char *es; int esl; e = regexec(&r->re,txt,0,0,0); switch (e) { case 0: return(1); break; case REG_NOMATCH: return(0); break; } esl = regerror(e,&r->re,0,0); es = malloc(esl); regerror(e,&r->re,es,esl); sprintf_output("regex match error [%s] in filter %s, pattern: %s",es,fname,r->pattern); free(es); return(-1); } static int filter_passes_line(FILTER *f, IRCLINE *l) { int accept; FMATCH *m; FCLAUSE *c; FILTER *sub; int subrv; if (f->flags & FF_BUSY) { sprintf_output("Attempted recursive use of filter `%s'",f->name); return(-1); } accept = f->accept; m = f->match; while <"matchlist"> (1) { if (! m) return(accept); do <"nextmatch"> { for (c=m->clauses;c;c=c->link) { switch (c->t) { default: abort(); break; case CT_TYPE: subrv = restr_match(&c->type,l->type,f->name); break; case CT_CONN: subrv = restr_match(&c->type,l->conn,f->name); break; case CT_FROM: subrv = restr_match(&c->type,l->prefix,f->name); break; case CT_CONTENT: subrv = restr_match(&c->type,l->body,f->name); break; case CT_USE: sub = find_filter_by_name(c->use); if (sub) { subrv = filter_passes_line(sub,l); } else { sprintf_output("use= filter %s undefined (in filter %s)",c->use,f->name); } break; case CT_FAIL: sub = find_filter_by_name(c->fail); if (sub) { subrv = filter_passes_line(sub,l); if (subrv >= 0) subrv = ! subrv; } else { sprintf_output("fail= filter %s undefined (in filter %s)",c->fail,f->name); } break; } if (subrv < 0) return(-1); if (! subrv) continue <"nextmatch">; } accept = m->accept; m = m->except; continue <"matchlist">; } while (0); m = m->link; } } static void scan_replacement(const char *s, int len, void (*ord)(const void *, int), void (*rep)(unsigned int)) { int i; int i0; int j; unsigned int v; i0 = -1; for <"scan"> (i=0;i= 0) { (*ord)(s+i0,i-i0); i0 = -1; } i ++; if (i >= len) break; if (Cisdigit(s[i])) { (*rep)(s[i]-'0'); } else if (s[i] == '(') { v = 0; for <"num"> (j=i+1;;j++) { if (j >= len) break <"scan">; switch (s[j]) { case ')': break <"num">; case '0': v = v * 10 ; break; case '1': v = (v * 10) + 1; break; case '2': v = (v * 10) + 2; break; case '3': v = (v * 10) + 3; break; case '4': v = (v * 10) + 4; break; case '5': v = (v * 10) + 5; break; case '6': v = (v * 10) + 6; break; case '7': v = (v * 10) + 7; break; case '8': v = (v * 10) + 8; break; case '9': v = (v * 10) + 9; break; default: break <"scan">; } } (*rep)(v); i = j; } else { i0 = i; } } else { if (i0 < 0) i0 = i; } } if (i0 >= 0) (*ord)(s+i0,i-i0); } static void gen_patsubst(BAL *str, BAL *pat, BAL *rep, BAL *nom, BAL *to) { regex_t re; regmatch_t *rm; int e; int maxrep; void gen_regerror(int err) { int l; char *emsg; l = regerror(err,&re,0,0); emsg = malloc(l); regerror(err,&re,emsg,l); bal_save_string(to,"[regcomp: "); bal_save_string(to,emsg); bal_save_string(to,"]"); free(emsg); } void ord_skip(const void *data __attribute__((__unused__)), int len __attribute__((__unused__))) { } void rep_count(unsigned int n) { if (n > maxrep) maxrep = n; } void ord_gen(const void *data, int len) { bal_save_block(to,data,len); } void rep_gen(unsigned int n) { bal_save_block(to,rm[n].rm_so+(char *)bal_buf(str),rm[n].rm_eo-rm[n].rm_so); } re.re_endp = bal_len(pat) + (char *)bal_buf(pat); e = regcomp(&re,bal_buf(pat),REG_EXTENDED|REG_PEND); if (e) { gen_regerror(e); return; } maxrep = -1; scan_replacement(bal_buf(rep),bal_len(rep),&ord_skip,&rep_count); if (maxrep < 0) maxrep = 0; rm = malloc((maxrep+1)*sizeof(regmatch_t)); rm[0].rm_so = 0; rm[0].rm_eo = bal_len(str); e = regexec(&re,bal_buf(str),maxrep+1,rm,REG_STARTEND); switch (e) { case 0: scan_replacement(bal_buf(rep),bal_len(rep),&ord_gen,&rep_gen); break; case REG_NOMATCH: bal_save_block(to,bal_buf(nom),bal_len(nom)); break; default: gen_regerror(e); break; } regfree(&re); } static int gen_substituted(FILE *f, char term, BAL *to, IRCLINE *l) { int bl; char *bb; int c; while (1) { c = fgetc(f); if (c == EOF) return(1); bl = bal_len(to); bb = bal_buf(to); if (c == ')') { if ((bl >= 6) && !bcmp(bb+bl-6,"$(TYPE",6)) { bal_shrink(to,6); bal_save_string(to,l->type); continue; } else if ((bl >= 8) && !bcmp(bb+bl-8,"$(PREFIX",8)) { bal_shrink(to,8); bal_save_string(to,l->prefix); continue; } else if ((bl >= 11) && !bcmp(bb+bl-11,"$(CONN_NAME",11)) { bal_shrink(to,11); bal_save_string(to,l->conn); continue; } else if ((bl >= 9) && !bcmp(bb+bl-9,"$(CONTENT",9)) { bal_shrink(to,9); bal_save_string(to,l->body); continue; } } if ((bl >= 3) && !Cisalnum(c) && !bcmp(bb+bl-3,"$(S",3)) { BAL str; BAL pat; BAL rep; BAL nom; bal_shrink(to,3); bal_init(&str); bal_init(&pat); bal_init(&rep); bal_init(&nom); if ( !gen_substituted(f,c,&str,l) && !gen_substituted(f,c,&pat,l) && !gen_substituted(f,c,&rep,l) && !gen_substituted(f,c,&nom,l) ) { gen_patsubst(&str,&pat,&rep,&nom,to); } bal_done(&str); bal_done(&pat); bal_done(&rep); bal_done(&nom); c = fgetc(f); if (c != ')') return(1); continue; } if (c == term) return(0); bal_save(to,c); } } static void process_listen_string(const char *str, IRCLINE *l) { BAL rv; FILE *f; bal_init(&rv); f = fopen_rstr(str,strlen(str)); gen_substituted(f,'\0',&rv,l); fclose(f); bal_save(&rv,'\0'); add_history(bal_buf(&rv)); bal_done(&rv); } static void process_listen_output(const char *outstr, IRCLINE *l) { if (outstr[0] == '"') { process_listen_string(outstr+1,l); } else { sprintf_output("invalid listen key character `%c'",outstr[0]); #if 0 sprintf_output("[%s] %s%s%s%s %s", l->conn, l->prefix[0] ? ":" : "", l->prefix, l->prefix[0] ? " " : "", l->type, l->body ); #endif } } static void sconn_input(SCONN *sc, const void *data, int len) { IRCLINE il; const char *dp; int i; int i0; dp = data; il.conn = strdup(sc->name); i = 0; if ((i < len) && (dp[i] == ':')) { i0 = ++i; while ((i < len) && !Cisspace(dp[i])) i ++; il.prefix = copy_blk_to_str(dp+i0,i-i0); } else { il.prefix = strdup(""); } while ((i < len) && Cisspace(dp[i])) i ++; i0 = i; while ((i < len) && !Cisspace(dp[i])) i ++; il.type = copy_blk_to_str(dp+i0,i-i0); while ((i < len) && Cisspace(dp[i])) i ++; il.body = copy_blk_to_str(dp+i,len-i); if (curctx) { LISTEN *l; for (l=curctx->listen;l;l=l->link) { FILTER *f; f = find_filter_by_name(l->filter); if (f) { if (filter_passes_line(f,&il) > 0) process_listen_output(l->output,&il); } else { sprintf_output("Filter `%s' not defined",l->filter); } } } } static void rd_sconn(void *scv) { SCONN *sc; unsigned char ibuf[8192]; int ni; int i; unsigned char pc; unsigned char c; sc = scv; ni = read(sc->fd,&ibuf[0],sizeof(ibuf)); if (ni < 0) { switch (errno) { NONBLOCK_CASES: return; } sprintf_output("%s: read: %s",sc->host,strerror(errno)); remove_poll_id(sc->id); sc->id = add_block_fn(&sconn_destroy_block,sc); return; } if (ni == 0) { sprintf_output("%s: read EOF",sc->host); remove_poll_id(sc->id); sc->id = add_block_fn(&sconn_destroy_block,sc); return; } i = bal_len(&sc->input); pc = (i > 0) ? ((unsigned char *)bal_buf(&sc->input))[i-1] : '\0'; for (i=0;iinput,1); sconn_input(sc,bal_buf(&sc->input),bal_len(&sc->input)); bal_reset(&sc->input); pc = '\0'; } else { bal_save(&sc->input,c); pc = c; } } } static int wtest_sconn(void *scv) { return(oq_nonempty(&((SCONN *)scv)->oq)); } static void wr_sconn(void *scv) { SCONN *sc; int w; sc = scv; w = oq_writev(&sc->oq,sc->fd); if (w < 0) { switch (errno) { NONBLOCK_CASES: return; } sprintf_output("%s: write: %s",sc->host,strerror(errno)); remove_poll_id(sc->id); sc->id = add_block_fn(&sconn_destroy_block,sc); return; } oq_dropdata(&sc->oq,w); } /* * The USER command I see in 2812 does not match what I see other * clients sending. On the assumption that the protocol has moved on, * I'm guessing that the new USER is: * * USER : * * where is the local user name of the user on the client * host, is the client host's idea of its name, * is what the client thinks the server host is called, * and is the client's idea of its user's real name (to the * extent that such a thing exists, of course). */ static int sconn_send_startup(void *scv) { SCONN *sc; sc = scv; remove_block_id(sc->id); oq_queue_printf(&sc->oq,"NICK %s\r\n",sc->nick); oq_queue_printf(&sc->oq,"USER %s %s %s :%s\r\n",sc->myuser,sc->myhost,sc->host,sc->myname); sc->id = add_poll_fd(sc->fd,&rwtest_always,&wtest_sconn,&rd_sconn,&wr_sconn,sc); return(BLOCK_LOOP); } static void psconn_destroy(PSCONN *psc) { if (psc->ai0) freeaddrinfo(psc->ai0); if (psc->sc) sconn_destroy(psc->sc); free(psc->aitext); free(psc); } static int psconn_try_cur_port(void *); // forward static int psconn_try_cur_ai(void *); // forward static int psconn_connect_succeeded(void *pscv) { SCONN *sc; PSCONN *psc; psc = pscv; sc = psc->sc; psc->sc = 0; sc->fd = psc->s; remove_block_id(psc->id); psconn_destroy(psc); sc->id = add_block_fn(&sconn_send_startup,sc); return(BLOCK_LOOP); } static void psconn_check_connect(void *pscv) { PSCONN *psc; int err; socklen_t errlen; psc = pscv; remove_poll_id(psc->id); errlen = sizeof(err); if (getsockopt(psc->s,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) { sprintf_output("%s (for %s/%s): SO_ERROR: %s",psc->aitext,psc->sc->host,*psc->portnum,strerror(errno)); } else if (err) { sprintf_output("%s (for %s/%s): connect: %s",psc->aitext,psc->sc->host,*psc->portnum,strerror(err)); } else { psc->id = add_block_fn(&psconn_connect_succeeded,psc); return; } close(psc->s); psc->ai = psc->ai->ai_next; psc->id = add_block_fn(&psconn_try_cur_ai,psc); } static int psconn_try_cur_ai(void *pscv) { PSCONN *psc; int s; char hname[NI_MAXHOST]; char sname[NI_MAXSERV]; psc = pscv; if (! psc->ai) { freeaddrinfo(psc->ai0); psc->ai0 = 0; psc->portnum ++; remove_block_id(psc->id); psc->id = add_block_fn(&psconn_try_cur_port,psc); return(BLOCK_LOOP); } free(psc->aitext); if (getnameinfo(psc->ai->ai_addr,psc->ai->ai_addrlen,&hname[0],NI_MAXHOST,&sname[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { asprintf(&psc->aitext,"[getnameinfo failed, af %d]",psc->ai->ai_addr->sa_family); } else { asprintf(&psc->aitext,"%s/%s",&hname[0],&sname[0]); } s = socket(psc->ai->ai_family,psc->ai->ai_socktype,psc->ai->ai_protocol); if (s < 0) { sprintf_output("%s (for %s/%s): socket: %s",psc->aitext,psc->sc->host,*psc->portnum,strerror(errno)); psc->ai = psc->ai->ai_next; return(BLOCK_LOOP); } fcntl(s,F_SETFL,fcntl(psc->s,F_GETFL,0)|O_NONBLOCK); if (connect(s,psc->ai->ai_addr,psc->ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { psc->s = s; remove_block_id(psc->id); psc->id = add_poll_fd(psc->s,&rwtest_never,&rwtest_always,0,&psconn_check_connect,psc); } else { sprintf_output("%s (for %s/%s): connect: %s",psc->aitext,psc->sc->host,*psc->portnum,strerror(errno)); close(s); psc->ai = psc->ai->ai_next; } } else { psc->s = s; remove_block_id(psc->id); psc->id = add_block_fn(&psconn_connect_succeeded,psc); } return(BLOCK_LOOP); } static int psconn_try_cur_port(void *pscv) { PSCONN *psc; struct addrinfo *ai0; struct addrinfo hints; int e; psc = pscv; if (*psc->portnum == 0) { sprintf_output("Connection to %s failed",psc->sc->host); remove_block_id(psc->id); psconn_destroy(psc); return(BLOCK_LOOP); } hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = 0; hints.ai_canonname = 0; hints.ai_next = 0; e = getaddrinfo(psc->sc->host,*psc->portnum,&hints,&ai0); if (e) { sprintf_output("%s/%s: lookup: %s",psc->sc->host,*psc->portnum,gai_strerror(e)); psc->portnum ++; } else { remove_block_id(psc->id); psc->ai0 = ai0; psc->ai = ai0; psc->id = add_block_fn(&psconn_try_cur_ai,psc); } return(BLOCK_LOOP); } static void start_sconn_connect(SCONN *sc) { PSCONN *psc; static const char *portstrs[] = { "6667", "194", 0 }; psc = malloc(sizeof(PSCONN)); psc->sc = sc; psc->portnum = &portstrs[0]; psc->ai0 = 0; psc->aitext = 0; psc->id = add_block_fn(&psconn_try_cur_port,psc); } static int start_config_sconn(void *scv) { SCONN *sc; sc = scv; if (redraw) return(BLOCK_NIL); sprintf_output("Starting %s [host %s nick %s]",sc->name,sc->host,sc->nick); remove_block_id(sc->id); sc->id = PL_NOID; start_sconn_connect(sc); return(BLOCK_LOOP); } static void end_construct(CONFIGPARSE *cp) { cp->curfilter = 0; cp->curctx = 0; } static void sconn_cleanup(void *scv) { SCONN *sc; sc = scv; /* Don't free ->name, ->host, or ->nick; they are cleaned up separately. */ free(sc->myuser); free(sc->myhost); free(sc->myname); free(sc); } static void configline_conn(CONFIGPARSE *cp) { char *shortname; char *server; char *nick; const char *t; SCONN *sc; end_construct(cp); t = configparse_token(cp); if (! t) configparse_error(cp,"no shortname on `conn' line"); shortname = strdup(t); configparse_addcleanup(cp,&free,shortname,CPCU_ERROR); t = configparse_token(cp); if (! t) configparse_error(cp,"no server name on `conn' line"); server = strdup(t); configparse_addcleanup(cp,&free,server,CPCU_ERROR); t = configparse_token(cp); if (! t) configparse_error(cp,"no nick name on `conn' line"); nick = strdup(t); configparse_addcleanup(cp,&free,nick,CPCU_ERROR); sc = malloc(sizeof(SCONN)); sc->name = shortname; sc->host = server; sc->fd = -1; sc->nick = nick; oq_init(&sc->oq); bal_init(&sc->input); sc->myuser = strdup(myuser); sc->myhost = strdup(myhost); sc->myname = strdup(myname); configparse_addcleanup(cp,&sconn_cleanup,sc,CPCU_ERROR); while (1) { char **vp; const char *arg; t = configparse_token(cp); if (! t) break; if (! strcmp(t,"-user")) { vp = &sc->myuser; } else if (! strcmp(t,"-host")) { vp = &sc->myhost; } else if (! strcmp(t,"-name")) { vp = &sc->myname; } else if (! strcmp(t,"-once")) { configparse_error(cp,"-once unimplemented on `conn' line"); } else { configparse_error(cp,"`%s': unrecognized option for `conn' line",t); } arg = configparse_token(cp); if (! arg) configparse_error(cp,"missing argument to %s on `conn' line",t); free(*vp); *vp = strdup(arg); } sc->id = add_block_fn(&start_config_sconn,sc); } static int action_token(const char *t) { switch (t[0]) { case 'a': if (!strcmp(t+1,"accept"+1) || !strcmp(t+1,"allow"+1)) return(1); break; case 'b': if (!strcmp(t+1,"block"+1)) return(0); break; case 'd': if (!strcmp(t+1,"deny"+1)) return(0); break; case 'f': if (!strcmp(t+1,"fail"+1)) return(0); break; case 'm': if (!strcmp(t+1,"match"+1)) return(1); break; case 'n': if (!strcmp(t+1,"no"+1)) return(0); break; case 'p': if (!strcmp(t+1,"pass"+1)) return(1); break; case 'r': if (!strcmp(t+1,"reject"+1)) return(0); break; case 'y': if (!strcmp(t+1,"yes"+1)) return(1); break; } return(-1); } static void configline_filter(CONFIGPARSE *cp) { char *name; int act; const char *t; FILTER *f; end_construct(cp); t = configparse_token(cp); if (! t) configparse_error(cp,"no name on `filter' line"); name = strdup(t); configparse_addcleanup(cp,&free,name,CPCU_ERROR); t = configparse_token(cp); if (! t) configparse_error(cp,"no action on `filter' line"); act = action_token(t); if (act < 0) configparse_error(cp,"bad action on `filter' line"); f = find_filter_by_name(name); if (f) configparse_error(cp,"filter `%s' already defined",name); cp->curfilter = new_filter(name,act); cp->treedepth = -1; } static RESTR *clause_type(FCLAUSE *c) { return(&c->type); } static RESTR *clause_conn(FCLAUSE *c) { return(&c->conn); } static RESTR *clause_from(FCLAUSE *c) { return(&c->from); } static RESTR *clause_content(FCLAUSE *c) { return(&c->content); } static char **clause_use(FCLAUSE *c) { return(&c->use); } static char **clause_fail(FCLAUSE *c) { return(&c->fail); } static void cleanup_fmatch(void *mv) { FMATCH *m; FCLAUSE *c; m = mv; while ((c = m->clauses)) { switch (c->t) { default: abort(); break; case CT_TYPE: free(c->type.pattern); regfree(&c->type.re); break; case CT_CONN: free(c->conn.pattern); regfree(&c->conn.re); break; case CT_FROM: free(c->from.pattern); regfree(&c->from.re); break; case CT_CONTENT: free(c->content.pattern); regfree(&c->content.re); break; case CT_USE: free(c->use); break; case CT_FAIL: free(c->fail); break; } m->clauses = c->link; free(c); } } static void configline_match(CONFIGPARSE *cp) { int depth; int act; FMATCH *m; const char *t; if (! cp->curfilter) configparse_error(cp,"`match' line not in a filter"); depth = 0; while (1) { while (Cisspace(cp->lp[cp->x])) cp->x ++; if (cp->lp[cp->x] != '>') break; depth ++; cp->x ++; } t = configparse_token(cp); if (! t) configparse_error(cp,"no action on `match' line"); act = action_token(t); if (act < 0) configparse_error(cp,"bad action on `match' line"); if (depth+1 > cp->amatchtree) { cp->amatchtree = depth + 8; cp->matchtree = realloc(cp->matchtree,cp->amatchtree*sizeof(*cp->matchtree)); cp->matchtreet = realloc(cp->matchtreet,cp->amatchtree*sizeof(*cp->matchtreet)); } if (depth > cp->treedepth) { if (depth > cp->treedepth+1) configparse_error(cp,"`match' line nested too deeply"); cp->matchtreet[depth] = depth ? &cp->matchtree[depth-1]->except : &cp->curfilter->match; } m = malloc(sizeof(FMATCH)); m->accept = act; m->clauses = 0; m->except = 0; configparse_addcleanup(cp,&cleanup_fmatch,m,CPCU_ERROR); while (1) { void regex_clause(CLAUSETYPE t, RESTR *(*get)(FCLAUSE *), const char *s) { FCLAUSE *c; RESTR *re; int e; c = malloc(sizeof(FCLAUSE)); c->t = t; re = (*get)(c); re->pattern = strdup(s); e = regcomp(&re->re,re->pattern,REG_EXTENDED|REG_NOSUB); if (e) { char *es; int el; el = regerror(e,&re->re,0,0); es = malloc(el); regerror(e,&re->re,es,el); configparse_addcleanup(cp,&free,es,CPCU_ERROR); configparse_addcleanup(cp,&free,re->pattern,CPCU_ERROR); configparse_addcleanup(cp,&free,c,CPCU_ERROR); configparse_error(cp,"bad regex `%s': %s",re->pattern,es); } c->link = m->clauses; m->clauses = c; } void string_clause(CLAUSETYPE t, char **(*get)(FCLAUSE *), const char *s) { FCLAUSE *c; c = malloc(sizeof(FCLAUSE)); c->t = t; *(*get)(c) = strdup(s); c->link = m->clauses; m->clauses = c; } t = configparse_token(cp); if (! t) break; if (! strncmp(t,"type=",5)) { regex_clause(CT_TYPE,&clause_type,t+5); } else if (! strncmp(t,"conn=",5)) { regex_clause(CT_CONN,&clause_conn,t+5); } else if (! strncmp(t,"from=",5)) { regex_clause(CT_FROM,&clause_from,t+5); } else if (! strncmp(t,"content=",8)) { regex_clause(CT_CONTENT,&clause_content,t+8); } else if (! strncmp(t,"use=",4)) { string_clause(CT_USE,&clause_use,t+4); } else if (! strncmp(t,"fail=",5)) { string_clause(CT_FAIL,&clause_fail,t+5); } else { const char *equal; equal = index(t,'='); if (equal) { configparse_error(cp,"unrecognized clause type `%.*s'",(int)(equal-t),t); } else { configparse_error(cp,"no type in clause `%s'",t); } } } *cp->matchtreet[depth] = m; cp->matchtreet[depth] = &m->link; cp->matchtree[depth] = m; cp->treedepth = depth; } static CTX *find_context_by_name(const char *name) { CTX *c; for (c=contexts;c;c=c->link) if (! strcmp(c->name,name)) return(c); return(0); } static void configline_context(CONFIGPARSE *cp) { CTX *ctx; const char *t; end_construct(cp); t = configparse_token(cp); if (! t) configparse_error(cp,"no name on `context' line"); ctx = find_context_by_name(t); if (ctx) configparse_error(cp,"context `%s' already defined",t); ctx = malloc(sizeof(CTX)); ctx->name = strdup(t); ctx->tell = 0; ctx->listen = 0; cp->curctx = ctx; cp->clt = &ctx->listen; ctx->link = contexts; contexts = ctx; } static void configline_tell(CONFIGPARSE *cp) { char *name; const char *t; DEST *d; if (! cp->curctx) configparse_error(cp,"`tell' line not in a context"); t = configparse_token(cp); if (! t) configparse_error(cp,"no connection name on `tell' line"); name = strdup(t); configparse_addcleanup(cp,&free,name,CPCU_ERROR); t = configparse_token(cp); if (! t) configparse_error(cp,"no destination on `tell' line"); d = malloc(sizeof(DEST)); d->conn = name; d->what = strdup(t); d->link = cp->curctx->tell; cp->curctx->tell = d; } static void configline_listen(CONFIGPARSE *cp) { char *filter; const char *t; LISTEN *l; if (! cp->curctx) configparse_error(cp,"`listen' line not in a context"); t = configparse_token(cp); if (! t) configparse_error(cp,"no filter name on `tell' line"); filter = strdup(t); t = configparse_trailingtext(cp); l = malloc(sizeof(LISTEN)); l->filter = filter; l->output = strdup(t); l->link = 0; *cp->clt = l; cp->clt = &l->link; } static void configline_use(CONFIGPARSE *cp) { const char *t; t = configparse_token(cp); if (! t) configparse_error(cp,"no keyword on `use' line"); if (! strcmp(t,"context")) { t = configparse_token(cp); free(cp->use_context); cp->use_context = strdup(t); } else { configparse_error(cp,"unknown keyword `%s' on `use' line",t); } } static void configline(CONFIGPARSE *cp) { const char *t; static const struct { const char *keyword; void (*handler)(CONFIGPARSE *); } keys[] = { { "conn", &configline_conn }, { "filter", &configline_filter }, { "match", &configline_match }, { "context", &configline_context }, { "tell", &configline_tell }, { "listen", &configline_listen }, { "use", &configline_use }, { 0 } }; int i; t = configparse_token(cp); if (!t ) return; switch (t[0]) { case '#': case ';': return; break; } for (i=0;keys[i].keyword;i++) { if (!strcmp(t,keys[i].keyword)) { (*keys[i].handler)(cp); return; } } configparse_error(cp,"unrecognized config line keyword `%s'",t); } static int loadconfig(const char *fn, FILE *ef) { FILE *sef; FILE *f; BAL b; int c; int e; char *pre; CONFIGPARSE cp; void finish_line(void) { __label__ catcher; void throw(void) { goto catcher; } bal_save(&b,'\0'); cp.lp = bal_buf(&b); cp.x = 0; cp.throw = &throw; configline(&cp); configparse_cleanup(&cp,CPCU_NORMAL); return; catcher:; e = 1; configparse_cleanup(&cp,CPCU_ERROR); } asprintf(&pre,"%s: ",fn); sef = fopen_w_bracket(ef,pre,"",FWB_NOTEMPTY|FWB_COPY); free(pre); f = fopen(fn,"r"); if (f == 0) { fprintf(sef,"%s\n",strerror(errno)); fclose(sef); return(1); } configparse_init(&cp); cp.ef = sef; bal_init(&b); e = 0; while <"reading"> (1) { c = getc(f); switch (c) { case EOF: if (ferror(f)) { fprintf(sef,"read error\n"); e = 1; } if (bal_len(&b) > 0) finish_line(); break <"reading">; case '\n': finish_line(); bal_reset(&b); break; default: bal_save(&b,c); break; } } { __label__ catcher; void throw(void) { goto catcher; } cp.throw = &throw; end_construct(&cp); if (0) { catcher:; e = 1; } } bal_done(&b); if (cp.use_context) { curctx = find_context_by_name(cp.use_context); if (! curctx) fprintf(sef,"can't use undefined context `%s'",cp.use_context); } if (! curctx) curctx = contexts; configparse_done(&cp); fclose(f); fclose(sef); return(e); } static int loadconfig_cmdline(const char *fn) { FILE *ef; char *s; int e; asprintf(&s,"%s: ",__progname); ef = fopen_w_bracket(stderr,s,"",FWB_NOTEMPTY); e = loadconfig(fn,ef); fclose(ef); free(s); return(e); } 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,"-c")) { WANTARG(); errs |= loadconfig_cmdline(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static KEYMAP *km_new(void) { KEYMAP *m; int i; m = malloc(sizeof(KEYMAP)); m->fxns = 0; for (i=256-1;i>=0;i--) m->map[i].type = BIND_NIL; return(m); } static void disp_init(void) { initscr(); noecho(); raw(); nonl(); timeout(0); keypad(stdscr,FALSE); w_output = derwin(stdscr,LINES-2,COLS,0,0); w_modeline = derwin(stdscr,1,COLS,LINES-2,0); w_input = derwin(stdscr,1,COLS,LINES-1,0); leaveok(stdscr,TRUE); leaveok(w_input,FALSE); scrollok(w_output,TRUE); redraw = REDRAW_FULL; } static void input_display(void) { int i; unsigned char cf; dline.n = 0; dcursor = -1; cf = 0; for (i=0;i= 0) { if ((i == icursor) && (i == imark)) { cfbappend_chardisp(&dline,&markdisp,0); } else if ((i == icursor) || (i == imark)) { cf ^= CF_STANDOUT; } } if (i >= bal_len(&iline)) break; cfbappend_chardisp(&dline,&chardisp[((unsigned char *)bal_buf(&iline))[i]],cf); } if (dcursor < 0) abort(); } static void add_mlmsg(double, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void add_mlmsg(double delay, const char *fmt, ...) { char *s; va_list ap; MLMSG *m; int sec; int usec; if (delay < 0) abort(); sec = delay; usec = (delay - sec) * 1000000; if (usec < 0) { usec += 1000000; sec --; } if (usec >= 1000000) { usec -= 1000000; sec ++; } if ((usec < 0) || (usec >= 1000000) || (sec < 0)) abort(); if ((sec == 0) && (usec == 0)) usec = 1; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); m = malloc(sizeof(MLMSG)); m->link = 0; m->msg = s; m->xfd = -1; m->xid = PL_NOID; m->itv.it_interval.tv_sec = 0; m->itv.it_interval.tv_usec = 0; m->itv.it_value.tv_sec = sec; m->itv.it_value.tv_usec = usec; *mlmsgst = m; mlmsgst = &m->link; redraw |= REDRAW_MODELINE; } static void expire_mlmsg(void *mv) { MLMSG *m; m = mv; remove_poll_id(m->xid); close(m->xfd); free(m->msg); m->msg = 0; redraw |= REDRAW_MODELINE; } static void refreshes(void) { wrefresh(w_modeline); wrefresh(w_output); wrefresh(w_input); } static void do_update(void) { if (redraw & REDRAW_FULL) { clearok(stdscr,TRUE); redraw = REDRAW_INPUT | REDRAW_ICURSOR | REDRAW_OUTPUT | REDRAW_MODELINE; } if (redraw & REDRAW_OUTPUT) { redraw &= ~REDRAW_OUTPUT; } if (redraw & REDRAW_MODELINE) { const char *s; char *sf; sf = 0; if (!line_is_cmd && !curctx) { line_is_cmd = 1; add_mlmsg(2,"No context available"); } redraw &= ~REDRAW_MODELINE; if (mlmsgs && !mlmsgs->msg) { MLMSG *m; m = mlmsgs; mlmsgs = m->link; free(m); if (! mlmsgs) mlmsgst = &mlmsgs; } if (mlmsgs) { if (mlmsgs->xfd < 0) { mlmsgs->xfd = socket(AF_TIMER,SOCK_STREAM,0); mlmsgs->xid = add_poll_fd(mlmsgs->xfd,&rwtest_always,&rwtest_never,&expire_mlmsg,0,mlmsgs); write(mlmsgs->xfd,&mlmsgs->itv,sizeof(mlmsgs->itv)); } s = mlmsgs->msg; } else if (line_is_cmd) { s = "---- Command"; } else if (curctx) { asprintf(&sf,"---- In %s",curctx->name); s = sf; } else { abort(); } wclear(w_modeline); wstandout(w_modeline); waddnstr(w_modeline,s,COLS-1); wstandend(w_modeline); wclrtoeol(w_modeline); free(sf); } if (redraw & (REDRAW_INPUT|REDRAW_ICURSOR)) { redraw &= ~(REDRAW_INPUT|REDRAW_ICURSOR); input_display(); if (dline.n < COLS-3) dlineo = 0; if (dcursor < dlineo) { dlineo = dcursor - ((COLS * 2) / 3); } else if (dcursor-dlineo > COLS-3) { dlineo = dcursor - (COLS / 3); } if (dlineo < 0) dlineo = 0; wclear(w_input); wmove(w_input,0,0); if (dlineo > 0) { if (dline.n-dlineo > COLS-3) { waddch(w_input,'<'); printcf(w_input,dline.b+dlineo,COLS-3); waddch(w_input,'>'); } else { waddch(w_input,'<'); printcf(w_input,dline.b+dlineo,dline.n-dlineo); } } else { if (dline.n > COLS-2) { printcf(w_input,dline.b+dlineo,COLS-2); waddch(w_input,'>'); } else { printcf(w_input,dline.b+dlineo,dline.n-dlineo); } } } wmove(w_input,0,dlineo?(dcursor-dlineo)+1:dcursor); refreshes(); } static void setiprompt(const char *s) { iprompt = s; ipromptlen = s ? strlen(s) : 0; } static void input_line(const char *line) { if (! curctx) { beep(); return; } // XXX line=line; } static void new_input_line(void) { bal_reset(&iline); imark = -1; icursor = 0; redraw |= REDRAW_INPUT; } #if 0 static int command_new_start(void *pv) { PRIV_NEW *p; SCONN *sc; p = pv; if (redraw) return(BLOCK_NIL); sprintf_output("Connecting to server %s nick %s...",p->server,p->nick); sc = malloc(sizeof(SCONN)); sc->shortname = strdup(p->server); sc->hostname = p->server; sc->fd = -1; sc->nick = p->nick; oq_init(&sc->oq); sc->id = PL_NOID; bal_init(&sc->input); start_sconn_connect(sc); free(p->prompt); remove_block_id(p->id); free(p); return(BLOCK_LOOP); } #endif #if 0 static void command_new_abort(void) { PRIV_NEW *p; p = line_priv; free(p->server); free(p->nick); free(p->prompt); free(p); line_ops = &default_line_ops; line_priv = 0; new_input_line(); } #endif #if 0 static void command_new_finish(const char *s) { PRIV_NEW *p; p = line_priv; if (! p->server) { if (badarg(s,AK_HOSTNAME)) { sprintf_output("Host name `%s' is unacceptable for IRC",s); command_new_abort(); return; } p->server = strdup(s); asprintf(&p->prompt,"Server %s nick ",s); setiprompt(p->prompt); } else { if (badarg(s,AK_NICKNAME)) { sprintf_output("Nick `%s' is unacceptable for IRC",s); command_new_abort(); return; } p->nick = strdup(s); p->id = add_block_fn(&command_new_start,p); line_ops = &default_line_ops; line_priv = 0; setiprompt(0); } new_input_line(); } #endif #if 0 static const LINE_OPS command_new_ops = LINE_OPS_INIT(command_new); #endif #if 0 static void command_new(void) { PRIV_NEW *p; p = malloc(sizeof(PRIV_NEW)); p->server = 0; p->nick = 0; p->prompt = 0; line_ops = &command_new_ops; line_priv = p; setiprompt("Shortname "); } #endif static void command_line(const char *line) { if (! strcmp(line,"")) { return; } #if 0 else if (! strcmp(line,"new")) { command_new(); } XXX #endif else { sprintf_output("Unrecognized: %s",line); } } static void default_finish(const char *line) { if (line_is_cmd) { command_line(line); } else { input_line(line); } } static void default_abort(void) { beep(); } static const LINE_OPS default_line_ops = LINE_OPS_INIT(default); static void kf__insert_n_at(int at, unsigned char *data, int n) { unsigned char *buf; int l; bal_room(&iline,n); buf = bal_buf(&iline); l = bal_len(&iline); if (at < l) bcopy(buf+at,buf+at+n,l-at); bcopy(data,buf+at,n); if (at <= imark) imark += n; if (at <= icursor) icursor += n; bal_grow(&iline,n); } static void kf__delete_n_at(int at, int n) { unsigned char *buf; int l; buf = bal_buf(&iline); l = bal_len(&iline); if (at+n > l) abort(); if (at < l-n) bcopy(buf+at+n,buf+at,l-at-n); if (at+n <= imark) { imark -= n; } else if (at < imark) { imark = at; } if (at+n <= icursor) { icursor -= n; } else if (at < icursor) { icursor = at; } bal_shrink(&iline,n); } #define KFN(name) void kf_##name(unsigned char ch __attribute__((__unused__))) static KFN(self_insert) { kf__insert_n_at(icursor,&ch,1); redraw |= REDRAW_INPUT; } static KFN(beginning_of_line) { if (icursor) { icursor = 0; redraw |= REDRAW_ICURSOR; } } static KFN(backward_character) { if (icursor > 0) { icursor --; redraw |= REDRAW_ICURSOR; } } static KFN(delete_forward_character) { if (icursor < bal_len(&iline)) { kf__delete_n_at(icursor,1); redraw |= REDRAW_INPUT; } } static KFN(end_of_line) { if (icursor != bal_len(&iline)) { icursor = bal_len(&iline); redraw |= REDRAW_ICURSOR; } } static KFN(forward_character) { if (icursor < bal_len(&iline)) { icursor ++; redraw |= REDRAW_ICURSOR; } } static KFN(delete_backward_character) { if (icursor > 0) { kf__delete_n_at(icursor-1,1); redraw |= REDRAW_INPUT; } } static KFN(kill_to_eol) { // XXX save on kill ring if (icursor < bal_len(&iline)) { bal_shrink(&iline,bal_len(&iline)-icursor); redraw |= REDRAW_INPUT; } } static KFN(redraw) { redraw |= REDRAW_FULL; } static KFN(transpose_before) { unsigned char t; unsigned char *buf; if (icursor < 2) return; buf = bal_buf(&iline); t = buf[icursor-1]; buf[icursor-1] = buf[icursor-2]; buf[icursor-2] = t; redraw |= REDRAW_INPUT; } static KFN(kill_whole_line) { // XXX save on kill ring if (bal_len(&iline)) { bal_reset(&iline); if (imark >= 0) imark = 0; icursor = 0; redraw |= REDRAW_INPUT; } } static KFN(finish_line) { bal_save(&iline,'\0'); (*line_ops->finish)(bal_buf(&iline)); new_input_line(); } static KFN(set_mark) { imark = icursor; redraw |= REDRAW_INPUT; } static KFN(clear_mark) { imark = -1; redraw |= REDRAW_INPUT; } static KFN(literal_next) { curmap = lnextmap; } static KFN(literal) { kf__insert_n_at(icursor,&ch,1); redraw |= REDRAW_INPUT; curmap = rootmap; } static KFN(quit) { wmove(w_input,0,0); refreshes(); endwin(); printf("\n"); exit(0); } static KFN(abort) { (*line_ops->abort)(); } static KFN(command_toggle) { line_is_cmd = ! line_is_cmd; redraw |= REDRAW_MODELINE; } static KFN(kill_region) { // XXX save on kill ring if (imark < 0) { beep(); } else { int at; int n; at = imark; n = icursor - imark; if (n < 0) { at += n; n = - n; } if (n > 0) kf__delete_n_at(at,n); imark = -1; redraw |= REDRAW_INPUT; } } static void input_keystroke(int ks) { BINDING *b; if ((ks < 0) || (ks > 255)) return; b = &curmap->map[ks]; switch (b->type) { case BIND_NIL: beep(); curmap = rootmap; break; case BIND_KEYMAP: curmap = b->keymap; break; case BIND_FUNCTION: curmap = rootmap; (*b->function->impl)(ks); break; default: abort(); break; } } static PRIMINIT priminit[] = { { "abort", &kf_abort }, { "backward-character", &kf_backward_character }, // { "backward-word", &kf_backward_word }, { "beginning-of-line", &kf_beginning_of_line }, // { "case-word-capitalize", &kf_case_word_capitalize }, // { "case-word-lower", &kf_case_word_lower }, // { "case-word-upper", &kf_case_word_upper }, { "clear-mark", &kf_clear_mark }, { "command-toggle", &kf_command_toggle }, { "delete-forward-character", &kf_delete_forward_character }, // { "delete-forward-word", &kf_delete_forward_word }, { "delete-backward-character", &kf_delete_backward_character }, // { "delete-backward-word", &kf_delete_backward_word }, { "end-of-line", &kf_end_of_line }, { "finish-line", &kf_finish_line }, { "forward-character", &kf_forward_character }, // { "forward-word", &kf_forward_word }, // { "history-^N", &kf_history_control_N }, // { "history-^P", &kf_history_control_P }, // { "history-search-forward", &kf_history_search_forward }, // { "history-search-reverse", &kf_history_search_reverse }, { "kill-whole-line", &kf_kill_whole_line }, { "kill-region", &kf_kill_region }, { "kill-to-eol", &kf_kill_to_eol }, { "literal-next", &kf_literal_next }, // { "noop", &kf_noop }, { "quit", &kf_quit }, { "redraw", &kf_redraw }, { "self-insert", &kf_self_insert }, { "set-mark", &kf_set_mark }, // { "transpose-after", &kf_transpose_after }, // { "transpose-around", &kf_transpose_around }, { "transpose-before", &kf_transpose_before }, // { "transpose-near-after", &kf_transpose_near_after }, // { "transpose-near-around", &kf_transpose_near_around }, // { "transpose-near-before", &kf_transpose_near_before }, // { "yank", &kf_yank } }; static int npriminit = ARRAYSIZE(priminit); static BINDINIT bindinit[] = { { "^@", "set-mark" }, { "^A", "beginning-of-line" }, { "^B", "backward-character" }, { "^C", "command-toggle" }, { "^D", "delete-forward-character" }, { "^E", "end-of-line" }, { "^F", "forward-character" }, { "^H", "delete-backward-character" }, { "^J", "finish-line" }, { "^K", "kill-to-eol" }, { "^L", "redraw" }, { "^M", "finish-line" }, // { "^N", "history-^N" }, // { "^P", "history-^P" }, { "^T", "transpose-before" }, { "^U", "kill-whole-line" }, { "^V", "literal-next" }, { "^W", "kill-region" }, { "^X", "kill-whole-line" }, // { "^Y", "yank" }, { "DEL", "delete-backward-character" }, { "ESC-space", "set-mark" }, { "ESC-.", "clear-mark" }, // { "ESC-6", "case-word-capitalize" }, // { "ESC-[-A", "history-^P" }, // { "ESC-[-B", "history-^N" }, { "ESC-[-C", "forward-character" }, { "ESC-[-D", "backward-character" }, { "ESC-^Q", "quit" }, { "ESC-a", "abort" }, // { "ESC-b", "backward-word" }, // { "ESC-d", "delete-forward-word" }, // { "ESC-f", "forward-word" }, // { "ESC-h", "delete-backward-word" }, // { "ESC-l", "case-word-lower" }, // { "ESC-r", "history-search-reverse" }, // { "ESC-s", "history-search-forward" }, // { "ESC-u", "case-word-upper" }, // { "ESC-y", "ring-yank" } }; static int nbindinit = ARRAYSIZE(bindinit); static unsigned char self_insert_init[] = { 0x09, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; static int n_self_insert_init = ARRAYSIZE(self_insert_init); static const unsigned char *keynames[] = { "\000^\100", "\000\\000", "\000o000", "\001^\140", "\000NUL", "\000nul", "\001^\101", "\001\\001", "\001o001", "\001^\141", "\001SOH", "\001soh", "\002^\102", "\002\\002", "\002o002", "\002^\142", "\002STX", "\002stx", "\003^\103", "\003\\003", "\003o003", "\003^\143", "\003ETX", "\003etx", "\004^\104", "\004\\004", "\004o004", "\004^\144", "\004EOT", "\004eot", "\005^\105", "\005\\005", "\005o005", "\005^\145", "\005ENQ", "\005enq", "\006^\106", "\006\\006", "\006o006", "\006^\146", "\006ACK", "\006ack", "\007^\107", "\007\\007", "\007o007", "\007^\147", "\007BEL", "\007bel", "\010^\110", "\010\\010", "\010o010", "\010^\150", "\010BS", "\010bs", "\011^\111", "\011\\011", "\011o011", "\011^\151", "\011HT", "\011ht", "\012^\112", "\012\\012", "\012o012", "\012^\152", "\012NL", "\012nl", "\013^\113", "\013\\013", "\013o013", "\013^\153", "\013VT", "\013vt", "\014^\114", "\014\\014", "\014o014", "\014^\154", "\014NP", "\014np", "\015^\115", "\015\\015", "\015o015", "\015^\155", "\015CR", "\015cr", "\016^\116", "\016\\016", "\016o016", "\016^\156", "\016SO", "\016so", "\017^\117", "\017\\017", "\017o017", "\017^\157", "\017SI", "\017si", "\020^\120", "\020\\020", "\020o020", "\020^\160", "\020DLE", "\020dle", "\021^\121", "\021\\021", "\021o021", "\021^\161", "\021DC1", "\021dc1", "\022^\122", "\022\\022", "\022o022", "\022^\162", "\022DC2", "\022dc2", "\023^\123", "\023\\023", "\023o023", "\023^\163", "\023DC3", "\023dc3", "\024^\124", "\024\\024", "\024o024", "\024^\164", "\024DC4", "\024dc4", "\025^\125", "\025\\025", "\025o025", "\025^\165", "\025NAK", "\025nak", "\026^\126", "\026\\026", "\026o026", "\026^\166", "\026SYN", "\026syn", "\027^\127", "\027\\027", "\027o027", "\027^\167", "\027ETB", "\027etb", "\030^\130", "\030\\030", "\030o030", "\030^\170", "\030CAN", "\030can", "\031^\131", "\031\\031", "\031o031", "\031^\171", "\031EM", "\031em", "\032^\132", "\032\\032", "\032o032", "\032^\172", "\032SUB", "\032sub", "\033^\133", "\033\\033", "\033o033", "\033^\173", "\033ESC", "\033esc", "\034^\134", "\034\\034", "\034o034", "\034^\174", "\034FS", "\034fs", "\035^\135", "\035\\035", "\035o035", "\035^\175", "\035GS", "\035gs", "\036^\136", "\036\\036", "\036o036", "\036^\176", "\036RS", "\036rs", "\037^\137", "\037\\037", "\037o037", "\037^\177", "\037US", "\037us", "\040space", "\040\\040", "\040o040", "\040\040", "\040SP", "\040SPACE", "\040sp", "\041\041", "\041\\041", "\041o041", "\042\042", "\042\\042", "\042o042", "\043\043", "\043\\043", "\043o043", "\044\044", "\044\\044", "\044o044", "\045\045", "\045\\045", "\045o045", "\046\046", "\046\\046", "\046o046", "\047\047", "\047\\047", "\047o047", "\050\050", "\050\\050", "\050o050", "\051\051", "\051\\051", "\051o051", "\052\052", "\052\\052", "\052o052", "\053\053", "\053\\053", "\053o053", "\054\054", "\054\\054", "\054o054", "\055\055", "\055\\055", "\055o055", "\056\056", "\056\\056", "\056o056", "\057\057", "\057\\057", "\057o057", "\060\060", "\060\\060", "\060o060", "\061\061", "\061\\061", "\061o061", "\062\062", "\062\\062", "\062o062", "\063\063", "\063\\063", "\063o063", "\064\064", "\064\\064", "\064o064", "\065\065", "\065\\065", "\065o065", "\066\066", "\066\\066", "\066o066", "\067\067", "\067\\067", "\067o067", "\070\070", "\070\\070", "\070o070", "\071\071", "\071\\071", "\071o071", "\072\072", "\072\\072", "\072o072", "\073\073", "\073\\073", "\073o073", "\074\074", "\074\\074", "\074o074", "\075\075", "\075\\075", "\075o075", "\076\076", "\076\\076", "\076o076", "\077\077", "\077\\077", "\077o077", "\100\100", "\100\\100", "\100o100", "\101\101", "\101\\101", "\101o101", "\102\102", "\102\\102", "\102o102", "\103\103", "\103\\103", "\103o103", "\104\104", "\104\\104", "\104o104", "\105\105", "\105\\105", "\105o105", "\106\106", "\106\\106", "\106o106", "\107\107", "\107\\107", "\107o107", "\110\110", "\110\\110", "\110o110", "\111\111", "\111\\111", "\111o111", "\112\112", "\112\\112", "\112o112", "\113\113", "\113\\113", "\113o113", "\114\114", "\114\\114", "\114o114", "\115\115", "\115\\115", "\115o115", "\116\116", "\116\\116", "\116o116", "\117\117", "\117\\117", "\117o117", "\120\120", "\120\\120", "\120o120", "\121\121", "\121\\121", "\121o121", "\122\122", "\122\\122", "\122o122", "\123\123", "\123\\123", "\123o123", "\124\124", "\124\\124", "\124o124", "\125\125", "\125\\125", "\125o125", "\126\126", "\126\\126", "\126o126", "\127\127", "\127\\127", "\127o127", "\130\130", "\130\\130", "\130o130", "\131\131", "\131\\131", "\131o131", "\132\132", "\132\\132", "\132o132", "\133\133", "\133\\133", "\133o133", "\134\134", "\134\\134", "\134o134", "\135\135", "\135\\135", "\135o135", "\136\136", "\136\\136", "\136o136", "\137\137", "\137\\137", "\137o137", "\140\140", "\140\\140", "\140o140", "\141\141", "\141\\141", "\141o141", "\142\142", "\142\\142", "\142o142", "\143\143", "\143\\143", "\143o143", "\144\144", "\144\\144", "\144o144", "\145\145", "\145\\145", "\145o145", "\146\146", "\146\\146", "\146o146", "\147\147", "\147\\147", "\147o147", "\150\150", "\150\\150", "\150o150", "\151\151", "\151\\151", "\151o151", "\152\152", "\152\\152", "\152o152", "\153\153", "\153\\153", "\153o153", "\154\154", "\154\\154", "\154o154", "\155\155", "\155\\155", "\155o155", "\156\156", "\156\\156", "\156o156", "\157\157", "\157\\157", "\157o157", "\160\160", "\160\\160", "\160o160", "\161\161", "\161\\161", "\161o161", "\162\162", "\162\\162", "\162o162", "\163\163", "\163\\163", "\163o163", "\164\164", "\164\\164", "\164o164", "\165\165", "\165\\165", "\165o165", "\166\166", "\166\\166", "\166o166", "\167\167", "\167\\167", "\167o167", "\170\170", "\170\\170", "\170o170", "\171\171", "\171\\171", "\171o171", "\172\172", "\172\\172", "\172o172", "\173\173", "\173\\173", "\173o173", "\174\174", "\174\\174", "\174o174", "\175\175", "\175\\175", "\175o175", "\176\176", "\176\\176", "\176o176", "\177DEL", "\177\\177", "\177o177", "\177^\077", "\177del", "\177DELETE", "\177delete", "\200^\340", "\200\\200", "\200o200", "\200^\300", "\201^\301", "\201\\201", "\201o201", "\201^\341", "\202^\302", "\202\\202", "\202o202", "\202^\342", "\203^\303", "\203\\203", "\203o203", "\203^\343", "\204^\304", "\204\\204", "\204o204", "\204^\344", "\205^\305", "\205\\205", "\205o205", "\205^\345", "\206^\306", "\206\\206", "\206o206", "\206^\346", "\207^\307", "\207\\207", "\207o207", "\207^\347", "\210^\310", "\210\\210", "\210o210", "\210^\350", "\211^\311", "\211\\211", "\211o211", "\211^\351", "\212^\312", "\212\\212", "\212o212", "\212^\352", "\213^\313", "\213\\213", "\213o213", "\213^\353", "\214^\314", "\214\\214", "\214o214", "\214^\354", "\215^\315", "\215\\215", "\215o215", "\215^\355", "\216^\316", "\216\\216", "\216o216", "\216^\356", "\217^\317", "\217\\217", "\217o217", "\217^\357", "\220^\320", "\220\\220", "\220o220", "\220^\360", "\221^\321", "\221\\221", "\221o221", "\221^\361", "\222^\322", "\222\\222", "\222o222", "\222^\362", "\223^\323", "\223\\223", "\223o223", "\223^\363", "\224^\324", "\224\\224", "\224o224", "\224^\364", "\225^\325", "\225\\225", "\225o225", "\225^\365", "\226^\326", "\226\\226", "\226o226", "\226^\366", "\227^\327", "\227\\227", "\227o227", "\227^\367", "\230^\330", "\230\\230", "\230o230", "\230^\370", "\231^\331", "\231\\231", "\231o231", "\231^\371", "\232^\332", "\232\\232", "\232o232", "\232^\372", "\233^\333", "\233\\233", "\233o233", "\233^\373", "\234^\334", "\234\\234", "\234o234", "\234^\374", "\235^\335", "\235\\235", "\235o235", "\235^\375", "\236^\336", "\236\\236", "\236o236", "\236^\376", "\237^\337", "\237\\237", "\237o237", "\237^\377", "\240nbsp", "\240\\240", "\240o240", "\240NBSP", "\241\241", "\241\\241", "\241o241", "\242\242", "\242\\242", "\242o242", "\243\243", "\243\\243", "\243o243", "\244\244", "\244\\244", "\244o244", "\245\245", "\245\\245", "\245o245", "\246\246", "\246\\246", "\246o246", "\247\247", "\247\\247", "\247o247", "\250\250", "\250\\250", "\250o250", "\251\251", "\251\\251", "\251o251", "\252\252", "\252\\252", "\252o252", "\253\253", "\253\\253", "\253o253", "\254\254", "\254\\254", "\254o254", "\255\255", "\255\\255", "\255o255", "\256\256", "\256\\256", "\256o256", "\257\257", "\257\\257", "\257o257", "\260\260", "\260\\260", "\260o260", "\261\261", "\261\\261", "\261o261", "\262\262", "\262\\262", "\262o262", "\263\263", "\263\\263", "\263o263", "\264\264", "\264\\264", "\264o264", "\265\265", "\265\\265", "\265o265", "\266\266", "\266\\266", "\266o266", "\267\267", "\267\\267", "\267o267", "\270\270", "\270\\270", "\270o270", "\271\271", "\271\\271", "\271o271", "\272\272", "\272\\272", "\272o272", "\273\273", "\273\\273", "\273o273", "\274\274", "\274\\274", "\274o274", "\275\275", "\275\\275", "\275o275", "\276\276", "\276\\276", "\276o276", "\277\277", "\277\\277", "\277o277", "\300\300", "\300\\300", "\300o300", "\301\301", "\301\\301", "\301o301", "\302\302", "\302\\302", "\302o302", "\303\303", "\303\\303", "\303o303", "\304\304", "\304\\304", "\304o304", "\305\305", "\305\\305", "\305o305", "\306\306", "\306\\306", "\306o306", "\307\307", "\307\\307", "\307o307", "\310\310", "\310\\310", "\310o310", "\311\311", "\311\\311", "\311o311", "\312\312", "\312\\312", "\312o312", "\313\313", "\313\\313", "\313o313", "\314\314", "\314\\314", "\314o314", "\315\315", "\315\\315", "\315o315", "\316\316", "\316\\316", "\316o316", "\317\317", "\317\\317", "\317o317", "\320\320", "\320\\320", "\320o320", "\321\321", "\321\\321", "\321o321", "\322\322", "\322\\322", "\322o322", "\323\323", "\323\\323", "\323o323", "\324\324", "\324\\324", "\324o324", "\325\325", "\325\\325", "\325o325", "\326\326", "\326\\326", "\326o326", "\327\327", "\327\\327", "\327o327", "\330\330", "\330\\330", "\330o330", "\331\331", "\331\\331", "\331o331", "\332\332", "\332\\332", "\332o332", "\333\333", "\333\\333", "\333o333", "\334\334", "\334\\334", "\334o334", "\335\335", "\335\\335", "\335o335", "\336\336", "\336\\336", "\336o336", "\337\337", "\337\\337", "\337o337", "\340\340", "\340\\340", "\340o340", "\341\341", "\341\\341", "\341o341", "\342\342", "\342\\342", "\342o342", "\343\343", "\343\\343", "\343o343", "\344\344", "\344\\344", "\344o344", "\345\345", "\345\\345", "\345o345", "\346\346", "\346\\346", "\346o346", "\347\347", "\347\\347", "\347o347", "\350\350", "\350\\350", "\350o350", "\351\351", "\351\\351", "\351o351", "\352\352", "\352\\352", "\352o352", "\353\353", "\353\\353", "\353o353", "\354\354", "\354\\354", "\354o354", "\355\355", "\355\\355", "\355o355", "\356\356", "\356\\356", "\356o356", "\357\357", "\357\\357", "\357o357", "\360\360", "\360\\360", "\360o360", "\361\361", "\361\\361", "\361o361", "\362\362", "\362\\362", "\362o362", "\363\363", "\363\\363", "\363o363", "\364\364", "\364\\364", "\364o364", "\365\365", "\365\\365", "\365o365", "\366\366", "\366\\366", "\366o366", "\367\367", "\367\\367", "\367o367", "\370\370", "\370\\370", "\370o370", "\371\371", "\371\\371", "\371o371", "\372\372", "\372\\372", "\372o372", "\373\373", "\373\\373", "\373o373", "\374\374", "\374\\374", "\374o374", "\375\375", "\375\\375", "\375o375", "\376\376", "\376\\376", "\376o376", "\377\377", "\377\\377", "\377o377" }; static int n_keynames = ARRAYSIZE(keynames); static int keyname_sort[ARRAYSIZE(keynames)]; static const unsigned char *official_keynames[256] = { "^\100", "^\101", "^\102", "^\103", "^\104", "^\105", "^\106", "^\107", "^\110", "^\111", "^\112", "^\113", "^\114", "^\115", "^\116", "^\117", "^\120", "^\121", "^\122", "^\123", "^\124", "^\125", "^\126", "^\127", "^\130", "^\131", "^\132", "ESC", "^\134", "^\135", "^\136", "^\137", "SP", "\041", "\042", "\043", "\044", "\045", "\046", "\047", "\050", "\051", "\052", "\053", "\054", "\055", "\056", "\057", "\060", "\061", "\062", "\063", "\064", "\065", "\066", "\067", "\070", "\071", "\072", "\073", "\074", "\075", "\076", "\077", "\100", "\101", "\102", "\103", "\104", "\105", "\106", "\107", "\110", "\111", "\112", "\113", "\114", "\115", "\116", "\117", "\120", "\121", "\122", "\123", "\124", "\125", "\126", "\127", "\130", "\131", "\132", "\133", "\134", "\135", "\136", "\137", "\140", "\141", "\142", "\143", "\144", "\145", "\146", "\147", "\150", "\151", "\152", "\153", "\154", "\155", "\156", "\157", "\160", "\161", "\162", "\163", "\164", "\165", "\166", "\167", "\170", "\171", "\172", "\173", "\174", "\175", "\176", "^\077", "^\340", "^\301", "^\302", "^\303", "^\304", "^\305", "^\306", "^\307", "^\310", "^\311", "^\312", "^\313", "^\314", "^\315", "^\316", "^\317", "^\320", "^\321", "^\322", "^\323", "^\324", "^\325", "^\326", "^\327", "^\330", "^\331", "^\332", "^\333", "^\334", "^\335", "^\336", "^\337", "NBSP", "\241", "\242", "\243", "\244", "\245", "\246", "\247", "\250", "\251", "\252", "\253", "\254", "\255", "\256", "\257", "\260", "\261", "\262", "\263", "\264", "\265", "\266", "\267", "\270", "\271", "\272", "\273", "\274", "\275", "\276", "\277", "\300", "\301", "\302", "\303", "\304", "\305", "\306", "\307", "\310", "\311", "\312", "\313", "\314", "\315", "\316", "\317", "\320", "\321", "\322", "\323", "\324", "\325", "\326", "\327", "\330", "\331", "\332", "\333", "\334", "\335", "\336", "\337", "\340", "\341", "\342", "\343", "\344", "\345", "\346", "\347", "\350", "\351", "\352", "\353", "\354", "\355", "\356", "\357", "\360", "\361", "\362", "\363", "\364", "\365", "\366", "\367", "\370", "\371", "\372", "\373", "\374", "\375", "\376", "\377" }; static int parse_kseq(const char *txt0, unsigned char **sp, int *nsp, FILE *ef) { const char *txt; unsigned char *s; int ns; int h; int m; int l; int x; txt = txt0; s = 0; ns = 0; while (1) { l = 0; /* not -1! less than all -> use [0] */ h = n_keynames; while (h-l > 1) { m = (h + l) / 2; if (strcmp(txt,keynames[keyname_sort[m]]+1) >= 0) l = m; else h = m; } x = keyname_sort[l]; l = strlen(keynames[x]+1); if (strncmp(txt,keynames[x]+1,l)) { fprintf(ef,"bad key name at %s",txt); free(s); return(1); } ns ++; s = realloc(s,ns); s[ns-1] = keynames[x][0]; txt += l; switch (*txt) { case '\0': *sp = s; *nsp = ns; return(0); break; case '-': txt ++; break; default: fprintf(ef,"junk after key name at %s",txt); free(s); return(1); break; } } } static void recursive_free_keymap(KEYMAP *km) { int i; BINDING *b; for (i=256-1;i>=0;i--) { b = &km->map[i]; switch (b->type) { default: abort(); break; case BIND_NIL: break; case BIND_KEYMAP: recursive_free_keymap(b->keymap); break; case BIND_FUNCTION: break; } } free(km); } static int set_binding(KEYMAP *km, const char *seq, const char *fn, FILE *ef) { KEYFN *f; unsigned char *ks; int nks; int i; BINDING *b; do <"found"> { for (f=km->fxns;f;f=f->link) { if (!strcmp(fn,f->name)) break <"found">; } fprintf(ef,"function `%s' not found\n",fn); return(1); } while (0); if (parse_kseq(seq,&ks,&nks,ef)) return(1); nks --; for (i=0;imap[ks[i]]; if (b->type != BIND_KEYMAP) { b->type = BIND_KEYMAP; b->keymap = km_new(); } km = b->keymap; } b = &km->map[ks[i]]; switch (b->type) { default: abort(); break; case BIND_NIL: break; case BIND_KEYMAP: recursive_free_keymap(b->keymap); break; case BIND_FUNCTION: break; } b->type = BIND_FUNCTION; b->function = f; free(ks); return(0); } static void init_prim(KEYMAP *map, const char *name, void (*impl)(unsigned char)) { KEYFN *f; f = malloc(sizeof(KEYFN)); f->name = name; f->impl = impl; f->link = map->fxns; map->fxns = f; } static void ile_init(void) { int i; FILE *ef; #define CMP(a,b) \ strcmp(keynames[keyname_sort[(a)]]+1,keynames[keyname_sort[(b)]]+1) void knheap(int x, int n) { int l; int r; int s; int t; while (1) { l = HL(x); r = HR(x); if ((l < n) && (CMP(x,l) < 0)) { if ((r < n) && (CMP(x,r) < 0)) { s = (CMP(l,r) > 0) ? l : r; } else { s = l; } } else { if ((r < n) && (CMP(x,r) < 0)) { s = r; } else { return; } } t = keyname_sort[x]; keyname_sort[x] = keyname_sort[s]; keyname_sort[s] = t; x = s; } } #undef CMP bal_init(&iline); icursor = 0; imark = -1; cfbinit(&dline); dlineo = 0; dcursor = 0; setiprompt(0); for (i=n_keynames-1;i>=0;i--) keyname_sort[i] = i; for (i=HU(n_keynames-1);i>=0;i--) knheap(i,n_keynames); for (i=n_keynames-1;i>0;i--) { int t; t = keyname_sort[0]; keyname_sort[0] = keyname_sort[i]; keyname_sort[i] = t; knheap(0,i); } rootmap = km_new(); lnextmap = km_new(); for (i=npriminit-1;i>=0;i--) init_prim(rootmap,priminit[i].name,priminit[i].impl); init_prim(lnextmap,"literal",&kf_literal); ef = fopen_null(); for (i=nbindinit-1;i>=0;i--) { if (set_binding(rootmap,bindinit[i].seq,bindinit[i].fxn,ef)) abort(); } for (i=n_self_insert_init-1;i>=0;i--) { if (set_binding(rootmap,official_keynames[self_insert_init[i]],"self-insert",ef)) abort(); } for (i=256-1;i>=0;i--) if (set_binding(lnextmap,official_keynames[i],"literal",ef)) abort(); for (i=256-1;i>=0;i--) { switch (chardisp_init[i][0]) { case CHARDISP_INIT_NORMAL: chardisp[i].kind = CDK_NORMAL; if (0) { case CHARDISP_INIT_STANDOUT: chardisp[i].kind = CDK_STANDOUT; } chardisp[i].s = strdup(chardisp_init[i]+1); chardisp[i].l = strlen(chardisp_init[i]+1); break; case CHARDISP_INIT_TAB: chardisp[i].kind = CDK_TAB; chardisp[i].s = 0; chardisp[i].l = 0; break; default: abort(); break; } } markdisp.kind = CDK_STANDOUT; markdisp.s = strdup("·"); markdisp.l = 1; fclose(ef); curmap = rootmap; line_is_cmd = 0; mlmsgs = 0; mlmsgst = &mlmsgs; line_ops = &default_line_ops; line_priv = 0; } /* * There is a problem here. * * curses reads from stdin, not fd 0. This means that we have to take * stdio buffering into account; we don't want to block waiting for * input on fd 0 if there's stuff in stdin's buffer. * * However, there is no stdio call to find out how much is in the * buffer for an input FILE *, not even a zero/nonzero check. * * Nor is there any call to make curses use a FILE * of our choice * rather than stdin, or we could track the amount of buffering. * (Before anyone points out newterm(), note that I'm talking about * facilities that exist in both 1.4T's libcurses and 4.0.1's.) * * Mercifully, there _is_ a way to do this without going under the hood * of curses. timeout() allows us to do a polled getch(), and * disp_init() sets it up suitably for us. */ static void keystroke_available(void *arg __attribute__((__unused__))) { int ic; while (1) { ic = getch(); if (ic == ERR) break; input_keystroke(ic); } } static int update_block(void *arg __attribute__((__unused__))) { if (! redraw) return(BLOCK_NIL); do_update(); if (redraw) abort(); return(BLOCK_LOOP); } int main(int, char **); int main(int ac, char **av) { init_polling(); init(); disp_init(); ile_init(); handleargs(ac,av); input_id = add_poll_fd(0,&rwtest_always,&rwtest_never,&keystroke_available,0,0); update_id = add_block_fn(&update_block,0); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"poll: %s",strerror(errno)); exit(1); } post_poll(); } }