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