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