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