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