xref: /openbsd-src/usr.bin/mg/cscope.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: cscope.c,v 1.20 2021/03/01 10:51:14 lum Exp $	*/
2 
3 /*
4  * This file is in the public domain.
5  *
6  * Author: Sunil Nimmagadda <sunil@openbsd.org>
7  */
8 
9 #include <sys/queue.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <ctype.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <fnmatch.h>
16 #include <limits.h>
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include "def.h"
24 
25 #define CSSYMBOL      0
26 #define CSDEFINITION  1
27 #define CSCALLEDFUNCS 2
28 #define CSCALLERFUNCS 3
29 #define CSTEXT        4
30 #define CSEGREP       6
31 #define CSFINDFILE    7
32 #define CSINCLUDES    8
33 
34 struct cstokens {
35 	const char *fname;
36 	const char *function;
37 	const char *lineno;
38 	const char *pattern;
39 };
40 
41 struct csmatch {
42 	TAILQ_ENTRY(csmatch) entry;
43 	int lineno;
44 };
45 
46 struct csrecord {
47 	TAILQ_ENTRY(csrecord) entry;
48 	char *filename;
49 	TAILQ_HEAD(matches, csmatch) matches;
50 };
51 
52 static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords);
53 static struct csrecord *addentryr;
54 static struct csrecord *currecord;
55 static struct csmatch  *curmatch;
56 static const char      *addentryfn;
57 static const char      *csprompt[] = {
58 	"Find this symbol: ",
59 	"Find this global definition: ",
60 	"Find functions called by this function: ",
61 	"Find functions calling this function: ",
62 	"Find this text string: ",
63 	"Change this text string: ",
64 	"Find this egrep pattern: ",
65 	"Find this file: ",
66 	"Find files #including this file: "
67 };
68 
69 static int  addentry(struct buffer *, char *);
70 static void csflush(void);
71 static int  do_cscope(int);
72 static int  csexists(const char *);
73 static int  getattr(char *, struct cstokens *);
74 static int  jumptomatch(void);
75 static void prettyprint(struct buffer *, struct cstokens *);
76 static const char *ltrim(const char *);
77 
78 /*
79  * Find this symbol. Bound to C-c s s
80  */
81 /* ARGSUSED */
82 int
83 cssymbol(int f, int n)
84 {
85 	return (do_cscope(CSSYMBOL));
86 }
87 
88 /*
89  * Find this global definition. Bound to C-c s d
90  */
91 /* ARGSUSED */int
92 csdefinition(int f, int n)
93 {
94 	return (do_cscope(CSDEFINITION));
95 }
96 
97 /*
98  * Find functions called by this function. Bound to C-c s l
99  */
100 /* ARGSUSED */
101 int
102 csfuncalled(int f, int n)
103 {
104 	return (do_cscope(CSCALLEDFUNCS));
105 }
106 
107 /*
108  * Find functions calling this function. Bound to C-c s c
109  */
110 /* ARGSUSED */
111 int
112 cscallerfuncs(int f, int n)
113 {
114 	return (do_cscope(CSCALLERFUNCS));
115 }
116 
117 /*
118  * Find this text. Bound to C-c s t
119  */
120 /* ARGSUSED */
121 int
122 csfindtext(int f, int n)
123 {
124 	return (do_cscope(CSTEXT));
125 }
126 
127 /*
128  * Find this egrep pattern. Bound to C-c s e
129  */
130 /* ARGSUSED */
131 int
132 csegrep(int f, int n)
133 {
134 	return (do_cscope(CSEGREP));
135 }
136 
137 /*
138  * Find this file. Bound to C-c s f
139  */
140 /* ARGSUSED */
141 int
142 csfindfile(int f, int n)
143 {
144 	return (do_cscope(CSFINDFILE));
145 }
146 
147 /*
148  * Find files #including this file. Bound to C-c s i
149  */
150 /* ARGSUSED */
151 int
152 csfindinc(int f, int n)
153 {
154 	return (do_cscope(CSINCLUDES));
155 }
156 
157 /*
158  * Create list of files to index in the given directory
159  * using cscope-indexer.
160  */
161 /* ARGSUSED */
162 int
163 cscreatelist(int f, int n)
164 {
165 	struct buffer *bp;
166 	struct stat sb;
167 	FILE *fpipe;
168 	char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp;
169 	size_t sz;
170 	ssize_t len;
171 	int clen;
172 
173 	line = NULL;
174 	sz = 0;
175 
176 	if (getbufcwd(dir, sizeof(dir)) == FALSE)
177 		dir[0] = '\0';
178 
179 	bufp = eread("Index files in directory: ", dir,
180 	    sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL);
181 
182 	if (bufp == NULL)
183 		return (ABORT);
184 	else if (bufp[0] == '\0')
185 		return (FALSE);
186 
187 	if (stat(dir, &sb) == -1)
188 		return(dobeep_msgs("stat: %s", strerror(errno)));
189 	else if (S_ISDIR(sb.st_mode) == 0)
190 		return(dobeep_msgs("%s: Not a directory", dir));
191 
192 	if (csexists("cscope-indexer") == FALSE)
193 		return(dobeep_msg("no such file or directory, cscope-indexer"));
194 
195 	clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir);
196 	if (clen < 0 || clen >= sizeof(cmd))
197 		return (FALSE);
198 
199 	if ((fpipe = popen(cmd, "r")) == NULL)
200 		return(dobeep_msg("problem opening pipe"));
201 
202 	bp = bfind("*cscope*", TRUE);
203 	if (bclear(bp) != TRUE) {
204 		pclose(fpipe);
205 		return (FALSE);
206 	}
207 	bp->b_flag |= BFREADONLY;
208 
209 	clen = snprintf(title, sizeof(title), "%s%s",
210 	    "Creating cscope file list 'cscope.files' in: ", dir);
211 	if (clen < 0 || clen >= sizeof(title)) {
212 		pclose(fpipe);
213 		return (FALSE);
214 	}
215 	addline(bp, title);
216 	addline(bp, "");
217 	while ((len = getline(&line, &sz, fpipe)) != -1) {
218 		if (line[len - 1] == *bp->b_nlchr)
219 			line[len - 1] = '\0';
220 		addline(bp, line);
221 	}
222 	free(line);
223 	if (ferror(fpipe))
224 		ewprintf("Problem reading pipe");
225 	pclose(fpipe);
226 	return (popbuftop(bp, WNONE));
227 }
228 
229 /*
230  * Next Symbol. Bound to C-c s n
231  */
232 /* ARGSUSED */
233 int
234 csnextmatch(int f, int n)
235 {
236 	struct csrecord *r;
237 	struct csmatch *m;
238 
239 	if (curmatch == NULL) {
240 		if ((r = TAILQ_FIRST(&csrecords)) == NULL)
241 			return(dobeep_msg("The *cscope* buffer does "
242 			    "not exist yet"));
243 
244 		currecord = r;
245 		curmatch = TAILQ_FIRST(&r->matches);
246 	} else {
247 		m = TAILQ_NEXT(curmatch, entry);
248 		if (m == NULL) {
249 			r = TAILQ_NEXT(currecord, entry);
250 			if (r == NULL) {
251 				return(dobeep_msg("The end of *cscope* buffer "
252 				    "has been reached"));
253 			} else {
254 				currecord = r;
255 				curmatch = TAILQ_FIRST(&currecord->matches);
256 			}
257 		} else
258 			curmatch = m;
259 	}
260 	return (jumptomatch());
261 }
262 
263 /*
264  * Previous Symbol. Bound to C-c s p
265  */
266 /* ARGSUSED */
267 int
268 csprevmatch(int f, int n)
269 {
270 	struct csmatch *m;
271 	struct csrecord *r;
272 
273 	if (curmatch == NULL)
274 		return (FALSE);
275 	else {
276 		m  = TAILQ_PREV(curmatch, matches, entry);
277 		if (m)
278 			curmatch = m;
279 		else {
280 			r = TAILQ_PREV(currecord, csrecords, entry);
281 			if (r == NULL) {
282 				return(dobeep_msg("The beginning of *cscope* "
283 				    "buffer has been reached"));
284 			} else {
285 				currecord = r;
286 				curmatch = TAILQ_LAST(&currecord->matches,
287 				    matches);
288 			}
289 		}
290 	}
291 	return (jumptomatch());
292 }
293 
294 /*
295  * Next file.
296  */
297 int
298 csnextfile(int f, int n)
299 {
300 	struct csrecord *r;
301 
302 	if (curmatch == NULL) {
303 		if ((r = TAILQ_FIRST(&csrecords)) == NULL)
304 			return(dobeep_msg("The *cscope* buffer does not "
305 			    "exist yet"));
306 	} else {
307 		if ((r = TAILQ_NEXT(currecord, entry)) == NULL)
308 			return(dobeep_msg("The end of *cscope* buffer has "
309 			    "been reached"));
310 	}
311 	currecord = r;
312 	curmatch = TAILQ_FIRST(&currecord->matches);
313 	return (jumptomatch());
314 }
315 
316 /*
317  * Previous file.
318  */
319 int
320 csprevfile(int f, int n)
321 {
322 	struct csrecord *r;
323 
324 	if (curmatch == NULL) {
325 		if ((r = TAILQ_FIRST(&csrecords)) == NULL)
326 			return(dobeep_msg("The *cscope* buffer does not"
327 			    "exist yet"));
328 	} else {
329 		if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL)
330 			return(dobeep_msg("The beginning of *cscope* buffer "
331 			    "has been reached"));
332 	}
333 	currecord = r;
334 	curmatch = TAILQ_FIRST(&currecord->matches);
335 	return (jumptomatch());
336 }
337 
338 /*
339  * The current symbol location is extracted from currecord->filename and
340  * curmatch->lineno. Load the file similar to filevisit and goto the
341  * lineno recorded.
342  */
343 int
344 jumptomatch(void)
345 {
346 	struct buffer *bp;
347 	char *adjf;
348 
349 	if (curmatch == NULL || currecord == NULL)
350 		return (FALSE);
351 	adjf = adjustname(currecord->filename, TRUE);
352 	if (adjf == NULL)
353 		return (FALSE);
354 	if ((bp = findbuffer(adjf)) == NULL)
355 		return (FALSE);
356 	curbp = bp;
357 	if (showbuffer(bp, curwp, WFFULL) != TRUE)
358 		return (FALSE);
359 	if (bp->b_fname[0] == '\0') {
360 		if (readin(adjf) != TRUE)
361 			killbuffer(bp);
362 	}
363 	gotoline(FFARG, curmatch->lineno);
364 	return (TRUE);
365 }
366 
367 /*
368  * Ask for the symbol, construct cscope commandline with the symbol
369  * and passed in index. Popen cscope, read the output into *cscope*
370  * buffer and pop it.
371  */
372 int
373 do_cscope(int i)
374 {
375 	struct buffer *bp;
376 	FILE *fpipe;
377 	char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ];
378 	char *p, *buf;
379 	int clen, nores = 0;
380 	size_t sz;
381 	ssize_t len;
382 
383 	buf = NULL;
384 	sz = 0;
385 
386 	/* If current buffer isn't a source file just return */
387 	if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0)
388 		return(dobeep_msg("C-c s not defined"));
389 
390 	if (curtoken(0, 1, pattern) == FALSE)
391 		return (FALSE);
392 	p = eread("%s", pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF, csprompt[i]);
393 	if (p == NULL)
394 		return (ABORT);
395 	else if (p[0] == '\0')
396 		return (FALSE);
397 
398 	if (csexists("cscope") == FALSE)
399 		return(dobeep_msg("no such file or directory, cscope"));
400 
401 	csflush();
402 	clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null",
403 	    i, pattern);
404 	if (clen < 0 || clen >= sizeof(cmd))
405 		return (FALSE);
406 
407 	if ((fpipe = popen(cmd, "r")) == NULL)
408 		return(dobeep_msg("problem opening pipe"));
409 
410 	bp = bfind("*cscope*", TRUE);
411 	if (bclear(bp) != TRUE) {
412 		pclose(fpipe);
413 		return (FALSE);
414 	}
415 	bp->b_flag |= BFREADONLY;
416 
417 	clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern);
418 	if (clen < 0 || clen >= sizeof(title)) {
419 		pclose(fpipe);
420 		return (FALSE);
421 	}
422 	addline(bp, title);
423 	addline(bp, "");
424 	addline(bp, "-------------------------------------------------------------------------------");
425 	while ((len = getline(&buf, &sz, fpipe)) != -1) {
426 		if (buf[len - 1] == *bp->b_nlchr)
427 			buf[len - 1] = '\0';
428 		if (addentry(bp, buf) != TRUE) {
429 			free(buf);
430 			return (FALSE);
431 		}
432 		nores = 1;
433 	}
434 	free(buf);
435 	if (ferror(fpipe))
436 		ewprintf("Problem reading pipe");
437 	pclose(fpipe);
438 	addline(bp, "-------------------------------------------------------------------------------");
439 	if (nores == 0)
440 		ewprintf("No matches were found.");
441 	return (popbuftop(bp, WNONE));
442 }
443 
444 /*
445  * For each line read from cscope output, extract the tokens,
446  * add them to list and pretty print a line in *cscope* buffer.
447  */
448 int
449 addentry(struct buffer *bp, char *csline)
450 {
451 	struct csrecord *r;
452 	struct csmatch *m;
453 	struct cstokens t;
454 	int lineno;
455 	char buf[BUFSIZ];
456 	const char *errstr;
457 
458 	r = NULL;
459 	if (getattr(csline, &t) == FALSE)
460 		return (FALSE);
461 
462 	lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr);
463 	if (errstr)
464 		return (FALSE);
465 
466 	if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) {
467 		if ((r = malloc(sizeof(struct csrecord))) == NULL)
468 			return (FALSE);
469 		addentryr = r;
470 		if ((r->filename = strndup(t.fname, NFILEN)) == NULL)
471 			goto cleanup;
472 		addentryfn = r->filename;
473 		TAILQ_INIT(&r->matches);
474 		if ((m = malloc(sizeof(struct csmatch))) == NULL)
475 			goto cleanup;
476 		m->lineno = lineno;
477 		TAILQ_INSERT_TAIL(&r->matches, m, entry);
478 		TAILQ_INSERT_TAIL(&csrecords, r, entry);
479 		addline(bp, "");
480 		if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0)
481 			goto cleanup;
482 		addline(bp, buf);
483 	} else {
484 		if ((m = malloc(sizeof(struct csmatch))) == NULL)
485 			goto cleanup;
486 		m->lineno = lineno;
487 		TAILQ_INSERT_TAIL(&addentryr->matches, m, entry);
488 	}
489 	prettyprint(bp, &t);
490 	return (TRUE);
491 cleanup:
492 	free(r);
493 	return (FALSE);
494 }
495 
496 /*
497  * Cscope line: <filename> <function> <lineno> <pattern>
498  */
499 int
500 getattr(char *line, struct cstokens *t)
501 {
502 	char *p;
503 
504 	if ((p = strchr(line, ' ')) == NULL)
505 		return (FALSE);
506 	*p++ = '\0';
507 	t->fname = line;
508 	line = p;
509 
510 	if ((p = strchr(line, ' ')) == NULL)
511 		return (FALSE);
512 	*p++ = '\0';
513 	t->function = line;
514 	line = p;
515 
516 	if ((p = strchr(line, ' ')) == NULL)
517 		return (FALSE);
518 	*p++ = '\0';
519 	t->lineno = line;
520 
521 	if (*p == '\0')
522 		return (FALSE);
523 	t->pattern = p;
524 
525 	return (TRUE);
526 }
527 
528 void
529 prettyprint(struct buffer *bp, struct cstokens *t)
530 {
531 	char buf[BUFSIZ];
532 
533 	if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s",
534 	    t->function, t->lineno, ltrim(t->pattern)) < 0)
535 		return;
536 	addline(bp, buf);
537 }
538 
539 const char *
540 ltrim(const char *s)
541 {
542 	while (isblank((unsigned char)*s))
543 		s++;
544 	return s;
545 }
546 
547 void
548 csflush(void)
549 {
550 	struct csrecord *r;
551 	struct csmatch *m;
552 
553 	while ((r = TAILQ_FIRST(&csrecords)) != NULL) {
554 		free(r->filename);
555 		while ((m = TAILQ_FIRST(&r->matches)) != NULL) {
556 			TAILQ_REMOVE(&r->matches, m, entry);
557 			free(m);
558 		}
559 		TAILQ_REMOVE(&csrecords, r, entry);
560 		free(r);
561 	}
562 	addentryr = NULL;
563 	addentryfn = NULL;
564 	currecord = NULL;
565 	curmatch = NULL;
566 }
567 
568 /*
569  * Check if the cmd exists in $PATH. Split on ":" and iterate through
570  * all paths in $PATH.
571  */
572 int
573 csexists(const char *cmd)
574 {
575 	char fname[NFILEN], *dir, *path, *pathc, *tmp;
576 	int  len, dlen;
577 
578 	/* Special case if prog contains '/' */
579 	if (strchr(cmd, '/')) {
580 		if (access(cmd, F_OK) == -1)
581 			return (FALSE);
582 		else
583 			return (TRUE);
584 	}
585 	if ((tmp = getenv("PATH")) == NULL)
586 		return (FALSE);
587 	if ((pathc = path = strndup(tmp, NFILEN)) == NULL)
588 		return(dobeep_msg("out of memory"));
589 
590 	while ((dir = strsep(&path, ":")) != NULL) {
591 		if (*dir == '\0')
592 			continue;
593 
594 		dlen = strlen(dir);
595 		while (dlen > 0 && dir[dlen-1] == '/')
596 			dir[--dlen] = '\0';     /* strip trailing '/' */
597 
598 		len = snprintf(fname, sizeof(fname), "%s/%s", dir, cmd);
599 		if (len < 0 || len >= sizeof(fname)) {
600 			(void)dobeep_msg("path too long");
601 			goto cleanup;
602 		}
603 		if(access(fname, F_OK) == 0) {
604 			free(pathc);
605 			return (TRUE);
606 		}
607 	}
608 cleanup:
609 	free(pathc);
610 	return (FALSE);
611 }
612