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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 * 530 ltrim(const char *s) 531 { 532 while (isblank((unsigned char)*s)) 533 s++; 534 return s; 535 } 536 537 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 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