Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.bin / tip / tip / remcap.c
1 /*
2  * Copyright (c) 1983, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #ifndef lint
35 static char sccsid[] = "@(#)remcap.c    8.1 (Berkeley) 6/6/93";
36 #endif /* not lint */
37
38 /*
39  * remcap - routines for dealing with the remote host data base
40  *
41  * derived from termcap
42  */
43 #include <sys/types.h>
44 #include <fcntl.h>
45 #include <ctype.h>
46 #include <stdlib.h>
47 #include "pathnames.h"
48
49 #ifndef BUFSIZ
50 #define BUFSIZ          1024
51 #endif
52 #define MAXHOP          32              /* max number of tc= indirections */
53
54 #define tgetent         rgetent
55 #define tnchktc         rnchktc
56 #define tnamatch        rnamatch
57 #define tgetnum         rgetnum
58 #define tgetflag        rgetflag
59 #define tgetstr         rgetstr
60 #define E_TERMCAP       RM = _PATH_REMOTE
61 #define V_TERMCAP       "REMOTE"
62 #define V_TERM          "HOST"
63
64 char    *RM;
65
66 /*
67  * termcap - routines for dealing with the terminal capability data base
68  *
69  * BUG:         Should use a "last" pointer in tbuf, so that searching
70  *              for capabilities alphabetically would not be a n**2/2
71  *              process when large numbers of capabilities are given.
72  * Note:        If we add a last pointer now we will screw up the
73  *              tc capability. We really should compile termcap.
74  *
75  * Essentially all the work here is scanning and decoding escapes
76  * in string capabilities.  We don't use stdio because the editor
77  * doesn't, and because living w/o it is not hard.
78  */
79
80 static  char *tbuf;
81 static  int hopcount;   /* detect infinite loops in termcap, init 0 */
82 static  char *tskip();
83 char    *tgetstr();
84 static  char *tdecode();
85 static  char *remotefile;
86
87 /*
88  * Get an entry for terminal name in buffer bp,
89  * from the termcap file.  Parse is very rudimentary;
90  * we just notice escaped newlines.
91  */
92 tgetent(bp, name)
93         char *bp, *name;
94 {
95         char lbuf[BUFSIZ], *cp, *p;
96         int rc1, rc2;
97
98         remotefile = cp = getenv(V_TERMCAP);
99         if (cp == (char *)0 || strcmp(cp, _PATH_REMOTE) == 0) {
100                 remotefile = cp = _PATH_REMOTE;
101                 return (getent(bp, name, cp));
102         } else {
103                 if ((rc1 = getent(bp, name, cp)) != 1)
104                         *bp = '\0';
105                 remotefile = cp = _PATH_REMOTE;
106                 rc2 = getent(lbuf, name, cp);
107                 if (rc1 != 1 && rc2 != 1)
108                         return (rc2);
109                 if (rc2 == 1) {
110                         p = lbuf;
111                         if (rc1 == 1)
112                                 while (*p++ != ':')
113                                         ;
114                         if (strlen(bp) + strlen(p) > BUFSIZ) {
115                                 write(2, "Remcap entry too long\n", 23);
116                                 return (-1);
117                         }
118                         strcat(bp, p);
119                 }
120                 tbuf = bp;
121                 return (1);
122         }
123 }
124
125 getent(bp, name, cp)
126         char *bp, *name, *cp;
127 {
128         register int c;
129         register int i = 0, cnt = 0;
130         char ibuf[BUFSIZ], *cp2;
131         int tf;
132
133         tbuf = bp;
134         tf = 0;
135         /*
136          * TERMCAP can have one of two things in it. It can be the
137          * name of a file to use instead of /etc/termcap. In this
138          * case it better start with a "/". Or it can be an entry to
139          * use so we don't have to read the file. In this case it
140          * has to already have the newlines crunched out.
141          */
142         if (cp && *cp) {
143                 if (*cp!='/') {
144                         cp2 = getenv(V_TERM);
145                         if (cp2 == (char *)0 || strcmp(name,cp2) == 0) {
146                                 strcpy(bp,cp);
147                                 return (tnchktc());
148                         } else
149                                 tf = open(E_TERMCAP, O_RDONLY);
150                 } else
151                         tf = open(RM = cp, O_RDONLY);
152         }
153         if (tf == 0)
154                 tf = open(E_TERMCAP, O_RDONLY);
155         if (tf < 0)
156                 return (-1);
157         for (;;) {
158                 cp = bp;
159                 for (;;) {
160                         if (i == cnt) {
161                                 cnt = read(tf, ibuf, BUFSIZ);
162                                 if (cnt <= 0) {
163                                         close(tf);
164                                         return (0);
165                                 }
166                                 i = 0;
167                         }
168                         c = ibuf[i++];
169                         if (c == '\n') {
170                                 if (cp > bp && cp[-1] == '\\') {
171                                         cp--;
172                                         continue;
173                                 }
174                                 break;
175                         }
176                         if (cp >= bp+BUFSIZ) {
177                                 write(2,"Remcap entry too long\n", 23);
178                                 break;
179                         } else
180                                 *cp++ = c;
181                 }
182                 *cp = 0;
183
184                 /*
185                  * The real work for the match.
186                  */
187                 if (tnamatch(name)) {
188                         close(tf);
189                         return (tnchktc());
190                 }
191         }
192 }
193
194 /*
195  * tnchktc: check the last entry, see if it's tc=xxx. If so,
196  * recursively find xxx and append that entry (minus the names)
197  * to take the place of the tc=xxx entry. This allows termcap
198  * entries to say "like an HP2621 but doesn't turn on the labels".
199  * Note that this works because of the left to right scan.
200  */
201 tnchktc()
202 {
203         register char *p, *q;
204         char tcname[16];        /* name of similar terminal */
205         char tcbuf[BUFSIZ];
206         char *holdtbuf = tbuf;
207         int l;
208         char *cp;
209
210         p = tbuf + strlen(tbuf) - 2;    /* before the last colon */
211         while (*--p != ':')
212                 if (p<tbuf) {
213                         write(2, "Bad remcap entry\n", 18);
214                         return (0);
215                 }
216         p++;
217         /* p now points to beginning of last field */
218         if (p[0] != 't' || p[1] != 'c')
219                 return (1);
220         strcpy(tcname, p+3);
221         q = tcname;
222         while (*q && *q != ':')
223                 q++;
224         *q = 0;
225         if (++hopcount > MAXHOP) {
226                 write(2, "Infinite tc= loop\n", 18);
227                 return (0);
228         }
229         if (getent(tcbuf, tcname, remotefile) != 1) {
230                 if (strcmp(remotefile, _PATH_REMOTE) == 0)
231                         return (0);
232                 else if (getent(tcbuf, tcname, _PATH_REMOTE) != 1)
233                         return (0);
234         }
235         for (q = tcbuf; *q++ != ':'; )
236                 ;
237         l = p - holdtbuf + strlen(q);
238         if (l > BUFSIZ) {
239                 write(2, "Remcap entry too long\n", 23);
240                 q[BUFSIZ - (p-holdtbuf)] = 0;
241         }
242         strcpy(p, q);
243         tbuf = holdtbuf;
244         return (1);
245 }
246
247 /*
248  * Tnamatch deals with name matching.  The first field of the termcap
249  * entry is a sequence of names separated by |'s, so we compare
250  * against each such name.  The normal : terminator after the last
251  * name (before the first field) stops us.
252  */
253 tnamatch(np)
254         char *np;
255 {
256         register char *Np, *Bp;
257
258         Bp = tbuf;
259         if (*Bp == '#')
260                 return (0);
261         for (;;) {
262                 for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
263                         continue;
264                 if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
265                         return (1);
266                 while (*Bp && *Bp != ':' && *Bp != '|')
267                         Bp++;
268                 if (*Bp == 0 || *Bp == ':')
269                         return (0);
270                 Bp++;
271         }
272 }
273
274 /*
275  * Skip to the next field.  Notice that this is very dumb, not
276  * knowing about \: escapes or any such.  If necessary, :'s can be put
277  * into the termcap file in octal.
278  */
279 static char *
280 tskip(bp)
281         register char *bp;
282 {
283
284         while (*bp && *bp != ':')
285                 bp++;
286         if (*bp == ':')
287                 bp++;
288         return (bp);
289 }
290
291 /*
292  * Return the (numeric) option id.
293  * Numeric options look like
294  *      li#80
295  * i.e. the option string is separated from the numeric value by
296  * a # character.  If the option is not found we return -1.
297  * Note that we handle octal numbers beginning with 0.
298  */
299 tgetnum(id)
300         char *id;
301 {
302         register int i, base;
303         register char *bp = tbuf;
304
305         for (;;) {
306                 bp = tskip(bp);
307                 if (*bp == 0)
308                         return (-1);
309                 if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
310                         continue;
311                 if (*bp == '@')
312                         return (-1);
313                 if (*bp != '#')
314                         continue;
315                 bp++;
316                 base = 10;
317                 if (*bp == '0')
318                         base = 8;
319                 i = 0;
320                 while (isdigit(*bp))
321                         i *= base, i += *bp++ - '0';
322                 return (i);
323         }
324 }
325
326 /*
327  * Handle a flag option.
328  * Flag options are given "naked", i.e. followed by a : or the end
329  * of the buffer.  Return 1 if we find the option, or 0 if it is
330  * not given.
331  */
332 tgetflag(id)
333         char *id;
334 {
335         register char *bp = tbuf;
336
337         for (;;) {
338                 bp = tskip(bp);
339                 if (!*bp)
340                         return (0);
341                 if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
342                         if (!*bp || *bp == ':')
343                                 return (1);
344                         else if (*bp == '@')
345                                 return (0);
346                 }
347         }
348 }
349
350 /*
351  * Get a string valued option.
352  * These are given as
353  *      cl=^Z
354  * Much decoding is done on the strings, and the strings are
355  * placed in area, which is a ref parameter which is updated.
356  * No checking on area overflow.
357  */
358 char *
359 tgetstr(id, area)
360         char *id, **area;
361 {
362         register char *bp = tbuf;
363
364         for (;;) {
365                 bp = tskip(bp);
366                 if (!*bp)
367                         return (0);
368                 if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
369                         continue;
370                 if (*bp == '@')
371                         return (0);
372                 if (*bp != '=')
373                         continue;
374                 bp++;
375                 return (tdecode(bp, area));
376         }
377 }
378
379 /*
380  * Tdecode does the grung work to decode the
381  * string capability escapes.
382  */
383 static char *
384 tdecode(str, area)
385         register char *str;
386         char **area;
387 {
388         register char *cp;
389         register int c;
390         register char *dp;
391         int i;
392
393         cp = *area;
394         while ((c = *str++) && c != ':') {
395                 switch (c) {
396
397                 case '^':
398                         c = *str++ & 037;
399                         break;
400
401                 case '\\':
402                         dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
403                         c = *str++;
404 nextc:
405                         if (*dp++ == c) {
406                                 c = *dp++;
407                                 break;
408                         }
409                         dp++;
410                         if (*dp)
411                                 goto nextc;
412                         if (isdigit(c)) {
413                                 c -= '0', i = 2;
414                                 do
415                                         c <<= 3, c |= *str++ - '0';
416                                 while (--i && isdigit(*str));
417                         }
418                         break;
419                 }
420                 *cp++ = c;
421         }
422         *cp++ = 0;
423         str = *area;
424         *area = cp;
425         return (str);
426 }