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