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