home *** CD-ROM | disk | FTP | other *** search
- /*
- * termcap.c Replacement for the GNU Emacs termcap routines.
- * These do more error checking, like preventing a loop
- * with the tc= capability and buffer overflow.
- * Also, these routines can stuff a whole lot more in
- * one buffer because duplicate capabilities are eliminated.
- *
- * Version: 1.1 20-Oct-1994 MvS (miquels@ow.org)
- *
- * Copyright (C) Miquel van Smoorenburg 1994.
- * This code falls under the LGPL.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <malloc.h>
- #include <string.h>
- #include <sys/ioctl.h>
- #include <termios.h>
- #include <termcap.h>
-
- /* Escape sequences we know about. */
- static char *escapes = "E\033r\rn\nb\bt\tf\f\\\\";
-
- /* Pointer for tgetstr() et al */
- static char *term_entry;
-
- /* Table with speeds for padding. */
- static short speeds[] = {
- 0, 50, 75, 110, 134, 150, -3, -6, -12, -18,
- -20, -24, -36, -48, -72, -96, -192, -384, -768, -1536
- };
-
- /* Some (undocumented?) global variables. */
- speed_t ospeed;
- int tputs_baud_rate;
- char PC;
- int tgetent_bufsize = 1024;
-
- /* We store a terminal description in a linked list. */
- struct tc_ent {
- struct tc_ent *next;
- char cap[1];
- };
-
- /* Safe malloc. */
- static void *xmalloc(int len)
- {
- void *x;
-
- if ((x = malloc(len)) != NULL) return(x);
- write(2, "Virtual memory exhausted.\n", 26);
- exit(1);
- }
-
- /* Safe strdup() */
- static char *strsave(char *s)
- {
- char *x;
-
- x = xmalloc(strlen(s) + 1);
- strcpy(x, s);
- return(x);
- }
-
- /* Build a linked list with capabilities. */
- static char *build_list(struct tc_ent **listp, char *buf)
- {
- struct tc_ent *i, *last = NULL, *list = *listp;
- char *s, *sp, *bp;
- int len;
- char *tc_next = NULL;
-
- /* Skip name field. */
- for(sp = buf; *sp && *sp != ':'; sp++)
- ;
- if (*sp) *sp++ = 0;
-
- /* Extract capabilities one-by-one. */
- while(*sp) {
- /* Find end of field. */
- bp = sp;
- while(*sp && *sp != ':') sp++;
- if (*sp) *sp++ = 0;
-
- /* Check for empty field. */
- while(*bp == ' ' || *bp == '\t' || *bp == '\n') bp++;
- if (*bp == 0 || *bp == ':') continue;
-
- /* Is this the "tc" capability? */
- if (!tc_next && strncmp(bp, "tc=", 3) == 0) {
- tc_next = strsave(bp + 3);
- continue;
- }
-
- /* Find name of capability. */
- if ((s = strchr(bp, '=')) != NULL)
- len = s - bp;
- else if ((s = strchr(bp, '@')) != NULL)
- len = s - bp;
- else if ((s = strchr(bp, '#')) != NULL)
- len = s - bp;
- else len = strlen(bp);
- if (len == 0) continue;
-
- /* See if the capability is already in the list. */
- if (list) {
- for(i = list; i; i = i->next) {
- last = i;
- if (strncmp(i->cap, bp, len) == 0) break;
- }
- }
- if (list != NULL && i != NULL) continue;
-
- /* Add capability to the list. */
- i = (struct tc_ent *)xmalloc(sizeof(struct tc_ent) + strlen(bp));
- if (i == NULL) break;
- strcpy(i->cap, bp);
- i->next = NULL;
- if (list == NULL)
- list = i;
- else
- last->next = i;
- }
- /* Done. */
- *listp = list;
- return(tc_next);
- }
-
- /* Add OR change a capability (hardcoded for li# and co#) */
- static void add_list(struct tc_ent **list, char *cap)
- {
- struct tc_ent *prev, *new, *l;
-
- /* Walk through the list. */
- prev = NULL;
- for(l = *list; l; l = l->next) {
- if (strncmp(l->cap, cap, 3) == 0) {
-
- /* Found: modify in-place. */
- new = xmalloc(sizeof(struct tc_ent) + strlen(cap));
- strcpy(new->cap, cap);
- new->next = l->next;
- if (prev)
- prev->next = new;
- else
- *list = new;
- free(l);
- l = new;
- break;
- }
- prev = l;
- }
- if (l != NULL) return;
-
- /* Not found, add to the end of the list. */
- new = xmalloc(sizeof(struct tc_ent) + strlen(cap));
- strcpy(new->cap, cap);
- new->next = NULL;
- if (prev)
- prev->next = new;
- else
- *list = new;
- }
-
- /* Convert a number to ASCII */
- static char *_itoa(int num, char *buf)
- {
- char *sp = buf + 16;
-
- *--sp = 0;
- do {
- *--sp = (num % 10) + '0';
- num /= 10;
- } while(num);
- return(sp);
- }
-
- /* Adjust lines and columns by doing a TIOCGWINSZ */
- static void adjust_lines_cols(struct tc_ent **l)
- {
- struct winsize ws;
- char buf[16];
- char num[16];
-
- /* Get and check window size. */
- if (ioctl(0, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
- return;
-
- /* Fill in li# and co# */
- strcpy(buf, "li#");
- strcpy(buf + 3, _itoa(ws.ws_row, num));
- add_list(l, buf);
-
- strcpy(buf, "co#");
- strcpy(buf + 3, _itoa(ws.ws_col, num));
- add_list(l, buf);
- }
-
- /* See if strings contains terminal name. */
- static int tc_comp(char *line, char *term)
- {
- char *sp, *bp;
- int found = 0, x;
- int len = strlen(term);
-
- bp = sp = line;
- x = *bp;
- while(x && x != ':' && x != '\n') {
- /* Find the end of this description. */
- while(*sp && *sp != ':' && *sp != '|' && *sp != '\n')
- sp++;
- if (len == (sp - bp) && strncmp(term, bp, len) == 0) {
- /* Found it! */
- found = 1;
- break;
- }
- x = *sp++;
- bp = sp;
- }
- return(found);
- }
-
-
- /* Load a specific terminal. */
- static char *get_one_entry(FILE *tfp, char *term)
- {
- char line[256];
- int status = 0;
- char *sp;
- char buf[4096];
- char *bufp = buf;
-
- if (term == NULL) return(NULL);
-
- /* Start at beginning. */
- rewind(tfp);
-
- /* Read line by line. */
- while(fgets(line, 256, tfp) != NULL) {
- if (line[0] == '#') continue;
-
- /* See if this is what we're looking for. */
- if (status == 0 && tc_comp(line, term) == 0) continue;
- status = 1;
-
- /* We are reading a description here. */
- for(sp = line; *sp == ' ' || *sp == '\t'; sp++)
- ;
-
- /* Add the rest until nl to the buffer. */
- while(*sp && *sp != '\n') {
- if (*sp == '\\' && (*(sp + 1) == '\n')) break;
- *bufp++ = *sp++;
- if (bufp - buf > 4092) {
- /* Buffer full, quit. */
- *sp = '\n';
- break;
- }
- }
- if (*sp == '\n') break;
- }
- /* Save buffer to malloced area. */
- *bufp++ = 0;
- if (status == 0) return(NULL);
- if ((sp = xmalloc(bufp - buf)) == NULL) return(sp);
- memcpy(sp, buf, bufp - buf);
- return(sp);
- }
-
-
- /* Read terminal description. */
- static char *tc_read(struct tc_ent **tcp, char *term)
- {
- FILE *fp;
- char *sp, *tc;
- char *desc = NULL;
- char *tc_file = "/etc/termcap";
- struct tc_ent *l = NULL;
- int first = 1;
- int loop = 0;
-
- *tcp = NULL;
-
- /* See if we have a TERMCAP environment variable. */
- if ((tc = getenv("TERMCAP")) != NULL) {
- if (*tc == '/')
- tc_file = tc;
- else {
- /* check if TERMCAP is term */
- if (tc_comp(tc, term)) {
- #if DEBUG
- printf("Using TERMCAP\n");
- #endif
- /* Just read the TERMCAP variable. */
- sp = strsave(tc);
- tc = build_list(&l, sp);
- if (tc) free(tc);
- *tcp = l;
- return(sp);
- }
- }
- }
-
- #if DEBUG
- printf("Using file %s\n", tc_file);
- #endif
-
- /* Now read the termcap file. */
- if ((fp = fopen(tc_file, "r")) == NULL) return(NULL);
-
- while(term) {
- if (++loop > 16) {
- write(2, "tgetent: loop detected, check your termcap\n", 43);
- break;
- }
- #if DEBUG
- printf("LOOKUP: term %s\n", term);
- #endif
- sp = get_one_entry(fp, term);
- if (sp == NULL) break;
- term = build_list(&l, sp);
- if (first)
- desc = sp;
- else
- free(sp);
- first = 0;
- }
- fclose(fp);
-
- /* Done. */
- *tcp = l;
- #if DEBUG
- printf(">> tc_read done, desc = %s\n", desc);
- #endif
- return(desc ? desc : "");
- }
-
-
- /* The tgetent function. */
- int tgetent(void *buffer, const char *term)
- {
- char *s;
- struct tc_ent *l, *i, *next;
- char *bp, *sp = (char *)buffer;
- int len, count, maxlen;
-
- /* Find the termcap entry. */
- s = tc_read(&l, (char *)term);
-
- /* Return -1 if we can't open the termcap file. */
- if (s == NULL) return(-1);
-
- /* Return 0 if the entry is not present. */
- if (l == NULL) return(0);
-
- /* Adjust lines and columns. */
- adjust_lines_cols(&l);
-
- /* Do we already have a buffer? */
- if (sp)
- maxlen = tgetent_bufsize - 1;
- else {
- /* Count how many bytes we need. */
- count = strlen(s) + 1;
- for(i = l; i; i = i->next)
- count += strlen(i->cap) + 1;
- count++;
-
- /* Malloc this amount. */
- sp = xmalloc(count);
- maxlen = count + 32; /* Just a lot. */
- }
-
- /* Save buffer into static variable (yuk!) */
- term_entry = sp;
-
- /* First copy the description to the buffer. */
- count = 0;
- for(bp = s; *bp; bp++) {
- *sp++ = *bp;
- count++;
- }
- *sp++ = ':';
- count++;
-
- /* And now the capabilities. */
- for(i = l; i; i = next) {
-
- /* Is this a 'skip' capability? */
- len = strlen(i->cap);
- if (strchr(i->cap, '=') == NULL && i->cap[len-1] == '@') {
- next = i->next;
- free(i);
- continue;
- }
-
- /* Check for buffer overflow. */
- count += len + 1;
- if (count >= maxlen) {
- write(2, "tgetent: warning: termcap entry too long\n", 41);
- break;
- }
-
- /* Add capability to buffer. */
- for(bp = i->cap; *bp; bp++)
- *sp++ = *bp;
- *sp++ = ':';
-
- /* Free space. */
- next = i->next;
- free(i);
- }
- *sp = 0;
-
- return(1);
- }
-
- /* Generic "find capability" routine. */
- static char *find_cap(char *bp, const char *cap, char sep)
- {
- #if 0
- int len = strlen(cap);
-
- if (len == 2) {
- /* Normal case, do fast lookup. */
- #endif
- while(*bp) {
- if (bp[0] == ':' &&
- bp[1] == cap[0] &&
- bp[2] == cap[1] &&
- bp[3] == sep) return(bp + 4);
- bp++;
- }
- return(NULL);
- #if 0
- }
- /* Longer string, use slow lookup. */
- while(*bp) {
- if (bp[0] == ':' && bp[len+1] == sep && strncmp(bp+1, cap, len) == 0)
- return(bp + len + 2);
- bp++;
- }
- return(NULL);
- #endif
- }
-
- /* Find a number capability. */
- int tgetnum(const char *cap)
- {
- char *s;
-
- s = find_cap(term_entry, cap, '#');
- return(s ? atoi(s) : -1);
- }
-
- /* Find a boolean capability. */
- int tgetflag(const char *cap)
- {
- return(find_cap(term_entry, cap, ':') ? 1 : 0);
- }
-
- /* Find a string capability. */
- char *tgetstr(const char *cap, char **bufp)
- {
- char *s;
- char *sp, *r, *ret;
- int c;
-
- s = find_cap(term_entry, cap, '=');
- if (s == NULL) return(s);
-
- /* Where to put the result. */
- if (bufp == (char **)NULL) {
- for(sp = s; *sp != ':' && *sp; sp++)
- ;
- ret = xmalloc(sp - s + 1);
- } else
- ret = *bufp;
- r = ret;
-
- /* Translate escaped characters and hat-notation. */
- while((c = *s++) && c != ':') {
- if (c == '\\' && *s != ':') {
-
- /* Escaped character. */
- c = *s++;
-
- if (c >= '0' && c <= '9') {
- /* Octal number. */
- c -= '0';
- while(*s >= '0' && *s <= '9') {
- c = (c * 8) + (*s - '0');
- s++;
- }
- } else {
- /* \r or \n or whatever. */
- for(sp = escapes; *sp; sp += 2)
- if (c == *sp) {
- c = *++sp;
- break;
- }
- }
- } else if (c == '^' && *s != ':')
- /* Hat notation. */
- c = *s++ & 0x1f;
- *r++ = c;
- }
- *r++ = 0;
-
- /* Do we need to update bufp? */
- if (bufp) *bufp = r;
-
- return(ret);
- }
-
- /* Output string with padding - stolen from GNU termcap.c :) */
- void tputs(const char *str, int nlines, int (*outfun)(int))
- {
- int padcount = 0;
- int speed;
-
- /* Safety check. */
- if (!str) return;
-
- /* Try to get output speed. */
- if (ospeed == 0)
- speed = tputs_baud_rate;
- else
- speed = speeds[ospeed];
-
- /* Read padding information. */
- while (*str >= '0' && *str <= '9') {
- padcount += *str++ - '0';
- padcount *= 10;
- }
- if (*str == '.') {
- str++;
- padcount += *str++ - '0';
- }
- if (*str == '*') {
- str++;
- padcount *= nlines;
- }
-
- /* Now output the capability string. */
- while (*str)
- (*outfun) (*str++);
-
- /* Do we need padding? */
- if (padcount == 0) return;
-
- /* padcount is now in units of tenths of msec. */
- padcount *= speeds[ospeed];
- padcount += 500;
- padcount /= 1000;
- if (speeds[ospeed] < 0)
- padcount = -padcount;
- else {
- padcount += 50;
- padcount /= 100;
- }
-
- /* And output the pad character. */
- while (padcount-- > 0)
- (*outfun) (PC);
- }
-
-
- #ifdef TEST
- /*ARGSUSED*/
- int main(int argc, char **argv)
- {
- char buf[1024];
- char *s;
-
- if (tgetent(buf, argv[1]) != 1) exit(1);
-
- printf("%s\n\n", term_entry);
-
- s = tgetstr("cm", NULL);
- if (s && *s == '\033') *s = '?';
-
- printf("tgetflag(li) = %d\n", tgetflag("li"));
- printf("tgetflag(mi) = %d\n", tgetflag("mi"));
- printf("tgetstr(cm) = [%s]\n", s);
- printf("tgetstr(ks) = [%s]\n", tgetstr("ks", NULL));
- printf("tgetnum(li) = %d\n", tgetnum("li"));
-
- return(0);
- }
- #endif
-
-