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