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