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