xref: /openbsd-src/usr.bin/mg/cscope.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: cscope.c,v 1.16 2016/01/19 14:51:00 sunil 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 len;
170 	int clen;
171 
172 	if (getbufcwd(dir, sizeof(dir)) == FALSE)
173 		dir[0] = '\0';
174 
175 	bufp = eread("Index files in directory: ", dir,
176 	    sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL);
177 
178 	if (bufp == NULL)
179 		return (ABORT);
180 	else if (bufp[0] == '\0')
181 		return (FALSE);
182 
183 	if (stat(dir, &sb) == -1) {
184 		dobeep();
185 		ewprintf("stat: %s", strerror(errno));
186 		return (FALSE);
187 	} else if (S_ISDIR(sb.st_mode) == 0) {
188 		dobeep();
189 		ewprintf("%s: Not a directory", dir);
190 		return (FALSE);
191 	}
192 
193 	if (csexists("cscope-indexer") == FALSE) {
194 		dobeep();
195 		ewprintf("no such file or directory, cscope-indexer");
196 		return (FALSE);
197 	}
198 
199 	clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir);
200 	if (clen < 0 || clen >= sizeof(cmd))
201 		return (FALSE);
202 
203 	if ((fpipe = popen(cmd, "r")) == NULL) {
204 		dobeep();
205 		ewprintf("problem opening pipe");
206 		return (FALSE);
207 	}
208 
209 	bp = bfind("*cscope*", TRUE);
210 	if (bclear(bp) != TRUE) {
211 		pclose(fpipe);
212 		return (FALSE);
213 	}
214 	bp->b_flag |= BFREADONLY;
215 
216 	clen = snprintf(title, sizeof(title), "%s%s",
217 	    "Creating cscope file list 'cscope.files' in: ", dir);
218 	if (clen < 0 || clen >= sizeof(title)) {
219 		pclose(fpipe);
220 		return (FALSE);
221 	}
222 	addline(bp, title);
223 	addline(bp, "");
224 	/* All lines are NUL terminated */
225 	while ((line = fgetln(fpipe, &len)) != NULL) {
226 		line[len - 1] = '\0';
227 		addline(bp, line);
228 	}
229 	pclose(fpipe);
230 	return (popbuftop(bp, WNONE));
231 }
232 
233 /*
234  * Next Symbol. Bound to C-c s n
235  */
236 /* ARGSUSED */
237 int
238 csnextmatch(int f, int n)
239 {
240 	struct csrecord *r;
241 	struct csmatch *m;
242 
243 	if (curmatch == NULL) {
244 		if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
245 			dobeep();
246 			ewprintf("The *cscope* buffer does not exist yet");
247 			return (FALSE);
248 		}
249 		currecord = r;
250 		curmatch = TAILQ_FIRST(&r->matches);
251 	} else {
252 		m = TAILQ_NEXT(curmatch, entry);
253 		if (m == NULL) {
254 			r = TAILQ_NEXT(currecord, entry);
255 			if (r == NULL) {
256 				dobeep();
257 				ewprintf("The end of *cscope* buffer has been"
258 				    " reached");
259 				return (FALSE);
260 			} else {
261 				currecord = r;
262 				curmatch = TAILQ_FIRST(&currecord->matches);
263 			}
264 		} else
265 			curmatch = m;
266 	}
267 	return (jumptomatch());
268 }
269 
270 /*
271  * Previous Symbol. Bound to C-c s p
272  */
273 /* ARGSUSED */
274 int
275 csprevmatch(int f, int n)
276 {
277 	struct csmatch *m;
278 	struct csrecord *r;
279 
280 	if (curmatch == NULL)
281 		return (FALSE);
282 	else {
283 		m  = TAILQ_PREV(curmatch, matches, entry);
284 		if (m)
285 			curmatch = m;
286 		else {
287 			r = TAILQ_PREV(currecord, csrecords, entry);
288 			if (r == NULL) {
289 				dobeep();
290 				ewprintf("The beginning of *cscope* buffer has"
291 				    " been reached");
292 				return (FALSE);
293 			} else {
294 				currecord = r;
295 				curmatch = TAILQ_LAST(&currecord->matches,
296 				    matches);
297 			}
298 		}
299 	}
300 	return (jumptomatch());
301 }
302 
303 /*
304  * Next file.
305  */
306 int
307 csnextfile(int f, int n)
308 {
309 	struct csrecord *r;
310 
311 	if (curmatch == NULL) {
312 		if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
313 			dobeep();
314 			ewprintf("The *cscope* buffer does not exist yet");
315 			return (FALSE);
316 		}
317 
318 	} else {
319 		if ((r = TAILQ_NEXT(currecord, entry)) == NULL) {
320 			dobeep();
321 			ewprintf("The end of *cscope* buffer has been reached");
322 			return (FALSE);
323 		}
324 	}
325 	currecord = r;
326 	curmatch = TAILQ_FIRST(&currecord->matches);
327 	return (jumptomatch());
328 }
329 
330 /*
331  * Previous file.
332  */
333 int
334 csprevfile(int f, int n)
335 {
336 	struct csrecord *r;
337 
338 	if (curmatch == NULL) {
339 		if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
340 			dobeep();
341 			ewprintf("The *cscope* buffer does not exist yet");
342 			return (FALSE);
343 		}
344 
345 	} else {
346 		if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL) {
347 			dobeep();
348 			ewprintf("The beginning of *cscope* buffer has been"
349 			    " reached");
350 			return (FALSE);
351 		}
352 	}
353 	currecord = r;
354 	curmatch = TAILQ_FIRST(&currecord->matches);
355 	return (jumptomatch());
356 }
357 
358 /*
359  * The current symbol location is extracted from currecord->filename and
360  * curmatch->lineno. Load the file similar to filevisit and goto the
361  * lineno recorded.
362  */
363 int
364 jumptomatch(void)
365 {
366 	struct buffer *bp;
367 	char *adjf;
368 
369 	if (curmatch == NULL || currecord == NULL)
370 		return (FALSE);
371 	adjf = adjustname(currecord->filename, TRUE);
372 	if (adjf == NULL)
373 		return (FALSE);
374 	if ((bp = findbuffer(adjf)) == NULL)
375 		return (FALSE);
376 	curbp = bp;
377 	if (showbuffer(bp, curwp, WFFULL) != TRUE)
378 		return (FALSE);
379 	if (bp->b_fname[0] == '\0') {
380 		if (readin(adjf) != TRUE)
381 			killbuffer(bp);
382 	}
383 	gotoline(FFARG, curmatch->lineno);
384 	return (TRUE);
385 }
386 
387 /*
388  * Ask for the symbol, construct cscope commandline with the symbol
389  * and passed in index. Popen cscope, read the output into *cscope*
390  * buffer and pop it.
391  */
392 int
393 do_cscope(int i)
394 {
395 	struct buffer *bp;
396 	FILE *fpipe;
397 	char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ];
398 	char *p, *buf;
399 	int clen, nores = 0;
400 	size_t len;
401 
402 	/* If current buffer isn't a source file just return */
403 	if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0) {
404 		dobeep();
405 		ewprintf("C-c s not defined");
406 		return (FALSE);
407 	}
408 
409 	if (curtoken(0, 1, pattern) == FALSE)
410 		return (FALSE);
411 	p = eread("%s", pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF, csprompt[i]);
412 	if (p == NULL)
413 		return (ABORT);
414 	else if (p[0] == '\0')
415 		return (FALSE);
416 
417 	if (csexists("cscope") == FALSE) {
418 		dobeep();
419 		ewprintf("no such file or directory, cscope");
420 		return (FALSE);
421 	}
422 
423 	csflush();
424 	clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null",
425 	    i, pattern);
426 	if (clen < 0 || clen >= sizeof(cmd))
427 		return (FALSE);
428 
429 	if ((fpipe = popen(cmd, "r")) == NULL) {
430 		dobeep();
431 		ewprintf("problem opening pipe");
432 		return (FALSE);
433 	}
434 
435 	bp = bfind("*cscope*", TRUE);
436 	if (bclear(bp) != TRUE) {
437 		pclose(fpipe);
438 		return (FALSE);
439 	}
440 	bp->b_flag |= BFREADONLY;
441 
442 	clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern);
443 	if (clen < 0 || clen >= sizeof(title)) {
444 		pclose(fpipe);
445 		return (FALSE);
446 	}
447 	addline(bp, title);
448 	addline(bp, "");
449 	addline(bp, "-------------------------------------------------------------------------------");
450 	/* All lines are NUL terminated */
451 	while ((buf = fgetln(fpipe, &len)) != NULL) {
452 		buf[len - 1] = '\0';
453 		if (addentry(bp, buf) != TRUE)
454 			return (FALSE);
455 		nores = 1;
456 	};
457 	pclose(fpipe);
458 	addline(bp, "-------------------------------------------------------------------------------");
459 	if (nores == 0)
460 		ewprintf("No matches were found.");
461 	return (popbuftop(bp, WNONE));
462 }
463 
464 /*
465  * For each line read from cscope output, extract the tokens,
466  * add them to list and pretty print a line in *cscope* buffer.
467  */
468 int
469 addentry(struct buffer *bp, char *csline)
470 {
471 	struct csrecord *r;
472 	struct csmatch *m;
473 	struct cstokens t;
474 	int lineno;
475 	char buf[BUFSIZ];
476 	const char *errstr;
477 
478 	r = NULL;
479 	if (getattr(csline, &t) == FALSE)
480 		return (FALSE);
481 
482 	lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr);
483 	if (errstr)
484 		return (FALSE);
485 
486 	if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) {
487 		if ((r = malloc(sizeof(struct csrecord))) == NULL)
488 			return (FALSE);
489 		addentryr = r;
490 		if ((r->filename = strndup(t.fname, NFILEN)) == NULL)
491 			goto cleanup;
492 		addentryfn = r->filename;
493 		TAILQ_INIT(&r->matches);
494 		if ((m = malloc(sizeof(struct csmatch))) == NULL)
495 			goto cleanup;
496 		m->lineno = lineno;
497 		TAILQ_INSERT_TAIL(&r->matches, m, entry);
498 		TAILQ_INSERT_TAIL(&csrecords, r, entry);
499 		addline(bp, "");
500 		if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0)
501 			goto cleanup;
502 		addline(bp, buf);
503 	} else {
504 		if ((m = malloc(sizeof(struct csmatch))) == NULL)
505 			goto cleanup;
506 		m->lineno = lineno;
507 		TAILQ_INSERT_TAIL(&addentryr->matches, m, entry);
508 	}
509 	prettyprint(bp, &t);
510 	return (TRUE);
511 cleanup:
512 	free(r);
513 	return (FALSE);
514 }
515 
516 /*
517  * Cscope line: <filename> <function> <lineno> <pattern>
518  */
519 int
520 getattr(char *line, struct cstokens *t)
521 {
522 	char *p;
523 
524 	if ((p = strchr(line, ' ')) == NULL)
525 		return (FALSE);
526 	*p++ = '\0';
527 	t->fname = line;
528 	line = p;
529 
530 	if ((p = strchr(line, ' ')) == NULL)
531 		return (FALSE);
532 	*p++ = '\0';
533 	t->function = line;
534 	line = p;
535 
536 	if ((p = strchr(line, ' ')) == NULL)
537 		return (FALSE);
538 	*p++ = '\0';
539 	t->lineno = line;
540 
541 	if (*p == '\0')
542 		return (FALSE);
543 	t->pattern = p;
544 
545 	return (TRUE);
546 }
547 
548 void
549 prettyprint(struct buffer *bp, struct cstokens *t)
550 {
551 	char buf[BUFSIZ];
552 
553 	if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s",
554 	    t->function, t->lineno, ltrim(t->pattern)) < 0)
555 		return;
556 	addline(bp, buf);
557 }
558 
559 const char *
560 ltrim(const char *s)
561 {
562 	while (isblank((unsigned char)*s))
563 		s++;
564 	return s;
565 }
566 
567 void
568 csflush(void)
569 {
570 	struct csrecord *r;
571 	struct csmatch *m;
572 
573 	while ((r = TAILQ_FIRST(&csrecords)) != NULL) {
574 		free(r->filename);
575 		while ((m = TAILQ_FIRST(&r->matches)) != NULL) {
576 			TAILQ_REMOVE(&r->matches, m, entry);
577 			free(m);
578 		}
579 		TAILQ_REMOVE(&csrecords, r, entry);
580 		free(r);
581 	}
582 	addentryr = NULL;
583 	addentryfn = NULL;
584 	currecord = NULL;
585 	curmatch = NULL;
586 }
587 
588 /*
589  * Check if the cmd exists in $PATH. Split on ":" and iterate through
590  * all paths in $PATH.
591  */
592 int
593 csexists(const char *cmd)
594 {
595 	char fname[NFILEN], *dir, *path, *pathc, *tmp;
596 	int  len, dlen;
597 
598 	/* Special case if prog contains '/' */
599 	if (strchr(cmd, '/')) {
600 		if (access(cmd, F_OK) == -1)
601 			return (FALSE);
602 		else
603 			return (TRUE);
604 	}
605 	if ((tmp = getenv("PATH")) == NULL)
606 		return (FALSE);
607 	if ((pathc = path = strndup(tmp, NFILEN)) == NULL) {
608 		dobeep();
609 		ewprintf("out of memory");
610 		return (FALSE);
611 	}
612 	while ((dir = strsep(&path, ":")) != NULL) {
613 		if (*dir == '\0')
614 			continue;
615 
616 		dlen = strlen(dir);
617 		while (dlen > 0 && dir[dlen-1] == '/')
618 			dir[--dlen] = '\0';     /* strip trailing '/' */
619 
620 		len = snprintf(fname, sizeof(fname), "%s/%s", dir, cmd);
621 		if (len == -1 || len >= sizeof(fname)) {
622 			dobeep();
623 			ewprintf("path too long");
624 			goto cleanup;
625 		}
626 		if(access(fname, F_OK) == 0) {
627 			free(pathc);
628 			return (TRUE);
629 		}
630 	}
631 cleanup:
632 	free(pathc);
633 	return (FALSE);
634 }
635