jcs's openbsd hax
openbsd
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
34struct cstokens {
35 const char *fname;
36 const char *function;
37 const char *lineno;
38 const char *pattern;
39};
40
41struct csmatch {
42 TAILQ_ENTRY(csmatch) entry;
43 int lineno;
44};
45
46struct csrecord {
47 TAILQ_ENTRY(csrecord) entry;
48 char *filename;
49 TAILQ_HEAD(matches, csmatch) matches;
50};
51
52static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords);
53static struct csrecord *addentryr;
54static struct csrecord *currecord;
55static struct csmatch *curmatch;
56static const char *addentryfn;
57static 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
69static int addentry(struct buffer *, char *);
70static void csflush(void);
71static int do_cscope(int);
72static int csexists(const char *);
73static int getattr(char *, struct cstokens *);
74static int jumptomatch(void);
75static void prettyprint(struct buffer *, struct cstokens *);
76static const char *ltrim(const char *);
77
78/*
79 * Find this symbol. Bound to C-c s s
80 */
81int
82cssymbol(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 */
90int
91csdefinition(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 */
99int
100csfuncalled(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 */
108int
109cscallerfuncs(int f, int n)
110{
111 return (do_cscope(CSCALLERFUNCS));
112}
113
114/*
115 * Find this text. Bound to C-c s t
116 */
117int
118csfindtext(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 */
126int
127csegrep(int f, int n)
128{
129 return (do_cscope(CSEGREP));
130}
131
132/*
133 * Find this file. Bound to C-c s f
134 */
135int
136csfindfile(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 */
144int
145csfindinc(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 */
154int
155cscreatelist(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 */
224int
225csnextmatch(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 */
257int
258csprevmatch(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 */
287int
288csnextfile(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 */
309int
310csprevfile(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 */
333int
334jumptomatch(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 */
362int
363do_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 */
438int
439addentry(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);
481cleanup:
482 free(r);
483 return (FALSE);
484}
485
486/*
487 * Cscope line: <filename> <function> <lineno> <pattern>
488 */
489int
490getattr(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
518void
519prettyprint(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
529const char *
530ltrim(const char *s)
531{
532 while (isblank((unsigned char)*s))
533 s++;
534 return s;
535}
536
537void
538csflush(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 */
562int
563csexists(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 }
598cleanup:
599 free(pathc);
600 return (FALSE);
601}