xref: /openbsd/usr.bin/vi/ex/ex_tag.c (revision b7041c07)
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