1 /*        $NetBSD: ex_tag.c,v 1.12 2014/08/22 21:28:20 aymeric Exp $ */
2 /*-
3  * Copyright (c) 1992, 1993, 1994
4  *        The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
6  *        Keith Bostic.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * David Hitz of Auspex Systems, Inc.
10  *
11  * See the LICENSE file for redistribution information.
12  */
13 
14 #include "config.h"
15 
16 #include <sys/cdefs.h>
17 #if 0
18 #ifndef lint
19 static const char sccsid[] = "Id: ex_tag.c,v 10.50 2004/03/16 14:09:11 skimo Exp  (Berkeley) Date: 2004/03/16 14:09:11 ";
20 #endif /* not lint */
21 #else
22 __RCSID("$NetBSD: ex_tag.c,v 1.12 2014/08/22 21:28:20 aymeric Exp $");
23 #endif
24 
25 #include <sys/param.h>
26 #include <sys/types.h>                  /* XXX: param.h may not have included types.h */
27 
28 #ifdef HAVE_SYS_MMAN_H
29 #include <sys/mman.h>
30 #endif
31 
32 #include <sys/queue.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 
36 #include <bitstring.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <limits.h>
41 #include <stddef.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "../common/common.h"
48 #include "../vi/vi.h"
49 #include "tag.h"
50 
51 static char         *binary_search __P((char *, char *, char *));
52 static int           compare __P((char *, char *, char *));
53 static void          ctag_file __P((SCR *, TAGF *, char *, char **, size_t *));
54 static int           ctag_search __P((SCR *, CHAR_T *, size_t, char *));
55 #ifdef GTAGS
56 static int           getentry __P((char *, char **, char **, char **));
57 static TAGQ         *gtag_slist __P((SCR *, CHAR_T *, int));
58 #endif
59 static int           ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *));
60 static TAGQ         *ctag_slist __P((SCR *, CHAR_T *));
61 static char         *linear_search __P((char *, char *, char *, unsigned long));
62 static int           tag_copy __P((SCR *, TAG *, TAG **));
63 static int           tag_pop __P((SCR *, TAGQ *, int));
64 static int           tagf_copy __P((SCR *, TAGF *, TAGF **));
65 static int           tagf_free __P((SCR *, TAGF *));
66 static int           tagq_copy __P((SCR *, TAGQ *, TAGQ **));
67 
68 /*
69  * ex_tag_first --
70  *        The tag code can be entered from main, e.g., "vi -t tag".
71  *
72  * PUBLIC: int ex_tag_first __P((SCR *, const CHAR_T *));
73  */
74 int
ex_tag_first(SCR * sp,const CHAR_T * tagarg)75 ex_tag_first(SCR *sp, const CHAR_T *tagarg)
76 {
77           EXCMD cmd;
78 
79           /* Build an argument for the ex :tag command. */
80           ex_cinit(sp, &cmd, C_TAG, 0, OOBLNO, OOBLNO, 0);
81           argv_exp0(sp, &cmd, tagarg, STRLEN(tagarg));
82 
83           /*
84            * XXX
85            * Historic vi went ahead and created a temporary file when it failed
86            * to find the tag.  We match historic practice, but don't distinguish
87            * between real error and failure to find the tag.
88            */
89           if (ex_tag_push(sp, &cmd))
90                     return (0);
91 
92           /* Display tags in the center of the screen. */
93           F_CLR(sp, SC_SCR_TOP);
94           F_SET(sp, SC_SCR_CENTER);
95 
96           return (0);
97 }
98 
99 #ifdef GTAGS
100 /*
101  * ex_rtag_push -- ^]
102  *              :rtag[!] [string]
103  *
104  * Enter a new TAGQ context based on a ctag string.
105  *
106  * PUBLIC: int ex_rtag_push __P((SCR *, EXCMD *));
107  */
108 int
ex_rtag_push(SCR * sp,EXCMD * cmdp)109 ex_rtag_push(SCR *sp, EXCMD *cmdp)
110 {
111           F_SET(cmdp, E_REFERENCE);
112           return ex_tag_push(sp, cmdp);
113 }
114 #endif
115 
116 /*
117  * ex_tag_push -- ^]
118  *                    :tag[!] [string]
119  *
120  * Enter a new TAGQ context based on a ctag string.
121  *
122  * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *));
123  */
124 int
ex_tag_push(SCR * sp,EXCMD * cmdp)125 ex_tag_push(SCR *sp, EXCMD *cmdp)
126 {
127           EX_PRIVATE *exp;
128           TAGQ *tqp;
129           unsigned long tl;
130 
131           exp = EXP(sp);
132           switch (cmdp->argc) {
133           case 1:
134                     if (exp->tag_last != NULL)
135                               free(exp->tag_last);
136 
137                     if ((exp->tag_last = v_wstrdup(sp, cmdp->argv[0]->bp,
138                         cmdp->argv[0]->len)) == NULL) {
139                               msgq(sp, M_SYSERR, NULL);
140                               return (1);
141                     }
142 
143                     /* Taglength may limit the number of characters. */
144                     if ((tl =
145                         O_VAL(sp, O_TAGLENGTH)) != 0 && STRLEN(exp->tag_last) > tl)
146                               exp->tag_last[tl] = '\0';
147                     break;
148           case 0:
149                     if (exp->tag_last == NULL) {
150                               msgq(sp, M_ERR, "158|No previous tag entered");
151                               return (1);
152                     }
153                     break;
154           default:
155                     abort();
156           }
157 
158           /* Get the tag information. */
159 #ifdef GTAGS
160           if (O_ISSET(sp, O_GTAGSMODE)) {
161                     if ((tqp = gtag_slist(sp, exp->tag_last,
162                         F_ISSET(cmdp, E_REFERENCE))) == NULL)
163                               return (1);
164           } else
165 #endif
166           if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL)
167                     return (1);
168 
169           if (tagq_push(sp, tqp, F_ISSET(cmdp, E_NEWSCREEN),
170                                      FL_ISSET(cmdp->iflags, E_C_FORCE)))
171                     return 1;
172 
173           return 0;
174 }
175 
176 /*
177  * ex_tag_next --
178  *        Switch context to the next TAG.
179  *
180  * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *));
181  */
182 int
ex_tag_next(SCR * sp,EXCMD * cmdp)183 ex_tag_next(SCR *sp, EXCMD *cmdp)
184 {
185           EX_PRIVATE *exp;
186           TAG *tp;
187           TAGQ *tqp;
188           const char *np;
189           size_t nlen;
190 
191           exp = EXP(sp);
192           if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) {
193                     tag_msg(sp, TAG_EMPTY, NULL);
194                     return (1);
195           }
196           if ((tp = TAILQ_NEXT(tqp->current, q)) == NULL) {
197                     msgq(sp, M_ERR, "282|Already at the last tag of this group");
198                     return (1);
199           }
200           if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
201                     return (1);
202           tqp->current = tp;
203 
204           if (F_ISSET(tqp, TAG_CSCOPE))
205                     (void)cscope_search(sp, tqp, tp);
206           else
207                     (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
208           if (tqp->current->msg) {
209               INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
210                          np, nlen);
211               msgq(sp, M_INFO, "%s", np);
212           }
213           return (0);
214 }
215 
216 /*
217  * ex_tag_prev --
218  *        Switch context to the next TAG.
219  *
220  * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *));
221  */
222 int
ex_tag_prev(SCR * sp,EXCMD * cmdp)223 ex_tag_prev(SCR *sp, EXCMD *cmdp)
224 {
225           EX_PRIVATE *exp;
226           TAG *tp;
227           TAGQ *tqp;
228           const char *np;
229           size_t nlen;
230 
231           exp = EXP(sp);
232           if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) {
233                     tag_msg(sp, TAG_EMPTY, NULL);
234                     return (0);
235           }
236           if ((tp = TAILQ_PREV(tqp->current, _tagqh, q)) == NULL) {
237                     msgq(sp, M_ERR, "255|Already at the first tag of this group");
238                     return (1);
239           }
240           if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE)))
241                     return (1);
242           tqp->current = tp;
243 
244           if (F_ISSET(tqp, TAG_CSCOPE))
245                     (void)cscope_search(sp, tqp, tp);
246           else
247                     (void)ctag_search(sp, tp->search, tp->slen, tqp->tag);
248           if (tqp->current->msg) {
249               INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
250                          np, nlen);
251               msgq(sp, M_INFO, "%s", np);
252           }
253           return (0);
254 }
255 
256 /*
257  * ex_tag_nswitch --
258  *        Switch context to the specified TAG.
259  *
260  * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int));
261  */
262 int
ex_tag_nswitch(SCR * sp,TAG * tp,int force)263 ex_tag_nswitch(SCR *sp, TAG *tp, int force)
264 {
265           /* Get a file structure. */
266           if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
267                     return (1);
268 
269           /* If not changing files, return, we're done. */
270           if (tp->frp == sp->frp)
271                     return (0);
272 
273           /* Check for permission to leave. */
274           if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
275                     return (1);
276 
277           /* Initialize the new file. */
278           if (file_init(sp, tp->frp, NULL, FS_SETALT))
279                     return (1);
280 
281           /* Display tags in the center of the screen. */
282           F_CLR(sp, SC_SCR_TOP);
283           F_SET(sp, SC_SCR_CENTER);
284 
285           /* Switch. */
286           F_SET(sp, SC_FSWITCH);
287           return (0);
288 }
289 
290 /*
291  * ex_tag_Nswitch --
292  *        Switch context to the specified TAG in a new screen.
293  *
294  * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int));
295  */
296 int
ex_tag_Nswitch(SCR * sp,TAG * tp,int force)297 ex_tag_Nswitch(SCR *sp, TAG *tp, int force)
298 {
299           SCR *new;
300 
301           /* Get a file structure. */
302           if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL)
303                     return (1);
304 
305           /* Get a new screen. */
306           if (screen_init(sp->gp, sp, &new))
307                     return (1);
308           if (vs_split(sp, new, 0)) {
309                     (void)file_end(new, new->ep, 1);
310                     (void)screen_fini(new);
311                     return (1);
312           }
313 
314           /* Get a backing file. */
315           if (tp->frp == sp->frp) {
316                     /* Copy file state. */
317                     new->ep = sp->ep;
318                     ++new->ep->refcnt;
319                     TAILQ_INSERT_HEAD(&new->ep->scrq, new, eq);
320 
321                     new->frp = tp->frp;
322                     new->frp->flags = sp->frp->flags;
323           } else if (file_init(new, tp->frp, NULL, force)) {
324                     (void)vs_discard(new, NULL);
325                     (void)screen_end(new);
326                     return (1);
327           }
328 
329           /* Create the argument list. */
330           new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name);
331 
332           /* Display tags in the center of the screen. */
333           F_CLR(new, SC_SCR_TOP);
334           F_SET(new, SC_SCR_CENTER);
335 
336           /* Switch. */
337           sp->nextdisp = new;
338           F_SET(sp, SC_SSWITCH);
339 
340           return (0);
341 }
342 
343 /*
344  * ex_tag_pop -- ^T
345  *                   :tagp[op][!] [number | file]
346  *
347  *        Pop to a previous TAGQ context.
348  *
349  * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *));
350  */
351 int
ex_tag_pop(SCR * sp,EXCMD * cmdp)352 ex_tag_pop(SCR *sp, EXCMD *cmdp)
353 {
354           EX_PRIVATE *exp;
355           TAGQ *tqp, *dtqp = NULL;
356           size_t arglen;
357           long off;
358           const char *arg;
359           char *p, *t;
360           size_t nlen;
361 
362           /* Check for an empty stack. */
363           exp = EXP(sp);
364           if (TAILQ_EMPTY(&exp->tq)) {
365                     tag_msg(sp, TAG_EMPTY, NULL);
366                     return (1);
367           }
368 
369           /* Find the last TAG structure that we're going to DISCARD! */
370           switch (cmdp->argc) {
371           case 0:                                 /* Pop one tag. */
372                     dtqp = TAILQ_FIRST(&exp->tq);
373                     break;
374           case 1:                                 /* Name or number. */
375                     INT2CHAR(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len+1,
376                                arg, nlen);
377                     off = strtol(arg, &p, 10);
378                     if (*p != '\0')
379                               goto filearg;
380 
381                     /* Number: pop that many queue entries. */
382                     if (off < 1)
383                               return (0);
384                     TAILQ_FOREACH(tqp, &exp->tq, q)
385                               if (--off <= 1)
386                                         break;
387                     if (tqp == NULL) {
388                               msgq(sp, M_ERR,
389           "159|Less than %s entries on the tags stack; use :display t[ags]",
390                                   arg);
391                               return (1);
392                     }
393                     dtqp = tqp;
394                     break;
395 
396                     /* File argument: pop to that queue entry. */
397 filearg:  arglen = strlen(arg);
398                     for (tqp = TAILQ_FIRST(&exp->tq);
399                         tqp != NULL;
400                         dtqp = tqp, tqp = TAILQ_NEXT(tqp, q)) {
401                               /* Don't pop to the current file. */
402                               if (tqp == TAILQ_FIRST(&exp->tq))
403                                         continue;
404                               p = tqp->current->frp->name;
405                               if ((t = strrchr(p, '/')) == NULL)
406                                         t = p;
407                               else
408                                         ++t;
409                               if (!strncmp(arg, t, arglen))
410                                         break;
411                     }
412                     if (tqp == NULL) {
413                               msgq_str(sp, M_ERR, arg,
414           "160|No file %s on the tags stack to return to; use :display t[ags]");
415                               return (1);
416                     }
417                     break;
418           default:
419                     abort();
420           }
421 
422           return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE)));
423 }
424 
425 /*
426  * ex_tag_top -- :tagt[op][!]
427  *        Clear the tag stack.
428  *
429  * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *));
430  */
431 int
ex_tag_top(SCR * sp,EXCMD * cmdp)432 ex_tag_top(SCR *sp, EXCMD *cmdp)
433 {
434           EX_PRIVATE *exp;
435           TAGQ *tqp;
436 
437           exp = EXP(sp);
438 
439           /* Check for an empty stack. */
440           if (TAILQ_EMPTY(&exp->tq)) {
441                     tag_msg(sp, TAG_EMPTY, NULL);
442                     return (1);
443           }
444 
445           /* Return to the oldest information. */
446           tqp = TAILQ_LAST(&exp->tq, _tqh);
447           tqp = TAILQ_PREV(tqp, _tqh, q);
448           return tag_pop(sp, tqp, FL_ISSET(cmdp->iflags, E_C_FORCE));
449 }
450 
451 /*
452  * tag_pop --
453  *        Pop up to and including the specified TAGQ context.
454  */
455 static int
tag_pop(SCR * sp,TAGQ * dtqp,int force)456 tag_pop(SCR *sp, TAGQ *dtqp, int force)
457 {
458           EX_PRIVATE *exp;
459           TAG *tp;
460           TAGQ *tqp;
461 
462           exp = EXP(sp);
463 
464           /*
465            * Update the cursor from the saved TAG information of the TAG
466            * structure we're moving to.
467            */
468           tp = TAILQ_NEXT(dtqp, q)->current;
469           if (tp->frp == sp->frp) {
470                     sp->lno = tp->lno;
471                     sp->cno = tp->cno;
472           } else {
473                     if (file_m1(sp, force, FS_ALL | FS_POSSIBLE))
474                               return (1);
475 
476                     tp->frp->lno = tp->lno;
477                     tp->frp->cno = tp->cno;
478                     F_SET(sp->frp, FR_CURSORSET);
479                     if (file_init(sp, tp->frp, NULL, FS_SETALT))
480                               return (1);
481 
482                     F_SET(sp, SC_FSWITCH);
483           }
484 
485           /* Pop entries off the queue up to and including dtqp. */
486           do {
487                     tqp = TAILQ_FIRST(&exp->tq);
488                     if (tagq_free(sp, tqp))
489                               return (0);
490           } while (tqp != dtqp);
491 
492           /*
493            * If only a single tag left, we've returned to the first tag point,
494            * and the stack is now empty.
495            */
496           if (TAILQ_NEXT(TAILQ_FIRST(&exp->tq), q) == NULL)
497                     tagq_free(sp, TAILQ_FIRST(&exp->tq));
498 
499           return (0);
500 }
501 
502 /*
503  * ex_tag_display --
504  *        Display the list of tags.
505  *
506  * PUBLIC: int ex_tag_display __P((SCR *));
507  */
508 int
ex_tag_display(SCR * sp)509 ex_tag_display(SCR *sp)
510 {
511           EX_PRIVATE *exp;
512           TAG *tp;
513           TAGQ *tqp;
514           int cnt;
515           size_t len;
516           char *p;
517 
518           exp = EXP(sp);
519           if (TAILQ_EMPTY(&exp->tq)) {
520                     tag_msg(sp, TAG_EMPTY, NULL);
521                     return (0);
522           }
523 
524           /*
525            * We give the file name 20 columns and the search string the rest.
526            * If there's not enough room, we don't do anything special, it's
527            * not worth the effort, it just makes the display more confusing.
528            *
529            * We also assume that characters in file names map 1-1 to printing
530            * characters.  This might not be true, but I don't think it's worth
531            * fixing.  (The obvious fix is to pass the filenames through the
532            * msg_print function.)
533            */
534 #define   L_NAME    30                  /* Name. */
535 #define   L_SLOP     4                  /* Leading number plus trailing *. */
536 #define   L_SPACE    5                  /* Spaces after name, before tag. */
537 #define   L_TAG     20                  /* Tag. */
538           if (sp->cols <= L_NAME + L_SLOP) {
539                     msgq(sp, M_ERR, "292|Display too small.");
540                     return (0);
541           }
542 
543           /*
544            * Display the list of tags for each queue entry.  The first entry
545            * is numbered, and the current tag entry has an asterisk appended.
546            */
547           for (cnt = 1, tqp = TAILQ_FIRST(&exp->tq); !INTERRUPTED(sp) &&
548               tqp != NULL; ++cnt, tqp = TAILQ_NEXT(tqp, q))
549                     TAILQ_FOREACH(tp, &tqp->tagq, q) {
550                               if (tp == TAILQ_FIRST(&tqp->tagq))
551                                         (void)ex_printf(sp, "%2d ", cnt);
552                               else
553                                         (void)ex_printf(sp, "   ");
554                               p = tp->frp == NULL ? tp->fname : tp->frp->name;
555                               if ((len = strlen(p)) > L_NAME) {
556                                         len = len - (L_NAME - 4);
557                                         (void)ex_printf(sp, "   ... %*.*s",
558                                             L_NAME - 4, L_NAME - 4, p + len);
559                               } else
560                                         (void)ex_printf(sp,
561                                             "   %*.*s", L_NAME, L_NAME, p);
562                               if (tqp->current == tp)
563                                         (void)ex_printf(sp, "*");
564 
565                               if (tp == TAILQ_FIRST(&tqp->tagq) && tqp->tag != NULL &&
566                                   (sp->cols - L_NAME) >= L_TAG + L_SPACE) {
567                                         len = strlen(tqp->tag);
568                                         if (len > sp->cols - (L_NAME + L_SPACE))
569                                                   len = sp->cols - (L_NAME + L_SPACE);
570                                         (void)ex_printf(sp, "%s%.*s",
571                                             tqp->current == tp ? "    " : "     ",
572                                             (int)len, tqp->tag);
573                               }
574                               (void)ex_printf(sp, "\n");
575                     }
576           return (0);
577 }
578 
579 /*
580  * ex_tag_copy --
581  *        Copy a screen's tag structures.
582  *
583  * PUBLIC: int ex_tag_copy __P((SCR *, SCR *));
584  */
585 int
ex_tag_copy(SCR * orig,SCR * sp)586 ex_tag_copy(SCR *orig, SCR *sp)
587 {
588           EX_PRIVATE *oexp, *nexp;
589           TAGQ *aqp, *tqp;
590           TAG *ap, *tp;
591           TAGF *atfp, *tfp;
592 
593           oexp = EXP(orig);
594           nexp = EXP(sp);
595 
596           /* Copy tag queue and tags stack. */
597           TAILQ_FOREACH(aqp, &oexp->tq, q) {
598                     if (tagq_copy(sp, aqp, &tqp))
599                               return (1);
600                     TAILQ_FOREACH(ap, &aqp->tagq, q) {
601                               if (tag_copy(sp, ap, &tp))
602                                         return (1);
603                               /* Set the current pointer. */
604                               if (aqp->current == ap)
605                                         tqp->current = tp;
606                               TAILQ_INSERT_TAIL(&tqp->tagq, tp, q);
607                     }
608                     TAILQ_INSERT_TAIL(&nexp->tq, tqp, q);
609                     F_SET(tqp, TAG_IS_LINKED);
610           }
611 
612           /* Copy list of tag files. */
613 
614           TAILQ_FOREACH(atfp, &oexp->tagfq, q) {
615                     if (tagf_copy(sp, atfp, &tfp))
616                               return (1);
617                     TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q);
618           }
619 
620           /* Copy the last tag. */
621           if (oexp->tag_last != NULL &&
622               (nexp->tag_last = v_wstrdup(sp, oexp->tag_last,
623                                                   STRLEN(oexp->tag_last))) == NULL) {
624                     msgq(sp, M_SYSERR, NULL);
625                     return (1);
626           }
627           return (0);
628 }
629 
630 /*
631  * tagf_copy --
632  *        Copy a TAGF structure and return it in new memory.
633  */
634 static int
tagf_copy(SCR * sp,TAGF * otfp,TAGF ** tfpp)635 tagf_copy(SCR *sp, TAGF *otfp, TAGF **tfpp)
636 {
637           TAGF *tfp;
638 
639           MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
640           *tfp = *otfp;
641 
642           /* XXX: Allocate as part of the TAGF structure!!! */
643           if ((tfp->name = strdup(otfp->name)) == NULL)
644                     return (1);
645 
646           *tfpp = tfp;
647           return (0);
648 }
649 
650 /*
651  * tagq_copy --
652  *        Copy a TAGQ structure and return it in new memory.
653  */
654 static int
tagq_copy(SCR * sp,TAGQ * otqp,TAGQ ** tqpp)655 tagq_copy(SCR *sp, TAGQ *otqp, TAGQ **tqpp)
656 {
657           TAGQ *tqp;
658           size_t len;
659 
660           len = sizeof(TAGQ);
661           if (otqp->tag != NULL)
662                     len += otqp->tlen + 1;
663           MALLOC_RET(sp, tqp, TAGQ *, len);
664           memcpy(tqp, otqp, len);
665 
666           TAILQ_INIT(&tqp->tagq);
667           tqp->current = NULL;
668           if (otqp->tag != NULL)
669                     tqp->tag = tqp->buf;
670 
671           *tqpp = tqp;
672           return (0);
673 }
674 
675 /*
676  * tag_copy --
677  *        Copy a TAG structure and return it in new memory.
678  */
679 static int
tag_copy(SCR * sp,TAG * otp,TAG ** tpp)680 tag_copy(SCR *sp, TAG *otp, TAG **tpp)
681 {
682           TAG *tp;
683           size_t len;
684 
685           len = sizeof(TAG);
686           if (otp->fname != NULL)
687                     len += otp->fnlen + 1;
688           if (otp->search != NULL)
689                     len += otp->slen + 1;
690           if (otp->msg != NULL)
691                     len += otp->mlen + 1;
692           MALLOC_RET(sp, tp, TAG *, len);
693           memcpy(tp, otp, len);
694 
695           if (otp->fname != NULL)
696                     tp->fname = (char *)tp->buf;
697           if (otp->search != NULL)
698                     tp->search = tp->buf + (otp->search - otp->buf);
699           if (otp->msg != NULL)
700                     tp->msg = tp->buf + (otp->msg - otp->buf);
701 
702           *tpp = tp;
703           return (0);
704 }
705 
706 /*
707  * tagf_free --
708  *        Free a TAGF structure.
709  */
710 static int
tagf_free(SCR * sp,TAGF * tfp)711 tagf_free(SCR *sp, TAGF *tfp)
712 {
713           EX_PRIVATE *exp;
714 
715           exp = EXP(sp);
716           TAILQ_REMOVE(&exp->tagfq, tfp, q);
717           free(tfp->name);
718           free(tfp);
719           return (0);
720 }
721 
722 /*
723  * tagq_free --
724  *        Free a TAGQ structure (and associated TAG structures).
725  *
726  * PUBLIC: int tagq_free __P((SCR *, TAGQ *));
727  */
728 int
tagq_free(SCR * sp,TAGQ * tqp)729 tagq_free(SCR *sp, TAGQ *tqp)
730 {
731           EX_PRIVATE *exp;
732           TAG *tp;
733 
734           exp = EXP(sp);
735           while ((tp = TAILQ_FIRST(&tqp->tagq)) != NULL) {
736                     TAILQ_REMOVE(&tqp->tagq, tp, q);
737                     free(tp);
738           }
739           /*
740            * !!!
741            * If allocated and then the user failed to switch files, the TAGQ
742            * structure was never attached to any list.
743            */
744           if (F_ISSET(tqp, TAG_IS_LINKED))
745                     TAILQ_REMOVE(&exp->tq, tqp, q);
746           free(tqp);
747           return (0);
748 }
749 
750 /*
751  * PUBLIC: int tagq_push __P((SCR*, TAGQ*, int, int ));
752  */
753 int
tagq_push(SCR * sp,TAGQ * tqp,int new_screen,int force)754 tagq_push(SCR *sp, TAGQ *tqp, int new_screen, int force)
755 {
756           EX_PRIVATE *exp;
757           FREF *frp;
758           TAG *rtp;
759           TAGQ *rtqp;
760           db_recno_t lno;
761           size_t cno;
762           int istmp;
763           const char *np;
764           size_t nlen;
765 
766           exp = EXP(sp);
767 
768           /*
769            * Allocate all necessary memory before swapping screens.  Initialize
770            * flags so we know what to free.
771            */
772           rtp = NULL;
773           rtqp = NULL;
774           if (TAILQ_EMPTY(&exp->tq)) {
775                     /* Initialize the `local context' tag queue structure. */
776                     CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ));
777                     TAILQ_INIT(&rtqp->tagq);
778 
779                     /* Initialize and link in its tag structure. */
780                     CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG));
781                     TAILQ_INSERT_HEAD(&rtqp->tagq, rtp, q);
782                     rtqp->current = rtp;
783           }
784 
785           /*
786            * Stick the current context information in a convenient place, we're
787            * about to lose it.  Note, if we're called on editor startup, there
788            * will be no FREF structure.
789            */
790           frp = sp->frp;
791           lno = sp->lno;
792           cno = sp->cno;
793           istmp = frp == NULL || (F_ISSET(frp, FR_TMPFILE) && !new_screen);
794 
795           /* Try to switch to the tag. */
796           if (new_screen) {
797                     if (ex_tag_Nswitch(sp, TAILQ_FIRST(&tqp->tagq), force))
798                               goto err;
799 
800                     /* Everything else gets done in the new screen. */
801                     sp = sp->nextdisp;
802                     exp = EXP(sp);
803           } else
804                     if (ex_tag_nswitch(sp, TAILQ_FIRST(&tqp->tagq), force))
805                               goto err;
806 
807           /*
808            * If this is the first tag, put a `current location' queue entry
809            * in place, so we can pop all the way back to the current mark.
810            * Note, it doesn't point to much of anything, it's a placeholder.
811            */
812           if (TAILQ_EMPTY(&exp->tq)) {
813                     TAILQ_INSERT_HEAD(&exp->tq, rtqp, q);
814                     F_SET(rtqp, TAG_IS_LINKED);
815           } else {
816                     free(rtqp);
817                     rtqp = TAILQ_FIRST(&exp->tq);
818           }
819 
820           /* Link the new TAGQ structure into place. */
821           TAILQ_INSERT_HEAD(&exp->tq, tqp, q);
822           F_SET(tqp, TAG_IS_LINKED);
823 
824           (void)ctag_search(sp,
825               tqp->current->search, tqp->current->slen, tqp->tag);
826           if (tqp->current->msg) {
827               INT2CHAR(sp, tqp->current->msg, tqp->current->mlen + 1,
828                          np, nlen);
829               msgq(sp, M_INFO, "%s", np);
830           }
831 
832           /*
833            * Move the current context from the temporary save area into the
834            * right structure.
835            *
836            * If we were in a temporary file, we don't have a context to which
837            * we can return, so just make it be the same as what we're moving
838            * to.  It will be a little odd that ^T doesn't change anything, but
839            * I don't think it's a big deal.
840            */
841           if (istmp) {
842                     rtqp->current->frp = sp->frp;
843                     rtqp->current->lno = sp->lno;
844                     rtqp->current->cno = sp->cno;
845           } else {
846                     rtqp->current->frp = frp;
847                     rtqp->current->lno = lno;
848                     rtqp->current->cno = cno;
849           }
850           return (0);
851 
852 err:
853 alloc_err:
854           if (rtqp != NULL)
855                     free(rtqp);
856           if (rtp != NULL)
857                     free(rtp);
858           tagq_free(sp, tqp);
859           return (1);
860 }
861 
862 /*
863  * tag_msg
864  *        A few common messages.
865  *
866  * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *));
867  */
868 void
tag_msg(SCR * sp,tagmsg_t msg,char * tag)869 tag_msg(SCR *sp, tagmsg_t msg, char *tag)
870 {
871           switch (msg) {
872           case TAG_BADLNO:
873                     msgq_str(sp, M_ERR, tag,
874               "164|%s: the tag's line number is past the end of the file");
875                     break;
876           case TAG_EMPTY:
877                     msgq(sp, M_INFO, "165|The tags stack is empty");
878                     break;
879           case TAG_SEARCH:
880                     msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found");
881                     break;
882           default:
883                     abort();
884           }
885 }
886 
887 /*
888  * ex_tagf_alloc --
889  *        Create a new list of ctag files.
890  *
891  * PUBLIC: int ex_tagf_alloc __P((SCR *, const char *));
892  */
893 int
ex_tagf_alloc(SCR * sp,const char * str)894 ex_tagf_alloc(SCR *sp, const char *str)
895 {
896           EX_PRIVATE *exp;
897           TAGF *tfp;
898           size_t len;
899           const char *p, *t;
900 
901           /* Free current queue. */
902           exp = EXP(sp);
903           while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL)
904                     tagf_free(sp, tfp);
905 
906           /* Create new queue. */
907           for (p = t = str;; ++p) {
908                     if (*p == '\0' || isblank((unsigned char)*p)) {
909                               if ((len = p - t) > 1) {
910                                         MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF));
911                                         MALLOC(sp, tfp->name, char *, len + 1);
912                                         if (tfp->name == NULL) {
913                                                   free(tfp);
914                                                   return (1);
915                                         }
916                                         memcpy(tfp->name, t, len);
917                                         tfp->name[len] = '\0';
918                                         tfp->flags = 0;
919                                         TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q);
920                               }
921                               t = p + 1;
922                     }
923                     if (*p == '\0')
924                                break;
925           }
926           return (0);
927 }
928                                                             /* Free previous queue. */
929 /*
930  * ex_tag_free --
931  *        Free the ex tag information.
932  *
933  * PUBLIC: int ex_tag_free __P((SCR *));
934  */
935 int
ex_tag_free(SCR * sp)936 ex_tag_free(SCR *sp)
937 {
938           EX_PRIVATE *exp;
939           TAGF *tfp;
940           TAGQ *tqp;
941 
942           /* Free up tag information. */
943           exp = EXP(sp);
944           while ((tqp = TAILQ_FIRST(&exp->tq)) != NULL)
945                     tagq_free(sp, tqp);
946           while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL)
947                     tagf_free(sp, tfp);
948           if (exp->tag_last != NULL)
949                     free(exp->tag_last);
950           return (0);
951 }
952 
953 /*
954  * ctag_search --
955  *        Search a file for a tag.
956  */
957 static int
ctag_search(SCR * sp,CHAR_T * search,size_t slen,char * tag)958 ctag_search(SCR *sp, CHAR_T *search, size_t slen, char *tag)
959 {
960           MARK m;
961           char *p;
962           const char *np;
963           size_t nlen;
964 
965           /*
966            * !!!
967            * The historic tags file format (from a long, long time ago...)
968            * used a line number, not a search string.  I got complaints, so
969            * people are still using the format.  POSIX 1003.2 permits it.
970            */
971           if (ISDIGIT((UCHAR_T)search[0])) {
972                     INT2CHAR(sp, search, slen+1, np, nlen);
973                     m.lno = atoi(np);
974                     if (!db_exist(sp, m.lno)) {
975                               tag_msg(sp, TAG_BADLNO, tag);
976                               return (1);
977                     }
978           } else {
979                     /*
980                      * Search for the tag; cheap fallback for C functions
981                      * if the name is the same but the arguments have changed.
982                      */
983                     m.lno = 1;
984                     m.cno = 0;
985                     if (f_search(sp, &m, &m,
986                         search, slen, NULL,
987                         SEARCH_FIRST | SEARCH_TAG | SEARCH_PARSE)) {
988                               INT2CHAR(sp, search, slen, np, nlen);
989                               if ((p = strrchr(np, '(')) != NULL) {
990                                         slen = p - np;
991                                         if (f_search(sp, &m, &m, search, slen,
992                                             NULL, SEARCH_FIRST | SEARCH_TAG))
993                                                   goto notfound;
994                               } else {
995 notfound:                     tag_msg(sp, TAG_SEARCH, tag);
996                                         return (1);
997                               }
998                     }
999                     /*
1000                      * !!!
1001                      * Historically, tags set the search direction if it wasn't
1002                      * already set.
1003                      */
1004                     if (sp->searchdir == NOTSET)
1005                               sp->searchdir = FORWARD;
1006           }
1007 
1008           /*
1009            * !!!
1010            * Tags move to the first non-blank, NOT the search pattern start.
1011            */
1012           sp->lno = m.lno;
1013           sp->cno = 0;
1014           (void)nonblank(sp, sp->lno, &sp->cno);
1015           return (0);
1016 }
1017 
1018 #ifdef GTAGS
1019 /*
1020  * getentry --
1021  *        get tag information from current line.
1022  *
1023  * gtags temporary file format.
1024  * <tag>   <lineno>  <file>         <image>
1025  *
1026  * sample.
1027  * +------------------------------------------------
1028  * |main     30      main.c         main(argc, argv)
1029  * |func     21      subr.c         func(arg)
1030  */
1031 static int
getentry(char * buf,char ** tag,char ** file,char ** line)1032 getentry(char *buf, char **tag, char **file, char **line)
1033 {
1034           char *p = buf;
1035 
1036           for (*tag = p; *p && !isspace((unsigned char)*p); p++)      /* tag name */
1037                     ;
1038           if (*p == 0)
1039                     goto err;
1040           *p++ = 0;
1041           for (; *p && isspace((unsigned char)*p); p++)     /* (skip blanks) */
1042                     ;
1043           if (*p == 0)
1044                     goto err;
1045           *line = p;                                                  /* line no */
1046           for (*line = p; *p && !isspace((unsigned char)*p); p++)
1047                     ;
1048           if (*p == 0)
1049                     goto err;
1050           *p++ = 0;
1051           for (; *p && isspace((unsigned char)*p); p++)     /* (skip blanks) */
1052                     ;
1053           if (*p == 0)
1054                     goto err;
1055           *file = p;                                                  /* file name */
1056           for (*file = p; *p && !isspace((unsigned char)*p); p++)
1057                     ;
1058           if (*p == 0)
1059                     goto err;
1060           *p = 0;
1061 
1062           /* value check */
1063           if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
1064                     return 1; /* OK */
1065 err:
1066           return 0;           /* ERROR */
1067 }
1068 
1069 /*
1070  * gtag_slist --
1071  *        Search the list of tags files for a tag, and return tag queue.
1072  */
1073 static TAGQ *
gtag_slist(SCR * sp,CHAR_T * tag,int ref)1074 gtag_slist(SCR *sp, CHAR_T *tag, int ref)
1075 {
1076           TAGQ *tqp;
1077           size_t len, nlen, slen, wlen;
1078           TAG *tp;
1079           const char *np;
1080           char *name, *file, *search;
1081           char command[BUFSIZ];
1082           char buf[BUFSIZ];
1083           const CHAR_T *wp;
1084           FILE *fp;
1085 
1086           /* Allocate and initialize the tag queue structure. */
1087           INT2CHAR(sp, tag, STRLEN(tag) + 1, np, nlen);
1088           len = nlen - 1;
1089           CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1090           TAILQ_INIT(&tqp->tagq);
1091           tqp->tag = tqp->buf;
1092           memcpy(tqp->tag, np, (tqp->tlen = len) + 1);
1093 
1094           /*
1095            * Find the tag, only display missing file messages once, and
1096            * then only if we didn't find the tag.
1097            */
1098           snprintf(command, sizeof(command), "global -%s '%s'", ref ? "rx" : "x",
1099               np);
1100           if ((fp = popen(command, "r")) != NULL) {
1101                     while (fgets(buf, sizeof(buf), fp)) {
1102                               if (buf[strlen(buf)-1] == '\n')                   /* chop(buf) */
1103                                         buf[strlen(buf)-1] = 0;
1104                               else
1105                                         while (fgetc(fp) != '\n')
1106                                                   ;
1107                               if (getentry(buf, &name, &file, &search) == 0) {
1108                                         break;
1109                               }
1110                               slen = strlen(search);
1111                               CALLOC_GOTO(sp, tp,
1112                                   TAG *, 1, sizeof(TAG) + strlen(file) + 1 +
1113                                   (slen + 1) * sizeof(CHAR_T));
1114                               tp->fname = (char *)tp->buf;
1115                               strcpy(tp->fname, file);
1116                               tp->fnlen = strlen(file);
1117                               tp->search = (CHAR_T *)(tp->fname + tp->fnlen + 1);
1118                               CHAR2INT(sp, search, slen + 1, wp, wlen);
1119                               MEMCPYW(tp->search, wp, (tp->slen = slen) + 1);
1120                               TAILQ_INSERT_TAIL(&tqp->tagq, tp, q);
1121                     }
1122                     pclose(fp);
1123           }
1124 
1125           /* Check to see if we found anything. */
1126           if (TAILQ_EMPTY(&tqp->tagq)) {
1127                     msgq_str(sp, M_ERR, np, "162|%s: tag not found");
1128                     free(tqp);
1129                     return (NULL);
1130           }
1131 
1132           tqp->current = TAILQ_FIRST(&tqp->tagq);
1133           return (tqp);
1134 
1135 alloc_err:
1136           free(tqp);
1137           return (NULL);
1138 }
1139 #endif
1140 
1141 /*
1142  * ctag_slist --
1143  *        Search the list of tags files for a tag, and return tag queue.
1144  */
1145 static TAGQ *
ctag_slist(SCR * sp,CHAR_T * tag)1146 ctag_slist(SCR *sp, CHAR_T *tag)
1147 {
1148           EX_PRIVATE *exp;
1149           TAGF *tfp;
1150           TAGQ *tqp;
1151           size_t len;
1152           int echk;
1153           const char *np;
1154           size_t nlen;
1155 
1156           exp = EXP(sp);
1157 
1158           /* Allocate and initialize the tag queue structure. */
1159           INT2CHAR(sp, tag, STRLEN(tag) + 1, np, nlen);
1160           len = nlen - 1;
1161           CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1);
1162           TAILQ_INIT(&tqp->tagq);
1163           tqp->tag = tqp->buf;
1164           memcpy(tqp->tag, np, (tqp->tlen = len) + 1);
1165 
1166           /*
1167            * Find the tag, only display missing file messages once, and
1168            * then only if we didn't find the tag.
1169            */
1170           echk = 0;
1171           TAILQ_FOREACH(tfp, &exp->tagfq, q)
1172                     if (ctag_sfile(sp, tfp, tqp, tqp->tag)) {
1173                               echk = 1;
1174                               F_SET(tfp, TAGF_ERR);
1175                     } else
1176                               F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN);
1177 
1178           /* Check to see if we found anything. */
1179           if (TAILQ_EMPTY(&tqp->tagq)) {
1180                     msgq_str(sp, M_ERR, tqp->tag, "162|%s: tag not found");
1181                     if (echk)
1182                               TAILQ_FOREACH(tfp, &exp->tagfq, q)
1183                                         if (F_ISSET(tfp, TAGF_ERR) &&
1184                                             !F_ISSET(tfp, TAGF_ERR_WARN)) {
1185                                                   errno = tfp->errnum;
1186                                                   msgq_str(sp, M_SYSERR, tfp->name, "%s");
1187                                                   F_SET(tfp, TAGF_ERR_WARN);
1188                                         }
1189                     free(tqp);
1190                     return (NULL);
1191           }
1192 
1193           tqp->current = TAILQ_FIRST(&tqp->tagq);
1194           return (tqp);
1195 
1196 alloc_err:
1197           return (NULL);
1198 }
1199 
1200 /*
1201  * ctag_sfile --
1202  *        Search a tags file for a tag, adding any found to the tag queue.
1203  */
1204 static int
ctag_sfile(SCR * sp,TAGF * tfp,TAGQ * tqp,char * tname)1205 ctag_sfile(SCR *sp, TAGF *tfp, TAGQ *tqp, char *tname)
1206 {
1207           struct stat sb;
1208           TAG *tp;
1209           size_t dlen, nlen = 0, slen;
1210           int fd, i, nf1, nf2;
1211           char *back, *front, *map, *p, *search, *t;
1212           char *cname = NULL, *dname = NULL, *name = NULL;
1213           const CHAR_T *wp;
1214           size_t wlen;
1215           unsigned long tl;
1216 
1217           if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) {
1218                     tfp->errnum = errno;
1219                     return (1);
1220           }
1221 
1222           /*
1223            * XXX
1224            * Some old BSD systems require MAP_FILE as an argument when mapping
1225            * regular files.
1226            */
1227 #ifndef MAP_FILE
1228 #define   MAP_FILE  0
1229 #endif
1230           /*
1231            * XXX
1232            * We'd like to test if the file is too big to mmap.  Since we don't
1233            * know what size or type off_t's or size_t's are, what the largest
1234            * unsigned integral type is, or what random insanity the local C
1235            * compiler will perpetrate, doing the comparison in a portable way
1236            * is flatly impossible.  Hope mmap fails if the file is too large.
1237            */
1238           if (fstat(fd, &sb) != 0 ||
1239               (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE,
1240               MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) {
1241                     tfp->errnum = errno;
1242                     (void)close(fd);
1243                     return (1);
1244           }
1245 
1246           tl = O_VAL(sp, O_TAGLENGTH);
1247           front = map;
1248           back = front + sb.st_size;
1249           front = binary_search(tname, front, back);
1250           front = linear_search(tname, front, back, tl);
1251           if (front == NULL)
1252                     goto done;
1253 
1254           /*
1255            * Initialize and link in the tag structure(s).  The historic ctags
1256            * file format only permitted a single tag location per tag.  The
1257            * obvious extension to permit multiple tags locations per tag is to
1258            * output multiple records in the standard format.  Unfortunately,
1259            * this won't work correctly with historic ex/vi implementations,
1260            * because their binary search assumes that there's only one record
1261            * per tag, and so will use a random tag entry if there si more than
1262            * one.  This code handles either format.
1263            *
1264            * The tags file is in the following format:
1265            *
1266            *        <tag> <filename> <line number> | <pattern>
1267            *
1268            * Figure out how long everything is so we can allocate in one swell
1269            * foop, but discard anything that looks wrong.
1270            */
1271           for (;;) {
1272                     /* Nul-terminate the end of the line. */
1273                     for (p = front; p < back && *p != '\n'; ++p);
1274                     if (p == back || *p != '\n')
1275                               break;
1276                     *p = '\0';
1277 
1278                     /* Update the pointers for the next time. */
1279                     t = p + 1;
1280                     p = front;
1281                     front = t;
1282 
1283                     /* Break the line into tokens. */
1284                     for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i)
1285                               switch (i) {
1286                               case 0:                       /* Tag. */
1287                                         cname = t;
1288                                         break;
1289                               case 1:                       /* Filename. */
1290                                         name = t;
1291                                         nlen = strlen(name);
1292                                         break;
1293                               }
1294 
1295                     /* Check for corruption. */
1296                     if (i != 2 || p == NULL || t == NULL)
1297                               goto corrupt;
1298 
1299                     /* The rest of the string is the search pattern. */
1300                     search = p;
1301                     if ((slen = strlen(p)) == 0) {
1302 corrupt:            p = msg_print(sp, tname, &nf1);
1303                               t = msg_print(sp, tfp->name, &nf2);
1304                               msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t);
1305                               if (nf1)
1306                                         FREE_SPACE(sp, p, 0);
1307                               if (nf2)
1308                                         FREE_SPACE(sp, t, 0);
1309                               continue;
1310                     }
1311 
1312                     /* Check for passing the last entry. */
1313                     if (tl ? strncmp(tname, cname, tl) : strcmp(tname, cname))
1314                               break;
1315 
1316                     /* Resolve the file name. */
1317                     ctag_file(sp, tfp, name, &dname, &dlen);
1318 
1319                     CALLOC_GOTO(sp, tp,
1320                         TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 +
1321                         (slen + 1) * sizeof(CHAR_T));
1322                     tp->fname = (char *)tp->buf;
1323                     if (dlen != 0) {
1324                               memcpy(tp->fname, dname, dlen);
1325                               tp->fname[dlen] = '/';
1326                               ++dlen;
1327                     }
1328                     memcpy(tp->fname + dlen, name, nlen + 1);
1329                     tp->fnlen = dlen + nlen;
1330                     tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1);
1331                     CHAR2INT(sp, search, slen + 1, wp, wlen);
1332                     MEMCPYW(tp->search, wp, (tp->slen = slen) + 1);
1333                     TAILQ_INSERT_TAIL(&tqp->tagq, tp, q);
1334           }
1335 
1336 alloc_err:
1337 done:     if (munmap(map, (size_t)sb.st_size))
1338                     msgq(sp, M_SYSERR, "munmap");
1339           if (close(fd))
1340                     msgq(sp, M_SYSERR, "close");
1341           return (0);
1342 }
1343 
1344 /*
1345  * ctag_file --
1346  *        Search for the right path to this file.
1347  */
1348 static void
ctag_file(SCR * sp,TAGF * tfp,char * name,char ** dirp,size_t * dlenp)1349 ctag_file(SCR *sp, TAGF *tfp, char *name, char **dirp, size_t *dlenp)
1350 {
1351           struct stat sb;
1352           char *p, buf[MAXPATHLEN];
1353 
1354           /*
1355            * !!!
1356            * If the tag file path is a relative path, see if it exists.  If it
1357            * doesn't, look relative to the tags file path.  It's okay for a tag
1358            * file to not exist, and historically, vi simply displayed a "new"
1359            * file.  However, if the path exists relative to the tag file, it's
1360            * pretty clear what's happening, so we may as well get it right.
1361            */
1362           *dlenp = 0;
1363           if (name[0] != '/' &&
1364               stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) {
1365                     *p = '\0';
1366                     (void)snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name);
1367                     if (stat(buf, &sb) == 0) {
1368                               *dirp = tfp->name;
1369                               *dlenp = strlen(*dirp);
1370                     }
1371                     *p = '/';
1372           }
1373 }
1374 
1375 /*
1376  * Binary search for "string" in memory between "front" and "back".
1377  *
1378  * This routine is expected to return a pointer to the start of a line at
1379  * *or before* the first word matching "string".  Relaxing the constraint
1380  * this way simplifies the algorithm.
1381  *
1382  * Invariants:
1383  *        front points to the beginning of a line at or before the first
1384  *        matching string.
1385  *
1386  *        back points to the beginning of a line at or after the first
1387  *        matching line.
1388  *
1389  * Base of the Invariants.
1390  *        front = NULL;
1391  *        back = EOF;
1392  *
1393  * Advancing the Invariants:
1394  *
1395  *        p = first newline after halfway point from front to back.
1396  *
1397  *        If the string at "p" is not greater than the string to match,
1398  *        p is the new front.  Otherwise it is the new back.
1399  *
1400  * Termination:
1401  *
1402  *        The definition of the routine allows it return at any point,
1403  *        since front is always at or before the line to print.
1404  *
1405  *        In fact, it returns when the chosen "p" equals "back".  This
1406  *        implies that there exists a string is least half as long as
1407  *        (back - front), which in turn implies that a linear search will
1408  *        be no more expensive than the cost of simply printing a string or two.
1409  *
1410  *        Trying to continue with binary search at this point would be
1411  *        more trouble than it's worth.
1412  */
1413 #define   EQUAL               0
1414 #define   GREATER             1
1415 #define   LESS                (-1)
1416 
1417 #define   SKIP_PAST_NEWLINE(p, back)    while (p < back && *p++ != '\n') continue;
1418 
1419 static char *
binary_search(register char * string,register char * front,register char * back)1420 binary_search(register char *string, register char *front, register char *back)
1421 {
1422           register char *p;
1423 
1424           p = front + (back - front) / 2;
1425           SKIP_PAST_NEWLINE(p, back);
1426 
1427           while (p != back) {
1428                     if (compare(string, p, back) == GREATER)
1429                               front = p;
1430                     else
1431                               back = p;
1432                     p = front + (back - front) / 2;
1433                     SKIP_PAST_NEWLINE(p, back);
1434           }
1435           return (front);
1436 }
1437 
1438 /*
1439  * Find the first line that starts with string, linearly searching from front
1440  * to back.
1441  *
1442  * Return NULL for no such line.
1443  *
1444  * This routine assumes:
1445  *
1446  *        o front points at the first character in a line.
1447  *        o front is before or at the first line to be printed.
1448  */
1449 static char *
linear_search(char * string,char * front,char * back,unsigned long tl)1450 linear_search(char *string, char *front, char *back, unsigned long tl)
1451 {
1452           char *end;
1453           while (front < back) {
1454                     end = tl && (unsigned long)(back-front) > tl ? front+tl : back;
1455                     switch (compare(string, front, end)) {
1456                     case EQUAL:                   /* Found it. */
1457                               return (front);
1458                     case LESS:                    /* No such string. */
1459                               return (NULL);
1460                     case GREATER:                 /* Keep going. */
1461                               break;
1462                     }
1463                     SKIP_PAST_NEWLINE(front, back);
1464           }
1465           return (NULL);
1466 }
1467 
1468 /*
1469  * Return LESS, GREATER, or EQUAL depending on how the string1 compares
1470  * with string2 (s1 ??? s2).
1471  *
1472  *        o Matches up to len(s1) are EQUAL.
1473  *        o Matches up to len(s2) are GREATER.
1474  *
1475  * The string "s1" is null terminated.  The string s2 is '\t', space, (or
1476  * "back") terminated.
1477  *
1478  * !!!
1479  * Reasonably modern ctags programs use tabs as separators, not spaces.
1480  * However, historic programs did use spaces, and, I got complaints.
1481  */
1482 static int
compare(register char * s1,register char * s2,register char * back)1483 compare(register char *s1, register char *s2, register char *back)
1484 {
1485           for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2)
1486                     if (*s1 != *s2)
1487                               return (*s1 < *s2 ? LESS : GREATER);
1488           return (*s1 ? GREATER : s2 < back &&
1489               (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL);
1490 }
1491